From 1d990a536b46fb14384200f9015d73c33689626c Mon Sep 17 00:00:00 2001 From: Raghu Simha Date: Thu, 25 Apr 2019 02:35:41 -0400 Subject: [PATCH] First pass of prettier across entire codebase --- 3p/3d-gltf/index.js | 33 +- 3p/3d-gltf/viewer.js | 88 +- 3p/3p.js | 28 +- 3p/ampcontext-integration.js | 31 +- 3p/ampcontext-lib.js | 3 - 3p/ampcontext.js | 80 +- 3p/beopinion.js | 2 +- 3p/bodymovinanimation.js | 23 +- 3p/embedly.js | 9 +- 3p/environment.js | 14 +- 3p/facebook.js | 71 +- 3p/frame-metadata.js | 13 +- 3p/github.js | 13 +- 3p/iframe-messaging-client.js | 15 +- 3p/iframe-transport-client.js | 99 +- 3p/integration.js | 90 +- 3p/mathml.js | 73 +- 3p/messaging.js | 18 +- 3p/polyfills.js | 1 - 3p/recaptcha.js | 111 +- 3p/reddit.js | 5 +- 3p/twitter.js | 32 +- 3p/viqeoplayer.js | 29 +- 3p/yotpo.js | 31 +- ads/_a4a-config.js | 22 +- ads/_config.js | 113 +- ads/_ping_.js | 13 +- ads/a9.js | 109 +- ads/adagio.js | 1 - ads/adblade.js | 4 +- ads/adbutler.js | 18 +- ads/adfox.js | 5 +- ads/adgeneration.js | 42 +- ads/adhese.js | 42 +- ads/adition.js | 8 +- ads/admanmedia.js | 21 +- ads/adocean.js | 59 +- ads/adpicker.js | 12 +- ads/adplugg.js | 46 +- ads/adreactor.js | 20 +- ads/adsensor.js | 7 +- ads/adspeed.js | 8 +- ads/adspirit.js | 6 +- ads/adtech.js | 23 +- ads/adthrive.js | 8 +- ads/adunity.js | 58 +- ads/aduptech.js | 1 - ads/adventive.js | 82 +- ads/adverticum.js | 1 - ads/advertserve.js | 19 +- ads/aja.js | 10 +- ads/alp/handler.js | 36 +- ads/appnexus.js | 5 +- ads/appvador.js | 19 +- ads/atomx.js | 1 - ads/baidu.js | 31 +- ads/bidtellect.js | 3 +- ads/brainy.js | 10 +- ads/bringhub.js | 17 +- ads/broadstreetads.js | 8 +- ads/caajainfeed.js | 50 +- ads/capirs.js | 8 +- ads/caprofitx.js | 1 - ads/cedato.js | 18 +- ads/chargeads.js | 5 +- ads/colombia.js | 12 +- ads/connatix.js | 11 +- ads/contentad.js | 18 +- ads/criteo.js | 14 +- ads/dable.js | 14 +- ads/directadvert.js | 22 +- ads/distroscale.js | 12 +- ads/eadv.js | 5 +- ads/engageya.js | 5 +- ads/epeex.js | 5 +- ads/eplanning.js | 7 +- ads/ezoic.js | 16 +- ads/f1e.js | 2 +- ads/f1h.js | 8 +- ads/felmat.js | 5 +- ads/flite.js | 29 +- ads/fluct.js | 12 +- ads/freewheel.js | 24 +- ads/fusion.js | 37 +- ads/giraff.js | 24 +- ads/google/a4a/experiment-utils.js | 31 +- .../a4a/line-delimited-response-handler.js | 62 +- .../a4a/shared/content-recommendation.js | 101 +- .../test/test-content-recommendation.js | 409 +- .../a4a/shared/test/test-url-builder.js | 5 +- ads/google/a4a/shared/url-builder.js | 27 +- .../test-line-delimited-response-handler.js | 112 +- .../a4a/test/test-traffic-experiments.js | 3 +- ads/google/a4a/test/test-utils.js | 518 +- ads/google/a4a/traffic-experiments.js | 39 +- ads/google/a4a/utils.js | 439 +- ads/google/adsense-amp-auto-ads-responsive.js | 18 +- ads/google/adsense.js | 77 +- ads/google/csa.js | 82 +- ads/google/doubleclick.js | 10 +- ads/google/ima-player-data.js | 1 - ads/google/imaVideo.js | 251 +- .../test-adsense-amp-auto-ads-responsive.js | 79 +- ads/google/test/test-adsense.js | 33 +- ads/google/test/test-utils.js | 127 +- ads/google/utils.js | 91 +- ads/gumgum.js | 45 +- ads/holder.js | 4 +- ads/ibillboard.js | 1 - ads/imedia.js | 71 +- ads/imonomy.js | 44 +- ads/improvedigital.js | 29 +- ads/inabox/frame-overlay-helper.js | 77 +- ads/inabox/frame-overlay-manager.js | 27 +- ads/inabox/inabox-host.js | 11 +- ads/inabox/inabox-messaging-host.js | 132 +- ads/inabox/position-observer.js | 73 +- ads/inabox/util.js | 2 - ads/inmobi.js | 4 +- ads/innity.js | 12 +- ads/ix.js | 28 +- ads/jubna.js | 6 +- ads/kargo.js | 54 +- ads/kiosked.js | 26 +- ads/kixer.js | 9 +- ads/kuadio.js | 7 +- ads/lockerdome.js | 6 +- ads/mantis.js | 24 +- ads/mediaimpact.js | 44 +- ads/medianet.js | 96 +- ads/medyanet.js | 8 +- ads/meg.js | 17 +- ads/miximedia.js | 13 +- ads/mixpo.js | 12 +- ads/nativo.js | 59 +- ads/navegg.js | 3 +- ads/netletix.js | 52 +- ads/nokta.js | 5 +- ads/onnetwork.js | 5 +- ads/openadstream.js | 12 +- ads/openx.js | 18 +- ads/outbrain.js | 10 +- ads/pixels.js | 21 +- ads/plista.js | 34 +- ads/popin.js | 11 +- ads/postquare.js | 1 - ads/pressboard.js | 15 +- ads/pubexchange.js | 1 - ads/pubguru.js | 8 +- ads/pubmine.js | 19 +- ads/pulsepoint.js | 35 +- ads/purch.js | 8 +- ads/rbinfox.js | 8 +- ads/recomad.js | 14 +- ads/relap.js | 4 +- ads/revcontent.js | 10 +- ads/revjet.js | 13 +- ads/rubicon.js | 28 +- ads/runative.js | 38 +- ads/sas.js | 10 +- ads/sekindo.js | 30 +- ads/slimcutmedia.js | 14 +- ads/smartclip.js | 20 +- ads/smi2.js | 13 +- ads/sortable.js | 11 +- ads/sovrn.js | 2 +- ads/speakol.js | 23 +- ads/spotx.js | 4 +- ads/sunmedia.js | 12 +- ads/svknative.js | 5 +- ads/swoop.js | 42 +- ads/taboola.js | 38 +- ads/tcsemotion.js | 6 +- ads/teads.js | 18 +- ads/torimochi.js | 4 +- ads/uas.js | 29 +- ads/ucfunnel.js | 1 - ads/unruly.js | 1 - ads/uzou.js | 16 +- ads/valuecommerce.js | 3 +- ads/videointelligence.js | 1 - ads/videonow.js | 24 +- ads/viralize.js | 9 +- ads/webediads.js | 2 +- ads/weborama.js | 5 +- ads/widespace.js | 10 +- ads/wisteria.js | 15 +- ads/wpmedia.js | 2 +- ads/xlift.js | 20 +- ads/yahoo.js | 6 +- ads/yahoojp.js | 6 +- ads/yandex.js | 50 +- ads/yengo.js | 22 +- ads/yieldbot.js | 27 +- ads/yieldmo.js | 3 +- ads/yieldone.js | 42 +- ads/yieldpro.js | 83 +- ads/zedo.js | 35 +- ads/zen.js | 7 +- ads/zucks.js | 10 +- build-system/amp-cors.js | 59 +- build-system/amp4test.js | 94 +- build-system/app-index/amphtml-helpers.js | 92 +- build-system/app-index/api/api.js | 5 - build-system/app-index/boilerplate.js | 3 +- build-system/app-index/document-modes.js | 1 - build-system/app-index/file-list.js | 203 +- build-system/app-index/form.js | 24 +- build-system/app-index/header-links.js | 3 +- build-system/app-index/html.js | 3 - build-system/app-index/proxy-form.js | 22 +- build-system/app-index/regex.js | 2 - build-system/app-index/settings.js | 73 +- build-system/app-index/template.js | 59 +- build-system/app-index/url.js | 10 +- build-system/app-utils.js | 60 +- build-system/app.js | 777 +-- .../index.js | 14 +- .../index.js | 14 +- .../index.js | 22 +- .../index.js | 21 +- .../index.js | 25 +- build-system/build.conf.js | 47 +- build-system/check-package-manager.js | 264 +- build-system/compile-wrappers.js | 33 +- build-system/config.js | 32 +- build-system/ctrlcHandler.js | 26 +- build-system/dep-check-config.js | 433 +- build-system/exec.js | 18 +- build-system/git.js | 21 +- build-system/pr-check.js | 34 +- build-system/pr-check/build-targets.js | 76 +- build-system/pr-check/build.js | 41 +- build-system/pr-check/checks.js | 7 +- build-system/pr-check/dist-tests.js | 27 +- build-system/pr-check/dist.js | 14 +- build-system/pr-check/e2e-tests.js | 7 +- build-system/pr-check/local-tests.js | 49 +- build-system/pr-check/remote-tests.js | 40 +- build-system/pr-check/utils.js | 105 +- build-system/pr-check/validator-tests.js | 13 +- build-system/pr-check/visual-diff-tests.js | 26 +- build-system/pr-check/yarn-checks.js | 61 +- build-system/recaptcha-router.js | 50 +- build-system/routes/list.js | 48 +- build-system/scope-require.js | 58 +- build-system/server.js | 29 +- build-system/shadow-viewer.js | 28 +- build-system/shorten-license.js | 1 - build-system/single-pass.js | 351 +- build-system/tasks/ava.js | 13 +- build-system/tasks/bundle-size.js | 145 +- build-system/tasks/changelog.js | 229 +- build-system/tasks/check-links.js | 145 +- build-system/tasks/clean.js | 10 +- build-system/tasks/compile-expr.js | 20 +- build-system/tasks/compile.js | 174 +- build-system/tasks/create-golden-css/index.js | 7 +- .../create-module-compatible-es5-bundle.js | 17 +- build-system/tasks/csvify-size/index.js | 186 +- build-system/tasks/csvify-size/test.js | 5 +- build-system/tasks/dep-check.js | 170 +- build-system/tasks/dev-dashboard-tests.js | 13 +- build-system/tasks/e2e/amp-driver.js | 23 +- build-system/tasks/e2e/describes-e2e.js | 55 +- build-system/tasks/e2e/driver/query-xpath.js | 26 +- build-system/tasks/e2e/expect.js | 31 +- .../tasks/e2e/functional-test-controller.js | 34 +- build-system/tasks/e2e/index.js | 9 +- .../tasks/e2e/puppeteer-controller.js | 153 +- build-system/tasks/e2e/repl.js | 4 +- .../e2e/selenium-webdriver-controller.js | 178 +- .../tasks/extension-generator/index.js | 64 +- build-system/tasks/firebase.js | 64 +- build-system/tasks/get-zindex/index.js | 64 +- build-system/tasks/get-zindex/test.js | 15 +- build-system/tasks/jsify-css.js | 12 +- build-system/tasks/json-check.js | 86 +- build-system/tasks/lint.js | 137 +- build-system/tasks/mocha-ci-reporter.js | 5 +- build-system/tasks/pr-check.js | 19 +- build-system/tasks/prepend-global/index.js | 202 +- build-system/tasks/prepend-global/test.js | 27 +- build-system/tasks/presubmit-checks.js | 491 +- build-system/tasks/process-3p-github-pr.js | 144 +- build-system/tasks/process-github-issues.js | 429 +- build-system/tasks/release-tagging.js | 86 +- build-system/tasks/runtime-test/helpers.js | 35 +- build-system/tasks/runtime-test/index.js | 489 +- .../tasks/runtime-test/status-report.js | 43 +- build-system/tasks/serve.js | 28 +- build-system/tasks/size.js | 63 +- build-system/tasks/todos.js | 63 +- build-system/tasks/update-packages.js | 75 +- build-system/tasks/validator.js | 7 +- build-system/tasks/visual-diff/helpers.js | 89 +- build-system/tasks/visual-diff/index.js | 492 +- .../tasks/visual-diff/percy-assets-loader.js | 11 +- build-system/test-server.js | 22 +- build-system/travis.js | 43 +- build-system/typescript.js | 30 +- builtins/amp-img.js | 44 +- builtins/amp-layout.js | 6 +- builtins/amp-pixel.js | 40 +- bundles.config.js | 115 +- extensions/amp-3d-gltf/0.1/amp-3d-gltf.js | 71 +- .../amp-3d-gltf/0.1/test/test-amp-3d-gltf.js | 163 +- extensions/amp-3q-player/0.1/amp-3q-player.js | 36 +- .../0.1/test/test-amp-3q-player.js | 154 +- extensions/amp-a4a/0.1/a4a-variable-source.js | 54 +- extensions/amp-a4a/0.1/amp-a4a.js | 1079 ++-- extensions/amp-a4a/0.1/amp-ad-network-base.js | 59 +- .../amp-a4a/0.1/amp-ad-template-helper.js | 45 +- extensions/amp-a4a/0.1/amp-ad-utils.js | 54 +- extensions/amp-a4a/0.1/callout-vendors.js | 66 +- .../amp-a4a/0.1/cryptographic-validator.js | 65 +- .../amp-a4a/0.1/friendly-frame-renderer.js | 8 +- extensions/amp-a4a/0.1/friendly-frame-util.js | 82 +- extensions/amp-a4a/0.1/name-frame-renderer.js | 35 +- .../amp-a4a/0.1/real-time-config-manager.js | 284 +- .../refresh-intersection-observer-wrapper.js | 9 +- extensions/amp-a4a/0.1/refresh-manager.js | 100 +- extensions/amp-a4a/0.1/signature-verifier.js | 258 +- extensions/amp-a4a/0.1/template-renderer.js | 52 +- extensions/amp-a4a/0.1/template-validator.js | 94 +- extensions/amp-a4a/0.1/test/fetch-mock.js | 24 +- .../amp-a4a/0.1/test/test-a4a-integration.js | 102 +- .../amp-a4a/0.1/test/test-a4a-var-source.js | 13 +- extensions/amp-a4a/0.1/test/test-amp-a4a.js | 1706 +++--- .../0.1/test/test-amp-ad-template-helper.js | 74 +- .../amp-a4a/0.1/test/test-amp-ad-utils.js | 5 +- .../amp-a4a/0.1/test/test-callout-vendors.js | 10 +- .../0.1/test/test-cryptographic-validator.js | 76 +- .../0.1/test/test-friendly-frame-renderer.js | 25 +- .../0.1/test/test-friendly-frame-util.js | 32 +- .../0.1/test/test-name-frame-renderer.js | 6 +- .../0.1/test/test-real-time-config-manager.js | 556 +- extensions/amp-a4a/0.1/test/test-refresh.js | 87 +- .../0.1/test/test-signature-verifier.js | 761 ++- .../0.1/test/test-template-renderer.js | 107 +- .../0.1/test/test-template-validator.js | 179 +- extensions/amp-a4a/0.1/test/utils.js | 6 +- .../0.1/amp-access-laterpay.js | 23 +- .../amp-access-laterpay/0.1/laterpay-impl.js | 166 +- .../0.1/test/test-amp-access-laterpay.js | 424 +- .../0.2/amp-access-laterpay.js | 23 +- .../amp-access-laterpay/0.2/laterpay-impl.js | 173 +- .../0.2/test/test-amp-access-laterpay.js | 509 +- .../amp-access-poool/0.1/amp-access-poool.js | 23 +- extensions/amp-access-poool/0.1/poool-impl.js | 81 +- .../0.1/test/test-amp-access-poool.js | 192 +- .../0.1/amp-access-scroll.js | 24 +- .../0.1/read-depth-tracker.js | 72 +- .../amp-access-scroll/0.1/scroll-impl.js | 225 +- .../0.1/test/read-depth-tracker.js | 116 +- extensions/amp-access/0.1/access-expr.js | 1 - extensions/amp-access/0.1/access-vars.js | 2 - extensions/amp-access/0.1/access-vendor.js | 2 - .../amp-access/0.1/amp-access-client.js | 36 +- .../amp-access/0.1/amp-access-iframe.js | 101 +- extensions/amp-access/0.1/amp-access-other.js | 6 +- .../amp-access/0.1/amp-access-server-jwt.js | 147 +- .../amp-access/0.1/amp-access-server.js | 105 +- .../amp-access/0.1/amp-access-source.js | 204 +- .../amp-access/0.1/amp-access-vendor.js | 8 +- extensions/amp-access/0.1/amp-access.js | 190 +- .../amp-access/0.1/amp-login-done-dialog.js | 44 +- .../0.1/iframe-api/access-controller.js | 2 - .../amp-access/0.1/iframe-api/iframe-api.js | 17 +- .../amp-access/0.1/iframe-api/messenger.js | 46 +- .../0.1/iframe-api/rollup.config.js | 4 +- extensions/amp-access/0.1/jwt.js | 56 +- extensions/amp-access/0.1/login-dialog.js | 97 +- .../amp-access/0.1/test/test-access-expr.js | 24 +- .../0.1/test/test-amp-access-client.js | 407 +- .../0.1/test/test-amp-access-iframe.js | 497 +- .../0.1/test/test-amp-access-other.js | 14 +- .../0.1/test/test-amp-access-server-jwt.js | 618 +- .../0.1/test/test-amp-access-server.js | 216 +- .../0.1/test/test-amp-access-source.js | 692 ++- .../0.1/test/test-amp-access-vendor.js | 79 +- .../amp-access/0.1/test/test-amp-access.js | 3328 ++++++----- .../0.1/test/test-amp-login-done-dialog.js | 290 +- .../amp-access/0.1/test/test-iframe-api.js | 119 +- .../0.1/test/test-iframe-messenger.js | 37 +- extensions/amp-access/0.1/test/test-jwt.js | 217 +- .../amp-access/0.1/test/test-login-dialog.js | 381 +- extensions/amp-accordion/0.1/amp-accordion.js | 236 +- .../test/integration/test-amp-accordion.js | 49 +- .../0.1/test/test-amp-accordion.js | 1238 ++-- .../amp-action-macro/0.1/amp-action-macro.js | 44 +- .../0.1/test/test-amp-action-macro.js | 342 +- extensions/amp-ad-custom/0.1/amp-ad-custom.js | 20 +- .../0.1/test/test-amp-ad-custom.js | 52 +- extensions/amp-ad-exit/0.1/amp-ad-exit.js | 248 +- extensions/amp-ad-exit/0.1/config.js | 49 +- .../amp-ad-exit/0.1/filters/click-delay.js | 35 +- .../amp-ad-exit/0.1/filters/click-location.js | 43 +- extensions/amp-ad-exit/0.1/filters/factory.js | 11 +- .../0.1/filters/inactive-element.js | 8 +- .../0.1/test/filters/test-click-delay.js | 44 +- .../amp-ad-exit/0.1/test/test-amp-ad-exit.js | 1046 ++-- .../0.1/adsense-a4a-config.js | 50 +- .../0.1/adsense-shared-state.js | 4 +- .../0.1/amp-ad-network-adsense-impl.js | 259 +- .../0.1/test/test-adsense-a4a-config.js | 136 +- .../test/test-amp-ad-network-adsense-impl.js | 1906 ++++--- .../0.1/amp-ad-network-adzerk-impl.js | 76 +- .../test/test-amp-ad-network-adzerk-impl.js | 126 +- .../0.1/amp-ad-network-cloudflare-impl.js | 22 +- .../test-amp-ad-network-cloudflare-impl.js | 281 +- .../0.1/amp-ad-network-doubleclick-impl.js | 837 +-- .../0.1/safeframe-host.js | 408 +- .../0.1/sra-utils.js | 153 +- .../test-amp-ad-network-doubleclick-impl.js | 1455 ++--- .../0.1/test/test-doubleclick-fluid.js | 55 +- .../0.1/test/test-doubleclick-rtc.js | 266 +- .../0.1/test/test-doubleclick-safeframe.js | 756 ++- .../0.1/test/test-doubleclick-sra.js | 448 +- .../0.1/amp-ad-network-fake-impl.js | 29 +- .../0.1/test/test-amp-ad-network-fake-impl.js | 87 +- .../0.1/amp-ad-network-gmossp-impl.js | 19 +- .../0.1/gmossp-a4a-config.js | 11 +- .../test/test-amp-ad-network-gmossp-impl.js | 116 +- .../0.1/amp-ad-network-triplelift-impl.js | 13 +- .../test-amp-ad-network-triplelift-impl.js | 84 +- .../0.1/triplelift-a4a-config.js | 8 +- extensions/amp-ad/0.1/amp-ad-3p-impl.js | 137 +- extensions/amp-ad/0.1/amp-ad-custom.js | 49 +- extensions/amp-ad/0.1/amp-ad-ui.js | 55 +- .../0.1/amp-ad-xorigin-iframe-handler.js | 271 +- extensions/amp-ad/0.1/amp-ad.js | 42 +- extensions/amp-ad/0.1/concurrent-load.js | 29 +- .../amp-ad/0.1/test/test-amp-ad-3p-impl.js | 999 ++-- .../amp-ad/0.1/test/test-amp-ad-custom.js | 126 +- extensions/amp-ad/0.1/test/test-amp-ad-ui.js | 448 +- .../test-amp-ad-xorigin-iframe-handler.js | 173 +- extensions/amp-ad/0.1/test/test-amp-ad.js | 130 +- .../amp-ad/0.1/test/test-concurrent-load.js | 46 +- .../amp-addthis/0.1/addthis-utils/classify.js | 81 +- .../amp-addthis/0.1/addthis-utils/cuid.js | 11 +- .../amp-addthis/0.1/addthis-utils/fragment.js | 10 +- ...overloaded-with-json-for-anonymous-mode.js | 69 +- .../amp-addthis/0.1/addthis-utils/lojson.js | 36 +- .../amp-addthis/0.1/addthis-utils/mode.js | 3 +- .../addthis-utils/monitors/click-monitor.js | 8 +- .../addthis-utils/monitors/dwell-monitor.js | 2 +- .../addthis-utils/monitors/scroll-monitor.js | 4 +- .../amp-addthis/0.1/addthis-utils/pixel.js | 111 +- .../amp-addthis/0.1/addthis-utils/pjson.js | 61 +- extensions/amp-addthis/0.1/amp-addthis.js | 189 +- extensions/amp-addthis/0.1/config-manager.js | 32 +- .../0.1/post-message-dispatcher.js | 7 +- .../0.1/test/addthis-utils/test-fragment.js | 5 +- .../amp-addthis/0.1/test/test-amp-addthis.js | 1058 ++-- extensions/amp-analytics/0.1/activity-impl.js | 46 +- extensions/amp-analytics/0.1/amp-analytics.js | 338 +- .../amp-analytics/0.1/analytics-group.js | 28 +- .../amp-analytics/0.1/analytics-root.js | 89 +- extensions/amp-analytics/0.1/config.js | 196 +- extensions/amp-analytics/0.1/cookie-writer.js | 41 +- extensions/amp-analytics/0.1/crc32.js | 6 +- extensions/amp-analytics/0.1/events.js | 496 +- .../0.1/iframe-transport-message-queue.js | 37 +- .../amp-analytics/0.1/iframe-transport.js | 135 +- .../amp-analytics/0.1/instrumentation.js | 39 +- .../amp-analytics/0.1/linker-manager.js | 114 +- extensions/amp-analytics/0.1/linker-reader.js | 13 +- extensions/amp-analytics/0.1/linker.js | 27 +- extensions/amp-analytics/0.1/opacity.js | 69 +- extensions/amp-analytics/0.1/requests.js | 182 +- .../amp-analytics/0.1/resource-timing.js | 68 +- .../amp-analytics/0.1/scroll-manager.js | 10 +- .../0.1/test/test-amp-analytics.js | 2856 +++++----- .../0.1/test/test-analytics-group.js | 48 +- .../0.1/test/test-analytics-root.js | 504 +- .../amp-analytics/0.1/test/test-config.js | 452 +- .../0.1/test/test-cookie-writer.js | 561 +- .../amp-analytics/0.1/test/test-events.js | 1154 ++-- .../0.1/test/test-iframe-transport-client.js | 112 +- .../test-iframe-transport-message-queue.js | 74 +- .../0.1/test/test-iframe-transport.js | 219 +- .../0.1/test/test-instrumentation.js | 96 +- .../0.1/test/test-linker-manager.js | 297 +- .../0.1/test/test-linker-reader.js | 43 +- .../amp-analytics/0.1/test/test-linker.js | 18 +- .../amp-analytics/0.1/test/test-opacity.js | 4 +- .../amp-analytics/0.1/test/test-requests.js | 135 +- .../0.1/test/test-resource-timing.js | 406 +- .../0.1/test/test-scroll-manager.js | 40 +- .../0.1/test/test-transport-serializers.js | 145 +- .../amp-analytics/0.1/test/test-transport.js | 829 +-- .../amp-analytics/0.1/test/test-variables.js | 121 +- .../amp-analytics/0.1/test/test-vendors.js | 302 +- .../test/test-visibility-manager-for-mapp.js | 38 +- .../0.1/test/test-visibility-manager.js | 1137 ++-- .../0.1/test/test-visibility-model.js | 445 +- .../amp-analytics/0.1/transport-serializer.js | 11 +- extensions/amp-analytics/0.1/transport.js | 79 +- extensions/amp-analytics/0.1/variables.js | 48 +- extensions/amp-analytics/0.1/vendors.js | 36 +- .../amp-analytics/0.1/vendors/acquialift.js | 12 +- .../0.1/vendors/adobeanalytics.js | 26 +- .../amp-analytics/0.1/vendors/afsanalytics.js | 6 +- .../amp-analytics/0.1/vendors/alexametrics.js | 6 +- .../amp-analytics/0.1/vendors/atinternet.js | 10 +- .../0.1/vendors/baiduanalytics.js | 8 +- extensions/amp-analytics/0.1/vendors/bg.js | 3 +- extensions/amp-analytics/0.1/vendors/burt.js | 15 +- .../amp-analytics/0.1/vendors/byside.js | 5 +- .../amp-analytics/0.1/vendors/chartbeat.js | 3 +- .../amp-analytics/0.1/vendors/clicky.js | 13 +- .../amp-analytics/0.1/vendors/colanalytics.js | 3 +- .../amp-analytics/0.1/vendors/comscore.js | 3 +- .../amp-analytics/0.1/vendors/cxense.js | 11 +- .../amp-analytics/0.1/vendors/dynatrace.js | 6 +- extensions/amp-analytics/0.1/vendors/epica.js | 3 +- .../0.1/vendors/euleriananalytics.js | 17 +- .../0.1/vendors/facebookpixel.js | 143 +- .../amp-analytics/0.1/vendors/gemius.js | 3 +- .../0.1/vendors/googleadwords.js | 44 +- .../0.1/vendors/googleanalytics.js | 111 +- extensions/amp-analytics/0.1/vendors/gtag.js | 130 +- .../0.1/vendors/ibeatanalytics.js | 47 +- .../amp-analytics/0.1/vendors/infonline.js | 3 +- .../amp-analytics/0.1/vendors/iplabel.js | 51 +- extensions/amp-analytics/0.1/vendors/krux.js | 6 +- .../amp-analytics/0.1/vendors/linkpulse.js | 61 +- .../0.1/vendors/marinsoftware.js | 22 +- .../amp-analytics/0.1/vendors/mediametrie.js | 3 +- .../0.1/vendors/mediarithmics.js | 3 +- .../amp-analytics/0.1/vendors/mediator.js | 20 +- .../amp-analytics/0.1/vendors/metrika.js | 12 +- extensions/amp-analytics/0.1/vendors/moat.js | 212 +- .../amp-analytics/0.1/vendors/mobify.js | 12 +- .../amp-analytics/0.1/vendors/mparticle.js | 53 +- .../amp-analytics/0.1/vendors/mpulse.js | 3 +- .../amp-analytics/0.1/vendors/navegg.js | 14 +- .../amp-analytics/0.1/vendors/newrelic.js | 5 +- .../amp-analytics/0.1/vendors/nielsen.js | 6 +- extensions/amp-analytics/0.1/vendors/oewa.js | 3 +- .../amp-analytics/0.1/vendors/oewadirect.js | 3 +- .../amp-analytics/0.1/vendors/parsely.js | 17 +- .../amp-analytics/0.1/vendors/permutive.js | 14 +- .../amp-analytics/0.1/vendors/piStats.js | 27 +- extensions/amp-analytics/0.1/vendors/piano.js | 6 +- .../amp-analytics/0.1/vendors/pinpoll.js | 3 +- .../amp-analytics/0.1/vendors/pressboard.js | 9 +- .../amp-analytics/0.1/vendors/quantcast.js | 3 +- extensions/amp-analytics/0.1/vendors/rakam.js | 9 +- .../amp-analytics/0.1/vendors/reppublika.js | 3 +- .../amp-analytics/0.1/vendors/retargetly.js | 9 +- .../amp-analytics/0.1/vendors/segment.js | 3 +- .../amp-analytics/0.1/vendors/shinystat.js | 26 +- .../amp-analytics/0.1/vendors/simplereach.js | 7 +- .../amp-analytics/0.1/vendors/snowplow.js | 24 +- .../amp-analytics/0.1/vendors/teaanalytics.js | 41 +- .../0.1/vendors/tealiumcollect.js | 53 +- .../amp-analytics/0.1/vendors/top100.js | 35 +- .../amp-analytics/0.1/vendors/topmailru.js | 23 +- .../amp-analytics/0.1/vendors/treasuredata.js | 3 +- .../0.1/vendors/umenganalytics.js | 21 +- .../amp-analytics/0.1/vendors/upscore.js | 40 +- .../amp-analytics/0.1/vendors/webtrekk.js | 18 +- .../amp-analytics/0.1/vendors/webtrekk_v2.js | 21 +- .../0.1/visibility-manager-for-mapp.js | 43 +- .../amp-analytics/0.1/visibility-manager.js | 230 +- .../amp-analytics/0.1/visibility-model.js | 86 +- extensions/amp-anim/0.1/amp-anim.js | 23 +- extensions/amp-anim/0.1/test/test-amp-anim.js | 223 +- extensions/amp-animation/0.1/amp-animation.js | 142 +- .../amp-animation/0.1/parsers/css-expr-ast.js | 118 +- .../amp-animation/0.1/parsers/css-expr.js | 1 - .../0.1/parsers/keyframes-extractor.js | 42 +- .../0.1/runners/animation-runner.js | 37 +- .../runners/native-web-animation-runner.js | 19 +- .../runners/scrolltimeline-worklet-runner.js | 67 +- extensions/amp-animation/0.1/runners/utils.js | 13 +- .../0.1/test/test-amp-animation.js | 1404 ++--- .../amp-animation/0.1/test/test-css-expr.js | 1119 ++-- .../0.1/test/test-keyframes-extractor.js | 119 +- .../0.1/test/test-web-animations.js | 1005 ++-- .../0.1/web-animation-service.js | 13 +- .../amp-animation/0.1/web-animation-types.js | 15 - .../0.1/web-animations-polyfill.js | 2 - .../amp-animation/0.1/web-animations.js | 314 +- .../0.1/amp-apester-media.js | 237 +- .../0.1/test/test-amp-apester-media-utils.js | 5 +- .../0.1/test/test-amp-apester-media.js | 321 +- extensions/amp-apester-media/0.1/utils.js | 60 +- .../amp-app-banner/0.1/amp-app-banner.js | 205 +- .../0.1/test/test-amp-app-banner.js | 898 +-- extensions/amp-audio/0.1/amp-audio.js | 57 +- .../amp-audio/0.1/test/test-amp-audio.js | 443 +- .../amp-auto-ads/0.1/ad-network-config.js | 73 +- extensions/amp-auto-ads/0.1/ad-strategy.js | 34 +- extensions/amp-auto-ads/0.1/ad-tracker.js | 70 +- extensions/amp-auto-ads/0.1/amp-auto-ads.js | 77 +- .../amp-auto-ads/0.1/anchor-ad-strategy.js | 33 +- extensions/amp-auto-ads/0.1/attributes.js | 9 +- extensions/amp-auto-ads/0.1/placement.js | 168 +- .../0.1/test/test-ad-network-config.js | 324 +- .../amp-auto-ads/0.1/test/test-ad-strategy.js | 776 +-- .../amp-auto-ads/0.1/test/test-ad-tracker.js | 91 +- .../0.1/test/test-amp-auto-ads.js | 701 +-- .../0.1/test/test-anchor-ad-strategy.js | 190 +- .../amp-auto-ads/0.1/test/test-attributes.js | 6 +- .../amp-auto-ads/0.1/test/test-placement.js | 2692 ++++----- .../0.1/amp-auto-lightbox.js | 116 +- .../0.1/carousel-criteria.js | 37 +- .../0.1/test/test-amp-auto-lightbox.js | 1122 ++-- .../0.1/test/test-carousel-criteria.js | 276 +- .../amp-autocomplete/0.1/amp-autocomplete.js | 198 +- .../0.1/test/test-amp-autocomplete-init.js | 353 +- .../0.1/test/test-amp-autocomplete.js | 1090 ++-- .../amp-base-carousel/0.1/action-source.js | 4 +- .../0.1/amp-base-carousel.js | 89 +- .../amp-base-carousel/0.1/auto-advance.js | 48 +- extensions/amp-base-carousel/0.1/carousel.js | 208 +- .../amp-base-carousel/0.1/dimensions.js | 33 +- .../0.1/responsive-attributes.js | 7 +- .../amp-base-carousel/0.1/test-e2e/helpers.js | 15 +- .../0.1/test-e2e/test-arrows-non-looping.js | 73 +- .../0.1/test-e2e/test-arrows.js | 66 +- .../0.1/test-e2e/test-autoadvance.js | 61 +- .../0.1/test-e2e/test-carousel.js | 362 +- .../0.1/test-e2e/test-grouping.js | 119 +- .../0.1/test-e2e/test-initial-slide.js | 45 +- .../test-e2e/test-mixed-lengths-no-snap.js | 91 +- .../0.1/test-e2e/test-mixed-lengths.js | 99 +- .../0.1/test-e2e/test-non-looping.js | 157 +- .../0.1/test-e2e/test-responsive.js | 149 +- .../0.1/test-e2e/test-vertical.js | 233 +- .../0.1/test/test-responsive-attributes.js | 33 +- extensions/amp-beopinion/0.1/amp-beopinion.js | 35 +- .../0.1/test/test-amp-beopinion.js | 118 +- extensions/amp-bind/0.1/amp-bind-macro.js | 5 +- extensions/amp-bind/0.1/amp-state.js | 58 +- extensions/amp-bind/0.1/bind-evaluator.js | 16 +- extensions/amp-bind/0.1/bind-expr-defines.js | 1 - extensions/amp-bind/0.1/bind-expression.js | 65 +- extensions/amp-bind/0.1/bind-impl.js | 385 +- extensions/amp-bind/0.1/bind-macro.js | 6 +- extensions/amp-bind/0.1/bind-validator.js | 2 +- .../0.1/test/integration/test-bind-impl.js | 1686 +++--- .../amp-bind/0.1/test/test-amp-state.js | 378 +- .../amp-bind/0.1/test/test-bind-evaluator.js | 263 +- .../amp-bind/0.1/test/test-bind-expression.js | 340 +- .../amp-bind/0.1/test/test-bind-validator.js | 160 +- .../0.1/amp-bodymovin-animation.js | 103 +- .../test-amp-bodymovin-animation.js | 77 +- .../amp-brid-player/0.1/amp-brid-player.js | 81 +- .../0.1/test/test-amp-brid-player.js | 288 +- .../amp-brightcove/0.1/amp-brightcove.js | 145 +- .../0.1/test/test-amp-brightcove.js | 340 +- .../0.1/amp-byside-content.js | 174 +- .../0.1/test/test-amp-byside-content.js | 285 +- .../0.1/amp-call-tracking.js | 38 +- .../0.1/test/test-amp-call-tracking.js | 191 +- extensions/amp-carousel/0.1/base-carousel.js | 25 +- extensions/amp-carousel/0.1/base-slides.js | 64 +- .../amp-carousel/0.1/scrollable-carousel.js | 111 +- extensions/amp-carousel/0.1/slidescroll.js | 248 +- .../amp-carousel/0.1/test/test-base-slide.js | 195 +- .../0.1/test/test-scrollable-carousel.js | 658 ++- .../amp-carousel/0.1/test/test-slidescroll.js | 1949 ++++--- extensions/amp-consent/0.1/amp-consent.js | 249 +- extensions/amp-consent/0.1/cmps.js | 2 +- extensions/amp-consent/0.1/consent-config.js | 50 +- extensions/amp-consent/0.1/consent-info.js | 48 +- .../amp-consent/0.1/consent-policy-manager.js | 81 +- .../amp-consent/0.1/consent-state-manager.js | 115 +- extensions/amp-consent/0.1/consent-ui.js | 84 +- .../amp-consent/0.1/test/test-amp-consent.js | 836 +-- .../0.1/test/test-consent-config.js | 252 +- .../amp-consent/0.1/test/test-consent-info.js | 168 +- .../0.1/test/test-consent-policy-manager.js | 860 +-- .../0.1/test/test-consent-state-manager.js | 229 +- .../amp-consent/0.1/test/test-consent-ui.js | 771 +-- .../amp-dailymotion/0.1/amp-dailymotion.js | 51 +- .../0.1/test/test-amp-dailymotion.js | 115 +- .../0.1/amp-date-countdown.js | 105 +- .../0.1/test/test-amp-date-countdown.js | 489 +- .../amp-date-display/0.1/amp-date-display.js | 49 +- .../0.1/test/test-amp-date-display.js | 314 +- .../amp-date-picker/0.1/amp-date-picker.js | 586 +- .../amp-date-picker/0.1/date-picker-common.js | 24 +- .../amp-date-picker/0.1/date-range-picker.js | 15 +- extensions/amp-date-picker/0.1/dates-list.js | 21 +- .../amp-date-picker/0.1/defaultPhrases.js | 2 +- extensions/amp-date-picker/0.1/react-utils.js | 7 +- .../amp-date-picker/0.1/single-date-picker.js | 16 +- ...est-integration-amp-date-picker-actions.js | 363 +- .../test-integration-dates-attributes.js | 91 +- .../test-integration-maximum-nights.js | 142 +- .../0.1/test/test-amp-date-picker.js | 676 +-- .../0.1/test/test-dates-list.js | 2 +- .../0.1/wrappers/maximum-nights.js | 32 +- .../0.1/amp-delight-player.js | 84 +- .../0.1/test/test-amp-delight-player.js | 87 +- .../0.1/amp-dynamic-css-classes.js | 12 +- .../0.1/test/test-runtime-classes.js | 105 +- .../0.1/amp-embedly-card-impl.js | 19 +- .../amp-embedly-card/0.1/amp-embedly-key.js | 7 +- .../0.1/test/test-amp-embedly-card.js | 93 +- .../amp-experiment/0.1/amp-experiment.js | 72 +- .../0.1/test/test-amp-experiment.js | 214 +- .../amp-experiment/0.1/test/test-variant.js | 457 +- extensions/amp-experiment/0.1/variant.js | 92 +- .../amp-experiment/1.0/amp-experiment.js | 70 +- .../amp-experiment/1.0/mutation-parser.js | 99 +- .../1.0/test/test-amp-experiment.js | 264 +- .../1.0/test/test-mutation-parser.js | 54 +- .../amp-experiment/1.0/test/test-variant.js | 653 ++- extensions/amp-experiment/1.0/variant.js | 133 +- .../0.1/amp-facebook-comments.js | 35 +- .../0.1/amp-facebook-like.js | 39 +- .../0.1/amp-facebook-page.js | 39 +- extensions/amp-facebook/0.1/amp-facebook.js | 36 +- .../0.1/test/test-amp-facebook.js | 449 +- extensions/amp-fit-text/0.1/amp-fit-text.js | 46 +- .../0.1/test/test-amp-fit-text.js | 105 +- extensions/amp-font/0.1/amp-font.js | 57 +- extensions/amp-font/0.1/fontloader.js | 106 +- extensions/amp-font/0.1/test/test-amp-font.js | 114 +- .../amp-font/0.1/test/test-fontloader.js | 387 +- extensions/amp-form/0.1/amp-form-textarea.js | 218 +- extensions/amp-form/0.1/amp-form.js | 420 +- extensions/amp-form/0.1/form-proxy.js | 64 +- .../amp-form/0.1/form-submit-service.js | 2 - extensions/amp-form/0.1/form-validators.js | 67 +- extensions/amp-form/0.1/form-verifiers.js | 72 +- extensions/amp-form/0.1/test-e2e/test-ssr.js | 106 +- .../test-integration-form-verifiers.js | 120 +- .../test/integration/test-integration-form.js | 611 +- .../0.1/test/test-amp-form-textarea.js | 295 +- extensions/amp-form/0.1/test/test-amp-form.js | 5014 +++++++++-------- .../amp-form/0.1/test/test-form-proxy.js | 561 +- .../amp-form/0.1/test/test-form-validators.js | 61 +- .../amp-form/0.1/test/test-form-verifiers.js | 217 +- .../0.1/test/test-validation-bubble.js | 4 +- extensions/amp-form/0.1/validation-bubble.js | 30 +- .../0.1/amp-fx-collection.js | 21 +- extensions/amp-fx-collection/0.1/fx-type.js | 27 +- .../0.1/providers/amp-fx-presets-utils.js | 23 +- .../0.1/providers/amp-fx-presets.js | 74 +- .../0.1/providers/fx-provider.js | 74 +- .../amp-fx-collection/0.1/scroll-toggle.js | 43 +- .../integration/test-amp-fx-fade-in-scroll.js | 156 +- .../test/integration/test-amp-fx-fade-in.js | 125 +- .../test/integration/test-amp-fx-fly-in.js | 129 +- .../test/integration/test-amp-fx-parallax.js | 115 +- .../0.1/test/test-amp-fx-collection.js | 98 +- .../0.1/test/test-fx-type.js | 57 +- .../0.1/amp-fx-flying-carpet.js | 38 +- .../0.1/test/test-amp-fx-flying-carpet.js | 377 +- extensions/amp-geo/0.1/amp-geo.js | 128 +- extensions/amp-geo/0.1/test/test-amp-geo.js | 782 ++- extensions/amp-gfycat/0.1/amp-gfycat.js | 49 +- .../amp-gfycat/0.1/test/test-amp-gfycat.js | 194 +- extensions/amp-gist/0.1/amp-gist.js | 19 +- extensions/amp-gist/0.1/test/test-amp-gist.js | 85 +- .../0.1/amp-google-document-embed.js | 21 +- .../test/test-amp-google-document-embed.js | 94 +- .../0.1/amp-google-vrview-image.js | 18 +- .../0.1/test/test-amp-google-vrview-image.js | 132 +- .../0.1/amp-gwd-animation-impl.js | 80 +- .../0.1/amp-gwd-animation.js | 70 +- .../0.1/test/test-amp-gwd-animation.js | 838 +-- extensions/amp-hulu/0.1/amp-hulu.js | 12 +- extensions/amp-hulu/0.1/test/test-amp-hulu.js | 88 +- extensions/amp-iframe/0.1/amp-iframe.js | 279 +- .../amp-iframe/0.1/test/test-amp-iframe.js | 1693 +++--- extensions/amp-ima-video/0.1/amp-ima-video.js | 83 +- .../amp-ima-video/0.1/ima-player-data.js | 1 - .../0.1/test/test-amp-ima-video.js | 2470 ++++---- .../0.1/amp-image-lightbox.js | 442 +- .../integration/test-amp-image-lightbox.js | 66 +- .../0.1/test/test-amp-image-lightbox.js | 938 +-- .../amp-image-slider/0.1/amp-image-slider.js | 300 +- .../test/integration/test-amp-image-slider.js | 1625 +++--- .../0.1/test/test-amp-image-slider.js | 17 +- .../amp-image-viewer/0.1/amp-image-viewer.js | 328 +- extensions/amp-imgur/0.1/amp-imgur.js | 58 +- .../amp-imgur/0.1/test/test-amp-imgur.js | 129 +- extensions/amp-inputmask/0.1/amp-inputmask.js | 5 +- .../0.1/inputmask-custom-alias.js | 56 +- .../0.1/inputmask-payment-card-alias.js | 64 +- extensions/amp-inputmask/0.1/mask-impl.js | 53 +- .../test-integration-amp-inputmask-date.js | 57 +- .../test-integration-amp-inputmask.js | 84 +- extensions/amp-inputmask/0.1/text-mask.js | 15 +- extensions/amp-instagram/0.1/amp-instagram.js | 87 +- .../0.1/test/test-amp-instagram.js | 316 +- .../0.1/amp-install-serviceworker.js | 203 +- .../test/test-amp-install-serviceworker.js | 1266 +++-- extensions/amp-izlesene/0.1/amp-izlesene.js | 25 +- .../0.1/test/test-amp-izlesene.js | 92 +- extensions/amp-jwplayer/0.1/amp-jwplayer.js | 64 +- .../0.1/test/test-amp-jwplayer.js | 317 +- .../0.1/amp-kaltura-player.js | 59 +- .../0.1/test/test-amp-kaltura-player.js | 197 +- .../0.1/amp-lightbox-gallery.js | 669 ++- extensions/amp-lightbox-gallery/0.1/events.js | 1 - .../0.1/service/lightbox-manager-impl.js | 72 +- .../0.1/service/lightbox-placeholders.js | 15 +- .../integration/test-amp-lightbox-gallery.js | 288 +- .../0.1/test/test-amp-lightbox-gallery.js | 52 +- extensions/amp-lightbox/0.1/amp-lightbox.js | 217 +- .../0.1/amp-link-rewriter.js | 17 +- .../amp-link-rewriter/0.1/config-options.js | 18 +- .../amp-link-rewriter/0.1/link-rewriter.js | 75 +- extensions/amp-link-rewriter/0.1/scope.js | 2 - .../0.1/test/test-amp-link-rewriter.js | 151 +- .../0.1/test/test-helpers.js | 2 - extensions/amp-list/0.1/amp-list.js | 401 +- .../amp-list/0.1/service/load-more-service.js | 22 +- .../0.1/test-e2e/test-load-more-auto.js | 103 +- .../0.1/test-e2e/test-load-more-manual.js | 186 +- extensions/amp-list/0.1/test-e2e/test-ssr.js | 110 +- .../0.1/test/integration/test-amp-list.js | 170 +- .../0.1/test/test-amp-list-load-more.js | 277 +- extensions/amp-list/0.1/test/test-amp-list.js | 1563 ++--- extensions/amp-live-list/0.1/amp-live-list.js | 199 +- .../amp-live-list/0.1/live-list-manager.js | 51 +- extensions/amp-live-list/0.1/poller.js | 42 +- .../0.1/test/test-amp-live-list.js | 2346 ++++---- .../0.1/test/test-live-list-manager.js | 542 +- .../amp-live-list/0.1/test/test-poller.js | 233 +- extensions/amp-mathml/0.1/amp-mathml.js | 24 +- extensions/amp-mowplayer/0.1/amp-mowplayer.js | 46 +- .../0.1/test/test-amp-mowplayer.js | 161 +- extensions/amp-mraid/0.1/amp-mraid.js | 42 +- extensions/amp-mraid/0.1/mraid-service.js | 45 +- extensions/amp-mustache/0.1/amp-mustache.js | 31 +- .../0.1/test/test-amp-mustache.js | 906 +-- extensions/amp-mustache/0.2/amp-mustache.js | 16 +- .../0.2/test/test-amp-mustache.js | 903 +-- extensions/amp-next-page/0.1/amp-next-page.js | 120 +- extensions/amp-next-page/0.1/config.js | 42 +- .../amp-next-page/0.1/next-page-service.js | 219 +- .../0.1/test/test-amp-next-page.js | 685 +-- .../amp-next-page/0.1/test/test-config.js | 282 +- .../0.1/amp-nexxtv-player.js | 35 +- .../0.1/test/test-amp-nexxtv-player.js | 133 +- extensions/amp-o2-player/0.1/amp-o2-player.js | 55 +- .../0.1/test/test-amp-o2-player.js | 212 +- .../0.1/amp-ooyala-player.js | 46 +- .../0.1/test/test-amp-ooyala.js | 175 +- .../0.1/amp-orientation-observer.js | 39 +- .../0.1/test/test-amp-orientation-observer.js | 227 +- extensions/amp-pan-zoom/0.1/amp-pan-zoom.js | 253 +- .../0.1/test/integration/test-amp-pan-zoom.js | 71 +- .../0.1/test/test-amp-pan-zoom.js | 620 +- extensions/amp-pinterest/0.1/amp-pinterest.js | 11 +- extensions/amp-pinterest/0.1/follow-button.js | 34 +- extensions/amp-pinterest/0.1/pin-widget.js | 242 +- extensions/amp-pinterest/0.1/save-button.js | 63 +- .../0.1/test/test-amp-pinterest.js | 490 +- extensions/amp-pinterest/0.1/util.js | 6 +- extensions/amp-playbuzz/0.1/amp-playbuzz.js | 91 +- .../0.1/test/test-amp-playbuzz.js | 312 +- extensions/amp-playbuzz/0.1/utils.js | 65 +- .../0.1/amp-position-observer.js | 121 +- .../0.1/test/test-amp-position-observer.js | 102 +- .../amp-powr-player/0.1/amp-powr-player.js | 83 +- .../0.1/test/test-amp-powr-player.js | 350 +- .../amp-reach-player/0.1/amp-reach-player.js | 20 +- .../0.1/test/test-amp-reach-player.js | 94 +- .../0.1/amp-recaptcha-input.js | 37 +- .../0.1/amp-recaptcha-service.js | 114 +- .../0.1/test/test-amp-recaptcha-input.js | 320 +- .../0.1/test/test-amp-recaptcha-service.js | 218 +- extensions/amp-reddit/0.1/amp-reddit.js | 44 +- .../amp-reddit/0.1/test/test-amp-reddit.js | 176 +- .../amp-riddle-quiz/0.1/amp-riddle-quiz.js | 32 +- .../0.1/test/test-amp-riddle-quiz.js | 41 +- extensions/amp-script/0.1/amp-script.js | 51 +- .../0.1/test-e2e/test-amp-script.js | 54 +- .../0.1/test/integration/test-amp-script.js | 402 +- .../test/unit/test-user-activation-tracker.js | 64 +- .../amp-script/0.1/user-activation-tracker.js | 37 +- extensions/amp-selector/0.1/amp-selector.js | 90 +- .../0.1/test/test-amp-selector.js | 2196 ++++---- .../0.1/amp-share-tracking.js | 78 +- .../0.1/test/test-amp-share-tracking.js | 497 +- extensions/amp-sidebar/0.1/amp-sidebar.js | 108 +- extensions/amp-sidebar/0.1/autoscroll.js | 11 +- .../0.1/test/integration/test-amp-sidebar.js | 118 +- .../amp-sidebar/0.1/test/test-amp-sidebar.js | 1018 ++-- .../amp-sidebar/0.1/test/test-toolbar.js | 114 +- extensions/amp-sidebar/0.1/toolbar.js | 57 +- .../0.1/affiliate-link-resolver.js | 33 +- extensions/amp-skimlinks/0.1/amp-skimlinks.js | 65 +- extensions/amp-skimlinks/0.1/constants.js | 11 +- .../link-rewriter/link-replacement-cache.js | 3 +- .../link-rewriter/link-rewriter-manager.js | 31 +- .../0.1/link-rewriter/link-rewriter.js | 34 +- extensions/amp-skimlinks/0.1/skim-options.js | 26 +- extensions/amp-skimlinks/0.1/test/helpers.js | 5 +- .../0.1/test/test-affiliate-link-resolver.js | 703 ++- .../0.1/test/test-amp-skimlinks.js | 587 +- .../0.1/test/test-link-rewriter.js | 152 +- .../0.1/test/test-skim-options.js | 197 +- .../amp-skimlinks/0.1/test/test-tracking.js | 662 +-- .../amp-skimlinks/0.1/test/test-waypoint.js | 212 +- extensions/amp-skimlinks/0.1/tracking.js | 106 +- extensions/amp-skimlinks/0.1/utils.js | 4 +- extensions/amp-skimlinks/0.1/waypoint.js | 11 +- extensions/amp-slides/0.1/amp-slides.js | 11 +- .../amp-smartlinks/0.1/amp-smartlinks.js | 88 +- extensions/amp-smartlinks/0.1/constants.js | 9 +- .../amp-smartlinks/0.1/linkmate-options.js | 1 - extensions/amp-smartlinks/0.1/linkmate.js | 47 +- .../0.1/test/test-amp-smartlinks.js | 364 +- .../amp-smartlinks/0.1/test/test-linkmate.js | 810 +-- .../amp-social-share/0.1/amp-social-share.js | 58 +- .../0.1/test/test-amp-social-share.js | 449 +- .../amp-soundcloud/0.1/amp-soundcloud.js | 39 +- .../0.1/test/test-amp-soundcloud.js | 155 +- .../0.1/amp-springboard-player.js | 90 +- .../0.1/test/test-amp-springboard-player.js | 309 +- extensions/amp-sticky-ad/1.0/amp-sticky-ad.js | 62 +- .../1.0/test/test-amp-sticky-ad.js | 718 +-- .../amp-story-auto-ads/0.1/_locales/ca.js | 10 +- .../amp-story-auto-ads/0.1/_locales/en.js | 116 +- .../amp-story-auto-ads/0.1/_locales/fr.js | 10 +- .../0.1/amp-story-auto-ads.js | 267 +- .../0.1/test/test-amp-story-auto-ads.js | 462 +- extensions/amp-story-auto-ads/0.1/utils.js | 10 +- extensions/amp-story/0.1/_locales/default.js | 6 +- extensions/amp-story/0.1/_locales/en.js | 131 +- extensions/amp-story/0.1/amp-story-bookend.js | 267 +- extensions/amp-story/0.1/amp-story-consent.js | 149 +- .../amp-story/0.1/amp-story-cta-layer.js | 1 - .../amp-story/0.1/amp-story-grid-layer.js | 27 +- extensions/amp-story/0.1/amp-story-hint.js | 64 +- .../amp-story/0.1/amp-story-info-dialog.js | 71 +- extensions/amp-story/0.1/amp-story-page.js | 172 +- .../0.1/amp-story-request-service.js | 13 +- .../amp-story/0.1/amp-story-share-menu.js | 33 +- extensions/amp-story/0.1/amp-story-share.js | 305 +- .../amp-story/0.1/amp-story-store-service.js | 90 +- .../amp-story/0.1/amp-story-system-layer.js | 112 +- .../amp-story-unsupported-browser-layer.js | 10 +- .../0.1/amp-story-viewport-warning-layer.js | 69 +- extensions/amp-story/0.1/amp-story.js | 523 +- extensions/amp-story/0.1/analytics.js | 20 +- .../amp-story/0.1/animation-presets-utils.js | 33 +- extensions/amp-story/0.1/animation-presets.js | 30 +- extensions/amp-story/0.1/animation-types.js | 4 - extensions/amp-story/0.1/animation.js | 148 +- extensions/amp-story/0.1/audio.js | 7 +- extensions/amp-story/0.1/background.js | 23 +- extensions/amp-story/0.1/default-media.js | 148 +- extensions/amp-story/0.1/development-ui.js | 75 +- extensions/amp-story/0.1/embed-mode.js | 3 - extensions/amp-story/0.1/events.js | 10 +- extensions/amp-story/0.1/jsonld.js | 8 +- extensions/amp-story/0.1/loading-spinner.js | 2 - extensions/amp-story/0.1/logging.js | 92 +- extensions/amp-story/0.1/media-pool.js | 235 +- extensions/amp-story/0.1/media-tasks.js | 39 +- extensions/amp-story/0.1/navigation-state.js | 3 - extensions/amp-story/0.1/page-advancement.js | 73 +- extensions/amp-story/0.1/page-element.js | 73 +- .../amp-story/0.1/pagination-buttons.js | 52 +- extensions/amp-story/0.1/progress-bar.js | 43 +- extensions/amp-story/0.1/related-articles.js | 75 +- extensions/amp-story/0.1/simple-template.js | 23 +- extensions/amp-story/0.1/sources.js | 10 +- .../0.1/test/test-amp-story-consent.js | 65 +- .../0.1/test/test-amp-story-cta-layer.js | 78 +- .../amp-story/0.1/test/test-amp-story-hint.js | 23 +- .../0.1/test/test-amp-story-info-dialog.js | 16 +- .../test/test-amp-story-request-service.js | 68 +- .../0.1/test/test-amp-story-share-menu.js | 7 +- .../0.1/test/test-amp-story-store-service.js | 1 - .../0.1/test/test-amp-story-system-layer.js | 7 +- .../amp-story/0.1/test/test-amp-story.js | 725 +-- .../amp-story/0.1/test/test-analytics.js | 1 - .../0.1/test/test-full-bleed-animations.js | 404 +- .../amp-story/0.1/test/test-media-pool.js | 20 +- .../amp-story/0.1/test/test-media-tasks.js | 5 +- .../0.1/test/test-navigation-state.js | 103 +- .../0.1/test/test-origin-whitelist.js | 9 +- extensions/amp-story/0.1/test/test-utils.js | 22 +- .../0.1/test/test-variable-service.js | 1 - extensions/amp-story/0.1/toast.js | 16 +- extensions/amp-story/0.1/utils.js | 36 +- extensions/amp-story/0.1/variable-service.js | 10 +- extensions/amp-story/1.0/_locales/af.js | 15 +- extensions/amp-story/1.0/_locales/bg.js | 19 +- extensions/amp-story/1.0/_locales/bn.js | 9 +- extensions/amp-story/1.0/_locales/bs.js | 4 +- extensions/amp-story/1.0/_locales/ca.js | 20 +- extensions/amp-story/1.0/_locales/cs.js | 5 +- extensions/amp-story/1.0/_locales/da.js | 10 +- extensions/amp-story/1.0/_locales/de.js | 20 +- extensions/amp-story/1.0/_locales/default.js | 11 +- extensions/amp-story/1.0/_locales/el.js | 30 +- extensions/amp-story/1.0/_locales/en-GB.js | 10 +- extensions/amp-story/1.0/_locales/en.js | 178 +- extensions/amp-story/1.0/_locales/es-419.js | 10 +- extensions/amp-story/1.0/_locales/es.js | 9 +- extensions/amp-story/1.0/_locales/fi.js | 4 +- extensions/amp-story/1.0/_locales/fil.js | 23 +- extensions/amp-story/1.0/_locales/fr.js | 20 +- extensions/amp-story/1.0/_locales/gl.js | 4 +- extensions/amp-story/1.0/_locales/gu.js | 5 +- extensions/amp-story/1.0/_locales/hi.js | 14 +- extensions/amp-story/1.0/_locales/hr.js | 5 +- extensions/amp-story/1.0/_locales/hu.js | 14 +- extensions/amp-story/1.0/_locales/id.js | 9 +- extensions/amp-story/1.0/_locales/it.js | 32 +- extensions/amp-story/1.0/_locales/iw.js | 6 +- extensions/amp-story/1.0/_locales/ja.js | 6 +- extensions/amp-story/1.0/_locales/ka.js | 14 +- extensions/amp-story/1.0/_locales/km.js | 7 +- extensions/amp-story/1.0/_locales/kn.js | 14 +- extensions/amp-story/1.0/_locales/lt.js | 15 +- extensions/amp-story/1.0/_locales/mk.js | 23 +- extensions/amp-story/1.0/_locales/ml.js | 14 +- extensions/amp-story/1.0/_locales/mn.js | 5 +- extensions/amp-story/1.0/_locales/mr.js | 14 +- extensions/amp-story/1.0/_locales/ms.js | 18 +- extensions/amp-story/1.0/_locales/my.js | 18 +- extensions/amp-story/1.0/_locales/ne.js | 10 +- extensions/amp-story/1.0/_locales/nl.js | 10 +- extensions/amp-story/1.0/_locales/no.js | 10 +- extensions/amp-story/1.0/_locales/pa.js | 18 +- extensions/amp-story/1.0/_locales/pl.js | 5 +- extensions/amp-story/1.0/_locales/pt-BR.js | 5 +- extensions/amp-story/1.0/_locales/pt-PT.js | 15 +- extensions/amp-story/1.0/_locales/ro.js | 14 +- extensions/amp-story/1.0/_locales/ru.js | 9 +- extensions/amp-story/1.0/_locales/si.js | 4 +- extensions/amp-story/1.0/_locales/sk.js | 10 +- extensions/amp-story/1.0/_locales/sl.js | 9 +- extensions/amp-story/1.0/_locales/sq.js | 15 +- extensions/amp-story/1.0/_locales/sr.js | 10 +- extensions/amp-story/1.0/_locales/sv.js | 5 +- extensions/amp-story/1.0/_locales/sw.js | 4 +- extensions/amp-story/1.0/_locales/ta.js | 19 +- extensions/amp-story/1.0/_locales/te.js | 4 +- extensions/amp-story/1.0/_locales/tr.js | 16 +- extensions/amp-story/1.0/_locales/uk.js | 5 +- extensions/amp-story/1.0/_locales/ur.js | 15 +- extensions/amp-story/1.0/_locales/vi.js | 5 +- extensions/amp-story/1.0/_locales/zu.js | 14 +- extensions/amp-story/1.0/amp-story-access.js | 65 +- extensions/amp-story/1.0/amp-story-consent.js | 150 +- .../amp-story/1.0/amp-story-cta-layer.js | 16 +- .../1.0/amp-story-embedded-component.js | 615 +- .../amp-story/1.0/amp-story-grid-layer.js | 21 +- extensions/amp-story/1.0/amp-story-hint.js | 101 +- .../amp-story/1.0/amp-story-info-dialog.js | 71 +- .../1.0/amp-story-page-attachment.js | 136 +- extensions/amp-story/1.0/amp-story-page.js | 383 +- .../1.0/amp-story-request-service.js | 23 +- .../amp-story/1.0/amp-story-share-menu.js | 39 +- extensions/amp-story/1.0/amp-story-share.js | 343 +- .../amp-story/1.0/amp-story-store-service.js | 198 +- .../amp-story/1.0/amp-story-system-layer.js | 228 +- .../amp-story-unsupported-browser-layer.js | 16 +- .../1.0/amp-story-viewport-warning-layer.js | 82 +- extensions/amp-story/1.0/amp-story.js | 945 ++-- .../amp-story/1.0/animation-presets-utils.js | 33 +- extensions/amp-story/1.0/animation-presets.js | 70 +- extensions/amp-story/1.0/animation-types.js | 4 - extensions/amp-story/1.0/animation.js | 226 +- extensions/amp-story/1.0/audio.js | 8 +- extensions/amp-story/1.0/background.js | 23 +- .../1.0/bookend/amp-story-bookend.js | 273 +- .../1.0/bookend/bookend-component.js | 33 +- .../1.0/bookend/components/article.js | 50 +- .../components/bookend-component-interface.js | 1 - .../1.0/bookend/components/cta-link.js | 34 +- .../1.0/bookend/components/heading.js | 15 +- .../1.0/bookend/components/landscape.js | 21 +- .../1.0/bookend/components/portrait.js | 29 +- .../1.0/bookend/components/text-box.js | 26 +- extensions/amp-story/1.0/default-media.js | 148 +- extensions/amp-story/1.0/development-ui.js | 75 +- extensions/amp-story/1.0/embed-mode.js | 3 - extensions/amp-story/1.0/events.js | 10 +- extensions/amp-story/1.0/jsonld.js | 8 +- extensions/amp-story/1.0/loading-spinner.js | 2 - extensions/amp-story/1.0/logging.js | 88 +- extensions/amp-story/1.0/media-pool.js | 261 +- extensions/amp-story/1.0/media-tasks.js | 38 +- extensions/amp-story/1.0/navigation-state.js | 6 +- extensions/amp-story/1.0/page-advancement.js | 257 +- .../amp-story/1.0/pagination-buttons.js | 89 +- extensions/amp-story/1.0/progress-bar.js | 42 +- extensions/amp-story/1.0/simple-template.js | 33 +- extensions/amp-story/1.0/sources.js | 10 +- extensions/amp-story/1.0/story-analytics.js | 23 +- .../1.0/test/test-amp-story-access.js | 101 +- .../1.0/test/test-amp-story-bookend.js | 682 +-- .../1.0/test/test-amp-story-consent.js | 106 +- .../1.0/test/test-amp-story-cta-layer.js | 182 +- .../test/test-amp-story-embedded-component.js | 99 +- .../amp-story/1.0/test/test-amp-story-hint.js | 20 +- .../1.0/test/test-amp-story-info-dialog.js | 16 +- .../amp-story/1.0/test/test-amp-story-page.js | 261 +- .../test/test-amp-story-request-service.js | 98 +- .../1.0/test/test-amp-story-share-menu.js | 7 +- .../1.0/test/test-amp-story-store-service.js | 14 +- .../1.0/test/test-amp-story-system-layer.js | 16 +- .../amp-story/1.0/test/test-amp-story.js | 2219 ++++---- .../amp-story/1.0/test/test-analytics.js | 1 - .../1.0/test/test-full-bleed-animations.js | 413 +- .../amp-story/1.0/test/test-media-pool.js | 34 +- .../amp-story/1.0/test/test-media-tasks.js | 5 +- .../1.0/test/test-navigation-state.js | 122 +- extensions/amp-story/1.0/test/test-utils.js | 22 +- .../1.0/test/test-variable-service.js | 6 +- extensions/amp-story/1.0/toast.js | 16 +- extensions/amp-story/1.0/utils.js | 54 +- extensions/amp-story/1.0/variable-service.js | 11 +- .../0.1/amp-subscriptions-google.js | 143 +- .../0.1/test/test-amp-subscriptions-google.js | 291 +- extensions/amp-subscriptions/0.1/actions.js | 66 +- .../0.1/amp-subscriptions.js | 266 +- extensions/amp-subscriptions/0.1/analytics.js | 19 +- .../amp-subscriptions/0.1/crypto-handler.js | 33 +- extensions/amp-subscriptions/0.1/dialog.js | 101 +- extensions/amp-subscriptions/0.1/doc-impl.js | 3 - .../amp-subscriptions/0.1/entitlement.js | 33 +- extensions/amp-subscriptions/0.1/expr.js | 1 - .../local-subscription-platform-renderer.js | 99 +- .../0.1/local-subscription-platform.js | 110 +- .../amp-subscriptions/0.1/platform-store.js | 137 +- extensions/amp-subscriptions/0.1/renderer.js | 37 +- .../amp-subscriptions/0.1/score-factors.js | 3 +- .../amp-subscriptions/0.1/service-adapter.js | 10 +- .../0.1/subscription-platform.js | 1 - .../0.1/test/test-actions.js | 196 +- .../0.1/test/test-amp-subscriptions.js | 738 ++- .../0.1/test/test-analytics.js | 6 +- .../0.1/test/test-crypto-handler.js | 170 +- .../amp-subscriptions/0.1/test/test-dialog.js | 123 +- .../0.1/test/test-doc-impl.js | 6 +- .../0.1/test/test-entitlement.js | 35 +- .../test/test-local-subscription-rendering.js | 89 +- .../0.1/test/test-local-subscriptions.js | 284 +- .../0.1/test/test-platform-store.js | 860 +-- .../0.1/test/test-renderer.js | 399 +- .../0.1/test/test-service-adapter.js | 201 +- .../0.1/test/test-url-builder.js | 82 +- .../0.1/test/test-viewer-platform.js | 196 +- .../0.1/test/test-viewer-tracker.js | 20 +- .../amp-subscriptions/0.1/url-builder.js | 1 - .../0.1/viewer-subscription-platform.js | 80 +- .../amp-subscriptions/0.1/viewer-tracker.js | 45 +- extensions/amp-timeago/0.1/amp-timeago.js | 11 +- .../amp-timeago/0.1/test/test-amp-timeago.js | 122 +- extensions/amp-twitter/0.1/amp-twitter.js | 87 +- .../amp-twitter/0.1/test/test-amp-twitter.js | 308 +- .../0.1/amp-user-notification.js | 146 +- .../0.1/test/test-amp-user-notification.js | 1258 +++-- .../0.1/amp-video-docking.js | 518 +- .../amp-video-docking/0.1/breakpoints.js | 4 +- extensions/amp-video-docking/0.1/controls.js | 196 +- extensions/amp-video-docking/0.1/events.js | 8 +- extensions/amp-video-docking/0.1/math.js | 15 +- .../0.1/test/test-amp-video-docking.js | 904 +-- .../0.1/test/test-breakpoints.js | 59 +- extensions/amp-video-docking/0.1/timeout.js | 1 - .../amp-video-iframe/0.1/amp-video-iframe.js | 106 +- .../0.1/test/test-amp-video-iframe.js | 619 +- extensions/amp-video/0.1/amp-video.js | 128 +- .../amp-video/0.1/test/test-amp-video.js | 1723 +++--- .../0.1/amp-viewer-assistance.js | 129 +- .../0.1/test/test-amp-viewer-assistance.js | 535 +- .../0.1/amp-viewer-integration.js | 101 +- .../amp-viewer-integration/0.1/findtext.js | 29 +- .../0.1/focus-handler.js | 12 +- .../0.1/highlight-handler.js | 23 +- .../0.1/keyboard-handler.js | 8 +- .../0.1/messaging/messaging.js | 120 +- .../test-amp-viewer-integration.js | 693 +-- .../test-amp-webview-viewer-integration.js | 49 +- .../test-viewer-initiated-handshake.js | 10 +- .../0.1/test/test-findtext.js | 46 +- .../0.1/test/test-focus-handler.js | 8 +- .../0.1/test/test-highlight-handler.js | 612 +- .../0.1/test/test-keyboard-handler.js | 449 +- .../0.1/test/test-touch-handler.js | 36 +- .../0.1/test/test-variable-service.js | 4 +- .../0.1/touch-handler.js | 46 +- .../0.1/variable-service.js | 16 +- .../0.1/viewer-for-testing.js | 83 +- ...-initiated-handshake-viewer-for-testing.js | 24 +- .../0.1/webview-viewer-for-testing.js | 49 +- extensions/amp-vimeo/0.1/amp-vimeo.js | 47 +- .../amp-vimeo/0.1/test/test-amp-vimeo.js | 84 +- extensions/amp-vine/0.1/amp-vine.js | 17 +- extensions/amp-vine/0.1/test/test-amp-vine.js | 85 +- .../amp-viqeo-player/0.1/amp-viqeo-player.js | 67 +- .../0.1/test/test-amp-viqeo-player.js | 239 +- extensions/amp-viz-vega/0.1/amp-viz-vega.js | 130 +- extensions/amp-vk/0.1/amp-vk.js | 146 +- extensions/amp-vk/0.1/test/test-amp-vk.js | 323 +- .../amp-web-push/0.1/amp-web-push-config.js | 54 +- .../0.1/amp-web-push-helper-frame.js | 145 +- .../0.1/amp-web-push-permission-dialog.js | 67 +- .../amp-web-push/0.1/amp-web-push-widget.js | 1 - extensions/amp-web-push/0.1/amp-web-push.js | 1 - .../0.1/amp-web-push.service-worker.js | 91 +- .../0.1/test/test-web-push-config.js | 269 +- .../test/test-web-push-permission-dialog.js | 401 +- .../0.1/test/test-web-push-service.js | 1324 ++--- .../amp-web-push/0.1/web-push-service.js | 356 +- .../amp-web-push/0.1/window-messenger.js | 185 +- .../0.1/amp-wistia-player.js | 47 +- .../0.1/test/test-amp-wistia-player.js | 66 +- extensions/amp-yotpo/0.1/amp-yotpo.js | 30 +- .../amp-yotpo/0.1/test/test-amp-yotpo.js | 122 +- extensions/amp-youtube/0.1/amp-youtube.js | 144 +- .../amp-youtube/0.1/test/test-amp-youtube.js | 546 +- gulpfile.js | 1339 +++-- src/3p-frame-messaging.js | 26 +- src/3p-frame.js | 125 +- src/ad-cid.js | 51 +- src/ad-helper.js | 6 +- src/amp-shadow.js | 34 +- src/amp.js | 87 +- src/animation.js | 35 +- src/async-input.js | 5 +- src/auto-lightbox.js | 31 +- src/base-element.js | 121 +- src/batched-json.js | 55 +- src/chunk.js | 40 +- src/clipboard.js | 2 - src/common-signals.js | 2 - src/config.js | 20 +- src/consent.js | 68 +- src/cookies.js | 32 +- src/css.js | 4 +- src/curve.js | 11 +- src/custom-element.js | 418 +- src/document-fetcher.js | 44 +- src/document-ready.js | 1 - src/document-submit.js | 80 +- src/dom.js | 174 +- src/element-service.js | 101 +- src/element-stub.js | 1 - src/error.js | 80 +- src/event-helper-listen.js | 32 +- src/event-helper.js | 80 +- src/examiner/examiner.js | 22 +- src/experiments.js | 69 +- src/exponential-backoff.js | 5 +- src/extension-analytics.js | 121 +- src/finite-state-machine.js | 5 +- src/focus-history.js | 7 +- src/font-stylesheet-timeout.js | 7 +- src/form-data-wrapper.js | 11 +- src/form.js | 31 +- src/friendly-iframe-embed.js | 139 +- src/full-overlay-frame-helper.js | 16 +- src/gesture-recognizers.js | 177 +- src/gesture.js | 36 +- src/get-bounding-client-rect.js | 7 +- src/get-html.js | 34 +- src/iframe-attributes.js | 20 +- src/iframe-helper.js | 128 +- src/iframe-video.js | 18 +- src/impression.js | 142 +- src/inabox/amp-inabox-lite.js | 62 +- src/inabox/amp-inabox.js | 99 +- src/inabox/host-services.js | 59 +- src/inabox/inabox-iframe-messaging-client.js | 16 +- src/inabox/inabox-viewport.js | 257 +- src/inabox/utils.js | 30 +- src/input.js | 62 +- src/intersection-observer-polyfill.js | 162 +- src/intersection-observer.js | 59 +- src/json.js | 19 +- src/layout-delay-meter.js | 5 +- src/layout-rect.js | 83 +- src/layout.js | 156 +- src/localized-strings.js | 25 +- src/log.js | 143 +- src/mediasession-helper.js | 22 +- src/mode.js | 15 +- src/module.js | 13 +- src/motion.js | 42 +- src/observable.js | 3 - src/pass.js | 2 - src/pixel.js | 11 +- src/polyfills.js | 11 +- src/polyfills/array-includes.js | 7 +- src/polyfills/custom-elements.js | 29 +- src/polyfills/document-contains.js | 2 - src/polyfills/domtokenlist-toggle.js | 3 - src/polyfills/fetch.js | 65 +- src/polyfills/math-sign.js | 1 - src/polyfills/object-assign.js | 1 - src/polyfills/object-values.js | 2 - src/preconnect.js | 20 +- src/pull-to-refresh.js | 20 +- src/purifier.js | 46 +- src/render-delaying-services.js | 32 +- src/runtime.js | 278 +- src/sanitation.js | 37 +- src/sanitizer.js | 22 +- src/service.js | 103 +- src/service/action-impl.js | 249 +- src/service/ampdoc-impl.js | 48 +- src/service/batched-xhr-impl.js | 25 +- src/service/cache-cid-api.js | 62 +- src/service/cid-api.js | 64 +- src/service/cid-impl.js | 193 +- src/service/crypto-impl.js | 60 +- src/service/custom-element-registry.js | 26 +- src/service/document-info-impl.js | 16 +- src/service/document-state.js | 23 +- src/service/extension-location.js | 14 +- src/service/extensions-impl.js | 165 +- src/service/fixed-layer.js | 461 +- src/service/hidden-observer-impl.js | 9 +- src/service/history-impl.js | 317 +- src/service/ie-media-bug.js | 7 +- src/service/jank-meter.js | 40 +- src/service/layers-impl.js | 121 +- src/service/localization.js | 55 +- src/service/navigation.js | 135 +- src/service/origin-experiments-impl.js | 86 +- src/service/performance-impl.js | 110 +- src/service/platform-impl.js | 31 +- .../position-observer-impl.js | 37 +- .../position-observer-worker.js | 47 +- src/service/resource.js | 245 +- src/service/resources-impl.js | 791 ++- src/service/standard-actions-impl.js | 126 +- src/service/storage-impl.js | 98 +- src/service/task-queue.js | 7 +- src/service/template-impl.js | 30 +- src/service/timer-impl.js | 25 +- src/service/url-expander/expander.js | 110 +- src/service/url-impl.js | 25 +- src/service/url-replacements-impl.js | 781 ++- src/service/variable-source.js | 33 +- src/service/video-manager-impl.js | 322 +- src/service/video-session-manager.js | 1 - src/service/video/autoplay.js | 24 +- src/service/video/install-autoplay-styles.js | 12 +- src/service/viewer-cid-api.js | 2 - src/service/viewer-impl.js | 222 +- src/service/viewport/viewport-binding-def.js | 1 - .../viewport/viewport-binding-ios-embed-sd.js | 70 +- .../viewport-binding-ios-embed-wrapper.js | 54 +- .../viewport/viewport-binding-natural.js | 81 +- src/service/viewport/viewport-impl.js | 330 +- src/service/vsync-impl.js | 64 +- src/service/xhr-impl.js | 73 +- src/services.js | 373 +- src/shadow-embed.js | 64 +- src/size-list.js | 57 +- src/srcset.js | 20 +- src/ssr-template-helper.js | 77 +- src/static-template.js | 2 +- src/string.js | 2 +- src/style-installer.js | 103 +- src/style.js | 70 +- src/time.js | 2 - src/transition.js | 19 +- src/types.js | 3 +- src/url-rewrite.js | 27 +- src/url.js | 122 +- src/utils/array.js | 1 - src/utils/base64.js | 7 +- src/utils/bytes.js | 9 +- src/utils/dom-fingerprint.js | 3 - src/utils/math.js | 3 +- src/utils/pem.js | 1 - src/utils/promise.js | 25 +- src/utils/signals.js | 9 +- src/utils/story.js | 39 +- src/utils/video.js | 9 +- src/utils/xhr-utils.js | 121 +- src/validator-integration.js | 20 +- src/video-iframe-integration.js | 72 +- src/video-interface.js | 9 - src/visibility-state.js | 2 - src/web-components.js | 13 +- src/web-worker/amp-worker.js | 61 +- src/web-worker/web-worker.js | 48 +- src/window-interface.js | 1 - test/_init_tests.js | 147 +- test/e2e/test-amp-bind-brightcove.js | 43 +- test/e2e/test-amp-bind-email.js | 73 +- test/e2e/test-amp-bind-form.js | 87 +- test/e2e/test-amp-bind-iframe.js | 58 +- test/e2e/test-amp-bind-live-list.js | 125 +- test/e2e/test-amp-bind-state.js | 110 +- test/e2e/test-amp-bind-video.js | 171 +- test/e2e/test-amp-bind-youtube.js | 43 +- test/integration/test-3p-frame.js | 1024 ++-- test/integration/test-actions.js | 93 +- test/integration/test-amp-ad-3p.js | 304 +- test/integration/test-amp-ad-custom.js | 71 +- test/integration/test-amp-ad-doubleclick.js | 303 +- test/integration/test-amp-ad-fake.js | 121 +- test/integration/test-amp-analytics.js | 662 ++- test/integration/test-amp-bind.js | 564 +- test/integration/test-amp-carousel.js | 625 +- test/integration/test-amp-img.js | 179 +- test/integration/test-amp-inabox.js | 580 +- test/integration/test-amp-pixel.js | 106 +- test/integration/test-amp-recaptcha-input.js | 315 +- test/integration/test-amp-skimlinks.js | 21 +- test/integration/test-boilerplates.js | 39 +- test/integration/test-configuration.js | 53 +- test/integration/test-errors.js | 60 +- test/integration/test-extensions-loading.js | 65 +- test/integration/test-position-observer.js | 141 +- test/integration/test-released.js | 75 +- test/integration/test-toggle-display.js | 88 +- test/integration/test-user-error-reporting.js | 105 +- test/integration/test-video-manager.js | 627 ++- test/integration/test-video-players-helper.js | 705 +-- test/integration/test-video-players.js | 22 +- test/integration/test-visibility-states.js | 741 +-- test/manual/amp-script/test1.js | 1 - test/manual/test-sw.js | 2 +- test/unit/3p/test-3p-messaging.js | 12 +- test/unit/3p/test-iframe-messaging-client.js | 393 +- test/unit/3p/test-recaptcha.js | 35 +- test/unit/ads/test-adplugg.js | 13 +- test/unit/ads/test-csa.js | 3 - test/unit/ads/test-unruly.js | 20 +- .../test-inabox-frame-overlay-manager.js | 28 +- test/unit/inabox/test-inabox-host.js | 14 +- .../unit/inabox/test-inabox-messaging-host.js | 366 +- test/unit/inabox/test-inabox-viewport.js | 198 +- test/unit/inabox/test-position-observer.js | 6 +- test/unit/inabox/test-utils.js | 36 +- test/unit/test-3p-environment.js | 24 +- test/unit/test-3p.js | 219 +- test/unit/test-action.js | 949 ++-- test/unit/test-activity.js | 160 +- test/unit/test-ad-cid.js | 8 +- test/unit/test-ad-helper.js | 89 +- test/unit/test-ads-config.js | 15 +- test/unit/test-alp-handler.js | 62 +- test/unit/test-amp-context.js | 39 +- test/unit/test-amp-img.js | 182 +- test/unit/test-amp-pixel.js | 172 +- test/unit/test-ampdoc.js | 150 +- test/unit/test-analytics.js | 92 +- test/unit/test-animation.js | 118 +- test/unit/test-base-element.js | 76 +- test/unit/test-batched-json.js | 87 +- test/unit/test-batched-xhr.js | 48 +- test/unit/test-cache-cid-api.js | 165 +- test/unit/test-chunk.js | 353 +- test/unit/test-cid-api.js | 219 +- test/unit/test-cid.js | 671 ++- test/unit/test-consent.js | 20 +- test/unit/test-cookies.js | 75 +- test/unit/test-crypto.js | 92 +- test/unit/test-css.js | 19 +- test/unit/test-curve.js | 2 - test/unit/test-custom-element-registry.js | 43 +- test/unit/test-custom-element.js | 3201 ++++++----- test/unit/test-describes.js | 43 +- test/unit/test-document-info.js | 254 +- test/unit/test-document-ready.js | 15 +- test/unit/test-document-state.js | 2 - test/unit/test-document-submit.js | 73 +- test/unit/test-dom.js | 505 +- test/unit/test-element-service.js | 518 +- test/unit/test-error.js | 459 +- test/unit/test-event-helper.js | 59 +- test/unit/test-experiments.js | 237 +- test/unit/test-exponential-backoff.js | 8 +- test/unit/test-extension-analytics.js | 870 +-- test/unit/test-extension-location.js | 154 +- test/unit/test-extensions.js | 1156 ++-- test/unit/test-finite-state-machine.js | 1 - test/unit/test-fixed-layer.js | 1533 ++--- test/unit/test-focus-history.js | 10 +- test/unit/test-font-stylesheet-timeout.js | 373 +- test/unit/test-form-data-wrapper.js | 104 +- test/unit/test-form.js | 53 +- test/unit/test-friendly-iframe-embed.js | 458 +- test/unit/test-gesture-recognizers.js | 731 ++- test/unit/test-gesture.js | 62 +- test/unit/test-get-html.js | 5 +- test/unit/test-hidden-observer.js | 127 +- test/unit/test-history.js | 1084 ++-- test/unit/test-ie-media-bug.js | 113 +- test/unit/test-iframe-helper.js | 98 +- test/unit/test-iframe-stub.js | 91 +- test/unit/test-impression.js | 155 +- test/unit/test-input.js | 7 +- test/unit/test-integration.js | 113 +- .../test-intersection-observer-polyfill.js | 401 +- test/unit/test-intersection-observer.js | 59 +- test/unit/test-jank-meter.js | 28 +- test/unit/test-json.js | 13 +- test/unit/test-layers.js | 920 +-- test/unit/test-layout-delay-meter.js | 131 +- test/unit/test-layout-rect.js | 9 +- test/unit/test-layout.js | 156 +- test/unit/test-localization.js | 78 +- test/unit/test-log.js | 22 +- test/unit/test-mediasession-helper.js | 26 +- test/unit/test-mode.js | 26 +- test/unit/test-motion.js | 24 +- test/unit/test-mustache.js | 28 +- test/unit/test-navigation.js | 1263 +++-- test/unit/test-notification-ui-manager.js | 12 +- test/unit/test-object.js | 9 +- test/unit/test-observable.js | 2 - test/unit/test-origin-experiments.js | 54 +- test/unit/test-pass.js | 86 +- test/unit/test-performance.js | 721 ++- test/unit/test-platform.js | 114 +- test/unit/test-polyfill-array-includes.js | 1 - test/unit/test-polyfill-document-contains.js | 14 +- .../unit/test-polyfill-domtokenlist-toggle.js | 185 +- test/unit/test-polyfill-object-assign.js | 6 +- test/unit/test-position-observer.js | 16 +- test/unit/test-preconnect.js | 277 +- test/unit/test-pull-to-refresh.js | 19 +- test/unit/test-purifier.js | 466 +- test/unit/test-render-delaying-services.js | 18 +- test/unit/test-resource.js | 682 ++- test/unit/test-resources.js | 2249 +++++--- test/unit/test-runtime.js | 3045 +++++----- test/unit/test-sanitizer.js | 331 +- test/unit/test-service.js | 134 +- test/unit/test-shadow-embed.js | 363 +- test/unit/test-size-list.js | 141 +- test/unit/test-srcset.js | 151 +- test/unit/test-ssr-template-helper.js | 292 +- test/unit/test-standard-actions.js | 242 +- test/unit/test-static-template.js | 16 +- test/unit/test-storage.js | 514 +- test/unit/test-string.js | 10 +- test/unit/test-style-installer.js | 491 +- test/unit/test-style.js | 75 +- test/unit/test-task-queue.js | 10 +- test/unit/test-template.js | 192 +- test/unit/test-timer.js | 136 +- test/unit/test-transition.js | 26 +- test/unit/test-types.js | 27 +- test/unit/test-url-replacements.js | 1391 +++-- test/unit/test-url-rewrite.js | 144 +- test/unit/test-url.js | 796 +-- test/unit/test-variable-source.js | 284 +- ...test-video-analytics-percentage-tracker.js | 356 +- test/unit/test-video-iframe-integration.js | 49 +- test/unit/test-video-rotate-to-fullscreen.js | 49 +- test/unit/test-viewer-cid-api.js | 69 +- test/unit/test-viewer.js | 639 ++- test/unit/test-viewport-binding.js | 634 ++- test/unit/test-viewport.js | 798 +-- test/unit/test-vsync.js | 153 +- test/unit/test-web-components.js | 23 +- test/unit/test-xhr-document-fetcher.js | 130 +- test/unit/test-xhr-fetch-polyfill.js | 116 +- test/unit/test-xhr.js | 1615 +++--- test/unit/test-yield.js | 63 +- test/unit/url-expander/test-expander.js | 877 +-- test/unit/utils/test-array.js | 101 +- test/unit/utils/test-base64.js | 129 +- test/unit/utils/test-bytes.js | 346 +- test/unit/utils/test-dom-fingerprint.js | 7 +- test/unit/utils/test-math.js | 2 - test/unit/utils/test-pem.js | 30 +- test/unit/utils/test-priority-queue.js | 4 +- test/unit/utils/test-promise.js | 37 +- test/unit/utils/test-signals.js | 43 +- test/unit/utils/test-xhr-utils.js | 184 +- test/unit/web-worker/test-amp-worker.js | 132 +- testing/describes.js | 169 +- testing/fake-dom.js | 96 +- testing/iframe.js | 162 +- testing/test-helper.js | 89 +- testing/yield.js | 9 +- tools/experiments/experiments.js | 135 +- validator/chromeextension/background.js | 166 +- validator/chromeextension/content_script.js | 38 +- validator/engine/amp4ads-parse-css.js | 79 +- validator/engine/amp4ads-parse-css_test.js | 455 +- validator/engine/css-selectors.js | 205 +- validator/engine/htmlparser-interface.js | 30 +- validator/engine/htmlparser.js | 509 +- validator/engine/htmlparser_test.js | 501 +- validator/engine/json-testutil.js | 45 +- validator/engine/keyframes-parse-css.js | 31 +- validator/engine/keyframes-parse-css_test.js | 220 +- validator/engine/parse-css.js | 512 +- validator/engine/parse-css_test.js | 2430 ++++---- validator/engine/parse-srcset.js | 17 +- validator/engine/parse-srcset_test.js | 58 +- validator/engine/parse-url.js | 164 +- validator/engine/parse-url_test.js | 18 +- validator/engine/tokenize-css.js | 175 +- validator/engine/validator-in-browser.js | 43 +- validator/engine/validator.js | 3516 +++++++----- validator/engine/validator_test.js | 1204 ++-- validator/gulpjs/index.js | 81 +- validator/gulpjs/sample/gulpfile.js | 17 +- validator/gulpjs/test/validate.js | 33 +- validator/nodejs/index.js | 247 +- validator/nodejs/index_test.js | 384 +- validator/webui/webui.js | 3 +- 1614 files changed, 144554 insertions(+), 114607 deletions(-) diff --git a/3p/3d-gltf/index.js b/3p/3d-gltf/index.js index f893471cfff52..9ddf8f3db762b 100644 --- a/3p/3d-gltf/index.js +++ b/3p/3d-gltf/index.js @@ -39,14 +39,15 @@ const loadThree = (global, cb) => { const loadScriptCb = url => cb => loadScript(global, url, cb); const loadThreeExample = examplePath => loadScriptCb( - 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/' + examplePath); + 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/' + examplePath + ); seq( - loadScriptCb( - 'https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js'), - parallel( - loadThreeExample('loaders/GLTFLoader.js'), - loadThreeExample('controls/OrbitControls.js')) + loadScriptCb('https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js'), + parallel( + loadThreeExample('loaders/GLTFLoader.js'), + loadThreeExample('controls/OrbitControls.js') + ) )(cb); }; @@ -65,16 +66,22 @@ export function gltfViewer(global) { if (!e.lengthComputable) { return; } - nonSensitiveDataPostMessage('progress', dict({ - 'total': e.total, - 'loaded': e.loaded, - })); + nonSensitiveDataPostMessage( + 'progress', + dict({ + 'total': e.total, + 'loaded': e.loaded, + }) + ); }, onerror: err => { user().error('3DGLTF', err); - nonSensitiveDataPostMessage('error', dict({ - 'error': (err || '').toString(), - })); + nonSensitiveDataPostMessage( + 'error', + dict({ + 'error': (err || '').toString(), + }) + ); }, }); listenParent(global, 'action', msg => { diff --git a/3p/3d-gltf/viewer.js b/3p/3d-gltf/viewer.js index 438cfbad7e727..ea706f389725b 100644 --- a/3p/3d-gltf/viewer.js +++ b/3p/3d-gltf/viewer.js @@ -21,8 +21,7 @@ import AnimationLoop from './animation-loop'; const CAMERA_DISTANCE_FACTOR = 1; const CAMERA_FAR_FACTOR = 50; -const CAMERA_NEAR_FACTOR = .1; - +const CAMERA_NEAR_FACTOR = 0.1; export default class GltfViewer { /** @@ -45,8 +44,9 @@ export default class GltfViewer { /** @private */ this.controls_ = new THREE.OrbitControls( - this.camera_, - this.renderer_.domElement); + this.camera_, + this.renderer_.domElement + ); /** @private */ this.scene_ = new THREE.Scene(); @@ -59,7 +59,7 @@ export default class GltfViewer { /** @private */ this.ampInViewport_ = - options['initialIntersection']['intersectionRatio'] > 0; + options['initialIntersection']['intersectionRatio'] > 0; /** @private */ this.setSize_ = this.setupSize_(); @@ -86,17 +86,20 @@ export default class GltfViewer { * @private */ setModelRotation_(args) { - const xAngle = 'x' in args - ? this.getModelRotationOnAxis_(args, 'x') - : this.model_.rotation.x; + const xAngle = + 'x' in args + ? this.getModelRotationOnAxis_(args, 'x') + : this.model_.rotation.x; - const yAngle = 'y' in args - ? this.getModelRotationOnAxis_(args, 'y') - : this.model_.rotation.y; + const yAngle = + 'y' in args + ? this.getModelRotationOnAxis_(args, 'y') + : this.model_.rotation.y; - const zAngle = 'z' in args - ? this.getModelRotationOnAxis_(args, 'z') - : this.model_.rotation.z; + const zAngle = + 'z' in args + ? this.getModelRotationOnAxis_(args, 'z') + : this.model_.rotation.z; this.model_.rotation.set(xAngle, yAngle, zAngle); this.animationLoop_.needsUpdate = true; @@ -161,12 +164,12 @@ export default class GltfViewer { * * @private */ setupLight_() { - const amb = new THREE.AmbientLight(0xEDECD5, .5); + const amb = new THREE.AmbientLight(0xedecd5, 0.5); - const dir1 = new THREE.DirectionalLight(0xFFFFFF, .5); + const dir1 = new THREE.DirectionalLight(0xffffff, 0.5); dir1.position.set(0, 5, 3); - const dir2 = new THREE.DirectionalLight(0xAECDD6, .4); + const dir2 = new THREE.DirectionalLight(0xaecdd6, 0.4); dir2.position.set(-1, -2, 4); const light = new THREE.Group(); @@ -188,12 +191,14 @@ export default class GltfViewer { this.renderer_.gammaOutput = true; this.renderer_.gammaFactor = 2.2; this.renderer_.setPixelRatio( - Math.min( - this.options_['rendererSettings']['maxPixelRatio'], - devicePixelRatio)); + Math.min( + this.options_['rendererSettings']['maxPixelRatio'], + devicePixelRatio + ) + ); this.renderer_.setClearColor( - this.options_['rendererSettings']['clearColor'], - this.options_['rendererSettings']['clearAlpha'] + this.options_['rendererSettings']['clearColor'], + this.options_['rendererSettings']['clearAlpha'] ); } @@ -222,9 +227,9 @@ export default class GltfViewer { this.camera_.far = sizeLength * CAMERA_FAR_FACTOR; this.camera_.near = sizeLength * CAMERA_NEAR_FACTOR; this.camera_.position.lerpVectors( - center, - bbox.max, - 1 + CAMERA_DISTANCE_FACTOR + center, + bbox.max, + 1 + CAMERA_DISTANCE_FACTOR ); this.camera_.lookAt(center); @@ -240,23 +245,22 @@ export default class GltfViewer { loader.crossOrigin = true; loader.load( - this.options_['src'], - /** @param {{scene: !THREE.Scene}} gltfData */ - gltfData => { - this.setupCameraForObject_(gltfData.scene); - gltfData.scene.children - .slice() - .forEach(child => { - this.model_.add(child); - }); - - this.scene_.add(this.model_); - - this.animationLoop_.needsUpdate = true; - this.handlers_.onload(); - }, - this.handlers_.onprogress, - this.handlers_.onerror); + this.options_['src'], + /** @param {{scene: !THREE.Scene}} gltfData */ + gltfData => { + this.setupCameraForObject_(gltfData.scene); + gltfData.scene.children.slice().forEach(child => { + this.model_.add(child); + }); + + this.scene_.add(this.model_); + + this.animationLoop_.needsUpdate = true; + this.handlers_.onload(); + }, + this.handlers_.onprogress, + this.handlers_.onerror + ); } /** @private */ diff --git a/3p/3p.js b/3p/3p.js index 755c79e875383..238aeb153fd14 100644 --- a/3p/3p.js +++ b/3p/3p.js @@ -21,16 +21,13 @@ // Note: loaded by 3p system. Cannot rely on babel polyfills. - import {devAssert, rethrowAsync, userAssert} from '../src/log'; import {hasOwn, map} from '../src/utils/object'; import {isArray} from '../src/types'; - /** @typedef {function(!Window, !Object)} */ let ThirdPartyFunctionDef; - /** * @const {!Object} * @visibleForTesting @@ -83,8 +80,9 @@ export function run(id, win, data) { */ export function writeScript(win, url, opt_cb) { /*eslint no-useless-concat: 0*/ - win.document - .write('<' + 'script src="' + encodeURI(url) + '"><' + '/script>'); + win.document.write( + '<' + 'script src="' + encodeURI(url) + '"><' + '/script>' + ); if (opt_cb) { executeAfterWriteScript(win, opt_cb); } @@ -120,7 +118,7 @@ export function loadScript(win, url, opt_cb, opt_errorCb) { export function nextTick(win, fn) { const P = win.Promise; if (P) { - P.resolve().then/*OK*/(fn); + P.resolve().then(/*OK*/ fn); } else { win.setTimeout(fn, 0); } @@ -231,8 +229,12 @@ export function validateData(data, mandatoryFields, opt_optionalFields) { validateExactlyOne(data, field); allowedFields = allowedFields.concat(field); } else { - userAssert(data[field], - 'Missing attribute for %s: %s.', data.type, field); + userAssert( + data[field], + 'Missing attribute for %s: %s.', + data.type, + field + ); allowedFields.push(field); } } @@ -248,10 +250,12 @@ export function validateData(data, mandatoryFields, opt_optionalFields) { * @param {!Array} alternativeFields */ function validateExactlyOne(data, alternativeFields) { - userAssert(alternativeFields.filter(field => data[field]).length === 1, - '%s must contain exactly one of attributes: %s.', - data.type, - alternativeFields.join(', ')); + userAssert( + alternativeFields.filter(field => data[field]).length === 1, + '%s must contain exactly one of attributes: %s.', + data.type, + alternativeFields.join(', ') + ); } /** diff --git a/3p/ampcontext-integration.js b/3p/ampcontext-integration.js index fd75cbc36d8c3..e71a05e86c5ff 100644 --- a/3p/ampcontext-integration.js +++ b/3p/ampcontext-integration.js @@ -19,7 +19,6 @@ import {computeInMasterFrame} from './3p'; import {dev, user, userAssert} from '../src/log'; import {dict} from '../src/utils/object'; - /** * Returns the "master frame" for all widgets of a given type. * This frame should be used to e.g. fetch scripts that can @@ -31,8 +30,8 @@ import {dict} from '../src/utils/object'; */ export function masterSelection(win, type) { type = type.toLowerCase(); - const configType = adConfig[type] && - adConfig[type]['masterFrameAccessibleType']; + const configType = + adConfig[type] && adConfig[type]['masterFrameAccessibleType']; // The master has a special name. const masterName = 'frame_' + (configType || type) + '_master'; let master; @@ -52,9 +51,7 @@ export function masterSelection(win, type) { return master; } - export class IntegrationAmpContext extends AbstractAmpContext { - /** @override */ isAbstractImplementation_() { return false; @@ -67,13 +64,14 @@ export class IntegrationAmpContext extends AbstractAmpContext { updateDimensionsEnabled_() { // Only make this available to selected embeds until the generic solution is // available. - return (this.embedType_ === 'facebook' - || this.embedType_ === 'twitter' - || this.embedType_ === 'github' - || this.embedType_ === 'mathml' - || this.embedType_ === 'reddit' - || this.embedType_ === 'yotpo' - || this.embedType_ === 'embedly' + return ( + this.embedType_ === 'facebook' || + this.embedType_ === 'twitter' || + this.embedType_ === 'github' || + this.embedType_ === 'mathml' || + this.embedType_ === 'reddit' || + this.embedType_ === 'yotpo' || + this.embedType_ === 'embedly' ); } @@ -132,9 +130,12 @@ export class IntegrationAmpContext extends AbstractAmpContext { * @param {string} entityId See comment above for content. */ reportRenderedEntityIdentifier(entityId) { - this.client_.sendMessage('entity-id', dict({ - 'id': user().assertString(entityId), - })); + this.client_.sendMessage( + 'entity-id', + dict({ + 'id': user().assertString(entityId), + }) + ); } /** diff --git a/3p/ampcontext-lib.js b/3p/ampcontext-lib.js index ff691c82028ff..84a8437f86a18 100644 --- a/3p/ampcontext-lib.js +++ b/3p/ampcontext-lib.js @@ -20,15 +20,12 @@ import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-impor import {AmpContext} from './ampcontext.js'; import {initLogConstructor, setReportError} from '../src/log'; - initLogConstructor(); - // TODO(alanorozco): Refactor src/error.reportError so it does not contain big // transitive dependencies and can be included here. setReportError(() => {}); - /** * If window.context does not exist, we must instantiate a replacement and * assign it to window.context, to provide the creative with all the required diff --git a/3p/ampcontext.js b/3p/ampcontext.js index 3865d900d9351..c8e9c5b2a0a1d 100644 --- a/3p/ampcontext.js +++ b/3p/ampcontext.js @@ -24,13 +24,14 @@ import {parseUrlDeprecated} from '../src/url'; import {tryParseJson} from '../src/json'; export class AbstractAmpContext { - /** * @param {!Window} win The window that the instance is built inside. */ constructor(win) { - devAssert(!this.isAbstractImplementation_(), - 'Should not construct AbstractAmpContext instances directly'); + devAssert( + !this.isAbstractImplementation_(), + 'Should not construct AbstractAmpContext instances directly' + ); /** @protected {!Window} */ this.win_ = win; @@ -128,12 +129,13 @@ export class AbstractAmpContext { /** Registers an general handler for page visibility. */ listenForPageVisibility_() { this.client_.makeRequest( - MessageType.SEND_EMBED_STATE, - MessageType.EMBED_STATE, - data => { - this.hidden = data['pageHidden']; - this.dispatchVisibilityChangeEvent_(); - }); + MessageType.SEND_EMBED_STATE, + MessageType.EMBED_STATE, + data => { + this.hidden = data['pageHidden']; + this.dispatchVisibilityChangeEvent_(); + } + ); } /** @@ -169,11 +171,12 @@ export class AbstractAmpContext { */ observeIntersection(callback) { const unlisten = this.client_.makeRequest( - MessageType.SEND_INTERSECTIONS, - MessageType.INTERSECTION, - intersection => { - callback(intersection['changes']); - }); + MessageType.SEND_INTERSECTIONS, + MessageType.INTERSECTION, + intersection => { + callback(intersection['changes']); + } + ); if (!isExperimentOn('no-initial-intersection')) { // eslint-disable-line // Call the callback with the value that was transmitted when the @@ -196,10 +199,14 @@ export class AbstractAmpContext { * @param {function(*)} callback to be invoked with the HTML string */ getHtml(selector, attributes, callback) { - this.client_.getData(MessageType.GET_HTML, dict({ - 'selector': selector, - 'attributes': attributes, - }), callback); + this.client_.getData( + MessageType.GET_HTML, + dict({ + 'selector': selector, + 'attributes': attributes, + }), + callback + ); } /** @@ -208,8 +215,7 @@ export class AbstractAmpContext { * @param {function(*)} callback */ getConsentState(callback) { - this.client_.getData( - MessageType.GET_CONSENT_STATE, null, callback); + this.client_.getData(MessageType.GET_CONSENT_STATE, null, callback); } /** @@ -220,11 +226,14 @@ export class AbstractAmpContext { * @param {boolean=} hasOverflow Whether the ad handles its own overflow ele */ requestResize(width, height, hasOverflow) { - this.client_.sendMessage(MessageType.EMBED_SIZE, dict({ - 'width': width, - 'height': height, - 'hasOverflow': hasOverflow, - })); + this.client_.sendMessage( + MessageType.EMBED_SIZE, + dict({ + 'width': width, + 'height': height, + 'hasOverflow': hasOverflow, + }) + ); } /** @@ -236,7 +245,8 @@ export class AbstractAmpContext { */ onResizeSuccess(callback) { this.client_.registerCallback(MessageType.EMBED_SIZE_CHANGED, obj => { - callback(obj['requestedHeight'], obj['requestedWidth']); }); + callback(obj['requestedHeight'], obj['requestedWidth']); + }); } /** @@ -278,8 +288,9 @@ export class AbstractAmpContext { setupMetadata_(data) { // TODO(alanorozco): Use metadata utils in 3p/frame-metadata const dataObject = devAssert( - typeof data === 'string' ? tryParseJson(data) : data, - 'Could not setup metadata.'); + typeof data === 'string' ? tryParseJson(data) : data, + 'Could not setup metadata.' + ); const context = dataObject._context || dataObject.attributes._context; @@ -327,7 +338,7 @@ export class AbstractAmpContext { // Add window keeping the top-most one at the front. ancestors.push(win.parent); } - return ancestors[(ancestors.length - 1) - depth]; + return ancestors[ancestors.length - 1 - depth]; } /** @@ -341,7 +352,7 @@ export class AbstractAmpContext { // TODO(alanorozco): why the heck could AMP_CONTEXT_DATA be two different // types? FIX THIS. if (isObject(this.win_.sf_) && this.win_.sf_.cfg) { - this.setupMetadata_(/** @type {string}*/(this.win_.sf_.cfg)); + this.setupMetadata_(/** @type {string}*/ (this.win_.sf_.cfg)); } else if (this.win_.AMP_CONTEXT_DATA) { if (typeof this.win_.AMP_CONTEXT_DATA == 'string') { this.sentinel = this.win_.AMP_CONTEXT_DATA; @@ -361,9 +372,12 @@ export class AbstractAmpContext { if (!e.message) { return; } - this.client_.sendMessage(MessageType.USER_ERROR_IN_IFRAME, dict({ - 'message': e.message, - })); + this.client_.sendMessage( + MessageType.USER_ERROR_IN_IFRAME, + dict({ + 'message': e.message, + }) + ); } } diff --git a/3p/beopinion.js b/3p/beopinion.js index c26bf1bd55098..394956c6a7657 100644 --- a/3p/beopinion.js +++ b/3p/beopinion.js @@ -100,7 +100,7 @@ function getBeOpinionAsyncInit(global, accountId) { }, onHeightChange: function(newHeight) { const c = global.document.getElementById('c'); - const boundingClientRect = c./*REVIEW*/getBoundingClientRect(); + const boundingClientRect = c./*REVIEW*/ getBoundingClientRect(); context.onResizeDenied(context.requestResize.bind(context)); context.requestResize(boundingClientRect.width, newHeight); }, diff --git a/3p/bodymovinanimation.js b/3p/bodymovinanimation.js index da00292144075..55a58ce55321c 100644 --- a/3p/bodymovinanimation.js +++ b/3p/bodymovinanimation.js @@ -34,9 +34,10 @@ let animationHandler; * @param {!Function} cb */ function getBodymovinAnimationSdk(global, renderer, cb) { - const scriptToLoad = renderer === 'svg' ? - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' : - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; + const scriptToLoad = + renderer === 'svg' + ? 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' + : 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; loadScript(global, scriptToLoad, function() { cb(global.bodymovin); }); @@ -58,8 +59,9 @@ function parseMessage(event) { if (eventMessage['valueType'] === 'time') { animationHandler.goToAndStop(eventMessage['value']); } else { - const frameNumber = - Math.round(eventMessage['value'] * animationHandler.totalFrames); + const frameNumber = Math.round( + eventMessage['value'] * animationHandler.totalFrames + ); animationHandler.goToAndStop(frameNumber, true); } } @@ -89,11 +91,12 @@ export function bodymovinanimation(global) { autoplay: dataReceived['autoplay'], animationData: dataReceived['animationData'], }); - const message = JSON.stringify(dict({ - 'action': 'ready', - })); + const message = JSON.stringify( + dict({ + 'action': 'ready', + }) + ); global.addEventListener('message', parseMessage, false); - global.parent. /*OK*/postMessage(message, '*'); + global.parent./*OK*/ postMessage(message, '*'); }); } - diff --git a/3p/embedly.js b/3p/embedly.js index 863933768c8e8..aeaf9420bf722 100644 --- a/3p/embedly.js +++ b/3p/embedly.js @@ -84,10 +84,7 @@ export function embedly(global, data) { // Add whitelisted data attributes and values to card // when these are provided by component. for (const key in CardOptions) { - if ( - hasOwn(CardOptions, key) && - typeof data[key] !== 'undefined' - ) { + if (hasOwn(CardOptions, key) && typeof data[key] !== 'undefined') { card.setAttribute(`data-${CardOptions[key]}`, data[key]); } } @@ -111,8 +108,8 @@ export function embedly(global, data) { // Use embedly SDK to listen to resize event from loaded card global.window['embedly']('on', RESIZE_EVENT_NAME, function(iframe) { context.requestResize( - iframe./*OK*/width, - parseInt(iframe./*OK*/height, 10) + /* margin */ 5 + iframe./*OK*/ width, + parseInt(iframe./*OK*/ height, 10) + /* margin */ 5 ); }); }); diff --git a/3p/environment.js b/3p/environment.js index a1213667a97dd..b531265ca4b9b 100644 --- a/3p/environment.js +++ b/3p/environment.js @@ -43,7 +43,7 @@ export function manageWin(win) { manageWin_(win); } catch (e) { // We use a try block, because the ad integrations often swallow errors. - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } } @@ -65,7 +65,6 @@ function manageWin_(win) { blockSyncPopups(win); } - /** * Add instrumentation code to doc.write. * @param {!Window} parent @@ -115,15 +114,14 @@ function maybeInstrumentsNodes(win, addedNodes) { } const src = node.getAttribute('src'); const srcdoc = node.getAttribute('srcdoc'); - if (src == null || /^(about:|javascript:)/i.test(src.trim()) || - srcdoc) { + if (src == null || /^(about:|javascript:)/i.test(src.trim()) || srcdoc) { if (node.contentWindow) { instrumentIframeWindow(node, win, node.contentWindow); node.addEventListener('load', () => { try { instrumentIframeWindow(node, win, node.contentWindow); } catch (e) { - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } }); } else if (srcdoc) { @@ -131,7 +129,7 @@ function maybeInstrumentsNodes(win, addedNodes) { } } } catch (e) { - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } } } @@ -196,7 +194,7 @@ function instrumentEntryPoints(win) { next(); if (typeof fn == 'string') { // Handle rare and dangerous string arg case. - return (0, win.eval/*NOT OK but whatcha gonna do.*/).call(win, fn); // lgtm [js/useless-expression] + return (0, win.eval /*NOT OK but whatcha gonna do.*/).call(win, fn); // lgtm [js/useless-expression] } else { return fn.apply(this, arguments); } @@ -245,7 +243,7 @@ function blockSyncPopups(win) { return false; }; } catch (e) { - console./*OK*/error(e.message, e.stack); + console./*OK*/ error(e.message, e.stack); } } diff --git a/3p/facebook.js b/3p/facebook.js index 7e08373c7357c..067d5b0d0c376 100644 --- a/3p/facebook.js +++ b/3p/facebook.js @@ -32,9 +32,13 @@ import {userAssert} from '../src/log'; * @param {string} locale */ function getFacebookSdk(global, cb, locale) { - loadScript(global, 'https://connect.facebook.net/' + locale + '/sdk.js', () => { - cb(global.FB); - }); + loadScript( + global, + 'https://connect.facebook.net/' + locale + '/sdk.js', + () => { + cb(global.FB); + } + ); } /** @@ -100,8 +104,10 @@ function getCommentContainer(global, data) { const c = global.document.getElementById('c'); const container = createContainer(global, 'comment-embed', data.href); container.setAttribute( - 'data-include-parent', data.includeCommentParent || 'false'); - container.setAttribute('data-width', c./*OK*/offsetWidth); + 'data-include-parent', + data.includeCommentParent || 'false' + ); + container.setAttribute('data-width', c./*OK*/ offsetWidth); return container; } @@ -123,9 +129,12 @@ function getDefaultEmbedAs(href) { function getEmbedContainer(global, data) { const embedAs = data.embedAs || getDefaultEmbedAs(data.href); - userAssert(['post', 'video', 'comment'].indexOf(embedAs) !== -1, - 'Attribute data-embed-as for value is wrong, should be' + - ' "post", "video" or "comment" but was: %s', embedAs); + userAssert( + ['post', 'video', 'comment'].indexOf(embedAs) !== -1, + 'Attribute data-embed-as for value is wrong, should be' + + ' "post", "video" or "comment" but was: %s', + embedAs + ); switch (embedAs) { case 'comment': @@ -157,7 +166,7 @@ function getPageContainer(global, data) { // Note: The facebook embed allows a maximum width of 500px. // If the container's width exceeds that, the embed's width will // be clipped to 500px. - container.setAttribute('data-width', c./*OK*/offsetWidth); + container.setAttribute('data-width', c./*OK*/ offsetWidth); return container; } @@ -211,30 +220,36 @@ export function facebook(global, data) { container = getLikeContainer(global, data); } else if (extension === 'AMP-FACEBOOK-COMMENTS') { container = getCommentsContainer(global, data); - } else /*AMP-FACEBOOK */ { + } /*AMP-FACEBOOK */ else { container = getEmbedContainer(global, data); } global.document.getElementById('c').appendChild(container); - getFacebookSdk(global, FB => { - // Dimensions are given by the parent frame. - delete data.width; - delete data.height; + getFacebookSdk( + global, + FB => { + // Dimensions are given by the parent frame. + delete data.width; + delete data.height; - FB.Event.subscribe('xfbml.resize', event => { - context.updateDimensions( + FB.Event.subscribe('xfbml.resize', event => { + context.updateDimensions( parseInt(event.width, 10), - parseInt(event.height, 10) + /* margins */ 20); - }); - - FB.init({xfbml: true, version: 'v2.5'}); - - // Report to parent that the SDK has loaded and is ready to paint - const message = JSON.stringify(dict({ - 'action': 'ready', - })); - global.parent. /*OK*/postMessage(message, '*'); - - }, data.locale ? data.locale : dashToUnderline(window.navigator.language)); + parseInt(event.height, 10) + /* margins */ 20 + ); + }); + + FB.init({xfbml: true, version: 'v2.5'}); + + // Report to parent that the SDK has loaded and is ready to paint + const message = JSON.stringify( + dict({ + 'action': 'ready', + }) + ); + global.parent./*OK*/ postMessage(message, '*'); + }, + data.locale ? data.locale : dashToUnderline(window.navigator.language) + ); } diff --git a/3p/frame-metadata.js b/3p/frame-metadata.js index 3d101052e9e91..c1ba8732b3c75 100644 --- a/3p/frame-metadata.js +++ b/3p/frame-metadata.js @@ -21,7 +21,6 @@ import {once} from '../src/utils/function.js'; import {parseJson} from '../src/json'; import {parseUrlDeprecated} from '../src/url'; - /** * @typedef {{ * ampcontextFilepath: ?string, @@ -46,7 +45,6 @@ import {parseUrlDeprecated} from '../src/url'; */ export let ContextStateDef; - /** @const {!JsonObject} */ const FALLBACK = dict({ 'attributes': dict({ @@ -54,7 +52,6 @@ const FALLBACK = dict({ }), }); - /** * Gets metadata encoded in iframe name attribute. * @return {!JsonObject} @@ -68,14 +65,12 @@ const allMetadata = once(() => { return parseJson(iframeName); } catch (err) { if (!getMode().test) { - dev().info( - 'INTEGRATION', 'Could not parse context from:', iframeName); + dev().info('INTEGRATION', 'Could not parse context from:', iframeName); } return FALLBACK; } }); - /** * @return {{mode: !Object, experimentToggles: !Object}} */ @@ -88,7 +83,6 @@ export function getAmpConfig() { }; } - /** * @return {!JsonObject} */ @@ -103,7 +97,6 @@ const getAttributeDataImpl_ = once(() => { return data; }); - /** * @return {!JsonObject} */ @@ -112,7 +105,6 @@ export function getAttributeData() { return getAttributeDataImpl_(); } - /** * @return {!Location} */ @@ -121,7 +113,6 @@ const getLocationImpl_ = once(() => { return parseUrlDeprecated(href); }); - /** * @return {!Location} */ @@ -130,7 +121,6 @@ export function getLocation() { return getLocationImpl_(); } - /** * @return {!ContextStateDef} */ @@ -158,7 +148,6 @@ export function getContextState() { }; } - /** * @return {string} */ diff --git a/3p/github.js b/3p/github.js index b7859ae6d7219..b97a14ec3a5e9 100644 --- a/3p/github.js +++ b/3p/github.js @@ -40,12 +40,13 @@ function getGistJs(global, scriptSource, cb) { */ export function github(global, data) { userAssert( - data.gistid, - 'The data-gistid attribute is required for %s', - data.element); + data.gistid, + 'The data-gistid attribute is required for %s', + data.element + ); let gistUrl = - 'https://gist.github.com/' + encodeURIComponent(data.gistid) + '.js'; + 'https://gist.github.com/' + encodeURIComponent(data.gistid) + '.js'; if (data.file) { gistUrl += '?file=' + encodeURIComponent(data.file); @@ -65,8 +66,8 @@ export function github(global, data) { } context.updateDimensions( - gistContainer./*OK*/offsetWidth, - gistContainer./*OK*/offsetHeight + gistContainer./*OK*/ offsetWidth, + gistContainer./*OK*/ offsetHeight ); }); } diff --git a/3p/iframe-messaging-client.js b/3p/iframe-messaging-client.js index 9fc838a24a5fa..3736f78dfa25a 100644 --- a/3p/iframe-messaging-client.js +++ b/3p/iframe-messaging-client.js @@ -26,7 +26,6 @@ import {getData} from '../src/event-helper'; import {getMode} from '../src/mode'; export class IframeMessagingClient { - /** * @param {!Window} win A window object. */ @@ -126,11 +125,15 @@ export class IframeMessagingClient { * @param {JsonObject=} opt_payload The payload of message to send. */ sendMessage(type, opt_payload) { - this.hostWindow_.postMessage/*OK*/( - serializeMessage( - type, dev().assertString(this.sentinel_), - opt_payload, this.rtvVersion_), - '*'); + this.hostWindow_.postMessage( + /*OK*/ serializeMessage( + type, + dev().assertString(this.sentinel_), + opt_payload, + this.rtvVersion_ + ), + '*' + ); } /** diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index a0c9fd9951cac..142879bb91536 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -27,7 +27,6 @@ const TAG_ = 'iframe-transport-client'; * creatives. */ export class IframeTransportClient { - /** @param {!Window} win */ constructor(win) { /** @private {!Window} */ @@ -39,47 +38,63 @@ export class IframeTransportClient { const parsedFrameName = tryParseJson(this.win_.name); /** @private {string} */ - this.vendor_ = dev().assertString(parsedFrameName['type'], - 'Parent frame must supply vendor name as type in ' + - this.win_.location.href); + this.vendor_ = dev().assertString( + parsedFrameName['type'], + 'Parent frame must supply vendor name as type in ' + + this.win_.location.href + ); // Note: amp-ad-exit will validate the vendor name before performing // variable substitution, so if the vendor name is not a valid one from // vendors.js, then its response messages will have no effect. - devAssert(this.vendor_.length, 'Vendor name cannot be empty in ' + - this.win_.location.href); + devAssert( + this.vendor_.length, + 'Vendor name cannot be empty in ' + this.win_.location.href + ); /** @protected {!IframeMessagingClient} */ this.iframeMessagingClient_ = new IframeMessagingClient(win); this.iframeMessagingClient_.setHostWindow(this.win_.parent); - this.iframeMessagingClient_.setSentinel(dev().assertString( + this.iframeMessagingClient_.setSentinel( + dev().assertString( parsedFrameName['sentinel'], - 'Invalid/missing sentinel on iframe name attribute' + this.win_.name)); + 'Invalid/missing sentinel on iframe name attribute' + this.win_.name + ) + ); this.iframeMessagingClient_.makeRequest( - MessageType.SEND_IFRAME_TRANSPORT_EVENTS, - MessageType.IFRAME_TRANSPORT_EVENTS, - eventData => { - const events = + MessageType.SEND_IFRAME_TRANSPORT_EVENTS, + MessageType.IFRAME_TRANSPORT_EVENTS, + eventData => { + const events = /** * @type * {!Array<../src/3p-frame-messaging.IframeTransportEvent>} */ - (eventData['events']); - devAssert(events, - 'Received malformed events list in ' + this.win_.location.href); - devAssert(events.length, - 'Received empty events list in ' + this.win_.location.href); - events.forEach(event => { - try { - devAssert(event.creativeId, - 'Received malformed event in ' + this.win_.location.href); - this.contextFor_(event.creativeId).dispatch(event.message); - } catch (e) { - user().error(TAG_, - 'Exception in callback passed to onAnalyticsEvent', - e); - } - }); + eventData['events']; + devAssert( + events, + 'Received malformed events list in ' + this.win_.location.href + ); + devAssert( + events.length, + 'Received empty events list in ' + this.win_.location.href + ); + events.forEach(event => { + try { + devAssert( + event.creativeId, + 'Received malformed event in ' + this.win_.location.href + ); + this.contextFor_(event.creativeId).dispatch(event.message); + } catch (e) { + user().error( + TAG_, + 'Exception in callback passed to onAnalyticsEvent', + e + ); + } }); + } + ); } /** @@ -90,10 +105,15 @@ export class IframeTransportClient { * @private */ contextFor_(creativeId) { - return this.creativeIdToContext_[creativeId] || - (this.creativeIdToContext_[creativeId] = - new IframeTransportContext(this.win_, this.iframeMessagingClient_, - creativeId, this.vendor_)); + return ( + this.creativeIdToContext_[creativeId] || + (this.creativeIdToContext_[creativeId] = new IframeTransportContext( + this.win_, + this.iframeMessagingClient_, + creativeId, + this.vendor_ + )) + ); } /** @@ -127,9 +147,11 @@ export class IframeTransportContext { /** @private {?function(string)} */ this.listener_ = null; - userAssert(win['onNewContextInstance'] && + userAssert( + win['onNewContextInstance'] && typeof win['onNewContextInstance'] == 'function', - 'Must implement onNewContextInstance in ' + win.location.href); + 'Must implement onNewContextInstance in ' + win.location.href + ); win['onNewContextInstance'](this); } @@ -158,9 +180,10 @@ export class IframeTransportContext { * @param {!Object} data */ sendResponseToCreative(data) { - this.iframeMessagingClient_./*OK*/sendMessage( - MessageType.IFRAME_TRANSPORT_RESPONSE, - /** @type {!JsonObject} */ - (Object.assign({message: data}, this.baseMessage_))); + this.iframeMessagingClient_./*OK*/ sendMessage( + MessageType.IFRAME_TRANSPORT_RESPONSE, + /** @type {!JsonObject} */ + (Object.assign({message: data}, this.baseMessage_)) + ); } } diff --git a/3p/integration.js b/3p/integration.js index 3ed5db951db71..d6efa47042b2f 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -25,16 +25,10 @@ // src/polyfills.js must be the first import. import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 -import { - IntegrationAmpContext, -} from './ampcontext-integration'; +import {IntegrationAmpContext} from './ampcontext-integration'; import {dict} from '../src/utils/object.js'; import {endsWith} from '../src/string'; -import { - getAmpConfig, - getEmbedType, - getLocation, -} from './frame-metadata'; +import {getAmpConfig, getEmbedType, getLocation} from './frame-metadata'; import {getMode} from '../src/mode'; import {getSourceUrl, isProxyOrigin, parseUrlDeprecated} from '../src/url'; import { @@ -45,11 +39,7 @@ import { } from '../src/log'; import {installEmbedStateListener, manageWin} from './environment'; import {parseJson} from '../src/json'; -import { - register, - run, - setExperimentToggles, -} from './3p'; +import {register, run, setExperimentToggles} from './3p'; import {startsWith} from '../src/string.js'; import {urls} from '../src/config'; import {version} from '../src/internal-version'; @@ -267,7 +257,6 @@ import {zergnet} from '../ads/zergnet'; import {zucks} from '../ads/zucks'; import {speakol} from '../ads/speakol'; - /** * Whether the embed type may be used with amp-embed tag. * @const {!Object} @@ -301,7 +290,6 @@ const AMP_EMBED_ALLOWED = { init(window); - if (getMode().test || getMode().localDev) { register('_ping_', _ping_); } @@ -363,7 +351,7 @@ register('caprofitx', caprofitx); register('cedato', cedato); register('chargeads', chargeads); register('colombia', colombia); -register('connatix',connatix); +register('connatix', connatix); register('contentad', contentad); register('criteo', criteo); register('csa', csa); @@ -498,7 +486,7 @@ register('weborama-display', weboramaDisplay); register('widespace', widespace); register('wisteria', wisteria); register('wpmedia', wpmedia); -register('xlift' , xlift); +register('xlift', xlift); register('yahoo', yahoo); register('yahoojp', yahoojp); register('yandex', yandex); @@ -528,7 +516,6 @@ const defaultAllowedTypesInCustomFrame = [ '_ping_', ]; - /** * Initialize 3p frame. * @param {!Window} win @@ -545,7 +532,6 @@ function init(win) { setExperimentToggles(config.experimentToggles); } - /** * Visible for testing. * Draws a 3p embed to the window. Expects the data to include the 3p type. @@ -560,12 +546,15 @@ function init(win) { export function draw3p(win, data, configCallback) { const type = data['type']; - userAssert(isTagNameAllowed(type, win.context.tagName), - 'Embed type %s not allowed with tag %s', type, win.context.tagName); + userAssert( + isTagNameAllowed(type, win.context.tagName), + 'Embed type %s not allowed with tag %s', + type, + win.context.tagName + ); if (configCallback) { configCallback(data, data => { - userAssert(data, - 'Expected configuration to be passed as first argument'); + userAssert(data, 'Expected configuration to be passed as first argument'); run(type, win, data); }); } else { @@ -585,8 +574,11 @@ export function draw3p(win, data, configCallback) { * @param {!Array=} opt_allowedEmbeddingOrigins List of domain suffixes * that are allowed to embed this frame. */ -window.draw3p = function(opt_configCallback, opt_allowed3pTypes, - opt_allowedEmbeddingOrigins) { +window.draw3p = function( + opt_configCallback, + opt_allowed3pTypes, + opt_allowedEmbeddingOrigins +) { try { const location = getLocation(); @@ -604,9 +596,10 @@ window.draw3p = function(opt_configCallback, opt_allowed3pTypes, // and the compiler not being able to discern otherwise // TODO(alanorozco): Do this more elegantly once old impl is cleaned up. draw3p( - window, - (/** @type {!IntegrationAmpContext} */ (window.context)).data || {}, - opt_configCallback); + window, + /** @type {!IntegrationAmpContext} */ (window.context).data || {}, + opt_configCallback + ); window.context.bootstrapLoaded(); } catch (e) { @@ -642,9 +635,12 @@ export function validateParentOrigin(window, parentLocation) { if (!ancestors || !ancestors.length) { return; } - userAssert(ancestors[0] == parentLocation.origin, - 'Parent origin mismatch: %s, %s', - ancestors[0], parentLocation.origin); + userAssert( + ancestors[0] == parentLocation.origin, + 'Parent origin mismatch: %s, %s', + ancestors[0], + parentLocation.origin + ); } /** @@ -670,8 +666,11 @@ export function validateAllowedTypes(window, type, allowedTypes) { if (defaultAllowedTypesInCustomFrame.indexOf(type) != -1) { return; } - userAssert(allowedTypes && allowedTypes.indexOf(type) != -1, - 'Non-whitelisted 3p type for custom iframe: %s', type); + userAssert( + allowedTypes && allowedTypes.indexOf(type) != -1, + 'Non-whitelisted 3p type for custom iframe: %s', + type + ); } /** @@ -694,7 +693,7 @@ export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { // the referrer. The referrer is used because it should be // trustable. hostname = parseUrlDeprecated(getSourceUrl(window.document.referrer)) - .hostname; + .hostname; } for (let i = 0; i < allowedHostnames.length; i++) { // Either the hostname is exactly as whitelisted… @@ -706,8 +705,9 @@ export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { return; } } - throw new Error('Invalid embedding hostname: ' + hostname + ' not in ' - + allowedHostnames); + throw new Error( + 'Invalid embedding hostname: ' + hostname + ' not in ' + allowedHostnames + ); } /** @@ -768,10 +768,16 @@ export function isTagNameAllowed(type, tagName) { * @param {boolean} isCanary */ function lightweightErrorReport(e, isCanary) { - new Image().src = urls.errorReporting + - '?3p=1&v=' + encodeURIComponent(version()) + - '&m=' + encodeURIComponent(e.message) + - '&ca=' + (isCanary ? 1 : 0) + - '&r=' + encodeURIComponent(document.referrer) + - '&s=' + encodeURIComponent(e.stack || ''); + new Image().src = + urls.errorReporting + + '?3p=1&v=' + + encodeURIComponent(version()) + + '&m=' + + encodeURIComponent(e.message) + + '&ca=' + + (isCanary ? 1 : 0) + + '&r=' + + encodeURIComponent(document.referrer) + + '&s=' + + encodeURIComponent(e.stack || ''); } diff --git a/3p/mathml.js b/3p/mathml.js index 23c88934b2a7d..ffe0d42133a8f 100644 --- a/3p/mathml.js +++ b/3p/mathml.js @@ -40,43 +40,44 @@ function getMathmlJs(global, scriptSource, cb) { */ export function mathml(global, data) { userAssert( - data.formula, - 'The formula attribute is required for %s', - data.element); + data.formula, + 'The formula attribute is required for %s', + data.element + ); getMathmlJs( - global, - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML', - mathjax => { - // Dimensions are given by the parent frame. - delete data.width; - delete data.height; - const div = document.createElement('div'); - div.setAttribute('id', 'mathmlformula'); - div.textContent = data.formula; - setStyle(div, 'visibility', 'hidden'); - global.document.body.appendChild(div); - mathjax.Hub.Config({ - showMathMenu: false, - }); - mathjax.Hub.Queue(function() { - const rendered = document.getElementById('MathJax-Element-1-Frame'); - // Remove built in mathjax margins. - let display = document.getElementsByClassName('MJXc-display'); - if (!display[0]) { - const span = document.createElement('span'); - span.setAttribute('class', 'mjx-chtml MJXc-display'); - span.appendChild(rendered); - div.appendChild(span); - display = document.getElementsByClassName('MJXc-display'); - } - display[0].setAttribute('style','margin-top:0;margin-bottom:0'); - context.requestResize( - rendered./*OK*/offsetWidth, - rendered./*OK*/offsetHeight - ); - setStyle(div, 'visibility', 'visible'); - }); - } + global, + 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML', + mathjax => { + // Dimensions are given by the parent frame. + delete data.width; + delete data.height; + const div = document.createElement('div'); + div.setAttribute('id', 'mathmlformula'); + div.textContent = data.formula; + setStyle(div, 'visibility', 'hidden'); + global.document.body.appendChild(div); + mathjax.Hub.Config({ + showMathMenu: false, + }); + mathjax.Hub.Queue(function() { + const rendered = document.getElementById('MathJax-Element-1-Frame'); + // Remove built in mathjax margins. + let display = document.getElementsByClassName('MJXc-display'); + if (!display[0]) { + const span = document.createElement('span'); + span.setAttribute('class', 'mjx-chtml MJXc-display'); + span.appendChild(rendered); + div.appendChild(span); + display = document.getElementsByClassName('MJXc-display'); + } + display[0].setAttribute('style', 'margin-top:0;margin-bottom:0'); + context.requestResize( + rendered./*OK*/ offsetWidth, + rendered./*OK*/ offsetHeight + ); + setStyle(div, 'visibility', 'visible'); + }); + } ); } diff --git a/3p/messaging.js b/3p/messaging.js index fc72a450ec943..c9d849a5d5b45 100644 --- a/3p/messaging.js +++ b/3p/messaging.js @@ -30,8 +30,7 @@ export function nonSensitiveDataPostMessage(type, opt_object) { const object = opt_object || /** @type {JsonObject} */ ({}); object['type'] = type; object['sentinel'] = window.context.sentinel; - window.parent./*OK*/postMessage(object, - window.context.location.origin); + window.parent./*OK*/ postMessage(object, window.context.location.origin); } /** @@ -76,15 +75,18 @@ function startListening(win) { win.addEventListener('message', function(event) { // Cheap operations first, so we don't parse JSON unless we have to. const eventData = getData(event); - if (event.source != win.parent || - event.origin != win.context.location.origin || - typeof eventData != 'string' || - eventData.indexOf('amp-') != 0) { + if ( + event.source != win.parent || + event.origin != win.context.location.origin || + typeof eventData != 'string' || + eventData.indexOf('amp-') != 0 + ) { return; } // Parse JSON only once per message. - const data = /** @type {!JsonObject} */ ( - parseJson(/**@type {string} */ (getData(event)).substr(4))); + const data = /** @type {!JsonObject} */ (parseJson( + /**@type {string} */ (getData(event).substr(4)) + )); if (win.context.sentinel && data['sentinel'] != win.context.sentinel) { return; } diff --git a/3p/polyfills.js b/3p/polyfills.js index f302d58898931..9d2545379b7af 100644 --- a/3p/polyfills.js +++ b/3p/polyfills.js @@ -18,7 +18,6 @@ * @fileoverview Loads all polyfills needed by the AMP 3p integration frame. */ - // This list should not get longer without a very good reason. import {install as installMathSign} from '../src/polyfills/math-sign'; import {install as installObjectAssign} from '../src/polyfills/object-assign'; diff --git a/3p/recaptcha.js b/3p/recaptcha.js index df94d94bb0da2..10945afd9dd84 100644 --- a/3p/recaptcha.js +++ b/3p/recaptcha.js @@ -17,8 +17,7 @@ // src/polyfills.js must be the first import. import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 -import ampToolboxCacheUrl from - '../third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm'; +import ampToolboxCacheUrl from '../third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm'; import {IframeMessagingClient} from './iframe-messaging-client'; import { @@ -79,7 +78,6 @@ init(); * @param {string} recaptchaApiBaseUrl */ export function initRecaptcha(recaptchaApiBaseUrl = RECAPTCHA_API_URL) { - const win = window; /** @@ -97,22 +95,27 @@ export function initRecaptcha(recaptchaApiBaseUrl = RECAPTCHA_API_URL) { // Get our sitekey from the iframe name attribute devAssert( - dataObject.sitekey, - 'The sitekey is required for the iframe' + dataObject.sitekey, + 'The sitekey is required for the iframe' ); sitekey = dataObject.sitekey; const recaptchaApiUrl = recaptchaApiBaseUrl + sitekey; - loadScript(win, recaptchaApiUrl, function() { - const {grecaptcha} = win; + loadScript( + win, + recaptchaApiUrl, + function() { + const {grecaptcha} = win; - grecaptcha.ready(function() { - initializeIframeMessagingClient(win, grecaptcha, dataObject); - iframeMessagingClient./*OK*/sendMessage('amp-recaptcha-ready'); - }); - }, function() { - dev().error(TAG + ' Failed to load recaptcha api script'); - }); + grecaptcha.ready(function() { + initializeIframeMessagingClient(win, grecaptcha, dataObject); + iframeMessagingClient./*OK*/ sendMessage('amp-recaptcha-ready'); + }); + }, + function() { + dev().error(TAG + ' Failed to load recaptcha api script'); + } + ); } window.initRecaptcha = initRecaptcha; @@ -126,8 +129,8 @@ function initializeIframeMessagingClient(win, grecaptcha, dataObject) { iframeMessagingClient = new IframeMessagingClient(win); iframeMessagingClient.setSentinel(dataObject.sentinel); iframeMessagingClient.registerCallback( - 'amp-recaptcha-action', - actionTypeHandler.bind(this, win, grecaptcha) + 'amp-recaptcha-action', + actionTypeHandler.bind(this, win, grecaptcha) ); } @@ -144,27 +147,38 @@ function initializeIframeMessagingClient(win, grecaptcha, dataObject) { * @param {Object} data */ function actionTypeHandler(win, grecaptcha, data) { - doesOriginDomainMatchIframeSrc(win, data).then(() => { - const executePromise = grecaptcha.execute(sitekey, { - action: data.action, - }); + doesOriginDomainMatchIframeSrc(win, data) + .then(() => { + const executePromise = grecaptcha.execute(sitekey, { + action: data.action, + }); - // .then() promise pollyfilled by recaptcha api script - executePromise./*OK*/then(function(token) { - iframeMessagingClient./*OK*/sendMessage('amp-recaptcha-token', dict({ - 'id': data.id, - 'token': token, - })); - }, function(err) { - user().error(TAG, '%s', err.message); - iframeMessagingClient./*OK*/sendMessage('amp-recaptcha-error', dict({ - 'id': data.id, - 'error': err.message, - })); + // .then() promise pollyfilled by recaptcha api script + executePromise./*OK*/ then( + function(token) { + iframeMessagingClient./*OK*/ sendMessage( + 'amp-recaptcha-token', + dict({ + 'id': data.id, + 'token': token, + }) + ); + }, + function(err) { + user().error(TAG, '%s', err.message); + iframeMessagingClient./*OK*/ sendMessage( + 'amp-recaptcha-error', + dict({ + 'id': data.id, + 'error': err.message, + }) + ); + } + ); + }) + .catch(error => { + dev().error(TAG, '%s', error.message); }); - }).catch(error => { - dev().error(TAG, '%s', error.message); - }); } /** @@ -175,11 +189,8 @@ function actionTypeHandler(win, grecaptcha, data) { * @return {!Promise} */ export function doesOriginDomainMatchIframeSrc(win, data) { - if (!data.origin) { - return Promise.reject( - new Error('Could not retreive the origin domain') - ); + return Promise.reject(new Error('Could not retreive the origin domain')); } // Using the deprecated parseUrl here, as we don't have access @@ -191,10 +202,11 @@ export function doesOriginDomainMatchIframeSrc(win, data) { return compareCurlsDomain(win, curlsSubdomain, data.origin); } - return ampToolboxCacheUrl.createCurlsSubdomain(data.origin) - .then(curlsSubdomain => { - return compareCurlsDomain(win, curlsSubdomain, data.origin); - }); + return ampToolboxCacheUrl + .createCurlsSubdomain(data.origin) + .then(curlsSubdomain => { + return compareCurlsDomain(win, curlsSubdomain, data.origin); + }); } /** @@ -206,19 +218,18 @@ export function doesOriginDomainMatchIframeSrc(win, data) { * @return {!Promise} */ function compareCurlsDomain(win, curlsSubdomain, origin) { - // Get the hostname after the culrs subdomain of the current iframe window - const locationWithoutCurlsSubdomain = - win.location.hostname.split('.').slice(1).join('.'); - const curlsHostname = - curlsSubdomain + '.' + locationWithoutCurlsSubdomain; + const locationWithoutCurlsSubdomain = win.location.hostname + .split('.') + .slice(1) + .join('.'); + const curlsHostname = curlsSubdomain + '.' + locationWithoutCurlsSubdomain; if (curlsHostname === win.location.hostname) { return Promise.resolve(); } return Promise.reject( - new Error('Origin domain does not match Iframe src: ' + origin) + new Error('Origin domain does not match Iframe src: ' + origin) ); } - diff --git a/3p/reddit.js b/3p/reddit.js index 2743e0aa12583..c1f4d81e8b2e9 100644 --- a/3p/reddit.js +++ b/3p/reddit.js @@ -85,7 +85,8 @@ export function reddit(global, data) { global.addEventListener('resize', event => { global.context.updateDimensions( - event.target.outerWidth, - event.target.outerHeight); + event.target.outerWidth, + event.target.outerHeight + ); }); } diff --git a/3p/twitter.js b/3p/twitter.js index 16f0640243e5b..f7b6c32eaf874 100644 --- a/3p/twitter.js +++ b/3p/twitter.js @@ -65,21 +65,24 @@ export function twitter(global, data) { delete data.height; if (data.tweetid) { - twttr.widgets.createTweet(cleanupTweetId_(data.tweetid), tweet, data) - ./*OK*/then(el => tweetCreated(twttr, el)); + twttr.widgets + .createTweet(cleanupTweetId_(data.tweetid), tweet, data) + ./*OK*/ then(el => tweetCreated(twttr, el)); } else if (data.momentid) { - twttr.widgets.createMoment(data.momentid, tweet, data) - ./*OK*/then(el => tweetCreated(twttr, el)); + twttr.widgets + .createMoment(data.momentid, tweet, data) + ./*OK*/ then(el => tweetCreated(twttr, el)); } else if (data.timelineSourceType) { // Extract properties starting with 'timeline'. const timelineData = Object.keys(data) - .filter(prop => startsWith(prop, 'timeline')) - .reduce((newData, prop) => { - newData[stripPrefixCamelCase(prop, 'timeline')] = data[prop]; - return newData; - }, {}); - twttr.widgets.createTimeline(timelineData, tweet, data) - ./*OK*/then(el => tweetCreated(twttr, el)); + .filter(prop => startsWith(prop, 'timeline')) + .reduce((newData, prop) => { + newData[stripPrefixCamelCase(prop, 'timeline')] = data[prop]; + return newData; + }, {}); + twttr.widgets + .createTimeline(timelineData, tweet, data) + ./*OK*/ then(el => tweetCreated(twttr, el)); } }); @@ -108,15 +111,16 @@ export function twitter(global, data) { * @param {!Element} container */ function resize(container) { - const height = container./*OK*/offsetHeight; + const height = container./*OK*/ offsetHeight; // 0 height is always wrong and we should get another resize request // later. if (height == 0) { return; } context.updateDimensions( - container./*OK*/offsetWidth, - height + /* margins */ 20); + container./*OK*/ offsetWidth, + height + /* margins */ 20 + ); } /** diff --git a/3p/viqeoplayer.js b/3p/viqeoplayer.js index 0b7f2397aebf5..787b930f3e17a 100644 --- a/3p/viqeoplayer.js +++ b/3p/viqeoplayer.js @@ -56,9 +56,9 @@ function viqeoPlayerInitLoaded(global, VIQEO) { * @private */ function subscribe(playerEventName, targetEventName) { - VIQEO['subscribeTracking']( - () => { sendMessage(targetEventName); }, - `Player:${playerEventName}`); + VIQEO['subscribeTracking'](() => { + sendMessage(targetEventName); + }, `Player:${playerEventName}`); } /** @@ -68,8 +68,8 @@ function viqeoPlayerInitLoaded(global, VIQEO) { */ function subscribeTracking(eventsDescription) { VIQEO['subscribeTracking'](params => { - const name = params && params['trackingParams'] && - params['trackingParams'].name; + const name = + params && params['trackingParams'] && params['trackingParams'].name; const targetEventName = eventsDescription[name]; if (targetEventName) { sendMessage(targetEventName); @@ -79,12 +79,12 @@ function viqeoPlayerInitLoaded(global, VIQEO) { const sendMessage = (eventName, value = null) => { const {parent} = global; - const message = /** @type {JsonObject} */({ + const message = /** @type {JsonObject} */ ({ source: 'ViqeoPlayer', action: eventName, value, }); - parent./*OK*/postMessage(message, '*'); + parent./*OK*/ postMessage(message, '*'); }; /** @@ -121,16 +121,11 @@ export function viqeoplayer(global) { let scriptPlayerInit = data['script-url']; scriptPlayerInit = - (scriptPlayerInit - && tryDecodeUriComponent(scriptPlayerInit) - ) - || - (kindIsProd - ? 'https://cdn.viqeo.tv/js/vq_starter.js' - : 'https://static.viqeo.tv/js/vq_player_init.js?branch=dev1' - ); + (scriptPlayerInit && tryDecodeUriComponent(scriptPlayerInit)) || + (kindIsProd + ? 'https://cdn.viqeo.tv/js/vq_starter.js' + : 'https://static.viqeo.tv/js/vq_player_init.js?branch=dev1'); - global['onViqeoLoad'] = VIQEO => - viqeoPlayerInitLoaded(global, VIQEO); + global['onViqeoLoad'] = VIQEO => viqeoPlayerInitLoaded(global, VIQEO); loadScript(global, scriptPlayerInit); } diff --git a/3p/yotpo.js b/3p/yotpo.js index a6fe7b7927fc7..3251fda419f96 100644 --- a/3p/yotpo.js +++ b/3p/yotpo.js @@ -27,7 +27,7 @@ function getContainerScript(global, scriptSource, cb) { global.Yotpo = global.Yotpo || {}; delete global.Yotpo.widgets['testimonials']; const yotpoWidget = - (typeof global.yotpo === 'undefined') ? undefined : global.yotpo; + typeof global.yotpo === 'undefined' ? undefined : global.yotpo; yotpoWidget.on('CssReady', function() { cb(yotpoWidget, 'cssLoaded'); }); @@ -152,8 +152,8 @@ function getReviewsTabContainer(global) { */ function getProductGalleryContainer(global, data) { const container = global.document.createElement('div'); - container.className = 'yotpo yotpo-pictures-gallery yotpo-product-gallery ' + - 'yotpo-size-6'; + container.className = + 'yotpo yotpo-pictures-gallery yotpo-product-gallery ' + 'yotpo-size-6'; container.setAttribute('data-product-id', data.productId); container.setAttribute('data-demo', data.demo); container.setAttribute('data-layout-rows', data.layoutRows); @@ -170,7 +170,6 @@ function getProductGalleryContainer(global, data) { return container; } - /** * Create DOM element for the Yotpo Visual UGC Gallery plugin: * @param {!Window} global @@ -211,10 +210,14 @@ function getEmbeddedWidgetContainer(global, data) { embeddedWidget.setAttribute('data-width', data.width); embeddedWidget.setAttribute('data-reviews', data.reviews); embeddedWidget.setAttribute('data-header-text', data.headerText); - embeddedWidget.setAttribute('data-header-background-color', - data.headerBackgroundColor); - embeddedWidget.setAttribute('data-body-background-color', - data.bodyBackgroundColor); + embeddedWidget.setAttribute( + 'data-header-background-color', + data.headerBackgroundColor + ); + embeddedWidget.setAttribute( + 'data-body-background-color', + data.bodyBackgroundColor + ); embeddedWidget.setAttribute('data-font-size', data.fontSize); embeddedWidget.setAttribute('data-font-color', data.fontColor); embeddedWidget.setAttribute('data-yotpo-element-id', data.yotpoElementId); @@ -250,7 +253,8 @@ function getPhotosCarouselContainer(global, data) { */ function getPromotedProductsContainer(global, data) { const container = global.document.createElement('div'); - container.className = 'yotpo yotpo-main-widget yotpo-promoted-product ' + + container.className = + 'yotpo yotpo-main-widget yotpo-promoted-product ' + 'yotpo-medium promoted-products-box'; container.setAttribute('id', 'widget-div-id'); @@ -293,10 +297,10 @@ export function yotpo(global, data) { global.document.getElementById('c').appendChild(container); - let cssLoaded = false; let batchLoaded = false; - const scriptSource = 'https://staticw2.yotpo.com/' + data.appKey + '/widget.js'; + const scriptSource = + 'https://staticw2.yotpo.com/' + data.appKey + '/widget.js'; getContainerScript(global, scriptSource, (yotpoWidget, eventType) => { if (eventType === 'cssLoaded') { cssLoaded = true; @@ -309,8 +313,9 @@ export function yotpo(global, data) { setTimeout(() => { if (yotpoWidget.widgets[0]) { context.updateDimensions( - yotpoWidget.widgets[0].element./*OK*/offsetWidth, - yotpoWidget.widgets[0].element./*OK*/offsetHeight); + yotpoWidget.widgets[0].element./*OK*/ offsetWidth, + yotpoWidget.widgets[0].element./*OK*/ offsetHeight + ); } }, 100); } diff --git a/ads/_a4a-config.js b/ads/_a4a-config.js index dc89c85da5dc3..15030ca0b3a15 100644 --- a/ads/_a4a-config.js +++ b/ads/_a4a-config.js @@ -14,22 +14,11 @@ * limitations under the License. */ -import { - adsenseIsA4AEnabled, -} from '../extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config'; -import { - cloudflareIsA4AEnabled, -} from - '../extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config'; -import { - gmosspIsA4AEnabled, -} from - '../extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config'; +import {adsenseIsA4AEnabled} from '../extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config'; +import {cloudflareIsA4AEnabled} from '../extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config'; +import {gmosspIsA4AEnabled} from '../extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config'; import {map} from '../src/utils/object'; -import { - tripleliftIsA4AEnabled, -} from - '../extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config'; +import {tripleliftIsA4AEnabled} from '../extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config'; /** * Registry for A4A (AMP Ads for AMPHTML pages) "is supported" predicates. @@ -79,5 +68,6 @@ export const signingServerURLs = { 'google': 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', 'google-dev': 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', 'cloudflare': 'https://amp.cloudflare.com/amp-ad-verifying-keyset.json', - 'cloudflare-dev': 'https://amp.cloudflare.com/amp-ad-verifying-keyset-dev.json', + 'cloudflare-dev': + 'https://amp.cloudflare.com/amp-ad-verifying-keyset-dev.json', }; diff --git a/ads/_config.js b/ads/_config.js index f24bde1ab255c..553e4d0d0fa8e 100644 --- a/ads/_config.js +++ b/ads/_config.js @@ -139,10 +139,7 @@ export const adConfig = { 'admixer': { renderStartImplemented: true, - preconnect: [ - 'https://inv-nets.admixer.net', - 'https://cdn.admixer.net', - ], + preconnect: ['https://inv-nets.admixer.net', 'https://cdn.admixer.net'], }, 'adocean': { @@ -201,16 +198,11 @@ export const adConfig = { 'adtech': { prefetch: 'https://s.aolcdn.com/os/ads/adsWrapper3.js', - preconnect: [ - 'https://mads.at.atwola.com', - 'https://aka-cdn.adtechus.com', - ], + preconnect: ['https://mads.at.atwola.com', 'https://aka-cdn.adtechus.com'], }, 'adthrive': { - prefetch: [ - 'https://www.googletagservices.com/tag/js/gpt.js', - ], + prefetch: ['https://www.googletagservices.com/tag/js/gpt.js'], preconnect: [ 'https://partner.googleadservices.com', 'https://securepubads.g.doubleclick.net', @@ -220,34 +212,24 @@ export const adConfig = { }, 'adunity': { - preconnect: [ - 'https://content.adunity.com', - ], + preconnect: ['https://content.adunity.com'], renderStartImplemented: true, }, 'aduptech': { prefetch: 'https://s.d.adup-tech.com/jsapi', - preconnect: [ - 'https://d.adup-tech.com', - 'https://m.adup-tech.com', - ], + preconnect: ['https://d.adup-tech.com', 'https://m.adup-tech.com'], renderStartImplemented: true, }, 'adventive': { - preconnect: [ - 'https://ads.adventive.com', - 'https://amp.adventivedev.com', - ], + preconnect: ['https://ads.adventive.com', 'https://amp.adventivedev.com'], renderStartImplemented: true, }, 'adverline': { prefetch: 'https://ads.adverline.com/richmedias/amp.js', - preconnect: [ - 'https://adnext.fr', - ], + preconnect: ['https://adnext.fr'], renderStartImplemented: true, }, @@ -270,8 +252,7 @@ export const adConfig = { renderStartImplemented: true, }, - 'aja': { - }, + 'aja': {}, 'appvador': { prefetch: [ @@ -285,10 +266,7 @@ export const adConfig = { }, 'amoad': { - prefetch: [ - 'https://j.amoad.com/js/a.js', - 'https://j.amoad.com/js/n.js', - ], + prefetch: ['https://j.amoad.com/js/a.js', 'https://j.amoad.com/js/n.js'], preconnect: [ 'https://d.amoad.com', 'https://i.amoad.com', @@ -323,10 +301,7 @@ export const adConfig = { 'bringhub': { renderStartImplemented: true, - preconnect: [ - 'https://static.bh-cdn.com', - 'https://core-api.bringhub.io', - ], + preconnect: ['https://static.bh-cdn.com', 'https://core-api.bringhub.io'], }, 'broadstreetads': { @@ -334,12 +309,8 @@ export const adConfig = { }, 'caajainfeed': { - prefetch: [ - 'https://cdn.amanad.adtdp.com/sdk/ajaamp.js', - ], - preconnect: [ - 'https://ad.amanad.adtdp.com', - ], + prefetch: ['https://cdn.amanad.adtdp.com/sdk/ajaamp.js'], + preconnect: ['https://ad.amanad.adtdp.com'], }, 'capirs': { @@ -370,7 +341,6 @@ export const adConfig = { 'contentad': {}, - 'criteo': { prefetch: 'https://static.criteo.net/js/ld/publishertag.js', preconnect: 'https://cas.criteo.com', @@ -533,9 +503,7 @@ export const adConfig = { }, 'ix': { - prefetch: [ - 'https://js-sec.indexww.com/apl/amp.js', - ], + prefetch: ['https://js-sec.indexww.com/apl/amp.js'], preconnect: 'https://as-sec.casalemedia.com', renderStartImplemented: true, }, @@ -645,10 +613,7 @@ export const adConfig = { 'mixpo': { prefetch: 'https://cdn.mixpo.com/js/loader.js', - preconnect: [ - 'https://player1.mixpo.com', - 'https://player2.mixpo.com', - ], + preconnect: ['https://player1.mixpo.com', 'https://player2.mixpo.com'], }, 'monetizer101': { @@ -684,16 +649,11 @@ export const adConfig = { 'nend': { prefetch: 'https://js1.nend.net/js/amp.js', - preconnect: [ - 'https://output.nend.net', - 'https://img1.nend.net', - ], + preconnect: ['https://output.nend.net', 'https://img1.nend.net'], }, 'netletix': { - preconnect: [ - 'https://call.netzathleten-media.de', - ], + preconnect: ['https://call.netzathleten-media.de'], renderStartImplemented: true, }, @@ -731,9 +691,7 @@ export const adConfig = { 'outbrain': { renderStartImplemented: true, prefetch: 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js', - preconnect: [ - 'https://odb.outbrain.com', - ], + preconnect: ['https://odb.outbrain.com'], consentHandlingOverride: true, }, @@ -818,7 +776,8 @@ export const adConfig = { }, 'revcontent': { - prefetch: 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js', + prefetch: + 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js', preconnect: [ 'https://trends.revcontent.com', 'https://cdn.revcontent.com', @@ -928,10 +887,7 @@ export const adConfig = { 'swoop': { prefetch: 'https://www.swoop-amp.com/amp.js', - preconnect: [ - 'https://www.swpsvc.com', - 'https://client.swpcld.com', - ], + preconnect: ['https://www.swpsvc.com', 'https://client.swpcld.com'], renderStartImplemented: true, }, @@ -972,9 +928,7 @@ export const adConfig = { }, 'uzou': { - preconnect: [ - 'https://speee-ad.akamaized.net', - ], + preconnect: ['https://speee-ad.akamaized.net'], renderStartImplemented: true, }, @@ -1007,19 +961,13 @@ export const adConfig = { 'vmfive': { prefetch: 'https://man.vm5apis.com/dist/adn-web-sdk.js', - preconnect: [ - 'https://vawpro.vm5apis.com', - 'https://vahfront.vm5apis.com', - ], + preconnect: ['https://vawpro.vm5apis.com', 'https://vahfront.vm5apis.com'], renderStartImplemented: true, }, 'webediads': { prefetch: 'https://eu1.wbdds.com/amp.min.js', - preconnect: [ - 'https://goutee.top', - 'https://mediaathay.org.uk', - ], + preconnect: ['https://goutee.top', 'https://mediaathay.org.uk'], renderStartImplemented: true, }, @@ -1038,10 +986,7 @@ export const adConfig = { 'wpmedia': { prefetch: 'https://std.wpcdn.pl/wpjslib/wpjslib-amp.js', - preconnect: [ - 'https://www.wp.pl', - 'https://v.wpimg.pl', - ], + preconnect: ['https://www.wp.pl', 'https://v.wpimg.pl'], renderStartImplemented: true, }, @@ -1082,10 +1027,7 @@ export const adConfig = { 'yieldmo': { prefetch: 'https://static.yieldmo.com/ym.1.js', - preconnect: [ - 'https://s.yieldmo.com', - 'https://ads.yieldmo.com', - ], + preconnect: ['https://s.yieldmo.com', 'https://ads.yieldmo.com'], renderStartImplemented: true, }, @@ -1105,9 +1047,7 @@ export const adConfig = { 'zen': { prefetch: 'https://zen.yandex.ru/widget-loader', - preconnect: [ - 'https://yastatic.net/', - ], + preconnect: ['https://yastatic.net/'], renderStartImplemented: true, }, @@ -1126,5 +1066,4 @@ export const adConfig = { prefetch: 'https://dup.baidustatic.com/js/dm.js', renderStartImplemented: true, }, - }; diff --git a/ads/_ping_.js b/ads/_ping_.js index e26d5dbaa294f..61f82bea41849 100644 --- a/ads/_ping_.js +++ b/ads/_ping_.js @@ -28,8 +28,7 @@ export function _ping_(global, data) { // for testing only. see #10628 global.networkIntegrationDataParamForTesting = data; - validateData(data, ['url'], - ['valid', 'adHeight', 'adWidth', 'enableIo']); + validateData(data, ['url'], ['valid', 'adHeight', 'adWidth', 'enableIo']); userAssert(!data['error'], 'Fake user error!'); global.document.getElementById('c').textContent = data.ping; global.ping = Object.create(null); @@ -43,8 +42,7 @@ export function _ping_(global, data) { }); if (data.ad_container) { - devAssert( - global.context.container == data.ad_container, 'wrong container'); + devAssert(global.context.container == data.ad_container, 'wrong container'); } if (data.valid == 'false') { // Immediately send no-content for visual diff test @@ -75,8 +73,11 @@ export function _ping_(global, data) { if (data.enableIo) { global.context.observeIntersection(function(changes) { changes.forEach(function(c) { - dev().info('AMP-AD', 'Intersection: (WxH)' + - `${c.intersectionRect.width}x${c.intersectionRect.height}`); + dev().info( + 'AMP-AD', + 'Intersection: (WxH)' + + `${c.intersectionRect.width}x${c.intersectionRect.height}` + ); }); // store changes to global.lastIO for testing purpose global.ping.lastIO = changes[changes.length - 1]; diff --git a/ads/a9.js b/ads/a9.js index fa02df6cb3bf2..c9a5deb759ef6 100644 --- a/ads/a9.js +++ b/ads/a9.js @@ -19,24 +19,58 @@ import {loadScript, validateData, writeScript} from '../3p/3p'; import {parseJson} from '../src/json'; const mandatoryParams = [], - optionalParams = [ - 'ad_mode', 'placement', 'tracking_id', 'ad_type', - 'marketplace', 'region', 'title', - 'default_search_phrase', 'default_category', 'linkid', - 'search_bar', 'search_bar_position', 'rows', - 'design', 'asins', 'debug', 'aax_src_id', - 'header_style', 'link_style', 'link_hover_style', - 'text_style', 'random_permute', 'render_full_page', - 'axf_exp_name', 'axf_treatment', 'disable_borders', - 'attributes', 'carousel', 'feedback_enable', - 'max_ads_in_a_row', 'list_price', 'prime', - 'prime_position', 'widget_padding', - 'strike_text_style', 'brand_text_link', 'brand_position', - 'large_rating', 'rating_position', 'max_title_height', - 'enable_swipe_on_mobile', 'overrides', - 'ead', 'force_win_bid', 'fallback_mode', 'url', - 'regionurl', 'divid', 'recomtype', 'adinstanceid', - ]; + optionalParams = [ + 'ad_mode', + 'placement', + 'tracking_id', + 'ad_type', + 'marketplace', + 'region', + 'title', + 'default_search_phrase', + 'default_category', + 'linkid', + 'search_bar', + 'search_bar_position', + 'rows', + 'design', + 'asins', + 'debug', + 'aax_src_id', + 'header_style', + 'link_style', + 'link_hover_style', + 'text_style', + 'random_permute', + 'render_full_page', + 'axf_exp_name', + 'axf_treatment', + 'disable_borders', + 'attributes', + 'carousel', + 'feedback_enable', + 'max_ads_in_a_row', + 'list_price', + 'prime', + 'prime_position', + 'widget_padding', + 'strike_text_style', + 'brand_text_link', + 'brand_position', + 'large_rating', + 'rating_position', + 'max_title_height', + 'enable_swipe_on_mobile', + 'overrides', + 'ead', + 'force_win_bid', + 'fallback_mode', + 'url', + 'regionurl', + 'divid', + 'recomtype', + 'adinstanceid', + ]; const prefix = 'amzn_assoc_'; /** @@ -51,25 +85,22 @@ export function a9(global, data) { validateData(data, mandatoryParams, optionalParams); - const publisherUrl = global.context.canonicalUrl || - global.context.sourceUrl; + const publisherUrl = global.context.canonicalUrl || global.context.sourceUrl; if (data.amzn_assoc_ad_mode) { if (data.amzn_assoc_ad_mode === 'auto') { - if (data.adinstanceid && - (data.adinstanceid !== '')) { + if (data.adinstanceid && data.adinstanceid !== '') { loadRecTag(global, data, publisherUrl); - } - else { + } else { loadSearTag(global, data, publisherUrl); } - } - else if ((data.amzn_assoc_ad_mode === 'search') - || (data.amzn_assoc_ad_mode === 'manual')) { + } else if ( + data.amzn_assoc_ad_mode === 'search' || + data.amzn_assoc_ad_mode === 'manual' + ) { loadSearTag(global, data, publisherUrl); } - } - else { + } else { loadSearTag(global, data, publisherUrl); } } @@ -79,7 +110,7 @@ export function a9(global, data) { */ function getURL(data) { let url = 'https://z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US'; - if (data.regionurl && (data.regionurl !== '')) { + if (data.regionurl && data.regionurl !== '') { url = data.regionurl; } @@ -98,8 +129,7 @@ function loadRecTag(global, data, publisherUrl) { if (data['recomtype'] === 'sync') { writeScript(global, url); - } - else if (data['recomtype'] === 'async') { + } else if (data['recomtype'] === 'async') { const d = global.document.createElement('div'); d.setAttribute('id', data['divid']); global.document.getElementById('c').appendChild(d); @@ -114,9 +144,9 @@ function loadRecTag(global, data, publisherUrl) { */ function loadSearTag(global, data, publisherUrl) { /** - * Sets macro type. - * @param {string} type - */ + * Sets macro type. + * @param {string} type + */ function setMacro(type) { if (!type) { return; @@ -128,8 +158,8 @@ function loadSearTag(global, data, publisherUrl) { } /** - * Sets the params. - */ + * Sets the params. + */ function setParams() { const url = getURL(data); let i; @@ -139,7 +169,7 @@ function loadSearTag(global, data, publisherUrl) { } if (data.amzn_assoc_fallback_mode) { - const str = (data.amzn_assoc_fallback_mode).split(','); + const str = data.amzn_assoc_fallback_mode.split(','); let types = str[0].split(':'); let typev = str[1].split(':'); types = parseJson(types[1]); @@ -151,8 +181,7 @@ function loadSearTag(global, data, publisherUrl) { } if (data.amzn_assoc_url) { global['amzn_assoc_URL'] = data['amzn_assoc_url']; - } - else { + } else { global['amzn_assoc_URL'] = publisherUrl; } diff --git a/ads/adagio.js b/ads/adagio.js index 5360b3b4b84cc..eb072bef42135 100755 --- a/ads/adagio.js +++ b/ads/adagio.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function adagio(global, data) { - validateData(data, ['sid', 'loc']); const $neodata = global; diff --git a/ads/adblade.js b/ads/adblade.js index 859c8223f89ea..495c63624d72e 100644 --- a/ads/adblade.js +++ b/ads/adblade.js @@ -40,8 +40,8 @@ function addAdiantUnit(hostname, global, data) { global.document.getElementById('c').appendChild(ins); ins.parentNode.addEventListener( - 'eventAdbladeRenderStart', - global.context.renderStart() + 'eventAdbladeRenderStart', + global.context.renderStart() ); // run our JavaScript code to display the ad unit diff --git a/ads/adbutler.js b/ads/adbutler.js index 11c89fcdc3fca..51efddaff521e 100644 --- a/ads/adbutler.js +++ b/ads/adbutler.js @@ -20,11 +20,11 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Window} global * @param {!Object} data */ -export function adbutler(global,data) { +export function adbutler(global, data) { validateData( - data, - ['account', 'zone', 'width', 'height'], - ['keyword', 'place'] + data, + ['account', 'zone', 'width', 'height'], + ['keyword', 'place'] ); data['place'] = data['place'] || 0; @@ -42,11 +42,11 @@ export function adbutler(global,data) { global.AdButler.ads.push({ handler(opt) { global.AdButler.register( - data['account'], - data['zone'], - [data['width'], data['height']], - placeholderID, - opt + data['account'], + data['zone'], + [data['width'], data['height']], + placeholderID, + opt ); }, opt: { diff --git a/ads/adfox.js b/ads/adfox.js index 260cccc2fff33..4027484825719 100644 --- a/ads/adfox.js +++ b/ads/adfox.js @@ -23,8 +23,9 @@ import {yandex} from './yandex'; */ export function adfox(global, data) { validateData(data, ['adfoxParams', 'ownerId']); - loadScript(global, 'https://yastatic.net/pcode/adfox/loader.js', - () => initAdFox(global, data)); + loadScript(global, 'https://yastatic.net/pcode/adfox/loader.js', () => + initAdFox(global, data) + ); } /** diff --git a/ads/adgeneration.js b/ads/adgeneration.js index 40e683b56bb35..418db5efaef6d 100644 --- a/ads/adgeneration.js +++ b/ads/adgeneration.js @@ -21,24 +21,27 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function adgeneration(global, data) { - validateData(data, ['id'], - ['targetid', 'displayid', 'adtype', 'option']); + validateData(data, ['id'], ['targetid', 'displayid', 'adtype', 'option']); // URL encoding const option = data.option ? encodeQueryValue(data.option) : null; - const url = 'https://i.socdm.com/sdk/js/adg-script-loader.js?' + - 'id=' + encodeURIComponent(data.id) + - '&width=' + encodeURIComponent(data.width) + - '&height=' + encodeURIComponent(data.height) + - '&async=true' + - '&adType=' + - (validateAdType(data.adType)) + - '&displayid=' + - (data.displayid ? encodeURIComponent(data.displayid) : '1') + - '&tagver=2.0.0' + - (data.targetid ? '&targetID=' + encodeURIComponent(data.targetid) : '') + - (option ? '&' + option : ''); + const url = + 'https://i.socdm.com/sdk/js/adg-script-loader.js?' + + 'id=' + + encodeURIComponent(data.id) + + '&width=' + + encodeURIComponent(data.width) + + '&height=' + + encodeURIComponent(data.height) + + '&async=true' + + '&adType=' + + validateAdType(data.adType) + + '&displayid=' + + (data.displayid ? encodeURIComponent(data.displayid) : '1') + + '&tagver=2.0.0' + + (data.targetid ? '&targetID=' + encodeURIComponent(data.targetid) : '') + + (option ? '&' + option : ''); writeScript(global, url); } @@ -48,11 +51,14 @@ export function adgeneration(global, data) { * @return {string} */ function encodeQueryValue(str) { - return str.split('&').map(v => { - const key = v.split('=')[0], + return str + .split('&') + .map(v => { + const key = v.split('=')[0], val = v.split('=')[1]; - return encodeURIComponent(key) + '=' + encodeURIComponent(val); - }).join('&'); + return encodeURIComponent(key) + '=' + encodeURIComponent(val); + }) + .join('&'); } /** * If adtype is "RECTANGLE", replace it with "RECT" diff --git a/ads/adhese.js b/ads/adhese.js index 9e2ac8d99b063..0dddd3f5d9844 100644 --- a/ads/adhese.js +++ b/ads/adhese.js @@ -21,7 +21,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function adhese(global, data) { - validateData(data, ['location','format', 'account', 'requestType']); + validateData(data, ['location', 'format', 'account', 'requestType']); let targetParam = ''; if (data['targeting']) { const targetList = data['targeting']; @@ -29,18 +29,27 @@ export function adhese(global, data) { targetParam += encodeURIComponent(category); const targets = targetList[category]; for (let x = 0; x < targets.length; x++) { - targetParam += encodeURIComponent(targets[x]) + - (targets.length - 1 > x ? ';' : ''); + targetParam += + encodeURIComponent(targets[x]) + (targets.length - 1 > x ? ';' : ''); } targetParam += '/'; } } targetParam += '?t=' + Date.now(); - writeScript(window, 'https://ads-' + encodeURIComponent(data['account']) + - '.adhese.com/' + encodeURIComponent(data['requestType']) + '/sl' + + writeScript( + window, + 'https://ads-' + + encodeURIComponent(data['account']) + + '.adhese.com/' + + encodeURIComponent(data['requestType']) + + '/sl' + encodeURIComponent(data['location']) + - encodeURIComponent(data['position']) + '-' + - encodeURIComponent(data['format']) + '/' + targetParam); + encodeURIComponent(data['position']) + + '-' + + encodeURIComponent(data['format']) + + '/' + + targetParam + ); const co = global.document.querySelector('#c'); co.width = data['width']; co.height = data['height']; @@ -52,13 +61,20 @@ export function adhese(global, data) { * @param {!Object} e */ function getAdInfo(global, e) { - if (e.detail.isReady && e.detail.width == e.target.width && - e.detail.height == e.target.height) { + if ( + e.detail.isReady && + e.detail.width == e.target.width && + e.detail.height == e.target.height + ) { global.context.renderStart(); - } else if (e.detail.isReady && (e.detail.width != e.target.width || - e.detail.height != e.target.height)) { - global.context.renderStart({width: e.detail.width, - height: e.detail.height}); + } else if ( + e.detail.isReady && + (e.detail.width != e.target.width || e.detail.height != e.target.height) + ) { + global.context.renderStart({ + width: e.detail.width, + height: e.detail.height, + }); } else { global.context.noContentAvailable(); } diff --git a/ads/adition.js b/ads/adition.js index 475898336e261..92a258ccde886 100644 --- a/ads/adition.js +++ b/ads/adition.js @@ -23,6 +23,10 @@ import {validateData, writeScript} from '../3p/3p'; export function adition(global, data) { validateData(data, ['version']); global.data = data; - writeScript(global, 'https://imagesrv.adition.com/js/amp/v' - + encodeURIComponent(data['version']) + '.js'); + writeScript( + global, + 'https://imagesrv.adition.com/js/amp/v' + + encodeURIComponent(data['version']) + + '.js' + ); } diff --git a/ads/admanmedia.js b/ads/admanmedia.js index 2e67e0dcc35c0..4a875550c0ea1 100644 --- a/ads/admanmedia.js +++ b/ads/admanmedia.js @@ -24,12 +24,17 @@ export function admanmedia(global, data) { validateData(data, ['id']); const encodedId = encodeURIComponent(data.id); - loadScript(global, `https://mona.admanmedia.com/go?id=${encodedId}`, () => { - const pattern = `script[src$="id=${encodedId}"]`; - const scriptTag = global.document.querySelector(pattern); - scriptTag.setAttribute('id', `hybs-${encodedId}`); - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + `https://mona.admanmedia.com/go?id=${encodedId}`, + () => { + const pattern = `script[src$="id=${encodedId}"]`; + const scriptTag = global.document.querySelector(pattern); + scriptTag.setAttribute('id', `hybs-${encodedId}`); + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/adocean.js b/ads/adocean.js index c4e4dbd501d4c..023c75e460a20 100644 --- a/ads/adocean.js +++ b/ads/adocean.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {CONSENT_POLICY_STATE} from '../src/consent-state'; import {computeInMasterFrame, validateData, writeScript} from '../3p/3p'; import {parseJson} from '../src/json'; @@ -43,7 +42,7 @@ function isFalseString(str) { function setupAdoConfig(mode, global, consent) { if (global['ado']) { const config = { - mode: (mode == 'sync') ? 'old' : 'new', + mode: mode == 'sync' ? 'old' : 'new', protocol: 'https:', fif: { enabled: mode != 'sync', @@ -120,8 +119,9 @@ let runSyncCount = 0; function runSync(global, cb) { global['__aoPrivFnct' + ++runSyncCount] = cb; /*eslint no-useless-concat: 0*/ - global.document - .write('<' + 'script>__aoPrivFnct' + runSyncCount + '();<' + '/script>'); + global.document.write( + '<' + 'script>__aoPrivFnct' + runSyncCount + '();<' + '/script>' + ); } /** @@ -201,15 +201,20 @@ function executeMaster(masterId, data, global, callback) { function requestCodes(masterId, data, global, callback) { const slaveId = data['aoId']; - computeInMasterFrame(global, 'ao-master-exec', done => { - executeMaster(masterId, data, global,codes => done(codes)); - }, codes => { - const creative = codes[slaveId]; - if (codes[slaveId + '_second_phase']) { - creative['code'] += '\n' + codes[slaveId + '_second_phase']['code']; + computeInMasterFrame( + global, + 'ao-master-exec', + done => { + executeMaster(masterId, data, global, codes => done(codes)); + }, + codes => { + const creative = codes[slaveId]; + if (codes[slaveId + '_second_phase']) { + creative['code'] += '\n' + codes[slaveId + '_second_phase']['code']; + } + callback(creative); } - callback(creative); - }); + ); } class AdoBuffer { @@ -233,8 +238,8 @@ class AdoBuffer { if (this.global.document.readyState === 'loading') { this.global.document.addEventListener( - 'DOMContentLoaded', - this._init.bind(this) + 'DOMContentLoaded', + this._init.bind(this) ); } else { this._init(); @@ -276,7 +281,6 @@ class AdoBuffer { } } - /** * * @param {string} slaveId @@ -297,7 +301,7 @@ function executeSlave(slaveId, config, global) { } else { const buffer = new AdoBuffer(placement, global); buffer.render(() => { - (new Function(config['sendHitsDef'] + config['code']))(); + new Function(config['sendHitsDef'] + config['code'])(); }); } } @@ -308,20 +312,14 @@ function executeSlave(slaveId, config, global) { * @param {!Object} data */ export function adocean(global, data) { - validateData(data, [ - 'aoEmitter', - 'aoId', - ], [ - 'aoMode', - 'aoPreview', - 'aoKeys', - 'aoVars', - 'aoClusters', - 'aoMaster', - ]); + validateData( + data, + ['aoEmitter', 'aoId'], + ['aoMode', 'aoPreview', 'aoKeys', 'aoVars', 'aoClusters', 'aoMaster'] + ); const masterId = data['aoMaster']; - const mode = (data['aoMode'] !== 'sync' || masterId ? 'buffered' : 'sync'); + const mode = data['aoMode'] !== 'sync' || masterId ? 'buffered' : 'sync'; const adoUrl = 'https://' + data['aoEmitter'] + ADO_JS_PATHS[mode]; const ctx = global.context; @@ -329,11 +327,10 @@ export function adocean(global, data) { * INSUFFICIENT and UNKNOWN should be treated as INSUFFICIENT * not defined states should be treated as INSUFFICIENT */ - const consent = ( + const consent = ctx.initialConsentState === null /* tags without data-block-on-consent */ || ctx.initialConsentState === CONSENT_POLICY_STATE.SUFFICIENT || - ctx.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED - ); + ctx.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED; writeScript(global, adoUrl, () => { setupAdoConfig(mode, global, consent); diff --git a/ads/adpicker.js b/ads/adpicker.js index c54662fc497ff..fd2e703db4b55 100644 --- a/ads/adpicker.js +++ b/ads/adpicker.js @@ -22,10 +22,12 @@ import {validateData, writeScript} from '../3p/3p'; */ export function adpicker(global, data) { validateData(data, ['ph']); - const url = 'https://cdn.adpicker.net' + - '/ads/main.js?et=amp' + - '&ph=' + encodeURIComponent(data.ph) + - '&cb=' + Math.floor(89999999 * Math.random() + 10000000); + const url = + 'https://cdn.adpicker.net' + + '/ads/main.js?et=amp' + + '&ph=' + + encodeURIComponent(data.ph) + + '&cb=' + + Math.floor(89999999 * Math.random() + 10000000); writeScript(global, url); } - diff --git a/ads/adplugg.js b/ads/adplugg.js index e0dafd854009d..7e179e2647fef 100644 --- a/ads/adplugg.js +++ b/ads/adplugg.js @@ -22,15 +22,15 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Window} global * @param {!Object} data */ -export function adplugg(global,data) { +export function adplugg(global, data) { // Load ad.js loadScript(global, 'https://www.adplugg.com/serve/js/ad.js'); // Validate the amp-ad attributes. validateData( - data, - ['accessCode', 'width', 'height'], //required - ['zone'] //optional + data, + ['accessCode', 'width', 'height'], //required + ['zone'] //optional ); // Get the amp wrapper element. @@ -46,39 +46,31 @@ export function adplugg(global,data) { ampwrapper.appendChild(adTag); // Get a handle on the AdPlugg SDK. - global.AdPlugg = (global.AdPlugg || []); + global.AdPlugg = global.AdPlugg || []; const {AdPlugg} = global; // Register event listeners (via async wrapper). AdPlugg.push(function() { const {AdPlugg} = global; // Register the renderStart event listener. - AdPlugg.on( - adTag, - 'adplugg:renderStart', - function(event) { - // Create the opt_data object. - const optData = {}; - if (hasOwn(event, 'width')) { - optData.width = event.width; - } - if (hasOwn(event, 'height')) { - optData.height = event.height; - } - global.context.renderStart(optData); - } - ); + AdPlugg.on(adTag, 'adplugg:renderStart', function(event) { + // Create the opt_data object. + const optData = {}; + if (hasOwn(event, 'width')) { + optData.width = event.width; + } + if (hasOwn(event, 'height')) { + optData.height = event.height; + } + global.context.renderStart(optData); + }); // Register the noContentAvailable event listener. - AdPlugg.on(adTag, 'adplugg:noContentAvailable', - function() { - global.context.noContentAvailable(); - } - ); - + AdPlugg.on(adTag, 'adplugg:noContentAvailable', function() { + global.context.noContentAvailable(); + }); }); // Fill the tag. AdPlugg.push({'command': 'run'}); - } diff --git a/ads/adreactor.js b/ads/adreactor.js index 806b9863aa6de..91c9844576ec5 100644 --- a/ads/adreactor.js +++ b/ads/adreactor.js @@ -23,12 +23,18 @@ import {validateData, writeScript} from '../3p/3p'; export function adreactor(global, data) { // TODO: check mandatory fields validateData(data, [], ['zid', 'pid', 'custom3']); - const url = 'https://adserver.adreactor.com' + - '/servlet/view/banner/javascript/zone?' + - 'zid=' + encodeURIComponent(data.zid) + - '&pid=' + encodeURIComponent(data.pid) + - '&custom3=' + encodeURIComponent(data.custom3) + - '&random=' + Math.floor(89999999 * Math.random() + 10000000) + - '&millis=' + Date.now(); + const url = + 'https://adserver.adreactor.com' + + '/servlet/view/banner/javascript/zone?' + + 'zid=' + + encodeURIComponent(data.zid) + + '&pid=' + + encodeURIComponent(data.pid) + + '&custom3=' + + encodeURIComponent(data.custom3) + + '&random=' + + Math.floor(89999999 * Math.random() + 10000000) + + '&millis=' + + Date.now(); writeScript(global, url); } diff --git a/ads/adsensor.js b/ads/adsensor.js index c2687e166527f..c15462c6e0b57 100644 --- a/ads/adsensor.js +++ b/ads/adsensor.js @@ -20,7 +20,8 @@ import {writeScript} from '../3p/3p'; * @param {!Window} global */ export function adsensor(global) { - - writeScript(global, 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js'); - + writeScript( + global, + 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js' + ); } diff --git a/ads/adspeed.js b/ads/adspeed.js index bb33d4dbb05e2..91b5f7e9be05a 100644 --- a/ads/adspeed.js +++ b/ads/adspeed.js @@ -23,7 +23,13 @@ import {validateData, writeScript} from '../3p/3p'; export function adspeed(global, data) { validateData(data, ['zone', 'client']); - const url = 'https://g.adspeed.net/ad.php?do=amphtml&zid=' + data.zone + '&oid=' + data.client + '&cb=' + Math.random(); + const url = + 'https://g.adspeed.net/ad.php?do=amphtml&zid=' + + data.zone + + '&oid=' + + data.client + + '&cb=' + + Math.random(); writeScript(global, url); } diff --git a/ads/adspirit.js b/ads/adspirit.js index 05497a9e6ae37..9aa21cfa12ef9 100644 --- a/ads/adspirit.js +++ b/ads/adspirit.js @@ -18,9 +18,9 @@ import {setStyles} from '../src/style'; import {validateData} from '../3p/3p'; /** - * @param {!Window} global - * @param {!Object} data - */ + * @param {!Window} global + * @param {!Object} data + */ export function adspirit(global, data) { // TODO: check mandatory fields validateData(data, [], ['asmParams', 'asmHost']); diff --git a/ads/adtech.js b/ads/adtech.js index dc673bbe6d732..8df0e89190ee4 100644 --- a/ads/adtech.js +++ b/ads/adtech.js @@ -32,11 +32,22 @@ export function adtech(global, data) { validateSrcContains('/addyn/', adsrc); writeScript(global, adsrc); } else { - validateData(data, ['atwmn', 'atwdiv'], [ - 'atwco', 'atwheight', 'atwhtnmat', - 'atwmoat', 'atwnetid', 'atwothat', 'atwplid', - 'atwpolar', 'atwsizes', 'atwwidth', - ]); + validateData( + data, + ['atwmn', 'atwdiv'], + [ + 'atwco', + 'atwheight', + 'atwhtnmat', + 'atwmoat', + 'atwnetid', + 'atwothat', + 'atwplid', + 'atwpolar', + 'atwsizes', + 'atwwidth', + ] + ); global.atwco = data.atwco; global.atwdiv = data.atwdiv; global.atwheight = data.atwheight; @@ -49,6 +60,6 @@ export function adtech(global, data) { global.atwpolar = data.atwpolar; global.atwsizes = data.atwsizes; global.atwwidth = data.atwwidth; - writeScript(global,'https://s.aolcdn.com/os/ads/adsWrapper3.js'); + writeScript(global, 'https://s.aolcdn.com/os/ads/adsWrapper3.js'); } } diff --git a/ads/adthrive.js b/ads/adthrive.js index 6449bbd3ef210..ad2c047a29bce 100644 --- a/ads/adthrive.js +++ b/ads/adthrive.js @@ -22,6 +22,10 @@ import {loadScript, validateData} from '../3p/3p'; */ export function adthrive(global, data) { validateData(data, ['siteId', 'adUnit'], ['sizes']); - loadScript(global, 'https://ads.adthrive.com/sites/' - + encodeURIComponent(data.siteId) + '/amp.min.js'); + loadScript( + global, + 'https://ads.adthrive.com/sites/' + + encodeURIComponent(data.siteId) + + '/amp.min.js' + ); } diff --git a/ads/adunity.js b/ads/adunity.js index f040016ce18bc..67c33461b3487 100644 --- a/ads/adunity.js +++ b/ads/adunity.js @@ -24,32 +24,31 @@ import {startsWith} from '../src/string'; export function adunity(global, data) { const doc = global.document; - validateData(data, [ - 'auAccount', - 'auSite', - ], - [ - 'auSection', - 'auZone', - 'auDemo', - 'auIsdemo', - 'auAd', - 'auOrder', - 'auSegment', - 'auOptions', - 'auSources', - 'auAds', - 'auTriggerFn', - 'auTriggerVal', - 'auCallbackVal', - 'auCallbackFn', - 'auPassbackFn', - 'auPassbackVal', - 'auClick', - 'auDual', - 'auImpression', - 'auVideo', - ] + validateData( + data, + ['auAccount', 'auSite'], + [ + 'auSection', + 'auZone', + 'auDemo', + 'auIsdemo', + 'auAd', + 'auOrder', + 'auSegment', + 'auOptions', + 'auSources', + 'auAds', + 'auTriggerFn', + 'auTriggerVal', + 'auCallbackVal', + 'auCallbackFn', + 'auPassbackFn', + 'auPassbackVal', + 'auClick', + 'auDual', + 'auImpression', + 'auVideo', + ] ); //prepare tag structure @@ -73,8 +72,7 @@ export function adunity(global, data) { if (startsWith(key, 'au')) { if (key == 'auVideo') { tag.setAttribute('class', 'au-video'); - } - else { + } else { const auKey = key.substring(2).toLowerCase(); tag.setAttribute('data-au-' + auKey, data[key]); } @@ -104,7 +102,9 @@ export function adunity(global, data) { * @param {!Object} data */ function renderTags(global, data) { - if (data == null) {return;} + if (data == null) { + return; + } global.context.renderStart({ width: data.width, diff --git a/ads/aduptech.js b/ads/aduptech.js index 6cde94c6a128d..b69e9d606ce2d 100644 --- a/ads/aduptech.js +++ b/ads/aduptech.js @@ -30,7 +30,6 @@ export function aduptech(global, data) { // load aduptech js api loadScript(global, 'https://s.d.adup-tech.com/jsapi', () => { - // force responsive ads for amp data.responsive = true; diff --git a/ads/adventive.js b/ads/adventive.js index 75fcaed58ddf4..471069c87f5b0 100644 --- a/ads/adventive.js +++ b/ads/adventive.js @@ -33,23 +33,23 @@ export function adventive(global, data) { } const adv = { - addInstance: () => {}, - args: {}, - isLibLoaded: false, - mode: { - dev: false, - live: false, - localDev: false, - preview: false, - prod: false, - testing: false, - }, + addInstance: () => {}, + args: {}, + isLibLoaded: false, + mode: { + dev: false, + live: false, + localDev: false, + preview: false, + prod: false, + testing: false, }, - requiredData = ['pid'], - optionalData = ['click', 'async', 'isDev'], - sld = {true: 'adventivedev', false: 'adventive'}, - thld = {true: 'amp', false: 'ads'}, - cacheTime = 5; + }, + requiredData = ['pid'], + optionalData = ['click', 'async', 'isDev'], + sld = {true: 'adventivedev', false: 'adventive'}, + thld = {true: 'amp', false: 'ads'}, + cacheTime = 5; /** * Future data: @@ -73,18 +73,23 @@ const adv = { function adventive_(global, data) { validateData(data, requiredData, optionalData); - if (!hasOwn(global, 'adventive')) { global.adventive = adv; } + if (!hasOwn(global, 'adventive')) { + global.adventive = adv; + } const ns = global.adventive; - if (!hasOwn(ns, 'context')) { ns.context = global.context; } + if (!hasOwn(ns, 'context')) { + ns.context = global.context; + } if (!Object.isFrozen(ns.mode)) { updateMode(ns.mode, global.context.location.hostname); } const cb = callback.bind(this, data.pid, ns), - url = getUrl(global.context, data, ns); - url ? - (hasOwn(data, 'async') ? loadScript : writeScript)(global, url, cb) : cb(); + url = getUrl(global.context, data, ns); + url + ? (hasOwn(data, 'async') ? loadScript : writeScript)(global, url, cb) + : cb(); } /** @@ -106,7 +111,9 @@ function updateMode(mode, hostname) { * @param {string} id * @param {!Object} ns */ -function callback(id, ns) { ns.addInstance(id); } +function callback(id, ns) { + ns.addInstance(id); +} /** * @param {!Object} context @@ -117,12 +124,13 @@ function callback(id, ns) { ns.addInstance(id); } */ function getUrl(context, data, ns) { const {mode} = ns, - isDev = hasOwn(data, 'isDev'), - sld_ = sld[!mode.dev], - thld_ = thld[isDev && !mode.live], - search = reduceSearch(ns, data.pid, data.click, context.referrer), - url = search ? - addParamsToUrl(`https://${thld_}.${sld_}.com/ad`, search) : false; + isDev = hasOwn(data, 'isDev'), + sld_ = sld[!mode.dev], + thld_ = thld[isDev && !mode.live], + search = reduceSearch(ns, data.pid, data.click, context.referrer), + url = search + ? addParamsToUrl(`https://${thld_}.${sld_}.com/ad`, search) + : false; return url; } @@ -143,15 +151,17 @@ function reduceSearch(ns, placementId, click, referrer) { let isRecipeStale = true; if (isRecipeLoaded) { const info = ns.args[placementId]; - isRecipeStale = (Date.now() - info['requestTime']) > (60 * cacheTime); + isRecipeStale = Date.now() - info['requestTime'] > 60 * cacheTime; } const needsRequest = !isRecipeLoaded || isRecipeStale; - return !needsRequest ? null : dict({ - 'click': click, - 'referrer': referrer, - 'isAmp': '1', - 'lib': !ns.isLibLoaded ? '1' : '', // may be prefetchable via _config - 'pid': needsRequest ? placementId : '', - }); + return !needsRequest + ? null + : dict({ + 'click': click, + 'referrer': referrer, + 'isAmp': '1', + 'lib': !ns.isLibLoaded ? '1' : '', // may be prefetchable via _config + 'pid': needsRequest ? placementId : '', + }); } diff --git a/ads/adverticum.js b/ads/adverticum.js index fcc8a57c28b3d..04f20a28683d3 100644 --- a/ads/adverticum.js +++ b/ads/adverticum.js @@ -39,5 +39,4 @@ export function adverticum(global, data) { document.getElementById(zoneid).appendChild(v); } writeScript(global, '//ad.adverticum.net/g3.js'); - } diff --git a/ads/advertserve.js b/ads/advertserve.js index 03e07faafd19f..54d2f3f3ade5a 100644 --- a/ads/advertserve.js +++ b/ads/advertserve.js @@ -23,12 +23,19 @@ import {validateData, writeScript} from '../3p/3p'; export function advertserve(global, data) { validateData(data, [], ['zid', 'pid', 'client']); - const url = 'https://' + data.client + '.advertserve.com' + - '/servlet/view/banner/javascript/zone?amp=true' + - '&zid=' + encodeURIComponent(data.zid) + - '&pid=' + encodeURIComponent(data.pid) + - '&random=' + Math.floor(89999999 * Math.random() + 10000000) + - '&millis=' + Date.now(); + const url = + 'https://' + + data.client + + '.advertserve.com' + + '/servlet/view/banner/javascript/zone?amp=true' + + '&zid=' + + encodeURIComponent(data.zid) + + '&pid=' + + encodeURIComponent(data.pid) + + '&random=' + + Math.floor(89999999 * Math.random() + 10000000) + + '&millis=' + + Date.now(); writeScript(global, url); } diff --git a/ads/aja.js b/ads/aja.js index 557fc0781e479..0d0e361242e88 100644 --- a/ads/aja.js +++ b/ads/aja.js @@ -21,12 +21,11 @@ import {validateData} from '../3p/3p'; * @param {!Object} data */ export function aja(global, data) { - validateData(data, ['sspCode']); - (global._aja = global._aja || { + global._aja = global._aja || { sspCode: data['sspCode'], - }); + }; const elStyle = global.document.createElement('iframe'); elStyle.setAttribute('id', 'adframe'); @@ -37,7 +36,8 @@ export function aja(global, data) { elStyle.setAttribute('marginwidth', '0'); elStyle.setAttribute('allowfullscreen', 'true'); elStyle.setAttribute('scrolling', 'no'); - elStyle.src = 'https://static.aja-recommend.com/html/amp.html?ssp_code=' + encodeURIComponent(data['sspCode']); + elStyle.src = + 'https://static.aja-recommend.com/html/amp.html?ssp_code=' + + encodeURIComponent(data['sspCode']); global.document.body.appendChild(elStyle); - } diff --git a/ads/alp/handler.js b/ads/alp/handler.js index 610a944a33b1e..c37966123391e 100644 --- a/ads/alp/handler.js +++ b/ads/alp/handler.js @@ -73,8 +73,11 @@ export function handleClick(e, opt_viewerNavigate) { // Tag the original href with &=1 and make it a fragment param with // name click. - const fragment = 'click=' + encodeURIComponent( - addParamToUrl(link.a.href, 'amp', '1', /* opt_addToFront */ true)); + const fragment = + 'click=' + + encodeURIComponent( + addParamToUrl(link.a.href, 'amp', '1', /* opt_addToFront */ true) + ); let destination = link.eventualUrl; if (link.eventualUrl.indexOf('#') == -1) { destination += '#' + fragment; @@ -85,8 +88,9 @@ export function handleClick(e, opt_viewerNavigate) { const ancestors = win.location.ancestorOrigins; if (ancestors && ancestors[ancestors.length - 1] == 'http://localhost:8000') { destination = destination.replace( - `${parseUrlDeprecated(link.eventualUrl).host}/c/`, - 'http://localhost:8000/max/'); + `${parseUrlDeprecated(link.eventualUrl).host}/c/`, + 'http://localhost:8000/max/' + ); } e.preventDefault(); if (opt_viewerNavigate) { @@ -134,8 +138,10 @@ function getEventualUrl(a) { if (!eventualUrl) { return; } - if (!isProxyOrigin(eventualUrl) || - !startsWith(parseUrlDeprecated(eventualUrl).pathname, '/c/')) { + if ( + !isProxyOrigin(eventualUrl) || + !startsWith(parseUrlDeprecated(eventualUrl).pathname, '/c/') + ) { return; } return eventualUrl; @@ -152,9 +158,15 @@ function navigateTo(win, a, url) { const target = (a.target || '_top').toLowerCase(); const a2aAncestor = getA2AAncestor(win); if (a2aAncestor) { - a2aAncestor.win./*OK*/postMessage('a2a;' + JSON.stringify(dict({ - 'url': url, - })), a2aAncestor.origin); + a2aAncestor.win./*OK*/ postMessage( + 'a2a;' + + JSON.stringify( + dict({ + 'url': url, + }) + ), + a2aAncestor.origin + ); return; } openWindowDialog(win, url, target); @@ -171,7 +183,7 @@ export function warmupStatic(win) { // preconnects. new win.Image().src = `${urls.cdn}/preconnect.gif`; // Preload the primary AMP JS that is render blocking. - const linkRel = /*OK*/document.createElement('link'); + const linkRel = /*OK*/ document.createElement('link'); linkRel.rel = 'preload'; linkRel.setAttribute('as', 'script'); linkRel.href = `${urls.cdn}/v0.js`; @@ -192,10 +204,10 @@ export function warmupDynamic(e) { // Preloading with empty as and newly specced value `fetch` meaning the same // thing. `document` would be the right value, but this is not yet supported // in browsers. - const linkRel0 = /*OK*/document.createElement('link'); + const linkRel0 = /*OK*/ document.createElement('link'); linkRel0.rel = 'preload'; linkRel0.href = link.eventualUrl; - const linkRel1 = /*OK*/document.createElement('link'); + const linkRel1 = /*OK*/ document.createElement('link'); linkRel1.rel = 'preload'; linkRel1.as = 'fetch'; linkRel1.href = link.eventualUrl; diff --git a/ads/appnexus.js b/ads/appnexus.js index 8fa22599904d0..508c59b820cca 100644 --- a/ads/appnexus.js +++ b/ads/appnexus.js @@ -56,7 +56,6 @@ export function appnexus(global, data) { } appnexusAst(global, data); - } /** @@ -66,7 +65,8 @@ export function appnexus(global, data) { function appnexusAst(global, data) { validateData(data, ['adUnits']); let apntag; - if (context.isMaster) { // in case we are in the master iframe, we load AST + if (context.isMaster) { + // in case we are in the master iframe, we load AST context.master.apntag = context.master.apntag || {}; context.master.apntag.anq = context.master.apntag.anq || []; apntag = context.master.apntag; @@ -83,7 +83,6 @@ function appnexusAst(global, data) { data.adUnits.forEach(adUnit => { apntag.defineTag(adUnit); }); - }); loadScript(global, APPNEXUS_AST_URL, () => { apntag.anq.push(() => { diff --git a/ads/appvador.js b/ads/appvador.js index 5bd056f55a151..271f3abbddff0 100644 --- a/ads/appvador.js +++ b/ads/appvador.js @@ -28,14 +28,19 @@ export function appvador(global, data) { apvDiv.setAttribute('id', 'apvad-' + data.id); container.appendChild(apvDiv); - const scriptUrl = data.customScriptSrc ? data.customScriptSrc : - 'https://cdn.apvdr.com/js/' + - (data.jsType ? encodeURIComponent(data.jsType) : 'VastAdUnit') + - '.min.js'; - const apvScript = 'new APV.' + + const scriptUrl = data.customScriptSrc + ? data.customScriptSrc + : 'https://cdn.apvdr.com/js/' + + (data.jsType ? encodeURIComponent(data.jsType) : 'VastAdUnit') + + '.min.js'; + const apvScript = + 'new APV.' + (data.jsType ? data.jsType : 'VASTAdUnit') + - '({s:"' + data.id + '",isAmpAd:true' + - (data.options ? (',' + data.options) : '') + '}).load();'; + '({s:"' + + data.id + + '",isAmpAd:true' + + (data.options ? ',' + data.options : '') + + '}).load();'; const cb = function() { const apvLoadScript = global.document.createElement('script'); diff --git a/ads/atomx.js b/ads/atomx.js index d6fb632908906..b44185aa8f41a 100644 --- a/ads/atomx.js +++ b/ads/atomx.js @@ -39,4 +39,3 @@ export function atomx(global, data) { writeScript(global, 'https://s.ato.mx/p.js#' + args.join('&')); } - diff --git a/ads/baidu.js b/ads/baidu.js index 4af9a0817aa3c..eecd54059af51 100644 --- a/ads/baidu.js +++ b/ads/baidu.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - loadScript, - validateData, -} from '../3p/3p'; +import {loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -26,7 +23,11 @@ import { export function baidu(global, data) { validateData(data, ['cproid']); - const id = '_' + Math.random().toString(36).slice(2); + const id = + '_' + + Math.random() + .toString(36) + .slice(2); const container = global.document.createElement('div'); container.id = id; global.document.getElementById('c').appendChild(container); @@ -44,15 +45,15 @@ export function baidu(global, data) { }); loadScript( - global, - 'https://dup.baidustatic.com/js/dm.js', - () => {}, - () => { - // noContentAvailable should be called, - // if parent iframe receives no message. - // setTimeout can work, but it's not that reliable. - // So, only the faliure of JS loading is dealed with for now. - global.context.noContentAvailable(); - } + global, + 'https://dup.baidustatic.com/js/dm.js', + () => {}, + () => { + // noContentAvailable should be called, + // if parent iframe receives no message. + // setTimeout can work, but it's not that reliable. + // So, only the faliure of JS loading is dealed with for now. + global.context.noContentAvailable(); + } ); } diff --git a/ads/bidtellect.js b/ads/bidtellect.js index a05105db3c80b..7950bbc485ec7 100644 --- a/ads/bidtellect.js +++ b/ads/bidtellect.js @@ -33,7 +33,8 @@ export function bidtellect(global, data) { 'videotypeid', 'videocloseicon', 'targetid', - 'bustframe']; + 'bustframe', + ]; validateData(data, requiredParams, optionalParams); let params = '?t=' + encodeURIComponent(data.t); params += '&pid=' + encodeURIComponent(data.pid); diff --git a/ads/brainy.js b/ads/brainy.js index 15a746f28cc6f..7647906f77b09 100644 --- a/ads/brainy.js +++ b/ads/brainy.js @@ -21,12 +21,14 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function brainy(global, data) { - validateData(data, [], ['aid', 'slotId']); - const url = 'https://proparm.jp/ssp/p/js1' - + '?_aid=' + encodeURIComponent(data['aid']) - + '&_slot=' + encodeURIComponent(data['slotId']); + const url = + 'https://proparm.jp/ssp/p/js1' + + '?_aid=' + + encodeURIComponent(data['aid']) + + '&_slot=' + + encodeURIComponent(data['slotId']); writeScript(global, url); } diff --git a/ads/bringhub.js b/ads/bringhub.js index 1cb93de7c41d3..2a206aa85f668 100644 --- a/ads/bringhub.js +++ b/ads/bringhub.js @@ -21,14 +21,21 @@ import {loadScript, writeScript} from '../3p/3p'; * @param {!Object} data */ export function bringhub(global, data) { - (global._bringhub = global._bringhub || { + global._bringhub = global._bringhub || { viewId: global.context.pageViewId, htmlURL: data['htmlurl'] || global.context.canonicalUrl, ampURL: data['ampurl'] || global.context.sourceUrl, referrer: data['referrer'] || global.context.referrer, - }); + }; - writeScript(global, `https://static.bh-cdn.com/msf/amp-loader.js?v=${Date.now()}`, function() { - loadScript(global, `https://static.bh-cdn.com/msf/amp-widget.js?v=${global._bringhub.hash}`); - }); + writeScript( + global, + `https://static.bh-cdn.com/msf/amp-loader.js?v=${Date.now()}`, + function() { + loadScript( + global, + `https://static.bh-cdn.com/msf/amp-widget.js?v=${global._bringhub.hash}` + ); + } + ); } diff --git a/ads/broadstreetads.js b/ads/broadstreetads.js index cf745330ae81d..43942742cd08f 100644 --- a/ads/broadstreetads.js +++ b/ads/broadstreetads.js @@ -20,11 +20,11 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Window} global * @param {!Object} data */ -export function broadstreetads(global,data) { +export function broadstreetads(global, data) { validateData( - data, - ['network', 'zone', 'width', 'height'], - ['keywords', 'place'] + data, + ['network', 'zone', 'width', 'height'], + ['keywords', 'place'] ); data.place = data.place || 0; diff --git a/ads/caajainfeed.js b/ads/caajainfeed.js index 68cd1136d2bdc..ae07567aca7fc 100644 --- a/ads/caajainfeed.js +++ b/ads/caajainfeed.js @@ -21,35 +21,33 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function caajainfeed(global, data) { - validateData( - data, - [], - [ - 'adSpot', - 'format', - 'test', - 'optout', - 'offset', - 'ipv4', - 'ipv6', - 'networkReachability', - 'osName', - 'osVersion', - 'osLang', - 'osTimezone', - 'deviceVersion', - 'appId', - 'appVersion', - 'kv', - 'uids', - 'template', - 'protocol', - 'fields', - ] + data, + [], + [ + 'adSpot', + 'format', + 'test', + 'optout', + 'offset', + 'ipv4', + 'ipv6', + 'networkReachability', + 'osName', + 'osVersion', + 'osLang', + 'osTimezone', + 'deviceVersion', + 'appId', + 'appVersion', + 'kv', + 'uids', + 'template', + 'protocol', + 'fields', + ] ); global.caAjaInfeedConfig = data; loadScript(global, 'https://cdn.amanad.adtdp.com/sdk/ajaamp.js'); - } diff --git a/ads/capirs.js b/ads/capirs.js index ebb00129edcc1..0854087576821 100644 --- a/ads/capirs.js +++ b/ads/capirs.js @@ -61,7 +61,9 @@ export function capirs(global, data) { const reportId = 'capirs-' + banner['banner_id']; global.context.reportRenderedEntityIdentifier(reportId); }, - unexist: function() { global.context.noContentAvailable(); }, + unexist: function() { + global.context.noContentAvailable(); + }, }, }; @@ -78,8 +80,8 @@ function getWidth(global, banner) { if (isResponsiveAd(banner)) { width = Math.max( - global.document.documentElement./*OK*/clientWidth, - global.window./*OK*/innerWidth || 0 + global.document.documentElement./*OK*/ clientWidth, + global.window./*OK*/ innerWidth || 0 ); } else { width = banner.width; diff --git a/ads/caprofitx.js b/ads/caprofitx.js index 9548ab3b09ac6..759b28836622f 100644 --- a/ads/caprofitx.js +++ b/ads/caprofitx.js @@ -21,7 +21,6 @@ import {loadScript} from '../3p/3p'; * @param {!Object} data */ export function caprofitx(global, data) { - global.caprofitxConfig = data; loadScript(global, 'https://cdn.caprofitx.com/tags/amp/profitx_amp.js'); } diff --git a/ads/cedato.js b/ads/cedato.js index ba5bb2e12cdbc..eff6ffc11f1d4 100644 --- a/ads/cedato.js +++ b/ads/cedato.js @@ -39,8 +39,8 @@ export function cedato(global, data) { } const cb = Math.floor(Math.random() * 10000); - const domain = data.domain || - parseUrlDeprecated(global.context.sourceUrl).origin; + const domain = + data.domain || parseUrlDeprecated(global.context.sourceUrl).origin; /* Create div for ad to target */ const playerDiv = global.document.createElement('div'); @@ -50,19 +50,19 @@ export function cedato(global, data) { height: '100%', }); const playerScript = global.document.createElement('script'); - const servingDomain = data.servingDomain ? - encodeURIComponent(data.servingDomain) : - 'algovid.com'; + const servingDomain = data.servingDomain + ? encodeURIComponent(data.servingDomain) + : 'algovid.com'; const srcParams = [ 'https://p.' + servingDomain + '/player/player.js', '?p=' + encodeURIComponent(data.id), '&cb=' + cb, '&w=' + encodeURIComponent(data.width), '&h=' + encodeURIComponent(data.height), - (data.version ? '&pv=' + encodeURIComponent(data.version) : ''), - (data.subid ? '&subid=' + encodeURIComponent(data.subid) : ''), - (domain ? '&d=' + encodeURIComponent(domain) : ''), - (data.extraParams || ''), // already encoded url query string + data.version ? '&pv=' + encodeURIComponent(data.version) : '', + data.subid ? '&subid=' + encodeURIComponent(data.subid) : '', + domain ? '&d=' + encodeURIComponent(domain) : '', + data.extraParams || '', // already encoded url query string ]; playerScript.onload = () => { diff --git a/ads/chargeads.js b/ads/chargeads.js index cc1ff638d77cd..f3f9901fa3a4b 100644 --- a/ads/chargeads.js +++ b/ads/chargeads.js @@ -22,6 +22,9 @@ import {validateSrcPrefix, writeScript} from '../3p/3p'; */ export function chargeads(global, data) { const {src} = data; - validateSrcPrefix(['https://www.chargeplatform.com/', 'https://tags.chargeplatform.com/'], src); + validateSrcPrefix( + ['https://www.chargeplatform.com/', 'https://tags.chargeplatform.com/'], + src + ); writeScript(global, src); } diff --git a/ads/colombia.js b/ads/colombia.js index 1f2ec93ffd8b3..df7a3b32b4870 100644 --- a/ads/colombia.js +++ b/ads/colombia.js @@ -22,8 +22,11 @@ import {loadScript, validateData} from '../3p/3p'; */ export function colombia(global, data) { validateData(data, [ - 'clmb_slot', 'clmb_position', 'clmb_section', - 'clmb_divid', 'loadingStrategy', + 'clmb_slot', + 'clmb_position', + 'clmb_section', + 'clmb_divid', + 'loadingStrategy', ]); // push the two object into the '_colombia' global (global._colombia = global._colombia || []).push({ @@ -43,5 +46,8 @@ export function colombia(global, data) { } }); }); - loadScript(global, 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js'); + loadScript( + global, + 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js' + ); } diff --git a/ads/connatix.js b/ads/connatix.js index 271677b3c2fa8..a80fc25b6e6ec 100644 --- a/ads/connatix.js +++ b/ads/connatix.js @@ -23,7 +23,6 @@ import {validateData} from '../3p/3p'; * @param {!Object} data */ export function connatix(global, data) { - validateData(data, ['connatix']); // Because 3p's loadScript does not allow for data attributes, @@ -37,9 +36,13 @@ export function connatix(global, data) { } } - window.addEventListener('connatix_no_content', function() { - window.context.noContentAvailable(); - }, false); + window.addEventListener( + 'connatix_no_content', + function() { + window.context.noContentAvailable(); + }, + false + ); script.onload = () => { global.context.renderStart(); diff --git a/ads/contentad.js b/ads/contentad.js index e6bf8110d8030..356906fca5dd1 100644 --- a/ads/contentad.js +++ b/ads/contentad.js @@ -41,12 +41,18 @@ export function contentad(global, data) { } /* Build API URL */ - const cadApi = 'https://api.content-ad.net/Scripts/widget2.aspx' - + '?id=' + encodeURIComponent(global.id) - + '&d=' + encodeURIComponent(global.d) - + '&wid=' + global.wid - + '&url=' + encodeURIComponent(sourceUrl) - + '&cb=' + Date.now(); + const cadApi = + 'https://api.content-ad.net/Scripts/widget2.aspx' + + '?id=' + + encodeURIComponent(global.id) + + '&d=' + + encodeURIComponent(global.d) + + '&wid=' + + global.wid + + '&url=' + + encodeURIComponent(sourceUrl) + + '&cb=' + + Date.now(); /* Call Content.ad Widget */ writeScript(global, cadApi); diff --git a/ads/criteo.js b/ads/criteo.js index e8cb79ea3f175..07442661bfa42 100644 --- a/ads/criteo.js +++ b/ads/criteo.js @@ -35,11 +35,17 @@ export function criteo(global, data) { integrationmode: 'amp', }); } else if (data.tagtype === 'rta' || data.tagtype === 'standalone') { - dev().error(TAG, 'You are using a deprecated Criteo integration', - data.tagtype); + dev().error( + TAG, + 'You are using a deprecated Criteo integration', + data.tagtype + ); } else { - dev().error(TAG, 'You are using an unknown Criteo integration', - data.tagtype); + dev().error( + TAG, + 'You are using an unknown Criteo integration', + data.tagtype + ); } }); } diff --git a/ads/dable.js b/ads/dable.js index 798b0c0d38f88..413c5b00be8a3 100644 --- a/ads/dable.js +++ b/ads/dable.js @@ -24,11 +24,15 @@ export function dable(global, data) { // check required props validateData(data, ['widgetId']); - (global.dable = global.dable || function() { - (global.dable.q = global.dable.q || []).push(arguments); - }); - global.dable('setService', data['serviceName'] || - global.window.context.location.hostname); + global.dable = + global.dable || + function() { + (global.dable.q = global.dable.q || []).push(arguments); + }; + global.dable( + 'setService', + data['serviceName'] || global.window.context.location.hostname + ); global.dable('setURL', global.window.context.sourceUrl); global.dable('setRef', global.window.context.referrer); diff --git a/ads/directadvert.js b/ads/directadvert.js index 0e460ae8432ba..d3e9f3083b54b 100644 --- a/ads/directadvert.js +++ b/ads/directadvert.js @@ -23,13 +23,19 @@ import {loadScript, validateData} from '../3p/3p'; export function directadvert(global, data) { validateData(data, ['blockId']); - const url = 'https://code.directadvert.ru/data/' + - encodeURIComponent(data['blockId']) + '.js?async=1&div=c'; - - loadScript(global, url, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + const url = + 'https://code.directadvert.ru/data/' + + encodeURIComponent(data['blockId']) + + '.js?async=1&div=c'; + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/distroscale.js b/ads/distroscale.js index d29a035cde0ce..3ad830f2f4b09 100644 --- a/ads/distroscale.js +++ b/ads/distroscale.js @@ -40,12 +40,16 @@ export function distroscale(global, data) { src += '&f=' + encodeURIComponent(srcUrl); - global.dsAMPCallbacks = { renderStart: global.context.renderStart, noContentAvailable: global.context.noContentAvailable, }; - loadScript(global, src, () => {}, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + src, + () => {}, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/eadv.js b/ads/eadv.js index a012355bfeb75..b61884465308a 100644 --- a/ads/eadv.js +++ b/ads/eadv.js @@ -22,5 +22,8 @@ import {validateData, writeScript} from '../3p/3p'; */ export function eadv(global, data) { validateData(data, ['x', 'u'], []); - writeScript(global, 'https://www.eadv.it/track/?x=' + data.x + '&u=' + data.u); + writeScript( + global, + 'https://www.eadv.it/track/?x=' + data.x + '&u=' + data.u + ); } diff --git a/ads/engageya.js b/ads/engageya.js index 2197a46e48c40..8a8864b86695d 100644 --- a/ads/engageya.js +++ b/ads/engageya.js @@ -21,10 +21,9 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function engageya(global, data) { - validateData(data, ['widgetids']); - (global._engageya = global._engageya || { + global._engageya = global._engageya || { viewId: global.context.pageViewId, widgetIds: data['widgetids'], websiteId: data['websiteid'], @@ -34,7 +33,7 @@ export function engageya(global, data) { mode: data['mode'] || 1, style: data['stylecss'] || '', referrer: global.context.referrer, - }); + }; loadScript(global, 'https://widget.engageya.com/engageya_amp_loader.js'); } diff --git a/ads/epeex.js b/ads/epeex.js index 503049748ed96..160d3023d88f0 100644 --- a/ads/epeex.js +++ b/ads/epeex.js @@ -21,14 +21,13 @@ import {loadScript} from '../3p/3p'; * @param {!Object} data */ export function epeex(global, data) { - - (global._epeex = global._epeex || { + global._epeex = global._epeex || { account: data['account'] || 'demoepeex', channel: data['channel'] || '1', htmlURL: data['htmlurl'] || encodeURIComponent(global.context.canonicalUrl), ampURL: data['ampurl'] || encodeURIComponent(global.context.sourceUrl), testMode: data['testmode'] || 'false', - }); + }; // load the epeex AMP remote js file loadScript(global, 'https://epeex.com/related/service/widget/amp/remote.js'); diff --git a/ads/eplanning.js b/ads/eplanning.js index 04972711af233..7c50740779e13 100644 --- a/ads/eplanning.js +++ b/ads/eplanning.js @@ -22,7 +22,12 @@ import {loadScript, validateData} from '../3p/3p'; */ export function eplanning(global, data) { validateData(data, [ - 'epl_si', 'epl_isv', 'epl_sv', 'epl_sec', 'epl_kvs', 'epl_e', + 'epl_si', + 'epl_isv', + 'epl_sv', + 'epl_sec', + 'epl_kvs', + 'epl_e', ]); // push the two object into the '_eplanning' global (global._eplanning = global._eplanning || []).push({ diff --git a/ads/ezoic.js b/ads/ezoic.js index 9f44b12aa291c..f783c5777917a 100644 --- a/ads/ezoic.js +++ b/ads/ezoic.js @@ -22,12 +22,16 @@ import {loadScript, validateData} from '../3p/3p'; */ export function ezoic(global, data) { // TODO: check mandatory fields - validateData(data, [], ['slot','targeting','extras']); + validateData(data, [], ['slot', 'targeting', 'extras']); loadScript(global, 'https://g.ezoic.net/ezoic/ampad.js', () => { - loadScript(global, 'https://www.googletagservices.com/tag/js/gpt.js', () => { - global.googletag.cmd.push(() => { - new window.EzoicAmpAd(global,data).createAd(); - }); - }); + loadScript( + global, + 'https://www.googletagservices.com/tag/js/gpt.js', + () => { + global.googletag.cmd.push(() => { + new window.EzoicAmpAd(global, data).createAd(); + }); + } + ); }); } diff --git a/ads/f1e.js b/ads/f1e.js index 180ab3d69f4e2..74fe9db6983d1 100644 --- a/ads/f1e.js +++ b/ads/f1e.js @@ -21,7 +21,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function f1e(global, data) { - validateData(data, ['url','target'], []); + validateData(data, ['url', 'target'], []); global.f1eData = data; writeScript(global, 'https://img.ak.impact-ad.jp/util/f1e_amp.min.js'); } diff --git a/ads/f1h.js b/ads/f1h.js index 0c38b485d6bac..942219b53faa8 100644 --- a/ads/f1h.js +++ b/ads/f1h.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - loadScript, - validateData, -} from '../3p/3p'; +import {loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -27,9 +24,8 @@ export function f1h(global, data) { validateData(data, ['sectionId', 'slot']); const scriptUrl = - data['debugsrc'] || 'https://img.ak.impact-ad.jp/fh/f1h_amp.js'; + data['debugsrc'] || 'https://img.ak.impact-ad.jp/fh/f1h_amp.js'; global.f1hData = data; loadScript(global, scriptUrl); } - diff --git a/ads/felmat.js b/ads/felmat.js index dc7532c0833c9..2bce7bf1ee79b 100644 --- a/ads/felmat.js +++ b/ads/felmat.js @@ -23,5 +23,8 @@ import {validateData, writeScript} from '../3p/3p'; export function felmat(global, data) { validateData(data, ['host', 'fmt', 'fmk', 'fmp']); global.fmParam = data; - writeScript(global, 'https://t.' + encodeURIComponent(data.host) + '/js/fmamp.js'); + writeScript( + global, + 'https://t.' + encodeURIComponent(data.host) + '/js/fmamp.js' + ); } diff --git a/ads/flite.js b/ads/flite.js index 602842d3001fb..95fbfbd91e4ee 100644 --- a/ads/flite.js +++ b/ads/flite.js @@ -22,19 +22,34 @@ import {loadScript, validateData} from '../3p/3p'; */ export function flite(global, data) { // TODO: check mandatory fields - validateData(data, [], ['guid','mixins']); - const {guid} = data, o = global, e = encodeURIComponent, x = 0; - let r = '', dep = ''; + validateData(data, [], ['guid', 'mixins']); + const {guid} = data, + o = global, + e = encodeURIComponent, + x = 0; + let r = '', + dep = ''; o.FLITE = o.FLITE || {}; o.FLITE.config = o.FLITE.config || {}; o.FLITE.config[guid] = o.FLITE.config[guid] || {}; o.FLITE.config[guid].cb = Math.random(); - o.FLITE.config[guid].ts = (+Number(new Date())); + o.FLITE.config[guid].ts = +Number(new Date()); r = global.context.location.href; const m = r.match(new RegExp('[A-Za-z]+:[/][/][A-Za-z0-9.-]+')); dep = data.mixins ? '&dep=' + data.mixins : ''; - const url = ['https://r.flite.com/syndication/uscript.js?i=',e(guid), - '&v=3',dep,'&x=us',x,'&cb=',o.FLITE.config[guid].cb,'&d=', - e((m && m[0]) || r), '&tz=', (new Date()).getTimezoneOffset()].join(''); + const url = [ + 'https://r.flite.com/syndication/uscript.js?i=', + e(guid), + '&v=3', + dep, + '&x=us', + x, + '&cb=', + o.FLITE.config[guid].cb, + '&d=', + e((m && m[0]) || r), + '&tz=', + new Date().getTimezoneOffset(), + ].join(''); loadScript(o, url); } diff --git a/ads/fluct.js b/ads/fluct.js index 4b0ddd7839a44..45863b78cc8a0 100644 --- a/ads/fluct.js +++ b/ads/fluct.js @@ -24,9 +24,11 @@ import {validateData, writeScript} from '../3p/3p'; */ export function fluct(global, data) { validateData(data, ['g', 'u']); - writeScript(global, - `https://cdn-fluct.sh.adingo.jp/f.js?G=${encodeURIComponent(data['g'])}`, - function() { - adingoFluct.showAd(data['u']); - }); + writeScript( + global, + `https://cdn-fluct.sh.adingo.jp/f.js?G=${encodeURIComponent(data['g'])}`, + function() { + adingoFluct.showAd(data['u']); + } + ); } diff --git a/ads/freewheel.js b/ads/freewheel.js index f08d7dc92b4b8..b0e0757ab56a9 100644 --- a/ads/freewheel.js +++ b/ads/freewheel.js @@ -27,11 +27,25 @@ export function freewheel(global, data) { }; validateData( - data, - ['zone'], - ['zone','gdpr','gdpr_consent','useCMP','zIndex','blurDisplay', - 'timeline','soundButton','defaultMute','onOver','closeAction', - 'errorAction','pauseRatio','label','vastUrlParams'] + data, + ['zone'], + [ + 'zone', + 'gdpr', + 'gdpr_consent', + 'useCMP', + 'zIndex', + 'blurDisplay', + 'timeline', + 'soundButton', + 'defaultMute', + 'onOver', + 'closeAction', + 'errorAction', + 'pauseRatio', + 'label', + 'vastUrlParams', + ] ); loadScript(global, 'https://cdn.stickyadstv.com/prime-time/fw-amp.min.js'); diff --git a/ads/fusion.js b/ads/fusion.js index 5c38422819259..3c2bda914f724 100644 --- a/ads/fusion.js +++ b/ads/fusion.js @@ -24,10 +24,13 @@ function queryParametersToObject(input) { if (!input) { return undefined; } - return input.split('&').filter(_ => _).reduce((obj, val) => { - const kv = val.split('='); - return Object.assign(obj, {[kv[0]]: kv[1] || true}); - }, {}); + return input + .split('&') + .filter(_ => _) + .reduce((obj, val) => { + const kv = val.split('='); + return Object.assign(obj, {[kv[0]]: kv[1] || true}); + }, {}); } /** @@ -35,8 +38,11 @@ function queryParametersToObject(input) { * @param {!Object} data */ export function fusion(global, data) { - validateData(data, [], - ['mediaZone', 'layout', 'adServer', 'space', 'parameters']); + validateData( + data, + [], + ['mediaZone', 'layout', 'adServer', 'space', 'parameters'] + ); const container = global.document.getElementById('c'); const ad = global.document.createElement('div'); @@ -44,14 +50,17 @@ export function fusion(global, data) { container.appendChild(ad); const parameters = queryParametersToObject(data.parameters); - writeScript(global, - 'https://assets.adtomafusion.net/fusion/latest/fusion-amp.min.js', () => { - global.Fusion.apply(container, global.Fusion.loadAds(data, parameters)); + writeScript( + global, + 'https://assets.adtomafusion.net/fusion/latest/fusion-amp.min.js', + () => { + global.Fusion.apply(container, global.Fusion.loadAds(data, parameters)); - global.Fusion.on.warning.run(ev => { - if (ev.msg === 'Space not present in response.') { - global.context.noContentAvailable(); - } - }); + global.Fusion.on.warning.run(ev => { + if (ev.msg === 'Space not present in response.') { + global.context.noContentAvailable(); + } }); + } + ); } diff --git a/ads/giraff.js b/ads/giraff.js index 160a0a3e4250b..4f317f3ffaa2a 100644 --- a/ads/giraff.js +++ b/ads/giraff.js @@ -24,18 +24,26 @@ export function giraff(global, data) { validateData(data, ['blockName']); const serverName = data['serverName'] || 'code.giraff.io'; - const url = '//' + encodeURIComponent(serverName) + '/data/widget-' + - encodeURIComponent(data['blockName']) + '.js'; + const url = + '//' + + encodeURIComponent(serverName) + + '/data/widget-' + + encodeURIComponent(data['blockName']) + + '.js'; - loadScript(global, url, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); const anchorEl = global.document.createElement('div'); const widgetId = data['widgetId'] ? '_' + data['widgetId'] : ''; anchorEl.id = 'grf_' + data['blockName'] + widgetId; global.document.getElementById('c').appendChild(anchorEl); - } diff --git a/ads/google/a4a/experiment-utils.js b/ads/google/a4a/experiment-utils.js index ba8a264bbd8a2..1bf47146d3534 100644 --- a/ads/google/a4a/experiment-utils.js +++ b/ads/google/a4a/experiment-utils.js @@ -20,9 +20,7 @@ import { getExperimentBranch, randomlySelectUnsetExperiments, } from '../../../src/experiments'; -import { - addExperimentIdToElement, -} from './traffic-experiments'; +import {addExperimentIdToElement} from './traffic-experiments'; /** * Attempts to select into experiment and forces branch if selected. @@ -33,12 +31,23 @@ import { * @param {boolean=} optAddExpIdToElement */ export function selectAndSetExperiments( - win, element, branches, expName, optAddExpIdToElement) { + win, + element, + branches, + expName, + optAddExpIdToElement +) { const experimentId = expUtils.maybeSelectExperiment( - win, element, branches, expName); + win, + element, + branches, + expName + ); if (!!experimentId) { - addExperimentIdToElement(optAddExpIdToElement ? - experimentId : undefined, element); + addExperimentIdToElement( + optAddExpIdToElement ? experimentId : undefined, + element + ); forceExperimentBranch(win, expName, experimentId); } return experimentId; @@ -51,10 +60,8 @@ export class ExperimentUtils { * @param {!Array} selectionBranches * @param {string} experimentName */ - maybeSelectExperiment( - win, element, selectionBranches, experimentName) { - const experimentInfoMap = - /** @type {!Object} */ ({}); + maybeSelectExperiment(win, element, selectionBranches, experimentName) { + const experimentInfoMap = /** @type {!Object} */ ({}); experimentInfoMap[experimentName] = { isTrafficEligible: () => true, branches: selectionBranches, @@ -67,5 +74,5 @@ export class ExperimentUtils { /** * ExperimentUtils singleton. * @type {!ExperimentUtils} -*/ + */ const expUtils = new ExperimentUtils(); diff --git a/ads/google/a4a/line-delimited-response-handler.js b/ads/google/a4a/line-delimited-response-handler.js index df5ccaf09a238..e7b2dee442b72 100644 --- a/ads/google/a4a/line-delimited-response-handler.js +++ b/ads/google/a4a/line-delimited-response-handler.js @@ -17,13 +17,13 @@ import {tryParseJson} from '../../../src/json'; /** - * Handles an XHR response by calling lineCallback for each line delineation. - * Uses streaming where possible otherwise falls back to text. - * @param {!Window} win - * @param {!Response} response - * @param {function(string, boolean)} lineCallback - * @private - */ + * Handles an XHR response by calling lineCallback for each line delineation. + * Uses streaming where possible otherwise falls back to text. + * @param {!Window} win + * @param {!Response} response + * @param {function(string, boolean)} lineCallback + * @private + */ export function lineDelimitedStreamer(win, response, lineCallback) { let line = ''; /** @@ -50,14 +50,15 @@ export function lineDelimitedStreamer(win, response, lineCallback) { } const decoder = new TextDecoder('utf-8'); - const reader = /** @type !ReadableStreamDefaultReader */ ( - response.body.getReader()); + const reader = /** @type !ReadableStreamDefaultReader */ response.body.getReader(); reader.read().then(function chunk(result) { if (result.value) { streamer( - decoder.decode( - /** @type {!ArrayBuffer} */(result.value), {'stream': true}), - result.done); + decoder.decode(/** @type {!ArrayBuffer} */ (result.value), { + 'stream': true, + }), + result.done + ); } if (!result.done) { // More chunks to read. @@ -67,22 +68,21 @@ export function lineDelimitedStreamer(win, response, lineCallback) { } /** - * Given each line, groups such that the first is JSON parsed and second - * html unescaped. - * @param {function(string, !Object, boolean)} callback - * @private - */ + * Given each line, groups such that the first is JSON parsed and second + * html unescaped. + * @param {function(string, !Object, boolean)} callback + * @private + */ export function metaJsonCreativeGrouper(callback) { let first; return function(line, done) { if (first) { const metadata = - /** @type {!Object} */(tryParseJson(first) || {}); - const lowerCasedMetadata = - Object.keys(metadata).reduce((newObj, key) => { - newObj[key.toLowerCase()] = metadata[key]; - return newObj; - }, {}); + /** @type {!Object} */ (tryParseJson(first) || {}); + const lowerCasedMetadata = Object.keys(metadata).reduce((newObj, key) => { + newObj[key.toLowerCase()] = metadata[key]; + return newObj; + }, {}); callback(unescapeLineDelimitedHtml_(line), lowerCasedMetadata, done); first = null; } else { @@ -92,13 +92,13 @@ export function metaJsonCreativeGrouper(callback) { } /** - * Unescapes characters that are escaped in line-delimited JSON-HTML. - * @param {string} html An html snippet. - * @return {string} - * @private - */ + * Unescapes characters that are escaped in line-delimited JSON-HTML. + * @param {string} html An html snippet. + * @return {string} + * @private + */ function unescapeLineDelimitedHtml_(html) { - return html.replace( - /\\(n|r|\\)/g, - (_, match) => match == 'n' ? '\n' : match == 'r' ? '\r' : '\\'); + return html.replace(/\\(n|r|\\)/g, (_, match) => + match == 'n' ? '\n' : match == 'r' ? '\r' : '\\' + ); } diff --git a/ads/google/a4a/shared/content-recommendation.js b/ads/google/a4a/shared/content-recommendation.js index d6f23e60a1c41..e5b85c7dc3b43 100644 --- a/ads/google/a4a/shared/content-recommendation.js +++ b/ads/google/a4a/shared/content-recommendation.js @@ -130,10 +130,11 @@ const LAYOUT_AD_WIDTH_MAP = { const PUB_CONTROL_LAYOUT_PREFIX = 'pub_control_'; -const PUB_CONTROL_EXAMPLE = '\n ' + - 'data-matched-content-rows-num=\"4,2\"\n' + - 'data-matched-content-columns-num=\"1,6\"\n' + - 'data-matched-content-ui-type=\"image_stacked,image_card_sidebyside\"'; +const PUB_CONTROL_EXAMPLE = + '\n ' + + 'data-matched-content-rows-num="4,2"\n' + + 'data-matched-content-columns-num="1,6"\n' + + 'data-matched-content-ui-type="image_stacked,image_card_sidebyside"'; /** * Configuration of content recommendation unit for current slot. This is the @@ -141,7 +142,8 @@ const PUB_CONTROL_EXAMPLE = '\n ' + * will be used in ad request. * @record */ -export class CoReConfig { // eslint-disable-line no-unused-vars +export class CoReConfig { + // eslint-disable-line no-unused-vars /** see comment on class */ constructor() { /** @const {number} */ @@ -195,7 +197,11 @@ export function getAutoConfig(availableWidth, isMobile) { const numColumns = 1; const numRows = 12; const slotSize = getLargerAdOneColumnSidebysideSize( - availableWidth, layoutType, numColumns, numRows); + availableWidth, + layoutType, + numColumns, + numRows + ); return { slotWidth: slotSize.width, slotHeight: slotSize.height, @@ -260,8 +266,10 @@ export function getPubControlConfig(availableWidth, rawPubControlParams) { } let index; - if (pubParams.layoutTypes.length === 2 && - availableWidth >= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP) { + if ( + pubParams.layoutTypes.length === 2 && + availableWidth >= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP + ) { // Publisher provided settings for both mobile and desktop and screen is // wide - use desktop. index = 1; @@ -273,11 +281,18 @@ export function getPubControlConfig(availableWidth, rawPubControlParams) { const layout = convertToPubControlLayoutType(pubParams.layoutTypes[index]); const numColumns = getOptimizedNumColumns( - availableWidth, pubParams.numberOfColumns[index], layout); + availableWidth, + pubParams.numberOfColumns[index], + layout + ); const numRows = pubParams.numberOfRows[index]; - const slotSize = - getPubControlSlotSize(availableWidth, numColumns, numRows, layout); + const slotSize = getPubControlSlotSize( + availableWidth, + numColumns, + numRows, + layout + ); if (slotSize.sizeError) { return { slotWidth: 0, @@ -323,24 +338,28 @@ function validateAndParsePubControlParams(params) { if (numberOfPubControlParams < 3) { return { validationError: `Tags ${ExternalCorePubVars.UI_TYPE}, ${ - ExternalCorePubVars.COLUMNS_NUM} and ${ - ExternalCorePubVars.ROWS_NUM} should be set together.`, + ExternalCorePubVars.COLUMNS_NUM + } and ${ExternalCorePubVars.ROWS_NUM} should be set together.`, }; } const /** !Array */ layoutTypes = params.layoutType.split(','); const /** !Array */ numberOfRows = params.numberOfRows.split(','); - const /** !Array */ numberOfColumns = - params.numberOfColumns.split(','); + const /** !Array */ numberOfColumns = params.numberOfColumns.split( + ',' + ); // Check all params have same length. - if (layoutTypes.length !== numberOfRows.length || - layoutTypes.length !== numberOfColumns.length) { + if ( + layoutTypes.length !== numberOfRows.length || + layoutTypes.length !== numberOfColumns.length + ) { return { validationError: `Lengths of parameters ${ExternalCorePubVars.UI_TYPE}, ${ - ExternalCorePubVars.COLUMNS_NUM} and ${ - ExternalCorePubVars.ROWS_NUM} must match. Example: ${ - PUB_CONTROL_EXAMPLE}`, + ExternalCorePubVars.COLUMNS_NUM + } and ${ + ExternalCorePubVars.ROWS_NUM + } must match. Example: ${PUB_CONTROL_EXAMPLE}`, }; } @@ -348,12 +367,14 @@ function validateAndParsePubControlParams(params) { return { validationError: `The parameter length of attribute ${ExternalCorePubVars.UI_TYPE}, ${ - ExternalCorePubVars.COLUMNS_NUM} and ${ - ExternalCorePubVars - .ROWS_NUM} is too long. At most 2 parameters for each ` + + ExternalCorePubVars.COLUMNS_NUM + } and ${ + ExternalCorePubVars.ROWS_NUM + } is too long. At most 2 parameters for each ` + 'attribute are needed: one for mobile and one for desktop, while ' + - `you are providing ${layoutTypes.length} parameters. Example: ${ - PUB_CONTROL_EXAMPLE}.`, + `you are providing ${ + layoutTypes.length + } parameters. Example: ${PUB_CONTROL_EXAMPLE}.`, }; } @@ -364,7 +385,8 @@ function validateAndParsePubControlParams(params) { if (isNaN(row) || row === 0) { return { validationError: `Wrong value '${numberOfRows[i]}' for ${ - ExternalCorePubVars.ROWS_NUM}.`, + ExternalCorePubVars.ROWS_NUM + }.`, }; } numberOfRowsAsNumbers.push(row); @@ -372,7 +394,8 @@ function validateAndParsePubControlParams(params) { if (isNaN(col) || col === 0) { return { validationError: `Wrong value '${numberOfColumns[i]}' for ${ - ExternalCorePubVars.COLUMNS_NUM}.`, + ExternalCorePubVars.COLUMNS_NUM + }.`, }; } numberOfColumnsAsNumbers.push(col); @@ -409,8 +432,9 @@ function getAutoSlotSize(availableWidth) { * @return {number} */ function getAdHeight(adWidth, layout) { - return adWidth * LAYOUT_ASPECT_RATIO_MAP[layout] + - LAYOUT_TEXT_HEIGHT_MAP[layout]; + return ( + adWidth * LAYOUT_ASPECT_RATIO_MAP[layout] + LAYOUT_TEXT_HEIGHT_MAP[layout] + ); } /** @@ -470,7 +494,6 @@ function getPubControlSlotSize(slotWidth, numColumns, numRows, layout) { return {width: slotWidth, height: slotHeight}; } - /** * @param {number} availableWidth * @param {!LayoutType} layoutType @@ -479,7 +502,11 @@ function getPubControlSlotSize(slotWidth, numColumns, numRows, layout) { * @return {{width: number, height: number}} */ function getLargerAdOneColumnSidebysideSize( - availableWidth, layoutType, numColumns, numRows) { + availableWidth, + layoutType, + numColumns, + numRows +) { const adWidth = getAdWidth(availableWidth, numColumns); // The title height of first ad slot 70px, should be consistent with what we // define in rendering js. @@ -498,9 +525,9 @@ function getLargerAdOneColumnSidebysideSize( * @return {!LayoutType} the new layout name with 'pub_control_' prefix. */ function convertToPubControlLayoutType(layout) { - return layout.indexOf(PUB_CONTROL_LAYOUT_PREFIX) === 0 ? - layout : - /** @type {!LayoutType} */ (PUB_CONTROL_LAYOUT_PREFIX + layout); + return layout.indexOf(PUB_CONTROL_LAYOUT_PREFIX) === 0 + ? layout + : /** @type {!LayoutType} */ (PUB_CONTROL_LAYOUT_PREFIX + layout); } /** @@ -517,8 +544,10 @@ function convertToPubControlLayoutType(layout) { function getOptimizedNumColumns(availableWidth, numColumns, layout) { const minWidth = LAYOUT_AD_WIDTH_MAP[layout]; let optimizedNumColumns = numColumns; - while (availableWidth / optimizedNumColumns < minWidth && - optimizedNumColumns > 1) { + while ( + availableWidth / optimizedNumColumns < minWidth && + optimizedNumColumns > 1 + ) { optimizedNumColumns--; } return optimizedNumColumns; diff --git a/ads/google/a4a/shared/test/test-content-recommendation.js b/ads/google/a4a/shared/test/test-content-recommendation.js index 88f4f04b83593..22594acdf8951 100644 --- a/ads/google/a4a/shared/test/test-content-recommendation.js +++ b/ads/google/a4a/shared/test/test-content-recommendation.js @@ -23,106 +23,147 @@ import { describe('getAutoConfig', function() { it('should use image_stacked on wide slots', function() { const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getAutoConfig(availableWidth, /* isMobile= */ false)) - .to.deep.equal({ - layoutType: LayoutType.IMAGE_STACKED, - numberOfColumns: 4, - numberOfRows: 2, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); + expect( + getAutoConfig(availableWidth, /* isMobile= */ false) + ).to.deep.equal({ + layoutType: LayoutType.IMAGE_STACKED, + numberOfColumns: 4, + numberOfRows: 2, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + }); }; runTest( - /* availableWidth= */ 1440, /* expectedWidth= */ 1200, - /* expectedHeight= */ 600); + /* availableWidth= */ 1440, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 600 + ); runTest( - /* availableWidth= */ 1200, /* expectedWidth= */ 1200, - /* expectedHeight= */ 600); + /* availableWidth= */ 1200, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 600 + ); runTest( - /* availableWidth= */ 800, /* expectedWidth= */ 800, - /* expectedHeight= */ 480); + /* availableWidth= */ 800, + /* expectedWidth= */ 800, + /* expectedHeight= */ 480 + ); runTest( - /* availableWidth= */ 500, /* expectedWidth= */ 500, - /* expectedHeight= */ 350); + /* availableWidth= */ 500, + /* expectedWidth= */ 500, + /* expectedHeight= */ 350 + ); runTest( - /* availableWidth= */ 468, /* expectedWidth= */ 468, - /* expectedHeight= */ 327); + /* availableWidth= */ 468, + /* expectedWidth= */ 468, + /* expectedHeight= */ 327 + ); }); - it('should use mobile_banner_image_sidebyside on narrow slots on mobile', - function() { - const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getAutoConfig(availableWidth, /* isMobile= */ true)) - .to.deep.equal({ - layoutType: LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE, - numberOfColumns: 1, - numberOfRows: 12, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); - }; - runTest( - /* availableWidth= */ 467, /* expectedWidth= */ 467, - /* expectedHeight= */ 1700); - runTest( - /* availableWidth= */ 414, /* expectedWidth= */ 414, - /* expectedHeight= */ 1520); - runTest( - /* availableWidth= */ 360, /* expectedWidth= */ 360, - /* expectedHeight= */ 1336); - runTest( - /* availableWidth= */ 320, /* expectedWidth= */ 320, - /* expectedHeight= */ 1200); - runTest( - /* availableWidth= */ 300, /* expectedWidth= */ 300, - /* expectedHeight= */ 1131); - runTest( - /* availableWidth= */ 200, /* expectedWidth= */ 200, - /* expectedHeight= */ 791); - runTest( - /* availableWidth= */ 120, /* expectedWidth= */ 120, - /* expectedHeight= */ 519); - }); + it('should use mobile_banner_image_sidebyside on narrow slots on mobile', function() { + const runTest = (availableWidth, expectedWidth, expectedHeight) => { + expect(getAutoConfig(availableWidth, /* isMobile= */ true)).to.deep.equal( + { + layoutType: LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE, + numberOfColumns: 1, + numberOfRows: 12, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + } + ); + }; + runTest( + /* availableWidth= */ 467, + /* expectedWidth= */ 467, + /* expectedHeight= */ 1700 + ); + runTest( + /* availableWidth= */ 414, + /* expectedWidth= */ 414, + /* expectedHeight= */ 1520 + ); + runTest( + /* availableWidth= */ 360, + /* expectedWidth= */ 360, + /* expectedHeight= */ 1336 + ); + runTest( + /* availableWidth= */ 320, + /* expectedWidth= */ 320, + /* expectedHeight= */ 1200 + ); + runTest( + /* availableWidth= */ 300, + /* expectedWidth= */ 300, + /* expectedHeight= */ 1131 + ); + runTest( + /* availableWidth= */ 200, + /* expectedWidth= */ 200, + /* expectedHeight= */ 791 + ); + runTest( + /* availableWidth= */ 120, + /* expectedWidth= */ 120, + /* expectedHeight= */ 519 + ); + }); it('should use image_sidebyside on narrow slots on desktop', function() { const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getAutoConfig(availableWidth, /* isMobile= */ false)) - .to.deep.equal({ - layoutType: LayoutType.IMAGE_SIDEBYSIDE, - numberOfColumns: 1, - numberOfRows: 13, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); + expect( + getAutoConfig(availableWidth, /* isMobile= */ false) + ).to.deep.equal({ + layoutType: LayoutType.IMAGE_SIDEBYSIDE, + numberOfColumns: 1, + numberOfRows: 13, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + }); }; runTest( - /* availableWidth= */ 467, /* expectedWidth= */ 467, - /* expectedHeight= */ 1606); + /* availableWidth= */ 467, + /* expectedWidth= */ 467, + /* expectedHeight= */ 1606 + ); runTest( - /* availableWidth= */ 414, /* expectedWidth= */ 414, - /* expectedHeight= */ 1424); + /* availableWidth= */ 414, + /* expectedWidth= */ 414, + /* expectedHeight= */ 1424 + ); runTest( - /* availableWidth= */ 360, /* expectedWidth= */ 360, - /* expectedHeight= */ 1238); + /* availableWidth= */ 360, + /* expectedWidth= */ 360, + /* expectedHeight= */ 1238 + ); runTest( - /* availableWidth= */ 320, /* expectedWidth= */ 320, - /* expectedHeight= */ 1100); + /* availableWidth= */ 320, + /* expectedWidth= */ 320, + /* expectedHeight= */ 1100 + ); runTest( - /* availableWidth= */ 300, /* expectedWidth= */ 300, - /* expectedHeight= */ 1032); + /* availableWidth= */ 300, + /* expectedWidth= */ 300, + /* expectedHeight= */ 1032 + ); runTest( - /* availableWidth= */ 250, /* expectedWidth= */ 250, - /* expectedHeight= */ 860); + /* availableWidth= */ 250, + /* expectedWidth= */ 250, + /* expectedHeight= */ 860 + ); runTest( - /* availableWidth= */ 200, /* expectedWidth= */ 200, - /* expectedHeight= */ 688); + /* availableWidth= */ 200, + /* expectedWidth= */ 200, + /* expectedHeight= */ 688 + ); runTest( - /* availableWidth= */ 120, /* expectedWidth= */ 120, - /* expectedHeight= */ 412); + /* availableWidth= */ 120, + /* expectedWidth= */ 120, + /* expectedHeight= */ 412 + ); }); }); - describe('getPubControlConfig', function() { it('should use setting when only one provided', function() { const rawPubControlParams = { @@ -131,80 +172,112 @@ describe('getPubControlConfig', function() { layoutType: LayoutType.IMAGE_STACKED, }; const runTest = (availableWidth, expectedWidth, expectedHeight) => { - expect(getPubControlConfig(availableWidth, rawPubControlParams)) - .to.deep.equal({ - layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, - numberOfColumns: 4, - numberOfRows: 2, - slotWidth: expectedWidth, - slotHeight: expectedHeight, - }); + expect( + getPubControlConfig(availableWidth, rawPubControlParams) + ).to.deep.equal({ + layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, + numberOfColumns: 4, + numberOfRows: 2, + slotWidth: expectedWidth, + slotHeight: expectedHeight, + }); }; runTest( - /* availableWidth= */ 1300, /* expectedWidth= */ 1300, - /* expectedHeight= */ 513); + /* availableWidth= */ 1300, + /* expectedWidth= */ 1300, + /* expectedHeight= */ 513 + ); runTest( - /* availableWidth= */ 1200, /* expectedWidth= */ 1200, - /* expectedHeight= */ 487); + /* availableWidth= */ 1200, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 487 + ); runTest( - /* availableWidth= */ 800, /* expectedWidth= */ 800, - /* expectedHeight= */ 382); + /* availableWidth= */ 800, + /* expectedWidth= */ 800, + /* expectedHeight= */ 382 + ); runTest( - /* availableWidth= */ 500, /* expectedWidth= */ 500, - /* expectedHeight= */ 304); + /* availableWidth= */ 500, + /* expectedWidth= */ 500, + /* expectedHeight= */ 304 + ); runTest( - /* availableWidth= */ 400, /* expectedWidth= */ 400, - /* expectedHeight= */ 278); + /* availableWidth= */ 400, + /* expectedWidth= */ 400, + /* expectedHeight= */ 278 + ); }); - it('should use different settings for mobile and desktop when two provided', - function() { - const rawPubControlParams = { - numberOfColumns: '1,4', - numberOfRows: '3,2', - layoutType: - `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}`, - }; - const expectedDesktopConfig = { - layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, - numberOfColumns: 4, - numberOfRows: 2, - }; - const expectedMobileConfig = { - layoutType: LayoutType.PUB_CONTROL_IMAGE_SIDEBYSIDE, - numberOfColumns: 1, - numberOfRows: 3, - }; - const runTest = - (availableWidth, expectedWidth, expectedHeight, expectedConfig) => { - expectedConfig.slotWidth = expectedWidth; - expectedConfig.slotHeight = expectedHeight; - expect(getPubControlConfig(availableWidth, rawPubControlParams)) - .to.deep.equal(expectedConfig); - }; + it('should use different settings for mobile and desktop when two provided', function() { + const rawPubControlParams = { + numberOfColumns: '1,4', + numberOfRows: '3,2', + layoutType: `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}`, + }; + const expectedDesktopConfig = { + layoutType: LayoutType.PUB_CONTROL_IMAGE_STACKED, + numberOfColumns: 4, + numberOfRows: 2, + }; + const expectedMobileConfig = { + layoutType: LayoutType.PUB_CONTROL_IMAGE_SIDEBYSIDE, + numberOfColumns: 1, + numberOfRows: 3, + }; + const runTest = ( + availableWidth, + expectedWidth, + expectedHeight, + expectedConfig + ) => { + expectedConfig.slotWidth = expectedWidth; + expectedConfig.slotHeight = expectedHeight; + expect( + getPubControlConfig(availableWidth, rawPubControlParams) + ).to.deep.equal(expectedConfig); + }; - // Above 468px the logic should use desktop setting. - runTest( - /* availableWidth= */ 1300, /* expectedWidth= */ 1300, - /* expectedHeight= */ 513, expectedDesktopConfig); - runTest( - /* availableWidth= */ 1200, /* expectedWidth= */ 1200, - /* expectedHeight= */ 487, expectedDesktopConfig); - runTest( - /* availableWidth= */ 800, /* expectedWidth= */ 800, - /* expectedHeight= */ 382, expectedDesktopConfig); - runTest( - /* availableWidth= */ 500, /* expectedWidth= */ 500, - /* expectedHeight= */ 304, expectedDesktopConfig); + // Above 468px the logic should use desktop setting. + runTest( + /* availableWidth= */ 1300, + /* expectedWidth= */ 1300, + /* expectedHeight= */ 513, + expectedDesktopConfig + ); + runTest( + /* availableWidth= */ 1200, + /* expectedWidth= */ 1200, + /* expectedHeight= */ 487, + expectedDesktopConfig + ); + runTest( + /* availableWidth= */ 800, + /* expectedWidth= */ 800, + /* expectedHeight= */ 382, + expectedDesktopConfig + ); + runTest( + /* availableWidth= */ 500, + /* expectedWidth= */ 500, + /* expectedHeight= */ 304, + expectedDesktopConfig + ); - // Below 468px the logic should use mobile setting. - runTest( - /* availableWidth= */ 400, /* expectedWidth= */ 400, - /* expectedHeight= */ 333, expectedMobileConfig); - runTest( - /* availableWidth= */ 300, /* expectedWidth= */ 300, - /* expectedHeight= */ 255, expectedMobileConfig); - }); + // Below 468px the logic should use mobile setting. + runTest( + /* availableWidth= */ 400, + /* expectedWidth= */ 400, + /* expectedHeight= */ 333, + expectedMobileConfig + ); + runTest( + /* availableWidth= */ 300, + /* expectedWidth= */ 300, + /* expectedHeight= */ 255, + expectedMobileConfig + ); + }); it('should return different sizes for different layouts', function() { // sanity check that when publisher provides different layouts we use @@ -237,45 +310,53 @@ describe('getPubControlConfig', function() { }; // One of pub control params is missing. runTest( - {numberOfRows: '1', numberOfColumns: '1'}, - /Tags .* should be set together/); + {numberOfRows: '1', numberOfColumns: '1'}, + /Tags .* should be set together/ + ); runTest( - {numberOfColumns: '1', layoutType: 'foo'}, - /Tags .* should be set together/); + {numberOfColumns: '1', layoutType: 'foo'}, + /Tags .* should be set together/ + ); runTest( - {numberOfRows: '1', layoutType: 'foo'}, - /Tags .* should be set together/); + {numberOfRows: '1', layoutType: 'foo'}, + /Tags .* should be set together/ + ); // Length of parameters doesn't match runTest( - {numberOfRows: '1', numberOfColumns: '1', layoutType: 'foo,bar'}, - /Lengths of parameters .* must match/); + {numberOfRows: '1', numberOfColumns: '1', layoutType: 'foo,bar'}, + /Lengths of parameters .* must match/ + ); runTest( - {numberOfRows: '1', numberOfColumns: '1,2', layoutType: 'foo'}, - /Lengths of parameters .* must match/); + {numberOfRows: '1', numberOfColumns: '1,2', layoutType: 'foo'}, + /Lengths of parameters .* must match/ + ); runTest( - {numberOfRows: '1,2', numberOfColumns: '1', layoutType: 'foo'}, - /Lengths of parameters .* must match/); + {numberOfRows: '1,2', numberOfColumns: '1', layoutType: 'foo'}, + /Lengths of parameters .* must match/ + ); // Length is more than 2. runTest( - { - numberOfRows: '1,2,3', - numberOfColumns: '1,2,3', - layoutType: 'foo,bar,baz', - }, - /At most 2 parameters for each attribute are needed/); + { + numberOfRows: '1,2,3', + numberOfColumns: '1,2,3', + layoutType: 'foo,bar,baz', + }, + /At most 2 parameters for each attribute are needed/ + ); // Passed non-number for rows/columns. runTest( - {numberOfRows: 'foo', numberOfColumns: '1', layoutType: 'foo'}, - /Wrong value 'foo' for/); + {numberOfRows: 'foo', numberOfColumns: '1', layoutType: 'foo'}, + /Wrong value 'foo' for/ + ); runTest( - {numberOfRows: '1', numberOfColumns: 'foo', layoutType: 'foo'}, - /Wrong value 'foo' for/); + {numberOfRows: '1', numberOfColumns: 'foo', layoutType: 'foo'}, + /Wrong value 'foo' for/ + ); }); - it('limits number of columns if publisher chose too many', function() { const rawPubControlParams = { numberOfColumns: '5', // want 5 columns. diff --git a/ads/google/a4a/shared/test/test-url-builder.js b/ads/google/a4a/shared/test/test-url-builder.js index 1904f5e240624..2c99a73007918 100644 --- a/ads/google/a4a/shared/test/test-url-builder.js +++ b/ads/google/a4a/shared/test/test-url-builder.js @@ -18,7 +18,8 @@ import {buildUrl} from '../url-builder'; describe('buildUrl', () => { it('should build a simple URL', () => { - expect(buildUrl('https://example.com', {'key': 'value'}, Infinity)) - .to.equal('https://example.com?key=value'); + expect( + buildUrl('https://example.com', {'key': 'value'}, Infinity) + ).to.equal('https://example.com?key=value'); }); }); diff --git a/ads/google/a4a/shared/url-builder.js b/ads/google/a4a/shared/url-builder.js index e60a04503f352..95a8e7daef356 100644 --- a/ads/google/a4a/shared/url-builder.js +++ b/ads/google/a4a/shared/url-builder.js @@ -29,15 +29,22 @@ export let QueryParameterDef; * @return {string} the fully constructed URL. */ export function buildUrl( - baseUrl, queryParams, maxLength, opt_truncationQueryParam) { + baseUrl, + queryParams, + maxLength, + opt_truncationQueryParam +) { const encodedParams = []; const encodedTruncationParam = - opt_truncationQueryParam && - !(opt_truncationQueryParam.value == null || - opt_truncationQueryParam.value === '') ? - encodeURIComponent(opt_truncationQueryParam.name) + '=' + - encodeURIComponent(String(opt_truncationQueryParam.value)) : - null; + opt_truncationQueryParam && + !( + opt_truncationQueryParam.value == null || + opt_truncationQueryParam.value === '' + ) + ? encodeURIComponent(opt_truncationQueryParam.name) + + '=' + + encodeURIComponent(String(opt_truncationQueryParam.value)) + : null; let capacity = maxLength - baseUrl.length; if (encodedTruncationParam) { capacity -= encodedTruncationParam.length + 1; @@ -54,9 +61,9 @@ export function buildUrl( const fullLength = encodedNameAndSep.length + encodedValue.length + 1; if (fullLength > capacity) { const truncatedValue = encodedValue - .substr(0, capacity - encodedNameAndSep.length - 1) - // Don't end with a partially truncated escape sequence - .replace(/%\w?$/, ''); + .substr(0, capacity - encodedNameAndSep.length - 1) + // Don't end with a partially truncated escape sequence + .replace(/%\w?$/, ''); if (truncatedValue) { encodedParams.push(encodedNameAndSep + truncatedValue); } diff --git a/ads/google/a4a/test/test-line-delimited-response-handler.js b/ads/google/a4a/test/test-line-delimited-response-handler.js index 2742274b5eafc..5f89c6356eb21 100644 --- a/ads/google/a4a/test/test-line-delimited-response-handler.js +++ b/ads/google/a4a/test/test-line-delimited-response-handler.js @@ -20,7 +20,6 @@ import { } from '../line-delimited-response-handler'; describe('#line-delimited-response-handler', () => { - let chunkHandlerStub; let slotData; let sandbox; @@ -34,8 +33,10 @@ describe('#line-delimited-response-handler', () => { let slotDataString = ''; slotData.forEach(slot => { // TODO: escape creative returns - const creative = slot.creative.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\n').replace(/\r/g, '\\r'); + const creative = slot.creative + .replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r'); slotDataString += `${JSON.stringify(slot.headers)}\n${creative}\n`; }); return slotDataString; @@ -45,7 +46,7 @@ describe('#line-delimited-response-handler', () => { // Streamed response calls chunk handlers after returning so need to // wait on chunks. let chunkResolver; - const chunkPromise = new Promise(resolver => chunkResolver = resolver); + const chunkPromise = new Promise(resolver => (chunkResolver = resolver)); const chunkHandlerWrapper = (creative, metaData) => { chunkHandlerStub(creative, metaData); if (chunkHandlerStub.callCount == slotData.length) { @@ -58,27 +59,35 @@ describe('#line-delimited-response-handler', () => { chunkResolver(); } lineDelimitedStreamer( - win, response, metaJsonCreativeGrouper(chunkHandlerWrapper)); + win, + response, + metaJsonCreativeGrouper(chunkHandlerWrapper) + ); return chunkPromise.then(() => { expect(chunkHandlerStub.callCount).to.equal(slotData.length); // Could have duplicate responses so need to iterate and get counts. // TODO: can't use objects as keys :( const calls = {}; slotData.forEach(slot => { - const normalizedHeaderNames = - Object.keys(slot.headers).map(s => [s.toLowerCase(), s]); + const normalizedHeaderNames = Object.keys(slot.headers).map(s => [ + s.toLowerCase(), + s, + ]); slot.normalizedHeaders = {}; normalizedHeaderNames.forEach( - namePair => - slot.normalizedHeaders[namePair[0]] = slot.headers[namePair[1]]); + namePair => + (slot.normalizedHeaders[namePair[0]] = slot.headers[namePair[1]]) + ); const key = slot.creative + JSON.stringify(slot.normalizedHeaders); calls[key] ? calls[key]++ : (calls[key] = 1); }); slotData.forEach(slot => { - expect(chunkHandlerStub.withArgs( - slot.creative, slot.normalizedHeaders).callCount) - .to.equal(calls[slot.creative + - JSON.stringify(slot.normalizedHeaders)]); + expect( + chunkHandlerStub.withArgs(slot.creative, slot.normalizedHeaders) + .callCount + ).to.equal( + calls[slot.creative + JSON.stringify(slot.normalizedHeaders)] + ); }); }); } @@ -99,7 +108,9 @@ describe('#line-delimited-response-handler', () => { }; win = { // TextDecoder should exist but not be called - TextDecoder: () => { throw new Error('fail'); }, + TextDecoder: () => { + throw new Error('fail'); + }, }; }); @@ -119,8 +130,10 @@ describe('#line-delimited-response-handler', () => { it('should fallback to text if no TextDecoder', () => { slotData = [ {headers: {foo: 'bar', hello: 'world'}, creative: 'baz\n'}, - {headers: {hello: 'world'}, - creative: '\r\n\nchunk\b me\r\b\n\r'}, + { + headers: {hello: 'world'}, + creative: '\r\n\nchunk\b me\r\b\n\r', + }, {headers: {foo: 'bar'}, creative: ''}, {headers: {}, creative: '\r\n\r\b\n\r'}, ]; @@ -138,8 +151,10 @@ describe('#line-delimited-response-handler', () => { const CHUNK_SIZE = 5; let chunk = 0; do { - const value = textEncoder.encode(responseString.substr( - chunk * CHUNK_SIZE, CHUNK_SIZE), {'stream': true}); + const value = textEncoder.encode( + responseString.substr(chunk * CHUNK_SIZE, CHUNK_SIZE), + {'stream': true} + ); const done = chunk * CHUNK_SIZE >= responseString.length - 1; readStub.onCall(chunk).returns(Promise.resolve({value, done})); } while (chunk++ * CHUNK_SIZE < responseString.length); @@ -168,12 +183,14 @@ describe('#line-delimited-response-handler', () => { }); // TODO(lannka, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should handle empty streamed ' + - 'response properly', () => { - slotData = []; - setup(); - return executeAndVerifyResponse(); - }); + it.configure().skipSafari( + 'should handle empty streamed ' + 'response properly', + () => { + slotData = []; + setup(); + return executeAndVerifyResponse(); + } + ); // TODO(lannka, #15748): Fails on Safari 11.1.0. it.configure().skipSafari('should handle no fill response properly', () => { @@ -183,31 +200,40 @@ describe('#line-delimited-response-handler', () => { }); // TODO(lannka, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should handle multiple no fill responses ' + - 'properly', () => { - slotData = [ - {headers: {}, creative: ''}, - {headers: {}, creative: ''}, - ]; - setup(); - return executeAndVerifyResponse(); - }); + it.configure().skipSafari( + 'should handle multiple no fill responses ' + 'properly', + () => { + slotData = [{headers: {}, creative: ''}, {headers: {}, creative: ''}]; + setup(); + return executeAndVerifyResponse(); + } + ); // TODO(lannka, #15748): Fails on Safari 11.1.0. it.configure().skipSafari('should stream properly', () => { slotData = [ {headers: {}, creative: ''}, - {headers: {foo: 'bar', hello: 'world'}, - creative: '\t\n\r\bbaz\r\n\n'}, - {headers: {Foo: 'bar', hello: 'world'}, - creative: '\t\n\r\bbaz\r\n\n'}, + { + headers: {foo: 'bar', hello: 'world'}, + creative: '\t\n\r\bbaz\r\n\n', + }, + { + headers: {Foo: 'bar', hello: 'world'}, + creative: '\t\n\r\bbaz\r\n\n', + }, {headers: {}, creative: ''}, - {headers: {Foo: 'bar', HELLO: 'Le Monde'}, - creative: '\t\n\r\bbaz\r\n\n'}, - {headers: {FOO: 'bar', Hello: 'Le Monde'}, - creative: '\t\n\r\bbaz\r\n\n'}, - {headers: {hello: 'world'}, - creative: '\nchu\nnk me'}, + { + headers: {Foo: 'bar', HELLO: 'Le Monde'}, + creative: '\t\n\r\bbaz\r\n\n', + }, + { + headers: {FOO: 'bar', Hello: 'Le Monde'}, + creative: '\t\n\r\bbaz\r\n\n', + }, + { + headers: {hello: 'world'}, + creative: '\nchu\nnk me', + }, {headers: {}, creative: ''}, ]; setup(); diff --git a/ads/google/a4a/test/test-traffic-experiments.js b/ads/google/a4a/test/test-traffic-experiments.js index 416e4b6714838..3c09a1ffdbe4f 100644 --- a/ads/google/a4a/test/test-traffic-experiments.js +++ b/ads/google/a4a/test/test-traffic-experiments.js @@ -72,7 +72,8 @@ describe('all-traffic-experiments-tests', () => { element.setAttribute(EXPERIMENT_ATTRIBUTE, '99,77,11,0122345'); addExperimentIdToElement('3', element); expect(element.getAttribute(EXPERIMENT_ATTRIBUTE)).to.equal( - '99,77,11,0122345,3'); + '99,77,11,0122345,3' + ); }); it('should should replace existing invalid experiments', () => { diff --git a/ads/google/a4a/test/test-utils.js b/ads/google/a4a/test/test-utils.js index 624ddd8d11566..50b9e71a9becb 100644 --- a/ads/google/a4a/test/test-utils.js +++ b/ads/google/a4a/test/test-utils.js @@ -38,17 +38,13 @@ import { mergeExperimentIds, } from '../utils'; import {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; -import { - MockA4AImpl, -} from '../../../../extensions/amp-a4a/0.1/test/utils'; +import {MockA4AImpl} from '../../../../extensions/amp-a4a/0.1/test/utils'; import {Services} from '../../../../src/services'; import {buildUrl} from '../shared/url-builder'; import {createElementWithAttributes} from '../../../../src/dom'; import {createIframePromise} from '../../../../testing/iframe'; import {installDocService} from '../../../../src/service/ampdoc-impl'; -import { - installExtensionsService, -} from '../../../../src/service/extensions-impl'; +import {installExtensionsService} from '../../../../src/service/extensions-impl'; import {installXhrService} from '../../../../src/service/xhr-impl'; import {toggleExperiment} from '../../../../src/experiments'; @@ -68,9 +64,19 @@ function setupForAdTesting(fixture) { // Because of the way the element is constructed, it doesn't have all of the // machinery that AMP expects it to have, so just no-op the irrelevant // functions. -function noopMethods(impl, doc, sandbox, pageLayoutBox = { - top: 11, left: 12, right: 0, bottom: 0, width: 0, height: 0, -}) { +function noopMethods( + impl, + doc, + sandbox, + pageLayoutBox = { + top: 11, + left: 12, + right: 0, + bottom: 0, + width: 0, + height: 0, + } +) { const noop = () => {}; impl.element.build = noop; impl.element.getPlaceholder = noop; @@ -80,7 +86,6 @@ function noopMethods(impl, doc, sandbox, pageLayoutBox = { } describe('Google A4A utils', () => { - //TODO: Add tests for other utils functions. describe('#additionalDimensions', () => { @@ -102,7 +107,8 @@ describe('Google A4A utils', () => { height: '101px', }; return expect(additionalDimensions(fakeWin, fakeSize)).to.equal( - '3,4,1,2,11,12,5,6,100px,101px'); + '3,4,1,2,11,12,5,6,100px,101px' + ); }); }); @@ -167,10 +173,12 @@ describe('Google A4A utils', () => { }); const a4a = new MockA4AImpl(element); url = 'not an array'; - allowConsoleError(() => - expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok); - allowConsoleError(() => - expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null); + allowConsoleError( + () => expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok + ); + allowConsoleError( + () => expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null + ); url = []; expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok; expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null; @@ -203,15 +211,21 @@ describe('Google A4A utils', () => { }; const qqid = 'qqid_string'; let newConfig = addCsiSignalsToAmpAnalyticsConfig( - window, mockElement, builtConfig, qqid, - /* isVerifiedAmpCreative */ true); + window, + mockElement, + builtConfig, + qqid, + /* isVerifiedAmpCreative */ true + ); expect(newConfig.requests.iniLoadCsi).to.not.be.null; expect(newConfig.requests.renderStartCsi).to.not.be.null; - expect(newConfig.triggers.continuousVisibleIniLoad.request) - .to.equal('iniLoadCsi'); - expect(newConfig.triggers.continuousVisibleRenderStart.request) - .to.equal('renderStartCsi'); + expect(newConfig.triggers.continuousVisibleIniLoad.request).to.equal( + 'iniLoadCsi' + ); + expect(newConfig.triggers.continuousVisibleRenderStart.request).to.equal( + 'renderStartCsi' + ); const getRegExps = metricName => [ /^https:\/\/csi\.gstatic\.com\/csi\?/, /(\?|&)s=a4a(&|$)/, @@ -234,48 +248,62 @@ describe('Google A4A utils', () => { expect(newConfig.requests.renderStartCsi).to.match(regExp); }); newConfig = addCsiSignalsToAmpAnalyticsConfig( - window, mockElement, builtConfig, qqid, - /* isVerifiedAmpCreative */ false, - /* lifecycle time events; not relevant here */ -1, -1); + window, + mockElement, + builtConfig, + qqid, + /* isVerifiedAmpCreative */ false, + /* lifecycle time events; not relevant here */ -1, + -1 + ); getRegExps('iniLoadCsiCrossDomain').forEach(regExp => { expect(newConfig.requests.iniLoadCsi).to.match(regExp); }); getRegExps('renderStartCsiCrossDomain').forEach(regExp => { expect(newConfig.requests.renderStartCsi).to.match(regExp); }); - }); }); describe('#getAmpRuntimeTypeParameter', () => { it('should specify that this is canary', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'canary'}, - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.equal('2'); + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'canary'}, + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.equal('2'); }); it('should specify that this is control', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'control'}, - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.equal('1'); + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'control'}, + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.equal('1'); }); it('should not have `art` parameter when AMP_CONFIG is undefined', () => { - expect(getAmpRuntimeTypeParameter({ - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.be.null; + expect( + getAmpRuntimeTypeParameter({ + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.be.null; }); it('should not have `art` parameter when binary type is production', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'production'}, - location: {origin: 'https://www-example-com.cdn.ampproject.org'}, - })).to.be.null; + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'production'}, + location: {origin: 'https://www-example-com.cdn.ampproject.org'}, + }) + ).to.be.null; }); it('should not have `art` parameter when canonical', () => { - expect(getAmpRuntimeTypeParameter({ - AMP_CONFIG: {type: 'canary'}, - location: {origin: 'https://www.example.com'}, - })).to.be.null; + expect( + getAmpRuntimeTypeParameter({ + AMP_CONFIG: {type: 'canary'}, + location: {origin: 'https://www.example.com'}, + }) + ).to.be.null; }); }); @@ -325,8 +353,12 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - const getRect = () => { return {'width': 100, 'height': 200}; }; - const getSize = () => { return {'width': 100, 'height': 200}; }; + const getRect = () => { + return {'width': 100, 'height': 200}; + }; + const getSize = () => { + return {'width': 100, 'height': 200}; + }; const getScrollLeft = () => 12; const getScrollTop = () => 34; const viewportStub = sandbox.stub(Services, 'viewportForDoc'); @@ -418,8 +450,10 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: { supports: () => true, @@ -427,7 +461,8 @@ describe('Google A4A utils', () => { }); return fixture.addElement(elem).then(() => { return expect(googleAdUrl(impl, '', 0, {}, [])).to.eventually.match( - /[&?]bc=7[&$]/); + /[&?]bc=7[&$]/ + ); }); }); }); @@ -444,14 +479,17 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: {}, }); return fixture.addElement(elem).then(() => { return expect(googleAdUrl(impl, '', 0, {}, [])).to.eventually.match( - /[&?]bc=1[&$]/); + /[&?]bc=1[&$]/ + ); }); }); }); @@ -469,8 +507,10 @@ describe('Google A4A utils', () => { const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); impl.win.SVGElement = undefined; - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: { supports: () => false, @@ -478,8 +518,8 @@ describe('Google A4A utils', () => { }); return fixture.addElement(elem).then(() => { return expect( - googleAdUrl(impl, '', 0, {}, [])).to.eventually.not.match( - /[&?]bc=1[&$]/); + googleAdUrl(impl, '', 0, {}, []) + ).to.eventually.not.match(/[&?]bc=1[&$]/); }); }); }); @@ -496,10 +536,13 @@ describe('Google A4A utils', () => { }); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox); - sandbox.stub(Services.viewerForDoc(impl.getAmpDoc()), 'getReferrerUrl') - .returns(new Promise(() => {})); - const createElementStub = - sandbox.stub(impl.win.document, 'createElement'); + sandbox + .stub(Services.viewerForDoc(impl.getAmpDoc()), 'getReferrerUrl') + .returns(new Promise(() => {})); + const createElementStub = sandbox.stub( + impl.win.document, + 'createElement' + ); createElementStub.withArgs('iframe').returns({ sandbox: { supports: () => false, @@ -508,8 +551,8 @@ describe('Google A4A utils', () => { expectAsyncConsoleError(/Referrer timeout/, 1); return fixture.addElement(elem).then(() => { return expect( - googleAdUrl(impl, '', 0, {}, [])).to.eventually.not.match( - /[&?]ref=[&$]/); + googleAdUrl(impl, '', 0, {}, []) + ).to.eventually.not.match(/[&?]ref=[&$]/); }); }); }); @@ -538,19 +581,27 @@ describe('Google A4A utils', () => { const elem = createElementWithAttributes(doc, 'amp-a4a', {}); const impl = new MockA4AImpl(elem); noopMethods(impl, doc, sandbox, { - top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, + top: 0, + left: 0, + right: 0, + bottom: 0, + width: 0, + height: 0, }); return fixture.addElement(elem).then(() => googleAdUrl(impl, '', Date.now(), [], []).then(url => { expect(url).to.match(/[&?]adx=0[&$]/); expect(url).to.match(/[&?]adx=0[&$]/); elem.setAttribute( - 'data-experiment-id', `123,${ADX_ADY_EXP.experiment},789`,); + 'data-experiment-id', + `123,${ADX_ADY_EXP.experiment},789` + ); return googleAdUrl(impl, '', Date.now(), [], []).then(url => { expect(url).to.match(/[&?]adx=1[&$]/); expect(url).to.match(/[&?]adx=1[&$]/); }); - })); + }) + ); }); }); }); @@ -560,12 +611,14 @@ describe('Google A4A utils', () => { expect(mergeExperimentIds(['12345'])).to.equal('12345'); }); it('should merge a single ID to a list', () => { - expect(mergeExperimentIds(['12345'], '3,4,5,6')) - .to.equal('3,4,5,6,12345'); + expect(mergeExperimentIds(['12345'], '3,4,5,6')).to.equal( + '3,4,5,6,12345' + ); }); it('should merge multiple IDs into a list', () => { - expect(mergeExperimentIds(['12345','6789'], '3,4,5,6')) - .to.equal('3,4,5,6,12345,6789'); + expect(mergeExperimentIds(['12345', '6789'], '3,4,5,6')).to.equal( + '3,4,5,6,12345,6789' + ); }); it('should discard invalid ID', () => { expect(mergeExperimentIds(['frob'], '3,4,5,6')).to.equal('3,4,5,6'); @@ -585,7 +638,11 @@ describe('Google A4A utils', () => { }); it('should not append parameter if truncated', () => { const truncUrl = buildUrl( - 'https://foo.com/bar', {hello: 'world'}, 15, TRUNCATION_PARAM); + 'https://foo.com/bar', + {hello: 'world'}, + 15, + TRUNCATION_PARAM + ); expect(truncUrl.indexOf(TRUNCATION_PARAM.name)).to.not.equal(-1); expect(maybeAppendErrorParameter(truncUrl, 'n')).to.not.be.ok; }); @@ -593,8 +650,10 @@ describe('Google A4A utils', () => { describes.realWin('#getEnclosingContainerTypes', {}, env => { it('should return empty if no containers', () => { - expect(getEnclosingContainerTypes( - env.win.document.createElement('amp-ad')).length).to.equal(0); + expect( + getEnclosingContainerTypes(env.win.document.createElement('amp-ad')) + .length + ).to.equal(0); }); Object.keys(ValidAdContainerTypes).forEach(container => { @@ -603,8 +662,9 @@ describe('Google A4A utils', () => { env.win.document.body.appendChild(containerElem); const ampAdElem = env.win.document.createElement('amp-ad'); containerElem.appendChild(ampAdElem); - expect(getEnclosingContainerTypes(ampAdElem)) - .to.deep.equal([ValidAdContainerTypes[container]]); + expect(getEnclosingContainerTypes(ampAdElem)).to.deep.equal([ + ValidAdContainerTypes[container], + ]); }); }); @@ -617,12 +677,14 @@ describe('Google A4A utils', () => { }); const ampAdElem = env.win.document.createElement('amp-ad'); prevContainer.appendChild(ampAdElem); - const ValidAdContainerTypeValues = - Object.keys(ValidAdContainerTypes).map(function(key) { - return ValidAdContainerTypes[key]; - }); - expect(getEnclosingContainerTypes(ampAdElem).sort()) - .to.deep.equal(ValidAdContainerTypeValues.sort()); + const ValidAdContainerTypeValues = Object.keys(ValidAdContainerTypes).map( + function(key) { + return ValidAdContainerTypes[key]; + } + ); + expect(getEnclosingContainerTypes(ampAdElem).sort()).to.deep.equal( + ValidAdContainerTypeValues.sort() + ); }); }); @@ -633,44 +695,49 @@ describe('Google A4A utils', () => { const documentInfoStub = sandbox.stub(Services, 'documentInfoForDoc'); doc = {}; fakeWin = {location: {}}; - documentInfoStub.withArgs(doc) - .returns({canonicalUrl: 'http://f.blah.com?some_site'}); + documentInfoStub + .withArgs(doc) + .returns({canonicalUrl: 'http://f.blah.com?some_site'}); }); it('should use google.com if at top', () => { fakeWin.top = fakeWin; fakeWin.location.ancestorOrigins = ['foo.google.com.eu']; expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.com/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.com/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use google.com if no ancestorOrigins', () => { expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.com/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.com/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use google.com if non-google top', () => { fakeWin.location.ancestorOrigins = ['foo.google2.com']; expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.com/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.com/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use google ancestor origin based top domain', () => { - fakeWin.location.ancestorOrigins = - ['foo.google.eu', 'blah.google.fr']; + fakeWin.location.ancestorOrigins = ['foo.google.eu', 'blah.google.fr']; expect(getIdentityTokenRequestUrl(fakeWin, doc)).to.equal( - 'https://adservice.google.fr/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.fr/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); it('should use supplied domain', () => { fakeWin.location.ancestorOrigins = ['foo.google.fr']; expect(getIdentityTokenRequestUrl(fakeWin, doc, '.google.eu')).to.equal( - 'https://adservice.google.eu/adsid/integrator.json?' + - 'domain=f.blah.com'); + 'https://adservice.google.eu/adsid/integrator.json?' + + 'domain=f.blah.com' + ); }); }); @@ -678,8 +745,9 @@ describe('Google A4A utils', () => { beforeEach(() => { installXhrService(env.win); const documentInfoStub = sandbox.stub(Services, 'documentInfoForDoc'); - documentInfoStub.withArgs(env.ampdoc) - .returns({canonicalUrl: 'http://f.blah.com?some_site'}); + documentInfoStub + .withArgs(env.ampdoc) + .returns({canonicalUrl: 'http://f.blah.com?some_site'}); }); afterEach(() => { @@ -688,9 +756,11 @@ describe('Google A4A utils', () => { }); const getUrl = domain => { - domain = domain || 'google\.com'; - return `https:\/\/adservice\.${domain}\/adsid\/integrator\.json\?` + - 'domain=f\.blah\.com'; + domain = domain || 'google.com'; + return ( + `https:\/\/adservice\.${domain}\/adsid\/integrator\.json\?` + + 'domain=f.blah.com' + ); }; it('should ignore response if required fields are missing', () => { @@ -706,13 +776,16 @@ describe('Google A4A utils', () => { }); it('should fetch full token as expected', () => { - env.expectFetch(getUrl(), JSON.stringify({ - newToken: 'abc', - '1p_jar': 'some_jar', - pucrd: 'some_pucrd', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - })); + env.expectFetch( + getUrl(), + JSON.stringify({ + newToken: 'abc', + '1p_jar': 'some_jar', + pucrd: 'some_pucrd', + freshLifetimeSecs: '1234', + validLifetimeSecs: '5678', + }) + ); return getIdentityToken(env.win, env.ampdoc).then(result => { expect(result.token).to.equal('abc'); expect(result.jar).to.equal('some_jar'); @@ -725,11 +798,14 @@ describe('Google A4A utils', () => { it('should redirect as expected', () => { env.expectFetch(getUrl(), JSON.stringify({altDomain: '.google.fr'})); - env.expectFetch(getUrl('google\.fr'), JSON.stringify({ - newToken: 'abc', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - })); + env.expectFetch( + getUrl('google.fr'), + JSON.stringify({ + newToken: 'abc', + freshLifetimeSecs: '1234', + validLifetimeSecs: '5678', + }) + ); return getIdentityToken(env.win, env.ampdoc, '').then(result => { expect(result.token).to.equal('abc'); expect(result.jar).to.equal(''); @@ -743,7 +819,9 @@ describe('Google A4A utils', () => { it('should stop after 1 redirect', () => { env.expectFetch(getUrl(), JSON.stringify({altDomain: '.google.fr'})); env.expectFetch( - getUrl('google\.fr'), JSON.stringify({altDomain: '.google.com'})); + getUrl('google.fr'), + JSON.stringify({altDomain: '.google.com'}) + ); return getIdentityToken(env.win, env.ampdoc).then(result => { expect(result.token).to.not.be.ok; expect(result.jar).to.not.be.ok; @@ -759,49 +837,61 @@ describe('Google A4A utils', () => { validLifetimeSecs: '5678', }; env.win['goog_identity_prom'] = Promise.resolve(ident); - return getIdentityToken(env.win, env.ampdoc) - .then(result => expect(result).to.jsonEqual(ident)); + return getIdentityToken(env.win, env.ampdoc).then(result => + expect(result).to.jsonEqual(ident) + ); }); it('should handle fetch error', () => { - sandbox.stub(Services, 'xhrFor').returns( - {fetchJson: () => Promise.reject('some network failure')}); - return getIdentityToken(env.win, env.ampdoc) - .then(result => expect(result).to.jsonEqual({})); + sandbox + .stub(Services, 'xhrFor') + .returns({fetchJson: () => Promise.reject('some network failure')}); + return getIdentityToken(env.win, env.ampdoc).then(result => + expect(result).to.jsonEqual({}) + ); }); it('should fetch if SUFFICIENT consent', () => { - env.expectFetch(getUrl(), JSON.stringify({ - newToken: 'abc', - '1p_jar': 'some_jar', - pucrd: 'some_pucrd', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - })); + env.expectFetch( + getUrl(), + JSON.stringify({ + newToken: 'abc', + '1p_jar': 'some_jar', + pucrd: 'some_pucrd', + freshLifetimeSecs: '1234', + validLifetimeSecs: '5678', + }) + ); sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.SUFFICIENT, - })); - return getIdentityToken(env.win, env.ampdoc, 'default').then( - result => expect(result.token).to.equal('abc')); + Promise.resolve({ + whenPolicyResolved: () => CONSENT_POLICY_STATE.SUFFICIENT, + }) + ); + return getIdentityToken(env.win, env.ampdoc, 'default').then(result => + expect(result.token).to.equal('abc') + ); }); it('should not fetch if INSUFFICIENT consent', () => { sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.INSUFFICIENT, - })); - return expect(getIdentityToken(env.win, env.ampdoc, 'default')) - .to.eventually.jsonEqual({}); + Promise.resolve({ + whenPolicyResolved: () => CONSENT_POLICY_STATE.INSUFFICIENT, + }) + ); + return expect( + getIdentityToken(env.win, env.ampdoc, 'default') + ).to.eventually.jsonEqual({}); }); it('should not fetch if UNKNOWN consent', () => { sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.UNKNOWN, - })); - return expect(getIdentityToken(env.win, env.ampdoc, 'default')) - .to.eventually.jsonEqual({}); + Promise.resolve({ + whenPolicyResolved: () => CONSENT_POLICY_STATE.UNKNOWN, + }) + ); + return expect( + getIdentityToken(env.win, env.ampdoc, 'default') + ).to.eventually.jsonEqual({}); }); }); @@ -851,8 +941,7 @@ describe('Google A4A utils', () => { it('should include scheduleTime for ad render start triggers', () => { a4a.element.layoutScheduleTime = 200; - const vars = getCsiAmpAnalyticsVariables( - 'ad-render-start', a4a, null); + const vars = getCsiAmpAnalyticsVariables('ad-render-start', a4a, null); expect(vars['scheduleTime']).to.be.a('number'); expect(vars['scheduleTime']).not.to.equal(0); }); @@ -892,7 +981,8 @@ describe('Google A4A utils', () => { {in: 'hello.com', out: 'hello.com'}, {in: '', out: ''}, ].forEach(test => - it(test.in, () => expect(extractHost(test.in)).to.equal(test.out))); + it(test.in, () => expect(extractHost(test.in)).to.equal(test.out)) + ); }); describes.realWin('#getCorrelator', {}, env => { @@ -946,17 +1036,21 @@ describes.realWin('#groupAmpAdsByType', {amp: true}, env => { } it('should find amp-ad of only given type', () => { - const resources = [createResource({type: 'doubleclick'}), - createResource({type: 'blah'}), createResource({}, 'amp-foo')]; - sandbox.stub(Services.resourcesForDoc(doc), 'getMeasuredResources') - .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); + const resources = [ + createResource({type: 'doubleclick'}), + createResource({type: 'blah'}), + createResource({}, 'amp-foo'), + ]; + sandbox + .stub(Services.resourcesForDoc(doc), 'getMeasuredResources') + .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); return groupAmpAdsByType(win, 'doubleclick', () => 'foo').then(result => { expect(Object.keys(result).length).to.equal(1); expect(result['foo']).to.be.ok; expect(result['foo'].length).to.equal(1); return result['foo'][0].then(baseElement => - expect(baseElement.element.getAttribute('type')) - .to.equal('doubleclick')); + expect(baseElement.element.getAttribute('type')).to.equal('doubleclick') + ); }); }); @@ -967,49 +1061,63 @@ describes.realWin('#groupAmpAdsByType', {amp: true}, env => { // as its owned by amp-sticky-ad. It will locate associated element // and block on whenUpgradedToCustomElement so override createdCallback // to cause it to return immediately. - const ampAdResource = - createResource({type: 'doubleclick'}, 'amp-ad', stickyResource.element); + const ampAdResource = createResource( + {type: 'doubleclick'}, + 'amp-ad', + stickyResource.element + ); ampAdResource.element.createdCallback = true; - sandbox.stub(Services.resourcesForDoc(doc), 'getMeasuredResources') - .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); - return groupAmpAdsByType(win, 'doubleclick', () => 'foo').then( - result => { - expect(Object.keys(result).length).to.equal(1); - expect(result['foo']).to.be.ok; - expect(result['foo'].length).to.equal(1); - return result['foo'][0].then(baseElement => - expect(baseElement.element.getAttribute('type')) - .to.equal('doubleclick')); - }); + sandbox + .stub(Services.resourcesForDoc(doc), 'getMeasuredResources') + .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); + return groupAmpAdsByType(win, 'doubleclick', () => 'foo').then(result => { + expect(Object.keys(result).length).to.equal(1); + expect(result['foo']).to.be.ok; + expect(result['foo'].length).to.equal(1); + return result['foo'][0].then(baseElement => + expect(baseElement.element.getAttribute('type')).to.equal('doubleclick') + ); + }); }); it('should find and group multiple, some in containers', () => { const stickyResource = createResource({}, 'amp-sticky-ad'); - const resources = [stickyResource, createResource({}, 'amp-foo'), + const resources = [ + stickyResource, + createResource({}, 'amp-foo'), createResource({type: 'doubleclick', foo: 'bar'}), - createResource({type: 'doubleclick', foo: 'hello'})]; + createResource({type: 'doubleclick', foo: 'hello'}), + ]; // Do not expect ampAdResource to be returned by getMeasuredResources // as its owned by amp-sticky-ad. It will locate associated element // and block on whenUpgradedToCustomElement so override createdCallback // to cause it to return immediately. - const ampAdResource = createResource({type: 'doubleclick', foo: 'bar'}, - 'amp-ad', stickyResource.element); + const ampAdResource = createResource( + {type: 'doubleclick', foo: 'bar'}, + 'amp-ad', + stickyResource.element + ); ampAdResource.element.createdCallback = true; - sandbox.stub(Services.resourcesForDoc(doc), 'getMeasuredResources') - .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); - return groupAmpAdsByType( - win, 'doubleclick', element => element.getAttribute('foo')).then( - result => { - expect(Object.keys(result).length).to.equal(2); - expect(result['bar']).to.be.ok; - expect(result['bar'].length).to.equal(2); - expect(result['hello']).to.be.ok; - expect(result['hello'].length).to.equal(1); - return Promise.all(result['bar'].concat(result['hello'])).then( - baseElements => baseElements.forEach(baseElement => - expect(baseElement.element.getAttribute('type')) - .to.equal('doubleclick'))); - }); + sandbox + .stub(Services.resourcesForDoc(doc), 'getMeasuredResources') + .callsFake((doc, fn) => Promise.resolve(resources.filter(fn))); + return groupAmpAdsByType(win, 'doubleclick', element => + element.getAttribute('foo') + ).then(result => { + expect(Object.keys(result).length).to.equal(2); + expect(result['bar']).to.be.ok; + expect(result['bar'].length).to.equal(2); + expect(result['hello']).to.be.ok; + expect(result['hello'].length).to.equal(1); + return Promise.all(result['bar'].concat(result['hello'])).then( + baseElements => + baseElements.forEach(baseElement => + expect(baseElement.element.getAttribute('type')).to.equal( + 'doubleclick' + ) + ) + ); + }); }); }); @@ -1021,7 +1129,11 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { }); function createResource( - config, layout, tagName = 'amp-ad', parent = doc.body) { + config, + layout, + tagName = 'amp-ad', + parent = doc.body + ) { config['layout'] = layout; const element = createElementWithAttributes(doc, tagName, config); parent.appendChild(element); @@ -1062,7 +1174,7 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(300); }); - it('should return parent\'s fixed width for FILL layout', () => { + it("should return parent's fixed width for FILL layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); @@ -1071,20 +1183,23 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(300); }); - it('should return the max-width, if present, for FIXED_HEIGHT layout', - () => { - const element = createResource({height: 300}, 'fixed-height'); - element.style.maxWidth = '250px'; - expect(getContainerWidth(win, element)).to.equal(250); - }); + it('should return the max-width, if present, for FIXED_HEIGHT layout', () => { + const element = createResource({height: 300}, 'fixed-height'); + element.style.maxWidth = '250px'; + expect(getContainerWidth(win, element)).to.equal(250); + }); - it('should return parent\'s fixed width for FIXED_HEIGHT layout', () => { + it("should return parent's fixed width for FIXED_HEIGHT layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); const element = createResource( - {height: 250}, 'fixed-height', 'amp-ad', parent); + {height: 250}, + 'fixed-height', + 'amp-ad', + parent + ); expect(getContainerWidth(win, element)).to.equal(300); }); @@ -1094,13 +1209,12 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(250); }); - it('should return parent\'s fixed width for FLUID layout', () => { + it("should return parent's fixed width for FLUID layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); - const element = createResource( - {height: 250}, 'fluid', 'amp-ad', parent); + const element = createResource({height: 250}, 'fluid', 'amp-ad', parent); expect(getContainerWidth(win, element)).to.equal(300); }); @@ -1110,20 +1224,25 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { expect(getContainerWidth(win, element)).to.equal(250); }); - it('should return parent\'s fixed width for RESPONSIVE layout', () => { + it("should return parent's fixed width for RESPONSIVE layout", () => { const parent = document.createElement('div'); parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); const element = createResource( - {height: 250, width: 250}, 'responsive', 'amp-ad', parent); + {height: 250, width: 250}, + 'responsive', + 'amp-ad', + parent + ); expect(getContainerWidth(win, element)).to.equal(300); }); it('should return the viewport width for CONTAINER layout', () => { const element = createResource({} /* config */, 'container'); - sandbox.stub(Services.viewportForDoc(element), 'getSize') - .returns({width: 300}); + sandbox + .stub(Services.viewportForDoc(element), 'getSize') + .returns({width: 300}); expect(getContainerWidth(win, element)).to.equal(300); }); @@ -1133,8 +1252,7 @@ describes.realWin('#getContainerWidth', {amp: true}, env => { parent.setAttribute('width', 300); parent.setAttribute('layout', 'fixed'); doc.body.appendChild(parent); - const element = createResource( - {height: 250}, layout, 'amp-ad', parent); + const element = createResource({height: 250}, layout, 'amp-ad', parent); expect(getContainerWidth(win, element, 1)).to.equal(-1); }); }); diff --git a/ads/google/a4a/traffic-experiments.js b/ads/google/a4a/traffic-experiments.js index 8695aaff63492..658e857c4be07 100644 --- a/ads/google/a4a/traffic-experiments.js +++ b/ads/google/a4a/traffic-experiments.js @@ -22,10 +22,7 @@ * impacts on click-throughs. */ -import { - EXPERIMENT_ATTRIBUTE, - mergeExperimentIds, -} from './utils'; +import {EXPERIMENT_ATTRIBUTE, mergeExperimentIds} from './utils'; import { ExperimentInfo, // eslint-disable-line no-unused-vars isExperimentOn, @@ -42,7 +39,6 @@ export let A4aExperimentBranches; /** @type {string} @private */ export const MANUAL_EXPERIMENT_ID = '117152632'; - /** * Experiment IDs used to identify single pass experiments. * @@ -59,7 +55,8 @@ export const SINGLE_PASS_EXPERIMENT_IDS = { * @return {?string} experiment extracted from page url. */ export function extractUrlExperimentId(win, element) { - const expParam = Services.viewerForDoc(element).getParam('exp') || + const expParam = + Services.viewerForDoc(element).getParam('exp') || parseQueryString(win.location.search)['exp']; if (!expParam) { return null; @@ -67,15 +64,20 @@ export function extractUrlExperimentId(win, element) { // Allow for per type experiment control with Doubleclick key set for 'da' // and AdSense using 'aa'. Fallback to 'a4a' if type specific is missing. const expKeys = [ - (element.getAttribute('type') || '').toLowerCase() == 'doubleclick' ? - 'da' : 'aa', + (element.getAttribute('type') || '').toLowerCase() == 'doubleclick' + ? 'da' + : 'aa', 'a4a', ]; let arg; let match; - expKeys.forEach(key => arg = arg || - ((match = new RegExp(`(?:^|,)${key}:(-?\\d+)`).exec(expParam)) && - match[1])); + expKeys.forEach( + key => + (arg = + arg || + ((match = new RegExp(`(?:^|,)${key}:(-?\\d+)`).exec(expParam)) && + match[1])) + ); return arg || null; } @@ -111,7 +113,10 @@ export function parseExperimentIds(idString) { */ export function isInExperiment(element, id) { return parseExperimentIds(element.getAttribute(EXPERIMENT_ATTRIBUTE)).some( - x => { return x === id; }); + x => { + return x === id; + } + ); } /** @@ -157,7 +162,9 @@ export function hasLaunched(win, element) { * @return {boolean} Whether all list elements are valid experiment IDs. */ export function validateExperimentIds(idList) { - return idList.every(id => { return !isNaN(parseInt(id, 10)); }); + return idList.every(id => { + return !isNaN(parseInt(id, 10)); + }); } /** @@ -173,8 +180,10 @@ export function addExperimentIdToElement(experimentId, element) { } const currentEids = element.getAttribute(EXPERIMENT_ATTRIBUTE); if (currentEids && validateExperimentIds(parseExperimentIds(currentEids))) { - element.setAttribute(EXPERIMENT_ATTRIBUTE, - mergeExperimentIds([experimentId], currentEids)); + element.setAttribute( + EXPERIMENT_ATTRIBUTE, + mergeExperimentIds([experimentId], currentEids) + ); } else { element.setAttribute(EXPERIMENT_ATTRIBUTE, experimentId); } diff --git a/ads/google/a4a/utils.js b/ads/google/a4a/utils.js index 3da5f3afa3337..9e43667622b3d 100644 --- a/ads/google/a4a/utils.js +++ b/ads/google/a4a/utils.js @@ -120,8 +120,12 @@ export const ADX_ADY_EXP = { * @return {number} */ function getNavigationTiming(win, timingEvent) { - return (win['performance'] && win['performance']['timing'] && - win['performance']['timing'][timingEvent]) || 0; + return ( + (win['performance'] && + win['performance']['timing'] && + win['performance']['timing'][timingEvent]) || + 0 + ); } /** @@ -135,8 +139,10 @@ function getNavigationTiming(win, timingEvent) { * pathway. */ export function isGoogleAdsA4AValidEnvironment(win) { - return supportsNativeCrypto(win) && ( - !!isCdnProxy(win) || getMode(win).localDev || getMode(win).test); + return ( + supportsNativeCrypto(win) && + (!!isCdnProxy(win) || getMode(win).localDev || getMode(win).test) + ); } /** @@ -170,8 +176,10 @@ export function isReportingEnabled(ampElement) { if (getMode(ampElement.win).localDev && !getMode(ampElement.win).test) { toggleExperiment(win, 'a4aProfilingRate', true, true); } - return (type == 'doubleclick' || type == 'adsense') && - isExperimentOn(win, 'a4aProfilingRate'); + return ( + (type == 'doubleclick' || type == 'adsense') && + isExperimentOn(win, 'a4aProfilingRate') + ); } /** @@ -219,12 +227,14 @@ export function groupAmpAdsByType(win, type, groupFn) { // TODO(keithwrightbos): what about slots that become measured due to removal // of display none (e.g. user resizes viewport and media selector makes // visible). - const ampAdSelector = - r => r.element./*OK*/querySelector(`amp-ad[type=${type}]`); + const ampAdSelector = r => + r.element./*OK*/ querySelector(`amp-ad[type=${type}]`); const {documentElement} = win.document; - return Services.resourcesForDoc(documentElement).getMeasuredResources(win, - r => { - const isAmpAdType = r.element.tagName == 'AMP-AD' && + return ( + Services.resourcesForDoc(documentElement) + .getMeasuredResources(win, r => { + const isAmpAdType = + r.element.tagName == 'AMP-AD' && r.element.getAttribute('type') == type; if (isAmpAdType) { return true; @@ -236,22 +246,29 @@ export function groupAmpAdsByType(win, type, groupFn) { }) // Need to wait on any contained element resolution followed by build // of child ad. - .then(resources => Promise.all(resources.map( - resource => { + .then(resources => + Promise.all( + resources.map(resource => { if (resource.element.tagName == 'AMP-AD') { return resource.element; } // Must be container element so need to wait for child amp-ad to // be upgraded. return whenUpgradedToCustomElement( - dev().assertElement(ampAdSelector(resource))); - }))) + dev().assertElement(ampAdSelector(resource)) + ); + }) + ) + ) // Group by networkId. - .then(elements => elements.reduce((result, element) => { - const groupId = groupFn(element); - (result[groupId] || (result[groupId] = [])).push(element.getImpl()); - return result; - }, {})); + .then(elements => + elements.reduce((result, element) => { + const groupId = groupFn(element); + (result[groupId] || (result[groupId] = [])).push(element.getImpl()); + return result; + }, {}) + ) + ); } /** @@ -264,63 +281,62 @@ export function googlePageParameters(a4a, startTime) { const ampDoc = a4a.getAmpDoc(); // Do not wait longer than 1 second to retrieve referrer to ensure // viewer integration issues do not cause ad requests to hang indefinitely. - const referrerPromise = Services.timerFor(win).timeoutPromise( - 1000, Services.viewerForDoc(ampDoc).getReferrerUrl()) - .catch(() => { - dev().expectedError('AMP-A4A', 'Referrer timeout!'); - return ''; - }); + const referrerPromise = Services.timerFor(win) + .timeoutPromise(1000, Services.viewerForDoc(ampDoc).getReferrerUrl()) + .catch(() => { + dev().expectedError('AMP-A4A', 'Referrer timeout!'); + return ''; + }); const domLoading = getNavigationTiming(win, 'domLoading'); return Promise.all([ - getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga'), referrerPromise]) - .then(promiseResults => { - const clientId = promiseResults[0]; - const referrer = promiseResults[1]; - const {pageViewId, canonicalUrl} = Services.documentInfoForDoc(ampDoc); - // Read by GPT for GA/GPT integration. - win.gaGlobal = win.gaGlobal || {cid: clientId, hid: pageViewId}; - const {screen} = win; - const viewport = Services.viewportForDoc(ampDoc); - const viewportRect = viewport.getRect(); - const viewportSize = viewport.getSize(); - const visibilityState = Services.viewerForDoc(ampDoc) - .getVisibilityState(); - return { - 'is_amp': a4a.isXhrAllowed() ? - AmpAdImplementation.AMP_AD_XHR_TO_IFRAME_OR_AMP : - AmpAdImplementation.AMP_AD_IFRAME_GET, - 'amp_v': version(), - 'd_imp': '1', - 'c': getCorrelator(win, ampDoc, clientId), - 'ga_cid': win.gaGlobal.cid || null, - 'ga_hid': win.gaGlobal.hid || null, - 'dt': startTime, - 'biw': viewportRect.width, - 'bih': viewportRect.height, - 'u_aw': screen ? screen.availWidth : null, - 'u_ah': screen ? screen.availHeight : null, - 'u_cd': screen ? screen.colorDepth : null, - 'u_w': screen ? screen.width : null, - 'u_h': screen ? screen.height : null, - 'u_tz': -new Date().getTimezoneOffset(), - 'u_his': getHistoryLength(win), - 'isw': win != win.top ? viewportSize.width : null, - 'ish': win != win.top ? viewportSize.height : null, - 'art': getAmpRuntimeTypeParameter(win), - 'vis': visibilityStateCodes[visibilityState] || '0', - 'scr_x': viewport.getScrollLeft(), - 'scr_y': viewport.getScrollTop(), - 'bc': getBrowserCapabilitiesBitmap(win) || null, - 'debug_experiment_id': - (/(?:#|,)deid=([\d,]+)/i.exec(win.location.hash) || [])[1] || - null, - 'url': canonicalUrl || null, - 'top': win != win.top ? topWindowUrlOrDomain(win) : null, - 'loc': win.location.href == canonicalUrl ? null : win.location.href, - 'ref': referrer || null, - 'bdt': domLoading ? startTime - domLoading : null, - }; - }); + getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga'), + referrerPromise, + ]).then(promiseResults => { + const clientId = promiseResults[0]; + const referrer = promiseResults[1]; + const {pageViewId, canonicalUrl} = Services.documentInfoForDoc(ampDoc); + // Read by GPT for GA/GPT integration. + win.gaGlobal = win.gaGlobal || {cid: clientId, hid: pageViewId}; + const {screen} = win; + const viewport = Services.viewportForDoc(ampDoc); + const viewportRect = viewport.getRect(); + const viewportSize = viewport.getSize(); + const visibilityState = Services.viewerForDoc(ampDoc).getVisibilityState(); + return { + 'is_amp': a4a.isXhrAllowed() + ? AmpAdImplementation.AMP_AD_XHR_TO_IFRAME_OR_AMP + : AmpAdImplementation.AMP_AD_IFRAME_GET, + 'amp_v': version(), + 'd_imp': '1', + 'c': getCorrelator(win, ampDoc, clientId), + 'ga_cid': win.gaGlobal.cid || null, + 'ga_hid': win.gaGlobal.hid || null, + 'dt': startTime, + 'biw': viewportRect.width, + 'bih': viewportRect.height, + 'u_aw': screen ? screen.availWidth : null, + 'u_ah': screen ? screen.availHeight : null, + 'u_cd': screen ? screen.colorDepth : null, + 'u_w': screen ? screen.width : null, + 'u_h': screen ? screen.height : null, + 'u_tz': -new Date().getTimezoneOffset(), + 'u_his': getHistoryLength(win), + 'isw': win != win.top ? viewportSize.width : null, + 'ish': win != win.top ? viewportSize.height : null, + 'art': getAmpRuntimeTypeParameter(win), + 'vis': visibilityStateCodes[visibilityState] || '0', + 'scr_x': viewport.getScrollLeft(), + 'scr_y': viewport.getScrollTop(), + 'bc': getBrowserCapabilitiesBitmap(win) || null, + 'debug_experiment_id': + (/(?:#|,)deid=([\d,]+)/i.exec(win.location.hash) || [])[1] || null, + 'url': canonicalUrl || null, + 'top': win != win.top ? topWindowUrlOrDomain(win) : null, + 'loc': win.location.href == canonicalUrl ? null : win.location.href, + 'ref': referrer || null, + 'bdt': domLoading ? startTime - domLoading : null, + }; + }); } /** @@ -334,14 +350,18 @@ export function googlePageParameters(a4a, startTime) { * @return {!Promise} */ export function googleAdUrl( - a4a, baseUrl, startTime, parameters, opt_experimentIds) { + a4a, + baseUrl, + startTime, + parameters, + opt_experimentIds +) { // TODO: Maybe add checks in case these promises fail. const blockLevelParameters = googleBlockParameters(a4a, opt_experimentIds); - return googlePageParameters(a4a, startTime) - .then(pageLevelParameters => { - Object.assign(parameters, blockLevelParameters, pageLevelParameters); - return truncAndTimeUrl(baseUrl, parameters, startTime); - }); + return googlePageParameters(a4a, startTime).then(pageLevelParameters => { + Object.assign(parameters, blockLevelParameters, pageLevelParameters); + return truncAndTimeUrl(baseUrl, parameters, startTime); + }); } /** @@ -351,9 +371,11 @@ export function googleAdUrl( * @return {string} */ export function truncAndTimeUrl(baseUrl, parameters, startTime) { - return buildUrl( - baseUrl, parameters, MAX_URL_LENGTH - 10, TRUNCATION_PARAM) - + '&dtd=' + elapsedTimeWithCeiling(Date.now(), startTime); + return ( + buildUrl(baseUrl, parameters, MAX_URL_LENGTH - 10, TRUNCATION_PARAM) + + '&dtd=' + + elapsedTimeWithCeiling(Date.now(), startTime) + ); } /** @@ -406,9 +428,11 @@ function topWindowUrlOrDomain(win) { return win.top.location.hostname; } const secondFromTop = secondWindowFromTop(win); - if (secondFromTop == win || - origin == ancestorOrigins[ancestorOrigins.length - 2]) { - return extractHost(secondFromTop./*OK*/document.referrer); + if ( + secondFromTop == win || + origin == ancestorOrigins[ancestorOrigins.length - 2] + ) { + return extractHost(secondFromTop./*OK*/ document.referrer); } return extractHost(topOrigin); } else { @@ -417,7 +441,7 @@ function topWindowUrlOrDomain(win) { } catch (e) {} const secondFromTop = secondWindowFromTop(win); try { - return extractHost(secondFromTop./*OK*/document.referrer); + return extractHost(secondFromTop./*OK*/ document.referrer); } catch (e) {} return null; } @@ -430,8 +454,7 @@ function topWindowUrlOrDomain(win) { function secondWindowFromTop(win) { let secondFromTop = win; let depth = 0; - while (secondFromTop.parent != secondFromTop.parent.parent && - depth < 100) { + while (secondFromTop.parent != secondFromTop.parent.parent && depth < 100) { secondFromTop = secondFromTop.parent; depth++; } @@ -463,10 +486,12 @@ function elapsedTimeWithCeiling(time, start) { */ export function getCorrelator(win, elementOrAmpDoc, opt_cid) { if (!win.ampAdPageCorrelator) { - win.ampAdPageCorrelator = isExperimentOn(win, 'exp-new-correlator') ? - Math.floor(4503599627370496 * Math.random()) : - makeCorrelator( - Services.documentInfoForDoc(elementOrAmpDoc).pageViewId, opt_cid); + win.ampAdPageCorrelator = isExperimentOn(win, 'exp-new-correlator') + ? Math.floor(4503599627370496 * Math.random()) + : makeCorrelator( + Services.documentInfoForDoc(elementOrAmpDoc).pageViewId, + opt_cid + ); } return win.ampAdPageCorrelator; } @@ -479,7 +504,7 @@ export function getCorrelator(win, elementOrAmpDoc, opt_cid) { function makeCorrelator(pageViewId, opt_clientId) { const pageViewIdNumeric = Number(pageViewId || 0); if (opt_clientId) { - return pageViewIdNumeric + ((opt_clientId.replace(/\D/g, '') % 1e6) * 1e6); + return pageViewIdNumeric + (opt_clientId.replace(/\D/g, '') % 1e6) * 1e6; } else { // In this case, pageViewIdNumeric is only 4 digits => too low entropy // to be useful as a page correlator. So synthesize one from scratch. @@ -489,7 +514,6 @@ function makeCorrelator(pageViewId, opt_clientId) { } } - /** * Collect additional dimensions for the brdim parameter. * @param {!Window} win The window for which we read the browser dimensions. @@ -512,7 +536,8 @@ export function additionalDimensions(win, viewportSize) { innerWidth = viewportSize.width; innerHeight = viewportSize.height; } catch (e) {} - return [win.screenLeft, + return [ + win.screenLeft, win.screenTop, screenX, screenY, @@ -521,7 +546,8 @@ export function additionalDimensions(win, viewportSize) { outerWidth, outerHeight, innerWidth, - innerHeight].join(); + innerHeight, + ].join(); } /** @@ -570,7 +596,7 @@ export function getCsiAmpAnalyticsConfig() { // ast => ad schedule time // ars => ad render start 'met.a4a': - 'ast.${scheduleTime}~ars_lvt.${viewerLastVisibleTime}~ars.${time}', + 'ast.${scheduleTime}~ars_lvt.${viewerLastVisibleTime}~ars.${time}', 'qqid': '${qqid}', }), 'adIframeLoaded': csiTrigger('ad-iframe-loaded', { @@ -631,8 +657,9 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { return null; } try { - const analyticsConfig = - parseJson(responseHeaders.get(AMP_ANALYTICS_HEADER)); + const analyticsConfig = parseJson( + responseHeaders.get(AMP_ANALYTICS_HEADER) + ); devAssert(Array.isArray(analyticsConfig['url'])); const urls = analyticsConfig['url']; if (!urls.length) { @@ -662,12 +689,15 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { } // Security review needed here. config['requests'] = requests; - config['triggers']['continuousVisible']['request'] = - Object.keys(requests); + config['triggers']['continuousVisible']['request'] = Object.keys(requests); return config; } catch (err) { - dev().error('AMP-A4A', 'Invalid analytics', err, - responseHeaders.get(AMP_ANALYTICS_HEADER)); + dev().error( + 'AMP-A4A', + 'Invalid analytics', + err, + responseHeaders.get(AMP_ANALYTICS_HEADER) + ); } return null; } @@ -690,8 +720,9 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { export function mergeExperimentIds(newIds, currentIdString) { const newIdString = newIds.filter(newId => Number(newId)).join(','); currentIdString = currentIdString || ''; - return currentIdString + (currentIdString && newIdString ? ',' : '') - + newIdString; + return ( + currentIdString + (currentIdString && newIdString ? ',' : '') + newIdString + ); } /** @@ -706,22 +737,31 @@ export function mergeExperimentIds(newIds, currentIdString) { * @return {?JsonObject} config or null if invalid/missing. */ export function addCsiSignalsToAmpAnalyticsConfig( - win, element, config, qqid, isVerifiedAmpCreative) { + win, + element, + config, + qqid, + isVerifiedAmpCreative +) { // Add CSI pingbacks. const correlator = getCorrelator(win, element); const slotId = Number(element.getAttribute('data-amp-slot-index')); - const eids = encodeURIComponent( - element.getAttribute(EXPERIMENT_ATTRIBUTE)); + const eids = encodeURIComponent(element.getAttribute(EXPERIMENT_ATTRIBUTE)); const adType = element.getAttribute('type'); - const initTime = - Number(getTimingDataSync(win, 'navigationStart') || Date.now()); - const deltaTime = Math.round(win.performance && win.performance.now ? - win.performance.now() : (Date.now() - initTime)); - const baseCsiUrl = 'https://csi.gstatic.com/csi?s=a4a' + - `&c=${correlator}&slotId=${slotId}&qqid.${slotId}=${qqid}` + - `&dt=${initTime}` + - (eids != 'null' ? `&e.${slotId}=${eids}` : '') + - `&rls=${version()}&adt.${slotId}=${adType}`; + const initTime = Number( + getTimingDataSync(win, 'navigationStart') || Date.now() + ); + const deltaTime = Math.round( + win.performance && win.performance.now + ? win.performance.now() + : Date.now() - initTime + ); + const baseCsiUrl = + 'https://csi.gstatic.com/csi?s=a4a' + + `&c=${correlator}&slotId=${slotId}&qqid.${slotId}=${qqid}` + + `&dt=${initTime}` + + (eids != 'null' ? `&e.${slotId}=${eids}` : '') + + `&rls=${version()}&adt.${slotId}=${adType}`; const isAmpSuffix = isVerifiedAmpCreative ? 'Friendly' : 'CrossDomain'; config['triggers']['continuousVisibleIniLoad'] = { 'on': 'ini-load', @@ -735,14 +775,14 @@ export function addCsiSignalsToAmpAnalyticsConfig( 'selectionMethod': 'closest', 'request': 'renderStartCsi', }; - config['requests']['iniLoadCsi'] = baseCsiUrl + - `&met.a4a.${slotId}=iniLoadCsi${isAmpSuffix}.${deltaTime}`; - config['requests']['renderStartCsi'] = baseCsiUrl + - `&met.a4a.${slotId}=renderStartCsi${isAmpSuffix}.${deltaTime}`; + config['requests']['iniLoadCsi'] = + baseCsiUrl + `&met.a4a.${slotId}=iniLoadCsi${isAmpSuffix}.${deltaTime}`; + config['requests']['renderStartCsi'] = + baseCsiUrl + `&met.a4a.${slotId}=renderStartCsi${isAmpSuffix}.${deltaTime}`; // Add CSI ping for visibility. - config['requests']['visibilityCsi'] = baseCsiUrl + - `&met.a4a.${slotId}=visibilityCsi.${deltaTime}`; + config['requests']['visibilityCsi'] = + baseCsiUrl + `&met.a4a.${slotId}=visibilityCsi.${deltaTime}`; config['triggers']['continuousVisible']['request'].push('visibilityCsi'); return config; } @@ -756,8 +796,11 @@ export function addCsiSignalsToAmpAnalyticsConfig( */ export function getEnclosingContainerTypes(adElement) { const containerTypeSet = {}; - for (let el = adElement.parentElement, counter = 0; - el && counter < 20; el = el.parentElement, counter++) { + for ( + let el = adElement.parentElement, counter = 0; + el && counter < 20; + el = el.parentElement, counter++ + ) { const tagName = el.tagName.toUpperCase(); if (ValidAdContainerTypes[tagName]) { containerTypeSet[ValidAdContainerTypes[tagName]] = true; @@ -779,9 +822,12 @@ export function maybeAppendErrorParameter(adUrl, parameterValue) { // truncated and error parameter is not already present. Note that we assume // that added, error parameter length will be less than truncation parameter // so adding will not cause length to exceed maximum. - if (new RegExp(`[?|&](${encodeURIComponent(TRUNCATION_PARAM.name)}=` + - `${encodeURIComponent(String(TRUNCATION_PARAM.value))}|aet=[^&]*)$`) - .test(adUrl)) { + if ( + new RegExp( + `[?|&](${encodeURIComponent(TRUNCATION_PARAM.name)}=` + + `${encodeURIComponent(String(TRUNCATION_PARAM.value))}|aet=[^&]*)$` + ).test(adUrl) + ) { return; } const modifiedAdUrl = adUrl + `&aet=${parameterValue}`; @@ -795,12 +841,14 @@ export function maybeAppendErrorParameter(adUrl, parameterValue) { * @return {?string} */ export function getBinaryTypeNumericalCode(type) { - return { - 'production': '0', - 'control': '1', - 'canary': '2', - 'rc': '3', - }[type] || null; + return ( + { + 'production': '0', + 'control': '1', + 'canary': '2', + 'rc': '3', + }[type] || null + ); } /** @const {!RegExp} */ @@ -825,16 +873,18 @@ export let IdentityToken; export function getIdentityToken(win, ampDoc, consentPolicyId) { // If configured to use amp-consent, delay request until consent state is // resolved. - win['goog_identity_prom'] = win['goog_identity_prom'] || - (consentPolicyId - ? getConsentPolicyState(ampDoc.getHeadNode(), consentPolicyId) - : Promise.resolve(CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED)) - .then(consentState => - consentState == CONSENT_POLICY_STATE.INSUFFICIENT || - consentState == CONSENT_POLICY_STATE.UNKNOWN ? - /** @type{!IdentityToken} */({}) : - executeIdentityTokenFetch(win, ampDoc)); - return /** @type {!Promise} */(win['goog_identity_prom']); + win['goog_identity_prom'] = + win['goog_identity_prom'] || + (consentPolicyId + ? getConsentPolicyState(ampDoc.getHeadNode(), consentPolicyId) + : Promise.resolve(CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED) + ).then(consentState => + consentState == CONSENT_POLICY_STATE.INSUFFICIENT || + consentState == CONSENT_POLICY_STATE.UNKNOWN + ? /** @type{!IdentityToken} */ ({}) + : executeIdentityTokenFetch(win, ampDoc) + ); + return /** @type {!Promise} */ (win['goog_identity_prom']); } /** @@ -845,42 +895,63 @@ export function getIdentityToken(win, ampDoc, consentPolicyId) { * @param {number=} startTime * @return {!Promise} */ -function executeIdentityTokenFetch(win, ampDoc, redirectsRemaining = 1, - domain = undefined, startTime = Date.now()) { +function executeIdentityTokenFetch( + win, + ampDoc, + redirectsRemaining = 1, + domain = undefined, + startTime = Date.now() +) { const url = getIdentityTokenRequestUrl(win, ampDoc, domain); - return Services.xhrFor(win).fetchJson(url, { - mode: 'cors', - method: 'GET', - ampCors: false, - credentials: 'include', - }).then(res => res.json()) - .then(obj => { - const token = obj['newToken']; - const jar = obj['1p_jar'] || ''; - const pucrd = obj['pucrd'] || ''; - const freshLifetimeSecs = parseInt(obj['freshLifetimeSecs'] || '', 10); - const validLifetimeSecs = parseInt(obj['validLifetimeSecs'] || '', 10); - const altDomain = obj['altDomain']; - const fetchTimeMs = Date.now() - startTime; - if (IDENTITY_DOMAIN_REGEXP_.test(altDomain)) { - if (!redirectsRemaining--) { - // Max redirects, log? - return {fetchTimeMs}; - } - return executeIdentityTokenFetch( - win, ampDoc, redirectsRemaining, altDomain, startTime); - } else if (freshLifetimeSecs > 0 && validLifetimeSecs > 0 && - typeof token == 'string') { - return {token, jar, pucrd, freshLifetimeSecs, validLifetimeSecs, - fetchTimeMs}; + return Services.xhrFor(win) + .fetchJson(url, { + mode: 'cors', + method: 'GET', + ampCors: false, + credentials: 'include', + }) + .then(res => res.json()) + .then(obj => { + const token = obj['newToken']; + const jar = obj['1p_jar'] || ''; + const pucrd = obj['pucrd'] || ''; + const freshLifetimeSecs = parseInt(obj['freshLifetimeSecs'] || '', 10); + const validLifetimeSecs = parseInt(obj['validLifetimeSecs'] || '', 10); + const altDomain = obj['altDomain']; + const fetchTimeMs = Date.now() - startTime; + if (IDENTITY_DOMAIN_REGEXP_.test(altDomain)) { + if (!redirectsRemaining--) { + // Max redirects, log? + return {fetchTimeMs}; } - // returning empty - return {fetchTimeMs}; - }) - .catch(unusedErr => { - // TODO log? - return {}; - }); + return executeIdentityTokenFetch( + win, + ampDoc, + redirectsRemaining, + altDomain, + startTime + ); + } else if ( + freshLifetimeSecs > 0 && + validLifetimeSecs > 0 && + typeof token == 'string' + ) { + return { + token, + jar, + pucrd, + freshLifetimeSecs, + validLifetimeSecs, + fetchTimeMs, + }; + } + // returning empty + return {fetchTimeMs}; + }) + .catch(unusedErr => { + // TODO log? + return {}; + }); } /** @@ -893,12 +964,14 @@ function executeIdentityTokenFetch(win, ampDoc, redirectsRemaining = 1, export function getIdentityTokenRequestUrl(win, ampDoc, domain = undefined) { if (!domain && win != win.top && win.location.ancestorOrigins) { const matches = IDENTITY_DOMAIN_REGEXP_.exec( - win.location.ancestorOrigins[win.location.ancestorOrigins.length - 1]); + win.location.ancestorOrigins[win.location.ancestorOrigins.length - 1] + ); domain = (matches && matches[0]) || undefined; } domain = domain || '.google.com'; - const canonical = - extractHost(Services.documentInfoForDoc(ampDoc).canonicalUrl); + const canonical = extractHost( + Services.documentInfoForDoc(ampDoc).canonicalUrl + ); return `https://adservice${domain}/adsid/integrator.json?domain=${canonical}`; } diff --git a/ads/google/adsense-amp-auto-ads-responsive.js b/ads/google/adsense-amp-auto-ads-responsive.js index 37ba794760420..590154d313566 100644 --- a/ads/google/adsense-amp-auto-ads-responsive.js +++ b/ads/google/adsense-amp-auto-ads-responsive.js @@ -14,18 +14,15 @@ * limitations under the License. */ - import { ExperimentInfo, // eslint-disable-line no-unused-vars getExperimentBranch, randomlySelectUnsetExperiments, } from '../../src/experiments'; - /** @const {string} */ export const ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME = - 'amp-auto-ads-adsense-responsive'; - + 'amp-auto-ads-adsense-responsive'; /** * @enum {string} @@ -35,7 +32,6 @@ export const AdSenseAmpAutoAdsResponsiveBranches = { EXPERIMENT: '19861211', // do attempt to expand auto ads to responsive format }; - /** @const {!ExperimentInfo} */ const ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_INFO = { isTrafficEligible: win => !!win.document.querySelector('AMP-AUTO-ADS'), @@ -45,7 +41,6 @@ const ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_INFO = { ], }; - /** * This has the side-effect of selecting the page into a branch of the * experiment, which becomes sticky for the entire pageview. @@ -55,9 +50,12 @@ const ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_INFO = { */ export function getAdSenseAmpAutoAdsResponsiveExperimentBranch(win) { const experiments = /** @type {!Object} */ ({}); - experiments[ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME] = - ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_INFO; + experiments[ + ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME + ] = ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_INFO; randomlySelectUnsetExperiments(win, experiments); - return getExperimentBranch(win, - ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME) || null; + return ( + getExperimentBranch(win, ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME) || + null + ); } diff --git a/ads/google/adsense.js b/ads/google/adsense.js index f41569d5b9210..8d69b59fb9de6 100644 --- a/ads/google/adsense.js +++ b/ads/google/adsense.js @@ -33,21 +33,43 @@ import {validateData} from '../../3p/3p'; */ export function adsense(global, data) { // TODO: check mandatory fields - validateData(data, [], - ['adClient', 'adSlot', 'adHost', 'adtest', 'tagOrigin', 'experimentId', - 'ampSlotIndex', 'adChannel', 'autoFormat', 'fullWidth', 'package', - 'npaOnUnknownConsent', 'matchedContentUiType', 'matchedContentRowsNum', - 'matchedContentColumnsNum']); + validateData( + data, + [], + [ + 'adClient', + 'adSlot', + 'adHost', + 'adtest', + 'tagOrigin', + 'experimentId', + 'ampSlotIndex', + 'adChannel', + 'autoFormat', + 'fullWidth', + 'package', + 'npaOnUnknownConsent', + 'matchedContentUiType', + 'matchedContentRowsNum', + 'matchedContentColumnsNum', + ] + ); - if (data['autoFormat'] == ADSENSE_RSPV_TAG || - data['autoFormat'] == ADSENSE_MCRSPV_TAG) { - userAssert(hasOwn(data, 'fullWidth'), - 'Responsive AdSense ad units require the attribute data-full-width.'); + if ( + data['autoFormat'] == ADSENSE_RSPV_TAG || + data['autoFormat'] == ADSENSE_MCRSPV_TAG + ) { + userAssert( + hasOwn(data, 'fullWidth'), + 'Responsive AdSense ad units require the attribute data-full-width.' + ); - userAssert(data['height'] == ADSENSE_RSPV_WHITELISTED_HEIGHT, - `Specified height ${data['height']} in tag is not equal to ` + - `the required height of ${ADSENSE_RSPV_WHITELISTED_HEIGHT} for ` + - 'responsive AdSense ad units.'); + userAssert( + data['height'] == ADSENSE_RSPV_WHITELISTED_HEIGHT, + `Specified height ${data['height']} in tag is not equal to ` + + `the required height of ${ADSENSE_RSPV_WHITELISTED_HEIGHT} for ` + + 'responsive AdSense ad units.' + ); } if (global.context.clientId) { @@ -62,14 +84,22 @@ export function adsense(global, data) { global.document.body.appendChild(s); const i = global.document.createElement('ins'); - ['adChannel', 'adClient', 'adSlot', 'adHost', 'adtest', 'tagOrigin', - 'package', 'matchedContentUiType', 'matchedContentRowsNum', - 'matchedContentColumnsNum'] - .forEach(datum => { - if (data[datum]) { - i.setAttribute('data-' + camelCaseToDash(datum), data[datum]); - } - }); + [ + 'adChannel', + 'adClient', + 'adSlot', + 'adHost', + 'adtest', + 'tagOrigin', + 'package', + 'matchedContentUiType', + 'matchedContentRowsNum', + 'matchedContentColumnsNum', + ].forEach(datum => { + if (data[datum]) { + i.setAttribute('data-' + camelCaseToDash(datum), data[datum]); + } + }); i.setAttribute('data-page-url', global.context.canonicalUrl); i.setAttribute('class', 'adsbygoogle'); setStyles(i, { @@ -85,8 +115,9 @@ export function adsense(global, data) { return; } case CONSENT_POLICY_STATE.INSUFFICIENT: - (global.adsbygoogle = global.adsbygoogle || []) - ['requestNonPersonalizedAds'] = true; + (global.adsbygoogle = global.adsbygoogle || [])[ + 'requestNonPersonalizedAds' + ] = true; break; } if (data['experimentId']) { diff --git a/ads/google/csa.js b/ads/google/csa.js index f1ed923418be8..0eea294220f38 100644 --- a/ads/google/csa.js +++ b/ads/google/csa.js @@ -39,7 +39,6 @@ export const AD_TYPE = { AFSH_BACKFILL: 3, }; - /** * Request Custom Search Ads (Adsense for Search or AdSense for Shopping). * @param {!Window} global The window object of the iframe @@ -47,15 +46,19 @@ export const AD_TYPE = { */ export function csa(global, data) { // Get parent width in case we want to override - const width = global.document.body./*OK*/clientWidth; + const width = global.document.body./*OK*/ clientWidth; - validateData(data, [], [ - 'afshPageOptions', - 'afshAdblockOptions', - 'afsPageOptions', - 'afsAdblockOptions', - 'ampSlotIndex', - ]); + validateData( + data, + [], + [ + 'afshPageOptions', + 'afshAdblockOptions', + 'afsPageOptions', + 'afsAdblockOptions', + 'ampSlotIndex', + ] + ); // Add the ad container to the document const containerDiv = global.document.createElement('div'); @@ -68,13 +71,21 @@ export function csa(global, data) { // Parse all the options const afshPage = Object.assign( - Object(tryParseJson(data['afshPageOptions'])), pageOptions); + Object(tryParseJson(data['afshPageOptions'])), + pageOptions + ); const afsPage = Object.assign( - Object(tryParseJson(data['afsPageOptions'])), pageOptions); + Object(tryParseJson(data['afsPageOptions'])), + pageOptions + ); const afshAd = Object.assign( - Object(tryParseJson(data['afshAdblockOptions'])), adblockOptions); + Object(tryParseJson(data['afshAdblockOptions'])), + adblockOptions + ); const afsAd = Object.assign( - Object(tryParseJson(data['afsAdblockOptions'])), adblockOptions); + Object(tryParseJson(data['afsAdblockOptions'])), + adblockOptions + ); // Special case for AFSh when "auto" is the requested width if (afshAd['width'] == 'auto') { @@ -82,18 +93,25 @@ export function csa(global, data) { } // Event listener needed for iOS9 bug - global.addEventListener('orientationchange', - orientationChangeHandler.bind(null, global, containerDiv)); + global.addEventListener( + 'orientationchange', + orientationChangeHandler.bind(null, global, containerDiv) + ); // Register resize callbacks global.context.onResizeSuccess( - resizeSuccessHandler.bind(null, global, containerDiv)); + resizeSuccessHandler.bind(null, global, containerDiv) + ); global.context.onResizeDenied( - resizeDeniedHandler.bind(null, global, containerDiv)); + resizeDeniedHandler.bind(null, global, containerDiv) + ); // Only call for ads once the script has loaded - loadScript(global, 'https://www.google.com/adsense/search/ads.js', - requestCsaAds.bind(null, global, data, afsPage, afsAd, afshPage, afshAd)); + loadScript( + global, + 'https://www.google.com/adsense/search/ads.js', + requestCsaAds.bind(null, global, data, afsPage, afsAd, afshPage, afshAd) + ); } /** @@ -109,7 +127,7 @@ function orientationChangeHandler(global, containerDiv) { global.setTimeout(() => { // Force DOM reflow and repaint. // eslint-disable-next-line no-unused-vars - const ignore = global.document.body./*OK*/offsetHeight; + const ignore = global.document.body./*OK*/ offsetHeight; // Capture new height. let newHeight = getStyle(containerDiv, 'height'); // In older versions of iOS, this height will be different because the @@ -122,8 +140,8 @@ function orientationChangeHandler(global, containerDiv) { // Also update the onclick function to resize to the right height. const overflow = global.document.getElementById('overflow'); if (overflow) { - overflow.onclick = - () => global.context.requestResize(undefined, newHeight); + overflow.onclick = () => + global.context.requestResize(undefined, newHeight); } // Resize the container to the correct height. global.context.requestResize(undefined, newHeight); @@ -218,8 +236,7 @@ function getAdType(data) { } if (data['afsPageOptions'] != null && data['afshPageOptions'] != null) { return AD_TYPE.AFSH_BACKFILL; - } - else { + } else { return AD_TYPE.UNSUPPORTED; } } @@ -249,7 +266,7 @@ export function callbackWithNoBackfill(global, containerName, hasAd) { * @param {string} containerName The name of the CSA container * @param {boolean} hasAd Whether or not CSA returned an ad * @visibleForTesting -*/ + */ export function callbackWithBackfill(global, page, ad, containerName, hasAd) { if (hasAd) { resizeIframe(global, containerName); @@ -268,10 +285,10 @@ export function callbackWithBackfill(global, page, ad, containerName, hasAd) { export function resizeIframe(global, containerName) { // Get actual height of container const container = global.document.getElementById(containerName); - const height = container./*OK*/offsetHeight; + const height = container./*OK*/ offsetHeight; // Set initial AMP height currentAmpHeight = - global.context.initialIntersection.boundingClientRect.height; + global.context.initialIntersection.boundingClientRect.height; // If the height of the container is larger than the height of the // initially requested AMP tag, add the overflow element @@ -336,10 +353,11 @@ function getOverflowLine(global) { * @return {!Element} */ function getOverflowChevron(global) { - const svg = '' + - ' '; + const svg = + '' + + ' '; const chevron = global.document.createElement('div'); setStyles(chevron, { @@ -349,7 +367,7 @@ function getOverflowChevron(global) { marginRight: 'auto', display: 'block', }); - chevron./*OK*/innerHTML = svg; + chevron./*OK*/ innerHTML = svg; return chevron; } diff --git a/ads/google/doubleclick.js b/ads/google/doubleclick.js index da1191d910d78..17bf71a20c2ef 100644 --- a/ads/google/doubleclick.js +++ b/ads/google/doubleclick.js @@ -21,8 +21,10 @@ const TAG = 'DOUBLECLICK - DEPRECATED'; * @param {!Object} opt_data */ export function doubleclick(opt_global, opt_data) { - dev().error(TAG, 'The use of doubleclick.js has been deprecated. Please ' + - 'switch to Fast Fetch. See documentation here: ' + - 'https://github.com/ampproject/amphtml/issues/11834'); - + dev().error( + TAG, + 'The use of doubleclick.js has been deprecated. Please ' + + 'switch to Fast Fetch. See documentation here: ' + + 'https://github.com/ampproject/amphtml/issues/11834' + ); } diff --git a/ads/google/ima-player-data.js b/ads/google/ima-player-data.js index e0064557036a7..64e884740a66d 100644 --- a/ads/google/ima-player-data.js +++ b/ads/google/ima-player-data.js @@ -15,7 +15,6 @@ */ export class ImaPlayerData { - /** * Create a new ImaPlayerData object. */ diff --git a/ads/google/imaVideo.js b/ads/google/imaVideo.js index 2ec9bddd7459b..53196b6e675a0 100644 --- a/ads/google/imaVideo.js +++ b/ads/google/imaVideo.js @@ -225,9 +225,8 @@ let showControlsThrottled = throttle(window, showControls, 1000); * @param {!Object} data */ export function imaVideo(global, data) { - - videoWidth = global./*OK*/innerWidth; - videoHeight = global./*OK*/innerHeight; + videoWidth = global./*OK*/ innerWidth; + videoHeight = global./*OK*/ innerHeight; adLabel = data.adLabel || 'Ad (%s of %s)'; // Wraps *everything*. @@ -430,7 +429,9 @@ export function imaVideo(global, data) { } videoPlayer.setAttribute('playsinline', true); videoPlayer.setAttribute( - 'controlsList', 'nodownload nofullscreen noremoteplayback'); + 'controlsList', + 'nodownload nofullscreen noremoteplayback' + ); if (data.src) { const sourceElement = document.createElement('source'); sourceElement.setAttribute('src', data.src); @@ -467,9 +468,11 @@ export function imaVideo(global, data) { mouseDownEvent = 'mousedown'; mouseMoveEvent = 'mousemove'; mouseUpEvent = 'mouseup'; - if (navigator.userAgent.match(/iPhone/i) || - navigator.userAgent.match(/iPad/i) || - navigator.userAgent.match(/Android/i)) { + if ( + navigator.userAgent.match(/iPhone/i) || + navigator.userAgent.match(/iPad/i) || + navigator.userAgent.match(/Android/i) + ) { mobileBrowser = true; interactEvent = 'touchend'; mouseDownEvent = 'touchstart'; @@ -481,18 +484,22 @@ export function imaVideo(global, data) { bigPlayDiv.addEventListener(mouseMoveEvent, onBigPlayTouchMove); bigPlayDiv.addEventListener(mouseUpEvent, onBigPlayTouchEnd); bigPlayDiv.addEventListener( - 'tapwithoutdrag', - onBigPlayClick.bind(null, global)); + 'tapwithoutdrag', + onBigPlayClick.bind(null, global) + ); } else { bigPlayDiv.addEventListener( - interactEvent, - onBigPlayClick.bind(null, global)); + interactEvent, + onBigPlayClick.bind(null, global) + ); } playPauseDiv.addEventListener(interactEvent, onPlayPauseClick); progressBarWrapperDiv.addEventListener(mouseDownEvent, onProgressClick); muteUnmuteDiv.addEventListener(interactEvent, onMuteUnmuteClick); - fullscreenDiv.addEventListener(interactEvent, - toggleFullscreen.bind(null, global)); + fullscreenDiv.addEventListener( + interactEvent, + toggleFullscreen.bind(null, global) + ); // Timeout is 1s, because showControls will hide after 3s showControlsThrottled = throttle(window, showControls, 1000); @@ -500,25 +507,31 @@ export function imaVideo(global, data) { const fullScreenEvents = [ 'fullscreenchange', 'mozfullscreenchange', - 'webkitfullscreenchange']; + 'webkitfullscreenchange', + ]; fullScreenEvents.forEach(fsEvent => { - global.document.addEventListener(fsEvent, - onFullscreenChange.bind(null, global), - false); + global.document.addEventListener( + fsEvent, + onFullscreenChange.bind(null, global), + false + ); }); consentState = global.context.initialConsentState; - if (consentState == 4) { // UNKNOWN + if (consentState == 4) { + // UNKNOWN // On unknown consent state, do not load IMA. Treat this the same as if IMA // failed to load. onImaLoadFail(); } else { // Set-up code that can't run until the IMA lib loads. loadScript( - /** @type {!Window} */ (global), - 'https://imasdk.googleapis.com/js/sdkloader/ima3.js', - () => onImaLoadSuccess(global, data), onImaLoadFail); + /** @type {!Window} */ (global), + 'https://imasdk.googleapis.com/js/sdkloader/ima3.js', + () => onImaLoadSuccess(global, data), + onImaLoadFail + ); } } @@ -569,8 +582,10 @@ function onImaLoadSuccess(global, data) { } } - adDisplayContainer = - new global.google.ima.AdDisplayContainer(adContainerDiv, videoPlayer); + adDisplayContainer = new global.google.ima.AdDisplayContainer( + adContainerDiv, + videoPlayer + ); adsLoader = new global.google.ima.AdsLoader(adDisplayContainer); adsLoader.getSettings().setPlayerType('amp-ima'); @@ -580,8 +595,12 @@ function onImaLoadSuccess(global, data) { // an AdDisplayContainer. // playerType and playerVersion are used by the developers to track usage, // so we do not want to allow users to overwrite those values. - const skippedSettings = - ['locale', 'vpaidMode', 'playerType', 'playerVersion']; + const skippedSettings = [ + 'locale', + 'vpaidMode', + 'playerType', + 'playerVersion', + ]; for (const setting in imaSettings) { if (!skippedSettings.includes(setting)) { // Change e.g. 'ppid' to 'setPpid'. @@ -592,13 +611,15 @@ function onImaLoadSuccess(global, data) { } } adsLoader.addEventListener( - global.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, - onAdsManagerLoaded.bind(null, global), - false); + global.google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, + onAdsManagerLoaded.bind(null, global), + false + ); adsLoader.addEventListener( - global.google.ima.AdErrorEvent.Type.AD_ERROR, - onAdsLoaderError, - false); + global.google.ima.AdErrorEvent.Type.AD_ERROR, + onAdsLoaderError, + false + ); videoPlayer.addEventListener('ended', onContentEnded); @@ -623,7 +644,10 @@ function onImaLoadSuccess(global, data) { function onImaLoadFail() { // Something blocked ima3.js from loading - ignore all IMA stuff and just play // content. - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type !Element */ videoPlayer, + showControlsThrottled + ); imaLoadAllowed = false; postMessage({event: VideoEvents.LOAD}); } @@ -634,7 +658,7 @@ function onImaLoadFail() { */ function htmlToElement(html) { const template = document.createElement('template'); - template./*OK*/innerHTML = html; + template./*OK*/ innerHTML = html; return template.content.firstChild; } @@ -652,7 +676,7 @@ function createIcon(global, name, fill = '#FFFFFF') { icon.setAttributeNS(null, 'width', '100%'); icon.setAttributeNS(null, 'viewBox', '0 0 24 24'); setStyle(icon, 'filter', 'drop-shadow(0px 0px 14px rgba(0,0,0,0.4))'); - icon./*OK*/innerHTML = icons[name]; + icon./*OK*/ innerHTML = icons[name]; return icon; } @@ -662,7 +686,7 @@ function createIcon(global, name, fill = '#FFFFFF') { * @param {string} [fill='#FFFFFF'] */ function changeIcon(element, name, fill = '#FFFFFF') { - element./*OK*/innerHTML = icons[name]; + element./*OK*/ innerHTML = icons[name]; if (fill != element.getAttributeNS(null, 'fill')) { element.setAttributeNS(null, 'fill', fill); } @@ -750,7 +774,10 @@ export function playAds(global) { // Ad request resolved. try { adsManager.init( - videoWidth, videoHeight, global.google.ima.ViewMode.NORMAL); + videoWidth, + videoHeight, + global.google.ima.ViewMode.NORMAL + ); adsManager.start(); } catch (adError) { playVideo(); @@ -794,25 +821,31 @@ export function onContentEnded() { export function onAdsManagerLoaded(global, adsManagerLoadedEvent) { const adsRenderingSettings = new global.google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; - adsManager = adsManagerLoadedEvent.getAdsManager(videoPlayer, - adsRenderingSettings); - adsManager.addEventListener(global.google.ima.AdErrorEvent.Type.AD_ERROR, - onAdError); + adsManager = adsManagerLoadedEvent.getAdsManager( + videoPlayer, + adsRenderingSettings + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.LOADED, - onAdLoad); + global.google.ima.AdErrorEvent.Type.AD_ERROR, + onAdError + ); + adsManager.addEventListener(global.google.ima.AdEvent.Type.LOADED, onAdLoad); adsManager.addEventListener( - global.google.ima.AdEvent.Type.AD_PROGRESS, - onAdProgress); + global.google.ima.AdEvent.Type.AD_PROGRESS, + onAdProgress + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, - onContentPauseRequested.bind(null, global)); + global.google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, + onContentPauseRequested.bind(null, global) + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, - onContentResumeRequested); + global.google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, + onContentResumeRequested + ); adsManager.addEventListener( - global.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, - onAllAdsCompleted); + global.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, + onAllAdsCompleted + ); if (muteAdsManagerOnLoaded) { adsManager.setVolume(0); } @@ -830,7 +863,10 @@ export function onAdsLoaderError() { // failing to load an ad is just as good as loading one as far as starting // playback is concerned because our content will be ready to play. postMessage({event: VideoEvents.LOAD}); - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type !Element */ videoPlayer, + showControlsThrottled + ); if (playbackStarted) { playVideo(); } @@ -847,7 +883,10 @@ export function onAdError() { if (adsManager) { adsManager.destroy(); } - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type !Element */ videoPlayer, + showControlsThrottled + ); playVideo(); } @@ -874,8 +913,7 @@ export function onAdProgress(global) { remainingSeconds = '0' + remainingSeconds; } const label = adLabel.replace('%s', adPosition).replace('%s', totalAds); - countdownDiv.textContent - = `${label}: ${remainingMinutes}:${remainingSeconds}`; + countdownDiv.textContent = `${label}: ${remainingMinutes}:${remainingSeconds}`; } /** @@ -886,15 +924,19 @@ export function onAdProgress(global) { export function onContentPauseRequested(global) { if (adsManagerWidthOnLoad) { adsManager.resize( - adsManagerWidthOnLoad, - adsManagerHeightOnLoad, - global.google.ima.ViewMode.NORMAL); + adsManagerWidthOnLoad, + adsManagerHeightOnLoad, + global.google.ima.ViewMode.NORMAL + ); adsManagerWidthOnLoad = null; adsManagerHeightOnLoad = null; } adsActive = true; postMessage({event: VideoEvents.AD_START}); - removeHoverEventFromElement(/** @type !Element */(videoPlayer), showControlsThrottled); + removeHoverEventFromElement( + /** @type !Element */ videoPlayer, + showControlsThrottled + ); setStyle(adContainerDiv, 'display', 'block'); videoPlayer.removeEventListener('ended', onContentEnded); showAdControls(); @@ -908,7 +950,10 @@ export function onContentPauseRequested(global) { */ export function onContentResumeRequested() { adsActive = false; - addHoverEventToElement(/** @type !Element */(videoPlayer), showControlsThrottled); + addHoverEventToElement( + /** @type !Element */ videoPlayer, + showControlsThrottled + ); postMessage({event: VideoEvents.AD_END}); resetControlsAfterAd(); if (!contentComplete) { @@ -963,12 +1008,10 @@ function playerDataTick() { * @visibleForTesting */ export function updateUi(currentTime, duration) { - timeNode.textContent = - formatTime(currentTime) + ' / ' + formatTime(duration); - const progressPercent = - Math.floor((currentTime / duration) * 100); + timeNode.textContent = formatTime(currentTime) + ' / ' + formatTime(duration); + const progressPercent = Math.floor((currentTime / duration) * 100); setStyle(progressLine, 'width', progressPercent + '%'); - setStyle(progressMarkerDiv, 'left', (progressPercent - 1) + '%'); + setStyle(progressMarkerDiv, 'left', progressPercent - 1 + '%'); } /** @@ -992,7 +1035,7 @@ export function formatTime(time) { } else { timeString += minutes + ':'; } - const seconds = Math.floor(time - ((hours * 3600) + (minutes * 60))); + const seconds = Math.floor(time - (hours * 3600 + minutes * 60)); timeString += zeroPad(seconds); return timeString; } @@ -1042,7 +1085,7 @@ function onProgressClickEnd() { function onProgressMove(event) { const progressWrapperPosition = getPagePosition(progressBarWrapperDiv); const progressListStart = progressWrapperPosition.x; - const progressListWidth = progressBarWrapperDiv./*OK*/offsetWidth; + const progressListWidth = progressBarWrapperDiv./*OK*/ offsetWidth; // Handle Android Chrome touch events. const eventX = event.clientX || event.touches[0].pageX; @@ -1063,12 +1106,14 @@ function onProgressMove(event) { */ function getPagePosition(el) { let lx, ly; - for (lx = 0, ly = 0; + for ( + lx = 0, ly = 0; el != null; - lx += el./*OK*/offsetLeft, ly += el./*OK*/offsetTop, - el = el./*OK*/offsetParent) - {} - return {x: lx,y: ly}; + lx += el./*OK*/ offsetLeft, + ly += el./*OK*/ offsetTop, + el = el./*OK*/ offsetParent + ) {} + return {x: lx, y: ly}; } /** @@ -1166,34 +1211,33 @@ export function unmuteVideo() { } } - /** * @param {Object} global */ function exitFullscreen(global) { // The video is currently in fullscreen mode - const cancelFullscreen = global.document.exitFullscreen || - global.document.exitFullScreen || - global.document.webkitCancelFullScreen || - global.document.mozCancelFullScreen; + const cancelFullscreen = + global.document.exitFullscreen || + global.document.exitFullScreen || + global.document.webkitCancelFullScreen || + global.document.mozCancelFullScreen; if (cancelFullscreen) { cancelFullscreen.call(document); } } - /** * @param {Object} global */ function enterFullscreen(global) { // Try to enter fullscreen mode in the browser const requestFullscreen = - global.document.documentElement.requestFullscreen || - global.document.documentElement.webkitRequestFullscreen || - global.document.documentElement.mozRequestFullscreen || - global.document.documentElement.requestFullScreen || - global.document.documentElement.webkitRequestFullScreen || - global.document.documentElement.mozRequestFullScreen; + global.document.documentElement.requestFullscreen || + global.document.documentElement.webkitRequestFullscreen || + global.document.documentElement.mozRequestFullscreen || + global.document.documentElement.requestFullScreen || + global.document.documentElement.webkitRequestFullScreen || + global.document.documentElement.mozRequestFullScreen; if (requestFullscreen) { fullscreenWidth = window.screen.width; fullscreenHeight = window.screen.height; @@ -1210,7 +1254,6 @@ function enterFullscreen(global) { } } - /** * @param {Object} global */ @@ -1222,7 +1265,6 @@ function toggleFullscreen(global) { enterFullscreen(global); } - /** * Called when the fullscreen mode of the browser or content player changes. * @param {Object} global @@ -1232,7 +1274,10 @@ function onFullscreenChange(global) { if (adsManager) { // Resize the ad container adsManager.resize( - videoWidth, videoHeight, global.google.ima.ViewMode.NORMAL); + videoWidth, + videoHeight, + global.google.ima.ViewMode.NORMAL + ); adsManagerWidthOnLoad = null; adsManagerHeightOnLoad = null; } @@ -1246,8 +1291,10 @@ function onFullscreenChange(global) { if (adsManager) { // Resize the ad container adsManager.resize( - fullscreenWidth, fullscreenHeight, - global.google.ima.ViewMode.FULLSCREEN); + fullscreenWidth, + fullscreenHeight, + global.google.ima.ViewMode.FULLSCREEN + ); adsManagerWidthOnLoad = null; adsManagerHeightOnLoad = null; } @@ -1284,9 +1331,12 @@ export function showAdControls() { 'height': miniControls ? '18px' : '22px', }; setStyles(fullscreenDiv, buttonDefaults); - setStyles(muteUnmuteDiv, Object.assign(buttonDefaults, { - 'margin-right': '10px', - })); + setStyles( + muteUnmuteDiv, + Object.assign(buttonDefaults, { + 'margin-right': '10px', + }) + ); // show ad controls setStyle(countdownWrapperDiv, 'display', 'flex'); showControls(); @@ -1308,9 +1358,12 @@ export function resetControlsAfterAd() { }); const buttonDefaults = {'height': '30px'}; setStyles(fullscreenDiv, buttonDefaults); - setStyles(muteUnmuteDiv, Object.assign(buttonDefaults, { - 'margin-right': '20px', - })); + setStyles( + muteUnmuteDiv, + Object.assign(buttonDefaults, { + 'margin-right': '20px', + }) + ); // show non-ad controls const showElement = button => setStyle(button, 'display', 'block'); [playPauseDiv, timeDiv, progressBarWrapperDiv].forEach(showElement); @@ -1409,8 +1462,10 @@ function onMessage(global, event) { }); if (adsActive && !fullscreen) { adsManager.resize( - msg.args.width, msg.args.height, - global.google.ima.ViewMode.NORMAL); + msg.args.width, + msg.args.height, + global.google.ima.ViewMode.NORMAL + ); } else { adsManagerWidthOnLoad = msg.args.width; adsManagerHeightOnLoad = msg.args.height; @@ -1438,15 +1493,13 @@ function onMessage(global, event) { } } - /** * @param {!Object} data */ function postMessage(data) { - window.parent./*OK*/postMessage(data, '*'); + window.parent./*OK*/ postMessage(data, '*'); } - /** * Returns the properties we need to access for testing. * diff --git a/ads/google/test/test-adsense-amp-auto-ads-responsive.js b/ads/google/test/test-adsense-amp-auto-ads-responsive.js index 1bd42fdf3519d..49272b49a45ea 100644 --- a/ads/google/test/test-adsense-amp-auto-ads-responsive.js +++ b/ads/google/test/test-adsense-amp-auto-ads-responsive.js @@ -44,42 +44,65 @@ describes.realWin('adsense-amp-auto-ads-responsive', {}, env => { RANDOM_NUMBER_GENERATORS.accuratePrng = cachedAccuratePrng; }); - it('should pick the control branch when experiment on and amp-auto-ads ' + - 'tag present.', () => { - const ampAutoAdsEl = win.document.createElement('amp-auto-ads'); - win.document.body.appendChild(ampAutoAdsEl); - toggleExperiment( - win, ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, true); - RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.4); - expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)) - .to.equal(AdSenseAmpAutoAdsResponsiveBranches.CONTROL); - }); + it( + 'should pick the control branch when experiment on and amp-auto-ads ' + + 'tag present.', + () => { + const ampAutoAdsEl = win.document.createElement('amp-auto-ads'); + win.document.body.appendChild(ampAutoAdsEl); + toggleExperiment( + win, + ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, + true + ); + RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.4); + expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)).to.equal( + AdSenseAmpAutoAdsResponsiveBranches.CONTROL + ); + } + ); - it('should pick the experiment branch when experiment on and amp-auto-ads ' + - 'tag present.', () => { - const ampAutoAdsEl = win.document.createElement('amp-auto-ads'); - win.document.body.appendChild(ampAutoAdsEl); - toggleExperiment( - win, ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, true); - RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.6); - expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)) - .to.equal(AdSenseAmpAutoAdsResponsiveBranches.EXPERIMENT); - }); + it( + 'should pick the experiment branch when experiment on and amp-auto-ads ' + + 'tag present.', + () => { + const ampAutoAdsEl = win.document.createElement('amp-auto-ads'); + win.document.body.appendChild(ampAutoAdsEl); + toggleExperiment( + win, + ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, + true + ); + RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.6); + expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)).to.equal( + AdSenseAmpAutoAdsResponsiveBranches.EXPERIMENT + ); + } + ); it('should not pick a branch when experiment off.', () => { const ampAutoAdsEl = win.document.createElement('amp-auto-ads'); win.document.body.appendChild(ampAutoAdsEl); toggleExperiment( - win, ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, false); + win, + ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, + false + ); RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.4); expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)).to.be.null; }); - it('should not pick a branch when experiment on but no and amp-auto-ads ' + - 'tag present.', () => { - toggleExperiment( - win, ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, true); - RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.4); - expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)).to.be.null; - }); + it( + 'should not pick a branch when experiment on but no and amp-auto-ads ' + + 'tag present.', + () => { + toggleExperiment( + win, + ADSENSE_AMP_AUTO_ADS_RESPONSIVE_EXPERIMENT_NAME, + true + ); + RANDOM_NUMBER_GENERATORS.accuratePrng.onFirstCall().returns(0.4); + expect(getAdSenseAmpAutoAdsResponsiveExperimentBranch(win)).to.be.null; + } + ); }); diff --git a/ads/google/test/test-adsense.js b/ads/google/test/test-adsense.js index 402bf0a9aabb1..c78bf40a3323e 100644 --- a/ads/google/test/test-adsense.js +++ b/ads/google/test/test-adsense.js @@ -17,7 +17,6 @@ import {CONSENT_POLICY_STATE} from '../../../src/consent-state'; import {adsense} from '../adsense'; describes.realWin('adsenseDelayedFetch', {}, env => { - let containerElem, data; const canonicalUrl = 'https://foo.com?some=page'; const clientId = 'some_clientId'; @@ -42,7 +41,8 @@ describes.realWin('adsenseDelayedFetch', {}, env => { env.win.context = {canonicalUrl, clientId, pageViewId}; data = {}; Object.keys(elementAttributes).forEach( - attr => data[attr] = `some-${attr}`); + attr => (data[attr] = `some-${attr}`) + ); data['experimentId'] = '1234,567,890'; }); @@ -55,15 +55,20 @@ describes.realWin('adsenseDelayedFetch', {}, env => { }, }; adsense(env.win, data); - expect(env.win.document.querySelector('script[src=' + - '"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]')) - .to.be.ok; + expect( + env.win.document.querySelector( + 'script[src=' + + '"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]' + ) + ).to.be.ok; const insElement = env.win.document.querySelector('ins.adsbygoogle'); expect(insElement).to.be.ok; expect(insElement.getAttribute('data-page-url')).to.equal(canonicalUrl); Object.keys(elementAttributes).forEach(attr => - expect(insElement.getAttribute(elementAttributes[attr])) - .to.equal(`some-${attr}`)); + expect(insElement.getAttribute(elementAttributes[attr])).to.equal( + `some-${attr}` + ) + ); expect(pushArg).to.be.ok; expect(pushArg).to.jsonEqual({ params: { @@ -91,7 +96,8 @@ describes.realWin('adsenseDelayedFetch', {}, env => { data['height'] = '666'; allowConsoleError(() => { expect(() => adsense(env.win, data)).to.throw( - /Specified height 666 in tag is not equal to the required/); + /Specified height 666 in tag is not equal to the required/ + ); }); }); @@ -100,13 +106,12 @@ describes.realWin('adsenseDelayedFetch', {}, env => { data['height'] = '320'; allowConsoleError(() => { expect(() => adsense(env.win, data)).to.throw( - /Responsive AdSense ad units require the attribute data-full-width.​/ + /Responsive AdSense ad units require the attribute data-full-width.​/ ); }); }); describe('amp-consent integration', () => { - let pushCount = 0; beforeEach(() => { pushCount = 0; @@ -175,17 +180,17 @@ describes.realWin('adsenseDelayedFetch', {}, env => { if (result == RESULT_STATE.NO_REQUEST) { expect(insElement).to.not.be.ok; expect(pushCount).to.equal(0); - expect(env.win.adsbygoogle['requestNonPersonalizedAds']) - .to.be.undefined; + expect(env.win.adsbygoogle['requestNonPersonalizedAds']).to.be + .undefined; } else { expect(insElement).to.be.ok; expect(pushCount).to.equal(1); expect(env.win.adsbygoogle).to.be.ok; expect(env.win.adsbygoogle['requestNonPersonalizedAds']).to.equal( - result == RESULT_STATE.NPA ? true : undefined); + result == RESULT_STATE.NPA ? true : undefined + ); } }); } - }); }); diff --git a/ads/google/test/test-utils.js b/ads/google/test/test-utils.js index e8e249388d6b5..2818e1ce9d78d 100644 --- a/ads/google/test/test-utils.js +++ b/ads/google/test/test-utils.js @@ -35,93 +35,131 @@ describe('#getMultiSizeDimensions', () => { const multiSizeDataStr = '300x300,300x250,250x250,250x200,150x50'; function verifyArray(actual, lower, upper) { - expect(multiSizes.filter((size, index) => index < upper && index >= lower)) - .to.deep.equal(actual); + expect( + multiSizes.filter((size, index) => index < upper && index >= lower) + ).to.deep.equal(actual); } it('should return all sizes', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 300, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 300, + 300, + /* Ignore lowerbound */ false + ); verifyArray(actual, 0, multiSizes.length); }); it('should return a smaller array', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 250, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 300, + 250, + /* Ignore lowerbound */ false + ); verifyArray(actual, 1, multiSizes.length); }); it('should return an even smaller array', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 250, 250, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 250, + 250, + /* Ignore lowerbound */ false + ); verifyArray(actual, 2, multiSizes.length); }); it('should return an empty array', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 100, 50, - /* Ignore lowerbound */ false); + multiSizeDataStr, + 100, + 50, + /* Ignore lowerbound */ false + ); verifyArray(actual, 0, 0); }); it('should return a smaller array due to lowerbound', () => { const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 300, - /* Use lowerbound */ true); + multiSizeDataStr, + 300, + 300, + /* Use lowerbound */ true + ); verifyArray(actual, 0, multiSizes.length - 1); }); - it('should return a smaller array due to lowerbound + smaller primary size', - () => { - const actual = getMultiSizeDimensions( - multiSizeDataStr, 300, 250, - /* Use lowerbound */ true); - verifyArray(actual, 1, multiSizes.length - 1); - }); + it('should return a smaller array due to lowerbound + smaller primary size', () => { + const actual = getMultiSizeDimensions( + multiSizeDataStr, + 300, + 250, + /* Use lowerbound */ true + ); + verifyArray(actual, 1, multiSizes.length - 1); + }); it('should return all positive sizes', () => { const actual = getMultiSizeDimensions( - '-1x300,' + multiSizeDataStr, 300, 300, - /* Ignore lowerbound */ false); + '-1x300,' + multiSizeDataStr, + 300, + 300, + /* Ignore lowerbound */ false + ); verifyArray(actual, 0, multiSizes.length); }); it('should add dummy size for fluid', () => { - expect(getMultiSizeDimensions('fluid', 300, 300, /* useLowerBound */ false)) - .to.deep.equal([[320, 50]]); + expect( + getMultiSizeDimensions('fluid', 300, 300, /* useLowerBound */ false) + ).to.deep.equal([[320, 50]]); }); it('should not add dummy size for fluid if fluid is primary size', () => { - expect(getMultiSizeDimensions( - 'fluid', 300, 300, + expect( + getMultiSizeDimensions( + 'fluid', + 300, + 300, /* useLowerBound */ false, - /* isFluidPrimary */ true)) - .to.deep.equal([]); + /* isFluidPrimary */ true + ) + ).to.deep.equal([]); }); it('should allow fluid with fixed sizes', () => { - expect(getMultiSizeDimensions( - 'fluid,300x300', 300, 300, /* useLowerBound */ false)) - .to.deep.equal([[320, 50], [300, 300]]); - expect(getMultiSizeDimensions( - '300x300,fluid', 300, 300, /* useLowerBound */ false)) - .to.deep.equal([[300, 300], [320, 50]]); + expect( + getMultiSizeDimensions( + 'fluid,300x300', + 300, + 300, + /* useLowerBound */ false + ) + ).to.deep.equal([[320, 50], [300, 300]]); + expect( + getMultiSizeDimensions( + '300x300,fluid', + 300, + 300, + /* useLowerBound */ false + ) + ).to.deep.equal([[300, 300], [320, 50]]); }); }); describe('#getMatchedContentResponsiveHeightAndUpdatePubParams', () => { it('should use auto logic when no pub params present', () => { const element = document.createElement('div'); - expect(getMatchedContentResponsiveHeightAndUpdatePubParams(400, element)) - .to.equal(1472); + expect( + getMatchedContentResponsiveHeightAndUpdatePubParams(400, element) + ).to.equal(1472); expect(element.getAttribute(ExternalCorePubVars.ROWS_NUM)).to.equal('12'); expect(element.getAttribute(ExternalCorePubVars.COLUMNS_NUM)).to.equal('1'); - expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)) - .to.equal(LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE); + expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)).to.equal( + LayoutType.MOBILE_BANNER_IMAGE_SIDEBYSIDE + ); }); it('should use pub control logic when pub params present', () => { @@ -129,14 +167,17 @@ describe('#getMatchedContentResponsiveHeightAndUpdatePubParams', () => { element.setAttribute(ExternalCorePubVars.ROWS_NUM, '1,2'); element.setAttribute(ExternalCorePubVars.COLUMNS_NUM, '3,4'); element.setAttribute( - ExternalCorePubVars.UI_TYPE, - `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}`); + ExternalCorePubVars.UI_TYPE, + `${LayoutType.IMAGE_SIDEBYSIDE},${LayoutType.IMAGE_STACKED}` + ); - expect(getMatchedContentResponsiveHeightAndUpdatePubParams(800, element)) - .to.equal(382); + expect( + getMatchedContentResponsiveHeightAndUpdatePubParams(800, element) + ).to.equal(382); expect(element.getAttribute(ExternalCorePubVars.ROWS_NUM)).to.equal('2'); expect(element.getAttribute(ExternalCorePubVars.COLUMNS_NUM)).to.equal('4'); - expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)) - .to.equal(LayoutType.PUB_CONTROL_IMAGE_STACKED); + expect(element.getAttribute(ExternalCorePubVars.UI_TYPE)).to.equal( + LayoutType.PUB_CONTROL_IMAGE_STACKED + ); }); }); diff --git a/ads/google/utils.js b/ads/google/utils.js index b2d60a3394a47..b113cd90df735 100644 --- a/ads/google/utils.js +++ b/ads/google/utils.js @@ -47,8 +47,9 @@ export const DUMMY_FLUID_SIZE = '320x50'; * Required size to be sent with fluid requests in array format. * @const {!Array} */ -const DUMMY_FLUID_SIZE_ARR = - DUMMY_FLUID_SIZE.split('x').map(dim => Number(dim)); +const DUMMY_FLUID_SIZE_ARR = DUMMY_FLUID_SIZE.split('x').map(dim => + Number(dim) +); /** * Given the amp-ad data attribute containing the multi-size dimensions, and a @@ -71,13 +72,12 @@ export function getMultiSizeDimensions( primaryWidth, primaryHeight, multiSizeValidation, - isFluidPrimary = false) { - + isFluidPrimary = false +) { const dimensions = []; const arrayOfSizeStrs = multiSizeDataStr.split(','); for (let i = 0; i < arrayOfSizeStrs.length; i++) { - const sizeStr = arrayOfSizeStrs[i]; if (sizeStr.toLowerCase() == 'fluid') { if (!isFluidPrimary) { @@ -99,22 +99,43 @@ export function getMultiSizeDimensions( const height = Number(size[1]); // Make sure that both dimensions given are positive numbers. - if (!validateDimensions(width, height, + if ( + !validateDimensions( + width, + height, w => isNaN(w) || w <= 0, h => isNaN(h) || h <= 0, - badParams => badParams.map(badParam => - `Invalid ${badParam.dim} of ${badParam.val} ` + - 'given for secondary size.').join(' '))) { + badParams => + badParams + .map( + badParam => + `Invalid ${badParam.dim} of ${badParam.val} ` + + 'given for secondary size.' + ) + .join(' ') + ) + ) { continue; } // Check that secondary size is not larger than primary size. - if (!isFluidPrimary && !validateDimensions(width, height, + if ( + !isFluidPrimary && + !validateDimensions( + width, + height, w => w > primaryWidth, h => h > primaryHeight, - badParams => badParams.map(badParam => - `Secondary ${badParam.dim} ${badParam.val} ` + - `can't be larger than the primary ${badParam.dim}.`).join(' '))) { + badParams => + badParams + .map( + badParam => + `Secondary ${badParam.dim} ${badParam.val} ` + + `can't be larger than the primary ${badParam.dim}.` + ) + .join(' ') + ) + ) { continue; } @@ -126,13 +147,22 @@ export function getMultiSizeDimensions( const minRatio = 2 / 3; const minWidth = minRatio * primaryWidth; const minHeight = minRatio * primaryHeight; - if (!validateDimensions(width, height, + if ( + !validateDimensions( + width, + height, w => w < minWidth, h => h < minHeight, - badParams => badParams.map(badParam => - `Secondary ${badParam.dim} ${badParam.val} is ` + - `smaller than 2/3rds of the primary ${badParam.dim}.`) - .join(' '))) { + badParams => + badParams + .map( + badParam => + `Secondary ${badParam.dim} ${badParam.val} is ` + + `smaller than 2/3rds of the primary ${badParam.dim}.` + ) + .join(' ') + ) + ) { continue; } } @@ -162,8 +192,13 @@ export function getMultiSizeDimensions( * A function that will produce an informative error message. * @return {boolean} */ -function validateDimensions(width, height, widthCond, heightCond, errorBuilder) -{ +function validateDimensions( + width, + height, + widthCond, + heightCond, + errorBuilder +) { const badParams = []; if (widthCond(width)) { badParams.push({dim: 'width', val: width}); @@ -188,23 +223,29 @@ function validateDimensions(width, height, widthCond, heightCond, errorBuilder) * @return {number} height to use for the matched content slot. */ export function getMatchedContentResponsiveHeightAndUpdatePubParams( - availableWidth, element) { + availableWidth, + element +) { const pubControlParams = { numberOfRows: element.getAttribute(ExternalCorePubVars.ROWS_NUM), numberOfColumns: element.getAttribute(ExternalCorePubVars.COLUMNS_NUM), layoutType: element.getAttribute(ExternalCorePubVars.UI_TYPE), }; let config; - if (pubControlParams.numberOfRows || - pubControlParams.numberOfColumns || - pubControlParams.layoutType) { + if ( + pubControlParams.numberOfRows || + pubControlParams.numberOfColumns || + pubControlParams.layoutType + ) { // Publisher provided at least 1 param which means we are in // "pub controlled matched content" mode. config = getPubControlConfig(availableWidth, pubControlParams); } else { // Publisher didn't provide any matched content params so use auto mode. config = getAutoConfig( - availableWidth, availableWidth <= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP); + availableWidth, + availableWidth <= MIN_PUB_CONTROL_WIDTH_OF_DESKTOP + ); } if (config.validationError) { user().error('AMP-AD', config.validationError); diff --git a/ads/gumgum.js b/ads/gumgum.js index 8f229cd991919..615ac9a2f87dc 100644 --- a/ads/gumgum.js +++ b/ads/gumgum.js @@ -24,32 +24,29 @@ import {setStyles} from '../src/style'; export function gumgum(global, data) { validateData(data, ['zone', 'slot']); - const - win = window, - ctx = win.context, - dom = global.document.getElementById('c'), - ampWidth = parseInt(data.width || '0', 10), - ampHeight = parseInt(data.height || '0', 10), - ggevents = global.ggevents || []; + const win = window, + ctx = win.context, + dom = global.document.getElementById('c'), + ampWidth = parseInt(data.width || '0', 10), + ampHeight = parseInt(data.height || '0', 10), + ggevents = global.ggevents || []; - const - {max} = Math, - slotId = parseInt(data.slot, 10), - onLoad = function(type) { - return function(evt) { - const - ad = Object.assign({width: 0, height: 0}, evt.ad || {}), - identifier = ['GUMGUM', type, evt.id].join('_'); - ctx.reportRenderedEntityIdentifier(identifier); - ctx.renderStart({ - width: max(ampWidth, ad.width), - height: max(ampHeight, ad.height), - }); - }; - }, - noFill = function() { - ctx.noContentAvailable(); + const {max} = Math, + slotId = parseInt(data.slot, 10), + onLoad = function(type) { + return function(evt) { + const ad = Object.assign({width: 0, height: 0}, evt.ad || {}), + identifier = ['GUMGUM', type, evt.id].join('_'); + ctx.reportRenderedEntityIdentifier(identifier); + ctx.renderStart({ + width: max(ampWidth, ad.width), + height: max(ampHeight, ad.height), + }); }; + }, + noFill = function() { + ctx.noContentAvailable(); + }; // Ads logic starts global.ggv2id = data.zone; diff --git a/ads/holder.js b/ads/holder.js index e35519abde0aa..dc5e5c14ab21d 100644 --- a/ads/holder.js +++ b/ads/holder.js @@ -24,10 +24,10 @@ export function holder(global, data) { validateData(data, ['block'], []); const wcl = global.context.location; const n = navigator.userAgent; - let l = '&r' + Math.round((Math.random() * 10000000)) + '&h' + wcl.href; + let l = '&r' + Math.round(Math.random() * 10000000) + '&h' + wcl.href; if (!(n.indexOf('Safari') != -1 && n.indexOf('Chrome') == -1)) { l += '&c1'; } data.queue = l; - writeScript(global,'https://i.holder.com.ua/js2/holder/ajax/ampv1.js'); + writeScript(global, 'https://i.holder.com.ua/js2/holder/ajax/ampv1.js'); } diff --git a/ads/ibillboard.js b/ads/ibillboard.js index aa1be9434746a..93bec6106ff6c 100644 --- a/ads/ibillboard.js +++ b/ads/ibillboard.js @@ -29,7 +29,6 @@ const validHosts = [ * @param {!Object} data */ export function ibillboard(global, data) { - validateData(data, ['src']); const {src} = data; validateSrcPrefix(validHosts, src); diff --git a/ads/imedia.js b/ads/imedia.js index ef34f61d2453a..8da74128d79ef 100644 --- a/ads/imedia.js +++ b/ads/imedia.js @@ -36,41 +36,48 @@ export function imedia(global, data) { } mW.inPagePositions.push({parentElement, context: global.context}); - computeInMasterFrame(global, 'imedia-load', done => { - loadScript(global, 'https://i.imedia.cz/js/im3.js', () => { - if (global.im != null) { - mW.im = global.im; - mW.im.conf.referer = context.canonicalUrl; + computeInMasterFrame( + global, + 'imedia-load', + done => { + loadScript(global, 'https://i.imedia.cz/js/im3.js', () => { + if (global.im != null) { + mW.im = global.im; + mW.im.conf.referer = context.canonicalUrl; - // send request to get all ads - mW.im.getAds(positions, {AMPcallback: ads => { - mW.ads = ads; - done(null); - }}); - }}); - }, () => { - mW.inPagePositions = mW.inPagePositions.filter(inPagePostion => { - let used = true; - positions.filter((position, index) => { - - // match right element and zone to write advert from adserver - if (inPagePostion.parentElement.id == position.id) { - used = false; - position.id = inPagePostion.parentElement; // right element "c" to position obj. - if (mW.im.writeAd) { - mW.im.writeAd(mW.ads[index], position); + // send request to get all ads + mW.im.getAds(positions, { + AMPcallback: ads => { + mW.ads = ads; + done(null); + }, + }); + } + }); + }, + () => { + mW.inPagePositions = mW.inPagePositions.filter(inPagePostion => { + let used = true; + positions.filter((position, index) => { + // match right element and zone to write advert from adserver + if (inPagePostion.parentElement.id == position.id) { + used = false; + position.id = inPagePostion.parentElement; // right element "c" to position obj. + if (mW.im.writeAd) { + mW.im.writeAd(mW.ads[index], position); - // inform AMP runtime when the ad starts rendering - if (mW.ads[index].impress) { - inPagePostion.context.renderStart(); - } else { - inPagePostion.context.noContentAvailable(); + // inform AMP runtime when the ad starts rendering + if (mW.ads[index].impress) { + inPagePostion.context.renderStart(); + } else { + inPagePostion.context.noContentAvailable(); + } } + return false; } - return false; - } + }); + return used; // remove (filter) element filled with add }); - return used; // remove (filter) element filled with add - }); - }); + } + ); } diff --git a/ads/imonomy.js b/ads/imonomy.js index 26b4d7d513751..c92969e029bf0 100644 --- a/ads/imonomy.js +++ b/ads/imonomy.js @@ -33,14 +33,17 @@ export function imonomy(global, data) { if (!('slot' in data)) { global.CasaleArgs = data; writeScript(global, `//tag.imonomy.com/${data.pid}/indexJTag.js`); - } else { //DFP ad request call + } else { + //DFP ad request call let calledDoubleclick = false; data.timeout = isNaN(data.timeout) ? DEFAULT_TIMEOUT : data.timeout; const timer = setTimeout(() => { callDoubleclick(EVENT_TIMEOUT); }, data.timeout); const callDoubleclick = function(code) { - if (calledDoubleclick) { return; } + if (calledDoubleclick) { + return; + } calledDoubleclick = true; clearTimeout(timer); reportStats(data, code); @@ -60,11 +63,15 @@ export function imonomy(global, data) { }; loadScript( - global, `//tag.imonomy.com/amp/${data.pid}/amp.js`, () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + global, + `//tag.imonomy.com/amp/${data.pid}/amp.js`, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } } @@ -87,7 +94,9 @@ function prepareData(data) { */ function reportStats(data, code) { try { - if (code == EVENT_BADTAG) { return; } + if (code == EVENT_BADTAG) { + return; + } const xhttp = new XMLHttpRequest(); xhttp.withCredentials = true; let unitFormat = ''; @@ -96,19 +105,19 @@ function reportStats(data, code) { pageLocation = encodeURIComponent(window.context.location.href); } const {subId, pid} = data, - trackId = 'AMP', - notFirst = true, - cid = '', - abLabel = '', - rand = Math.random(); + trackId = 'AMP', + notFirst = true, + cid = '', + abLabel = '', + rand = Math.random(); if (!isNaN(data.width) && !isNaN(data.height)) { unitFormat = `${data.width}x${data.height}`; } const uid = '', - isLocked = false, - isTrackable = false, - isClient = false, - tier = 0; + isLocked = false, + isTrackable = false, + isClient = false, + tier = 0; const baseUrl = '//srv.imonomy.com/internal/reporter'; let unitCodeUrl = `${baseUrl}?v=2&subid=${subId}&sid=${pid}&`; unitCodeUrl = unitCodeUrl + `format=${unitFormat}&ai=`; @@ -134,6 +143,5 @@ function reportStats(data, code) { xhttp.open('GET', unitCodeUrl, true); xhttp.setRequestHeader('Content-Type', 'application/json'); xhttp.send(); - } catch (e) {} } diff --git a/ads/improvedigital.js b/ads/improvedigital.js index 8452f26ec981c..16419918b010c 100755 --- a/ads/improvedigital.js +++ b/ads/improvedigital.js @@ -23,13 +23,19 @@ import {validateData, writeScript} from '../3p/3p'; export function improvedigital(global, data) { validateData(data, ['placement'], ['width', 'height', 'optin', 'keyvalue']); - let url = 'https://ad.360yield.com' + - '/adj?' + - 'p=' + encodeURIComponent(data.placement) + - '&w=' + encodeURIComponent(data.width) + - '&h=' + encodeURIComponent(data.height) + - '&optin=' + encodeURIComponent(data.optin) + - '&tz=' + new Date().getTimezoneOffset(); + let url = + 'https://ad.360yield.com' + + '/adj?' + + 'p=' + + encodeURIComponent(data.placement) + + '&w=' + + encodeURIComponent(data.width) + + '&h=' + + encodeURIComponent(data.height) + + '&optin=' + + encodeURIComponent(data.optin) + + '&tz=' + + new Date().getTimezoneOffset(); const value = data.keyvalue; let newData = ''; @@ -38,11 +44,12 @@ export function improvedigital(global, data) { if (value && value.length > 0) { const keys = value.split('&'); - for (let i = 0; i < keys.length; i++) - { - if (!keys[i]) {continue;} + for (let i = 0; i < keys.length; i++) { + if (!keys[i]) { + continue; + } const segment = keys[i].split('='); - const segment1 = (segment[1] ? encodeURIComponent(segment[1]) : ''); + const segment1 = segment[1] ? encodeURIComponent(segment[1]) : ''; if (validKey > 0) { newData += amps; } diff --git a/ads/inabox/frame-overlay-helper.js b/ads/inabox/frame-overlay-helper.js index 4752def46b133..3f3eadb1f8f6b 100644 --- a/ads/inabox/frame-overlay-helper.js +++ b/ads/inabox/frame-overlay-helper.js @@ -26,7 +26,6 @@ import { import {resetStyles, setImportantStyles} from '../../src/style'; import {restrictedVsync, timer} from './util'; - const CENTER_TRANSITION_TIME_MS = 150; const CENTER_TRANSITION_END_WAIT_TIME_MS = 50; @@ -38,38 +37,47 @@ const CENTER_TRANSITION_END_WAIT_TIME_MS = 50; * @private */ const expandFrameImpl = function(win, iframe, onFinish) { - restrictedVsync(win, { - measure(state) { - state.viewportSize = { - width: win./*OK*/innerWidth, - height: win./*OK*/innerHeight, - }; - state.rect = layoutRectFromDomRect(iframe./*OK*/getBoundingClientRect()); + restrictedVsync( + win, + { + measure(state) { + state.viewportSize = { + width: win./*OK*/ innerWidth, + height: win./*OK*/ innerHeight, + }; + state.rect = layoutRectFromDomRect( + iframe./*OK*/ getBoundingClientRect() + ); + }, + mutate(state) { + const {width, height} = state.viewportSize; + const expandedRect = layoutRectLtwh(0, 0, width, height); + + centerFrameUnderVsyncMutate( + iframe, + state.rect, + state.viewportSize, + CENTER_TRANSITION_TIME_MS + ); + + // To prevent double click during transition; + setImportantStyles(iframe, {'pointer-events': 'none'}); + + timer(() => { + restrictedVsync(win, { + mutate() { + resetStyles(iframe, ['pointer-events']); + expandFrameUnderVsyncMutate(iframe); + onFinish(state.rect, expandedRect); + }, + }); + }, CENTER_TRANSITION_TIME_MS + CENTER_TRANSITION_END_WAIT_TIME_MS); + }, }, - mutate(state) { - const {width, height} = state.viewportSize; - const expandedRect = layoutRectLtwh(0, 0, width, height); - - centerFrameUnderVsyncMutate(iframe, state.rect, state.viewportSize, - CENTER_TRANSITION_TIME_MS); - - // To prevent double click during transition; - setImportantStyles(iframe, {'pointer-events': 'none'}); - - timer(() => { - restrictedVsync(win, { - mutate() { - resetStyles(iframe, ['pointer-events']); - expandFrameUnderVsyncMutate(iframe); - onFinish(state.rect, expandedRect); - }, - }); - }, CENTER_TRANSITION_TIME_MS + CENTER_TRANSITION_END_WAIT_TIME_MS); - }, - }, {}); + {} + ); }; - /** * Resets the frame from full overlay mode. * @param {!Window} win Host window. @@ -89,14 +97,14 @@ const collapseFrameImpl = function(win, iframe, onFinish, onMeasure) { restrictedVsync(win, { measure() { onMeasure( - layoutRectFromDomRect(iframe./*OK*/getBoundingClientRect())); + layoutRectFromDomRect(iframe./*OK*/ getBoundingClientRect()) + ); }, }); }, }); }; - /** * Places the child frame in full overlay mode. * @param {!Window} win Host window. @@ -105,7 +113,6 @@ const collapseFrameImpl = function(win, iframe, onFinish, onMeasure) { */ export let expandFrame = expandFrameImpl; - /** * @param {!Function} implFn * @visibleForTesting @@ -114,7 +121,6 @@ export function stubExpandFrameForTesting(implFn) { expandFrame = implFn; } - /** * @visibleForTesting */ @@ -122,7 +128,6 @@ export function resetExpandFrameForTesting() { expandFrame = expandFrameImpl; } - /** * Places the child frame in full overlay mode. * @param {!Window} win Host window. @@ -132,7 +137,6 @@ export function resetExpandFrameForTesting() { */ export let collapseFrame = collapseFrameImpl; - /** * @param {!Function} implFn * @visibleForTesting @@ -141,7 +145,6 @@ export function stubCollapseFrameForTesting(implFn) { collapseFrame = implFn; } - /** * @visibleForTesting */ diff --git a/ads/inabox/frame-overlay-manager.js b/ads/inabox/frame-overlay-manager.js index 58105f872e5a5..77683409567b8 100644 --- a/ads/inabox/frame-overlay-manager.js +++ b/ads/inabox/frame-overlay-manager.js @@ -16,12 +16,10 @@ import {collapseFrame, expandFrame} from './frame-overlay-helper'; - /** * Inabox host manager for full overlay frames. */ export class FrameOverlayManager { - /** * @param {!Window} win */ @@ -82,18 +80,23 @@ export class FrameOverlayManager { // We know what the collapsed box was. If the viewport has not changed while // expanded, we can immediately notify the consumer of the collapsed // box rect since it should be the same. Otherwise, we wait for remeasure. - collapseFrame(this.win_, iframe, () => { - this.isExpanded_ = false; + collapseFrame( + this.win_, + iframe, + () => { + this.isExpanded_ = false; - if (!this.viewportChangedSinceExpand_) { - callback(this.collapsedRect_); - } - }, collapsedRect => { - this.collapsedRect_ = collapsedRect; + if (!this.viewportChangedSinceExpand_) { + callback(this.collapsedRect_); + } + }, + collapsedRect => { + this.collapsedRect_ = collapsedRect; - if (this.viewportChangedSinceExpand_) { - callback(this.collapsedRect_); + if (this.viewportChangedSinceExpand_) { + callback(this.collapsedRect_); + } } - }); + ); } } diff --git a/ads/inabox/inabox-host.js b/ads/inabox/inabox-host.js index becb491d54d44..aa8b5535195e8 100644 --- a/ads/inabox/inabox-host.js +++ b/ads/inabox/inabox-host.js @@ -69,13 +69,13 @@ export class InaboxHost { win.AMP[INABOX_UNREGISTER_IFRAME] = host.unregisterIframe.bind(host); } const queuedMsgs = win[PENDING_MESSAGES]; - const processMessageFn = /** @type function(Event)*/(evt => { + const processMessageFn = /** @type function(Event)*/ evt => { try { host.processMessage(evt); } catch (err) { dev().error(TAG, 'Error processing inabox message', evt, err); } - }); + }; if (queuedMsgs) { if (Array.isArray(queuedMsgs)) { queuedMsgs.forEach(message => { @@ -107,9 +107,10 @@ export class InaboxHost { function validateMessage(message) { const valid = !!(message.source && message.source.postMessage); if (!valid) { - user().error(TAG, - 'Missing message.source. message.data=' - + JSON.stringify(getData(message))); + user().error( + TAG, + 'Missing message.source. message.data=' + JSON.stringify(getData(message)) + ); } return valid; } diff --git a/ads/inabox/inabox-messaging-host.js b/ads/inabox/inabox-messaging-host.js index 537082e284da4..9353ef12f1bcb 100644 --- a/ads/inabox/inabox-messaging-host.js +++ b/ads/inabox/inabox-messaging-host.js @@ -34,7 +34,6 @@ const READ_ONLY_MESSAGES = [MessageType.SEND_POSITIONS]; /** Simple helper for named callbacks. */ class NamedObservable { - /** * Creates an instance of NamedObservable. */ @@ -76,7 +75,6 @@ class NamedObservable { let AdFrameDef; export class InaboxMessagingHost { - /** * @param {!Window} win * @param {!Array} iframes @@ -98,13 +96,19 @@ export class InaboxMessagingHost { this.frameOverlayManager_ = new FrameOverlayManager(win); this.msgObservable_.listen( - MessageType.SEND_POSITIONS, this.handleSendPositions_); + MessageType.SEND_POSITIONS, + this.handleSendPositions_ + ); this.msgObservable_.listen( - MessageType.FULL_OVERLAY_FRAME, this.handleEnterFullOverlay_); + MessageType.FULL_OVERLAY_FRAME, + this.handleEnterFullOverlay_ + ); this.msgObservable_.listen( - MessageType.CANCEL_FULL_OVERLAY_FRAME, this.handleCancelFullOverlay_); + MessageType.CANCEL_FULL_OVERLAY_FRAME, + this.handleCancelFullOverlay_ + ); } /** @@ -125,24 +129,29 @@ export class InaboxMessagingHost { return false; } - const adFrame = - this.getFrameElement_(message.source, request['sentinel']); + const adFrame = this.getFrameElement_(message.source, request['sentinel']); if (!adFrame) { dev().info(TAG, 'Ignored message from untrusted iframe:', message); return false; } const allowedTypes = adFrame.iframe.dataset['ampAllowed']; - const allowedTypesList = allowedTypes ? - allowedTypes.split(/\s*,\s*/) : - READ_ONLY_MESSAGES; + const allowedTypesList = allowedTypes + ? allowedTypes.split(/\s*,\s*/) + : READ_ONLY_MESSAGES; if (allowedTypesList.indexOf(request['type']) === -1) { dev().info(TAG, 'Ignored non-whitelisted message type:', message); return false; } - if (!this.msgObservable_.fire(request['type'], this, - [adFrame.measurableFrame, request, message.source, message.origin])) { + if ( + !this.msgObservable_.fire(request['type'], this, [ + adFrame.measurableFrame, + request, + message.source, + message.origin, + ]) + ) { dev().warn(TAG, 'Unprocessed AMP message:', message); return false; } @@ -160,16 +169,27 @@ export class InaboxMessagingHost { handleSendPositions_(iframe, request, source, origin) { const viewportRect = this.positionObserver_.getViewportRect(); const targetRect = this.positionObserver_.getTargetRect(iframe); - this.sendPosition_(request, source, origin, dict({ - 'viewportRect': viewportRect, - 'targetRect': targetRect, - })); + this.sendPosition_( + request, + source, + origin, + dict({ + 'viewportRect': viewportRect, + 'targetRect': targetRect, + }) + ); devAssert(this.iframeMap_[request.sentinel]); this.iframeMap_[request.sentinel].observeUnregisterFn = - this.iframeMap_[request.sentinel].observeUnregisterFn || - this.positionObserver_.observe(iframe, data => - this.sendPosition_(request, source, origin, /** @type ?JsonObject */(data))); + this.iframeMap_[request.sentinel].observeUnregisterFn || + this.positionObserver_.observe(iframe, data => + this.sendPosition_( + request, + source, + origin, + /** @type ?JsonObject */ data + ) + ); return true; } @@ -182,9 +202,10 @@ export class InaboxMessagingHost { */ sendPosition_(request, source, origin, data) { dev().fine(TAG, 'Sent position data to [%s] %s', request.sentinel, data); - source./*OK*/postMessage( - serializeMessage(MessageType.POSITION, request.sentinel, data), - origin); + source./*OK*/ postMessage( + serializeMessage(MessageType.POSITION, request.sentinel, data), + origin + ); } /** @@ -199,15 +220,17 @@ export class InaboxMessagingHost { */ handleEnterFullOverlay_(iframe, request, source, origin) { this.frameOverlayManager_.expandFrame(iframe, boxRect => { - source./*OK*/postMessage( - serializeMessage( - MessageType.FULL_OVERLAY_FRAME_RESPONSE, - request.sentinel, - dict({ - 'success': true, - 'boxRect': boxRect, - })), - origin); + source./*OK*/ postMessage( + serializeMessage( + MessageType.FULL_OVERLAY_FRAME_RESPONSE, + request.sentinel, + dict({ + 'success': true, + 'boxRect': boxRect, + }) + ), + origin + ); }); return true; @@ -222,15 +245,17 @@ export class InaboxMessagingHost { */ handleCancelFullOverlay_(iframe, request, source, origin) { this.frameOverlayManager_.collapseFrame(iframe, boxRect => { - source./*OK*/postMessage( - serializeMessage( - MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, - request.sentinel, - dict({ - 'success': true, - 'boxRect': boxRect, - })), - origin); + source./*OK*/ postMessage( + serializeMessage( + MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, + request.sentinel, + dict({ + 'success': true, + 'boxRect': boxRect, + }) + ), + origin + ); }); return true; @@ -271,8 +296,11 @@ export class InaboxMessagingHost { const measurableWin = measurableFrame.contentWindow; for (let i = 0; i < this.iframes_.length; i++) { const iframe = this.iframes_[i]; - for (let j = 0, tempWin = measurableWin; - j < 10; j++, tempWin = tempWin.parent) { + for ( + let j = 0, tempWin = measurableWin; + j < 10; + j++, tempWin = tempWin.parent + ) { if (iframe.contentWindow == tempWin) { this.iframeMap_[sentinel] = {iframe, measurableFrame}; return this.iframeMap_[sentinel]; @@ -304,9 +332,11 @@ export class InaboxMessagingHost { // hierarchy. If win is not nested within x-domain framing, then // this loop breaks immediately. let topXDomainWin; - for (let j = 0, tempWin = win; + for ( + let j = 0, tempWin = win; j < 10 && tempWin != tempWin.top && !canInspectWindow(tempWin); - j++, topXDomainWin = tempWin, tempWin = tempWin.parent) {} + j++, topXDomainWin = tempWin, tempWin = tempWin.parent + ) {} // If topXDomainWin exists, we know that the frame we want to measure // is a x-domain frame. Unfortunately, you can not access properties // on a x-domain window, so we can not do window.frameElement, and @@ -314,18 +344,20 @@ export class InaboxMessagingHost { // over that parent's child iframes until we find the frame element // that corresponds to topXDomainWin. if (!!topXDomainWin) { - const iframes = - topXDomainWin.parent.document.querySelectorAll('iframe'); - for (let k = 0, frame = iframes[k]; k < iframes.length; - k++, frame = iframes[k]) { + const iframes = topXDomainWin.parent.document.querySelectorAll('iframe'); + for ( + let k = 0, frame = iframes[k]; + k < iframes.length; + k++, frame = iframes[k] + ) { if (frame.contentWindow == topXDomainWin) { - return /** @type {!HTMLIFrameElement} */(frame); + return /** @type {!HTMLIFrameElement} */ (frame); } } } // If topXDomainWin does not exist, then win is friendly, and we can // just return its frameElement directly. - return /** @type {!HTMLIFrameElement} */(win.frameElement); + return /** @type {!HTMLIFrameElement} */ (win.frameElement); } /** diff --git a/ads/inabox/position-observer.js b/ads/inabox/position-observer.js index 0247de9917485..8f069cbd1ef39 100644 --- a/ads/inabox/position-observer.js +++ b/ads/inabox/position-observer.js @@ -35,7 +35,6 @@ let PositionEntryDef; const MIN_EVENT_INTERVAL_IN_MS = 100; export class PositionObserver { - /** * @param {!Window} win */ @@ -60,10 +59,14 @@ export class PositionObserver { observe(element, callback) { if (!this.positionObservable_) { this.positionObservable_ = new Observable(); - const listener = throttle(this.win_, () => { - this.update_(); - this.positionObservable_.fire(); - }, MIN_EVENT_INTERVAL_IN_MS); + const listener = throttle( + this.win_, + () => { + this.update_(); + this.positionObservable_.fire(); + }, + MIN_EVENT_INTERVAL_IN_MS + ); this.update_(); this.win_.addEventListener('scroll', listener, true); this.win_.addEventListener('resize', listener, true); @@ -89,7 +92,7 @@ export class PositionObserver { */ getPositionEntry_(element) { return { - viewportRect: /** @type {!LayoutRectDef} */(this.viewportRect_), + viewportRect: /** @type {!LayoutRectDef} */ (this.viewportRect_), // relative position to viewport targetRect: this.getTargetRect(element), }; @@ -101,15 +104,16 @@ export class PositionObserver { getViewportRect() { const {scrollingElement_: scrollingElement, win_: win} = this; - const scrollLeft = scrollingElement./*OK*/scrollLeft || - win./*OK*/pageXOffset; - const scrollTop = scrollingElement./*OK*/scrollTop || - win./*OK*/pageYOffset; + const scrollLeft = + scrollingElement./*OK*/ scrollLeft || win./*OK*/ pageXOffset; + const scrollTop = + scrollingElement./*OK*/ scrollTop || win./*OK*/ pageYOffset; return layoutRectLtwh( - Math.round(scrollLeft), - Math.round(scrollTop), - win./*OK*/innerWidth, - win./*OK*/innerHeight); + Math.round(scrollLeft), + Math.round(scrollTop), + win./*OK*/ innerWidth, + win./*OK*/ innerHeight + ); } /** @@ -122,16 +126,23 @@ export class PositionObserver { * @return {!LayoutRectDef} */ getTargetRect(element) { - let targetRect = - layoutRectFromDomRect(element./*OK*/getBoundingClientRect()); + let targetRect = layoutRectFromDomRect( + element./*OK*/ getBoundingClientRect() + ); const parentWin = element.ownerDocument.defaultView; - for (let j = 0, tempWin = parentWin; + for ( + let j = 0, tempWin = parentWin; j < 10 && tempWin != this.win_ && tempWin != this.win_.top; - j++, tempWin = tempWin.parent) { + j++, tempWin = tempWin.parent + ) { const parentFrameRect = layoutRectFromDomRect( - tempWin.frameElement./*OK*/getBoundingClientRect()); - targetRect = moveLayoutRect(targetRect, - parentFrameRect.left, parentFrameRect.top); + tempWin.frameElement./*OK*/ getBoundingClientRect() + ); + targetRect = moveLayoutRect( + targetRect, + parentFrameRect.left, + parentFrameRect.top + ); } return targetRect; } @@ -143,16 +154,18 @@ export class PositionObserver { */ function getScrollingElement(win) { const doc = win.document; - if (doc./*OK*/scrollingElement) { - return doc./*OK*/scrollingElement; + if (doc./*OK*/ scrollingElement) { + return doc./*OK*/ scrollingElement; } - if (doc.body - // Due to https://bugs.webkit.org/show_bug.cgi?id=106133, WebKit - // browsers have to use `body` and NOT `documentElement` for - // scrolling purposes. This has mostly being resolved via - // `scrollingElement` property, but this branch is still necessary - // for backward compatibility purposes. - && isWebKit(win.navigator.userAgent)) { + if ( + doc.body && + // Due to https://bugs.webkit.org/show_bug.cgi?id=106133, WebKit + // browsers have to use `body` and NOT `documentElement` for + // scrolling purposes. This has mostly being resolved via + // `scrollingElement` property, but this branch is still necessary + // for backward compatibility purposes. + isWebKit(win.navigator.userAgent) + ) { return doc.body; } return doc.documentElement; diff --git a/ads/inabox/util.js b/ads/inabox/util.js index c8aaea5a4893f..daf2ebdd1c68f 100644 --- a/ads/inabox/util.js +++ b/ads/inabox/util.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** * Executes a "restricted" read/write vsync cycle. * This function exists mainly since the vsync service is not available for the @@ -42,7 +41,6 @@ export function restrictedVsync(win, task, opt_state) { }); } - /** * Executes a function after a certain time. * The timer service is not available for the inabox host script, hence this diff --git a/ads/inmobi.js b/ads/inmobi.js index 063b027685c97..77f4e9d64c23d 100644 --- a/ads/inmobi.js +++ b/ads/inmobi.js @@ -22,7 +22,7 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function inmobi(global, data) { - validateData(data, ['siteid','slotid'], []); + validateData(data, ['siteid', 'slotid'], []); const inmobiConf = { siteid: data.siteid, @@ -40,7 +40,7 @@ export function inmobi(global, data) { }; writeScript(global, 'https://cf.cdn.inmobi.com/ad/inmobi.secure.js', () => { - global.document.write('
'); + global.document.write("
"); global._inmobi.getNewAd(document.getElementById('my-ad-slot'), inmobiConf); }); } diff --git a/ads/innity.js b/ads/innity.js index 0006d9d0a6ba8..26c1b88f95b5d 100644 --- a/ads/innity.js +++ b/ads/innity.js @@ -24,9 +24,15 @@ export function innity(global, data) { validateData(data, ['pub', 'zone'], ['channel']); writeScript(global, 'https://cdn.innity.net/admanager.js', () => { const innityAMPZone = global.innity_adZone; - const innityAMPTag = new innityAMPZone(encodeURIComponent(data.pub), - encodeURIComponent(data.zone), {width: data.width, height: data.height, - channel: data.channel ? encodeURIComponent(data.channel) : ''}); + const innityAMPTag = new innityAMPZone( + encodeURIComponent(data.pub), + encodeURIComponent(data.zone), + { + width: data.width, + height: data.height, + channel: data.channel ? encodeURIComponent(data.channel) : '', + } + ); // AMP handling or noContentAvailable innityAMPTag.amp(global.context); // else renderStart (with at least house ad) diff --git a/ads/ix.js b/ads/ix.js index 6677ce90ddd4e..1089899dfaf70 100644 --- a/ads/ix.js +++ b/ads/ix.js @@ -32,7 +32,8 @@ export function ix(global, data) { if (!('slot' in data)) { global.CasaleArgs = data; writeScript(global, 'https://js-sec.indexww.com/indexJTag.js'); - } else { //DFP ad request call + } else { + //DFP ad request call const start = Date.now(); let calledDoubleclick = false; @@ -42,7 +43,9 @@ export function ix(global, data) { }, data.ixTimeout); const callDoubleclick = function(code) { - if (calledDoubleclick) { return; } + if (calledDoubleclick) { + return; + } calledDoubleclick = true; clearTimeout(timer); reportStats(data.ixId, data.ixSlot, data.slot, start, code); @@ -61,9 +64,14 @@ export function ix(global, data) { ampError: EVENT_ERROR, }; - loadScript(global, 'https://js-sec.indexww.com/apl/amp.js', undefined, () => { - callDoubleclick(EVENT_ERROR); - }); + loadScript( + global, + 'https://js-sec.indexww.com/apl/amp.js', + undefined, + () => { + callDoubleclick(EVENT_ERROR); + } + ); } } @@ -89,13 +97,15 @@ function prepareData(data) { */ function reportStats(siteID, slotID, dfpSlot, start, code) { try { - if (code == EVENT_BADTAG) { return; } + if (code == EVENT_BADTAG) { + return; + } const xhttp = new XMLHttpRequest(); xhttp.withCredentials = true; const deltat = Date.now() - start; - const ts = start / 1000 >> 0; - const ets = Date.now() / 1000 >> 0; + const ts = (start / 1000) >> 0; + const ets = (Date.now() / 1000) >> 0; let url = 'https://as-sec.casalemedia.com/headerstats?s=' + siteID; if (typeof window.context.location.href !== 'undefined') { url += '&u=' + encodeURIComponent(window.context.location.href); @@ -112,7 +122,7 @@ function reportStats(siteID, slotID, dfpSlot, start, code) { stats += '"n":"amp-e",'; } stats += '"v":"' + deltat + '",'; - stats += '"b": "INDX","x": "' + dfpSlot.substring(0,64) + '"}]}]}'; + stats += '"b": "INDX","x": "' + dfpSlot.substring(0, 64) + '"}]}]}'; xhttp.open('POST', url, true); xhttp.setRequestHeader('Content-Type', 'application/json'); diff --git a/ads/jubna.js b/ads/jubna.js index d88b71eebf71f..8661f0cb855a0 100644 --- a/ads/jubna.js +++ b/ads/jubna.js @@ -17,9 +17,9 @@ import {loadScript, validateData} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function jubna(global, data) { validateData(data, ['wid', 'pid']); global._jubna = global._jubna || { diff --git a/ads/kargo.js b/ads/kargo.js index edb7ef160bc25..75a7226f6f871 100644 --- a/ads/kargo.js +++ b/ads/kargo.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - computeInMasterFrame, - loadScript, - validateData, -} from '../3p/3p'; +import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; /** * @param {!Window} global @@ -30,7 +26,8 @@ export function kargo(global, data) { validateData(data, ['site', 'slot'], ['options']); // Kargo AdTag url - const kargoScriptUrl = 'https://storage.cloud.kargo.com/ad/network/tag/v3/' + data.site + '.js'; + const kargoScriptUrl = + 'https://storage.cloud.kargo.com/ad/network/tag/v3/' + data.site + '.js'; // parse extra ad call options (optional) let options = {}; @@ -43,28 +40,33 @@ export function kargo(global, data) { // Add window source reference to ad options options.source_window = global; - computeInMasterFrame(global, 'kargo-load', function(done) { - // load AdTag in Master window - loadScript(this, kargoScriptUrl, () => { - let success = false; - if (this.Kargo != null && this.Kargo.loaded) { - success = true; - } + computeInMasterFrame( + global, + 'kargo-load', + function(done) { + // load AdTag in Master window + loadScript(this, kargoScriptUrl, () => { + let success = false; + if (this.Kargo != null && this.Kargo.loaded) { + success = true; + } - done(success); - }); - }, success => { - if (success) { - const w = options.source_window; + done(success); + }); + }, + success => { + if (success) { + const w = options.source_window; - // Add reference to Kargo api to this window if it's not the Master window - if (!w.context.isMaster) { - w.Kargo = w.context.master.Kargo; - } + // Add reference to Kargo api to this window if it's not the Master window + if (!w.context.isMaster) { + w.Kargo = w.context.master.Kargo; + } - w.Kargo.getAd(data.slot, options); - } else { - throw new Error('Kargo AdTag failed to load'); + w.Kargo.getAd(data.slot, options); + } else { + throw new Error('Kargo AdTag failed to load'); + } } - }); + ); } diff --git a/ads/kiosked.js b/ads/kiosked.js index 2aa5d57c06d6a..235df8a09fab5 100644 --- a/ads/kiosked.js +++ b/ads/kiosked.js @@ -27,14 +27,24 @@ export function kiosked(global, data) { if (hasOwn(data, 'scriptid')) { scriptId = data['scriptid']; } - window.addEventListener('kioskedAdRender', function() { - global.context.renderStart(); - }, false); + window.addEventListener( + 'kioskedAdRender', + function() { + global.context.renderStart(); + }, + false + ); - window.addEventListener('kioskedAdNoFill', function() { - global.context.noContentAvailable(); - }, false); - - writeScript(global, 'https://scripts.kiosked.com/loader/kiosked-ad.js?staticTagId=' + scriptId); + window.addEventListener( + 'kioskedAdNoFill', + function() { + global.context.noContentAvailable(); + }, + false + ); + writeScript( + global, + 'https://scripts.kiosked.com/loader/kiosked-ad.js?staticTagId=' + scriptId + ); } diff --git a/ads/kixer.js b/ads/kixer.js index a8cbdf59516f5..3ef34177e9549 100644 --- a/ads/kixer.js +++ b/ads/kixer.js @@ -54,11 +54,13 @@ export function kixer(global, data) { const kxviewCheck = function(intersectionEntry) { inView = intersectionEntry.intersectionRatio > 0.5; // Half of the unit is in the viewport if (inView) { - if (!viewed && viewTimer == null) { // If the ad hasn't been viewed and the timer is not set + if (!viewed && viewTimer == null) { + // If the ad hasn't been viewed and the timer is not set viewTimer = setTimeout(kxviewFire, 900); // Set a Timeout to check the ad in 900ms and fire the view } } else { - if (viewTimer) { // If the Timeout is set + if (viewTimer) { + // If the Timeout is set clearTimeout(viewTimer); // Clear the Timeout viewTimer = null; } @@ -66,7 +68,8 @@ export function kixer(global, data) { }; const kxviewFire = function() { - if (inView) { // if the ad is still in the viewport + if (inView) { + // if the ad is still in the viewport if (typeof __kx_viewability.process_locked === 'function') { viewed = true; __kx_viewability.process_locked(data.adslot); // Fire kixer view diff --git a/ads/kuadio.js b/ads/kuadio.js index 0c0f9aec7456c..94f13ea47142c 100644 --- a/ads/kuadio.js +++ b/ads/kuadio.js @@ -21,8 +21,11 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function kuadio(global, data) { - validateData(data, ['widgetId'], - ['region', 'baseUrl', 'betaMode', 'debugMode', 'fastParse', 'ref']); + validateData( + data, + ['widgetId'], + ['region', 'baseUrl', 'betaMode', 'debugMode', 'fastParse', 'ref'] + ); global._pvmax = { region: data.region, diff --git a/ads/lockerdome.js b/ads/lockerdome.js index 4058d18d676b8..e15c76077991f 100644 --- a/ads/lockerdome.js +++ b/ads/lockerdome.js @@ -17,9 +17,9 @@ import {loadScript, validateData} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function lockerdome(global, data) { validateData(data, ['slot']); global.SLOT = data.slot; diff --git a/ads/mantis.js b/ads/mantis.js index 9c52a19e90515..5712ea12522b6 100644 --- a/ads/mantis.js +++ b/ads/mantis.js @@ -24,9 +24,13 @@ export function mantisDisplay(global, data) { validateData(data, ['property', 'zone'], []); global.mantis = global.mantis || []; - global.mantis.push(['display', 'load', { - property: data['property'], - }]); + global.mantis.push([ + 'display', + 'load', + { + property: data['property'], + }, + ]); const d = global.document.createElement('div'); d.setAttribute('data-mantis-zone', data['zone']); @@ -43,11 +47,15 @@ export function mantisRecommend(global, data) { validateData(data, ['property'], ['css']); global.mantis = global.mantis || []; - global.mantis.push(['recommend', 'load', { - property: data['property'], - render: 'recommended', - css: data['css'], - }]); + global.mantis.push([ + 'recommend', + 'load', + { + property: data['property'], + render: 'recommended', + css: data['css'], + }, + ]); const d = global.document.createElement('div'); d.setAttribute('id', 'recommended'); diff --git a/ads/mediaimpact.js b/ads/mediaimpact.js index 2c001e062304c..5f8c00ff68fdf 100644 --- a/ads/mediaimpact.js +++ b/ads/mediaimpact.js @@ -25,10 +25,11 @@ export function mediaimpact(global, data) { /* eslint google-camelcase/google-camelcase: 0 */ global.sas_loadHandler = function(f) { if (f.hasAd) { - f.crea1 || (f.crea1 = { - width: 300, - height: 250, - }); + f.crea1 || + (f.crea1 = { + width: 300, + height: 250, + }); global.context.renderStart({ width: f.crea1.width, height: f.crea1.height, @@ -37,23 +38,34 @@ export function mediaimpact(global, data) { global.context.noContentAvailable(); } }; - window.addEventListener('load', function() { - asmi.sas.call(data.site + '/(' + data.page + ')', // eslint-disable-line no-undef + window.addEventListener( + 'load', + function() { + asmi.sas.call( + data.site + '/(' + data.page + ')', // eslint-disable-line no-undef data.format, data.target + ';googleAMP=1;', '', - 'sas_' + data.slot.replace('sas_',''), - 1); - }, false); - asmiSetup = { // eslint-disable-line no-unused-vars, no-undef + 'sas_' + data.slot.replace('sas_', ''), + 1 + ); + }, + false + ); + asmiSetup = { + // eslint-disable-line no-unused-vars, no-undef view: 'm', async: true, }; - loadScript(global, 'https://ec-ns.sascdn.com/diff/251/pages/amp_default.js', () => { - if (!document.getElementById('sas_' + data.slot.replace('sas_',''))) { - const adContainer = global.document.createElement('div'); - adContainer.id = 'sas_' + data.slot.replace('sas_',''); - global.document.body.appendChild(adContainer); + loadScript( + global, + 'https://ec-ns.sascdn.com/diff/251/pages/amp_default.js', + () => { + if (!document.getElementById('sas_' + data.slot.replace('sas_', ''))) { + const adContainer = global.document.createElement('div'); + adContainer.id = 'sas_' + data.slot.replace('sas_', ''); + global.document.body.appendChild(adContainer); + } } - }); + ); } diff --git a/ads/medianet.js b/ads/medianet.js index 9c49c2ea13b39..19206f93ee2cf 100644 --- a/ads/medianet.js +++ b/ads/medianet.js @@ -19,14 +19,24 @@ import {getSourceUrl, parseUrlDeprecated} from '../src/url'; import {hasOwn} from '../src/utils/object'; const mandatoryParams = ['tagtype', 'cid'], - optionalParams = [ - 'timeout', 'crid', 'misc', - 'slot', 'targeting', 'categoryExclusions', - 'tagForChildDirectedTreatment', 'cookieOptions', - 'overrideWidth', 'overrideHeight', 'loadingStrategy', - 'consentNotificationId', 'useSameDomainRenderingUntilDeprecated', - 'experimentId', 'multiSize', 'multiSizeValidation', - ]; + optionalParams = [ + 'timeout', + 'crid', + 'misc', + 'slot', + 'targeting', + 'categoryExclusions', + 'tagForChildDirectedTreatment', + 'cookieOptions', + 'overrideWidth', + 'overrideHeight', + 'loadingStrategy', + 'consentNotificationId', + 'useSameDomainRenderingUntilDeprecated', + 'experimentId', + 'multiSize', + 'multiSizeValidation', + ]; // useSameDomainRenderingUntilDeprecated is included to ensure publisher // amp-tags don't break before 29th March @@ -37,11 +47,12 @@ const mandatoryParams = ['tagtype', 'cid'], export function medianet(global, data) { validateData(data, mandatoryParams, optionalParams); - const publisherUrl = global.context.canonicalUrl || - getSourceUrl(global.context.location.href), - referrerUrl = global.context.referrer; + const publisherUrl = + global.context.canonicalUrl || getSourceUrl(global.context.location.href), + referrerUrl = global.context.referrer; - if (data.tagtype === 'headerbidder') { //parameter tagtype is used to identify the product the publisher is using. Going ahead we plan to support more product types. + if (data.tagtype === 'headerbidder') { + //parameter tagtype is used to identify the product the publisher is using. Going ahead we plan to support more product types. loadHBTag(global, data, publisherUrl, referrerUrl); } else if (data.tagtype === 'cm' && data.crid) { loadCMTag(global, data, publisherUrl, referrerUrl); @@ -143,7 +154,6 @@ function loadCMTag(global, data, publisherUrl, referrerUrl) { * @param {?string} referrerUrl */ function loadHBTag(global, data, publisherUrl, referrerUrl) { - /** * Loads MNETAd. */ @@ -162,8 +172,10 @@ function loadHBTag(global, data, publisherUrl, referrerUrl) { data.targeting = data.targeting || {}; - if (global.advBidxc && - typeof global.advBidxc.setAmpTargeting === 'function') { + if ( + global.advBidxc && + typeof global.advBidxc.setAmpTargeting === 'function' + ) { global.advBidxc.setAmpTargeting(global, data); } global.advBidxc.loadAmpAd(global, data); @@ -174,8 +186,10 @@ function loadHBTag(global, data, publisherUrl, referrerUrl) { */ function mnetHBHandle() { global.advBidxc = global.context.master.advBidxc; - if (global.advBidxc && - typeof global.advBidxc.registerAmpSlot === 'function') { + if ( + global.advBidxc && + typeof global.advBidxc.registerAmpSlot === 'function' + ) { global.advBidxc.registerAmpSlot({ cb: loadMNETAd, data, @@ -184,22 +198,34 @@ function loadHBTag(global, data, publisherUrl, referrerUrl) { } } - computeInMasterFrame(global, 'medianet-hb-load', done => { - /*eslint "google-camelcase/google-camelcase": 0*/ - global.advBidxc_requrl = publisherUrl; - global.advBidxc_refurl = referrerUrl; - global.advBidxc = { - registerAmpSlot: () => {}, - setAmpTargeting: () => {}, - renderAmpAd: () => {}, - loadAmpAd: () => { - global.context.noContentAvailable(); - }, - }; - global.advBidxc.amp = getCallbacksObject(); - const publisherDomain = parseUrlDeprecated(publisherUrl).hostname; - writeScript(global, 'https://contextual.media.net/bidexchange.js?https=1&=1&cid=' + encodeURIComponent(data.cid) + '&dn=' + encodeURIComponent(publisherDomain), () => { - done(null); - }); - }, mnetHBHandle); + computeInMasterFrame( + global, + 'medianet-hb-load', + done => { + /*eslint "google-camelcase/google-camelcase": 0*/ + global.advBidxc_requrl = publisherUrl; + global.advBidxc_refurl = referrerUrl; + global.advBidxc = { + registerAmpSlot: () => {}, + setAmpTargeting: () => {}, + renderAmpAd: () => {}, + loadAmpAd: () => { + global.context.noContentAvailable(); + }, + }; + global.advBidxc.amp = getCallbacksObject(); + const publisherDomain = parseUrlDeprecated(publisherUrl).hostname; + writeScript( + global, + 'https://contextual.media.net/bidexchange.js?https=1&=1&cid=' + + encodeURIComponent(data.cid) + + '&dn=' + + encodeURIComponent(publisherDomain), + () => { + done(null); + } + ); + }, + mnetHBHandle + ); } diff --git a/ads/medyanet.js b/ads/medyanet.js index 5fb1dff2749b8..a291e6e91012e 100644 --- a/ads/medyanet.js +++ b/ads/medyanet.js @@ -46,13 +46,15 @@ function medyanetAds(global, data) { f.setAttribute('allowfullscreen', 'true'); f.setAttribute('scrolling', 'no'); setStyles(f, { - border: '0 none transparent' , - position: 'relative' , + border: '0 none transparent', + position: 'relative', }); f.onload = function() { window.context.renderStart(); }; - f.src = `https://app.medyanetads.com/amp/medyanetads.html?bidderData=${global.domain}&adunit=${global.adunit}&size=${global.size}`; + f.src = `https://app.medyanetads.com/amp/medyanetads.html?bidderData=${ + global.domain + }&adunit=${global.adunit}&size=${global.size}`; const url = window.top.location.search.substring(1); if (url && url.indexOf('hb=true') !== -1) { f.src = f.src + '&hb=true'; diff --git a/ads/meg.js b/ads/meg.js index a0b60d69d2f20..1355673c6f3c1 100644 --- a/ads/meg.js +++ b/ads/meg.js @@ -34,10 +34,15 @@ export function meg(global, data) { global.context.noContentAvailable(); }, }; - loadScript(global, url, () => { - // Meg has been loaded - }, () => { - // Cannot load meg embed.js - global.context.noContentAvailable(); - }); + loadScript( + global, + url, + () => { + // Meg has been loaded + }, + () => { + // Cannot load meg embed.js + global.context.noContentAvailable(); + } + ); } diff --git a/ads/miximedia.js b/ads/miximedia.js index 7a46854bf7ad6..96cd9ea2755f2 100644 --- a/ads/miximedia.js +++ b/ads/miximedia.js @@ -22,7 +22,7 @@ import {loadScript, validateData} from '../3p/3p'; */ export function miximedia(global, data) { validateData(data, ['blockid']); - (global._miximedia = global._miximedia || { + global._miximedia = global._miximedia || { viewId: global.context.pageViewId, blockId: data['blockid'], htmlURL: data['canonical'] || global.context.canonicalUrl, @@ -34,14 +34,17 @@ export function miximedia(global, data) { domFingerprint: window.context.domFingerprint, location: window.context.location, startTime: window.context.startTime, - - }); + }; global._miximedia.AMPCallbacks = { renderStart: global.context.renderStart, noContentAvailable: global.context.noContentAvailable, }; // load the miximedia AMP JS file script asynchronously const rand = Math.round(Math.random() * 100000000); - loadScript(global, 'https://amp.mixi.media/ampclient/mixi.js?rand=' + rand, () => {}, - global.context.noContentAvailable); + loadScript( + global, + 'https://amp.mixi.media/ampclient/mixi.js?rand=' + rand, + () => {}, + global.context.noContentAvailable + ); } diff --git a/ads/mixpo.js b/ads/mixpo.js index 67e4b01731722..a8367dc513fe5 100644 --- a/ads/mixpo.js +++ b/ads/mixpo.js @@ -14,22 +14,18 @@ * limitations under the License. */ -import {validateData,writeScript} from '../3p/3p'; +import {validateData, writeScript} from '../3p/3p'; /** * @param {!Window} global * @param {!Object} data */ export function mixpo(global, data) { - validateData(data, [ - 'guid', - 'subdomain', - ]); + validateData(data, ['guid', 'subdomain']); const g = global, - cdnSubdomain = (data.subdomain == 'www') ? - 'cdn' : data.subdomain + '-cdn', - url = data.loader || `https://${cdnSubdomain}.mixpo.com/js/loader.js`; + cdnSubdomain = data.subdomain == 'www' ? 'cdn' : data.subdomain + '-cdn', + url = data.loader || `https://${cdnSubdomain}.mixpo.com/js/loader.js`; g.mixpoAd = { amp: true, diff --git a/ads/nativo.js b/ads/nativo.js index 8b78bcfb42ed2..1633187e3b0a2 100644 --- a/ads/nativo.js +++ b/ads/nativo.js @@ -22,11 +22,11 @@ import {loadScript} from '../3p/3p'; export function nativo(global, data) { let ntvAd; (function(ntvAd, global, data) { - global - .history - .replaceState(null, - '', - location.pathname + location.hash.replace(/({).*(})/, '')); + global.history.replaceState( + null, + '', + location.pathname + location.hash.replace(/({).*(})/, '') + ); // Private let delayedAdLoad = false; let percentageOfadViewed; @@ -36,26 +36,27 @@ export function nativo(global, data) { * @return {boolean} */ function isValidDelayTime(delay) { - return ((typeof delay != 'undefined' - && !isNaN(delay) - && parseInt(delay,10) >= 0)); + return ( + typeof delay != 'undefined' && !isNaN(delay) && parseInt(delay, 10) >= 0 + ); } /** * @param {!Object} data * @return {boolean} */ function isDelayedTimeStart(data) { - return (isValidDelayTime(data.delayByTime) - && ('delay' in data) - && !('delayByView' in data)); + return ( + isValidDelayTime(data.delayByTime) && + 'delay' in data && + !('delayByView' in data) + ); } /** * @param {!Object} data * @return {boolean} */ function isDelayedViewStart(data) { - return (isValidDelayTime(data.delayByTime) - && ('delayByView' in data)); + return isValidDelayTime(data.delayByTime) && 'delayByView' in data; } /** * Loads ad when done. @@ -64,9 +65,11 @@ export function nativo(global, data) { const g = global; global.context.observeIntersection(function(positions) { const coordinates = getLastPositionCoordinates(positions); - if (typeof coordinates.rootBounds != 'undefined' - && (coordinates.intersectionRect.top == ( - coordinates.rootBounds.top + coordinates.boundingClientRect.y))) { + if ( + typeof coordinates.rootBounds != 'undefined' && + coordinates.intersectionRect.top == + coordinates.rootBounds.top + coordinates.boundingClientRect.y + ) { if (isDelayedViewStart(data) && !delayedAdLoad) { g.PostRelease.Start(); delayedAdLoad = true; @@ -104,10 +107,10 @@ export function nativo(global, data) { function viewabilityConfiguration(positions) { const coordinates = getLastPositionCoordinates(positions); setPercentageOfadViewed( - (((coordinates.intersectionRect - .height * 100) / coordinates - .boundingClientRect - .height) / 100)); + (coordinates.intersectionRect.height * 100) / + coordinates.boundingClientRect.height / + 100 + ); global.PostRelease.checkIsAdVisible(); } // Public @@ -124,11 +127,17 @@ export function nativo(global, data) { global._prx.push(['cfg.RequestUrl', data['requestUrl'] || loc.href]); for (const key in data) { switch (key) { - case 'premium': global._prx.push(['cfg.SetUserPremium']); break; - case 'debug': global._prx.push(['cfg.Debug']); break; - case 'delay': if (isValidDelayTime(data.delayByTime)) { - global._prx.push(['cfg.SetNoAutoStart']); - } break; + case 'premium': + global._prx.push(['cfg.SetUserPremium']); + break; + case 'debug': + global._prx.push(['cfg.Debug']); + break; + case 'delay': + if (isValidDelayTime(data.delayByTime)) { + global._prx.push(['cfg.SetNoAutoStart']); + } + break; } } }; diff --git a/ads/navegg.js b/ads/navegg.js index a576f98b5d88a..5c7fc5252934d 100644 --- a/ads/navegg.js +++ b/ads/navegg.js @@ -24,7 +24,8 @@ import {loadScript, validateData} from '../3p/3p'; export function navegg(global, data) { validateData(data, ['acc']); const {acc} = data; - let seg, nvg = function() {}; + let seg, + nvg = function() {}; delete data.acc; nvg.prototype.getProfile = function() {}; data.targeting = data.targeting || {}; diff --git a/ads/netletix.js b/ads/netletix.js index 7cda96409d54f..635b636101fcd 100644 --- a/ads/netletix.js +++ b/ads/netletix.js @@ -37,32 +37,39 @@ const DEFAULT_NX_SITE = 'none'; export function netletix(global, data) { /*eslint "google-camelcase/google-camelcase": 0*/ global._netletix_amp = { - allowed_data: ['nxasync','nxv','nxsite','nxid','nxscript'], - mandatory_data: ['nxkey','nxunit','nxwidth','nxheight'], + allowed_data: ['nxasync', 'nxv', 'nxsite', 'nxid', 'nxscript'], + mandatory_data: ['nxkey', 'nxunit', 'nxwidth', 'nxheight'], data, }; - validateData(data, - global._netletix_amp.mandatory_data, global._netletix_amp.allowed_data); + validateData( + data, + global._netletix_amp.mandatory_data, + global._netletix_amp.allowed_data + ); - const nxh = (data.nxheight || DEFAULT_NX_HEIGHT); - const nxw = (data.nxwidth || DEFAULT_NX_WIDTH); + const nxh = data.nxheight || DEFAULT_NX_HEIGHT; + const nxw = data.nxwidth || DEFAULT_NX_WIDTH; const url = assertHttpsUrl( - addParamsToUrl( - NX_URL_FULL + encodeURIComponent(data.nxkey || DEFAULT_NX_KEY), - dict({ - 'unit': data.nxunit || DEFAULT_NX_UNIT, - 'width': data.nxwidth || DEFAULT_NX_WIDTH, - 'height': data.nxheight || DEFAULT_NX_HEIGHT, - 'v': data.nxv || DEFAULT_NX_V, - 'site': data.nxsite || DEFAULT_NX_SITE, - 'ord': Math.round(Math.random() * 100000000), - })), - data.ampSlotIndex); + addParamsToUrl( + NX_URL_FULL + encodeURIComponent(data.nxkey || DEFAULT_NX_KEY), + dict({ + 'unit': data.nxunit || DEFAULT_NX_UNIT, + 'width': data.nxwidth || DEFAULT_NX_WIDTH, + 'height': data.nxheight || DEFAULT_NX_HEIGHT, + 'v': data.nxv || DEFAULT_NX_V, + 'site': data.nxsite || DEFAULT_NX_SITE, + 'ord': Math.round(Math.random() * 100000000), + }) + ), + data.ampSlotIndex + ); window.addEventListener('message', event => { - if (event.data.type && - startsWith(dev().assertString(event.data.type), 'nx-')) { + if ( + event.data.type && + startsWith(dev().assertString(event.data.type), 'nx-') + ) { switch (event.data.type) { case 'nx-resize': const renderconfig = { @@ -70,8 +77,11 @@ export function netletix(global, data) { 'height': event.data.height, }; global.context.renderStart(renderconfig); - if (event.data.width && event.data.height && - (event.data.width != nxw || event.data.height != nxh)) { + if ( + event.data.width && + event.data.height && + (event.data.width != nxw || event.data.height != nxh) + ) { global.context.requestResize(event.data.width, event.data.height); } break; diff --git a/ads/nokta.js b/ads/nokta.js index 3baf19aadf061..8f8ee985de4c5 100644 --- a/ads/nokta.js +++ b/ads/nokta.js @@ -27,5 +27,8 @@ export function nokta(global, data) { global.zone = data.zone; global.iwidth = data.width; global.iheight = data.height; - writeScript(global, 'https://static.virgul.com/theme/mockups/noktaamp/ampjs.js'); + writeScript( + global, + 'https://static.virgul.com/theme/mockups/noktaamp/ampjs.js' + ); } diff --git a/ads/onnetwork.js b/ads/onnetwork.js index 28c3f62021b6b..4dc618ba3efe8 100644 --- a/ads/onnetwork.js +++ b/ads/onnetwork.js @@ -42,9 +42,8 @@ export function onnetwork(global, data) { // Movie tag using "data-sid" attribute else if (sid) { url = hosts.video + '/embed.php?ampsrc=1&sid=' + encodeURIComponent(sid); - // Movie placement using "data-mid" attribute - } - else if (mid) { + // Movie placement using "data-mid" attribute + } else if (mid) { url = hosts.video + '/embed.php?ampsrc=1&mid=' + encodeURIComponent(mid); } diff --git a/ads/openadstream.js b/ads/openadstream.js index 53209047ffe58..cbd6bb0a2737e 100644 --- a/ads/openadstream.js +++ b/ads/openadstream.js @@ -23,9 +23,15 @@ import {validateData, writeScript} from '../3p/3p'; export function openadstream(global, data) { validateData(data, ['adhost', 'sitepage', 'pos'], ['query']); - let url = 'https://' + encodeURIComponent(data.adhost) - + '/3/' + data.sitepage - + '/1' + String(Math.random()).substring(2, 11) + '@' + data.pos; + let url = + 'https://' + + encodeURIComponent(data.adhost) + + '/3/' + + data.sitepage + + '/1' + + String(Math.random()).substring(2, 11) + + '@' + + data.pos; if (data.query) { url = url + '?' + data.query; diff --git a/ads/openx.js b/ads/openx.js index c3f38b876802e..28d5b89f2ddef 100644 --- a/ads/openx.js +++ b/ads/openx.js @@ -57,7 +57,7 @@ export function openx(global, data) { if (startsWith(openxKey, 'dfp')) { // Remove 'dfp' prefix, lowercase the first letter. let fixKey = openxKey.substring(3); - fixKey = fixKey.substring(0,1).toLowerCase() + fixKey.substring(1); + fixKey = fixKey.substring(0, 1).toLowerCase() + fixKey.substring(1); dfpData[fixKey] = data[openxKey]; } delete dfpData[openxKey]; @@ -82,7 +82,8 @@ export function openx(global, data) { } else { standardImplementation(global, jssdk, dfpData); } - } else if (data.auid) { // Just show an ad. + } else if (data.auid) { + // Just show an ad. global.OX_cmds = [ () => { const oxRequest = OX(); @@ -102,7 +103,8 @@ export function openx(global, data) { ]; loadScript(global, jssdk); } - } else if (data.dfpSlot) { // Fall back to a DFP ad. + } else if (data.dfpSlot) { + // Fall back to a DFP ad. doubleclick(global, dfpData); } } @@ -136,8 +138,9 @@ function advanceImplementation(global, jssdk, dfpData, data) { callback: () => { const priceMap = global.oxhbjs && global.oxhbjs.getPriceMap(); const slot = priceMap && priceMap['c']; - const targeting = slot ? - `${slot.size}_${slot.price},hb-bid-${slot.bid_id}` : 'none_t'; + const targeting = slot + ? `${slot.size}_${slot.price},hb-bid-${slot.bid_id}` + : 'none_t'; dfpData.targeting = dfpData.targeting || {}; assign(dfpData.targeting, {oxb: targeting}); doubleclick(global, dfpData); @@ -171,8 +174,9 @@ function setCustomVars(oxRequest, customVars) { */ function filterCustomVar(customVars) { const filterPattern = /^[A-Za-z0-9._]{1,20}$/; - const filteredKeys = Object.keys(customVars) - .filter(key => filterPattern.test(key)); + const filteredKeys = Object.keys(customVars).filter(key => + filterPattern.test(key) + ); const filteredCustomVar = {}; filteredKeys.forEach(key => { filteredCustomVar[key.toLowerCase()] = customVars[key]; diff --git a/ads/outbrain.js b/ads/outbrain.js index 53e2594e5cea2..6251836949d9d 100644 --- a/ads/outbrain.js +++ b/ads/outbrain.js @@ -21,11 +21,10 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function outbrain(global, data) { - // ensure we have valid widgetIds value validateData(data, ['widgetids']); - (global._outbrain = global._outbrain || { + global._outbrain = global._outbrain || { viewId: global.context.pageViewId, widgetIds: data['widgetids'], htmlURL: data['htmlurl'] || global.context.canonicalUrl, @@ -34,8 +33,11 @@ export function outbrain(global, data) { testMode: data['testmode'] || 'false', styleFile: data['stylefile'] || '', referrer: data['referrer'] || global.context.referrer, - }); + }; // load the Outbrain AMP JS file - loadScript(global, 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js'); + loadScript( + global, + 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js' + ); } diff --git a/ads/pixels.js b/ads/pixels.js index 94bf8f1267f69..afe513a47f481 100644 --- a/ads/pixels.js +++ b/ads/pixels.js @@ -21,19 +21,20 @@ import {validateData, writeScript} from '../3p/3p'; * @param {!Object} data */ export function pixels(global, data) { - validateData(data, - ['origin', 'sid', 'tag'], - ['clickTracker', 'viewability'] - ); + validateData(data, ['origin', 'sid', 'tag'], ['clickTracker', 'viewability']); data.tag = data.tag.toString().toLowerCase(); global._pixelsParam = data; if (data.tag === 'sync') { - writeScript(global, 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', () => { - const pixelsAMPAd = global.pixelsAd; - const pixelsAMPTag = new pixelsAMPAd(data); - pixelsAMPTag.renderAmp(global.context); - global.context.renderStart(); - }); + writeScript( + global, + 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', + () => { + const pixelsAMPAd = global.pixelsAd; + const pixelsAMPTag = new pixelsAMPAd(data); + pixelsAMPTag.renderAmp(global.context); + global.context.renderStart(); + } + ); } else { global.context.noContentAvailable(); } diff --git a/ads/plista.js b/ads/plista.js index d7ffaf0521caf..b7a8390fa53a5 100644 --- a/ads/plista.js +++ b/ads/plista.js @@ -22,20 +22,31 @@ import {loadScript, validateData} from '../3p/3p'; */ export function plista(global, data) { // TODO: check mandatory fields - validateData(data, [], [ - 'publickey', 'widgetname', 'urlprefix', - 'item', 'geo', 'categories', 'countrycode', - ]); + validateData( + data, + [], + [ + 'publickey', + 'widgetname', + 'urlprefix', + 'item', + 'geo', + 'categories', + 'countrycode', + ] + ); const div = global.document.createElement('div'); div.setAttribute('data-display', 'plista_widget_' + data.widgetname); // container with id "c" is provided by amphtml global.document.getElementById('c').appendChild(div); window.PLISTA = { publickey: data.publickey, - widgets: [{ - name: data.widgetname, - pre: data.urlprefix, - }], + widgets: [ + { + name: data.widgetname, + pre: data.urlprefix, + }, + ], item: data.item, geo: data.geo, categories: data.categories, @@ -45,5 +56,10 @@ export function plista(global, data) { }; // load the plista modules asynchronously - loadScript(global, 'https://static' + (data.countrycode ? '-' + encodeURIComponent(data.countrycode) : '') + '.plista.com/async.js'); + loadScript( + global, + 'https://static' + + (data.countrycode ? '-' + encodeURIComponent(data.countrycode) : '') + + '.plista.com/async.js' + ); } diff --git a/ads/popin.js b/ads/popin.js index 8981c3f762130..b16f4fc5bf979 100644 --- a/ads/popin.js +++ b/ads/popin.js @@ -17,9 +17,9 @@ import {loadScript, validateData} from '../3p/3p'; /** -* @param {!Window} global -* @param {!Object} data -*/ + * @param {!Window} global + * @param {!Object} data + */ export function popin(global, data) { validateData(data, ['mediaid']); @@ -27,7 +27,10 @@ export function popin(global, data) { d.id = '_popIn_amp_recommend'; global.document.getElementById('c').appendChild(d); - const url = 'https://api.popin.cc/searchbox/' + encodeURIComponent(data['mediaid']) + '.js'; + const url = + 'https://api.popin.cc/searchbox/' + + encodeURIComponent(data['mediaid']) + + '.js'; loadScript(global, url); } diff --git a/ads/postquare.js b/ads/postquare.js index 0366031db692a..c4e50182fbf7a 100644 --- a/ads/postquare.js +++ b/ads/postquare.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function postquare(global, data) { - validateData(data, ['widgetids']); global._postquare = global._postquare || { diff --git a/ads/pressboard.js b/ads/pressboard.js index 75f1f9afaf75d..a68bc8fc969f4 100644 --- a/ads/pressboard.js +++ b/ads/pressboard.js @@ -24,9 +24,14 @@ export function pressboard(global, data) { validateData(data, ['media']); data.baseUrl = 'https://adserver.pressboard.ca'; global.pbParams = data; - loadScript(global, data.baseUrl + '/js/amp-ad.js', () => { - global.context.renderStart(); - }, () => { - global.context.noContentAvailable(); - }); + loadScript( + global, + data.baseUrl + '/js/amp-ad.js', + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); } diff --git a/ads/pubexchange.js b/ads/pubexchange.js index d1ea82a1475eb..a002281e00248 100644 --- a/ads/pubexchange.js +++ b/ads/pubexchange.js @@ -21,7 +21,6 @@ import {loadScript, validateData} from '../3p/3p'; * @param {!Object} data */ export function pubexchange(global, data) { - // ensure we have valid widgetIds value validateData(data, ['publication', 'moduleId', 'moduleNum'], ['test']); diff --git a/ads/pubguru.js b/ads/pubguru.js index 9c7ea34bbe1e7..6d6c2fdb36386 100644 --- a/ads/pubguru.js +++ b/ads/pubguru.js @@ -29,6 +29,10 @@ export function pubguru(global, data) { el.setAttribute('id', 'the-ad-unit'); global.document.getElementById('c').appendChild(el); - loadScript(global, 'https://amp.pubguru.org/amp.' - + encodeURIComponent(data.publisher) + '.min.js'); + loadScript( + global, + 'https://amp.pubguru.org/amp.' + + encodeURIComponent(data.publisher) + + '.min.js' + ); } diff --git a/ads/pubmine.js b/ads/pubmine.js index c713fdb3d9198..3ae5ed0fd5f16 100644 --- a/ads/pubmine.js +++ b/ads/pubmine.js @@ -17,8 +17,8 @@ import {validateData, writeScript} from '../3p/3p'; const pubmineOptional = ['section', 'pt', 'ht'], - pubmineRequired = ['siteid'], - pubmineURL = 'https://s.pubmine.com/head.js'; + pubmineRequired = ['siteid'], + pubmineURL = 'https://s.pubmine.com/head.js'; /** * @param {!Window} global @@ -42,14 +42,15 @@ export function pubmine(global, data) { writeScript(global, pubmineURL); const o = { - sectionId: data['siteid'] + ('section' in data ? data.section : '1'), - height: data.height == 250 ? 250 : data.height - 15, - width: data.width, - }, - wr = global.document.write; + sectionId: data['siteid'] + ('section' in data ? data.section : '1'), + height: data.height == 250 ? 250 : data.height - 15, + width: data.width, + }, + wr = global.document.write; - wr.call(global.document, - `
+ wr.call( + global.document, + `
@@ -64,8 +64,7 @@ app.use('/compose-doc', function(req, res) { } // TODO: Do we need to inject amp-3p-iframe-src for non-ad tests? - const head = - `${experimentsBlock} + const head = `${experimentsBlock} `; const doc = composeDocument({ @@ -104,23 +103,20 @@ const bank = {}; * Deposit a request. An ID has to be specified. Will override previous request * if the same ID already exists. */ -app.use( - '/request-bank/:bid/deposit/:id/', - upload.array(), - (req, res) => { - cors.enableCors(req, res); - if (!bank[req.params.bid]) { - bank[req.params.bid] = {}; - } - const key = req.params.id; - log('SERVER-LOG [DEPOSIT]: ', key); - if (typeof bank[req.params.bid][key] === 'function') { - bank[req.params.bid][key](req); - } else { - bank[req.params.bid][key] = req; - } - res.end(); - }); +app.use('/request-bank/:bid/deposit/:id/', upload.array(), (req, res) => { + cors.enableCors(req, res); + if (!bank[req.params.bid]) { + bank[req.params.bid] = {}; + } + const key = req.params.id; + log('SERVER-LOG [DEPOSIT]: ', key); + if (typeof bank[req.params.bid][key] === 'function') { + bank[req.params.bid][key](req); + } else { + bank[req.params.bid][key] = req; + } + res.end(); +}); /** * Withdraw a request. If the request of the given ID is already in the bank, @@ -230,9 +226,9 @@ app.get('/a4a/:bid', (req, res) => { function composeDocument(config) { const {body, css, extensions, head, spec, mode} = config; - const m = (mode || process.env.SERVE_MODE); - const cdn = (m === 'cdn'); - const compiled = (m === 'compiled'); + const m = mode || process.env.SERVE_MODE; + const cdn = m === 'cdn'; + const compiled = m === 'compiled'; const cssTag = css ? `` : ''; @@ -243,22 +239,25 @@ function composeDocument(config) { switch (amp) { case 'amp': canonical = ''; - boilerplate = ''; - runtime = (cdn) + boilerplate = + ''; + runtime = cdn ? 'https://cdn.ampproject.org/v0.js' : `/dist/${compiled ? 'v0' : 'amp'}.js`; break; case 'amp4ads': canonical = ''; - boilerplate = ''; - runtime = (cdn) + boilerplate = + ''; + runtime = cdn ? 'https://cdn.ampproject.org/amp4ads-v0.js' : `/dist/${compiled ? 'amp4ads-v0' : 'amp-inabox'}.js`; break; case 'amp4email': canonical = ''; - boilerplate = ''; - runtime = (cdn) + boilerplate = + ''; + runtime = cdn ? 'https://cdn.ampproject.org/v0.js' : `/dist/${compiled ? 'v0' : 'amp'}.js`; break; @@ -270,19 +269,20 @@ function composeDocument(config) { // Generate extension `; - }).join('\n'); + extensionScripts = extensions + .map(extension => { + const tuple = extension.split(':'); + const name = tuple[0]; + const version = tuple[1] || '0.1'; + const src = cdn + ? `https://cdn.ampproject.org/v0/${name}-${version}.js` + : `/dist/v0/${name}-${version}.${compiled ? '' : 'max.'}js`; + return ``; + }) + .join('\n'); } - const topHalfOfHtml = - ` + const topHalfOfHtml = ` AMP TEST @@ -304,7 +304,8 @@ function composeDocument(config) { const start = topHalfOfHtml.indexOf(runtimeScript); let end = start + runtimeScript.length; - let customElements = [], extensionsMap = []; + let customElements = [], + extensionsMap = []; if (extensions) { end = topHalfOfHtml.indexOf(extensionScripts) + extensionScripts.length; // Filter out extensions that are not custom elements, e.g. amp-mustache. @@ -317,8 +318,7 @@ function composeDocument(config) { }; }); } - ampAdMeta = - ``; - + html` + + `; const AmpState = (id, state) => html` - `; - + +`; const ternaryExpr = (condition, onTrue, onFalse) => `${condition} ? ${onTrue} : ${onFalse}`; - const containsExpr = (haystack, needle, onTrue, onFalse) => ternaryExpr(`${haystack}.indexOf(${needle}) > -1`, onTrue, onFalse); - const ampStateKey = (...keys) => keys.join('.'); - const AmpDoc = ({body, css, head, canonical}) => { assert(canonical); return html` - + - - AMP Dev Server - - - ${css ? html`` : ''} - - ${boilerPlate} - - ${head || ''} - - - ${body} - - `; + + AMP Dev Server + + + ${css + ? html` + + ` + : ''} + + ${boilerPlate} + + ${head || ''} + + + ${body} + + + `; }; - const componentExtensionNameMapping = { 'amp-state': 'amp-bind', }; @@ -91,25 +95,27 @@ const componentExtensionNameMapping = { const componentExtensionName = tagName => componentExtensionNameMapping[tagName] || tagName; - -const addRequiredExtensionsToHead = (docStr, extensionConf = { - 'amp-mustache': {version: '0.2'}, -}) => { +const addRequiredExtensionsToHead = ( + docStr, + extensionConf = { + 'amp-mustache': {version: '0.2'}, + } +) => { const extensions = {}; const addExtension = (name, defaultConf = {}) => - extensions[name] = {name, ...defaultConf, ...(extensionConf[name] || {})}; + (extensions[name] = {name, ...defaultConf, ...(extensionConf[name] || {})}); const addTemplate = (name, defaultConf = {}) => addExtension(name, {isTemplate: true, ...defaultConf}); Array.from(matchIterator(componentTagNameRegex, docStr)) - .map(([unusedFullMatch, tagName]) => componentExtensionName(tagName)) - .forEach(addExtension); + .map(([unusedFullMatch, tagName]) => componentExtensionName(tagName)) + .forEach(addExtension); Array.from(matchIterator(templateTagTypeRegex, docStr)) - .map(([unusedFullMatch, type]) => type) - .forEach(addTemplate); + .map(([unusedFullMatch, type]) => type) + .forEach(addTemplate); // TODO(alanorozco): Too greedy. Parse "on" attributes instead. if (docStr.indexOf('AMP.setState') >= 0) { @@ -120,11 +126,13 @@ const addRequiredExtensionsToHead = (docStr, extensionConf = { addExtension('amp-form'); } - return docStr.replace(/\<\/head\>/i, headClosingTag => - joinFragments(Object.values(extensions), ExtensionScript) + headClosingTag); + return docStr.replace( + /\<\/head\>/i, + headClosingTag => + joinFragments(Object.values(extensions), ExtensionScript) + headClosingTag + ); }; - module.exports = { AmpDoc, AmpState, diff --git a/build-system/app-index/api/api.js b/build-system/app-index/api/api.js index 482257bc77930..a11cdd267bd30 100644 --- a/build-system/app-index/api/api.js +++ b/build-system/app-index/api/api.js @@ -23,11 +23,9 @@ const Fuse = require('fuse.js'); const path = require('path'); const {getListing} = require('../util/listing'); - // Sitting on /build-system/app-index/api, so we go back thrice for the root. const root = path.join(__dirname, '../../../'); - function searchListing(fileSet, opt_searchQuery) { if (!opt_searchQuery) { return fileSet; @@ -42,7 +40,6 @@ function searchListing(fileSet, opt_searchQuery) { return fuse.search(opt_searchQuery).map(i => fileSet[i]); } - async function handleListingRequest({query: {path, search}}, res) { try { assert(path); @@ -58,10 +55,8 @@ async function handleListingRequest({query: {path, search}}, res) { } } - function installExpressMiddleware(app) { app.get('/dashboard/api/listing', handleListingRequest); } - module.exports = {installExpressMiddleware}; diff --git a/build-system/app-index/boilerplate.js b/build-system/app-index/boilerplate.js index ddfea76a26725..337bfbe1e9d84 100644 --- a/build-system/app-index/boilerplate.js +++ b/build-system/app-index/boilerplate.js @@ -14,4 +14,5 @@ * limitations under the License. */ /* eslint-disable max-len */ -module.exports = ''; +module.exports = + ''; diff --git a/build-system/app-index/document-modes.js b/build-system/app-index/document-modes.js index 3038e461b3c4f..9ed300ff1ac1d 100644 --- a/build-system/app-index/document-modes.js +++ b/build-system/app-index/document-modes.js @@ -14,7 +14,6 @@ * limitations under the License. */ - module.exports = { 'standard': '/', 'shadow': '/shadow/', diff --git a/build-system/app-index/file-list.js b/build-system/app-index/file-list.js index 386aa48ae3b8d..d36b58de12989 100644 --- a/build-system/app-index/file-list.js +++ b/build-system/app-index/file-list.js @@ -23,7 +23,6 @@ const {appendQueryParamsToUrl, replaceLeadingSlash} = require('./url'); const {html, joinFragments} = require('./html'); const {KeyValueOptions} = require('./form'); - const examplesPathRegex = /^\/examples\//; const htmlDocRegex = /\.html$/; @@ -37,74 +36,79 @@ const endpointStateId = 'listingEndpoint'; const endpointStateKey = 'src'; const endpointKey = ampStateKey(endpointStateId, endpointStateKey); - const FileListSearchInput = ({basepath}) => html` - `; - + })" + /> +`; const ExamplesDocumentModeSelect = () => html` `; - + +`; const linksToExample = (shouldContainBasepath, opt_name) => examplesPathRegex.test(shouldContainBasepath) && - htmlDocRegex.test(opt_name || shouldContainBasepath); - + htmlDocRegex.test(opt_name || shouldContainBasepath); const ExamplesSelectModeOptional = ({basepath, selectModePrefix}) => - !examplesPathRegex.test(basepath + '/') ? '' : ExamplesDocumentModeSelect({ - selectModePrefix, - }); - + !examplesPathRegex.test(basepath + '/') + ? '' + : ExamplesDocumentModeSelect({ + selectModePrefix, + }); const FileListItem = ({name, href, boundHref}) => - html``; - + html` + + `; const PlaceholderFileListItem = ({name, href, selectModePrefix}) => - linksToExample(href) ? - FileListItem({ - name, - href: selectModePrefix + replaceLeadingSlash(href, ''), - boundHref: `(${selectModeKey} || '${selectModePrefix}') + '${ - replaceLeadingSlash(href, '')}'`, - }) : - FileListItem({href, name}); - + linksToExample(href) + ? FileListItem({ + name, + href: selectModePrefix + replaceLeadingSlash(href, ''), + boundHref: `(${selectModeKey} || '${selectModePrefix}') + '${replaceLeadingSlash( + href, + '' + )}'`, + }) + : FileListItem({href, name}); const maybePrefixExampleDocHref = (basepath, name, selectModePrefix) => - (linksToExample(basepath, name) ? - replaceLeadingSlash(basepath, selectModePrefix) : - basepath) + - name; - + (linksToExample(basepath, name) + ? replaceLeadingSlash(basepath, selectModePrefix) + : basepath) + name; const FileListHeading = ({basepath, selectModePrefix}) => html`
@@ -119,66 +123,79 @@ const FileListHeading = ({basepath, selectModePrefix}) => html` ${ExamplesSelectModeOptional({basepath, selectModePrefix})} List root directory
-
`; - +
+`; const wrapFileList = rendered => html`
${rendered}
-
`; - + +`; const FileList = ({basepath, fileSet, selectModePrefix}) => - wrapFileList(joinFragments([ - AmpState(endpointStateId, { - [endpointStateKey]: endpoint({path: basepath}), - }), - - FileListHeading({basepath, selectModePrefix}), - - html` - -
Failed to load data.
- -
-
- ${joinFragments(fileSet, name => - PlaceholderFileListItem({ - name, - href: maybePrefixExampleDocHref(basepath, name, selectModePrefix), - selectModePrefix, - }))} -
-
- - + +
+ Show more +
+
+ `, + ]) + ); module.exports = {FileList}; diff --git a/build-system/app-index/form.js b/build-system/app-index/form.js index 94bcdd3b1c38d..a4accb316a1f1 100644 --- a/build-system/app-index/form.js +++ b/build-system/app-index/form.js @@ -18,15 +18,19 @@ const {html} = require('./html'); - -const Option = ({value, name}) => html``; - - -const KeyValueOptions = options => Object.keys(options).map(name => - Option({ - name, - value: options[name], - })).join(''); - +const Option = ({value, name}) => + html` + + `; + +const KeyValueOptions = options => + Object.keys(options) + .map(name => + Option({ + name, + value: options[name], + }) + ) + .join(''); module.exports = {Option, KeyValueOptions}; diff --git a/build-system/app-index/header-links.js b/build-system/app-index/header-links.js index 9586857fc6243..e2956a9559eac 100644 --- a/build-system/app-index/header-links.js +++ b/build-system/app-index/header-links.js @@ -17,7 +17,8 @@ module.exports = [ { 'name': 'Developing', - 'href': 'https://' + + 'href': + 'https://' + 'github.com/ampproject/amphtml/blob/master/contributing/DEVELOPING.md', }, { diff --git a/build-system/app-index/html.js b/build-system/app-index/html.js index cf311af0a1e39..87c1786a1a98d 100644 --- a/build-system/app-index/html.js +++ b/build-system/app-index/html.js @@ -16,7 +16,6 @@ const identity = a => a; - /** * Takes a set of HTML fragments and concatenates them. * @param {!Array} fragments @@ -27,7 +26,6 @@ const identity = a => a; const joinFragments = (fragments, renderer = identity) => fragments.map(renderer).join(''); - /** * pass-through for syntax highlighting * @param {!Array} strings @@ -37,5 +35,4 @@ const joinFragments = (fragments, renderer = identity) => const html = (strings, ...values) => joinFragments(strings, (string, i) => string + (values[i] || '')); - module.exports = {html, joinFragments}; diff --git a/build-system/app-index/proxy-form.js b/build-system/app-index/proxy-form.js index 20dcf64c2d107..830aaa551eb67 100644 --- a/build-system/app-index/proxy-form.js +++ b/build-system/app-index/proxy-form.js @@ -19,28 +19,34 @@ const documentModes = require('./document-modes'); const {html} = require('./html'); const {KeyValueOptions} = require('./form'); - -module.exports = () => html`
+module.exports = () => html` +
Takes canonical, AMPHTML and Google viewer URLs. - + What's this?
-
`; +
+`; diff --git a/build-system/app-index/regex.js b/build-system/app-index/regex.js index f42d89f1fb57c..f4f202d75ea38 100644 --- a/build-system/app-index/regex.js +++ b/build-system/app-index/regex.js @@ -14,7 +14,6 @@ * limitations under the License. */ - function* matchIterator(regex, subject) { let match = regex.exec(subject); while (match != null) { @@ -23,5 +22,4 @@ function* matchIterator(regex, subject) { } } - module.exports = {matchIterator}; diff --git a/build-system/app-index/settings.js b/build-system/app-index/settings.js index 9fb4373476e13..b8363c0ed5efe 100644 --- a/build-system/app-index/settings.js +++ b/build-system/app-index/settings.js @@ -35,64 +35,75 @@ const serveModes = [ }, ]; - const SelectorBlock = ({id, value, selected, children}) => html` -
+
${children} -
`; - +
+`; const ServeModeSelector = ({serveMode}) => html`
+ id="serve-mode-form" + > + name="mode" + > ${joinFragments(serveModes, ({value, description}) => SelectorBlock({ id: `serve_mode_${value}`, value, selected: serveMode == value, - children: html`${value} -

${description}

`, - }))} + children: html` + ${value} +

${description}

+ `, + }) + )}
-
`; - + +`; -const SettingsOpenButton = () => html`
html` +
+ class="settings-cog-icon icon" + > Settings -
`; +
+`; - -const SettingsCloseButton = () => html`
html` +
+ class="close-icon icon" + > Close Settings -
`; - +
+`; -const SettingsModal = ({serveMode}) => html` -
+const SettingsModal = ({serveMode}) => html` + +
${SettingsCloseButton()} @@ -106,7 +117,7 @@ const SettingsModal = ({serveMode}) => html`
- `; - + +`; module.exports = {SettingsModal, SettingsOpenButton}; diff --git a/build-system/app-index/template.js b/build-system/app-index/template.js index 368dee2fd157a..db23a5ed37d24 100644 --- a/build-system/app-index/template.js +++ b/build-system/app-index/template.js @@ -26,14 +26,13 @@ const {FileList} = require('./file-list'); const {html, joinFragments} = require('./html'); const {SettingsModal, SettingsOpenButton} = require('./settings'); - const HeaderLink = ({name, href, divider}) => html`
  • ${name} -
  • `; - + +`; const Header = ({isMainPage, links}) => html`
    @@ -43,31 +42,26 @@ const Header = ({isMainPage, links}) => html`
      ${joinFragments(links, ({name, href, divider}, i) => - HeaderLink({ - divider: divider || i == links.length - 1, - name, - href, - }))} + HeaderLink({ + divider: divider || i == links.length - 1, + name, + href, + }) + )}
    • ${SettingsOpenButton()}
    - `; - + +`; -const HeaderBackToMainLink = () => html`← Back to main`; - - -const ProxyFormOptional = ({isMainPage}) => isMainPage ? ProxyForm() : ''; +const HeaderBackToMainLink = () => + html` + ← Back to main + `; +const ProxyFormOptional = ({isMainPage}) => (isMainPage ? ProxyForm() : ''); function renderTemplate(opt_params) { - const { - basepath, - css, - isMainPage, - fileSet, - serveMode, - selectModePrefix, - } = { + const {basepath, css, isMainPage, fileSet, serveMode, selectModePrefix} = { basepath: '/', isMainPage: false, fileSet: [], @@ -77,17 +71,21 @@ function renderTemplate(opt_params) { }; const body = joinFragments([ - html`
    - ${Header({isMainPage, links: headerLinks})} - ${ProxyFormOptional({isMainPage})} -
    `, + html` +
    + ${Header({isMainPage, links: headerLinks})} + ${ProxyFormOptional({isMainPage})} +
    + `, FileList({basepath, selectModePrefix, fileSet}), - html`
    - Built with 💙 by - the AMP Project. -
    `, + html` +
    + Built with 💙 by + the AMP Project. +
    + `, SettingsModal({serveMode}), ]); @@ -97,5 +95,4 @@ function renderTemplate(opt_params) { return addRequiredExtensionsToHead(docWithoutExtensions); } - module.exports = {renderTemplate}; diff --git a/build-system/app-index/url.js b/build-system/app-index/url.js index 5c64de143b0c9..91a5fa43e9033 100644 --- a/build-system/app-index/url.js +++ b/build-system/app-index/url.js @@ -14,17 +14,17 @@ * limitations under the License. */ - const appendQueryParamsToUrl = (url, params) => - url + '?' + Object.keys(params).map(k => - `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`).join('&'); - + url + + '?' + + Object.keys(params) + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) + .join('&'); const leadingSlashRegex = /^\//; const replaceLeadingSlash = (subject, replacement) => subject.replace(leadingSlashRegex, replacement); - module.exports = { appendQueryParamsToUrl, replaceLeadingSlash, diff --git a/build-system/app-utils.js b/build-system/app-utils.js index 6d6da6d4936ff..a87a6570f4e1b 100644 --- a/build-system/app-utils.js +++ b/build-system/app-utils.js @@ -27,24 +27,30 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { // TODO:(ccordry) remove this when story 0.1 is deprecated if (storyV1) { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/amp-story-0\.1\.js/g, - hostName + '/dist/v0/amp-story-1.0.max.js'); + /https:\/\/cdn\.ampproject\.org\/v0\/amp-story-0\.1\.js/g, + hostName + '/dist/v0/amp-story-1.0.max.js' + ); } file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\.js/g, - hostName + '/dist/amp.js'); + /https:\/\/cdn\.ampproject\.org\/v0\.js/g, + hostName + '/dist/amp.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, - hostName + '/dist/amp-shadow.js'); + /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, + hostName + '/dist/amp-shadow.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, - hostName + '/dist/amp-inabox.js'); + /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, + hostName + '/dist/amp-inabox.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, - hostName + '/dist/video-iframe-integration.js'); + /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, + hostName + '/dist/video-iframe-integration.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, - hostName + '/dist/v0/$1.max.js'); + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + hostName + '/dist/v0/$1.max.js' + ); if (inabox) { let filename; if (inabox == '1') { @@ -57,23 +63,29 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { } } else if (mode == 'compiled') { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\.js/g, - hostName + '/dist/v0.js'); + /https:\/\/cdn\.ampproject\.org\/v0\.js/g, + hostName + '/dist/v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, - hostName + '/dist/shadow-v0.js'); + /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, + hostName + '/dist/shadow-v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, - hostName + '/dist/amp4ads-v0.js'); + /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, + hostName + '/dist/amp4ads-v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, - hostName + '/dist/video-iframe-integration-v0.js'); + /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, + hostName + '/dist/video-iframe-integration-v0.js' + ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, - hostName + '/dist/v0/$1.js'); + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + hostName + '/dist/v0/$1.js' + ); file = file.replace( - /\/dist.3p\/current\/(.*)\.max.html/g, - hostName + '/dist.3p/current-min/$1.html'); + /\/dist.3p\/current\/(.*)\.max.html/g, + hostName + '/dist.3p/current-min/$1.html' + ); if (inabox) { let filename; if (inabox == '1') { diff --git a/build-system/app.js b/build-system/app.js index ad0e25906f6ec..045aadfa59e55 100644 --- a/build-system/app.js +++ b/build-system/app.js @@ -55,7 +55,8 @@ app.use('/list/', require('./routes/list')); app.use((req, res, next) => { if (req.query.csp) { res.set({ - 'content-security-policy': "default-src * blob: data:; script-src https://cdn.ampproject.org/rtv/ https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/ https://cdn.ampproject.org/viewer/ http://localhost:8000 https://localhost:8000; object-src 'none'; style-src 'unsafe-inline' https://cdn.ampproject.org/rtv/ https://cdn.materialdesignicons.com https://cloud.typography.com https://fast.fonts.net https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://p.typekit.net https://use.fontawesome.com https://use.typekit.net; report-uri https://csp-collector.appspot.com/csp/amp", + 'content-security-policy': + "default-src * blob: data:; script-src https://cdn.ampproject.org/rtv/ https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/ https://cdn.ampproject.org/viewer/ http://localhost:8000 https://localhost:8000; object-src 'none'; style-src 'unsafe-inline' https://cdn.ampproject.org/rtv/ https://cdn.materialdesignicons.com https://cloud.typography.com https://fast.fonts.net https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://p.typekit.net https://use.fontawesome.com https://use.typekit.net; report-uri https://csp-collector.appspot.com/csp/amp", }); } next(); @@ -85,7 +86,6 @@ if (!global.AMP_TESTING) { devDashboard.installExpressMiddleware(app); } - // Changes the current serve mode via query param // e.g. /serve_mode_change?mode=(default|compiled|cdn) // (See ./app-index/settings.js) @@ -103,7 +103,6 @@ app.get('/serve_mode_change', (req, res) => { res.status(400).json({ok: false}); }); - // Redirects to a proxied document with optional mode through query params. // // Mode can be one of: @@ -122,11 +121,10 @@ app.get('/serve_mode_change', (req, res) => { // // This passthrough is useful to generate the URL from
    values, // (See ./app-index/proxy-form.js) -app.get('/proxy', async(req, res, next) => { +app.get('/proxy', async (req, res, next) => { const {mode, url} = req.query; const urlSuffixClearPrefixReStr = - '^https?://' + - '((www\.)?google\.(com?|[a-z]{2}|com?\.[a-z]{2}|cat)/amp/s/)?'; + '^https?://' + '((www.)?google.(com?|[a-z]{2}|com?.[a-z]{2}|cat)/amp/s/)?'; const urlSuffix = url.replace(new RegExp(urlSuffixClearPrefixReStr, 'i'), ''); try { @@ -141,7 +139,6 @@ app.get('/proxy', async(req, res, next) => { } }); - /** * Resolves an AMPHTML URL from a canonical URL. If AMPHTML is canonical, same * URL is returned. @@ -154,9 +151,10 @@ function requestAmphtmlDocUrl(urlSuffix, protocol = 'https') { console.log(`Fetching URL: ${defaultUrl}`); return new Promise((resolve, reject) => { request(defaultUrl, (error, response, body) => { - if (error || (response && ( - response.statusCode < 200 || response.statusCode >= 300))) { - + if ( + error || + (response && (response.statusCode < 200 || response.statusCode >= 300)) + ) { if (protocol == 'https') { return requestAmphtmlDocUrl(urlSuffix, 'http'); } @@ -181,25 +179,22 @@ function requestAmphtmlDocUrl(urlSuffix, protocol = 'https') { * integration tests. Using this to mock * out the recaptcha api. */ -app.get( - '/dist.3p/*/recaptcha.*html', - recaptchaFrameRequestHandler -); -app.use( - '/recaptcha', - recaptchaRouter -); +app.get('/dist.3p/*/recaptcha.*html', recaptchaFrameRequestHandler); +app.use('/recaptcha', recaptchaRouter); // Deprecate usage of .min.html/.max.html -app.get([ - '/examples/*.(min|max).html', - '/test/manual/*.(min|max).html', - '/test/fixtures/e2e/*/*.(min|max).html', - '/dist/cache-sw.(min|max).html', -], (req, res) => { - const filePath = req.url; - res.send(generateInfo(filePath)); -}); +app.get( + [ + '/examples/*.(min|max).html', + '/test/manual/*.(min|max).html', + '/test/fixtures/e2e/*/*.(min|max).html', + '/dist/cache-sw.(min|max).html', + ], + (req, res) => { + const filePath = req.url; + res.send(generateInfo(filePath)); + } +); app.use('/pwa', (req, res) => { let file; @@ -280,14 +275,12 @@ app.use('/form/html/post', (req, res) => { }); }); - app.use('/form/redirect-to/post', (req, res) => { cors.assertCors(req, res, ['POST'], ['AMP-Redirect-To']); res.setHeader('AMP-Redirect-To', 'https://google.com'); res.end('{}'); }); - app.use('/form/echo-json/post', (req, res) => { cors.assertCors(req, res, ['POST']); const form = new formidable.IncomingForm(); @@ -305,21 +298,28 @@ app.use('/form/json/poll1', (req, res) => { const form = new formidable.IncomingForm(); form.parse(req, () => { res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - result: [{ - answer: 'Penguins', - percentage: new Array(77), - }, { - answer: 'Ostriches', - percentage: new Array(8), - }, { - answer: 'Kiwis', - percentage: new Array(14), - }, { - answer: 'Wekas', - percentage: new Array(1), - }], - })); + res.end( + JSON.stringify({ + result: [ + { + answer: 'Penguins', + percentage: new Array(77), + }, + { + answer: 'Ostriches', + percentage: new Array(8), + }, + { + answer: 'Kiwis', + percentage: new Array(14), + }, + { + answer: 'Wekas', + percentage: new Array(1), + }, + ], + }) + ); }); }); @@ -351,7 +351,6 @@ app.use('/form/search-html/get', (req, res) => { `); }); - app.use('/form/search-json/get', (req, res) => { cors.assertCors(req, res, ['GET']); res.json({ @@ -361,8 +360,17 @@ app.use('/form/search-json/get', (req, res) => { }); }); -const autocompleteColors = ['red', 'orange', 'yellow', 'green', 'blue', - 'purple', 'pink', 'black', 'white']; +const autocompleteColors = [ + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'purple', + 'pink', + 'black', + 'white', +]; app.use('/form/autocomplete/query', (req, res) => { const query = req.query.q; @@ -370,32 +378,63 @@ app.use('/form/autocomplete/query', (req, res) => { res.json({items: autocompleteColors}); } else { const lowerCaseQuery = query.toLowerCase(); - const filtered = autocompleteColors.filter( - l => l.toLowerCase().includes(lowerCaseQuery)); + const filtered = autocompleteColors.filter(l => + l.toLowerCase().includes(lowerCaseQuery) + ); res.json({items: filtered}); } }); -const autosuggestLanguages = ['ActionScript', 'AppleScript', 'Asp', 'BASIC', - 'C', 'C++', 'Clojure', 'COBOL', 'ColdFusion', 'Erlang', 'Fortran', 'Go', - 'Groovy', 'Haskell', 'Java', 'JavaScript', 'Lisp', 'Perl', 'PHP', 'Python', - 'Ruby', 'Scala', 'Scheme']; +const autosuggestLanguages = [ + 'ActionScript', + 'AppleScript', + 'Asp', + 'BASIC', + 'C', + 'C++', + 'Clojure', + 'COBOL', + 'ColdFusion', + 'Erlang', + 'Fortran', + 'Go', + 'Groovy', + 'Haskell', + 'Java', + 'JavaScript', + 'Lisp', + 'Perl', + 'PHP', + 'Python', + 'Ruby', + 'Scala', + 'Scheme', +]; app.use('/form/autosuggest/query', (req, res) => { cors.assertCors(req, res, ['GET']); const MAX_RESULTS = 4; const query = req.query.q; if (!query) { - res.json({items: [{ - results: autosuggestLanguages.slice(0, MAX_RESULTS), - }]}); + res.json({ + items: [ + { + results: autosuggestLanguages.slice(0, MAX_RESULTS), + }, + ], + }); } else { const lowerCaseQuery = query.toLowerCase(); - const filtered = autosuggestLanguages.filter( - l => l.toLowerCase().includes(lowerCaseQuery)); - res.json({items: [{ - results: filtered.slice(0, MAX_RESULTS)}, - ]}); + const filtered = autosuggestLanguages.filter(l => + l.toLowerCase().includes(lowerCaseQuery) + ); + res.json({ + items: [ + { + results: filtered.slice(0, MAX_RESULTS), + }, + ], + }); } }); @@ -429,19 +468,21 @@ app.use('/form/verify-search-json/post', (req, res) => { if (fields.city !== 'Mountain View' || fields.zip !== '94043') { errors.push({ name: 'city', - message: 'City doesn\'t match zip (Mountain View and 94043)', + message: "City doesn't match zip (Mountain View and 94043)", }); } if (errors.length === 0) { - res.end(JSON.stringify({ - results: [ - {title: 'Result 1'}, - {title: 'Result 2'}, - {title: 'Result 3'}, - ], - committed: true, - })); + res.end( + JSON.stringify({ + results: [ + {title: 'Result 1'}, + {title: 'Result 2'}, + {title: 'Result 3'}, + ], + committed: true, + }) + ); } else { res.statusCode = 400; res.end(JSON.stringify({verifyErrors: errors})); @@ -450,8 +491,10 @@ app.use('/form/verify-search-json/post', (req, res) => { }); app.use('/share-tracking/get-outgoing-fragment', (req, res) => { - res.setHeader('AMP-Access-Control-Allow-Source-Origin', - req.protocol + '://' + req.headers.host); + res.setHeader( + 'AMP-Access-Control-Allow-Source-Origin', + req.protocol + '://' + req.headers.host + ); res.json({ fragment: '54321', }); @@ -460,17 +503,20 @@ app.use('/share-tracking/get-outgoing-fragment', (req, res) => { // Fetches an AMP document from the AMP proxy and replaces JS // URLs, so that they point to localhost. function proxyToAmpProxy(req, res, mode) { - const url = 'https://cdn.ampproject.org/' - + (req.query['amp_js_v'] ? 'v' : 'c') - + req.url; + const url = + 'https://cdn.ampproject.org/' + + (req.query['amp_js_v'] ? 'v' : 'c') + + req.url; console.log('Fetching URL: ' + url); request(url, function(error, response, body) { body = body - // Unversion URLs. - .replace(/https\:\/\/cdn\.ampproject\.org\/rtv\/\d+\//g, - 'https://cdn.ampproject.org/') - // href pointing to the proxy, so that images, etc. still work. - .replace('', ''); + // Unversion URLs. + .replace( + /https\:\/\/cdn\.ampproject\.org\/rtv\/\d+\//g, + 'https://cdn.ampproject.org/' + ) + // href pointing to the proxy, so that images, etc. still work. + .replace('', ''); const inabox = req.query['inabox']; // TODO(ccordry): Remove this when story v01 is depricated. const storyV1 = req.query['story_v'] === '1'; @@ -485,7 +531,6 @@ function proxyToAmpProxy(req, res, mode) { }); } - let itemCtr = 2; const doctype = '\n'; const liveListDocs = Object.create(null); @@ -500,7 +545,7 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { // When we already have state in memory and user refreshes page, we flush // the dom we maintain on the server. if (!('amp_latest_update_time' in req.query) && liveListDoc) { - let outerHTML = liveListDoc.documentElement./*OK*/outerHTML; + let outerHTML = liveListDoc.documentElement./*OK*/ outerHTML; outerHTML = replaceUrls(mode, outerHTML); res.send(`${doctype}${outerHTML}`); return; @@ -509,8 +554,9 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { const liveListUpdateFullPath = `${pc.cwd()}${req.baseUrl}`; console.log('liveListUpdateFullPath', liveListUpdateFullPath); const liveListFile = fs.readFileSync(liveListUpdateFullPath); - liveListDoc = liveListDocs[req.baseUrl] = new jsdom.JSDOM(liveListFile) - .window.document; + liveListDoc = liveListDocs[req.baseUrl] = new jsdom.JSDOM( + liveListFile + ).window.document; liveListDoc.ctr = 0; } const liveList = liveListDoc.querySelector('#my-live-list'); @@ -519,35 +565,38 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { const pagination = liveListDoc.querySelector('#my-live-list [pagination]'); const item1 = liveList.querySelector('#list-item-1'); if (liveListDoc.ctr != 0) { - if (Math.random() < .8) { + if (Math.random() < 0.8) { // Always run a replace on the first item liveListReplace(item1); - if (Math.random() < .5) { + if (Math.random() < 0.5) { liveListTombstone(liveList); } - if (Math.random() < .8) { + if (Math.random() < 0.8) { liveListInsert(liveList, item1); } pagination.textContent = ''; - const liveChildren = [].slice.call(items.children) - .filter(x => !x.hasAttribute('data-tombstone')); + const liveChildren = [].slice + .call(items.children) + .filter(x => !x.hasAttribute('data-tombstone')); const pageCount = Math.ceil(liveChildren.length / perPage); const pageListItems = Array.apply(null, Array(pageCount)) - .map((_, i) => `
  • ${i + 1}
  • `).join(''); - const newPagination = ''; - pagination./*OK*/innerHTML = newPagination; + .map((_, i) => `
  • ${i + 1}
  • `) + .join(''); + const newPagination = + ''; + pagination./*OK*/ innerHTML = newPagination; } else { // Sometimes we want an empty response to simulate no changes. res.send(`${doctype}`); return; } } - let outerHTML = liveListDoc.documentElement./*OK*/outerHTML; + let outerHTML = liveListDoc.documentElement./*OK*/ outerHTML; outerHTML = replaceUrls(mode, outerHTML); liveListDoc.ctr++; res.send(`${doctype}${outerHTML}`); @@ -577,19 +626,19 @@ function liveListTombstone(liveList) { // We can tombstone any list item except item-1 since we always do a // replace example on item-1. if (tombstoneId != 1) { - const item = liveList./*OK*/querySelector(`#list-item-${tombstoneId}`); + const item = liveList./*OK*/ querySelector(`#list-item-${tombstoneId}`); if (item) { item.setAttribute('data-tombstone', ''); } } } - // Generate a random number between min and max // Value is inclusive of both min and max values. function range(min, max) { - const values = - Array.apply(null, new Array(max - min + 1)).map((_, i) => min + i); + const values = Array.apply(null, new Array(max - min + 1)).map( + (_, i) => min + i + ); return values[Math.round(Math.random() * (max - min))]; } @@ -603,12 +652,18 @@ function getLiveBlogItem() { // Generate a 3 to 7 worded headline const headline = bacon(range(3, 7)); const numOfParagraphs = range(1, 2); - const body = Array.apply(null, new Array(numOfParagraphs)).map(() => { - return `

    ${bacon(range(50, 90))}

    `; - }).join('\n'); + const body = Array.apply(null, new Array(numOfParagraphs)) + .map(() => { + return `

    ${bacon(range(50, 90))}

    `; + }) + .join('\n'); const img = ` `; @@ -651,9 +706,11 @@ function getLiveBlogItemWithBindAttributes() { const now = Date.now(); // Generate a 3 to 7 worded headline const numOfParagraphs = range(1, 2); - const body = Array.apply(null, new Array(numOfParagraphs)).map(() => { - return `

    ${bacon(range(50, 90))}

    `; - }).join('\n'); + const body = Array.apply(null, new Array(numOfParagraphs)) + .map(() => { + return `

    ${bacon(range(50, 90))}

    `; + }) + .join('\n'); return ` @@ -670,25 +727,26 @@ function getLiveBlogItemWithBindAttributes() { `; } -app.use('/examples/live-blog(-non-floating-button)?.amp.html', - (req, res, next) => { - if ('amp_latest_update_time' in req.query) { - res.setHeader('Content-Type', 'text/html'); - res.end(getLiveBlogItem()); - return; - } - next(); - }); +app.use( + '/examples/live-blog(-non-floating-button)?.amp.html', + (req, res, next) => { + if ('amp_latest_update_time' in req.query) { + res.setHeader('Content-Type', 'text/html'); + res.end(getLiveBlogItem()); + return; + } + next(); + } +); -app.use('/examples/bind/live-list.amp.html', - (req, res, next) => { - if ('amp_latest_update_time' in req.query) { - res.setHeader('Content-Type', 'text/html'); - res.end(getLiveBlogItemWithBindAttributes()); - return; - } - next(); - }); +app.use('/examples/bind/live-list.amp.html', (req, res, next) => { + if ('amp_latest_update_time' in req.query) { + res.setHeader('Content-Type', 'text/html'); + res.end(getLiveBlogItemWithBindAttributes()); + return; + } + next(); +}); app.use('/impression-proxy/', (req, res) => { cors.assertCors(req, res, ['GET']); @@ -755,16 +813,19 @@ app.get('/a4a_template/*', (req, res) => { res.end('Invalid path: ' + req.path); return; } - const filePath = `${pc.cwd()}/extensions/amp-ad-network-${match[1]}-impl/` + - `0.1/data/${match[2]}.template`; - fs.readFileAsync(filePath).then(file => { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('AMP-template-amp-creative', 'amp-mustache'); - res.end(file); - }).error(() => { - res.status(404); - res.end('Not found: ' + filePath); - }); + const filePath = + `${pc.cwd()}/extensions/amp-ad-network-${match[1]}-impl/` + + `0.1/data/${match[2]}.template`; + fs.readFileAsync(filePath) + .then(file => { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('AMP-template-amp-creative', 'amp-mustache'); + res.end(file); + }) + .error(() => { + res.status(404); + res.end('Not found: ' + filePath); + }); }); // Returns a document that echoes any post messages received from parent. @@ -775,7 +836,7 @@ app.get('/a4a_template/*', (req, res) => { app.get('/iframe-echo-message', (req, res) => { const {message} = req.query; res.send( - ` + ` - `); + ` + ); }); // A4A envelope. @@ -801,8 +863,7 @@ app.use('/a4a(|-3p)/', (req, res) => { let adUrl = req.url; const templatePath = '/build-system/server-a4a-template.html'; const urlPrefix = getUrlPrefix(req); - if (!adUrl.startsWith('/proxy') && - urlPrefix.indexOf('//localhost') != -1) { + if (!adUrl.startsWith('/proxy') && urlPrefix.indexOf('//localhost') != -1) { // This is a special case for testing. `localhost` URLs are transformed to // `ads.localhost` to ensure that the iframe is fully x-origin. adUrl = urlPrefix.replace('localhost', 'ads.localhost') + adUrl; @@ -810,12 +871,12 @@ app.use('/a4a(|-3p)/', (req, res) => { adUrl = addQueryParam(adUrl, 'inabox', 1); fs.readFileAsync(pc.cwd() + templatePath, 'utf8').then(template => { const result = template - .replace(/CHECKSIG/g, force3p || '') - .replace(/DISABLE3PFALLBACK/g, !force3p) - .replace(/OFFSET/g, req.query.offset || '0px') - .replace(/AD_URL/g, adUrl) - .replace(/AD_WIDTH/g, req.query.width || '300') - .replace(/AD_HEIGHT/g, req.query.height || '250'); + .replace(/CHECKSIG/g, force3p || '') + .replace(/DISABLE3PFALLBACK/g, !force3p) + .replace(/OFFSET/g, req.query.offset || '0px') + .replace(/AD_URL/g, adUrl) + .replace(/AD_WIDTH/g, req.query.width || '300') + .replace(/AD_HEIGHT/g, req.query.height || '250'); res.end(result); }); }); @@ -828,8 +889,10 @@ app.use('/inabox/:version/', (req, res) => { let adUrl = req.url; const templatePath = '/build-system/server-inabox-template.html'; const urlPrefix = getUrlPrefix(req); - if (!adUrl.startsWith('/proxy') && // Ignore /proxy - urlPrefix.indexOf('//localhost') != -1) { + if ( + !adUrl.startsWith('/proxy') && // Ignore /proxy + urlPrefix.indexOf('//localhost') != -1 + ) { // This is a special case for testing. `localhost` URLs are transformed to // `ads.localhost` to ensure that the iframe is fully x-origin. adUrl = urlPrefix.replace('localhost', 'ads.localhost') + adUrl; @@ -837,10 +900,10 @@ app.use('/inabox/:version/', (req, res) => { adUrl = addQueryParam(adUrl, 'inabox', req.params['version']); fs.readFileAsync(pc.cwd() + templatePath, 'utf8').then(template => { const result = template - .replace(/AD_URL/g, adUrl) - .replace(/OFFSET/g, req.query.offset || '0px') - .replace(/AD_WIDTH/g, req.query.width || '300') - .replace(/AD_HEIGHT/g, req.query.height || '250'); + .replace(/AD_URL/g, adUrl) + .replace(/OFFSET/g, req.query.offset || '0px') + .replace(/AD_WIDTH/g, req.query.width || '300') + .replace(/AD_HEIGHT/g, req.query.height || '250'); res.end(result); }); }); @@ -850,14 +913,16 @@ app.use('/examples/analytics.config.json', (req, res, next) => { next(); }); -app.use(['/examples/*', '/extensions/*', '/test/manual/*'], - (req, res, next) => { - const sourceOrigin = req.query['__amp_source_origin']; - if (sourceOrigin) { - res.setHeader('AMP-Access-Control-Allow-Source-Origin', sourceOrigin); - } - next(); - }); +app.use( + ['/examples/*', '/extensions/*', '/test/manual/*'], + (req, res, next) => { + const sourceOrigin = req.query['__amp_source_origin']; + if (sourceOrigin) { + res.setHeader('AMP-Access-Control-Allow-Source-Origin', sourceOrigin); + } + next(); + } +); /** * Append ?sleep=5 to any included JS file in examples to emulate delay in @@ -879,66 +944,78 @@ app.use(['/dist/v0/amp-*.js'], (req, res, next) => { */ app.get('/test/manual/amp-video.amp.html', runVideoTestBench); -app.get([ - '/examples/*.html', - '/test/manual/*.html', - '/test/fixtures/e2e/*/*.html'], (req, res, next) => { - const filePath = req.path; - const mode = pc.env.SERVE_MODE; - const inabox = req.query['inabox']; - const stream = Number(req.query['stream']); - fs.readFileAsync(pc.cwd() + filePath, 'utf8').then(file => { - if (req.query['amp_js_v']) { - file = addViewerIntegrationScript(req.query['amp_js_v'], file); - } - file = file.replace(/__TEST_SERVER_PORT__/g, TEST_SERVER_PORT); +app.get( + ['/examples/*.html', '/test/manual/*.html', '/test/fixtures/e2e/*/*.html'], + (req, res, next) => { + const filePath = req.path; + const mode = pc.env.SERVE_MODE; + const inabox = req.query['inabox']; + const stream = Number(req.query['stream']); + fs.readFileAsync(pc.cwd() + filePath, 'utf8') + .then(file => { + if (req.query['amp_js_v']) { + file = addViewerIntegrationScript(req.query['amp_js_v'], file); + } + file = file.replace(/__TEST_SERVER_PORT__/g, TEST_SERVER_PORT); - if (inabox && req.headers.origin && req.query.__amp_source_origin) { - // Allow CORS requests for A4A. - cors.enableCors(req, res, req.headers.origin); - } else { - file = replaceUrls(mode, file, '', inabox); - } + if (inabox && req.headers.origin && req.query.__amp_source_origin) { + // Allow CORS requests for A4A. + cors.enableCors(req, res, req.headers.origin); + } else { + file = replaceUrls(mode, file, '', inabox); + } - // Extract amp-ad for the given 'type' specified in URL query. - if (req.path.indexOf('/examples/ads.amp.html') == 0 && req.query.type) { - const ads = file.match( - elementExtractor('(amp-ad|amp-embed)', req.query.type)); - file = file.replace( - /[\s\S]+<\/body>/m, '' + ads.join('') + ''); - } + // Extract amp-ad for the given 'type' specified in URL query. + if (req.path.indexOf('/examples/ads.amp.html') == 0 && req.query.type) { + const ads = file.match( + elementExtractor('(amp-ad|amp-embed)', req.query.type) + ); + file = file.replace( + /[\s\S]+<\/body>/m, + '' + ads.join('') + '' + ); + } - // Extract amp-analytics for the given 'type' specified in URL query. - if (req.path.indexOf( - '/examples/analytics-vendors.amp.html') == 0 && req.query.type) { - const analytics = file.match( - elementExtractor('amp-analytics', req.query.type)); - file = file.replace( - /
    [\s\S]+<\/div>/m, - '
    ' + analytics.join('') + '
    '); - } + // Extract amp-analytics for the given 'type' specified in URL query. + if ( + req.path.indexOf('/examples/analytics-vendors.amp.html') == 0 && + req.query.type + ) { + const analytics = file.match( + elementExtractor('amp-analytics', req.query.type) + ); + file = file.replace( + /
    [\s\S]+<\/div>/m, + '
    ' + analytics.join('') + '
    ' + ); + } - if (stream > 0) { - res.writeHead(200, {'Content-Type': 'text/html'}); - let pos = 0; - const writeChunk = function() { - const chunk = file.substring(pos, Math.min(pos + stream, file.length)); - res.write(chunk); - pos += stream; - if (pos < file.length) { - setTimeout(writeChunk, 500); + if (stream > 0) { + res.writeHead(200, {'Content-Type': 'text/html'}); + let pos = 0; + const writeChunk = function() { + const chunk = file.substring( + pos, + Math.min(pos + stream, file.length) + ); + res.write(chunk); + pos += stream; + if (pos < file.length) { + setTimeout(writeChunk, 500); + } else { + res.end(); + } + }; + writeChunk(); } else { - res.end(); + res.send(file); } - }; - writeChunk(); - } else { - res.send(file); - } - }).catch(() => { - next(); - }); -}); + }) + .catch(() => { + next(); + }); + } +); appTestEndpoints(app); @@ -949,8 +1026,9 @@ function escapeRegExp(string) { function elementExtractor(tagName, type) { type = escapeRegExp(type); return new RegExp( - `<${tagName}[\\s][^>]*['"]${type}['"][^>]*>([\\s\\S]+?)`, - 'gm'); + `<${tagName}[\\s][^>]*['"]${type}['"][^>]*>([\\s\\S]+?)`, + 'gm' + ); } // Data for example: http://localhost:8000/examples/bind/xhr.amp.html @@ -1062,75 +1140,81 @@ app.get('/adzerk/*', (req, res) => { return; } const filePath = - pc.cwd() + '/extensions/amp-ad-network-adzerk-impl/0.1/data/' + match[1]; - fs.readFileAsync(filePath).then(file => { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('AMP-Ad-Template-Extension', 'amp-mustache'); - res.setHeader('AMP-Ad-Response-Type', 'template'); - res.end(file); - }).error(() => { - res.status(404); - res.end('Not found: ' + filePath); - }); + pc.cwd() + '/extensions/amp-ad-network-adzerk-impl/0.1/data/' + match[1]; + fs.readFileAsync(filePath) + .then(file => { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('AMP-Ad-Template-Extension', 'amp-mustache'); + res.setHeader('AMP-Ad-Response-Type', 'template'); + res.end(file); + }) + .error(() => { + res.status(404); + res.end('Not found: ' + filePath); + }); }); /* * Serve extension scripts and their source maps. */ -app.get(['/dist/rtv/*/v0/*.js', '/dist/rtv/*/v0/*.js.map'], - (req, res, next) => { - const mode = pc.env.SERVE_MODE; - const fileName = path.basename(req.path).replace('.max.', '.'); - let filePath = 'https://cdn.ampproject.org/v0/' + fileName; - if (mode == 'cdn') { - // This will not be useful until extension-location.js change in prod - // Require url from cdn - request(filePath, (error, response) => { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); - return; - } - const isJsMap = filePath.endsWith('.map'); - if (isJsMap) { - filePath = filePath.replace(/\.js\.map$/, '\.js'); - } - filePath = replaceUrls(mode, filePath); - req.url = filePath + (isJsMap ? '.map' : ''); - next(); - }); +app.get( + ['/dist/rtv/*/v0/*.js', '/dist/rtv/*/v0/*.js.map'], + (req, res, next) => { + const mode = pc.env.SERVE_MODE; + const fileName = path.basename(req.path).replace('.max.', '.'); + let filePath = 'https://cdn.ampproject.org/v0/' + fileName; + if (mode == 'cdn') { + // This will not be useful until extension-location.js change in prod + // Require url from cdn + request(filePath, (error, response) => { + if (error) { + res.status(404); + res.end(); + } else { + res.send(response); + } + }); + return; + } + const isJsMap = filePath.endsWith('.map'); + if (isJsMap) { + filePath = filePath.replace(/\.js\.map$/, '.js'); + } + filePath = replaceUrls(mode, filePath); + req.url = filePath + (isJsMap ? '.map' : ''); + next(); + } +); /** * Serve entry point script url */ -app.get(['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], - (req, res, next) => { - // Special case for entry point script url. Use compiled for testing - const mode = pc.env.SERVE_MODE; - const fileName = path.basename(req.path); - if (mode == 'cdn') { - // This will not be useful until extension-location.js change in prod - // Require url from cdn - const filePath = 'https://cdn.ampproject.org/' + fileName; - request(filePath, function(error, response) { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); - return; - } - if (mode == 'default') { - req.url = req.url.replace(/\.js$/, '.max.js'); - } - next(); - }); +app.get( + ['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], + (req, res, next) => { + // Special case for entry point script url. Use compiled for testing + const mode = pc.env.SERVE_MODE; + const fileName = path.basename(req.path); + if (mode == 'cdn') { + // This will not be useful until extension-location.js change in prod + // Require url from cdn + const filePath = 'https://cdn.ampproject.org/' + fileName; + request(filePath, function(error, response) { + if (error) { + res.status(404); + res.end(); + } else { + res.send(response); + } + }); + return; + } + if (mode == 'default') { + req.url = req.url.replace(/\.js$/, '.max.js'); + } + next(); + } +); app.get('/dist/iframe-transport-client-lib.js', (req, res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); @@ -1150,19 +1234,26 @@ app.get('/dist/amp-inabox-host.js', (req, res, next) => { */ app.get('/dist/sw(.max)?.js', (req, res, next) => { const filePath = req.path; - fs.readFileAsync(pc.cwd() + filePath, 'utf8').then(file => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= ((n.getMinutes() % 5) * 1000 * 60) - + (n.getSeconds() * 1000) + n.getMilliseconds(); - file = 'self.AMP_CONFIG = {v: "99' + n + '",' + - 'cdnUrl: "http://localhost:8000/dist"};' - + file; - res.setHeader('Content-Type', 'application/javascript'); - res.setHeader('Date', new Date().toUTCString()); - res.setHeader('Cache-Control', 'no-cache;max-age=150'); - res.end(file); - }).catch(next); + fs.readFileAsync(pc.cwd() + filePath, 'utf8') + .then(file => { + let n = new Date(); + // Round down to the nearest 5 minutes. + n -= + (n.getMinutes() % 5) * 1000 * 60 + + n.getSeconds() * 1000 + + n.getMilliseconds(); + file = + 'self.AMP_CONFIG = {v: "99' + + n + + '",' + + 'cdnUrl: "http://localhost:8000/dist"};' + + file; + res.setHeader('Content-Type', 'application/javascript'); + res.setHeader('Date', new Date().toUTCString()); + res.setHeader('Cache-Control', 'no-cache;max-age=150'); + res.end(file); + }) + .catch(next); }); app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { @@ -1174,10 +1265,12 @@ app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { // Cause a delay, to show the "stale-while-revalidate" if (req.path.includes('v0.js')) { const path = req.path.replace(/rtv\/\d+/, ''); - return fs.readFileAsync(pc.cwd() + path, 'utf8') - .then(file => { - res.end(file); - }).catch(next); + return fs + .readFileAsync(pc.cwd() + path, 'utf8') + .then(file => { + res.end(file); + }) + .catch(next); } res.end(` @@ -1190,30 +1283,36 @@ app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { app.get(['/dist/cache-sw.html'], (req, res, next) => { const filePath = '/test/manual/cache-sw.html'; - fs.readFileAsync(pc.cwd() + filePath, 'utf8').then(file => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= ((n.getMinutes() % 5) * 1000 * 60) - + (n.getSeconds() * 1000) + n.getMilliseconds(); - const percent = parseFloat(req.query.canary) || 0.01; - let env = '99'; - if (Math.random() < percent) { - env = '98'; - n += 5 * 1000 * 60; - } - file = file.replace(/dist\/v0/g, `dist/rtv/${env}${n}/v0`); - file = file.replace(/CURRENT_RTV/, env + n); + fs.readFileAsync(pc.cwd() + filePath, 'utf8') + .then(file => { + let n = new Date(); + // Round down to the nearest 5 minutes. + n -= + (n.getMinutes() % 5) * 1000 * 60 + + n.getSeconds() * 1000 + + n.getMilliseconds(); + const percent = parseFloat(req.query.canary) || 0.01; + let env = '99'; + if (Math.random() < percent) { + env = '98'; + n += 5 * 1000 * 60; + } + file = file.replace(/dist\/v0/g, `dist/rtv/${env}${n}/v0`); + file = file.replace(/CURRENT_RTV/, env + n); - res.setHeader('Content-Type', 'text/html'); - res.end(file); - }).catch(next); + res.setHeader('Content-Type', 'text/html'); + res.end(file); + }) + .catch(next); }); app.get('/dist/diversions', (req, res) => { let n = new Date(); // Round down to the nearest 5 minutes. - n -= ((n.getMinutes() % 5) * 1000 * 60) - + (n.getSeconds() * 1000) + n.getMilliseconds(); + n -= + (n.getMinutes() % 5) * 1000 * 60 + + n.getSeconds() * 1000 + + n.getMilliseconds(); n += 5 * 1000 * 60; res.setHeader('Content-Type', 'application/json'); res.setHeader('Date', new Date().toUTCString()); @@ -1243,14 +1342,16 @@ app.use('/shadow/', (req, res) => { const {url} = req; const isProxyUrl = /^\/proxy\//.test(url); - const baseHref = isProxyUrl ? - 'https://cdn.ampproject.org/' : - `${path.dirname(url)}/`; + const baseHref = isProxyUrl + ? 'https://cdn.ampproject.org/' + : `${path.dirname(url)}/`; - res.end(renderShadowViewer({ - src: req.url.replace(/^\//, ''), - baseHref, - })); + res.end( + renderShadowViewer({ + src: req.url.replace(/^\//, ''), + baseHref, + }) + ); }); /** @@ -1263,18 +1364,22 @@ function addViewerIntegrationScript(ampJsVersion, file) { return file; } let viewerScript; - if (Number.isInteger(ampJsVersion)) { // eslint-disable-line amphtml-internal/no-es2015-number-props + if (Number.isInteger(ampJsVersion)) { + // eslint-disable-line amphtml-internal/no-es2015-number-props // Viewer integration script from gws, such as // https://cdn.ampproject.org/viewer/google/v7.js viewerScript = - ''; + ''; } else { // Viewer integration script from runtime, such as // https://cdn.ampproject.org/v0/amp-viewer-integration-0.1.js - viewerScript = ''; + viewerScript = + ''; } file = file.replace('', viewerScript + ''); return file; @@ -1292,7 +1397,7 @@ function getUrlPrefix(req) { */ function addQueryParam(url, param, value) { const paramValue = - encodeURIComponent(param) + '=' + encodeURIComponent(value); + encodeURIComponent(param) + '=' + encodeURIComponent(value); if (!url.includes('?')) { url += '?' + paramValue; } else { @@ -1301,21 +1406,25 @@ function addQueryParam(url, param, value) { return url; } - function generateInfo(filePath) { const mode = pc.env.SERVE_MODE; filePath = filePath.substr(0, filePath.length - 9) + '.html'; - return '

    Please note that .min/.max is no longer supported

    ' + - '

    Current serving mode is ' + mode + '

    ' + - '

    Please go to Unversioned Link to view the page

    ' + - '

    ' + - '

    ' + - 'Change to DEFAULT mode (unminified JS)

    ' + - '

    ' + - 'Change to COMPILED mode (minified JS)

    ' + - '

    Change to CDN mode (prod JS)

    '; + return ( + '

    Please note that .min/.max is no longer supported

    ' + + '

    Current serving mode is ' + + mode + + '

    ' + + '

    Please go to Unversioned Link to view the page

    ' + + '

    ' + + '

    ' + + 'Change to DEFAULT mode (unminified JS)

    ' + + '

    ' + + 'Change to COMPILED mode (minified JS)

    ' + + '

    Change to CDN mode (prod JS)

    ' + ); } module.exports = app; diff --git a/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js b/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js index 16d935759358e..9009b65e0b6f1 100644 --- a/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-amp-mode-transformer/index.js @@ -15,10 +15,10 @@ */ /** -* Changes the values of getMode().test, getMode().localDev to false -* and getMode().localDev to true. -* @param {Object} babelTypes -*/ + * Changes the values of getMode().test, getMode().localDev to false + * and getMode().localDev to true. + * @param {Object} babelTypes + */ const {resolve, dirname} = require('path'); module.exports = function({types: t}) { let getModeFound = false; @@ -32,7 +32,9 @@ module.exports = function({types: t}) { specifiers.forEach(specifier => { if (specifier.imported.name === 'getMode') { const filepath = resolve( - dirname(state.file.opts.filename), source.value); + dirname(state.file.opts.filename), + source.value + ); if (filepath.endsWith('/amphtml/src/mode')) { getModeFound = true; } @@ -54,7 +56,7 @@ module.exports = function({types: t}) { path.replaceWith(t.booleanLiteral(true)); } } - }, + }, }, }; }; diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js index c27d1c33feeb4..3cad3ad8e4148 100644 --- a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js @@ -15,18 +15,20 @@ */ /** - * Changes the values of IS_DEV to false and IS_MINIFIED to true. - * The above said variables are in src/mode.js file. - * @param {Object} babelTypes - */ + * Changes the values of IS_DEV to false and IS_MINIFIED to true. + * The above said variables are in src/mode.js file. + * @param {Object} babelTypes + */ module.exports = function({types: t}) { return { visitor: { VariableDeclarator(path) { const {node} = path; const {id, init} = node; - if (t.isIdentifier(id, {name: 'IS_DEV'}) - && t.isBooleanLiteral(init, {value: true})) { + if ( + t.isIdentifier(id, {name: 'IS_DEV'}) && + t.isBooleanLiteral(init, {value: true}) + ) { node.init = t.booleanLiteral(false); } }, diff --git a/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js index 3d9f8daa5157f..f57e1b497d36e 100644 --- a/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js @@ -15,23 +15,25 @@ */ /** - * Changes the values of IS_DEV to false and IS_MINIFIED to true. - * The above said variables are in src/mode.js file. - * @param {Object} babelTypes - */ + * Changes the values of IS_DEV to false and IS_MINIFIED to true. + * The above said variables are in src/mode.js file. + * @param {Object} babelTypes + */ module.exports = function(babelTypes) { const {types: t} = babelTypes; return { visitor: { VariableDeclarator(path) { const {id, init} = path.node; - if (t.isIdentifier(id, {name: 'IS_MINIFIED'}) - && t.isBooleanLiteral(init, {value: false})) { + if ( + t.isIdentifier(id, {name: 'IS_MINIFIED'}) && + t.isBooleanLiteral(init, {value: false}) + ) { path.replaceWith( - t.variableDeclarator( - t.identifier('IS_MINIFIED'), - t.booleanLiteral(true) - ) + t.variableDeclarator( + t.identifier('IS_MINIFIED'), + t.booleanLiteral(true) + ) ); } }, diff --git a/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js b/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js index 49d67155ad118..e7ca4a87cd4f8 100644 --- a/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js @@ -43,7 +43,6 @@ const removableDevAsserts = [ const removableUserAsserts = ['fine']; - module.exports = function(babel) { const {types: t} = babel; return { @@ -52,8 +51,8 @@ module.exports = function(babel) { const {node} = path; const {callee} = node; const {parenthesized} = node.extra || {}; - const isMemberAndCallExpression = t.isMemberExpression(callee) - && t.isCallExpression(callee.object); + const isMemberAndCallExpression = + t.isMemberExpression(callee) && t.isCallExpression(callee.object); if (!isMemberAndCallExpression) { return; @@ -61,11 +60,13 @@ module.exports = function(babel) { const logCallee = callee.object.callee; const {property} = callee; - const isRemovableDevCall = t.isIdentifier(logCallee, {name: 'dev'}) && - isRemovableMethod(t, property, removableDevAsserts); + const isRemovableDevCall = + t.isIdentifier(logCallee, {name: 'dev'}) && + isRemovableMethod(t, property, removableDevAsserts); - const isRemovableUserCall = t.isIdentifier(logCallee, {name: 'user'}) && - isRemovableMethod(t, property, removableUserAsserts); + const isRemovableUserCall = + t.isIdentifier(logCallee, {name: 'user'}) && + isRemovableMethod(t, property, removableUserAsserts); if (!(isRemovableDevCall || isRemovableUserCall)) { return; @@ -81,9 +82,9 @@ module.exports = function(babel) { if (parenthesized) { path.replaceWith(t.parenthesizedExpression(args)); path.skip(); - // If is not an assert type, we won't need to do type annotation. - // If it has no type that we can cast to, then we also won't need to - // do type annotation. + // If is not an assert type, we won't need to do type annotation. + // If it has no type that we can cast to, then we also won't need to + // do type annotation. } else if (!property.name.startsWith('assert') || !type) { path.replaceWith(args); } else { diff --git a/build-system/babel-plugins/babel-plugin-transform-html-template/index.js b/build-system/babel-plugins/babel-plugin-transform-html-template/index.js index ea04b6750e47a..6adde21d885b8 100644 --- a/build-system/babel-plugins/babel-plugin-transform-html-template/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-html-template/index.js @@ -26,8 +26,11 @@ const INSERTED_TEMPLATES = new Map(); */ function optimizeLiteralOutput(templateLiteral) { if (templateLiteral.quasis.length !== 1) { - console/* OK */.log('Improperly formatted `html` tagged template literal' + - ', more than one template element present.'); + console /* OK */ + .log( + 'Improperly formatted `html` tagged template literal' + + ', more than one template element present.' + ); return null; } return minify(templateLiteral.quasis[0].value.cooked, { @@ -44,9 +47,11 @@ module.exports = function({types: t}) { TaggedTemplateExpression(path) { const {tag} = path.node; - if (t.isIdentifier(tag, {name: 'html'}) || - (t.isCallExpression(tag) && - t.isIdentifier(tag.callee, {name: 'htmlFor'}))) { + if ( + t.isIdentifier(tag, {name: 'html'}) || + (t.isCallExpression(tag) && + t.isIdentifier(tag.callee, {name: 'htmlFor'})) + ) { // Replace a matching TemplateExpression by either inlining a // transpiled template or hoisting the template and referring // to its value. @@ -54,13 +59,13 @@ module.exports = function({types: t}) { const template = optimizeLiteralOutput(path.node.quasi); if (template !== null) { - const templateArrayExpression = t.arrayExpression( - [t.stringLiteral(template)] - ); + const templateArrayExpression = t.arrayExpression([ + t.stringLiteral(template), + ]); if (t.isProgram(path.scope.block)) { path.replaceWith( - t.callExpression(tag, [templateArrayExpression]) + t.callExpression(tag, [templateArrayExpression]) ); } else { // Since the template is inline, and the block scope @@ -73,7 +78,7 @@ module.exports = function({types: t}) { } else { // Template not hoisted. Hoist it. hoistedIdentifier = path.scope.generateUidIdentifier( - 'template' + 'template' ); const program = path.findParent(path => path.isProgram()); diff --git a/build-system/build.conf.js b/build-system/build.conf.js index bef8a14e25d6d..31214de04d8e9 100644 --- a/build-system/build.conf.js +++ b/build-system/build.conf.js @@ -15,36 +15,35 @@ */ const defaultPlugins = [ + require.resolve('./babel-plugins/babel-plugin-transform-amp-asserts'), + require.resolve('./babel-plugins/babel-plugin-transform-html-template'), require.resolve( - './babel-plugins/babel-plugin-transform-amp-asserts'), + './babel-plugins/babel-plugin-transform-parenthesize-expression' + ), require.resolve( - './babel-plugins/babel-plugin-transform-html-template'), - require.resolve( - './babel-plugins/babel-plugin-transform-parenthesize-expression'), - require.resolve( - './babel-plugins/babel-plugin-is_minified-constant-transformer'), + './babel-plugins/babel-plugin-is_minified-constant-transformer' + ), ]; module.exports = { - plugins: ({ - isEsmBuild, - isCommonJsModule, - isForTesting, - }) => { + plugins: ({isEsmBuild, isCommonJsModule, isForTesting}) => { let pluginsToApply = defaultPlugins; if (isEsmBuild) { pluginsToApply = pluginsToApply.concat([ - [require.resolve('babel-plugin-filter-imports'), { - 'imports': { - './polyfills/fetch': ['installFetch'], - './polyfills/domtokenlist-toggle': ['installDOMTokenListToggle'], - './polyfills/document-contains': ['installDocContains'], - './polyfills/math-sign': ['installMathSign'], - './polyfills/object-assign': ['installObjectAssign'], - './polyfills/object-values': ['installObjectValues'], - './polyfills/promise': ['installPromise'], + [ + require.resolve('babel-plugin-filter-imports'), + { + 'imports': { + './polyfills/fetch': ['installFetch'], + './polyfills/domtokenlist-toggle': ['installDOMTokenListToggle'], + './polyfills/document-contains': ['installDocContains'], + './polyfills/math-sign': ['installMathSign'], + './polyfills/object-assign': ['installObjectAssign'], + './polyfills/object-values': ['installObjectValues'], + './polyfills/promise': ['installPromise'], + }, }, - }], + ], ]); } if (isCommonJsModule) { @@ -55,11 +54,9 @@ module.exports = { if (!isForTesting) { pluginsToApply = pluginsToApply.concat([ require.resolve( - './babel-plugins/babel-plugin-is_dev-constant-transformer' - ), - require.resolve( - './babel-plugins/babel-plugin-amp-mode-transformer' + './babel-plugins/babel-plugin-is_dev-constant-transformer' ), + require.resolve('./babel-plugins/babel-plugin-amp-mode-transformer'), ]); } return pluginsToApply; diff --git a/build-system/check-package-manager.js b/build-system/check-package-manager.js index b6fbcd72ef108..0a53b9417507e 100644 --- a/build-system/check-package-manager.js +++ b/build-system/check-package-manager.js @@ -25,9 +25,11 @@ const fs = require('fs'); const https = require('https'); const {getStdout} = require('./exec'); -const setupInstructionsUrl = 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md#one-time-setup'; +const setupInstructionsUrl = + 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md#one-time-setup'; const nodeDistributionsUrl = 'https://nodejs.org/dist/index.json'; -const gulpHelpUrl = 'https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467'; +const gulpHelpUrl = + 'https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467'; const yarnExecutable = 'npx yarn'; const gulpExecutable = 'npx gulp'; @@ -35,10 +37,18 @@ const gulpExecutable = 'npx gulp'; const updatesNeeded = []; // Color formatting libraries may not be available when this script is run. -function red(text) {return '\x1b[31m' + text + '\x1b[0m';} -function cyan(text) {return '\x1b[36m' + text + '\x1b[0m';} -function green(text) {return '\x1b[32m' + text + '\x1b[0m';} -function yellow(text) {return '\x1b[33m' + text + '\x1b[0m';} +function red(text) { + return '\x1b[31m' + text + '\x1b[0m'; +} +function cyan(text) { + return '\x1b[36m' + text + '\x1b[0m'; +} +function green(text) { + return '\x1b[32m' + text + '\x1b[0m'; +} +function yellow(text) { + return '\x1b[33m' + text + '\x1b[0m'; +} /** * @fileoverview Perform checks on the AMP toolchain. @@ -47,23 +57,33 @@ function yellow(text) {return '\x1b[33m' + text + '\x1b[0m';} // If npm is being run, print a message and cause 'npm install' to fail. function ensureYarn() { if (process.env.npm_execpath.indexOf('yarn') === -1) { - console.log(red( - '*** The AMP project uses yarn for package management ***'), '\n'); + console.log( + red('*** The AMP project uses yarn for package management ***'), + '\n' + ); console.log(yellow('To install all packages:')); console.log(cyan('$'), 'yarn', '\n'); console.log( - yellow('To install a new (runtime) package to "dependencies":')); + yellow('To install a new (runtime) package to "dependencies":') + ); console.log(cyan('$'), 'yarn add --exact [package_name@version]', '\n'); console.log( - yellow('To install a new (toolset) package to "devDependencies":')); - console.log(cyan('$'), - 'yarn add --dev --exact [package_name@version]', '\n'); + yellow('To install a new (toolset) package to "devDependencies":') + ); + console.log( + cyan('$'), + 'yarn add --dev --exact [package_name@version]', + '\n' + ); console.log(yellow('To upgrade a package:')); console.log(cyan('$'), 'yarn upgrade --exact [package_name@version]', '\n'); console.log(yellow('To remove a package:')); console.log(cyan('$'), 'yarn remove [package_name]', '\n'); - console.log(yellow('For detailed instructions, see'), - cyan(setupInstructionsUrl), '\n'); + console.log( + yellow('For detailed instructions, see'), + cyan(setupInstructionsUrl), + '\n' + ); process.exit(1); } } @@ -72,43 +92,64 @@ function ensureYarn() { function checkNodeVersion() { const nodeVersion = getStdout('node --version').trim(); return new Promise(resolve => { - https.get(nodeDistributionsUrl, res => { - res.setEncoding('utf8'); - let distributions = ''; - res.on('data', data => { - distributions += data; - }); - res.on('end', () => { - const distributionsJson = JSON.parse(distributions); - const latestLtsVersion = getNodeLatestLtsVersion(distributionsJson); - if (latestLtsVersion === '') { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not determine the latest LTS version of node.')); - } else if (nodeVersion !== latestLtsVersion) { - console.log(yellow('WARNING: Detected node version'), + https + .get(nodeDistributionsUrl, res => { + res.setEncoding('utf8'); + let distributions = ''; + res.on('data', data => { + distributions += data; + }); + res.on('end', () => { + const distributionsJson = JSON.parse(distributions); + const latestLtsVersion = getNodeLatestLtsVersion(distributionsJson); + if (latestLtsVersion === '') { + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not determine the latest LTS version of node.' + ) + ); + } else if (nodeVersion !== latestLtsVersion) { + console.log( + yellow('WARNING: Detected node version'), cyan(nodeVersion) + - yellow('. Recommended (latest LTS) version is'), - cyan(latestLtsVersion) + yellow('.')); - console.log(yellow('⤷ To fix this, run'), - cyan('"nvm install --lts"'), yellow('or see'), + yellow('. Recommended (latest LTS) version is'), + cyan(latestLtsVersion) + yellow('.') + ); + console.log( + yellow('⤷ To fix this, run'), + cyan('"nvm install --lts"'), + yellow('or see'), cyan('https://nodejs.org/en/download/package-manager'), - yellow('for instructions.')); - updatesNeeded.push('node'); - } else { - console.log(green('Detected'), cyan('node'), green('version'), - cyan(nodeVersion + ' (latest LTS)') + - green('.')); - } + yellow('for instructions.') + ); + updatesNeeded.push('node'); + } else { + console.log( + green('Detected'), + cyan('node'), + green('version'), + cyan(nodeVersion + ' (latest LTS)') + green('.') + ); + } + resolve(); + }); + }) + .on('error', () => { + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not download node version info from ' + + cyan(nodeDistributionsUrl) + + yellow('.') + ) + ); + console.log( + yellow('⤷ Detected node version'), + cyan(nodeVersion) + yellow('.') + ); resolve(); }); - }).on('error', () => { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not download node version info from ' + - cyan(nodeDistributionsUrl) + yellow('.'))); - console.log(yellow('⤷ Detected node version'), cyan(nodeVersion) + - yellow('.')); - resolve(); - }); }); } @@ -116,9 +157,11 @@ function getNodeLatestLtsVersion(distributionsJson) { if (distributionsJson) { // Versions are in descending order, so the first match is the latest lts. return distributionsJson.find(function(distribution) { - return distribution.hasOwnProperty('version') && - distribution.hasOwnProperty('lts') && - distribution.lts; + return ( + distribution.hasOwnProperty('version') && + distribution.hasOwnProperty('lts') && + distribution.lts + ); }).version; } else { return ''; @@ -132,28 +175,42 @@ function checkYarnVersion() { const yarnInfoJson = JSON.parse(yarnInfo.split('\n')[0]); // First line const stableVersion = getYarnStableVersion(yarnInfoJson); if (stableVersion === '') { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not determine the stable version of yarn.')); + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not determine the stable version of yarn.' + ) + ); } else if (yarnVersion !== stableVersion) { - console.log(yellow('WARNING: Detected yarn version'), - cyan(yarnVersion) + yellow('. Recommended (stable) version is'), - cyan(stableVersion) + yellow('.')); - console.log(yellow('⤷ To fix this, run'), - cyan('"curl -o- -L https://yarnpkg.com/install.sh | bash"'), - yellow('or see'), cyan('https://yarnpkg.com/docs/install'), - yellow('for instructions.')); + console.log( + yellow('WARNING: Detected yarn version'), + cyan(yarnVersion) + yellow('. Recommended (stable) version is'), + cyan(stableVersion) + yellow('.') + ); + console.log( + yellow('⤷ To fix this, run'), + cyan('"curl -o- -L https://yarnpkg.com/install.sh | bash"'), + yellow('or see'), + cyan('https://yarnpkg.com/docs/install'), + yellow('for instructions.') + ); updatesNeeded.push('yarn'); } else { - console.log(green('Detected'), cyan('yarn'), green('version'), - cyan(yarnVersion + ' (stable)') + - green('. Installing packages...')); + console.log( + green('Detected'), + cyan('yarn'), + green('version'), + cyan(yarnVersion + ' (stable)') + green('. Installing packages...') + ); } } function getYarnStableVersion(infoJson) { - if (infoJson && - infoJson.hasOwnProperty('data') && - infoJson.data.hasOwnProperty('version')) { + if ( + infoJson && + infoJson.hasOwnProperty('data') && + infoJson.data.hasOwnProperty('version') + ) { return infoJson.data.version; } else { return ''; @@ -166,29 +223,50 @@ function checkGlobalGulp() { const globalGulp = globalPackages.match(/"gulp@.*" has binaries/); const globalGulpCli = globalPackages.match(/"gulp-cli@.*" has binaries/); if (globalGulp) { - console.log(yellow('WARNING: Detected a global install of'), - cyan('gulp') + yellow('. It is recommended that you use'), - cyan('gulp-cli'), yellow('instead.')); - console.log(yellow('⤷ To fix this, run'), - cyan('"yarn global remove gulp"'), yellow('followed by'), - cyan('"yarn global add gulp-cli"') + yellow('.')); - console.log(yellow('⤷ See'), cyan(gulpHelpUrl), - yellow('for more information.')); + console.log( + yellow('WARNING: Detected a global install of'), + cyan('gulp') + yellow('. It is recommended that you use'), + cyan('gulp-cli'), + yellow('instead.') + ); + console.log( + yellow('⤷ To fix this, run'), + cyan('"yarn global remove gulp"'), + yellow('followed by'), + cyan('"yarn global add gulp-cli"') + yellow('.') + ); + console.log( + yellow('⤷ See'), + cyan(gulpHelpUrl), + yellow('for more information.') + ); updatesNeeded.push('gulp'); } else if (!globalGulpCli) { - console.log(yellow('WARNING: Could not find'), - cyan('gulp-cli') + yellow('.')); - console.log(yellow('⤷ To install it, run'), - cyan('"yarn global add gulp-cli"') + yellow('.')); + console.log( + yellow('WARNING: Could not find'), + cyan('gulp-cli') + yellow('.') + ); + console.log( + yellow('⤷ To install it, run'), + cyan('"yarn global add gulp-cli"') + yellow('.') + ); } else if (!firstInstall) { const gulpVersions = getStdout(gulpExecutable + ' --version').trim(); const gulpVersion = gulpVersions.match(/Local version (.*?)$/); if (gulpVersion && gulpVersion.length == 2) { - console.log(green('Detected'), cyan('gulp'), green('version'), - cyan(gulpVersion[1]) + green('.')); + console.log( + green('Detected'), + cyan('gulp'), + green('version'), + cyan(gulpVersion[1]) + green('.') + ); } else { - console.log(yellow('WARNING: Something went wrong. ' + - 'Could not determine the local version of gulp.')); + console.log( + yellow( + 'WARNING: Something went wrong. ' + + 'Could not determine the local version of gulp.' + ) + ); } } } @@ -203,14 +281,24 @@ function main() { checkGlobalGulp(); checkYarnVersion(); if (!process.env.TRAVIS && updatesNeeded.length > 0) { - console.log(yellow('\nWARNING: Detected missing updates for'), - cyan(updatesNeeded.join(', '))); - console.log(yellow('⤷ Continuing install in'), cyan('5'), - yellow('seconds...')); - console.log(yellow('⤷ Press'), cyan('Ctrl + C'), - yellow('to abort and fix...')); + console.log( + yellow('\nWARNING: Detected missing updates for'), + cyan(updatesNeeded.join(', ')) + ); + console.log( + yellow('⤷ Continuing install in'), + cyan('5'), + yellow('seconds...') + ); + console.log( + yellow('⤷ Press'), + cyan('Ctrl + C'), + yellow('to abort and fix...') + ); let resolver; - const deferred = new Promise(resolverIn => {resolver = resolverIn;}); + const deferred = new Promise(resolverIn => { + resolver = resolverIn; + }); setTimeout(() => { console.log(yellow('\nAttempting to install packages...')); resolver(); diff --git a/build-system/compile-wrappers.js b/build-system/compile-wrappers.js index c146ddf060ffc..2a2c5790012c5 100644 --- a/build-system/compile-wrappers.js +++ b/build-system/compile-wrappers.js @@ -18,17 +18,22 @@ const {VERSION} = require('./internal-version'); // If there is a sync JS error during initial load, // at least try to unhide the body. -exports.mainBinary = 'var global=self;self.AMP=self.AMP||[];' + - 'try{(function(_){\n<%= contents %>})(AMP._=AMP._||{})}catch(e){' + - 'setTimeout(function(){' + - 'var s=document.body.style;' + - 's.opacity=1;' + - 's.visibility="visible";' + - 's.animation="none";' + - 's.WebkitAnimation="none;"},1000);throw e};'; +exports.mainBinary = + 'var global=self;self.AMP=self.AMP||[];' + + 'try{(function(_){\n<%= contents %>})(AMP._=AMP._||{})}catch(e){' + + 'setTimeout(function(){' + + 'var s=document.body.style;' + + 's.opacity=1;' + + 's.visibility="visible";' + + 's.animation="none";' + + 's.WebkitAnimation="none;"},1000);throw e};'; -exports.extension = function(name, loadPriority, intermediateDeps, - opt_splitMarker) { +exports.extension = function( + name, + loadPriority, + intermediateDeps, + opt_splitMarker +) { opt_splitMarker = opt_splitMarker || ''; let deps = ''; if (intermediateDeps) { @@ -50,9 +55,11 @@ exports.extension = function(name, loadPriority, intermediateDeps, } priority = 'p:"high",'; } - return `(self.AMP=self.AMP||[]).push({n:"${name}",${priority}${deps}` + - `v:"${VERSION}",f:(function(AMP,_){${opt_splitMarker}\n` + - '<%= contents %>\n})});'; + return ( + `(self.AMP=self.AMP||[]).push({n:"${name}",${priority}${deps}` + + `v:"${VERSION}",f:(function(AMP,_){${opt_splitMarker}\n` + + '<%= contents %>\n})});' + ); }; exports.none = '<%= contents %>'; diff --git a/build-system/config.js b/build-system/config.js index b3d4f6b20719f..e3b5ec1ee9da6 100644 --- a/build-system/config.js +++ b/build-system/config.js @@ -15,9 +15,7 @@ */ 'use strict'; -const initTestsPath = [ - 'test/_init_tests.js', -]; +const initTestsPath = ['test/_init_tests.js']; const fixturesExamplesPaths = [ 'test/fixtures/*.html', @@ -58,8 +56,10 @@ const builtRuntimePaths = [ const commonUnitTestPaths = initTestsPath.concat(fixturesExamplesPaths); -const commonIntegrationTestPaths = - initTestsPath.concat(fixturesExamplesPaths, builtRuntimePaths); +const commonIntegrationTestPaths = initTestsPath.concat( + fixturesExamplesPaths, + builtRuntimePaths +); const testPaths = commonIntegrationTestPaths.concat([ 'test/*/!(e2e)/**/*.js', @@ -73,9 +73,7 @@ const a4aTestPaths = initTestsPath.concat([ 'ads/google/a4a/test/*.js', ]); -const chaiAsPromised = [ - 'test/chai-as-promised/chai-as-promised.js', -]; +const chaiAsPromised = ['test/chai-as-promised/chai-as-promised.js']; const unitTestPaths = [ 'test/unit/**/*.js', @@ -83,10 +81,7 @@ const unitTestPaths = [ 'extensions/**/test/*.js', ]; -const unitTestOnSaucePaths = [ - 'test/unit/**/*.js', - 'ads/**/test/test-*.js', -]; +const unitTestOnSaucePaths = ['test/unit/**/*.js', 'ads/**/test/test-*.js']; const integrationTestPaths = [ 'test/integration/**/*.js', @@ -94,14 +89,9 @@ const integrationTestPaths = [ 'extensions/**/test/integration/**/*.js', ]; -const e2eTestPaths = [ - 'test/e2e/*.js', - 'extensions/**/test-e2e/*.js', -]; +const e2eTestPaths = ['test/e2e/*.js', 'extensions/**/test-e2e/*.js']; -const devDashboardTestPaths = [ - 'build-system/app-index/test/**/*.js', -]; +const devDashboardTestPaths = ['build-system/app-index/test/**/*.js']; const lintGlobs = [ '**/*.js', @@ -150,7 +140,7 @@ module.exports = { jsonGlobs: [ '**/*.json', '!{node_modules,build,dist,dist.3p,dist.tools,' + - 'third_party,build-system}/**/*.*', + 'third_party,build-system}/**/*.*', ], presubmitGlobs: [ '**/*.{css,js,go}', @@ -158,7 +148,7 @@ module.exports = { // built 3p binary. This is done, so we make sure our special 3p checks // run against the entire transitive closure of deps. '!{node_modules,build,dist,dist.tools,' + - 'dist.3p/[0-9]*,dist.3p/current,dist.3p/current-min}/**/*.*', + 'dist.3p/[0-9]*,dist.3p/current,dist.3p/current-min}/**/*.*', '!dist.3p/current/**/ampcontext-lib.js', '!dist.3p/current/**/iframe-transport-client-lib.js', '!out/**/*.*', diff --git a/build-system/ctrlcHandler.js b/build-system/ctrlcHandler.js index d7464da4c6ec6..fd64d34cfbb2e 100644 --- a/build-system/ctrlcHandler.js +++ b/build-system/ctrlcHandler.js @@ -21,9 +21,8 @@ const {isTravisBuild} = require('./travis'); const {green, cyan} = colors; -const killCmd = - (process.platform == 'win32') ? 'taskkill /f /pid' : 'kill -KILL'; -const killSuffix = (process.platform == 'win32') ? '>NUL' : ''; +const killCmd = process.platform == 'win32' ? 'taskkill /f /pid' : 'kill -KILL'; +const killSuffix = process.platform == 'win32' ? '>NUL' : ''; /** * Creates an async child process that handles Ctrl + C and immediately cancels @@ -33,11 +32,19 @@ const killSuffix = (process.platform == 'win32') ? '>NUL' : ''; */ exports.createCtrlcHandler = function(command) { if (!isTravisBuild()) { - log(green('Running'), cyan(command) + green('. Press'), cyan('Ctrl + C'), - green('to cancel...')); + log( + green('Running'), + cyan(command) + green('. Press'), + cyan('Ctrl + C'), + green('to cancel...') + ); } - const killMessage = green('\nDetected ') + cyan('Ctrl + C') + - green('. Canceling ') + cyan(command) + green('.'); + const killMessage = + green('\nDetected ') + + cyan('Ctrl + C') + + green('. Canceling ') + + cyan(command) + + green('.'); const listenerCmd = ` #!/bin/sh ctrlcHandler() { @@ -48,8 +55,9 @@ exports.createCtrlcHandler = function(command) { trap 'ctrlcHandler' INT read _ # Waits until the process is terminated `; - return execScriptAsync( - listenerCmd, {'stdio': [null, process.stdout, process.stderr]}).pid; + return execScriptAsync(listenerCmd, { + 'stdio': [null, process.stdout, process.stderr], + }).pid; }; /** diff --git a/build-system/dep-check-config.js b/build-system/dep-check-config.js index 890eb05795fcd..f5c127d32a016 100644 --- a/build-system/dep-check-config.js +++ b/build-system/dep-check-config.js @@ -75,13 +75,13 @@ exports.rules = [ mustNotDependOn: 'third_party/**/*.js', whitelist: [ 'extensions/amp-crypto-polyfill/**/*.js->' + - 'third_party/closure-library/sha384-generated.js', + 'third_party/closure-library/sha384-generated.js', 'extensions/amp-mustache/**/amp-mustache.js->' + - 'third_party/mustache/mustache.js', + 'third_party/mustache/mustache.js', 'extensions/amp-ad-network-adzerk-impl/0.1/' + - 'amp-ad-network-adzerk-impl.js->third_party/mustache/mustache.js', + 'amp-ad-network-adzerk-impl.js->third_party/mustache/mustache.js', 'extensions/amp-timeago/0.1/amp-timeago.js->' + - 'third_party/timeagojs/timeago.js', + 'third_party/timeagojs/timeago.js', '3p/polyfills.js->third_party/babel/custom-babel-helpers.js', 'src/sanitizer.js->third_party/caja/html-sanitizer.js', 'extensions/amp-viz-vega/**->third_party/vega/vega.js', @@ -89,21 +89,21 @@ exports.rules = [ 'src/css.js->third_party/css-escape/css-escape.js', 'src/shadow-embed.js->third_party/webcomponentsjs/ShadowCSS.js', 'third_party/timeagojs/timeago.js->' + - 'third_party/timeagojs/timeago-locales.js', + 'third_party/timeagojs/timeago-locales.js', 'extensions/amp-date-picker/**->third_party/react-dates/bundle.js', 'extensions/amp-date-picker/**->third_party/rrule/rrule.js', 'extensions/amp-subscriptions/**/*.js->' + - 'third_party/subscriptions-project/apis.js', + 'third_party/subscriptions-project/apis.js', 'extensions/amp-subscriptions/**/*.js->' + - 'third_party/subscriptions-project/config.js', + 'third_party/subscriptions-project/config.js', 'extensions/amp-subscriptions-google/**/*.js->' + - 'third_party/subscriptions-project/apis.js', + 'third_party/subscriptions-project/apis.js', 'extensions/amp-subscriptions-google/**/*.js->' + - 'third_party/subscriptions-project/config.js', + 'third_party/subscriptions-project/config.js', 'extensions/amp-subscriptions-google/**/*.js->' + - 'third_party/subscriptions-project/swg.js', + 'third_party/subscriptions-project/swg.js', 'extensions/amp-recaptcha-input/**/*.js->' + - 'third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm.js', + 'third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm.js', ], }, // Rules for 3p @@ -181,18 +181,18 @@ exports.rules = [ whitelist: [ // See todo note in ads/_a4a-config.js 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config.js', + 'extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-doubleclick-impl/0.1/' + - 'doubleclick-a4a-config.js', + 'extensions/amp-ad-network-doubleclick-impl/0.1/' + + 'doubleclick-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-fake-impl/0.1/fake-a4a-config.js', + 'extensions/amp-ad-network-fake-impl/0.1/fake-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config.js', + 'extensions/amp-ad-network-triplelift-impl/0.1/triplelift-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config.js', + 'extensions/amp-ad-network-cloudflare-impl/0.1/cloudflare-a4a-config.js', 'ads/_a4a-config.js->' + - 'extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config.js', + 'extensions/amp-ad-network-gmossp-impl/0.1/gmossp-a4a-config.js', ], }, // Rules for extensions and main src. @@ -275,401 +275,397 @@ exports.rules = [ mustNotDependOn: 'src/service/**/*.js', whitelist: [ 'extensions/amp-a4a/0.1/a4a-variable-source.js->' + - 'src/service/variable-source.js', + 'src/service/variable-source.js', 'extensions/amp-a4a/0.1/amp-a4a.js->' + - 'src/service/url-replacements-impl.js', + 'src/service/url-replacements-impl.js', 'extensions/amp-video-service/**->' + - 'src/service/video-service-interface.js', + 'src/service/video-service-interface.js', 'extensions/amp-video/0.1/amp-video.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-video-iframe/0.1/amp-video-iframe.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-ooyala-player/0.1/amp-ooyala-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-youtube/0.1/amp-youtube.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-viqeo-player/0.1/amp-viqeo-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-brightcove/0.1/amp-brightcove.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-powr-player/0.1/amp-powr-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-dailymotion/0.1/amp-dailymotion.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-brid-player/0.1/amp-brid-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-gfycat/0.1/amp-gfycat.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-a4a/0.1/amp-a4a.js->src/service/variable-source.js', 'extensions/amp-a4a/0.1/friendly-frame-util.js->' + - 'src/service/url-replacements-impl.js', + 'src/service/url-replacements-impl.js', 'extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-3q-player/0.1/amp-3q-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-ima-video/0.1/amp-ima-video.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-vimeo/0.1/amp-vimeo.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-wistia-player/0.1/amp-wistia-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-delight-player/0.1/amp-delight-player.js->' + - 'src/service/video-manager-impl.js', + 'src/service/video-manager-impl.js', 'extensions/amp-analytics/0.1/iframe-transport.js->' + - 'src/service/extension-location.js', + 'src/service/extension-location.js', 'extensions/amp-analytics/0.1/iframe-transport.js->' + - 'src/service/jank-meter.js', + 'src/service/jank-meter.js', 'extensions/amp-position-observer/0.1/amp-position-observer.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-position-observer/0.1/amp-position-observer.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-fx-collection/0.1/providers/fx-provider.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-fx-collection/0.1/providers/fx-provider.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-video-docking/0.1/amp-video-docking.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-video-docking/0.1/amp-video-docking.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-analytics/0.1/amp-analytics.js->' + - 'src/service/cid-impl.js', + 'src/service/cid-impl.js', 'extensions/amp-analytics/0.1/cookie-writer.js->' + - 'src/service/cid-impl.js', + 'src/service/cid-impl.js', 'extensions/amp-next-page/0.1/next-page-service.js->' + - 'src/service/position-observer/position-observer-impl.js', + 'src/service/position-observer/position-observer-impl.js', 'extensions/amp-next-page/0.1/next-page-service.js->' + - 'src/service/position-observer/position-observer-worker.js', + 'src/service/position-observer/position-observer-worker.js', 'extensions/amp-user-notification/0.1/amp-user-notification.js->' + - 'src/service/notification-ui-manager.js', + 'src/service/notification-ui-manager.js', 'extensions/amp-consent/0.1/amp-consent.js->' + - 'src/service/notification-ui-manager.js', + 'src/service/notification-ui-manager.js', // For autoplay delegation: 'extensions/amp-story/0.1/amp-story-page.js->' + - 'src/service/video-service-sync-impl.js', + 'src/service/video-service-sync-impl.js', 'extensions/amp-story/1.0/amp-story-page.js->' + - 'src/service/video-service-sync-impl.js', + 'src/service/video-service-sync-impl.js', // Accessing USER_INTERACTED constant: 'extensions/amp-story/1.0/media-pool.js->' + - 'src/service/video-service-interface.js', + 'src/service/video-service-interface.js', 'extensions/amp-story/1.0/page-advancement.js->' + - 'src/service/action-impl.js', + 'src/service/action-impl.js', 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->' + - 'src/service/navigation.js', + 'src/service/navigation.js', 'extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js->' + - 'src/service/navigation.js', - 'extensions/amp-mowplayer/0.1/amp-mowplayer.js->' + - 'src/service/video-manager-impl.js', + 'src/service/navigation.js', + 'extensions/amp-mowplayer/0.1/amp-mowplayer.js->' + + 'src/service/video-manager-impl.js', 'extensions/amp-analytics/0.1/linker-manager.js->' + - 'src/service/navigation.js', + 'src/service/navigation.js', 'extensions/amp-skimlinks/0.1/link-rewriter/link-rewriter-manager.js->' + 'src/service/navigation.js', - 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/xhr-impl.js', - 'extensions/amp-form/0.1/amp-form.js->' + - 'src/service/xhr-impl.js', + 'extensions/amp-list/0.1/amp-list.js->' + 'src/service/xhr-impl.js', + 'extensions/amp-form/0.1/amp-form.js->' + 'src/service/xhr-impl.js', // Accessing extension-location.calculateExtensionScriptUrl(). 'extensions/amp-script/0.1/amp-script.js->' + - 'src/service/extension-location.js', + 'src/service/extension-location.js', 'extensions/amp-list/0.1/amp-list.js->' + - 'src/service/origin-experiments-impl.js', + 'src/service/origin-experiments-impl.js', 'extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js->' + 'src/service/origin-experiments-impl.js', 'extensions/amp-experiment/1.0/amp-experiment.js->' + 'src/service/origin-experiments-impl.js', // For action macros. 'extensions/amp-action-macro/0.1/amp-action-macro.js->' + - 'src/service/action-impl.js', + 'src/service/action-impl.js', 'extensions/amp-link-rewriter/0.1/amp-link-rewriter.js->' + - 'src/service/navigation.js', + 'src/service/navigation.js', // For localization. - 'extensions/amp-story/0.1/amp-story.js->' + - 'src/service/localization.js', - 'extensions/amp-story/1.0/amp-story.js->' + - 'src/service/localization.js', + 'extensions/amp-story/0.1/amp-story.js->' + 'src/service/localization.js', + 'extensions/amp-story/1.0/amp-story.js->' + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/af.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/am.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ar.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/bg.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/bn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/bs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ca.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/cs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/da.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/de.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/default.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/el.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/en-GB.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/en.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/es-419.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/es.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/et.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/eu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fil.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/fr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/gl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/gu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/hi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/hr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/hu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/id.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/is.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/it.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/iw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ja.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ka.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/km.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/kn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ko.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/lo.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/lt.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/lv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/mk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ml.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/mn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/mr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ms.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/my.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ne.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/nl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/no.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/pa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/pt-BR.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/pt-PT.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ro.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ru.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/si.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sq.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/sw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ta.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/te.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/th.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/tr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/uk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/ur.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/vi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/zh-CN.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/zh-TW.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story/1.0/_locales/zu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/amp-story-auto-ads.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/af.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/am.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ar.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/bg.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/bn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/bs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ca.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/cs.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/da.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/de.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/el.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/en-GB.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/en.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/es-419.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/es.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/et.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/eu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fil.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/fr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/gl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/gu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/hi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/hr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/hu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/id.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/is.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/it.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/iw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ja.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ka.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/km.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/kn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ko.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/lo.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/lt.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/lv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/mk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ml.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/mn.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/mr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ms.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/my.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ne.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/nl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/no.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/pa.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/pt-BR.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/pt-PT.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ro.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ru.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/si.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sl.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sq.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sv.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/sw.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ta.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/te.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/th.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/tr.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/uk.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/ur.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/vi.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/zh-CN.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/zh-TW.js->' + - 'src/service/localization.js', + 'src/service/localization.js', 'extensions/amp-story-auto-ads/0.1/_locales/zu.js->' + - 'src/service/localization.js', + 'src/service/localization.js', ], }, { @@ -728,7 +724,8 @@ exports.rules = [ 'src/3p-frame.js', 'src/iframe-helper.js', ], - whitelist: 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->src/3p-frame.js', + whitelist: + 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js->src/3p-frame.js', }, { @@ -757,9 +754,7 @@ exports.rules = [ // Do not add any additional files to this whitelist without express // permission from @bradfrizzell, @keithwrightbos, or @robhazan. { - mustNotDependOn: [ - 'ads/google/doubleclick.js', - ], + mustNotDependOn: ['ads/google/doubleclick.js'], whitelist: [ /** DO NOT ADD TO WHITELIST **/ 'ads/ix.js->ads/google/doubleclick.js', diff --git a/build-system/exec.js b/build-system/exec.js index d9d9a43418147..82afff22cc29c 100644 --- a/build-system/exec.js +++ b/build-system/exec.js @@ -21,8 +21,8 @@ const childProcess = require('child_process'); -const shellCmd = (process.platform == 'win32') ? 'cmd' : '/bin/sh'; -const shellFlag = (process.platform == 'win32') ? '/C' : '-c'; +const shellCmd = process.platform == 'win32' ? 'cmd' : '/bin/sh'; +const shellFlag = process.platform == 'win32' ? '/C' : '-c'; /** * Spawns the given command in a child process with the given options. @@ -77,14 +77,12 @@ exports.execOrDie = function(cmd, options) { * @return {!Object} */ function getOutput(cmd) { - const p = spawnProcess( - cmd, - { - 'cwd': process.cwd(), - 'env': process.env, - 'stdio': 'pipe', - 'encoding': 'utf-8', - }); + const p = spawnProcess(cmd, { + 'cwd': process.cwd(), + 'env': process.env, + 'stdio': 'pipe', + 'encoding': 'utf-8', + }); return p; } diff --git a/build-system/git.js b/build-system/git.js index a6fdbbe858cc0..df3b9e3dfed36 100644 --- a/build-system/git.js +++ b/build-system/git.js @@ -67,7 +67,9 @@ exports.shortSha = function(sha) { */ exports.gitDiffNameOnlyMaster = function() { const masterBaseline = gitMasterBaseline(); - return getStdout(`git diff --name-only ${masterBaseline}`).trim().split('\n'); + return getStdout(`git diff --name-only ${masterBaseline}`) + .trim() + .split('\n'); }; /** @@ -100,7 +102,9 @@ ${colors.cyan(commitLogMaxCount)} commits. \ Branch ${colors.cyan(exports.gitBranchName())} may not have been forked from \ ${colors.cyan('master')}.`; commitLog += `\n${colors.yellow('WARNING:')} See \ -${colors.cyan('https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md')} \ +${colors.cyan( + 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md' +)} \ for how to fix this.`; } return commitLog; @@ -114,7 +118,8 @@ for how to fix this.`; exports.gitDiffAddedNameOnlyMaster = function() { const branchPoint = gitMergeBaseLocalMaster(); return getStdout(`git diff --name-only --diff-filter=ARC ${branchPoint}`) - .trim().split('\n'); + .trim() + .split('\n'); }; /** @@ -130,9 +135,9 @@ exports.gitDiffColor = function() { * @return {string} */ exports.gitBranchName = function() { - return isTravisPullRequestBuild() ? - travisPullRequestBranch() : - getStdout('git rev-parse --abbrev-ref HEAD').trim(); + return isTravisPullRequestBuild() + ? travisPullRequestBranch() + : getStdout('git rev-parse --abbrev-ref HEAD').trim(); }; /** @@ -160,8 +165,8 @@ exports.gitCommitterEmail = function() { */ exports.gitCommitFormattedTime = function() { return getStdout( - 'TZ=UTC git log -1 --pretty="%cd" --date=format-local:%y%m%d%H%M%S') - .trim(); + 'TZ=UTC git log -1 --pretty="%cd" --date=format-local:%y%m%d%H%M%S' + ).trim(); }; /** diff --git a/build-system/pr-check.js b/build-system/pr-check.js index dfdc0be0f3ebc..8c279cb15d007 100644 --- a/build-system/pr-check.js +++ b/build-system/pr-check.js @@ -24,13 +24,15 @@ const argv = require('minimist')(process.argv.slice(2)); const colors = require('ansi-colors'); const { isYarnLockFileInSync, - isYarnLockFileProperlyUpdated} = require('./pr-check/yarn-checks'); + isYarnLockFileProperlyUpdated, +} = require('./pr-check/yarn-checks'); const { printChangeSummary, startTimer, stopTimer, timedExec, - timedExecOrDie} = require('./pr-check/utils'); + timedExecOrDie, +} = require('./pr-check/utils'); const FILENAME = 'pr-check.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); @@ -102,16 +104,20 @@ const command = { timedExecOrDie('gulp dist --fortesting --single_pass --pseudo_names'); // TODO(choumx, #19658): --headless disabled for integration tests on // Travis until Chrome 72. - timedExecOrDie('gulp test --integration --nobuild ' - + '--compiled --single_pass'); + timedExecOrDie( + 'gulp test --integration --nobuild ' + '--compiled --single_pass' + ); timedExecOrDie('rm -R dist'); }, runVisualDiffTests: function(opt_mode) { if (!process.env.PERCY_PROJECT || !process.env.PERCY_TOKEN) { console.log( - '\n' + FILELOGPREFIX, 'Could not find environment variables', - colors.cyan('PERCY_PROJECT'), 'and', - colors.cyan('PERCY_TOKEN') + '. Skipping visual diff tests.'); + '\n' + FILELOGPREFIX, + 'Could not find environment variables', + colors.cyan('PERCY_PROJECT'), + 'and', + colors.cyan('PERCY_TOKEN') + '. Skipping visual diff tests.' + ); return; } let cmd = 'gulp visual-diff --nobuild'; @@ -122,8 +128,12 @@ const command = { } const {status} = timedExec(cmd); if (status != 0) { - console.error(FILELOGPREFIX, colors.red('ERROR:'), - 'Found errors while running', colors.cyan(cmd)); + console.error( + FILELOGPREFIX, + colors.red('ERROR:'), + 'Found errors while running', + colors.cyan(cmd) + ); } }, runPresubmitTests: function() { @@ -175,8 +185,10 @@ function main() { const startTime = startTimer(FILENAME, FILENAME); // Make sure package.json and yarn.lock are in sync and up-to-date. - if (!isYarnLockFileInSync(FILENAME) || - !isYarnLockFileProperlyUpdated(FILENAME)) { + if ( + !isYarnLockFileInSync(FILENAME) || + !isYarnLockFileProperlyUpdated(FILENAME) + ) { return 1; } diff --git a/build-system/pr-check/build-targets.js b/build-system/pr-check/build-targets.js index a02fa2a476902..babf3355adc92 100644 --- a/build-system/pr-check/build-targets.js +++ b/build-system/pr-check/build-targets.js @@ -43,7 +43,8 @@ function isValidatorWebuiFile(filePath) { * @return {boolean} */ function isBuildSystemFile(filePath) { - return (filePath.startsWith('build-system') && + return ( + (filePath.startsWith('build-system') && // Exclude textproto from build-system since we want it to trigger // tests and type check. path.extname(filePath) != '.textproto' && @@ -55,9 +56,10 @@ function isBuildSystemFile(filePath) { !isDevDashboardFile(filePath) && // Exclude visual diff files from build-system since we want it to trigger // visual diff tests. - !isVisualDiffFile(filePath)) - // OWNERS.yaml files should trigger build system to run tests - || isOwnersFile(filePath); + !isVisualDiffFile(filePath)) || + // OWNERS.yaml files should trigger build system to run tests + isOwnersFile(filePath) + ); } /** @@ -67,13 +69,13 @@ function isBuildSystemFile(filePath) { * @return {boolean} */ function isBuildSystemAndRuntimeFile(filePath) { - return isBuildSystemFile(filePath) && - // These build system files are involved in the compilation/bundling and - // are likely to affect the runtime as well. - ( - filePath.startsWith('build-system/babel-plugins') || - filePath.startsWith('build-system/runner') - ); + return ( + isBuildSystemFile(filePath) && + // These build system files are involved in the compilation/bundling and + // are likely to affect the runtime as well. + (filePath.startsWith('build-system/babel-plugins') || + filePath.startsWith('build-system/runner')) + ); } /** @@ -101,9 +103,12 @@ function isValidatorFile(filePath) { // Validator files take the form of validator-.*\.(html|out|protoascii) const name = path.basename(filePath); - return name.startsWith('validator-') && - (name.endsWith('.out') || name.endsWith('.html') || - name.endsWith('.protoascii')); + return ( + name.startsWith('validator-') && + (name.endsWith('.out') || + name.endsWith('.html') || + name.endsWith('.protoascii')) + ); } /** @@ -131,9 +136,11 @@ function isDocFile(filePath) { */ function isVisualDiffFile(filePath) { const filename = path.basename(filePath); - return (filename == 'visual-diff.js' || - filename == 'visual-tests' || - filePath.startsWith('examples/visual-tests/')); + return ( + filename == 'visual-diff.js' || + filename == 'visual-tests' || + filePath.startsWith('examples/visual-tests/') + ); } /** @@ -155,8 +162,10 @@ function isUnitTest(filePath) { * @return {boolean} */ function isDevDashboardFile(filePath) { - return (filePath === 'build-system/app.js' || - filePath.startsWith('build-system/app-index/')); + return ( + filePath === 'build-system/app.js' || + filePath.startsWith('build-system/app-index/') + ); } /** @@ -178,7 +187,7 @@ function isIntegrationTest(filePath) { */ function isFlagConfig(filePath) { const filename = path.basename(filePath); - return (filename == 'prod-config.json' || filename == 'canary-config.json'); + return filename == 'prod-config.json' || filename == 'canary-config.json'; } /** @@ -191,17 +200,23 @@ function isFlagConfig(filePath) { function areValidBuildTargets(buildTargets, fileName) { const files = gitDiffNameOnlyMaster(); if (buildTargets.has('FLAG_CONFIG') && buildTargets.has('RUNTIME')) { - console.log(fileName, colors.red('ERROR:'), - 'Looks like your PR contains', - colors.cyan('{prod|canary}-config.json'), - 'in addition to some other files. Config and code are not kept in', - 'sync, and config needs to be backwards compatible with code for at', - 'least two weeks. See #8188'); + console.log( + fileName, + colors.red('ERROR:'), + 'Looks like your PR contains', + colors.cyan('{prod|canary}-config.json'), + 'in addition to some other files. Config and code are not kept in', + 'sync, and config needs to be backwards compatible with code for at', + 'least two weeks. See #8188' + ); const nonFlagConfigFiles = files.filter(file => !isFlagConfig(file)); const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - console.log(fileLogPrefix, colors.red('ERROR:'), - 'Please move these files to a separate PR:', - colors.cyan(nonFlagConfigFiles.join(', '))); + console.log( + fileLogPrefix, + colors.red('ERROR:'), + 'Please move these files to a separate PR:', + colors.cyan(nonFlagConfigFiles.join(', ')) + ); return false; } return true; @@ -226,7 +241,8 @@ function determineBuildTargets() { 'INTEGRATION_TEST', 'DOCS', 'FLAG_CONFIG', - 'VISUAL_DIFF']); + 'VISUAL_DIFF', + ]); } const targetSet = new Set(); for (const p of filePaths) { diff --git a/build-system/pr-check/build.js b/build-system/pr-check/build.js index 89c5ab6e2dd68..6ac48ba45a276 100644 --- a/build-system/pr-check/build.js +++ b/build-system/pr-check/build.js @@ -24,27 +24,32 @@ const colors = require('ansi-colors'); const { areValidBuildTargets, - determineBuildTargets} = require('./build-targets'); + determineBuildTargets, +} = require('./build-targets'); const { isYarnLockFileInSync, - isYarnLockFileProperlyUpdated} = require('./yarn-checks'); + isYarnLockFileProperlyUpdated, +} = require('./yarn-checks'); const { printChangeSummary, startTimer, stopTimer, timedExecOrDie: timedExecOrDieBase, - uploadBuildOutput} = require('./utils'); + uploadBuildOutput, +} = require('./utils'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'build.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); // Make sure package.json and yarn.lock are in sync and up-to-date. - if (!isYarnLockFileInSync(FILENAME) || - !isYarnLockFileProperlyUpdated(FILENAME)) { + if ( + !isYarnLockFileInSync(FILENAME) || + !isYarnLockFileProperlyUpdated(FILENAME) + ) { process.exitCode = 1; return; } @@ -62,20 +67,24 @@ function main() { uploadBuildOutput(FILENAME); } else { printChangeSummary(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('VISUAL_DIFF')) { - + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('FLAG_CONFIG') || + buildTargets.has('VISUAL_DIFF') + ) { timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp build --fortesting'); uploadBuildOutput(FILENAME); } else { - console.log(`${FILELOGPREFIX} Skipping ` + colors.cyan('Build ') + + console.log( + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Build ') + 'because this commit does not affect the runtime, build system, ' + - 'test files, or visual diff files'); + 'test files, or visual diff files' + ); } } diff --git a/build-system/pr-check/checks.js b/build-system/pr-check/checks.js index 1b0c2575a1396..74e2e07469ae6 100644 --- a/build-system/pr-check/checks.js +++ b/build-system/pr-check/checks.js @@ -26,14 +26,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const {reportAllExpectedTests} = require('../tasks/runtime-test/status-report'); const FILENAME = 'checks.js'; -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function runCommonChecks() { timedExecOrDie('gulp lint'); diff --git a/build-system/pr-check/dist-tests.js b/build-system/pr-check/dist-tests.js index 61d7cfed02780..097d17142b9e9 100644 --- a/build-system/pr-check/dist-tests.js +++ b/build-system/pr-check/dist-tests.js @@ -27,21 +27,23 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'dist-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function runSinglePassTest_() { timedExecOrDie('gulp clean'); timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp dist --fortesting --single_pass --pseudo_names'); - timedExecOrDie('gulp test --integration ' + - '--nobuild --compiled --single_pass --headless'); + timedExecOrDie( + 'gulp test --integration ' + '--nobuild --compiled --single_pass --headless' + ); } function main() { @@ -66,19 +68,22 @@ function main() { timedExecOrDie('gulp bundle-size --on_skipped_build'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST')) { - + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') + ) { runSinglePassTest_(); ranTests = true; } if (!ranTests) { - console.log(`${FILELOGPREFIX} Skipping ` + + console.log( + `${FILELOGPREFIX} Skipping ` + colors.cyan('Dist, Bundle Size, Single Pass Tests ') + 'because this commit does not affect the runtime, build system, ' + - 'or integration test files.'); + 'or integration test files.' + ); } } diff --git a/build-system/pr-check/dist.js b/build-system/pr-check/dist.js index 6dbb27b9ddf22..3f36259e510b3 100644 --- a/build-system/pr-check/dist.js +++ b/build-system/pr-check/dist.js @@ -27,12 +27,13 @@ const { startTimer, stopTimer, timedExecOrDie: timedExecOrDieBase, - uploadDistOutput} = require('./utils'); + uploadDistOutput, +} = require('./utils'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'dist.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -43,8 +44,11 @@ function main() { uploadDistOutput(FILENAME); } else { printChangeSummary(); - console.log(`${FILELOGPREFIX} Skipping ` + colors.cyan('Dist ') + - 'because this is a PR build'); + console.log( + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Dist ') + + 'because this is a PR build' + ); } stopTimer(FILENAME, FILENAME, startTime); diff --git a/build-system/pr-check/e2e-tests.js b/build-system/pr-check/e2e-tests.js index 0aeacd302c979..fa473a24d425f 100644 --- a/build-system/pr-check/e2e-tests.js +++ b/build-system/pr-check/e2e-tests.js @@ -27,12 +27,13 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'e2e-tests.js'; -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); async function main() { const startTime = startTimer(FILENAME, FILENAME); diff --git a/build-system/pr-check/local-tests.js b/build-system/pr-check/local-tests.js index 32e28d3f091c8..6bf9a877071b2 100644 --- a/build-system/pr-check/local-tests.js +++ b/build-system/pr-check/local-tests.js @@ -27,14 +27,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'local-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -50,34 +51,46 @@ function main() { } else { printChangeSummary(FILENAME); - if (!(buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST'))) { + if ( + !( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') + ) + ) { console.log( - `${FILELOGPREFIX} Skipping ` + colors.cyan('Local Tests ') + + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Local Tests ') + 'because this commit not affect the runtime, build system, ' + - 'unit test files, or integration test files.'); + 'unit test files, or integration test files.' + ); stopTimer(FILENAME, FILENAME, startTime); return; } downloadBuildOutput(FILENAME); timedExecOrDie('gulp update-packages'); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') + ) { timedExecOrDie('gulp test --unit --nobuild --headless --local-changes'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') + ) { timedExecOrDie('gulp test --integration --nobuild --headless --coverage'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') + ) { timedExecOrDie('gulp test --unit --nobuild --headless --coverage'); //TODO(estherkim): turn on when stabilized :) //timedExecOrDie('gulp e2e --nobuild'); diff --git a/build-system/pr-check/remote-tests.js b/build-system/pr-check/remote-tests.js index 858eab4baf0cd..19ebd92e89dd9 100644 --- a/build-system/pr-check/remote-tests.js +++ b/build-system/pr-check/remote-tests.js @@ -30,14 +30,15 @@ const { stopTimer, startSauceConnect, stopSauceConnect, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'remote-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); async function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -54,15 +55,20 @@ async function main() { stopSauceConnect(FILENAME); } else { printChangeSummary(FILENAME); - if (!(buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST') || - buildTargets.has('INTEGRATION_TEST'))) { + if ( + !( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') + ) + ) { console.log( - `${FILELOGPREFIX} Skipping ` + + `${FILELOGPREFIX} Skipping ` + colors.cyan('Remote (Sauce Labs) Tests ') + 'because this commit does not affect the runtime, ' + - 'build system, or integration test files.'); + 'build system, or integration test files.' + ); stopTimer(FILENAME, FILENAME, startTime); return; } @@ -70,15 +76,19 @@ async function main() { timedExecOrDie('gulp update-packages'); await startSauceConnect(FILENAME); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('UNIT_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') + ) { timedExecOrDie('gulp test --unit --nobuild --saucelabs_lite'); } - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST')) { + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') + ) { timedExecOrDie('gulp test --integration --nobuild --saucelabs'); } stopSauceConnect(FILENAME); diff --git a/build-system/pr-check/utils.js b/build-system/pr-check/utils.js index 68a5108aa9558..ed271a72f9257 100644 --- a/build-system/pr-check/utils.js +++ b/build-system/pr-check/utils.js @@ -25,18 +25,24 @@ const { gitTravisMasterBaseline, shortSha, } = require('../git'); +const { + isTravisBuild, + travisBuildNumber, + travisPullRequestSha, +} = require('../travis'); const {execOrDie, exec} = require('../exec'); -const {isTravisBuild, travisBuildNumber, travisPullRequestSha} = require('../travis'); -const BUILD_OUTPUT_FILE = - isTravisBuild() ? `amp_build_${travisBuildNumber()}.zip` : ''; -const DIST_OUTPUT_FILE = - isTravisBuild() ? `amp_dist_${travisBuildNumber()}.zip` : ''; +const BUILD_OUTPUT_FILE = isTravisBuild() + ? `amp_build_${travisBuildNumber()}.zip` + : ''; +const DIST_OUTPUT_FILE = isTravisBuild() + ? `amp_dist_${travisBuildNumber()}.zip` + : ''; const OUTPUT_DIRS = 'build/ dist/ dist.3p/ EXTENSIONS_CSS_MAP'; const OUTPUT_STORAGE_LOCATION = 'gs://amp-travis-builds'; const OUTPUT_STORAGE_KEY_FILE = 'sa-travis-key.json'; const OUTPUT_STORAGE_SERVICE_ACCOUNT = - 'sa-travis@amp-travis-build-storage.iam.gserviceaccount.com'; + 'sa-travis@amp-travis-build-storage.iam.gserviceaccount.com'; /** * Prints a summary of files changed by, and commits included in the PR. @@ -47,11 +53,13 @@ function printChangeSummary(fileName) { if (isTravisBuild()) { console.log( - `${fileLogPrefix} ${colors.cyan('origin/master')} is currently at ` + - `commit ${colors.cyan(shortSha(gitTravisMasterBaseline()))}`); + `${fileLogPrefix} ${colors.cyan('origin/master')} is currently at ` + + `commit ${colors.cyan(shortSha(gitTravisMasterBaseline()))}` + ); console.log( - `${fileLogPrefix} Testing the following changes at commit ` + - `${colors.cyan(shortSha(travisPullRequestSha()))}`); + `${fileLogPrefix} Testing the following changes at commit ` + + `${colors.cyan(shortSha(travisPullRequestSha()))}` + ); } const filesChanged = gitDiffStatMaster(); @@ -59,9 +67,10 @@ function printChangeSummary(fileName) { const branchPoint = gitMergeBaseMaster(); console.log( - `${fileLogPrefix} Commit log since branch ` + + `${fileLogPrefix} Commit log since branch ` + `${colors.cyan(gitBranchName())} was forked from ` + - `${colors.cyan('master')} at ${colors.cyan(shortSha(branchPoint))}:`); + `${colors.cyan('master')} at ${colors.cyan(shortSha(branchPoint))}:` + ); console.log(gitDiffCommitLog() + '\n'); } @@ -71,12 +80,17 @@ function printChangeSummary(fileName) { */ async function startSauceConnect(functionName) { process.env['SAUCE_USERNAME'] = 'amphtml'; - const response = await requestPromise('https://amphtml-sauce-token-dealer.appspot.com/getJwtToken'); + const response = await requestPromise( + 'https://amphtml-sauce-token-dealer.appspot.com/getJwtToken' + ); process.env['SAUCE_ACCESS_KEY'] = response.trim(); const startScCmd = 'build-system/sauce_connect/start_sauce_connect.sh'; const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - console.log('\n' + fileLogPrefix, - 'Starting Sauce Connect Proxy:', colors.cyan(startScCmd)); + console.log( + '\n' + fileLogPrefix, + 'Starting Sauce Connect Proxy:', + colors.cyan(startScCmd) + ); execOrDie(startScCmd); } @@ -87,8 +101,11 @@ async function startSauceConnect(functionName) { function stopSauceConnect(functionName) { const stopScCmd = 'build-system/sauce_connect/stop_sauce_connect.sh'; const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - console.log('\n' + fileLogPrefix, - 'Stopping Sauce Connect Proxy:', colors.cyan(stopScCmd)); + console.log( + '\n' + fileLogPrefix, + 'Stopping Sauce Connect Proxy:', + colors.cyan(stopScCmd) + ); execOrDie(stopScCmd); } @@ -102,7 +119,10 @@ function startTimer(functionName, fileName) { const startTime = Date.now(); const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); console.log( - '\n' + fileLogPrefix, 'Running', colors.cyan(functionName) + '...'); + '\n' + fileLogPrefix, + 'Running', + colors.cyan(functionName) + '...' + ); return startTime; } @@ -117,11 +137,15 @@ function stopTimer(functionName, fileName, startTime) { const endTime = Date.now(); const executionTime = endTime - startTime; const mins = Math.floor(executionTime / 60000); - const secs = Math.floor(executionTime % 60000 / 1000); + const secs = Math.floor((executionTime % 60000) / 1000); const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); console.log( - fileLogPrefix, 'Done running', colors.cyan(functionName), - 'Total time:', colors.green(mins + 'm ' + secs + 's')); + fileLogPrefix, + 'Done running', + colors.cyan(functionName), + 'Total time:', + colors.green(mins + 'm ' + secs + 's') + ); } /** @@ -157,19 +181,21 @@ function timedExecOrDie(cmd, fileName = 'utils.js') { */ async function downloadOutput_(functionName, outputFileName) { const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - const buildOutputDownloadUrl = - `${OUTPUT_STORAGE_LOCATION}/${outputFileName}`; + const buildOutputDownloadUrl = `${OUTPUT_STORAGE_LOCATION}/${outputFileName}`; console.log( - `${fileLogPrefix} Downloading build output from ` + - colors.cyan(buildOutputDownloadUrl) + '...'); + `${fileLogPrefix} Downloading build output from ` + + colors.cyan(buildOutputDownloadUrl) + + '...' + ); exec('echo travis_fold:start:download_results && echo'); authenticateWithStorageLocation_(); execOrDie(`gsutil cp ${buildOutputDownloadUrl} ${outputFileName}`); exec('echo travis_fold:end:download_results'); console.log( - `${fileLogPrefix} Extracting ` + colors.cyan(outputFileName) + '...'); + `${fileLogPrefix} Extracting ` + colors.cyan(outputFileName) + '...' + ); exec('echo travis_fold:start:unzip_results && echo'); execOrDie(`unzip -o ${outputFileName}`); exec('echo travis_fold:end:unzip_results'); @@ -190,16 +216,23 @@ async function uploadOutput_(functionName, outputFileName) { const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); console.log( - `\n${fileLogPrefix} Compressing ` + + `\n${fileLogPrefix} Compressing ` + colors.cyan(OUTPUT_DIRS.split(' ').join(', ')) + - ' into ' + colors.cyan(outputFileName) + '...'); + ' into ' + + colors.cyan(outputFileName) + + '...' + ); exec('echo travis_fold:start:zip_results && echo'); execOrDie(`zip -r ${outputFileName} ${OUTPUT_DIRS}`); exec('echo travis_fold:end:zip_results'); console.log( - `${fileLogPrefix} Uploading ` + colors.cyan(outputFileName) + ' to ' + - colors.cyan(OUTPUT_STORAGE_LOCATION) + '...'); + `${fileLogPrefix} Uploading ` + + colors.cyan(outputFileName) + + ' to ' + + colors.cyan(OUTPUT_STORAGE_LOCATION) + + '...' + ); exec('echo travis_fold:start:upload_results && echo'); authenticateWithStorageLocation_(); execOrDie(`gsutil -m cp -r ${outputFileName} ${OUTPUT_STORAGE_LOCATION}`); @@ -208,8 +241,10 @@ async function uploadOutput_(functionName, outputFileName) { function authenticateWithStorageLocation_() { decryptTravisKey_(); - execOrDie('gcloud auth activate-service-account ' + - `--key-file ${OUTPUT_STORAGE_KEY_FILE}`); + execOrDie( + 'gcloud auth activate-service-account ' + + `--key-file ${OUTPUT_STORAGE_KEY_FILE}` + ); execOrDie(`gcloud config set account ${OUTPUT_STORAGE_SERVICE_ACCOUNT}`); execOrDie('gcloud config set pass_credentials_to_gsutil true'); execOrDie('gcloud config list'); @@ -254,8 +289,10 @@ function decryptTravisKey_() { // -md sha256 is required due to encryption differences between // openssl 1.1.1a, which was used to encrypt the key, and // openssl 1.0.2g, which is used by Travis to decrypt. - execOrDie(`openssl aes-256-cbc -md sha256 -k ${process.env.GCP_TOKEN} -in ` + - `build-system/sa-travis-key.json.enc -out ${OUTPUT_STORAGE_KEY_FILE} -d`); + execOrDie( + `openssl aes-256-cbc -md sha256 -k ${process.env.GCP_TOKEN} -in ` + + `build-system/sa-travis-key.json.enc -out ${OUTPUT_STORAGE_KEY_FILE} -d` + ); } module.exports = { diff --git a/build-system/pr-check/validator-tests.js b/build-system/pr-check/validator-tests.js index 0719fe187c569..e7ff6be3c8fc5 100644 --- a/build-system/pr-check/validator-tests.js +++ b/build-system/pr-check/validator-tests.js @@ -26,14 +26,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'validator-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -50,9 +51,11 @@ function main() { timedExecOrDie('gulp validator-webui'); } else { console.log( - `${FILELOGPREFIX} Skipping ` + colors.cyan('Validator Tests ') + + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Validator Tests ') + 'because this commit does not affect ' + - 'the validator or validator web UI.'); + 'the validator or validator web UI.' + ); } } diff --git a/build-system/pr-check/visual-diff-tests.js b/build-system/pr-check/visual-diff-tests.js index 2ff344349b167..21e7d95747e81 100644 --- a/build-system/pr-check/visual-diff-tests.js +++ b/build-system/pr-check/visual-diff-tests.js @@ -28,14 +28,15 @@ const { printChangeSummary, startTimer, stopTimer, - timedExecOrDie: timedExecOrDieBase} = require('./utils'); + timedExecOrDie: timedExecOrDieBase, +} = require('./utils'); const {determineBuildTargets} = require('./build-targets'); const {isTravisPullRequestBuild} = require('../travis'); const FILENAME = 'visual-diff-tests.js'; const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = - (cmd, unusedFileName) => timedExecOrDieBase(cmd, FILENAME); +const timedExecOrDie = (cmd, unusedFileName) => + timedExecOrDieBase(cmd, FILENAME); function main() { const startTime = startTimer(FILENAME, FILENAME); @@ -49,22 +50,25 @@ function main() { } else { printChangeSummary(FILENAME); process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); - if (buildTargets.has('RUNTIME') || - buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('VISUAL_DIFF') || - buildTargets.has('FLAG_CONFIG')) { - + if ( + buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('VISUAL_DIFF') || + buildTargets.has('FLAG_CONFIG') + ) { downloadBuildOutput(FILENAME); timedExecOrDie('gulp update-packages'); timedExecOrDie('gulp visual-diff --nobuild'); } else { timedExecOrDie('gulp visual-diff --nobuild --empty'); console.log( - `${FILELOGPREFIX} Skipping ` + colors.cyan('Visual Diff Tests ') + + `${FILELOGPREFIX} Skipping ` + + colors.cyan('Visual Diff Tests ') + 'because this commit does not affect the ' + 'runtime, build system, integration test files, ' + - 'visual diff test files, or flag config files.'); + 'visual diff test files, or flag config files.' + ); } } diff --git a/build-system/pr-check/yarn-checks.js b/build-system/pr-check/yarn-checks.js index 90f128485e596..529d7c7dfc7c9 100644 --- a/build-system/pr-check/yarn-checks.js +++ b/build-system/pr-check/yarn-checks.js @@ -34,19 +34,34 @@ function isYarnLockFileInSync(fileName = 'yarn-checks.js') { const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); const yarnIntegrityCheck = getStderr('yarn check --integrity').trim(); if (yarnIntegrityCheck.includes('error')) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Found the following', colors.cyan('yarn'), 'errors:\n' + - colors.cyan(yarnIntegrityCheck)); - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Updates to', colors.cyan('package.json'), - 'must be accompanied by a corresponding update to', - colors.cyan('yarn.lock')); - console.error(fileLogPrefix, colors.yellow('NOTE:'), - 'To update', colors.cyan('yarn.lock'), 'after changing', - colors.cyan('package.json') + ',', 'run', - '"' + colors.cyan('yarn install') + '"', - 'and include the updated', colors.cyan('yarn.lock'), - 'in your PR.'); + console.error( + fileLogPrefix, + colors.red('ERROR:'), + 'Found the following', + colors.cyan('yarn'), + 'errors:\n' + colors.cyan(yarnIntegrityCheck) + ); + console.error( + fileLogPrefix, + colors.red('ERROR:'), + 'Updates to', + colors.cyan('package.json'), + 'must be accompanied by a corresponding update to', + colors.cyan('yarn.lock') + ); + console.error( + fileLogPrefix, + colors.yellow('NOTE:'), + 'To update', + colors.cyan('yarn.lock'), + 'after changing', + colors.cyan('package.json') + ',', + 'run', + '"' + colors.cyan('yarn install') + '"', + 'and include the updated', + colors.cyan('yarn.lock'), + 'in your PR.' + ); return false; } return true; @@ -62,12 +77,20 @@ function isYarnLockFileProperlyUpdated(fileName = 'yarn-checks.js') { const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); if (localChanges.includes('yarn.lock')) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'This PR did not properly update', colors.cyan('yarn.lock') + '.'); - console.error(fileLogPrefix, colors.yellow('NOTE:'), - 'To fix this, sync your branch to', colors.cyan('upstream/master') + - ', run', colors.cyan('gulp update-packages') + - ', and push a new commit containing the changes.'); + console.error( + fileLogPrefix, + colors.red('ERROR:'), + 'This PR did not properly update', + colors.cyan('yarn.lock') + '.' + ); + console.error( + fileLogPrefix, + colors.yellow('NOTE:'), + 'To fix this, sync your branch to', + colors.cyan('upstream/master') + ', run', + colors.cyan('gulp update-packages') + + ', and push a new commit containing the changes.' + ); console.error(fileLogPrefix, 'Expected changes:'); console.log(localChanges); return false; diff --git a/build-system/recaptcha-router.js b/build-system/recaptcha-router.js index 1b273ba3a931b..64837f0d16dcb 100644 --- a/build-system/recaptcha-router.js +++ b/build-system/recaptcha-router.js @@ -33,7 +33,10 @@ window.grecaptcha = { const recaptchaFrameRequestHandler = (req, res, next) => { if (global.AMP_TESTING) { fs.readFileAsync(pc.cwd() + req.path, 'utf8').then(file => { - file = file.replace(/initRecaptcha\(.*\)/g, 'initRecaptcha("/recaptcha/mock.js?sitekey=")'); + file = file.replace( + /initRecaptcha\(.*\)/g, + 'initRecaptcha("/recaptcha/mock.js?sitekey=")' + ); res.end(file); }); } else { @@ -45,34 +48,31 @@ recaptchaRouter.get('/mock.js', (req, res) => { res.end(recaptchaMock); }); -recaptchaRouter.post( - '/submit', - upload.array(), - (req, res) => { - cors.enableCors(req, res); +recaptchaRouter.post('/submit', upload.array(), (req, res) => { + cors.enableCors(req, res); - const responseJson = { - message: 'Success!', - }; + const responseJson = { + message: 'Success!', + }; - Object.keys(req.body).forEach(bodyKey => { - responseJson[bodyKey] = req.body[bodyKey]; - }); + Object.keys(req.body).forEach(bodyKey => { + responseJson[bodyKey] = req.body[bodyKey]; + }); - const containsRecaptchaInResponse = Object.keys(responseJson) - .some(responseJsonKey => { - return responseJsonKey.toLowerCase().includes('recaptcha'); - }); - - if (containsRecaptchaInResponse) { - res.status(200).json(responseJson); - } else { - res.status(400).json({ - message: 'Did not include a recaptcha token', - }); - } + const containsRecaptchaInResponse = Object.keys(responseJson).some( + responseJsonKey => { + return responseJsonKey.toLowerCase().includes('recaptcha'); } -); + ); + + if (containsRecaptchaInResponse) { + res.status(200).json(responseJson); + } else { + res.status(400).json({ + message: 'Did not include a recaptcha token', + }); + } +}); module.exports = { recaptchaFrameRequestHandler, diff --git a/build-system/routes/list.js b/build-system/routes/list.js index da5f0bd03867e..9fdb868951f4f 100644 --- a/build-system/routes/list.js +++ b/build-system/routes/list.js @@ -48,8 +48,9 @@ router.get('/search/countries', function(req, res) { if (req.query.hasOwnProperty('q')) { const query = req.query.q.toLowerCase(); - filtered = countries.items - .filter(country => country.name.toLowerCase().startsWith(query)); + filtered = countries.items.filter(country => + country.name.toLowerCase().startsWith(query) + ); } const results = { @@ -76,10 +77,14 @@ const squareImgUrl = width => { const randomFalsy = () => { const rand = randInt(4); switch (rand) { - case 1: return null; - case 2: return undefined; - case 3: return ''; - default: return false; + case 1: + return null; + case 2: + return undefined; + case 3: + return ''; + default: + return false; } }; @@ -108,8 +113,9 @@ const generateResults = (category, count = 2) => { } r.items = items; - r['load-more-src'] = - `/list/infinite-scroll-random/${category}?${randInt(10000)}`; + r['load-more-src'] = `/list/infinite-scroll-random/${category}?${randInt( + 10000 + )}`; return r; }; @@ -146,15 +152,18 @@ router.get('/infinite-scroll', function(req, res) { const items = generateJson(numberOfItems, pagesLeft); - const nextUrl = '/list/infinite-scroll?items=' + - numberOfItems + '&left=' + (pagesLeft - 1) + - '&latency=' + latency; + const nextUrl = + '/list/infinite-scroll?items=' + + numberOfItems + + '&left=' + + (pagesLeft - 1) + + '&latency=' + + latency; const next = pagesLeft == 0 ? randomFalsy() : nextUrl; - const results = next === false ? {items} - : {items, next, - 'loadMoreButtonText': 'test', - 'loadMoreEndText': 'end', - }; + const results = + next === false + ? {items} + : {items, next, 'loadMoreButtonText': 'test', 'loadMoreEndText': 'end'}; if (latency) { setTimeout(() => res.json(results), latency); @@ -178,8 +187,11 @@ router.get('/infinite-scroll-state', function(req, res) { const numberOfItems = query['items'] || 2; const pagesLeft = query['left'] || 0; const items = generateJsonWithState(numberOfItems, pagesLeft); - const next = '/list/infinite-scroll-state?left=' + (pagesLeft - 1) - + '&items=' + numberOfItems; + const next = + '/list/infinite-scroll-state?left=' + + (pagesLeft - 1) + + '&items=' + + numberOfItems; const results = { items, next, diff --git a/build-system/scope-require.js b/build-system/scope-require.js index a19cabd75d484..058f5d87827e7 100644 --- a/build-system/scope-require.js +++ b/build-system/scope-require.js @@ -39,9 +39,10 @@ function scopeRequire(src, scopeName) { const flatGlobals = globals.reduce((acc, g) => acc.concat(g.nodes), []); flatGlobals - .filter(node => isIdentifier(node) && isRequire(node)) - .forEach(node => - replaceIdentifier(node.parent, test => test === node, scopeName)); + .filter(node => isIdentifier(node) && isRequire(node)) + .forEach(node => + replaceIdentifier(node.parent, test => test === node, scopeName) + ); return escodegen.generate(ast, {format: {compact: true}}); } @@ -102,35 +103,44 @@ function createMemberNode(identifierNode, scopeName) { }; } - program - .description('Scope global `require` calls to an object.') - .option('-i, --infile [filename]', 'The path of the input file.' + - ' Reads from stdin if unspecified') - .option('-o, --outfile [filename]', 'The path for the output file.' + - ' Writes to stdout if unspecified.') - .option('-n --name [name]', 'The name to reference `require` calls from.' + - ' The default is `AMP`', 'AMP') - .parse(process.argv); + .description('Scope global `require` calls to an object.') + .option( + '-i, --infile [filename]', + 'The path of the input file.' + ' Reads from stdin if unspecified' + ) + .option( + '-o, --outfile [filename]', + 'The path for the output file.' + ' Writes to stdout if unspecified.' + ) + .option( + '-n --name [name]', + 'The name to reference `require` calls from.' + ' The default is `AMP`', + 'AMP' + ) + .parse(process.argv); -const inputStream = (program.infile && program.infile !== '-' ? - fs.createReadStream(program.infile) : - process.stdin); +const inputStream = + program.infile && program.infile !== '-' + ? fs.createReadStream(program.infile) + : process.stdin; inputStream.on('error', err => { - console./*OK*/error(colors.red('\nError reading file: ' + err.path)); + console./*OK*/ error(colors.red('\nError reading file: ' + err.path)); }); -const outputStream = (program.outfile && program.outfile !== '-' ? - fs.createWriteStream(program.outfile) : - process.stdout); +const outputStream = + program.outfile && program.outfile !== '-' + ? fs.createWriteStream(program.outfile) + : process.stdout; outputStream.on('error', err => { - console./*OK*/error(colors.red('\nError writing file: ' + err.path)); + console./*OK*/ error(colors.red('\nError writing file: ' + err.path)); }); const scopeRequireStream = es.map((inputFile, cb) => - cb(null, scopeRequire(inputFile.toString('utf8'), program.name))); + cb(null, scopeRequire(inputFile.toString('utf8'), program.name)) +); inputStream - .pipe(es.wait()) - .pipe(scopeRequireStream) - .pipe(outputStream); + .pipe(es.wait()) + .pipe(scopeRequireStream) + .pipe(outputStream); diff --git a/build-system/server.js b/build-system/server.js index 2f6f6dad0ae9b..fb0fadae57a5c 100644 --- a/build-system/server.js +++ b/build-system/server.js @@ -36,8 +36,8 @@ const { const useHttps = process.env.SERVE_USEHTTPS == 'true'; const quiet = process.env.SERVE_QUIET == 'true'; const sendCachingHeaders = process.env.SERVE_CACHING_HEADERS == 'true'; -const noCachingExtensions = process.env.SERVE_EXTENSIONS_WITHOUT_CACHING == - 'true'; +const noCachingExtensions = + process.env.SERVE_EXTENSIONS_WITHOUT_CACHING == 'true'; const header = require('connect-header'); // Exit if the port is in use. @@ -64,9 +64,11 @@ if (!quiet) { } middleware.push(app); if (sendCachingHeaders) { - middleware.push(header({ - 'cache-control': ' max-age=600', - })); + middleware.push( + header({ + 'cache-control': ' max-age=600', + }) + ); } if (noCachingExtensions) { @@ -80,11 +82,12 @@ if (noCachingExtensions) { } // Start gulp webserver -gulp.src(process.cwd()) - .pipe(webserver({ - port, - host, - directoryListing: true, - https: useHttps, - middleware, - })); +gulp.src(process.cwd()).pipe( + webserver({ + port, + host, + directoryListing: true, + https: useHttps, + middleware, + }) +); diff --git a/build-system/shadow-viewer.js b/build-system/shadow-viewer.js index 6a9633ce1c117..30f406a53c1fa 100644 --- a/build-system/shadow-viewer.js +++ b/build-system/shadow-viewer.js @@ -144,19 +144,19 @@ const SCRIPT = ` }; `; - -const renderShadowViewer = ({src, baseHref}) => html` - - - - - - - - -`; - +const renderShadowViewer = ({src, baseHref}) => html` + + + + + + + + + + +`; module.exports = {renderShadowViewer}; diff --git a/build-system/shorten-license.js b/build-system/shorten-license.js index 78794aa445593..a62b72a60ed10 100644 --- a/build-system/shorten-license.js +++ b/build-system/shorten-license.js @@ -90,4 +90,3 @@ module.exports = function() { return pumpify.obj(streams); }; - diff --git a/build-system/single-pass.js b/build-system/single-pass.js index fffb320913564..0bc2745f6ee7d 100644 --- a/build-system/single-pass.js +++ b/build-system/single-pass.js @@ -36,11 +36,11 @@ const {isTravisBuild} = require('./travis'); const {TopologicalSort} = require('topological-sort'); const TYPES_VALUES = Object.keys(TYPES).map(x => TYPES[x]); const wrappers = require('./compile-wrappers'); -const {VERSION: internalRuntimeVersion} = require('./internal-version') ; +const {VERSION: internalRuntimeVersion} = require('./internal-version'); const argv = minimist(process.argv.slice(2)); -let singlePassDest = typeof argv.single_pass_dest === 'string' ? - argv.single_pass_dest : './dist/'; +let singlePassDest = + typeof argv.single_pass_dest === 'string' ? argv.single_pass_dest : './dist/'; if (!singlePassDest.endsWith('/')) { singlePassDest = `${singlePassDest}/`; @@ -60,24 +60,26 @@ const commonJsModules = [ const mainBundle = 'src/amp.js'; const extensionsInfo = {}; -let extensions = extensionBundles.concat(altMainBundles) - .filter(unsupportedExtensions).map(ext => { - const path = buildFullPathFromConfig(ext); - if (Array.isArray(path)) { - path.forEach((p, index) => { - extensionsInfo[p] = Object.create(ext); - extensionsInfo[p].filename = ext.name + '-' + ext.version[index]; - }); +let extensions = extensionBundles + .concat(altMainBundles) + .filter(unsupportedExtensions) + .map(ext => { + const path = buildFullPathFromConfig(ext); + if (Array.isArray(path)) { + path.forEach((p, index) => { + extensionsInfo[p] = Object.create(ext); + extensionsInfo[p].filename = ext.name + '-' + ext.version[index]; + }); + } else { + extensionsInfo[path] = Object.create(ext); + if (isAltMainBundle(ext.name) && ext.path) { + extensionsInfo[path].filename = ext.name; } else { - extensionsInfo[path] = Object.create(ext); - if (isAltMainBundle(ext.name) && ext.path) { - extensionsInfo[path].filename = ext.name; - } else { - extensionsInfo[path].filename = ext.name + '-' + ext.version; - } + extensionsInfo[path].filename = ext.name + '-' + ext.version; } - return path; - }); + } + return path; + }); // Flatten nested arrays to support multiple versions extensions = [].concat.apply([], extensions); @@ -95,9 +97,7 @@ exports.getFlags = function(config) { create_source_map: '%outname%.map', parse_inline_source_maps: true, apply_input_source_maps: true, - source_map_location_mapping: [ - '|/', - ], + source_map_location_mapping: ['|/'], //new_type_inf: true, language_in: 'ES6', language_out: config.language_out || 'ES5', @@ -124,24 +124,25 @@ exports.getFlags = function(config) { // Turn object into deterministically sorted array. const flagsArray = []; - Object.keys(flags).sort().forEach(function(flag) { - const val = flags[flag]; - if (val instanceof Array) { - val.forEach(function(item) { - flagsArray.push('--' + flag, item); - }); - } else { - if (val != null) { - flagsArray.push('--' + flag, val); + Object.keys(flags) + .sort() + .forEach(function(flag) { + const val = flags[flag]; + if (val instanceof Array) { + val.forEach(function(item) { + flagsArray.push('--' + flag, item); + }); } else { - flagsArray.push('--' + flag); + if (val != null) { + flagsArray.push('--' + flag, val); + } else { + flagsArray.push('--' + flag); + } } - } - }); + }); return exports.getGraph(config.modules, config).then(function(g) { - return flagsArray.concat( - exports.getBundleFlags(g, flagsArray)); + return flagsArray.concat(exports.getBundleFlags(g, flagsArray)); }); }; @@ -151,9 +152,11 @@ exports.getBundleFlags = function(g) { // Write all the packages (directories with a package.json) as --js // inputs to the flags. Closure compiler reads the packages to resolve // non-relative module names. - Object.keys(g.packages).sort().forEach(function(pkg) { - flagsArray.push('--js', pkg); - }); + Object.keys(g.packages) + .sort() + .forEach(function(pkg) { + flagsArray.push('--js', pkg); + }); // Build up the weird flag structure that closure compiler calls // modules and we call bundles. @@ -188,8 +191,12 @@ exports.getBundleFlags = function(g) { } else { // TODO(@cramforce): Remove special case. if (!/_base/.test(bundle.name)) { - throw new Error('Unexpected missing extension info ' + bundle.name + - ',' + JSON.stringify(bundle)); + throw new Error( + 'Unexpected missing extension info ' + + bundle.name + + ',' + + JSON.stringify(bundle) + ); } name = bundle.name; info = { @@ -197,7 +204,7 @@ exports.getBundleFlags = function(g) { }; } // And now build --module $name:$numberOfJsFiles:$bundleDeps - let cmd = name + ':' + (bundle.modules.length); + let cmd = name + ':' + bundle.modules.length; const bundleDeps = []; if (!isMain) { const configEntry = getExtensionBundleConfig(originalName); @@ -217,8 +224,8 @@ exports.getBundleFlags = function(g) { flagsArray.push('--module', cmd); if (bundleKeys.length > 1) { function massageWrapper(w) { - return (w.replace('<%= contents %>', '%s') - /*+ '\n//# sourceMappingURL=%basename%.map\n'*/); + return w.replace('<%= contents %>', '%s'); + /*+ '\n//# sourceMappingURL=%basename%.map\n'*/ } // We need to post wrap the main bundles. We can't wrap v0.js either // since it would have the wrapper already when we read it and prepend @@ -227,11 +234,23 @@ exports.getBundleFlags = function(g) { jsFilesToWrap.push(name); } else { const configEntry = getExtensionBundleConfig(originalName); - const marker = configEntry && Array.isArray(configEntry.postPrepend) ? - SPLIT_MARKER : ''; - flagsArray.push('--module_wrapper', name + ':' + - massageWrapper(wrappers.extension( - info.name, info.loadPriority, bundleDeps, marker))); + const marker = + configEntry && Array.isArray(configEntry.postPrepend) + ? SPLIT_MARKER + : ''; + flagsArray.push( + '--module_wrapper', + name + + ':' + + massageWrapper( + wrappers.extension( + info.name, + info.loadPriority, + bundleDeps, + marker + ) + ) + ); } } else { throw new Error('Expect to build more than one bundle.'); @@ -288,72 +307,87 @@ exports.getGraph = function(entryModules, config) { deps: true, detectGlobals: false, }) - // The second stage are transforms that closure compiler supports - // directly and which we don't want to apply during deps finding. - .transform(babelify, { - compact: false, - plugins: [ - require.resolve('babel-plugin-transform-es2015-modules-commonjs'), - ], - }); + // The second stage are transforms that closure compiler supports + // directly and which we don't want to apply during deps finding. + .transform(babelify, { + compact: false, + plugins: [ + require.resolve('babel-plugin-transform-es2015-modules-commonjs'), + ], + }); // This gets us the actual deps. We collect them in an array, so // we can sort them prior to building the dep tree. Otherwise the tree // will not be stable. const depEntries = []; - b.pipeline.get('deps').push(through.obj(function(row, enc, next) { - row.source = null; // Release memory - depEntries.push(row); - next(); - })); - - b.bundle().on('end', function() { - const edges = {}; - depEntries.sort(function(a, b) { - return a.id < b.id; - }).forEach(function(row) { - const id = unifyPath(exports.maybeAddDotJs( - relativePath(process.cwd(), row.id))); - topo.addNode(id, id); - const deps = edges[id] = Object.keys(row.deps).sort().map(function(dep) { - return unifyPath(relativePath(process.cwd(), - row.deps[dep])); - }); - graph.deps[id] = deps; - if (row.entry) { - graph.depOf[id] = {}; - graph.depOf[id][id] = true; // Self edge. - deps.forEach(function(dep) { - graph.depOf[id][dep] = true; + b.pipeline.get('deps').push( + through.obj(function(row, enc, next) { + row.source = null; // Release memory + depEntries.push(row); + next(); + }) + ); + + b.bundle() + .on('end', function() { + const edges = {}; + depEntries + .sort(function(a, b) { + return a.id < b.id; + }) + .forEach(function(row) { + const id = unifyPath( + exports.maybeAddDotJs(relativePath(process.cwd(), row.id)) + ); + topo.addNode(id, id); + const deps = (edges[id] = Object.keys(row.deps) + .sort() + .map(function(dep) { + return unifyPath(relativePath(process.cwd(), row.deps[dep])); + })); + graph.deps[id] = deps; + if (row.entry) { + graph.depOf[id] = {}; + graph.depOf[id][id] = true; // Self edge. + deps.forEach(function(dep) { + graph.depOf[id][dep] = true; + }); + } }); - } - }); - Object.keys(edges).sort().forEach(function(id) { - edges[id].forEach(function(dep) { - topo.addEdge(id, dep); - }); - }); - graph.sorted = Array.from(topo.sort().keys()).reverse(); - - setupBundles(graph); - transformPathsToTempDir(graph, config); - resolve(graph); - fs.writeFileSync('deps.txt', JSON.stringify(graph, null, 2)); - }).on('error', reject).pipe(devnull()); + Object.keys(edges) + .sort() + .forEach(function(id) { + edges[id].forEach(function(dep) { + topo.addEdge(id, dep); + }); + }); + graph.sorted = Array.from(topo.sort().keys()).reverse(); + + setupBundles(graph); + transformPathsToTempDir(graph, config); + resolve(graph); + fs.writeFileSync('deps.txt', JSON.stringify(graph, null, 2)); + }) + .on('error', reject) + .pipe(devnull()); return promise; }; function setupBundles(graph) { // For each module, mark them as to whether any of the entry // modules depends on them (transitively). - Array.from(graph.sorted).reverse().forEach(function(id) { - graph.deps[id].forEach(function(dep) { - Object.keys(graph.depOf).sort().forEach(function(entry) { - if (graph.depOf[entry][id]) { - graph.depOf[entry][dep] = true; - } + Array.from(graph.sorted) + .reverse() + .forEach(function(id) { + graph.deps[id].forEach(function(dep) { + Object.keys(graph.depOf) + .sort() + .forEach(function(entry) { + if (graph.depOf[entry][id]) { + graph.depOf[entry][dep] = true; + } + }); }); }); - }); // Create the bundles. graph.sorted.forEach(function(id) { @@ -363,18 +397,26 @@ function setupBundles(graph) { // Bundles that this item must be available to. const bundleDestCandidates = []; // Count in how many bundles a modules wants to be. - Object.keys(graph.depOf).sort().forEach(function(entry) { - if (graph.depOf[entry][id]) { - inBundleCount++; - dest = entry; - const configEntry = getExtensionBundleConfig(entry); - const type = configEntry ? configEntry.type : mainBundle; - bundleDestCandidates.push(type); - } - }); - console/*OK*/.assert(inBundleCount >= 1, - 'Should be in at least 1 bundle', id, 'Bundle count', - inBundleCount, graph.depOf); + Object.keys(graph.depOf) + .sort() + .forEach(function(entry) { + if (graph.depOf[entry][id]) { + inBundleCount++; + dest = entry; + const configEntry = getExtensionBundleConfig(entry); + const type = configEntry ? configEntry.type : mainBundle; + bundleDestCandidates.push(type); + } + }); + console /*OK*/ + .assert( + inBundleCount >= 1, + 'Should be in at least 1 bundle', + id, + 'Bundle count', + inBundleCount, + graph.depOf + ); // If a module is in more than 1 bundle, it must go into _base. if (bundleDestCandidates.length > 1) { const first = bundleDestCandidates[0]; @@ -507,28 +549,34 @@ function isAltMainBundle(name) { } exports.singlePassCompile = function(entryModule, options) { - return exports.getFlags({ - modules: [entryModule].concat(extensions), - writeTo: singlePassDest, - define: options.define, - externs: options.externs, - hideWarningsFor: options.hideWarningsFor, - }).then(compile).then(function() { - // Move things into place as AMP expects them. - fs.ensureDirSync(`${singlePassDest}/v0`); - return Promise.all([ - // Move all files that need to live in /v0/. ex. _base files - // all extensions. - move(`${singlePassDest}/amp*`, `${singlePassDest}/v0`).then(() => { - return move('dist/v0/amp4ads*', 'dist'); - }), - move(`${singlePassDest}/_base*`, `${singlePassDest}/v0`), - ]); - }).then(wrapMainBinaries).then(postProcessConcat).catch(e => { - // NOTE: passing the message here to colors.red breaks the output. - console./*OK*/error(e.message); - process.exit(1); - }); + return exports + .getFlags({ + modules: [entryModule].concat(extensions), + writeTo: singlePassDest, + define: options.define, + externs: options.externs, + hideWarningsFor: options.hideWarningsFor, + }) + .then(compile) + .then(function() { + // Move things into place as AMP expects them. + fs.ensureDirSync(`${singlePassDest}/v0`); + return Promise.all([ + // Move all files that need to live in /v0/. ex. _base files + // all extensions. + move(`${singlePassDest}/amp*`, `${singlePassDest}/v0`).then(() => { + return move('dist/v0/amp4ads*', 'dist'); + }), + move(`${singlePassDest}/_base*`, `${singlePassDest}/v0`), + ]); + }) + .then(wrapMainBinaries) + .then(postProcessConcat) + .catch(e => { + // NOTE: passing the message here to colors.red breaks the output. + console./*OK*/ error(e.message); + process.exit(1); + }); }; /** @@ -550,8 +598,11 @@ function wrapMainBinaries() { const path = `dist/${x}.js`; const bootstrapCode = path === 'dist/v0.js' ? '' : mainFile; const isAmpAltstring = path === 'dist/v0.js' ? '' : 'self.IS_AMP_ALT=1;'; - fs.writeFileSync(path, `${isAmpAltstring}${prefix}${bootstrapCode}` + - `${fs.readFileSync(path).toString()}${suffix}`); + fs.writeFileSync( + path, + `${isAmpAltstring}${prefix}${bootstrapCode}` + + `${fs.readFileSync(path).toString()}${suffix}` + ); }); } @@ -561,8 +612,7 @@ function wrapMainBinaries() { * source maps. */ function postProcessConcat() { - const extensions = extensionBundles.filter( - x => Array.isArray(x.postPrepend)); + const extensions = extensionBundles.filter(x => Array.isArray(x.postPrepend)); extensions.forEach(extension => { const isAltMainBundle = altMainBundles.some(x => { return x.name === extension.name; @@ -581,11 +631,15 @@ function postProcessConcat() { targets.push(createFullPath(extension.version)); } targets.forEach(path => { - const prependContent = extension.postPrepend.map(x => { - return ';' + fs.readFileSync(x, 'utf8').toString(); - }).join(''); - const content = fs.readFileSync(path, 'utf8').toString() - .split(SPLIT_MARKER); + const prependContent = extension.postPrepend + .map(x => { + return ';' + fs.readFileSync(x, 'utf8').toString(); + }) + .join(''); + const content = fs + .readFileSync(path, 'utf8') + .toString() + .split(SPLIT_MARKER); const prefix = content[0]; const suffix = content[1]; fs.writeFileSync(path, prefix + prependContent + suffix, 'utf8'); @@ -619,8 +673,11 @@ function compile(flagsArray) { }); } else { reject( - new Error(colors.red('Single pass compilation failed:\n') + - formatSinglePassClosureCompilerError(stdErr))); + new Error( + colors.red('Single pass compilation failed:\n') + + formatSinglePassClosureCompilerError(stdErr) + ) + ); } }); }); diff --git a/build-system/tasks/ava.js b/build-system/tasks/ava.js index 4efc4a572df98..9334ada41ab8e 100644 --- a/build-system/tasks/ava.js +++ b/build-system/tasks/ava.js @@ -23,12 +23,13 @@ const {isTravisBuild} = require('../travis'); * Runs ava tests. */ function runAvaTests() { - return gulp.src([ - 'csvify-size/test.js', - 'get-zindex/test.js', - 'prepend-global/test.js', - ]) - .pipe(ava({silent: isTravisBuild()})); + return gulp + .src([ + 'csvify-size/test.js', + 'get-zindex/test.js', + 'prepend-global/test.js', + ]) + .pipe(ava({silent: isTravisBuild()})); } gulp.task('ava', 'Runs ava tests for gulp tasks', runAvaTests); diff --git a/build-system/tasks/bundle-size.js b/build-system/tasks/bundle-size.js index a5fd1d5d900f7..2cb01d48b8a67 100644 --- a/build-system/tasks/bundle-size.js +++ b/build-system/tasks/bundle-size.js @@ -70,21 +70,32 @@ function getGzippedBundleSize() { */ function storeBundleSize() { if (!isTravisPushBuild()) { - log(yellow('Skipping'), cyan('--on_push_build') + ':', - 'this action can only be performed on `push` builds on Travis'); + log( + yellow('Skipping'), + cyan('--on_push_build') + ':', + 'this action can only be performed on `push` builds on Travis' + ); return; } if (travisRepoSlug() !== expectedGitHubRepoSlug) { - log(yellow('Skipping'), cyan('--on_push_build') + ':', - 'this action can only be performed on Travis builds on the', - cyan(expectedGitHubRepoSlug), 'repository'); + log( + yellow('Skipping'), + cyan('--on_push_build') + ':', + 'this action can only be performed on Travis builds on the', + cyan(expectedGitHubRepoSlug), + 'repository' + ); return; } if (!process.env.GITHUB_ARTIFACTS_RW_TOKEN) { - log(red('ERROR: Missing GITHUB_ARTIFACTS_RW_TOKEN, cannot store the ' + - 'bundle size in the artifacts repository on GitHub!')); + log( + red( + 'ERROR: Missing GITHUB_ARTIFACTS_RW_TOKEN, cannot store the ' + + 'bundle size in the artifacts repository on GitHub!' + ) + ); process.exitCode = 1; return; } @@ -99,23 +110,43 @@ function storeBundleSize() { auth: `token ${process.env.GITHUB_ARTIFACTS_RW_TOKEN}`, }); - return octokit.repos.getContents(githubApiCallOptions).then(() => { - log('The file', cyan(`bundle-size/${commitHash}`), 'already exists in the', - 'build artifacts repository on GitHub. Skipping...'); - }).catch(() => { - return octokit.repos.createFile(Object.assign(githubApiCallOptions, { - message: `bundle-size: ${commitHash} (${bundleSize})`, - content: Buffer.from(bundleSize).toString('base64'), - })).then(() => { - log('Stored the new bundle size of', cyan(bundleSize), 'in the artifacts', - 'repository on GitHub'); - }).catch(error => { - log(red(`ERROR: Failed to create the bundle-size/${commitHash} file in`), - red('the build artifacts repository on GitHub!')); - log(red('Error message was:'), error.message); - process.exitCode = 1; + return octokit.repos + .getContents(githubApiCallOptions) + .then(() => { + log( + 'The file', + cyan(`bundle-size/${commitHash}`), + 'already exists in the', + 'build artifacts repository on GitHub. Skipping...' + ); + }) + .catch(() => { + return octokit.repos + .createFile( + Object.assign(githubApiCallOptions, { + message: `bundle-size: ${commitHash} (${bundleSize})`, + content: Buffer.from(bundleSize).toString('base64'), + }) + ) + .then(() => { + log( + 'Stored the new bundle size of', + cyan(bundleSize), + 'in the artifacts', + 'repository on GitHub' + ); + }) + .catch(error => { + log( + red( + `ERROR: Failed to create the bundle-size/${commitHash} file in` + ), + red('the build artifacts repository on GitHub!') + ); + log(red('Error message was:'), error.message); + process.exitCode = 1; + }); }); - }); } /** @@ -125,12 +156,16 @@ async function skipBundleSize() { if (isTravisPullRequestBuild()) { const commitHash = gitCommitHash(); try { - const response = await requestPost(url.resolve(bundleSizeAppBaseUrl, - path.join('commit', commitHash, 'skip'))); + const response = await requestPost( + url.resolve( + bundleSizeAppBaseUrl, + path.join('commit', commitHash, 'skip') + ) + ); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + - response.body); + `${response.statusCode} ${response.statusMessage}: ` + response.body + ); } } catch (error) { log(red('Could not report a skipped pull request')); @@ -139,8 +174,12 @@ async function skipBundleSize() { return; } } else { - log(yellow('Not marking this pull request to skip because that can only be ' - + 'done on Travis')); + log( + yellow( + 'Not marking this pull request to skip because that can only be ' + + 'done on Travis' + ) + ); } } @@ -154,8 +193,10 @@ async function reportBundleSize() { const commitHash = gitCommitHash(); try { const response = await requestPost({ - uri: url.resolve(bundleSizeAppBaseUrl, - path.join('commit', commitHash, 'report')), + uri: url.resolve( + bundleSizeAppBaseUrl, + path.join('commit', commitHash, 'report') + ), json: true, body: { baseSha, @@ -164,8 +205,8 @@ async function reportBundleSize() { }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + - response.body); + `${response.statusCode} ${response.statusMessage}: ` + response.body + ); } } catch (error) { log(red('Could not report the bundle size of this pull request')); @@ -174,8 +215,12 @@ async function reportBundleSize() { return; } } else { - log(yellow('Not reporting the bundle size of this pull request because ' - + 'that can only be done on Travis')); + log( + yellow( + 'Not reporting the bundle size of this pull request because ' + + 'that can only be done on Travis' + ) + ); } } @@ -192,18 +237,20 @@ async function performBundleSizeCheck() { } } - gulp.task( - 'bundle-size', - 'Checks if the minified AMP binary has exceeded its size cap', - performBundleSizeCheck, - { - options: { - 'on_push_build': ' Store bundle size in AMP build artifacts repo ' - + '(also implies --on_pr_build)', - 'on_pr_build': ' Report the bundle size of this pull request to ' - + 'GitHub', - 'on_skipped_build': ' Set the status of this pull request\'s bundle ' - + 'size check in GitHub to `skipped`', - }, - }); + 'bundle-size', + 'Checks if the minified AMP binary has exceeded its size cap', + performBundleSizeCheck, + { + options: { + 'on_push_build': + ' Store bundle size in AMP build artifacts repo ' + + '(also implies --on_pr_build)', + 'on_pr_build': + ' Report the bundle size of this pull request to ' + 'GitHub', + 'on_skipped_build': + " Set the status of this pull request's bundle " + + 'size check in GitHub to `skipped`', + }, + } +); diff --git a/build-system/tasks/changelog.js b/build-system/tasks/changelog.js index f8f688cadbc8f..f2ba54f21df3a 100644 --- a/build-system/tasks/changelog.js +++ b/build-system/tasks/changelog.js @@ -101,12 +101,20 @@ let PrMetadataDef; function changelog() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('Warning! You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log( + colors.red( + 'Warning! You have not set the ' + + 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.' + ) + ); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } @@ -128,25 +136,24 @@ function getGitMetadata() { branch: undefined, }; return getLastProdReleasedGitTag(gitMetadata) - .then(getCurrentBranchName) - .then(getGitLog) - .then(getGithubPullRequestsMetadata) - .then(getGithubFilesMetadata) - .then(getLastGitTag) - .then(buildChangelog) - .then(function(gitMetadata) { - log(colors.blue('\n' + gitMetadata.changelog)); - if (isDryrun) { - return; - } - return getCurrentSha().then( - submitReleaseNotes.bind(null, argv.tag, gitMetadata.changelog) - ); - }) - .catch(errHandler); + .then(getCurrentBranchName) + .then(getGitLog) + .then(getGithubPullRequestsMetadata) + .then(getGithubFilesMetadata) + .then(getLastGitTag) + .then(buildChangelog) + .then(function(gitMetadata) { + log(colors.blue('\n' + gitMetadata.changelog)); + if (isDryrun) { + return; + } + return getCurrentSha().then( + submitReleaseNotes.bind(null, argv.tag, gitMetadata.changelog) + ); + }) + .catch(errHandler); } - /** * Get the last git tag this current HEAD bases off of from. * @@ -235,39 +242,45 @@ function buildChangelog(gitMetadata) { let changelog = `## Version: ${argv.tag}\n\n`; if (gitMetadata.baseTag) { - changelog += `## Baseline: [${gitMetadata.baseTag}]` + - '(https://github.com/ampproject/amphtml/releases/' + - `tag/${gitMetadata.baseTag})\n\n`; + changelog += + `## Baseline: [${gitMetadata.baseTag}]` + + '(https://github.com/ampproject/amphtml/releases/' + + `tag/${gitMetadata.baseTag})\n\n`; } // Append all titles - changelog += gitMetadata.logs.filter(function(log) { - const {pr} = log; - if (!pr) { - return true; - } - // Ignore PRs that are just all docs changes. - return !pr.filenames.every(function(filename) { - return config.changelogIgnoreFileTypes.test(filename); - }); - }).map(function(log) { - const {pr} = log; - if (!pr) { - return ' - ' + log.title; - } - return ` - ${pr.title.trim()} (#${pr.id})`; - }).join('\n'); + changelog += gitMetadata.logs + .filter(function(log) { + const {pr} = log; + if (!pr) { + return true; + } + // Ignore PRs that are just all docs changes. + return !pr.filenames.every(function(filename) { + return config.changelogIgnoreFileTypes.test(filename); + }); + }) + .map(function(log) { + const {pr} = log; + if (!pr) { + return ' - ' + log.title; + } + return ` - ${pr.title.trim()} (#${pr.id})`; + }) + .join('\n'); changelog += '\n\n## Breakdown by component\n\n'; const sections = buildSections(gitMetadata); - Object.keys(sections).sort().forEach(function(section) { - changelog += `
    \n${section}\n`; - const uniqueItems = sections[section].filter(function(title, idx) { - return sections[section].indexOf(title) == idx; + Object.keys(sections) + .sort() + .forEach(function(section) { + changelog += `
    \n${section}\n`; + const uniqueItems = sections[section].filter(function(title, idx) { + return sections[section].indexOf(title) == idx; + }); + changelog += uniqueItems.join(''); + changelog += '
    \n'; }); - changelog += uniqueItems.join(''); - changelog += '
    \n'; - }); gitMetadata.changelog = changelog; return gitMetadata; @@ -353,15 +366,21 @@ function getLastProdReleasedGitTag(gitMetadata) { * @return {!Promise} */ function getGitLog(gitMetadata) { - const args = `log ${gitMetadata.branch}...${gitMetadata.tag} ` + - '--pretty=oneline --first-parent'; + const args = + `log ${gitMetadata.branch}...${gitMetadata.tag} ` + + '--pretty=oneline --first-parent'; const options = {args}; return gitExec(options).then(function(logs) { if (!logs) { - throw new Error('No logs found "git log ' + gitMetadata.branch + '...' + - gitMetadata.tag + '".\nIs it possible that there is no delta?\n' + + throw new Error( + 'No logs found "git log ' + + gitMetadata.branch + + '...' + + gitMetadata.tag + + '".\nIs it possible that there is no delta?\n' + 'Make sure to fetch and rebase (or reset --hard) the latest ' + - 'from remote upstream.'); + 'from remote upstream.' + ); } const commits = logs.split('\n').filter(log => !!log.length); gitMetadata.logs = commits.map(log => { @@ -384,31 +403,31 @@ function getGithubPullRequestsMetadata(gitMetadata) { getClosedPullRequests(2), getClosedPullRequests(3), ]) - .then(requests => [].concat.apply([], requests)) - .then(prs => { - gitMetadata.prs = prs; - const githubPrRequest = gitMetadata.logs.map(log => { - const pr = prs.filter(pr => pr.merge_commit_sha == log.sha)[0]; - if (pr) { - log.pr = buildPrMetadata(pr); - } else if (isPrIdInTitle(log.title)) { - const id = getPrIdFromCommit(log.title); - const prOptions = extend({}, pullOptions); - prOptions.url += `/${id}`; - const fileOptions = extend({}, prOptions); - fileOptions.url += '/files'; - // If we couldn't find the matching pull request from 3 pages - // of closed pull request try and fetch it through the id - // if we can retrieve it from the commit message (only available - // through github merge). - return getPullRequest(prOptions, log); - } - return BBPromise.resolve(); - }); - return BBPromise.all(githubPrRequest).then(() => { - return gitMetadata; - }); + .then(requests => [].concat.apply([], requests)) + .then(prs => { + gitMetadata.prs = prs; + const githubPrRequest = gitMetadata.logs.map(log => { + const pr = prs.filter(pr => pr.merge_commit_sha == log.sha)[0]; + if (pr) { + log.pr = buildPrMetadata(pr); + } else if (isPrIdInTitle(log.title)) { + const id = getPrIdFromCommit(log.title); + const prOptions = extend({}, pullOptions); + prOptions.url += `/${id}`; + const fileOptions = extend({}, prOptions); + fileOptions.url += '/files'; + // If we couldn't find the matching pull request from 3 pages + // of closed pull request try and fetch it through the id + // if we can retrieve it from the commit message (only available + // through github merge). + return getPullRequest(prOptions, log); + } + return BBPromise.resolve(); }); + return BBPromise.all(githubPrRequest).then(() => { + return gitMetadata; + }); + }); } /** @@ -477,8 +496,10 @@ function getPullRequestFiles(filesOption, pr) { return request(filesOption).then(function(res) { const body = JSON.parse(res.body); - assert(Array.isArray(body) && body.length > 0, - 'Pull request response must not be empty. ' + res.body); + assert( + Array.isArray(body) && body.length > 0, + 'Pull request response must not be empty. ' + res.body + ); const filenames = body.map(function(file) { return file.filename; }); @@ -502,7 +523,7 @@ function errHandler(err) { * @return {boolean} */ function isPrIdInTitle(str) { - return str./* OK*/indexOf('Merge pull request #') == 0; + return str./* OK*/ indexOf('Merge pull request #') == 0; } /** @@ -523,10 +544,9 @@ function getPrIdFromCommit(commit) { * @return {boolean} */ function isJs(str) { - return str./* OK*/endsWith('.js'); + return str./* OK*/ endsWith('.js'); } - /** * @param {!JSONValue} pr * @return {!PrMetadata} @@ -543,12 +563,20 @@ function buildPrMetadata(pr) { function changelogUpdate() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('Warning! You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log( + colors.red( + 'Warning! You have not set the ' + + 'GITHUB_ACCESS_TOKEN env var. Aborting "changelog" task.' + ) + ); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } if (!argv.message) { @@ -558,8 +586,9 @@ function changelogUpdate() { } function update() { - const url = 'https://api.github.com/repos/ampproject/amphtml/releases/tags/' + - `${argv.tag}`; + const url = + 'https://api.github.com/repos/ampproject/amphtml/releases/tags/' + + `${argv.tag}`; const tagsOptions = { url, method: 'GET', @@ -601,12 +630,13 @@ function update() { } else { releasesOptions.body.body = argv.message + release.body; } - return request(releasesOptions).then(() => { - log(colors.green('Update Successful.')); - }) - .catch(e => { - log(colors.red('Update Failed. ' + e.message)); - }); + return request(releasesOptions) + .then(() => { + log(colors.green('Update Successful.')); + }) + .catch(e => { + log(colors.red('Update Failed. ' + e.message)); + }); }); } @@ -618,8 +648,9 @@ gulp.task('changelog', 'Create github release draft', changelog, { }, }); -const updateMessage = 'Update github release. Ex. prepend ' + - 'canary percentage changes to release'; +const updateMessage = + 'Update github release. Ex. prepend ' + + 'canary percentage changes to release'; gulp.task('changelog:update', updateMessage, changelogUpdate, { options: { dryrun: ' Generate changelog but dont push it out', diff --git a/build-system/tasks/check-links.js b/build-system/tasks/check-links.js index d01c4ec86d096..f89e5aff424c7 100644 --- a/build-system/tasks/check-links.js +++ b/build-system/tasks/check-links.js @@ -52,60 +52,65 @@ function checkLinks() { const linkCheckers = markdownFiles.map(function(markdownFile) { return runLinkChecker(markdownFile); }); - return BBPromise.all(linkCheckers) - .then(function(allResults) { - let deadLinksFound = false; - const filesWithDeadLinks = []; - allResults.map(function(results, index) { - // Skip files that were deleted by the PR. - if (!fs.existsSync(markdownFiles[index])) { - return; - } - let deadLinksFoundInFile = false; - results.forEach(function(result) { - // Skip links to files that were introduced by the PR. - if (isLinkToFileIntroducedByPR(result.link)) { - return; - } - if (result.status === 'dead') { - deadLinksFound = true; - deadLinksFoundInFile = true; - log('[%s] %s', colors.red('✖'), result.link); - } else if (!isTravisBuild()) { - log('[%s] %s', colors.green('✔'), result.link); - } - }); - if (deadLinksFoundInFile) { - filesWithDeadLinks.push(markdownFiles[index]); - log( - colors.red('ERROR'), - 'Possible dead link(s) found in', - colors.magenta(markdownFiles[index])); - } else { - log( - colors.green('SUCCESS'), - 'All links in', - colors.magenta(markdownFiles[index]), 'are alive.'); - } - }); - if (deadLinksFound) { - log( - colors.red('ERROR'), - 'Please update dead link(s) in', - colors.magenta(filesWithDeadLinks.join(',')), - 'or whitelist them in build-system/tasks/check-links.js'); - log( - colors.yellow('NOTE'), - 'If the link(s) above are not meant to resolve to a real webpage', - 'surrounding them with backticks will exempt them from the link', - 'checker.'); - process.exit(1); - } else { - log( - colors.green('SUCCESS'), - 'All links in all markdown files in this branch are alive.'); + return BBPromise.all(linkCheckers).then(function(allResults) { + let deadLinksFound = false; + const filesWithDeadLinks = []; + allResults.map(function(results, index) { + // Skip files that were deleted by the PR. + if (!fs.existsSync(markdownFiles[index])) { + return; + } + let deadLinksFoundInFile = false; + results.forEach(function(result) { + // Skip links to files that were introduced by the PR. + if (isLinkToFileIntroducedByPR(result.link)) { + return; + } + if (result.status === 'dead') { + deadLinksFound = true; + deadLinksFoundInFile = true; + log('[%s] %s', colors.red('✖'), result.link); + } else if (!isTravisBuild()) { + log('[%s] %s', colors.green('✔'), result.link); } }); + if (deadLinksFoundInFile) { + filesWithDeadLinks.push(markdownFiles[index]); + log( + colors.red('ERROR'), + 'Possible dead link(s) found in', + colors.magenta(markdownFiles[index]) + ); + } else { + log( + colors.green('SUCCESS'), + 'All links in', + colors.magenta(markdownFiles[index]), + 'are alive.' + ); + } + }); + if (deadLinksFound) { + log( + colors.red('ERROR'), + 'Please update dead link(s) in', + colors.magenta(filesWithDeadLinks.join(',')), + 'or whitelist them in build-system/tasks/check-links.js' + ); + log( + colors.yellow('NOTE'), + 'If the link(s) above are not meant to resolve to a real webpage', + 'surrounding them with backticks will exempt them from the link', + 'checker.' + ); + process.exit(1); + } else { + log( + colors.green('SUCCESS'), + 'All links in all markdown files in this branch are alive.' + ); + } + }); } /** @@ -116,7 +121,7 @@ function checkLinks() { */ function isLinkToFileIntroducedByPR(link) { return gitDiffAddedNameOnlyMaster().some(function(file) { - return (file.length > 0 && link.includes(path.parse(file).base)); + return file.length > 0 && link.includes(path.parse(file).base); }); } @@ -130,8 +135,10 @@ function filterWhitelistedLinks(markdown) { let filteredMarkdown = markdown; // localhost links optionally preceded by ( or [ (not served on Travis) - filteredMarkdown = - filteredMarkdown.replace(/(\(|\[)?http:\/\/localhost:8000/g, ''); + filteredMarkdown = filteredMarkdown.replace( + /(\(|\[)?http:\/\/localhost:8000/g, + '' + ); // Links in script tags (illustrative, and not always valid) filteredMarkdown = filteredMarkdown.replace(/src="http.*?"/g, ''); @@ -144,11 +151,15 @@ function filterWhitelistedLinks(markdown) { // The heroku nightly build page is not always acccessible by the checker. filteredMarkdown = filteredMarkdown.replace( - /\(http:\/\/amphtml-nightly\.herokuapp\.com\/\)/g, ''); + /\(http:\/\/amphtml-nightly\.herokuapp\.com\/\)/g, + '' + ); // The Googlebot help page is currently only available to signed-in users. filteredMarkdown = filteredMarkdown.replace( - /\(https:\/\/support\.google\.com\/webmasters\/answer\/182072\)/g, ''); + /\(https:\/\/support\.google\.com\/webmasters\/answer\/182072\)/g, + '' + ); // After all whitelisting is done, clean up any remaining empty blocks bounded // by backticks. Otherwise, `` will be treated as the start of a code block @@ -173,19 +184,19 @@ function runLinkChecker(markdownFile) { const markdown = fs.readFileSync(markdownFile).toString(); const filteredMarkdown = filterWhitelistedLinks(markdown); const opts = { - baseUrl: 'file://' + path.dirname(path.resolve((markdownFile))), + baseUrl: 'file://' + path.dirname(path.resolve(markdownFile)), }; return markdownLinkCheck(filteredMarkdown, opts); } gulp.task( - 'check-links', - 'Detects dead links in markdown files', - maybeUpdatePackages, - checkLinks, - { - options: { - 'files': ' CSV list of files in which to check links', - }, - } + 'check-links', + 'Detects dead links in markdown files', + maybeUpdatePackages, + checkLinks, + { + options: { + 'files': ' CSV list of files in which to check links', + }, + } ); diff --git a/build-system/tasks/clean.js b/build-system/tasks/clean.js index d84845359da49..f571871ac36b3 100644 --- a/build-system/tasks/clean.js +++ b/build-system/tasks/clean.js @@ -18,19 +18,11 @@ const del = require('del'); const gulp = require('gulp-help')(require('gulp')); - /** * Clean up the build artifacts */ function clean() { - return del([ - 'dist', - 'dist.3p', - 'dist.tools', - 'build', - '.amp-build', - ]); + return del(['dist', 'dist.3p', 'dist.tools', 'build', '.amp-build']); } - gulp.task('clean', 'Removes build output', clean); diff --git a/build-system/tasks/compile-expr.js b/build-system/tasks/compile-expr.js index 73c4ccb9c2d0b..3a0ad65f4c842 100644 --- a/build-system/tasks/compile-expr.js +++ b/build-system/tasks/compile-expr.js @@ -37,18 +37,14 @@ function compileExpr(path, jisonFilename, imports, parserName, jsFilename) { const generator = new jison.Generator(bnf, settings); const jsModule = generator.generate(settings); - const license = fs.readFileSync( - 'build-system/tasks/js-license.txt', 'utf8'); - const suppressCheckTypes = '/** @fileoverview ' + - '@suppress {checkTypes, suspiciousCode, uselessCode} */'; + const license = fs.readFileSync('build-system/tasks/js-license.txt', 'utf8'); + const suppressCheckTypes = + '/** @fileoverview ' + + '@suppress {checkTypes, suspiciousCode, uselessCode} */'; const jsExports = 'export const ' + parserName + ' = parser;'; - const out = [ - license, - suppressCheckTypes, - imports, - jsModule, - jsExports] + const out = + [license, suppressCheckTypes, imports, jsModule, jsExports] .join('\n\n') // Required in order to support babel 7, since 'token-stack: true' will // adversely affect lexer performance. @@ -69,7 +65,7 @@ function compileAccessExpr() { function compileBindExpr() { const path = 'extensions/amp-bind/0.1/'; const jisonFilename = 'bind-expr-impl.jison'; - const imports = 'import {AstNode, AstNodeType} from \'./bind-expr-defines\';'; + const imports = "import {AstNode, AstNodeType} from './bind-expr-defines';"; const parserName = 'bindParser'; const jsFilename = 'bind-expr-impl.js'; compileExpr(path, jisonFilename, imports, parserName, jsFilename); @@ -78,7 +74,7 @@ function compileBindExpr() { function compileCssExpr() { const path = 'extensions/amp-animation/0.1/parsers/'; const jisonFilename = 'css-expr-impl.jison'; - const imports = 'import * as ast from \'./css-expr-ast\';'; + const imports = "import * as ast from './css-expr-ast';"; const parserName = 'cssParser'; const jsFilename = 'css-expr-impl.js'; compileExpr(path, jisonFilename, imports, parserName, jsFilename); diff --git a/build-system/tasks/compile.js b/build-system/tasks/compile.js index 2498191fa2336..ef1f03bbfc579 100644 --- a/build-system/tasks/compile.js +++ b/build-system/tasks/compile.js @@ -28,7 +28,7 @@ const sourcemaps = require('gulp-sourcemaps'); const {highlight} = require('cli-highlight'); const {isTravisBuild} = require('../travis'); const {singlePassCompile} = require('../single-pass'); -const {VERSION: internalRuntimeVersion} = require('../internal-version') ; +const {VERSION: internalRuntimeVersion} = require('../internal-version'); const isProdBuild = !!argv.type; const queue = []; @@ -39,26 +39,32 @@ const NAILGUN_PORT = '2113'; // Also used in gulpfile.js // Compiles AMP with the closure compiler. This is intended only for // production use. During development we intend to continue using // babel, as it has much faster incremental compilation. -exports.closureCompile = function(entryModuleFilename, outputDir, - outputFilename, options) { +exports.closureCompile = function( + entryModuleFilename, + outputDir, + outputFilename, + options +) { // Rate limit closure compilation to MAX_PARALLEL_CLOSURE_INVOCATIONS // concurrent processes. return new Promise(function(resolve) { function start() { inProgress++; - compile(entryModuleFilename, outputDir, outputFilename, options) - .then(function() { - if (isTravisBuild()) { - // Print a progress dot after each task to avoid Travis timeouts. - process.stdout.write('.'); - } - inProgress--; - next(); - resolve(); - }, function(e) { - console./*OK*/error(colors.red('Compilation error:'), e.message); - process.exit(1); - }); + compile(entryModuleFilename, outputDir, outputFilename, options).then( + function() { + if (isTravisBuild()) { + // Print a progress dot after each task to avoid Travis timeouts. + process.stdout.write('.'); + } + inProgress--; + next(); + resolve(); + }, + function(e) { + console./*OK*/ error(colors.red('Compilation error:'), e.message); + process.exit(1); + } + ); } function next() { if (!queue.length) { @@ -124,9 +130,7 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { 'third_party/moment/moment.extern.js', 'third_party/react-externs/externs.js', ]; - const define = [ - `VERSION=${internalRuntimeVersion}`, - ]; + const define = [`VERSION=${internalRuntimeVersion}`]; if (argv.pseudo_names) { define.push('PSEUDO_NAMES=true'); } @@ -146,19 +150,18 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { define.push('ESM_BUILD=true'); } - console/*OK*/.assert(typeof entryModuleFilenames == 'string'); + console /*OK*/ + .assert(typeof entryModuleFilenames == 'string'); const entryModule = entryModuleFilenames; // TODO(@cramforce): Run the post processing step - return singlePassCompile( - entryModule, - compilationOptions - ).then(() => { + return singlePassCompile(entryModule, compilationOptions).then(() => { return new Promise((resolve, reject) => { - gulp.src(outputDir + '/**/*.js') - .pipe(shortenLicense()) - .pipe(gulp.dest(outputDir)) - .on('end', resolve) - .on('error', reject); + gulp + .src(outputDir + '/**/*.js') + .pipe(shortenLicense()) + .pipe(gulp.dest(outputDir)) + .on('end', resolve) + .on('error', reject); }); }); } @@ -186,8 +189,10 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { let sourceMapBase = 'http://localhost:8000/'; if (isProdBuild) { // Point sourcemap to fetch files from correct GitHub tag. - sourceMapBase = 'https://raw.githubusercontent.com/ampproject/amphtml/' + - internalRuntimeVersion + '/'; + sourceMapBase = + 'https://raw.githubusercontent.com/ampproject/amphtml/' + + internalRuntimeVersion + + '/'; } const srcs = [ '3p/3p.js', @@ -265,9 +270,9 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { 'node_modules/web-activities/activity-ports.js', 'node_modules/@ampproject/animations/dist/animations.mjs', 'node_modules/@ampproject/worker-dom/dist/' + - 'unminified.index.safe.mjs.patched.js', + 'unminified.index.safe.mjs.patched.js', 'node_modules/document-register-element/build/' + - 'document-register-element.patched.js', + 'document-register-element.patched.js', // 'node_modules/core-js/modules/**.js', // Not sure what these files are, but they seem to duplicate code // one level below and confuse the compiler. @@ -294,9 +299,7 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { srcs.push.apply(srcs, options.extraGlobs); } if (options.include3pDirectories) { - srcs.push( - '3p/**/*.js', - 'ads/**/*.js'); + srcs.push('3p/**/*.js', 'ads/**/*.js'); } // Many files include the polyfills, but we only want to deliver them // once. Since all files automatically wait for the main binary to load @@ -308,31 +311,36 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { return p !== 'custom-elements.js'; }); srcs.push( - '!build/fake-module/src/polyfills.js', - '!build/fake-module/src/polyfills/**/*.js', - '!build/fake-polyfills/src/polyfills.js', - 'src/polyfills/custom-elements.js', - 'build/fake-polyfills/**/*.js'); + '!build/fake-module/src/polyfills.js', + '!build/fake-module/src/polyfills/**/*.js', + '!build/fake-polyfills/src/polyfills.js', + 'src/polyfills/custom-elements.js', + 'build/fake-polyfills/**/*.js' + ); polyfillsShadowList.forEach(polyfillFile => { srcs.push(`!src/polyfills/${polyfillFile}`); - fs.writeFileSync('build/fake-polyfills/src/polyfills/' + polyfillFile, - 'export function install() {}'); + fs.writeFileSync( + 'build/fake-polyfills/src/polyfills/' + polyfillFile, + 'export function install() {}' + ); }); } else if (options.includePolyfills) { srcs.push( - '!build/fake-module/src/polyfills.js', - '!build/fake-module/src/polyfills/**/*.js', - '!build/fake-polyfills/**/*.js', + '!build/fake-module/src/polyfills.js', + '!build/fake-module/src/polyfills/**/*.js', + '!build/fake-polyfills/**/*.js' ); } else { - srcs.push('!src/polyfills.js', '!build/fake-polyfills/**/*.js',); + srcs.push('!src/polyfills.js', '!build/fake-polyfills/**/*.js'); unneededFiles.push('build/fake-module/src/polyfills.js'); } unneededFiles.forEach(function(fake) { if (!fs.existsSync(fake)) { - fs.writeFileSync(fake, - '// Not needed in closure compiler\n' + - 'export function deadCode() {}'); + fs.writeFileSync( + fake, + '// Not needed in closure compiler\n' + + 'export function deadCode() {}' + ); } }); @@ -389,13 +397,14 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { // it won't do strict type checking if its whitespace only. compilerOptions.define.push('TYPECHECK_ONLY=true'); compilerOptions.jscomp_error.push( - 'checkTypes', - 'accessControls', - 'const', - 'constantProperty', - 'globalThis'); + 'checkTypes', + 'accessControls', + 'const', + 'constantProperty', + 'globalThis' + ); compilerOptions.conformance_configs = - 'build-system/conformance-config.textproto'; + 'build-system/conformance-config.textproto'; } if (compilerOptions.define.length == 0) { @@ -406,13 +415,15 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { const pluginOptions = { platform: ['java'], // Override the binary used by closure compiler extraArguments: ['-XX:+TieredCompilation'], // Significant speed up! - logger: errors => compilerErrors = errors, // Capture compiler errors + logger: errors => (compilerErrors = errors), // Capture compiler errors }; // SIGNIFICANTLY speed up compilation on Mac and Linux using nailgun // See https://github.com/facebook/nailgun. - if (!argv.single_pass && - (process.platform == 'darwin' || process.platform == 'linux')) { + if ( + !argv.single_pass && + (process.platform == 'darwin' || process.platform == 'linux') + ) { const compilerOptionsArray = [ '--nailgun-port', NAILGUN_PORT, @@ -439,33 +450,38 @@ function compile(entryModuleFilenames, outputDir, outputFilename, options) { } // Override to local closure compiler JAR - closureCompiler.compiler.JAR_PATH = - require.resolve('../runner/dist/runner.jar'); + closureCompiler.compiler.JAR_PATH = require.resolve( + '../runner/dist/runner.jar' + ); const handleCompilerError = function(err) { - console./*OK*/error(colors.red( - 'Compilation failed for ' + outputFilename + ':\n') + - formatClosureCompilerError(compilerErrors) + '\n' + - err); + console./*OK*/ error( + colors.red('Compilation failed for ' + outputFilename + ':\n') + + formatClosureCompilerError(compilerErrors) + + '\n' + + err + ); process.exit(1); }; if (options.typeCheckOnly) { - return gulp.src(srcs, {base: '.'}) - .pipe(closureCompiler.gulp()(compilerOptions, pluginOptions)) - .on('error', handleCompilerError) - .pipe(nop()) - .on('end', resolve); + return gulp + .src(srcs, {base: '.'}) + .pipe(closureCompiler.gulp()(compilerOptions, pluginOptions)) + .on('error', handleCompilerError) + .pipe(nop()) + .on('end', resolve); } else { - return gulp.src(srcs, {base: '.'}) - .pipe(sourcemaps.init({loadMaps: true})) - .pipe(closureCompiler.gulp()(compilerOptions, pluginOptions)) - .on('error', handleCompilerError) - .pipe(rename(outputFilename)) - .pipe(sourcemaps.write('.')) - .pipe(shortenLicense()) - .pipe(gulp.dest(outputDir)) - .on('end', resolve); + return gulp + .src(srcs, {base: '.'}) + .pipe(sourcemaps.init({loadMaps: true})) + .pipe(closureCompiler.gulp()(compilerOptions, pluginOptions)) + .on('error', handleCompilerError) + .pipe(rename(outputFilename)) + .pipe(sourcemaps.write('.')) + .pipe(shortenLicense()) + .pipe(gulp.dest(outputDir)) + .on('end', resolve); } }); } diff --git a/build-system/tasks/create-golden-css/index.js b/build-system/tasks/create-golden-css/index.js index b1ebc36191989..1be878eb9e78e 100644 --- a/build-system/tasks/create-golden-css/index.js +++ b/build-system/tasks/create-golden-css/index.js @@ -27,5 +27,8 @@ function main() { }); } -gulp.task('create-golden-css', 'Creates a golden file for untransformed css', - main); +gulp.task( + 'create-golden-css', + 'Creates a golden file for untransformed css', + main +); diff --git a/build-system/tasks/create-module-compatible-es5-bundle.js b/build-system/tasks/create-module-compatible-es5-bundle.js index ce222873d327a..b3c2a593b3092 100644 --- a/build-system/tasks/create-module-compatible-es5-bundle.js +++ b/build-system/tasks/create-module-compatible-es5-bundle.js @@ -29,9 +29,16 @@ const gulp = $$.help(require('gulp')); * Changes `global?global:VARNAME}(this)` to `global?global:VARNAME}(self)` */ exports.createModuleCompatibleES5Bundle = function(src) { - return gulp.src('dist/' + src) - .pipe($$.sourcemaps.init({loadMaps: true})) - .pipe($$.regexpSourcemaps(/(window.global\?window.global:\w*)this/, '$1self', 'module-global')) - .pipe($$.sourcemaps.write('./')) - .pipe(gulp.dest('dist')); + return gulp + .src('dist/' + src) + .pipe($$.sourcemaps.init({loadMaps: true})) + .pipe( + $$.regexpSourcemaps( + /(window.global\?window.global:\w*)this/, + '$1self', + 'module-global' + ) + ) + .pipe($$.sourcemaps.write('./')) + .pipe(gulp.dest('dist')); }; diff --git a/build-system/tasks/csvify-size/index.js b/build-system/tasks/csvify-size/index.js index c7562a656f5b4..f1c94b07d5f50 100644 --- a/build-system/tasks/csvify-size/index.js +++ b/build-system/tasks/csvify-size/index.js @@ -15,7 +15,6 @@ */ 'use strict'; - const BBPromise = require('bluebird'); const childProcess = require('child_process'); const exec = BBPromise.promisify(childProcess.exec); @@ -24,7 +23,6 @@ const fs = BBPromise.promisifyAll(require('fs')); const gulp = require('gulp-help')(require('gulp')); const log = require('fancy-log'); - const prettyBytesUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; /** @@ -43,9 +41,7 @@ let FieldsDef; const filePath = 'test/size.txt'; -const tableHeaders = [ - ['"datetime"'], -]; +const tableHeaders = [['"datetime"']]; const dateTimes = []; @@ -54,8 +50,9 @@ const dateTimes = []; * @return {!Array} */ function getLog(format) { - return exec(`git log --format="${format}" ${filePath}`) - .then(logs => logs.trim().split('\n')); + return exec(`git log --format="${format}" ${filePath}`).then(logs => + logs.trim().split('\n') + ); } /** @@ -64,7 +61,10 @@ function getLog(format) { */ function parseSizeFile(file) { const lines = file.trim().split('\n'); - const headers = lines[0].trim().split('|').map(x => x.trim()); + const headers = lines[0] + .trim() + .split('|') + .map(x => x.trim()); let minPos = -1; // Find the "min" column which is the closure compiled or the "size" column // which was previously babelify compiled file. @@ -80,48 +80,58 @@ function parseSizeFile(file) { // Remove separator lines.shift(); - return lines.map(line => { - const columns = line.split('|').map(x => x.trim()); - let name = columns[columns.length - 1]; - - // Older size.txt files contained duplicate entries of the same "entity", - // for example a file had an entry for its .min and its .max file. - const shouldSkip = (name.endsWith('max.js') && - !name.endsWith('alp.max.js') && !/\s\/\s/.test(name)) - || name == 'current/integration.js' || name == 'amp.js' || - name == 'cc.js' || name.endsWith('-latest.js'); + return lines + .map(line => { + const columns = line.split('|').map(x => x.trim()); + let name = columns[columns.length - 1]; + // Older size.txt files contained duplicate entries of the same "entity", + // for example a file had an entry for its .min and its .max file. + const shouldSkip = + (name.endsWith('max.js') && + !name.endsWith('alp.max.js') && + !/\s\/\s/.test(name)) || + name == 'current/integration.js' || + name == 'amp.js' || + name == 'cc.js' || + name.endsWith('-latest.js'); - if (shouldSkip) { - return null; - } + if (shouldSkip) { + return null; + } - // Normalize names. We made mistakes at some point with duplicate entries - // or renamed entries so we make sure to identify these entities - // and put then into the same column. - if (name == 'v0.js / amp.js' || name == 'current-min/v0.js') { - name = 'v0.js'; - } else if (name == 'current-min/f.js / current/integration.js' || - name == 'current-min/f.js') { - name = 'f.js'; - } else if (name == 'alp.max.js' || name == 'alp.js / install-alp.js' || - name == 'alp.js / alp.max.js') { - name = 'alp.js'; - } else if (name == 'sw.js / sw.max.js') { - name = 'sw.js'; - } else if (name == 'sw-kill.js / sw-kill.max.js') { - name = 'sw-kill.js'; - } else if (name == 'a4a-host-v0.js / amp-inabox-host.js') { - name = 'amp4ads-host-v0.js / amp-inabox-host.js'; - } else if (name == 'a4a-v0.js / amp-inabox.js') { - name = 'amp4ads-v0.js / amp-inabox.js'; - } + // Normalize names. We made mistakes at some point with duplicate entries + // or renamed entries so we make sure to identify these entities + // and put then into the same column. + if (name == 'v0.js / amp.js' || name == 'current-min/v0.js') { + name = 'v0.js'; + } else if ( + name == 'current-min/f.js / current/integration.js' || + name == 'current-min/f.js' + ) { + name = 'f.js'; + } else if ( + name == 'alp.max.js' || + name == 'alp.js / install-alp.js' || + name == 'alp.js / alp.max.js' + ) { + name = 'alp.js'; + } else if (name == 'sw.js / sw.max.js') { + name = 'sw.js'; + } else if (name == 'sw-kill.js / sw-kill.max.js') { + name = 'sw-kill.js'; + } else if (name == 'a4a-host-v0.js / amp-inabox-host.js') { + name = 'amp4ads-host-v0.js / amp-inabox-host.js'; + } else if (name == 'a4a-v0.js / amp-inabox.js') { + name = 'amp4ads-v0.js / amp-inabox.js'; + } - return { - name: `"${name}"`, - size: `"${reversePrettyBytes(columns[minPos])}"`, - }; - }).filter(x => !!x); + return { + name: `"${name}"`, + size: `"${reversePrettyBytes(columns[minPos])}"`, + }; + }) + .filter(x => !!x); } /** @@ -150,20 +160,23 @@ function mergeTables(dateTimes, tables) { }); // Populate the headers array with unique file names for row 1 - Object.keys(obj).sort().forEach(fileName => { - // TODO(erwinm): figure out where this is occurring. - if (fileName.trim() == '""') { - return; - } - tableHeaders[0].push(fileName); - }); + Object.keys(obj) + .sort() + .forEach(fileName => { + // TODO(erwinm): figure out where this is occurring. + if (fileName.trim() == '""') { + return; + } + tableHeaders[0].push(fileName); + }); // Populate column A with all the dates we've seen and then // populate all other columns with their respective file size if any. dateTimes.forEach(dateTime => { // Seed array with empty string values - const row = - Array.apply(null, Array(tableHeaders[0].length)).map(() => '""'); + const row = Array.apply(null, Array(tableHeaders[0].length)).map( + () => '""' + ); rows.push(row); row[0] = dateTime; // Exclude the datetime column @@ -188,7 +201,8 @@ function mergeTables(dateTimes, tables) { */ function reversePrettyBytes(prettyBytes) { const triple = prettyBytes.match( - /(\d+(?:\.\d+)?)\s+(B|kB|MB|GB|TB|PB|EB|ZB|YB)/); + /(\d+(?:\.\d+)?)\s+(B|kB|MB|GB|TB|PB|EB|ZB|YB)/ + ); if (!triple) { throw new Error('No matching bytes data found'); } @@ -217,27 +231,30 @@ function serializeCheckout(logs) { return acc.then(tables => { // We checkout all the known commits for the file and accumulate // all the tables. - return exec(`git checkout ${sha} ${filePath}`).then(() => { - return fs.readFileAsync(`${filePath}`); - }).then(file => { - const quotedDateTime = `"${dateTime}"`; - dateTimes.push(quotedDateTime); - // We convert the read file string into an Table objects - const fields = parseSizeFile(file.toString()).map(field => { - field.dateTime = quotedDateTime; - return field; - }); - tables.push(fields); - return tables; - }).catch(e => { - // Ignore if pathspec error. This can happen if the file was - // deleted in git. - if (/error: pathspec/.test(e.message)) { - tables.push([]); + return exec(`git checkout ${sha} ${filePath}`) + .then(() => { + return fs.readFileAsync(`${filePath}`); + }) + .then(file => { + const quotedDateTime = `"${dateTime}"`; + dateTimes.push(quotedDateTime); + // We convert the read file string into an Table objects + const fields = parseSizeFile(file.toString()).map(field => { + field.dateTime = quotedDateTime; + return field; + }); + tables.push(fields); return tables; - } - log(colors.red(e.message)); - }); + }) + .catch(e => { + // Ignore if pathspec error. This can happen if the file was + // deleted in git. + if (/error: pathspec/.test(e.message)) { + tables.push([]); + return tables; + } + log(colors.red(e.message)); + }); }); }, Promise.resolve(tables)); return promise.then(mergeTables.bind(null, dateTimes)); @@ -245,15 +262,14 @@ function serializeCheckout(logs) { function csvify() { const shaAndDate = '%H %ai'; - return getLog(shaAndDate) - .then(logs => { - // Reverse it from oldest to newest - return serializeCheckout(logs.reverse()).then(rows => { - rows.unshift.apply(rows, tableHeaders); - const tbl = rows.map(row => row.join(',')).join('\n'); - return fs.writeFileAsync('test/size.csv', `${tbl}\n`); - }); - }); + return getLog(shaAndDate).then(logs => { + // Reverse it from oldest to newest + return serializeCheckout(logs.reverse()).then(rows => { + rows.unshift.apply(rows, tableHeaders); + const tbl = rows.map(row => row.join(',')).join('\n'); + return fs.writeFileAsync('test/size.csv', `${tbl}\n`); + }); + }); } gulp.task('csvify-size', 'create a CSV file out of the size.txt file', csvify); diff --git a/build-system/tasks/csvify-size/test.js b/build-system/tasks/csvify-size/test.js index d4cb46e8f252d..37d68cb875c26 100644 --- a/build-system/tasks/csvify-size/test.js +++ b/build-system/tasks/csvify-size/test.js @@ -15,7 +15,6 @@ */ 'use strict'; - const m = require('./'); const test = require('ava'); @@ -50,9 +49,7 @@ test('sync - parse table typedef', t => { t.plan(1); const dateTimes = ['"0"', '"1"', '"2"']; const tables = [ - [ - {name: '"v0.js"', size: '"5.5"', dateTime: '"0"'}, - ], + [{name: '"v0.js"', size: '"5.5"', dateTime: '"0"'}], [ {name: '"v0.js"', size: '"8.5"', dateTime: '"1"'}, {name: '"f.js"', size: '"70.11"', dateTime: '"1"'}, diff --git a/build-system/tasks/dep-check.js b/build-system/tasks/dep-check.js index 78705264aff84..01251dc5c5006 100644 --- a/build-system/tasks/dep-check.js +++ b/build-system/tasks/dep-check.js @@ -15,7 +15,6 @@ */ 'use strict'; - const babelify = require('babelify'); const BBPromise = require('bluebird'); const browserify = require('browserify'); @@ -31,7 +30,6 @@ const through = require('through2'); const {createCtrlcHandler, exitCtrlcHandler} = require('../ctrlcHandler'); const {isTravisBuild} = require('../travis'); - const root = process.cwd(); const absPathRegExp = new RegExp(`^${root}/`); const red = msg => log(colors.red(msg)); @@ -107,8 +105,9 @@ Rule.prototype.matchBadDeps = function(moduleName, deps) { return []; } - const isFilenameMatch = this.filesMatching_ - .some(x => minimatch(moduleName, x)); + const isFilenameMatch = this.filesMatching_.some(x => + minimatch(moduleName, x) + ); if (!isFilenameMatch) { return []; } @@ -119,7 +118,6 @@ Rule.prototype.matchBadDeps = function(moduleName, deps) { deps.forEach(dep => { this.mustNotDependOn_.forEach(badDepPattern => { if (minimatch(dep, badDepPattern)) { - // Allow extension files to depend on their own code. const dir = path.dirname(dep); if (dir.startsWith('extensions/')) { @@ -142,8 +140,10 @@ Rule.prototype.matchBadDeps = function(moduleName, deps) { if (inWhitelist) { return; } - mustNotDependErrors.push(`${moduleName} must not depend on ${dep}. ` + - `Rule: ${JSON.stringify(this.config_)}.`); + mustNotDependErrors.push( + `${moduleName} must not depend on ${dep}. ` + + `Rule: ${JSON.stringify(this.config_)}.` + ); } }); }); @@ -161,24 +161,34 @@ const rules = depCheckConfig.rules.map(config => new Rule(config)); * @return {!Promise>} */ function getSrcs() { - return fs.readdirAsync('extensions').then(dirItems => { - // Look for extension entry points - return flatten(dirItems - .map(x => `extensions/${x}`) - .filter(x => fs.statSync(x).isDirectory()) - .map(getEntryModule) - // Concat the core binary and integration binary as entry points. - .concat('src/amp.js', '3p/integration.js')); - }).then(files => { - // Write all the entry modules into a single file so they can be processed - // together. - fs.mkdirpSync('./.amp-build'); - const filename = './.amp-build/gulp-dep-check-collection.js'; - fs.writeFileSync(filename, files.map(file => { - return `import '../${file}';`; - }).join('\n')); - return [filename]; - }); + return fs + .readdirAsync('extensions') + .then(dirItems => { + // Look for extension entry points + return flatten( + dirItems + .map(x => `extensions/${x}`) + .filter(x => fs.statSync(x).isDirectory()) + .map(getEntryModule) + // Concat the core binary and integration binary as entry points. + .concat('src/amp.js', '3p/integration.js') + ); + }) + .then(files => { + // Write all the entry modules into a single file so they can be processed + // together. + fs.mkdirpSync('./.amp-build'); + const filename = './.amp-build/gulp-dep-check-collection.js'; + fs.writeFileSync( + filename, + files + .map(file => { + return `import '../${file}';`; + }) + .join('\n') + ); + return [filename]; + }); } /** @@ -196,29 +206,31 @@ function getGraph(entryModule) { // TODO(erwinm): Try and work this in with `gulp build` so that // we're not running browserify twice on travis. - const bundler = browserify(entryModule, {debug: true}) - .transform(babelify, { - compact: false, - // Transform files in node_modules since deps use ES6 export. - // https://github.com/babel/babelify#why-arent-files-in-node_modules-being-transformed - global: true, - }); + const bundler = browserify(entryModule, {debug: true}).transform(babelify, { + compact: false, + // Transform files in node_modules since deps use ES6 export. + // https://github.com/babel/babelify#why-arent-files-in-node_modules-being-transformed + global: true, + }); - bundler.pipeline.get('deps').push(through.obj(function(row, enc, next) { - module.deps.push({ - name: row.file.replace(absPathRegExp, ''), - deps: row.deps, - }); - this.push(row); - next(); - })); - bundler.bundle() - .pipe(source(entryModule)) - // Unfortunately we need to write the files out. - .pipe(gulp.dest('./.amp-build')) - .on('end', () => { - resolve(module); + bundler.pipeline.get('deps').push( + through.obj(function(row, enc, next) { + module.deps.push({ + name: row.file.replace(absPathRegExp, ''), + deps: row.deps, }); + this.push(row); + next(); + }) + ); + bundler + .bundle() + .pipe(source(entryModule)) + // Unfortunately we need to write the files out. + .pipe(gulp.dest('./.amp-build')) + .on('end', () => { + resolve(module); + }); return promise; } @@ -228,12 +240,13 @@ function getGraph(entryModule) { */ function getEntryModule(extensionFolder) { const extension = path.basename(extensionFolder); - return fs.readdirSync(extensionFolder) - .map(x => `${extensionFolder}/${x}`) - .filter(x => fs.statSync(x).isDirectory()) - .map(x => `${x}/${extension}.js`) - .filter(x => fs.existsSync(x)) - .filter(x => fs.statSync(x).isFile()); + return fs + .readdirSync(extensionFolder) + .map(x => `${extensionFolder}/${x}`) + .filter(x => fs.statSync(x).isDirectory()) + .map(x => `${x}/${extension}.js`) + .filter(x => fs.existsSync(x)) + .filter(x => fs.statSync(x).isFile()); } /** @@ -250,16 +263,15 @@ function flattenGraph(entryPoints) { // the entry points. entryPoints = entryPoints.map(entryPoint => entryPoint.deps); // Now make the graph have unique entries - return flatten(entryPoints) - .reduce((acc, cur) => { - const {name} = cur; - if (!acc[name]) { - acc[name] = Object.keys(cur.deps) - // Get rid of the absolute path for minimatch'ing - .map(x => cur.deps[x].replace(absPathRegExp, '')); - } - return acc; - }, Object.create(null)); + return flatten(entryPoints).reduce((acc, cur) => { + const {name} = cur; + if (!acc[name]) { + acc[name] = Object.keys(cur.deps) + // Get rid of the absolute path for minimatch'ing + .map(x => cur.deps[x].replace(absPathRegExp, '')); + } + return acc; + }, Object.create(null)); } /** @@ -285,16 +297,21 @@ function runRules(modules) { function depCheck() { const handlerProcess = createCtrlcHandler('dep-check'); - return getSrcs().then(entryPoints => { - // This check is for extension folders that actually dont have - // an extension entry point module yet. - entryPoints = entryPoints.filter(x => fs.existsSync(x)); - return BBPromise.all(entryPoints.map(getGraph)); - }).then(flattenGraph).then(runRules).then(errorsFound => { - if (errorsFound) { - process.exit(1); - } - }).then(() => exitCtrlcHandler(handlerProcess)); + return getSrcs() + .then(entryPoints => { + // This check is for extension folders that actually dont have + // an extension entry point module yet. + entryPoints = entryPoints.filter(x => fs.existsSync(x)); + return BBPromise.all(entryPoints.map(getGraph)); + }) + .then(flattenGraph) + .then(runRules) + .then(errorsFound => { + if (errorsFound) { + process.exit(1); + } + }) + .then(() => exitCtrlcHandler(handlerProcess)); } /** @@ -324,7 +341,8 @@ function flatten(arr) { } gulp.task( - 'dep-check', - 'Runs a dependency check on each module', - maybeUpdatePackages.concat(['css']), - depCheck); + 'dep-check', + 'Runs a dependency check on each module', + maybeUpdatePackages.concat(['css']), + depCheck +); diff --git a/build-system/tasks/dev-dashboard-tests.js b/build-system/tasks/dev-dashboard-tests.js index 86f2c98cf7be5..ab10b3020974e 100644 --- a/build-system/tasks/dev-dashboard-tests.js +++ b/build-system/tasks/dev-dashboard-tests.js @@ -21,7 +21,6 @@ const gulp = require('gulp-help')(require('gulp')); const Mocha = require('mocha'); const {isTravisBuild} = require('../travis'); - /** * Run all the dev dashboard tests */ @@ -36,7 +35,9 @@ function runDevDashboardTests() { // Create our deffered let resolver; - const deferred = new Promise(resolverIn => {resolver = resolverIn;}); + const deferred = new Promise(resolverIn => { + resolver = resolverIn; + }); // Run the tests. mocha.run(function(failures) { @@ -48,6 +49,8 @@ function runDevDashboardTests() { return deferred; } - -gulp.task('dev-dashboard-tests', 'Runs all the dev dashboard tests', - runDevDashboardTests); +gulp.task( + 'dev-dashboard-tests', + 'Runs all the dev dashboard tests', + runDevDashboardTests +); diff --git a/build-system/tasks/e2e/amp-driver.js b/build-system/tasks/e2e/amp-driver.js index 014ebe1fef8a7..3f37b9bed367d 100644 --- a/build-system/tasks/e2e/amp-driver.js +++ b/build-system/tasks/e2e/amp-driver.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** @enum {string} */ const AmpdocEnvironment = { SINGLE: 'single', @@ -35,8 +34,9 @@ const EnvironmentBehaviorMap = { [AmpdocEnvironment.VIEWER_DEMO]: { ready(controller) { - return controller.findElement('#AMP_DOC_dynamic[data-loaded]') - .then(frame => controller.switchToFrame(frame)); + return controller + .findElement('#AMP_DOC_dynamic[data-loaded]') + .then(frame => controller.switchToFrame(frame)); }, url(url) { @@ -50,7 +50,8 @@ const EnvironmentBehaviorMap = { // TODO(cvializ): this is a HACK // There should be a better way to detect that the shadowdoc is ready. const shadowHost = await controller.findElement( - '.amp-doc-host[style="visibility: visible;"]'); + '.amp-doc-host[style="visibility: visible;"]' + ); await controller.switchToShadow(shadowHost); }, @@ -80,11 +81,15 @@ class AmpDriver { * @return {!Promise} */ async toggleExperiment(name, toggle) { - await this.controller_.evaluate((name, toggle) => { - (window.AMP = window.AMP || []).push(AMP => { - AMP.toggleExperiment(name, toggle); - }); - }, name, toggle); + await this.controller_.evaluate( + (name, toggle) => { + (window.AMP = window.AMP || []).push(AMP => { + AMP.toggleExperiment(name, toggle); + }); + }, + name, + toggle + ); } /** diff --git a/build-system/tasks/e2e/describes-e2e.js b/build-system/tasks/e2e/describes-e2e.js index 2c3249a889f97..d6675408d67aa 100644 --- a/build-system/tasks/e2e/describes-e2e.js +++ b/build-system/tasks/e2e/describes-e2e.js @@ -18,12 +18,14 @@ require('chromedriver'); // eslint-disable-line no-unused-vars const puppeteer = require('puppeteer'); +const { + SeleniumWebDriverController, +} = require('./selenium-webdriver-controller'); const {AmpDriver, AmpdocEnvironment} = require('./amp-driver'); const {Builder, Capabilities} = require('selenium-webdriver'); const {clearLastExpectError, getLastExpectError} = require('./expect'); const {installRepl, uninstallRepl} = require('./repl'); const {PuppeteerController} = require('./puppeteer-controller'); -const {SeleniumWebDriverController} = require('./selenium-webdriver-controller'); /** Should have something in the name, otherwise nothing is shown. */ const SUB = ' '; @@ -147,20 +149,24 @@ let TestSpec; /** * An end2end test using selenium web driver on a regular amp page */ -const endtoend = describeEnv(spec => [ - new AmpPageFixture(spec), -]); +const endtoend = describeEnv(spec => [new AmpPageFixture(spec)]); /** * Maps an environment enum value to a `describes.repeated` variant object. */ const EnvironmentVariantMap = { - [AmpdocEnvironment.SINGLE]: - {name: 'Standalone environment', value: {environment: 'single'}}, - [AmpdocEnvironment.VIEWER_DEMO]: - {name: 'Viewer environment', value: {environment: 'viewer-demo'}}, - [AmpdocEnvironment.SHADOW_DEMO]: - {name: 'Shadow environment', value: {environment: 'shadow-demo'}}, + [AmpdocEnvironment.SINGLE]: { + name: 'Standalone environment', + value: {environment: 'single'}, + }, + [AmpdocEnvironment.VIEWER_DEMO]: { + name: 'Viewer environment', + value: {environment: 'viewer-demo'}, + }, + [AmpdocEnvironment.SHADOW_DEMO]: { + name: 'Shadow environment', + value: {environment: 'shadow-demo'}, + }, }; const defaultEnvironments = [ @@ -209,7 +215,7 @@ function describeEnv(factory) { const env = Object.create(variant); let asyncErrorTimerId; this.timeout(TIMEOUT); - beforeEach(async() => { + beforeEach(async () => { // Set up all fixtures. for (const fixture of fixtures) { await fixture.setup(env); @@ -221,13 +227,16 @@ function describeEnv(factory) { clearLastExpectError(); clearTimeout(asyncErrorTimerId); // Tear down all fixtures. - fixtures.slice(0).reverse().forEach(fixture => { - // TODO(cvializ): handle errors better - // if (this.currentTest.state == 'failed') { - // fixture.handleError(); - // } - fixture.teardown(env); - }); + fixtures + .slice(0) + .reverse() + .forEach(fixture => { + // TODO(cvializ): handle errors better + // if (this.currentTest.state == 'failed') { + // fixture.handleError(); + // } + fixture.teardown(env); + }); // Delete all other keys. for (const key in env) { @@ -269,7 +278,7 @@ function describeEnv(factory) { * @param {function(!Object)} fn */ mainFunc.only = function(name, spec, fn) { - return templateFunc(name, spec, fn, describe./*OK*/only); + return templateFunc(name, spec, fn, describe./*OK*/ only); }; mainFunc.skip = function(name, variants, fn) { @@ -279,10 +288,8 @@ function describeEnv(factory) { return mainFunc; } - /** @interface */ class FixtureInterface { - /** @return {boolean} */ isOn() {} @@ -300,7 +307,6 @@ class FixtureInterface { /** @implements {FixtureInterface} */ class AmpPageFixture { - /** @param {!TestSpec} spec */ constructor(spec) { /** @const */ @@ -352,10 +358,7 @@ class AmpPageFixture { * Get the controller object for the configured engine. * @param {!DescribesConfigDef} describesConfig */ -async function getController({ - engine = 'selenium', - headless = false, -}) { +async function getController({engine = 'selenium', headless = false}) { if (engine == 'puppeteer') { const browser = await createPuppeteer({headless}); return new PuppeteerController(browser); diff --git a/build-system/tasks/e2e/driver/query-xpath.js b/build-system/tasks/e2e/driver/query-xpath.js index 9033cd1c46037..670ee5d0e4754 100644 --- a/build-system/tasks/e2e/driver/query-xpath.js +++ b/build-system/tasks/e2e/driver/query-xpath.js @@ -57,16 +57,21 @@ function queryXpath(xpathString, context) { // Add an ID to every element in the tree so we can reference them later. // This allows us to correlate nodes from the cloned tree to the // original tree. - for (const {item, index} of - createIndexedInterator(createElementIterator(context))) { + for (const {item, index} of createIndexedInterator( + createElementIterator(context) + )) { setData(item, index); } const fakeDocument = document.implementation.createDocument( - 'http://www.w3.org/1999/xhtml', 'html', null); + 'http://www.w3.org/1999/xhtml', + 'html', + null + ); try { fakeDocument.documentElement.appendChild( - context.cloneNode(/* deep */ true)); + context.cloneNode(/* deep */ true) + ); } catch (e) { // Appending the AMP `CustomElement`s to the new document throws errors // because the implementations expect the AmpDoc to be present and it @@ -77,11 +82,12 @@ function queryXpath(xpathString, context) { // Map the xpath results from the fake tree to the corresponding nodes in // the test document tree. const xpathIterator = createXpathIterator( - evaluate(xpathString, fakeDocument, null, XPathResult.ANY_TYPE)); + evaluate(xpathString, fakeDocument, null, XPathResult.ANY_TYPE) + ); const elements = [...xpathIterator].map(node => { const testId = getData(node); const selector = `[${TEST_ID_ATTRIBUTE}="${testId}"]`; - return context./*OK*/querySelector(selector); + return context./*OK*/ querySelector(selector); }); // Restore the DOM to the original state without the ID in the dataset. @@ -126,8 +132,9 @@ function removeData(node) { function createElementIterator(element) { const document = element.ownerDocument; return createCommonIterator( - document.createNodeIterator(element, NodeFilter.SHOW_ELEMENT), - 'nextNode'); + document.createNodeIterator(element, NodeFilter.SHOW_ELEMENT), + 'nextNode' + ); } /** @@ -148,7 +155,7 @@ function createXpathIterator(xpathResult) { */ function* createCommonIterator(iterator, nextProperty) { let item; - while (Boolean(item = iterator[nextProperty]())) { + while (Boolean((item = iterator[nextProperty]()))) { yield item; } } @@ -168,5 +175,4 @@ function* createIndexedInterator(iter) { } } - window.queryXpath = queryXpath; diff --git a/build-system/tasks/e2e/expect.js b/build-system/tasks/e2e/expect.js index 445697a8ee3a0..382396b355141 100644 --- a/build-system/tasks/e2e/expect.js +++ b/build-system/tasks/e2e/expect.js @@ -69,13 +69,25 @@ function installIncludeWrapper(chai, utils) { const overwrite = overwriteAlwaysUseSuper(utils); Assertion.overwriteChainableMethod( - 'include', overwrite, inheritChainingBehavior); + 'include', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'includes', overwrite, inheritChainingBehavior); + 'includes', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'contain', overwrite, inheritChainingBehavior); + 'contain', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'contains', overwrite, inheritChainingBehavior); + 'contains', + overwrite, + inheritChainingBehavior + ); } function installMatchWrapper(chai, utils) { @@ -91,9 +103,15 @@ function installLengthWrapper(chai, utils) { const overwrite = overwriteAlwaysUseSuper(utils); Assertion.overwriteChainableMethod( - 'length', overwrite, inheritChainingBehavior); + 'length', + overwrite, + inheritChainingBehavior + ); Assertion.overwriteChainableMethod( - 'lengthOf', overwrite, inheritChainingBehavior); + 'lengthOf', + overwrite, + inheritChainingBehavior + ); } function installAboveWrapper(chai, utils) { @@ -146,7 +164,6 @@ function installIsNullWrapper(chai, utils) { Assertion.overwriteProperty('null', overwrite); } - function overwriteAlwaysUseSuper(utils) { const {flag} = utils; diff --git a/build-system/tasks/e2e/functional-test-controller.js b/build-system/tasks/e2e/functional-test-controller.js index ae5bf976d890f..8f0b0adb84860 100644 --- a/build-system/tasks/e2e/functional-test-controller.js +++ b/build-system/tasks/e2e/functional-test-controller.js @@ -40,7 +40,7 @@ class ElementHandle { * @package */ getElement() { - return this.element_; + return this.element_; } } @@ -60,9 +60,10 @@ class ControllerPromise { * @param {function(TYPE,function(TYPE): ?TYPE): !Promise=} opt_waitForValue */ constructor(executorOrPromise, opt_waitForValue) { - this.promise_ = typeof executorOrPromise == 'function' ? - new Promise(executorOrPromise) : - executorOrPromise; + this.promise_ = + typeof executorOrPromise == 'function' + ? new Promise(executorOrPromise) + : executorOrPromise; /** * Returns a Promise that resolves when the given expected value fulfills @@ -76,15 +77,17 @@ class ControllerPromise { /** @override */ catch(onRejected) { return new ControllerPromise( - this.promise_.catch(onRejected), - this.waitForValue); + this.promise_.catch(onRejected), + this.waitForValue + ); } - /** @override */ + /** @override */ finally(onFinally) { return new ControllerPromise( - this.promise_.finally(onFinally), - this.waitForValue); + this.promise_.finally(onFinally), + this.waitForValue + ); } /** @override */ @@ -96,13 +99,15 @@ class ControllerPromise { wrappedWait = (condition, opt_mutate) => { opt_mutate = opt_mutate || (x => x); return this.waitForValue(condition, value => - opt_mutate(opt_onFulfilled(value))); + opt_mutate(opt_onFulfilled(value)) + ); }; } return new ControllerPromise( - this.promise_.then(opt_onFulfilled, opt_onRejected), - wrappedWait); + this.promise_.then(opt_onFulfilled, opt_onRejected), + wrappedWait + ); } } @@ -429,13 +434,12 @@ class FunctionalTestController { async dispose() {} } - /** * @typedef {{ * width: number, * height: number * }} WindowRectDef -*/ + */ let WindowRectDef; /** @@ -445,7 +449,7 @@ let WindowRectDef; * width: number, * height: number * }} -*/ + */ let DOMRectDef; /** @enum {string} */ diff --git a/build-system/tasks/e2e/index.js b/build-system/tasks/e2e/index.js index 27f74f9d9f7db..744750cce399f 100644 --- a/build-system/tasks/e2e/index.js +++ b/build-system/tasks/e2e/index.js @@ -45,7 +45,8 @@ function buildRuntime_() { function launchWebServer_() { webServerProcess_ = execScriptAsync( - `gulp serve --host ${HOST} --port ${PORT}`); + `gulp serve --host ${HOST} --port ${PORT}` + ); let resolver; const deferred = new Promise(resolverIn => { @@ -117,8 +118,7 @@ async function e2e() { delete require.cache[file]; mocha.addFile(file); }); - } - else { + } else { config.e2eTestPaths.forEach(path => { glob.sync(path).forEach(file => { delete require.cache[file]; @@ -139,8 +139,7 @@ async function e2e() { process.exit(); resolver(); }); - } - else { + } else { const filesToWatch = argv.files ? [argv.files] : [config.e2eTestPaths]; const watcher = watch(filesToWatch); log('Watching', cyan(filesToWatch), 'for changes...'); diff --git a/build-system/tasks/e2e/puppeteer-controller.js b/build-system/tasks/e2e/puppeteer-controller.js index d3fe1043720d1..9430c68f63c7e 100644 --- a/build-system/tasks/e2e/puppeteer-controller.js +++ b/build-system/tasks/e2e/puppeteer-controller.js @@ -53,7 +53,10 @@ async function waitFor(page, valueFn, args, condition, opt_mutate) { while (!condition(value)) { const handle = await page.waitForFunction( - valueFn, {timeout: DEFAULT_WAIT_TIMEOUT}, ...args); + valueFn, + {timeout: DEFAULT_WAIT_TIMEOUT}, + ...args + ); value = await handle.jsonValue(); if (opt_mutate) { value = await opt_mutate(value); @@ -129,7 +132,7 @@ class PuppeteerController { * @template T */ getWaitFn_(valueFn, ...args) { - return async(condition, opt_mutate) => { + return async (condition, opt_mutate) => { const frame = await this.getCurrentFrame_(); return waitFor(frame, valueFn, args, condition, opt_mutate); }; @@ -155,9 +158,14 @@ class PuppeteerController { async findElement(selector) { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const jsHandle = await frame.waitForFunction((root, selector) => { - return root./*OK*/querySelector(selector); - }, {timeout: DEFAULT_WAIT_TIMEOUT}, root, selector); + const jsHandle = await frame.waitForFunction( + (root, selector) => { + return root./*OK*/ querySelector(selector); + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + root, + selector + ); const elementHandle = jsHandle.asElement(); return new ElementHandle(elementHandle); } @@ -170,10 +178,15 @@ class PuppeteerController { async findElements(selector) { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const nodeListHandle = await frame.waitForFunction((root, selector) => { - const nodeList = root./*OK*/querySelectorAll(selector); - return nodeList.length > 0 ? nodeList : null; - }, {timeout: DEFAULT_WAIT_TIMEOUT}, root, selector); + const nodeListHandle = await frame.waitForFunction( + (root, selector) => { + const nodeList = root./*OK*/ querySelectorAll(selector); + return nodeList.length > 0 ? nodeList : null; + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + root, + selector + ); const lengthHandle = await nodeListHandle.getProperty('length'); const length = await lengthHandle.jsonValue(); @@ -197,10 +210,15 @@ class PuppeteerController { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const jsHandle = await frame.waitForFunction((xpath, root) => { - const results = window.queryXpath(xpath, root); - return results && results[0]; - }, {timeout: DEFAULT_WAIT_TIMEOUT}, xpath, root); + const jsHandle = await frame.waitForFunction( + (xpath, root) => { + const results = window.queryXpath(xpath, root); + return results && results[0]; + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + xpath, + root + ); return new ElementHandle(jsHandle.asElement()); } @@ -215,9 +233,14 @@ class PuppeteerController { const frame = await this.getCurrentFrame_(); const root = await this.getRoot_(); - const arrayHandle = await frame.waitForFunction((xpath, root) => { - return window.queryXpath(xpath, root); - }, {timeout: DEFAULT_WAIT_TIMEOUT}, xpath, root); + const arrayHandle = await frame.waitForFunction( + (xpath, root) => { + return window.queryXpath(xpath, root); + }, + {timeout: DEFAULT_WAIT_TIMEOUT}, + xpath, + root + ); const lengthHandle = await arrayHandle.getProperty('length'); const length = await lengthHandle.jsonValue(); @@ -277,10 +300,9 @@ class PuppeteerController { */ async type(handle, keys) { const frame = await this.getCurrentFrame_(); - const targetElement = handle ? - handle.getElement() : - await frame.$(':focus'); - + const targetElement = handle + ? handle.getElement() + : await frame.$(':focus'); const key = KeysMapping[keys.toUpperCase()]; if (key) { @@ -300,8 +322,9 @@ class PuppeteerController { const element = handle.getElement(); const getter = element => element.textContent; return new ControllerPromise( - this.evaluate(getter, element), - this.getWaitFn_(getter, element)); + this.evaluate(getter, element), + this.getWaitFn_(getter, element) + ); } /** @@ -313,11 +336,12 @@ class PuppeteerController { getElementCssValue(handle, styleProperty) { const element = handle.getElement(); const getter = (element, styleProperty) => { - return window/*OK*/['getComputedStyle'](element)[styleProperty]; + return window /*OK*/['getComputedStyle'](element)[styleProperty]; }; return new ControllerPromise( - this.evaluate(getter, element, styleProperty), - this.getWaitFn_(getter, element, styleProperty)); + this.evaluate(getter, element, styleProperty), + this.getWaitFn_(getter, element, styleProperty) + ); } /** @@ -330,8 +354,9 @@ class PuppeteerController { const element = handle.getElement(); const getter = (element, attribute) => element.getAttribute(attribute); return new ControllerPromise( - this.evaluate(getter, element, attribute), - this.getWaitFn_(getter, element, attribute)); + this.evaluate(getter, element, attribute), + this.getWaitFn_(getter, element, attribute) + ); } /** @@ -346,8 +371,9 @@ class PuppeteerController { return element[property]; }; return new ControllerPromise( - this.evaluate(getter, element, property), - this.getWaitFn_(getter, element, property)); + this.evaluate(getter, element, property), + this.getWaitFn_(getter, element, property) + ); } /** @@ -361,7 +387,7 @@ class PuppeteerController { // Extracting the values seems to perform better than returning // the raw ClientRect from the element, in terms of flakiness. // The raw ClientRect also has hundredths of a pixel. We round to int. - const {x, y, width, height} = element./*OK*/getBoundingClientRect(); + const {x, y, width, height} = element./*OK*/ getBoundingClientRect(); return { x: Math.round(x), y: Math.round(y), @@ -370,8 +396,9 @@ class PuppeteerController { }; }; return new ControllerPromise( - this.evaluate(getter, element), - this.getWaitFn_(getter, element)); + this.evaluate(getter, element), + this.getWaitFn_(getter, element) + ); } /** @@ -380,14 +407,10 @@ class PuppeteerController { * @override */ async setWindowRect(rect) { - const { - width, - height, - } = rect; + const {width, height} = rect; await this.resizeWindow_(width, height); } - /** * Resize the window and the viewport. The `page.setViewport` method only * changes the size of the rendered area of the browser, not the window size. @@ -426,7 +449,7 @@ class PuppeteerController { }); const chromePaddingWidth = outerWidth > 0 ? outerWidth - clientWidth : 0; const chromePaddingHeight = - outerHeight > 0 ? outerHeight - clientHeight : 0; + outerHeight > 0 ? outerHeight - clientHeight : 0; await page.setViewport({ width: width + chromePaddingWidth, @@ -435,27 +458,30 @@ class PuppeteerController { }); // Any tab. - const {targetInfos: [{targetId}]} = await browser._connection.send( - 'Target.getTargets'); + const { + targetInfos: [{targetId}], + } = await browser._connection.send('Target.getTargets'); // Tab window. try { const {windowId} = await browser._connection.send( - 'Browser.getWindowForTarget', {targetId}); + 'Browser.getWindowForTarget', + {targetId} + ); // Resize. - await browser._connection.send( - 'Browser.setWindowBounds', { - bounds: { - width: width + chromePaddingWidth, - height: height + chromePaddingHeight, - }, - windowId, - }); + await browser._connection.send('Browser.setWindowBounds', { + bounds: { + width: width + chromePaddingWidth, + height: height + chromePaddingHeight, + }, + windowId, + }); } catch (e) { // Catch if we're in headless. - if (!e.toString().includes( - 'Protocol error (Browser.getWindowForTarget)')) { + if ( + !e.toString().includes('Protocol error (Browser.getWindowForTarget)') + ) { throw e; } } @@ -466,8 +492,9 @@ class PuppeteerController { */ getTitle() { const title = this.getCurrentFrame_().then(frame => frame.title()); - return new ControllerPromise( - title, () => this.getCurrentFrame_().then(frame => frame.title())); + return new ControllerPromise(title, () => + this.getCurrentFrame_().then(frame => frame.title()) + ); } /** @@ -498,9 +525,13 @@ class PuppeteerController { */ async scroll(handle, opt_scrollToOptions) { const element = handle.getElement(); - await this.evaluate((element, opt_scrollToOptions) => { - element./*OK*/scrollTo(opt_scrollToOptions); - }, element, opt_scrollToOptions); + await this.evaluate( + (element, opt_scrollToOptions) => { + element./*OK*/ scrollTo(opt_scrollToOptions); + }, + element, + opt_scrollToOptions + ); } /** @@ -511,9 +542,13 @@ class PuppeteerController { */ async scrollBy(handle, opt_scrollToOptions) { const element = handle.getElement(); - await this.evaluate((element, opt_scrollToOptions) => { - element./*OK*/scrollBy(opt_scrollToOptions); - }, element, opt_scrollToOptions); + await this.evaluate( + (element, opt_scrollToOptions) => { + element./*OK*/ scrollBy(opt_scrollToOptions); + }, + element, + opt_scrollToOptions + ); } /** diff --git a/build-system/tasks/e2e/repl.js b/build-system/tasks/e2e/repl.js index 789b7003175e4..9120f2c563eb4 100644 --- a/build-system/tasks/e2e/repl.js +++ b/build-system/tasks/e2e/repl.js @@ -59,7 +59,7 @@ function installRepl(global, env) { }); } - console./*OK*/log(READY_MESSAGE); + console./*OK*/ log(READY_MESSAGE); return replPromise; }; @@ -76,7 +76,7 @@ function installRepl(global, env) { delete global.repl.env; delete global.repl.continue; - console./*OK*/log(CONTINUE_MESSAGE); + console./*OK*/ log(CONTINUE_MESSAGE); } } diff --git a/build-system/tasks/e2e/selenium-webdriver-controller.js b/build-system/tasks/e2e/selenium-webdriver-controller.js index b981d046520a9..e024213315274 100644 --- a/build-system/tasks/e2e/selenium-webdriver-controller.js +++ b/build-system/tasks/e2e/selenium-webdriver-controller.js @@ -15,16 +15,11 @@ */ const fs = require('fs'); -const { - By, - Condition, - Key, - error, -} = require('selenium-webdriver'); const { ControllerPromise, ElementHandle, } = require('./functional-test-controller'); +const {By, Condition, Key, error} = require('selenium-webdriver'); const {expect} = require('chai'); const {NoSuchElementError} = error; @@ -40,7 +35,7 @@ const ELEMENT_WAIT_TIMEOUT = 5000; */ function expectCondition(valueFn, condition, opt_mutate) { opt_mutate = opt_mutate || (x => x); - return new Condition('value matches condition', async() => { + return new Condition('value matches condition', async () => { const value = await valueFn(); const mutatedValue = await opt_mutate(value); return condition(mutatedValue); @@ -63,8 +58,9 @@ function waitFor(driver, valueFn, condition, opt_mutate) { // (like "") do not cause driver.wait to continue waiting. return condition(value) ? {value} : null; }; - return driver.wait(expectCondition(valueFn, conditionValue, opt_mutate)) - .then(result => result.value); // Unbox the value. + return driver + .wait(expectCondition(valueFn, conditionValue, opt_mutate)) + .then(result => result.value); // Unbox the value. } /** @implements {FunctionalTestController} */ @@ -112,7 +108,7 @@ class SeleniumWebDriverController { const bySelector = By.css(selector); const label = 'for element to be located ' + selector; - const condition = new Condition(label, async() => { + const condition = new Condition(label, async () => { try { const root = await this.getRoot_(); return await root.findElement(bySelector); @@ -143,7 +139,7 @@ class SeleniumWebDriverController { const bySelector = By.css(selector); const label = 'for at least one element to be located ' + selector; - const condition = new Condition(label, async() => { + const condition = new Condition(label, async () => { try { const root = await this.getRoot_(); const elements = await root.findElements(bySelector); @@ -168,13 +164,20 @@ class SeleniumWebDriverController { await this.maybeInstallXpath_(); const label = 'for element to be located ' + xpath; - const webElement = await this.driver.wait(new Condition(label, async() => { - const root = await this.getRoot_(); - const results = await this.evaluate((xpath, root) => { - return window.queryXpath(xpath, root); - }, xpath, root); - return (results && results[0]); - }), ELEMENT_WAIT_TIMEOUT); + const webElement = await this.driver.wait( + new Condition(label, async () => { + const root = await this.getRoot_(); + const results = await this.evaluate( + (xpath, root) => { + return window.queryXpath(xpath, root); + }, + xpath, + root + ); + return results && results[0]; + }), + ELEMENT_WAIT_TIMEOUT + ); return new ElementHandle(webElement, this); } @@ -186,13 +189,20 @@ class SeleniumWebDriverController { async findElementsXPath(xpath) { await this.maybeInstallXpath_(); const label = 'for at least one element to be located ' + xpath; - const webElements = await this.driver.wait(new Condition(label, async() => { - const root = await this.getRoot_(); - const results = await this.evaluate((xpath, root) => { - return window.queryXpath(xpath, root); - }, xpath, root); - return results; - }), ELEMENT_WAIT_TIMEOUT); + const webElements = await this.driver.wait( + new Condition(label, async () => { + const root = await this.getRoot_(); + const results = await this.evaluate( + (xpath, root) => { + return window.queryXpath(xpath, root); + }, + xpath, + root + ); + return results; + }), + ELEMENT_WAIT_TIMEOUT + ); return webElements.map(webElement => new ElementHandle(webElement, this)); } @@ -220,8 +230,7 @@ class SeleniumWebDriverController { async getActiveElement() { const root = await this.getRoot_(); const getter = root => root.parentNode.activeElement; - const activeElement = - await this.driver.executeScript(getter, root); + const activeElement = await this.driver.executeScript(getter, root); return new ElementHandle(activeElement); } @@ -232,8 +241,7 @@ class SeleniumWebDriverController { async getDocumentElement() { const root = await this.getRoot_(); const getter = root => root.ownerDocument.documentElement; - const documentElement = - await this.driver.executeScript(getter, root); + const documentElement = await this.driver.executeScript(getter, root); return new ElementHandle(documentElement); } @@ -253,10 +261,9 @@ class SeleniumWebDriverController { * @override */ async type(handle, keys) { - const targetElement = handle ? - handle.getElement() : - await this.driver.switchTo().activeElement(); - + const targetElement = handle + ? handle.getElement() + : await this.driver.switchTo().activeElement(); const key = Key[keys.toUpperCase()]; if (key) { @@ -274,8 +281,9 @@ class SeleniumWebDriverController { getElementText(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.getText(), - this.getWaitFn_(() => webElement.getText())); + webElement.getText(), + this.getWaitFn_(() => webElement.getText()) + ); } /** @@ -298,8 +306,9 @@ class SeleniumWebDriverController { const webElement = handle.getElement(); const getter = (element, attribute) => element.getAttribute(attribute); return new ControllerPromise( - this.evaluate(getter, webElement, attribute), - this.getWaitFn_(() => this.evaluate(getter, webElement, attribute))); + this.evaluate(getter, webElement, attribute), + this.getWaitFn_(() => this.evaluate(getter, webElement, attribute)) + ); } /** @@ -313,9 +322,11 @@ class SeleniumWebDriverController { const getProperty = (element, property) => element[property]; return new ControllerPromise( - this.driver.executeScript(getProperty, webElement, property), - this.getWaitFn_(() => this.driver.executeScript( - getProperty, webElement, property))); + this.driver.executeScript(getProperty, webElement, property), + this.getWaitFn_(() => + this.driver.executeScript(getProperty, webElement, property) + ) + ); } /** @@ -326,8 +337,9 @@ class SeleniumWebDriverController { getElementRect(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.getRect(), - this.getWaitFn_(() => webElement.getRect())); + webElement.getRect(), + this.getWaitFn_(() => webElement.getRect()) + ); } /** @@ -339,8 +351,9 @@ class SeleniumWebDriverController { getElementCssValue(handle, styleProperty) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.getCssValue(styleProperty), - this.getWaitFn_(() => webElement.getCssValue(styleProperty))); + webElement.getCssValue(styleProperty), + this.getWaitFn_(() => webElement.getCssValue(styleProperty)) + ); } /** @@ -351,8 +364,9 @@ class SeleniumWebDriverController { isElementEnabled(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.isEnabled(), - this.getWaitFn_(() => webElement.isEnabled())); + webElement.isEnabled(), + this.getWaitFn_(() => webElement.isEnabled()) + ); } /** @@ -363,8 +377,9 @@ class SeleniumWebDriverController { isElementSelected(handle) { const webElement = handle.getElement(); return new ControllerPromise( - webElement.isSelected(), - this.getWaitFn_(() => webElement.isSelected())); + webElement.isSelected(), + this.getWaitFn_(() => webElement.isSelected()) + ); } /** @@ -374,18 +389,17 @@ class SeleniumWebDriverController { * @override */ async setWindowRect(rect) { - const { - width, - height, - } = rect; - - - await this.driver.manage().window().setRect({ - x: 0, - y: 0, - width, - height, - }); + const {width, height} = rect; + + await this.driver + .manage() + .window() + .setRect({ + x: 0, + y: 0, + width, + height, + }); // Check to make sure we resized the content to the correct size. const htmlElement = this.driver.findElement(By.tagName('html')); @@ -408,10 +422,13 @@ class SeleniumWebDriverController { const horizBorder = width - clientWidth; const vertBorder = height - clientHeight; - await this.driver.manage().window().setRect({ - width: width + horizBorder, - height: height + vertBorder, - }); + await this.driver + .manage() + .window() + .setRect({ + width: width + horizBorder, + height: height + vertBorder, + }); // Verify the size. The browser may refuse to resize smaller than some // size when not running headless. It is better to fail here rather than @@ -427,11 +444,13 @@ class SeleniumWebDriverController { // to fail immediately,.Figure out why, we want the test to fail here // instead of continuing. expect(resultWidth).to.equal( - width, - 'Failed to resize the window to the requested width.'); + width, + 'Failed to resize the window to the requested width.' + ); expect(resultHeight).to.equal( - height, - 'Failed to resize the window to the requested height.'); + height, + 'Failed to resize the window to the requested height.' + ); } /** @@ -443,8 +462,9 @@ class SeleniumWebDriverController { const getTitle = () => document.title; return new ControllerPromise( - this.driver.executeScript(getTitle), - this.getWaitFn_(() => this.driver.executeScript(getTitle))); + this.driver.executeScript(getTitle), + this.getWaitFn_(() => this.driver.executeScript(getTitle)) + ); } /** @@ -466,11 +486,14 @@ class SeleniumWebDriverController { async scroll(handle, opt_scrollToOptions) { const webElement = handle.getElement(); const scrollTo = (element, opt_scrollToOptions) => { - element./*OK*/scrollTo(opt_scrollToOptions); + element./*OK*/ scrollTo(opt_scrollToOptions); }; return await this.driver.executeScript( - scrollTo, webElement, opt_scrollToOptions); + scrollTo, + webElement, + opt_scrollToOptions + ); } /** @@ -482,11 +505,14 @@ class SeleniumWebDriverController { async scrollBy(handle, opt_scrollToOptions) { const webElement = handle.getElement(); const scrollBy = (element, opt_scrollToOptions) => { - element./*OK*/scrollBy(opt_scrollToOptions); + element./*OK*/ scrollBy(opt_scrollToOptions); }; return await this.driver.executeScript( - scrollBy, webElement, opt_scrollToOptions); + scrollBy, + webElement, + opt_scrollToOptions + ); } /** @@ -549,7 +575,9 @@ class SeleniumWebDriverController { async switchToShadow(handle) { const shadowHost = handle.getElement(); const shadowRootBody = await this.evaluate( - shadowHost => shadowHost.shadowRoot.body, shadowHost); + shadowHost => shadowHost.shadowRoot.body, + shadowHost + ); this.shadowRoot_ = shadowRootBody; } diff --git a/build-system/tasks/extension-generator/index.js b/build-system/tasks/extension-generator/index.js index f98ce9501a4ca..bae0fda83f6b3 100644 --- a/build-system/tasks/extension-generator/index.js +++ b/build-system/tasks/extension-generator/index.js @@ -26,8 +26,12 @@ const year = new Date().getFullYear(); /*eslint "max-len": 0*/ function pascalCase(str) { - return str[0].toUpperCase() + str.slice(1).replace(/-([a-z])/g, - function(g) { return g[1].toUpperCase(); }); + return ( + str[0].toUpperCase() + + str.slice(1).replace(/-([a-z])/g, function(g) { + return g[1].toUpperCase(); + }) + ); } function getValidatorFile(name) { @@ -275,33 +279,45 @@ function getExamplesFile(name) { function makeExtension() { if (!argv.name) { - log(colors.red( - 'Error! Please pass in the "--name" flag with a value')); + log(colors.red('Error! Please pass in the "--name" flag with a value')); } const {name} = argv; const examplesFile = getExamplesFile(name); fs.mkdirpSync(`extensions/${name}/0.1/test`); - fs.writeFileSync(`extensions/${name}/${name}.md`, - getMarkdownExtensionFile(name)); - fs.writeFileSync(`extensions/${name}/validator-${name}.protoascii`, - getValidatorFile(name)); - fs.writeFileSync(`extensions/${name}/0.1/${name}.js`, - getJsExtensionFile(name)); - fs.writeFileSync(`extensions/${name}/0.1/test/test-${name}.js`, - getJsTestExtensionFile(name)); - fs.writeFileSync(`extensions/${name}/0.1/test/validator-${name}.html`, - examplesFile); - - const examplesFileValidatorOut = examplesFile.trim().split('\n') - .map(line => `| ${line}`) - .join('\n'); - - fs.writeFileSync(`extensions/${name}/0.1/test/validator-${name}.out`, - ['PASS', examplesFileValidatorOut].join('\n')); - - fs.writeFileSync(`examples/${name}.amp.html`, - examplesFile); + fs.writeFileSync( + `extensions/${name}/${name}.md`, + getMarkdownExtensionFile(name) + ); + fs.writeFileSync( + `extensions/${name}/validator-${name}.protoascii`, + getValidatorFile(name) + ); + fs.writeFileSync( + `extensions/${name}/0.1/${name}.js`, + getJsExtensionFile(name) + ); + fs.writeFileSync( + `extensions/${name}/0.1/test/test-${name}.js`, + getJsTestExtensionFile(name) + ); + fs.writeFileSync( + `extensions/${name}/0.1/test/validator-${name}.html`, + examplesFile + ); + + const examplesFileValidatorOut = examplesFile + .trim() + .split('\n') + .map(line => `| ${line}`) + .join('\n'); + + fs.writeFileSync( + `extensions/${name}/0.1/test/validator-${name}.out`, + ['PASS', examplesFileValidatorOut].join('\n') + ); + + fs.writeFileSync(`examples/${name}.amp.html`, examplesFile); } gulp.task('make-extension', 'Create an extension skeleton', makeExtension, { diff --git a/build-system/tasks/firebase.js b/build-system/tasks/firebase.js index 405b47108de1f..733312fd38e57 100644 --- a/build-system/tasks/firebase.js +++ b/build-system/tasks/firebase.js @@ -27,9 +27,9 @@ async function walk(dest) { for (let i = 0; i < files.length; i++) { const file = `${dest}/${files[i]}`; - fs.statSync(file).isDirectory() ? - Array.prototype.push.apply(filelist, await walk(file)) : - filelist.push(file); + fs.statSync(file).isDirectory() + ? Array.prototype.push.apply(filelist, await walk(file)) + : filelist.push(file); } return filelist; @@ -39,8 +39,9 @@ async function copyAndReplaceUrls(src, dest) { await fs.copy(src, dest, {overwrite: true}); // Recursively gets all the files within the directory and its children. const files = await walk(dest); - const promises = files.filter(fileName => path.extname(fileName) == '.html') - .map(file => replaceUrls(file)); + const promises = files + .filter(fileName => path.extname(fileName) == '.html') + .map(file => replaceUrls(file)); await Promise.all(promises); } @@ -48,8 +49,9 @@ async function modifyThirdPartyUrl() { const filePath = 'firebase/dist/amp.js'; const data = await fs.readFile('firebase/dist/amp.js', 'utf8'); const result = data.replace( - 'self.AMP_CONFIG={', - 'self.AMP_CONFIG={"thirdPartyUrl":location.origin,'); + 'self.AMP_CONFIG={', + 'self.AMP_CONFIG={"thirdPartyUrl":location.origin,' + ); await fs.writeFile(filePath, result, 'utf8'); } @@ -58,8 +60,9 @@ async function generateFirebaseFolder() { if (argv.file) { log(colors.green(`Processing file: ${argv.file}.`)); log(colors.green('Writing file to firebase.index.html.')); - await fs.copyFile(/*src*/ argv.file, 'firebase/index.html', - {overwrite: true}); + await fs.copyFile(/*src*/ argv.file, 'firebase/index.html', { + overwrite: true, + }); await replaceUrls('firebase/index.html'); } else { await Promise.all([ @@ -74,18 +77,28 @@ async function generateFirebaseFolder() { ]); await Promise.all([ modifyThirdPartyUrl(), - fs.copyFile('firebase/dist/ww.max.js', 'firebase/dist/ww.js', - {overwrite: true}), + fs.copyFile('firebase/dist/ww.max.js', 'firebase/dist/ww.js', { + overwrite: true, + }), ]); } async function replaceUrls(filePath) { const data = await fs.readFile(filePath, 'utf8'); - let result = data.replace(/https:\/\/cdn\.ampproject\.org\/v0\.js/g, '/dist/amp.js'); + let result = data.replace( + /https:\/\/cdn\.ampproject\.org\/v0\.js/g, + '/dist/amp.js' + ); if (argv.min) { - result = result.replace(/https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, '/dist/v0/$1.js'); + result = result.replace( + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + '/dist/v0/$1.js' + ); } else { - result = result.replace(/https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, '/dist/v0/$1.max.js'); + result = result.replace( + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, + '/dist/v0/$1.max.js' + ); } await fs.writeFile(filePath, result, 'utf8'); } @@ -97,14 +110,15 @@ if (!argv.nobuild) { } gulp.task( - 'firebase', - 'Generates firebase folder for deployment', - tasks, - generateFirebaseFolder, - { - options: { - 'file': 'File to deploy to firebase as index.html', - 'min': 'Source from minified files', - 'nobuild': 'Skips the gulp build|dist step.', - }, - }); + 'firebase', + 'Generates firebase folder for deployment', + tasks, + generateFirebaseFolder, + { + options: { + 'file': 'File to deploy to firebase as index.html', + 'min': 'Source from minified files', + 'nobuild': 'Skips the gulp build|dist step.', + }, + } +); diff --git a/build-system/tasks/get-zindex/index.js b/build-system/tasks/get-zindex/index.js index a0fcb186be1a0..d7ff733761bdc 100644 --- a/build-system/tasks/get-zindex/index.js +++ b/build-system/tasks/get-zindex/index.js @@ -15,7 +15,6 @@ */ 'use strict'; - const fs = require('fs'); const gulp = require('gulp-help')(require('gulp')); const PluginError = require('plugin-error'); @@ -23,17 +22,13 @@ const postcss = require('postcss'); const table = require('text-table'); const through = require('through2'); -const tableHeaders = [ - ['selector', 'z-index', 'file'], - ['---', '---', '---'], -]; +const tableHeaders = [['selector', 'z-index', 'file'], ['---', '---', '---']]; const tableOptions = { align: ['l', 'l', 'l'], hsep: ' | ', }; - /** * @param {!Object} acc accumulator object for selectors * @param {!Rules} css post css rules object @@ -74,11 +69,12 @@ function onFileThrough(file, enc, cb) { const selectors = Object.create(null); postcss([zIndexCollector.bind(null, selectors)]) - .process(file.contents.toString(), { - from: file.relative, - }).then(() => { - cb(null, {name: file.relative, selectors}); - }); + .process(file.contents.toString(), { + from: file.relative, + }) + .then(() => { + cb(null, {name: file.relative, selectors}); + }); } /** @@ -88,14 +84,18 @@ function onFileThrough(file, enc, cb) { */ function createTable(filesData) { const rows = []; - Object.keys(filesData).sort().forEach(fileName => { - const selectors = filesData[fileName]; - Object.keys(selectors).sort().forEach(selectorName => { - const zIndex = selectors[selectorName]; - const row = [selectorName, zIndex, fileName]; - rows.push(row); + Object.keys(filesData) + .sort() + .forEach(fileName => { + const selectors = filesData[fileName]; + Object.keys(selectors) + .sort() + .forEach(selectorName => { + const zIndex = selectors[selectorName]; + const row = [selectorName, zIndex, fileName]; + rows.push(row); + }); }); - }); rows.sort((a, b) => { const aZIndex = parseInt(a[1], 10); const bZIndex = parseInt(b[1], 10); @@ -104,7 +104,6 @@ function createTable(filesData) { return rows; } - /** * @param {string} glob * @return {!Stream} @@ -120,20 +119,23 @@ function getZindexForAmp(cb) { const filesData = Object.create(null); // Don't return the stream here since we do a `writeFileSync` getZindex('{css,src,extensions}/**/*.css') - .on('data', chunk => { - filesData[chunk.name] = chunk.selectors; - }) - .on('end', () => { - const rows = createTable(filesData); - rows.unshift.apply(rows, tableHeaders); - const tbl = table(rows, tableOptions); - fs.writeFileSync('css/Z_INDEX.md', tbl); - cb(); - }); + .on('data', chunk => { + filesData[chunk.name] = chunk.selectors; + }) + .on('end', () => { + const rows = createTable(filesData); + rows.unshift.apply(rows, tableHeaders); + const tbl = table(rows, tableOptions); + fs.writeFileSync('css/Z_INDEX.md', tbl); + cb(); + }); } -gulp.task('get-zindex', 'Runs through all css files of project to gather ' + - 'z-index values', getZindexForAmp); +gulp.task( + 'get-zindex', + 'Runs through all css files of project to gather ' + 'z-index values', + getZindexForAmp +); exports.getZindex = getZindex; exports.createTable = createTable; diff --git a/build-system/tasks/get-zindex/test.js b/build-system/tasks/get-zindex/test.js index 72678cb28dbc7..1b5674caf9657 100644 --- a/build-system/tasks/get-zindex/test.js +++ b/build-system/tasks/get-zindex/test.js @@ -15,7 +15,6 @@ */ 'use strict'; - const m = require('./'); const test = require('ava'); @@ -34,13 +33,13 @@ test.cb('collects selectors', t => { const data = Object.create(null); const testFiles = `${__dirname}/*.css`; m.getZindex(testFiles) - .on('data', chunk => { - data[chunk.name] = chunk.selectors; - }) - .on('end', () => { - t.deepEqual(data, result); - t.end(); - }); + .on('data', chunk => { + data[chunk.name] = chunk.selectors; + }) + .on('end', () => { + t.deepEqual(data, result); + t.end(); + }); }); test('sync - create array of arrays with z index order', t => { diff --git a/build-system/tasks/jsify-css.js b/build-system/tasks/jsify-css.js index 113d0820c0dd6..dbc7d6ff1b011 100644 --- a/build-system/tasks/jsify-css.js +++ b/build-system/tasks/jsify-css.js @@ -15,7 +15,6 @@ */ 'use strict'; - const autoprefixer = require('autoprefixer'); const colors = require('ansi-colors'); const cssnano = require('cssnano'); @@ -61,12 +60,15 @@ const cssNanoDefaultOptions = { * @return {!Promise} that resolves with the css content after * processing */ -const transformCss = exports.transformCss = function(filename, opt_cssnano) { +const transformCss = (exports.transformCss = function(filename, opt_cssnano) { opt_cssnano = opt_cssnano || Object.create(null); // See http://cssnano.co/optimisations/ for full list. // We try and turn off any optimization that is marked unsafe. - const cssnanoOptions = Object.assign(Object.create(null), - cssNanoDefaultOptions, opt_cssnano); + const cssnanoOptions = Object.assign( + Object.create(null), + cssNanoDefaultOptions, + opt_cssnano + ); const cssnanoTransformer = cssnano({preset: ['default', cssnanoOptions]}); const css = fs.readFileSync(filename, 'utf8'); @@ -74,7 +76,7 @@ const transformCss = exports.transformCss = function(filename, opt_cssnano) { return postcss(transformers).process(css.toString(), { 'from': filename, }); -}; +}); /** * 'Jsify' a CSS file - Adds vendor specific css prefixes to the css file, diff --git a/build-system/tasks/json-check.js b/build-system/tasks/json-check.js index ae996aaa97739..921b4166b88bb 100644 --- a/build-system/tasks/json-check.js +++ b/build-system/tasks/json-check.js @@ -27,29 +27,35 @@ const expectedCaches = ['cloudflare', 'google']; * Fail if caches.json is missing some expected caches. */ function checkCachesJson() { - return gulp.src(['caches.json']) - .pipe(through2.obj(function(file) { - let obj; - try { - obj = JSON.parse(file.contents.toString()); - } catch (e) { - log(colors.yellow('Could not parse caches.json. ' - + 'This is most likely a fatal error that ' - + 'will be found by checkValidJson')); - return; - } - const foundCaches = []; - for (const foundCache of obj.caches) { - foundCaches.push(foundCache.id); + return gulp.src(['caches.json']).pipe( + through2.obj(function(file) { + let obj; + try { + obj = JSON.parse(file.contents.toString()); + } catch (e) { + log( + colors.yellow( + 'Could not parse caches.json. ' + + 'This is most likely a fatal error that ' + + 'will be found by checkValidJson' + ) + ); + return; + } + const foundCaches = []; + for (const foundCache of obj.caches) { + foundCaches.push(foundCache.id); + } + for (const cache of expectedCaches) { + if (!foundCaches.includes(cache)) { + log( + colors.red('Missing expected cache "' + cache + '" in caches.json') + ); + process.exitCode = 1; } - for (const cache of expectedCaches) { - if (!foundCaches.includes(cache)) { - log(colors.red('Missing expected cache "' - + cache + '" in caches.json')); - process.exitCode = 1; - } - } - })); + } + }) + ); } /** @@ -57,25 +63,35 @@ function checkCachesJson() { */ function checkValidJson() { let hasError = false; - return gulp.src(jsonGlobs) - .pipe(through2.obj(function(file) { + return gulp + .src(jsonGlobs) + .pipe( + through2.obj(function(file) { try { JSON.parse(file.contents.toString()); } catch (e) { - log(colors.red('Invalid JSON in ' - + file.relative + ': ' + e.message)); + log( + colors.red('Invalid JSON in ' + file.relative + ': ' + e.message) + ); hasError = true; } - })) - .on('end', function() { - if (hasError) { - process.exit(1); - } - }); + }) + ) + .on('end', function() { + if (hasError) { + process.exit(1); + } + }); } -gulp.task('caches-json', 'Check that some expected caches are included.', - checkCachesJson); +gulp.task( + 'caches-json', + 'Check that some expected caches are included.', + checkCachesJson +); gulp.task( - 'json-syntax', 'Check that JSON files are valid JSON.', checkValidJson); + 'json-syntax', + 'Check that JSON files are valid JSON.', + checkValidJson +); diff --git a/build-system/tasks/lint.js b/build-system/tasks/lint.js index bfecd743e2376..c6e37dfb6f253 100644 --- a/build-system/tasks/lint.js +++ b/build-system/tasks/lint.js @@ -15,7 +15,6 @@ */ 'use strict'; - const argv = require('minimist')(process.argv.slice(2)); const colors = require('ansi-colors'); const config = require('../config'); @@ -29,7 +28,7 @@ const watch = require('gulp-watch'); const {gitDiffNameOnlyMaster} = require('../git'); const {isTravisBuild, isTravisPullRequestBuild} = require('../travis'); -const isWatching = (argv.watch || argv.w) || false; +const isWatching = argv.watch || argv.w || false; const options = { fix: false, quiet: argv.quiet || false, @@ -48,7 +47,10 @@ const rootDir = path.dirname(path.dirname(__dirname)); function initializeStream(globs, streamOptions) { let stream = gulp.src(globs, streamOptions); if (isWatching) { - const watcher = lazypipe().pipe(watch, globs); + const watcher = lazypipe().pipe( + watch, + globs + ); stream = stream.pipe(watcher()); } return stream; @@ -81,51 +83,71 @@ function runLinter(filePath, stream, options) { if (collapseLintResults) { // TODO(#15255, #14761): Remove log folding after warnings are fixed. log(colors.bold(colors.yellow('Lint results: ')) + 'Expand this section'); - console./* OK*/log('travis_fold:start:lint_results\n'); + console./* OK*/ log('travis_fold:start:lint_results\n'); } const fixedFiles = {}; - return stream.pipe(eslint(options)) - .pipe(eslint.formatEach('stylish', function(msg) { + return stream + .pipe(eslint(options)) + .pipe( + eslint.formatEach('stylish', function(msg) { logOnSameLine(msg.trim() + '\n'); - })) - .pipe(eslintIfFixed(filePath)) - .pipe(eslint.result(function(result) { + }) + ) + .pipe(eslintIfFixed(filePath)) + .pipe( + eslint.result(function(result) { if (!isTravisBuild()) { logOnSameLine(colors.green('Linted: ') + result.filePath); } if (options.fix && result.fixed) { const relativePath = path.relative(rootDir, result.filePath); - const status = result.errorCount == 0 ? - colors.green('Fixed: ') : colors.yellow('Partially fixed: '); + const status = + result.errorCount == 0 + ? colors.green('Fixed: ') + : colors.yellow('Partially fixed: '); logOnSameLine(status + colors.cyan(relativePath)); fixedFiles[relativePath] = status; } - })) - .pipe(eslint.results(function(results) { + }) + ) + .pipe( + eslint.results(function(results) { // TODO(#15255, #14761): Remove log folding after warnings are fixed. if (collapseLintResults) { - console./* OK*/log('travis_fold:end:lint_results'); + console./* OK*/ log('travis_fold:end:lint_results'); } if (results.errorCount == 0 && results.warningCount == 0) { if (!isTravisBuild()) { - logOnSameLine(colors.green('SUCCESS: ') + - 'No linter warnings or errors.'); + logOnSameLine( + colors.green('SUCCESS: ') + 'No linter warnings or errors.' + ); } } else { - const prefix = results.errorCount == 0 ? - colors.yellow('WARNING: ') : colors.red('ERROR: '); - logOnSameLine(prefix + 'Found ' + - results.errorCount + ' error(s) and ' + - results.warningCount + ' warning(s).'); + const prefix = + results.errorCount == 0 + ? colors.yellow('WARNING: ') + : colors.red('ERROR: '); + logOnSameLine( + prefix + + 'Found ' + + results.errorCount + + ' error(s) and ' + + results.warningCount + + ' warning(s).' + ); if (!options.fix) { - log(colors.yellow('NOTE 1:'), - 'You may be able to automatically fix some of these warnings ' + + log( + colors.yellow('NOTE 1:'), + 'You may be able to automatically fix some of these warnings ' + '/ errors by running', - colors.cyan('gulp lint --local-changes --fix'), - 'from your local branch.'); - log(colors.yellow('NOTE 2:'), - 'Since this is a destructive operation (that edits your files', - 'in-place), make sure you commit before running the command.'); + colors.cyan('gulp lint --local-changes --fix'), + 'from your local branch.' + ); + log( + colors.yellow('NOTE 2:'), + 'Since this is a destructive operation (that edits your files', + 'in-place), make sure you commit before running the command.' + ); } } if (options.fix && Object.keys(fixedFiles).length > 0) { @@ -134,8 +156,9 @@ function runLinter(filePath, stream, options) { log(fixedFiles[file] + colors.cyan(file)); }); } - })) - .pipe(eslint.failAfterError()); + }) + ) + .pipe(eslint.failAfterError()); } /** @@ -159,10 +182,14 @@ function eslintRulesChanged() { if (!isTravisPullRequestBuild()) { return false; } - return gitDiffNameOnlyMaster().filter(function(file) { - return path.basename(file).includes('.eslintrc') || - path.dirname(file) === 'build-system/eslint-rules'; - }).length > 0; + return ( + gitDiffNameOnlyMaster().filter(function(file) { + return ( + path.basename(file).includes('.eslintrc') || + path.dirname(file) === 'build-system/eslint-rules' + ); + }).length > 0 + ); } /** @@ -171,8 +198,9 @@ function eslintRulesChanged() { * @param {!Array} files */ function setFilesToLint(files) { - config.lintGlobs = - config.lintGlobs.filter(e => e !== '**/*.js').concat(files); + config.lintGlobs = config.lintGlobs + .filter(e => e !== '**/*.js') + .concat(files); if (!isTravisBuild()) { log(colors.green('INFO: ') + 'Running lint on the following files:'); files.forEach(file => { @@ -192,10 +220,12 @@ function lint() { } if (argv.files) { setFilesToLint(argv.files.split(',')); - } else if (!eslintRulesChanged() && - (isTravisPullRequestBuild() || - process.env.LOCAL_PR_CHECK || - argv['local-changes'])) { + } else if ( + !eslintRulesChanged() && + (isTravisPullRequestBuild() || + process.env.LOCAL_PR_CHECK || + argv['local-changes']) + ) { const jsFiles = jsFilesChanged(); if (jsFiles.length == 0) { log(colors.green('INFO: ') + 'No JS files in this PR'); @@ -208,18 +238,17 @@ function lint() { return runLinter(basePath, stream, options); } - gulp.task( - 'lint', - 'Validates against Google Closure Linter', - maybeUpdatePackages, - lint, - { - options: { - 'watch': ' Watches for changes in files, validates against the linter', - 'fix': ' Fixes simple lint errors (spacing etc)', - 'local-changes': - ' Lints just the changes commited to the local branch', - 'quiet': ' Suppress warnings from outputting', - }, - }); + 'lint', + 'Validates against Google Closure Linter', + maybeUpdatePackages, + lint, + { + options: { + 'watch': ' Watches for changes in files, validates against the linter', + 'fix': ' Fixes simple lint errors (spacing etc)', + 'local-changes': ' Lints just the changes commited to the local branch', + 'quiet': ' Suppress warnings from outputting', + }, + } +); diff --git a/build-system/tasks/mocha-ci-reporter.js b/build-system/tasks/mocha-ci-reporter.js index bc6e8ce122c36..fd5d65cd64531 100644 --- a/build-system/tasks/mocha-ci-reporter.js +++ b/build-system/tasks/mocha-ci-reporter.js @@ -48,8 +48,9 @@ function ciReporter(runner) { const {failures, stats} = self; Base.list(failures); process.stdout.write( - `Executed ${stats.failures + stats.passes} of ${stats.tests} ` + - `(Skipped ${stats.pending}) `); + `Executed ${stats.failures + stats.passes} of ${stats.tests} ` + + `(Skipped ${stats.pending}) ` + ); if (stats.failures == 0) { process.stdout.write(Base.color('green', 'SUCCESS \n')); } else { diff --git a/build-system/tasks/pr-check.js b/build-system/tasks/pr-check.js index d255e9c0a9dca..5d0bc8eb423c3 100644 --- a/build-system/tasks/pr-check.js +++ b/build-system/tasks/pr-check.js @@ -19,7 +19,6 @@ const argv = require('minimist')(process.argv.slice(2)); const gulp = require('gulp-help')(require('gulp')); const {execOrDie} = require('../exec'); - /** * Simple wrapper around pr-check.js. */ @@ -35,13 +34,13 @@ function prCheck() { } gulp.task( - 'pr-check', - 'Locally runs the PR checks that are run by Travis CI.', - prCheck, - { - options: { - 'files': ' Restricts unit / integration tests to just these files', - 'nobuild': ' Skips building the runtime via `gulp build`.', - }, - } + 'pr-check', + 'Locally runs the PR checks that are run by Travis CI.', + prCheck, + { + options: { + 'files': ' Restricts unit / integration tests to just these files', + 'nobuild': ' Skips building the runtime via `gulp build`.', + }, + } ); diff --git a/build-system/tasks/prepend-global/index.js b/build-system/tasks/prepend-global/index.js index 8081eeb00312f..132856431395b 100644 --- a/build-system/tasks/prepend-global/index.js +++ b/build-system/tasks/prepend-global/index.js @@ -48,7 +48,8 @@ function sanityCheck(str) { const numMatches = numConfigs(str); if (numMatches != 1) { throw new Error( - 'Found ' + numMatches + ' AMP_CONFIG(s) before write. Aborting!'); + 'Found ' + numMatches + ' AMP_CONFIG(s) before write. Aborting!' + ); } } @@ -64,15 +65,14 @@ function checkoutBranchConfigs(filename, opt_localBranch, opt_branch) { } const branch = opt_branch || 'origin/master'; // One bad path here will fail the whole operation. - return exec(`git checkout ${branch} ${filename}`) - .catch(function(e) { - // This means the files don't exist in master. Assume that it exists - // in the current branch. - if (/did not match any file/.test(e.message)) { - return; - } - throw e; - }); + return exec(`git checkout ${branch} ${filename}`).catch(function(e) { + // This means the files don't exist in master. Assume that it exists + // in the current branch. + if (/did not match any file/.test(e.message)) { + return; + } + throw e; + }); } /** @@ -81,8 +81,10 @@ function checkoutBranchConfigs(filename, opt_localBranch, opt_branch) { * @return {string} */ function prependConfig(configString, fileString) { - return `self.AMP_CONFIG||(self.AMP_CONFIG=${configString});` + - `/*AMP_CONFIG*/${fileString}`; + return ( + `self.AMP_CONFIG||(self.AMP_CONFIG=${configString});` + + `/*AMP_CONFIG*/${fileString}` + ); } /** @@ -123,42 +125,48 @@ function valueOrDefault(value, defaultValue) { * @return {!Promise} */ function applyConfig( - config, target, filename, opt_localDev, opt_localBranch, opt_branch, - opt_fortesting) { + config, + target, + filename, + opt_localDev, + opt_localBranch, + opt_branch, + opt_fortesting +) { return checkoutBranchConfigs(filename, opt_localBranch, opt_branch) - .then(() => { - return Promise.all([ - fs.readFileAsync(filename), - fs.readFileAsync(target), - ]); - }) - .then(files => { - let configJson; - try { - configJson = JSON.parse(files[0].toString()); - } catch (e) { - log(red(`Error parsing config file: ${filename}`)); - throw e; - } - if (opt_localDev) { - configJson = enableLocalDev(config, target, configJson); - } - if (opt_fortesting) { - configJson = Object.assign({test: true}, configJson); - } - const targetString = files[1].toString(); - const configString = JSON.stringify(configJson); - return prependConfig(configString, targetString); - }) - .then(fileString => { - sanityCheck(fileString); - return writeTarget(target, fileString, argv.dryrun); - }) - .then(() => { - if (!isTravisBuild()) { - log('Wrote', cyan(config), 'AMP config to', cyan(target)); - } - }); + .then(() => { + return Promise.all([ + fs.readFileAsync(filename), + fs.readFileAsync(target), + ]); + }) + .then(files => { + let configJson; + try { + configJson = JSON.parse(files[0].toString()); + } catch (e) { + log(red(`Error parsing config file: ${filename}`)); + throw e; + } + if (opt_localDev) { + configJson = enableLocalDev(config, target, configJson); + } + if (opt_fortesting) { + configJson = Object.assign({test: true}, configJson); + } + const targetString = files[1].toString(); + const configString = JSON.stringify(configJson); + return prependConfig(configString, targetString); + }) + .then(fileString => { + sanityCheck(fileString); + return writeTarget(target, fileString, argv.dryrun); + }) + .then(() => { + if (!isTravisBuild()) { + log('Wrote', cyan(config), 'AMP config to', cyan(target)); + } + }); } /** @@ -174,10 +182,10 @@ function enableLocalDev(config, target, configJson) { } const TESTING_HOST = process.env.AMP_TESTING_HOST; if (typeof TESTING_HOST == 'string') { - const TESTING_HOST_FULL_URL = TESTING_HOST.match(/^https?:\/\//) ? - TESTING_HOST : 'http://' + TESTING_HOST; - const TESTING_HOST_NO_PROTOCOL = - TESTING_HOST.replace(/^https?:\/\//, ''); + const TESTING_HOST_FULL_URL = TESTING_HOST.match(/^https?:\/\//) + ? TESTING_HOST + : 'http://' + TESTING_HOST; + const TESTING_HOST_NO_PROTOCOL = TESTING_HOST.replace(/^https?:\/\//, ''); LOCAL_DEV_AMP_CONFIG = Object.assign(LOCAL_DEV_AMP_CONFIG, { thirdPartyUrl: TESTING_HOST_FULL_URL, @@ -185,8 +193,14 @@ function enableLocalDev(config, target, configJson) { thirdPartyFrameRegex: TESTING_HOST_NO_PROTOCOL, }); if (!isTravisBuild()) { - log('Set', cyan('TESTING_HOST'), 'to', cyan(TESTING_HOST), - 'in', cyan(target)); + log( + 'Set', + cyan('TESTING_HOST'), + 'to', + cyan(TESTING_HOST), + 'in', + cyan(target) + ); } } return Object.assign(LOCAL_DEV_AMP_CONFIG, configJson); @@ -197,25 +211,23 @@ function enableLocalDev(config, target, configJson) { * @return {!Promise} */ function removeConfig(target) { - return fs.readFileAsync(target) - .then(file => { - let contents = file.toString(); - if (numConfigs(contents) == 0) { - if (!isTravisBuild()) { - log('No configs found in', cyan(target)); - } - return Promise.resolve(); - } - sanityCheck(contents); - const config = - /self\.AMP_CONFIG\|\|\(self\.AMP_CONFIG=.*?\/\*AMP_CONFIG\*\//; - contents = contents.replace(config, ''); - return writeTarget(target, contents, argv.dryrun).then(() => { - if (!isTravisBuild()) { - log('Removed existing config from', cyan(target)); - } - }); - }); + return fs.readFileAsync(target).then(file => { + let contents = file.toString(); + if (numConfigs(contents) == 0) { + if (!isTravisBuild()) { + log('No configs found in', cyan(target)); + } + return Promise.resolve(); + } + sanityCheck(contents); + const config = /self\.AMP_CONFIG\|\|\(self\.AMP_CONFIG=.*?\/\*AMP_CONFIG\*\//; + contents = contents.replace(config, ''); + return writeTarget(target, contents, argv.dryrun).then(() => { + if (!isTravisBuild()) { + log('Removed existing config from', cyan(target)); + } + }); + }); } function main() { @@ -241,34 +253,48 @@ function main() { // Prod by default. const config = argv.canary ? 'canary' : 'prod'; if (argv.canary) { - filename = valueOrDefault(argv.canary, - 'build-system/global-configs/canary-config.json'); + filename = valueOrDefault( + argv.canary, + 'build-system/global-configs/canary-config.json' + ); } else { - filename = valueOrDefault(argv.prod, - 'build-system/global-configs/prod-config.json'); + filename = valueOrDefault( + argv.prod, + 'build-system/global-configs/prod-config.json' + ); } return removeConfig(target).then(() => { return applyConfig( - config, target, filename, - argv.local_dev, argv.local_branch, argv.branch, argv.fortesting); + config, + target, + filename, + argv.local_dev, + argv.local_branch, + argv.branch, + argv.fortesting + ); }); } gulp.task('prepend-global', 'Prepends a json config to a target file', main, { options: { 'target': ' The file to prepend the json config to.', - 'canary': ' Prepend the default canary config. ' + - 'Takes in an optional value for a custom canary config source.', - 'prod': ' Prepend the default prod config. ' + - 'Takes in an optional value for a custom prod config source.', + 'canary': + ' Prepend the default canary config. ' + + 'Takes in an optional value for a custom canary config source.', + 'prod': + ' Prepend the default prod config. ' + + 'Takes in an optional value for a custom prod config source.', 'local_dev': ' Enables runtime to be used for local development.', - 'branch': ' Switch to a git branch to get config source from. ' + - 'Uses master by default.', - 'local_branch': ' Don\'t switch branches and use the config from the ' + - 'local branch.', + 'branch': + ' Switch to a git branch to get config source from. ' + + 'Uses master by default.', + 'local_branch': + " Don't switch branches and use the config from the " + 'local branch.', 'fortesting': ' Force the config to return true for getMode().test', - 'remove': ' Removes previously prepended json config from the target ' + - 'file (if present).', + 'remove': + ' Removes previously prepended json config from the target ' + + 'file (if present).', }, }); diff --git a/build-system/tasks/prepend-global/test.js b/build-system/tasks/prepend-global/test.js index 60b049f6831da..3d75765c47d26 100644 --- a/build-system/tasks/prepend-global/test.js +++ b/build-system/tasks/prepend-global/test.js @@ -15,16 +15,17 @@ */ 'use strict'; - const m = require('./'); const test = require('ava'); - test('sync - prepends global config', t => { t.plan(1); const res = m.prependConfig('{"hello":"world"}', 'var x = 1 + 1;'); - t.is(res, 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"});' + - '/*AMP_CONFIG*/var x = 1 + 1;'); + t.is( + res, + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"});' + + '/*AMP_CONFIG*/var x = 1 + 1;' + ); }); test('sync - valueOrDefault', t => { @@ -37,15 +38,17 @@ test('sync - valueOrDefault', t => { test('sync - sanityCheck', t => { t.plan(3); - const badStr = 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'var x = 1 + 1;'; + const badStr = + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'var x = 1 + 1;'; const badStr2 = 'var x = 1 + 1;'; - const goodStr = 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'var x = 1 + 1;'; + const goodStr = + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'var x = 1 + 1;'; t.false(m.numConfigs(badStr) == 1); t.true(m.numConfigs(goodStr) == 1); t.false(m.numConfigs(badStr2) == 1); diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index b93e7b9d2e2cc..9ca6016136fdf 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -25,22 +25,25 @@ const through2 = require('through2'); const dedicatedCopyrightNoteSources = /(\.js|\.css|\.go)$/; const requiresReviewPrivacy = - 'Usage of this API requires dedicated review due to ' + - 'being privacy sensitive. Please file an issue asking for permission' + - ' to use if you have not yet done so.'; + 'Usage of this API requires dedicated review due to ' + + 'being privacy sensitive. Please file an issue asking for permission' + + ' to use if you have not yet done so.'; -const privateServiceFactory = 'This service should only be installed in ' + - 'the whitelisted files. Other modules should use a public function ' + - 'typically called serviceNameFor.'; +const privateServiceFactory = + 'This service should only be installed in ' + + 'the whitelisted files. Other modules should use a public function ' + + 'typically called serviceNameFor.'; const shouldNeverBeUsed = - 'Usage of this API is not allowed - only for internal purposes.'; + 'Usage of this API is not allowed - only for internal purposes.'; -const backwardCompat = 'This method must not be called. It is only retained ' + - 'for backward compatibility during rollout.'; +const backwardCompat = + 'This method must not be called. It is only retained ' + + 'for backward compatibility during rollout.'; -const realiasGetMode = 'Do not re-alias getMode or its return so it can be ' + - 'DCE\'d. Use explicitly like "getMode().localDev" instead.'; +const realiasGetMode = + 'Do not re-alias getMode or its return so it can be ' + + 'DCE\'d. Use explicitly like "getMode().localDev" instead.'; // Terms that must not appear in our source files. const forbiddenTerms = { @@ -72,11 +75,12 @@ const forbiddenTerms = { 'dev\\(\\)\\.assert\\(': 'Use the devAssert function instead.', '[^.]user\\(\\)\\.assert\\(': 'Use the userAssert function instead.', 'it\\.only': '', - 'Math\.random[^;()]*=': 'Use Sinon to stub!!!', + 'Math.random[^;()]*=': 'Use Sinon to stub!!!', 'gulp-util': { - message: '`gulp-util` will be deprecated soon. See ' + - 'https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5 ' + - 'for a list of alternatives.', + message: + '`gulp-util` will be deprecated soon. See ' + + 'https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5 ' + + 'for a list of alternatives.', }, 'document-register-element.node': { message: 'Use `document-register-element.patched` instead', @@ -92,12 +96,14 @@ const forbiddenTerms = { message: 'Use a sandbox instead to avoid repeated `#restore` calls', }, 'sandbox\\.(spy|stub|mock)\\([^,\\s]*[iI]?frame[^,\\s]*,': { - message: 'Do NOT stub on a cross domain iframe! #5359\n' + - ' If this is same domain, mark /*OK*/.\n' + - ' If this is cross domain, overwrite the method directly.', + message: + 'Do NOT stub on a cross domain iframe! #5359\n' + + ' If this is same domain, mark /*OK*/.\n' + + ' If this is cross domain, overwrite the method directly.', }, 'console\\.\\w+\\(': { - message: 'If you run against this, use console/*OK*/.[log|error] to ' + + message: + 'If you run against this, use console/*OK*/.[log|error] to ' + 'whitelist a legit case.', whitelist: [ 'build-system/pr-check.js', @@ -128,10 +134,7 @@ const forbiddenTerms = { // as a variable. '\\bgetMode\\([^)]*\\)(?!\\.)': { message: realiasGetMode, - whitelist: [ - 'src/mode.js', - 'dist.3p/current/integration.js', - ], + whitelist: ['src/mode.js', 'dist.3p/current/integration.js'], }, 'import[^}]*\\bgetMode as': { message: realiasGetMode, @@ -146,19 +149,17 @@ const forbiddenTerms = { ], }, '(?:var|let|const) +IS_DEV +=': { - message: 'IS_DEV local var only allowed in mode.js and ' + - 'dist.3p/current/integration.js', - whitelist: [ - 'src/mode.js', + message: + 'IS_DEV local var only allowed in mode.js and ' + 'dist.3p/current/integration.js', - ], + whitelist: ['src/mode.js', 'dist.3p/current/integration.js'], }, '\\.prefetch\\(': { message: 'Do not use preconnect.prefetch, use preconnect.preload instead.', }, 'iframePing': { - message: 'This is only available in vendor config for ' + - 'temporary workarounds.', + message: + 'This is only available in vendor config for ' + 'temporary workarounds.', whitelist: [ 'build-system/routes/analytics.js', 'extensions/amp-analytics/0.1/config.js', @@ -193,23 +194,15 @@ const forbiddenTerms = { }, 'cidServiceForDocForTesting': { message: privateServiceFactory, - whitelist: [ - 'src/service/cid-impl.js', - ], + whitelist: ['src/service/cid-impl.js'], }, 'installCryptoService': { message: privateServiceFactory, - whitelist: [ - 'src/service/crypto-impl.js', - 'src/runtime.js', - ], + whitelist: ['src/service/crypto-impl.js', 'src/runtime.js'], }, 'installDocumentStateService': { message: privateServiceFactory, - whitelist: [ - 'src/service/document-state.js', - 'src/runtime.js', - ], + whitelist: ['src/service/document-state.js', 'src/runtime.js'], }, 'installDocService': { message: privateServiceFactory, @@ -243,10 +236,7 @@ const forbiddenTerms = { }, 'installTemplatesService': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/template-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/template-impl.js'], }, 'installUrlReplacementsServiceForDoc': { message: privateServiceFactory, @@ -267,17 +257,11 @@ const forbiddenTerms = { }, 'setViewerVisibilityState': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/viewer-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/viewer-impl.js'], }, 'installViewportServiceForDoc': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/viewport/viewport-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/viewport/viewport-impl.js'], }, 'installVsyncService': { message: privateServiceFactory, @@ -299,10 +283,7 @@ const forbiddenTerms = { }, 'installXhrService': { message: privateServiceFactory, - whitelist: [ - 'src/runtime.js', - 'src/service/xhr-impl.js', - ], + whitelist: ['src/runtime.js', 'src/service/xhr-impl.js'], }, 'installPositionObserverServiceForDoc': { message: privateServiceFactory, @@ -403,10 +384,7 @@ const forbiddenTerms = { }, 'getBaseCid': { message: requiresReviewPrivacy, - whitelist: [ - 'src/service/cid-impl.js', - 'src/service/viewer-impl.js', - ], + whitelist: ['src/service/cid-impl.js', 'src/service/viewer-impl.js'], }, 'isTrustedViewer': { message: requiresReviewPrivacy, @@ -424,9 +402,7 @@ const forbiddenTerms = { }, 'eval\\(': { message: shouldNeverBeUsed, - whitelist: [ - 'extension/amp-bind/0.1/test/test-bind-expr.js', - ], + whitelist: ['extension/amp-bind/0.1/test/test-bind-expr.js'], }, 'storageForDoc': { message: requiresReviewPrivacy, @@ -507,8 +483,9 @@ const forbiddenTerms = { ], }, 'internalListenImplementation': { - message: 'Use `listen()` in either `event-helper` or `3p-frame-messaging`' + - ', depending on your use case.', + message: + 'Use `listen()` in either `event-helper` or `3p-frame-messaging`' + + ', depending on your use case.', whitelist: [ 'src/3p-frame-messaging.js', 'src/event-helper.js', @@ -518,22 +495,21 @@ const forbiddenTerms = { }, 'setTimeout.*throw': { message: 'Use dev.error or user.error instead.', - whitelist: [ - 'src/log.js', - ], + whitelist: ['src/log.js'], }, - '(dev|user)\\(\\)\\.(fine|info|warn|error)\\((?!\\s*([A-Z0-9-]+|[\'"`][A-Z0-9-]+[\'"`]))[^,)\n]*': { // eslint-disable-line max-len - message: 'Logging message require explicitly `TAG`, or an all uppercase' + - ' string as the first parameter', + '(dev|user)\\(\\)\\.(fine|info|warn|error)\\((?!\\s*([A-Z0-9-]+|[\'"`][A-Z0-9-]+[\'"`]))[^,)\n]*': { + // eslint-disable-line max-len + message: + 'Logging message require explicitly `TAG`, or an all uppercase' + + ' string as the first parameter', }, '\\.schedulePass\\(': { message: 'schedulePass is heavy, think twice before using it', - whitelist: [ - 'src/service/resources-impl.js', - ], + whitelist: ['src/service/resources-impl.js'], }, '\\.requireLayout\\(': { - message: 'requireLayout is restricted b/c it affects non-contained elements', // eslint-disable-line max-len + message: + 'requireLayout is restricted b/c it affects non-contained elements', // eslint-disable-line max-len whitelist: [ 'extensions/amp-animation/0.1/web-animations.js', 'extensions/amp-lightbox-gallery/0.1/amp-lightbox-gallery.js', @@ -550,14 +526,11 @@ const forbiddenTerms = { }, '(win|Win)(dow)?(\\(\\))?\\.open\\W': { message: 'Use dom.openWindowDialog', - whitelist: [ - 'src/dom.js', - ], + whitelist: ['src/dom.js'], }, '\\.getWin\\(': { message: backwardCompat, - whitelist: [ - ], + whitelist: [], }, '/\\*\\* @type \\{\\!Element\\} \\*/': { message: 'Use assertElement instead of casting to !Element.', @@ -577,8 +550,9 @@ const forbiddenTerms = { ], }, 'AMP_CONFIG': { - message: 'Do not access AMP_CONFIG directly. Use isExperimentOn() ' + - 'and getMode() to access config', + message: + 'Do not access AMP_CONFIG directly. Use isExperimentOn() ' + + 'and getMode() to access config', whitelist: [ 'build-system/amp.extern.js', 'build-system/app.js', @@ -597,21 +571,19 @@ const forbiddenTerms = { ], }, 'data:image/svg(?!\\+xml;charset=utf-8,)[^,]*,': { - message: 'SVG data images must use charset=utf-8: ' + - '"data:image/svg+xml;charset=utf-8,..."', + message: + 'SVG data images must use charset=utf-8: ' + + '"data:image/svg+xml;charset=utf-8,..."', }, 'new CustomEvent\\(': { message: 'Use createCustomEvent() helper instead.', - whitelist: [ - 'src/event-helper.js', - ], + whitelist: ['src/event-helper.js'], }, 'new FormData\\(': { - message: 'Use createFormDataWrapper() instead and call ' + - 'formDataWrapper.getFormData() to get the native FormData object.', - whitelist: [ - 'src/form-data-wrapper.js', - ], + message: + 'Use createFormDataWrapper() instead and call ' + + 'formDataWrapper.getFormData() to get the native FormData object.', + whitelist: ['src/form-data-wrapper.js'], }, '([eE]xit|[eE]nter|[cC]ancel|[rR]equest)Full[Ss]creen\\(': { message: 'Use fullscreenEnter() and fullscreenExit() from dom.js instead.', @@ -626,26 +598,25 @@ const forbiddenTerms = { '\\.defer\\(\\)': { message: 'Promise.defer() is deprecated and should not be used.', }, - '(dev|user)\\(\\)\\.assert(Element|String|Number)?\\(\\s*([A-Z][A-Z0-9-]*,)': { // eslint-disable-line max-len + '(dev|user)\\(\\)\\.assert(Element|String|Number)?\\(\\s*([A-Z][A-Z0-9-]*,)': { + // eslint-disable-line max-len message: 'TAG is not an argument to assert(). Will cause false positives.', }, 'eslint no-unused-vars': { message: 'Use a line-level "no-unused-vars" rule instead.', - whitelist: [ - 'extensions/amp-access/0.1/iframe-api/access-controller.js', - ], + whitelist: ['extensions/amp-access/0.1/iframe-api/access-controller.js'], }, 'this\\.skip\\(\\)': { - message: 'Use of `this.skip()` is forbidden in test files. Use ' + - '`this.skipTest()` from within a `before()` block instead. See #17245.', + message: + 'Use of `this.skip()` is forbidden in test files. Use ' + + '`this.skipTest()` from within a `before()` block instead. See #17245.', checkInTestFolder: true, - whitelist: [ - 'test/_init_tests.js', - ], + whitelist: ['test/_init_tests.js'], }, '[^\\.]makeBodyVisible\\(': { - message: 'This is a protected function. If you are calling this to show ' + - 'body after an error please use `makeBodyVisibleRecovery`', + message: + 'This is a protected function. If you are calling this to show ' + + 'body after an error please use `makeBodyVisibleRecovery`', whitelist: [ 'src/amp.js', 'src/amp-shadow.js', @@ -656,8 +627,9 @@ const forbiddenTerms = { }, }; -const ThreePTermsMessage = 'The 3p bootstrap iframe has no polyfills loaded' + - ' and can thus not use most modern web APIs.'; +const ThreePTermsMessage = + 'The 3p bootstrap iframe has no polyfills loaded' + + ' and can thus not use most modern web APIs.'; const forbidden3pTerms = { // We need to forbid promise usage because we don't have our own polyfill @@ -667,19 +639,20 @@ const forbidden3pTerms = { '\\.then\\((?!callNext)': ThreePTermsMessage, }; -const bannedTermsHelpString = 'Please review viewport service for helper ' + - 'methods or mark with `/*OK*/` or `/*REVIEW*/` and consult the AMP team. ' + - 'Most of the forbidden property/method access banned on the ' + - '`forbiddenTermsSrcInclusive` object can be found in ' + - '[What forces layout / reflow gist by Paul Irish]' + - '(https://gist.github.com/paulirish/5d52fb081b3570c81e3a). ' + - 'These properties/methods when read/used require the browser ' + - 'to have the up-to-date value to return which might possibly be an ' + - 'expensive computation and could also be triggered multiple times ' + - 'if we are not careful. Please mark the call with ' + - '`object./*OK*/property` if you explicitly need to read or update the ' + - 'forbidden property/method or mark it with `object./*REVIEW*/property` ' + - 'if you are unsure and so that it stands out in code reviews.'; +const bannedTermsHelpString = + 'Please review viewport service for helper ' + + 'methods or mark with `/*OK*/` or `/*REVIEW*/` and consult the AMP team. ' + + 'Most of the forbidden property/method access banned on the ' + + '`forbiddenTermsSrcInclusive` object can be found in ' + + '[What forces layout / reflow gist by Paul Irish]' + + '(https://gist.github.com/paulirish/5d52fb081b3570c81e3a). ' + + 'These properties/methods when read/used require the browser ' + + 'to have the up-to-date value to return which might possibly be an ' + + 'expensive computation and could also be triggered multiple times ' + + 'if we are not careful. Please mark the call with ' + + '`object./*OK*/property` if you explicitly need to read or update the ' + + 'forbidden property/method or mark it with `object./*REVIEW*/property` ' + + 'if you are unsure and so that it stands out in code reviews.'; const forbiddenTermsSrcInclusive = { '\\.innerHTML(?!_)': bannedTermsHelpString, @@ -733,16 +706,15 @@ const forbiddenTermsSrcInclusive = { ], }, 'getComputedStyle\\(': { - message: 'Due to various bugs in Firefox, you must use the computedStyle ' + - 'helper in style.js.', - whitelist: [ - 'src/style.js', - 'dist.3p/current/integration.js', - ], + message: + 'Due to various bugs in Firefox, you must use the computedStyle ' + + 'helper in style.js.', + whitelist: ['src/style.js', 'dist.3p/current/integration.js'], }, 'decodeURIComponent\\(': { - message: 'decodeURIComponent throws for malformed URL components. Please ' + - 'use tryDecodeUriComponent from src/url.js', + message: + 'decodeURIComponent throws for malformed URL components. Please ' + + 'use tryDecodeUriComponent from src/url.js', whitelist: [ '3p/integration.js', 'dist.3p/current/integration.js', @@ -757,8 +729,9 @@ const forbiddenTermsSrcInclusive = { ], }, 'Text(Encoder|Decoder)\\(': { - message: 'TextEncoder/TextDecoder is not supported in all browsers.' + - ' Please use UTF8 utilities from src/bytes.js', + message: + 'TextEncoder/TextDecoder is not supported in all browsers.' + + ' Please use UTF8 utilities from src/bytes.js', whitelist: [ 'ads/google/a4a/line-delimited-response-handler.js', 'examples/pwa/pwa.js', @@ -810,8 +783,9 @@ const forbiddenTermsSrcInclusive = { ], }, 'reject\\(\\)': { - message: 'Always supply a reason in rejections. ' + - 'error.cancellation() may be applicable.', + message: + 'Always supply a reason in rejections. ' + + 'error.cancellation() may be applicable.', whitelist: [ 'extensions/amp-access/0.1/access-expr-impl.js', 'extensions/amp-animation/0.1/parsers/css-expr-impl.js', @@ -839,9 +813,7 @@ const forbiddenTermsSrcInclusive = { }, '\\.getTime\\(\\)': { message: 'Unless you do weird date math (whitelist), use Date.now().', - whitelist: [ - 'extensions/amp-timeago/0.1/amp-timeago.js', - ], + whitelist: ['extensions/amp-timeago/0.1/amp-timeago.js'], }, '\\.expandStringSync\\(': { message: requiresReviewPrivacy, @@ -882,8 +854,9 @@ const forbiddenTermsSrcInclusive = { ], }, '(cdn|3p)\\.ampproject\\.': { - message: 'The CDN domain should typically not be hardcoded in source ' + - 'code. Use a property of urls from src/config.js instead.', + message: + 'The CDN domain should typically not be hardcoded in source ' + + 'code. Use a property of urls from src/config.js instead.', whitelist: [ 'ads/_a4a-config.js', 'build-system/app.js', @@ -912,29 +885,21 @@ const forbiddenTermsSrcInclusive = { }, '\\.indexOf\\([\'"][^)]+\\)\\s*===?\\s*0\\b': { message: 'use startsWith helper in src/string.js', - whitelist: [ - 'dist.3p/current/integration.js', - 'build-system/app.js', - ], + whitelist: ['dist.3p/current/integration.js', 'build-system/app.js'], }, '\\.indexOf\\(.*===?.*\\.length': 'use endsWith helper in src/string.js', '/url-parse-query-string': { message: 'Import parseQueryString from `src/url.js`', - whitelist: [ - 'src/url.js', - 'src/mode.js', - 'dist.3p/current/integration.js', - ], + whitelist: ['src/url.js', 'src/mode.js', 'dist.3p/current/integration.js'], }, '\\.trim(Left|Right)\\(\\)': { message: 'Unsupported on IE; use trim() or a helper instead.', - whitelist: [ - 'validator/engine/validator.js', - ], + whitelist: ['validator/engine/validator.js'], }, - 'process\\.env(\\.TRAVIS|\\[\\\'TRAVIS)': { - message: 'Do not directly use process.env.TRAVIS. Instead, add a ' + - 'function to build-system/travis.js', + "process\\.env(\\.TRAVIS|\\[\\'TRAVIS)": { + message: + 'Do not directly use process.env.TRAVIS. Instead, add a ' + + 'function to build-system/travis.js', whitelist: [ 'build-system/check-package-manager.js', 'build-system/travis.js', @@ -945,15 +910,11 @@ const forbiddenTermsSrcInclusive = { // Terms that must appear in a source file. const requiredTerms = { - 'Copyright 20(15|16|17|18|19) The AMP HTML Authors\\.': - dedicatedCopyrightNoteSources, - 'Licensed under the Apache License, Version 2\\.0': - dedicatedCopyrightNoteSources, - 'http\\://www\\.apache\\.org/licenses/LICENSE-2\\.0': - dedicatedCopyrightNoteSources, + 'Copyright 20(15|16|17|18|19) The AMP HTML Authors\\.': dedicatedCopyrightNoteSources, + 'Licensed under the Apache License, Version 2\\.0': dedicatedCopyrightNoteSources, + 'http\\://www\\.apache\\.org/licenses/LICENSE-2\\.0': dedicatedCopyrightNoteSources, }; - /** * Check if root of path is test/ or file is in a folder named test. * @param {string} path @@ -971,8 +932,10 @@ function isInTestFolder(path) { */ function isInBuildSystemFixtureFolder(filePath) { const folder = path.dirname(filePath); - return folder.startsWith('build-system/babel-plugins') && - folder.includes('test/fixtures'); + return ( + folder.startsWith('build-system/babel-plugins') && + folder.includes('test/fixtures') + ); } /** @@ -1010,61 +973,75 @@ function stripComments(contents) { function matchTerms(file, terms) { const contents = stripComments(file.contents.toString()); const {relative} = file; - return Object.keys(terms).map(function(term) { - let fix; - const {whitelist, checkInTestFolder} = terms[term]; - // NOTE: we could do a glob test instead of exact check in the future - // if needed but that might be too permissive. - if (isInBuildSystemFixtureFolder(relative) || (Array.isArray(whitelist) && - (whitelist.indexOf(relative) != -1 || - (isInTestFolder(relative) && !checkInTestFolder)))) { - return false; - } - // we can't optimize building the `RegExp` objects early unless we build - // another mapping of term -> regexp object to be able to get back to the - // original term to get the possible fix value. This is ok as the - // presubmit doesn't have to be blazing fast and this is most likely - // negligible. - const regex = new RegExp(term, 'gm'); - let index = 0; - let line = 1; - let column = 0; - let match; - let hasTerm = false; + return Object.keys(terms) + .map(function(term) { + let fix; + const {whitelist, checkInTestFolder} = terms[term]; + // NOTE: we could do a glob test instead of exact check in the future + // if needed but that might be too permissive. + if ( + isInBuildSystemFixtureFolder(relative) || + (Array.isArray(whitelist) && + (whitelist.indexOf(relative) != -1 || + (isInTestFolder(relative) && !checkInTestFolder))) + ) { + return false; + } + // we can't optimize building the `RegExp` objects early unless we build + // another mapping of term -> regexp object to be able to get back to the + // original term to get the possible fix value. This is ok as the + // presubmit doesn't have to be blazing fast and this is most likely + // negligible. + const regex = new RegExp(term, 'gm'); + let index = 0; + let line = 1; + let column = 0; + let match; + let hasTerm = false; - while ((match = regex.exec(contents))) { - hasTerm = true; - for (index; index < match.index; index++) { - if (contents[index] === '\n') { - line++; - column = 1; - } else { - column++; + while ((match = regex.exec(contents))) { + hasTerm = true; + for (index; index < match.index; index++) { + if (contents[index] === '\n') { + line++; + column = 1; + } else { + column++; + } } - } - log(colors.red('Found forbidden: "' + match[0] + - '" in ' + relative + ':' + line + ':' + column)); - if (typeof terms[term] === 'string') { - fix = terms[term]; - } else { - fix = terms[term].message; - } + log( + colors.red( + 'Found forbidden: "' + + match[0] + + '" in ' + + relative + + ':' + + line + + ':' + + column + ) + ); + if (typeof terms[term] === 'string') { + fix = terms[term]; + } else { + fix = terms[term].message; + } - // log the possible fix information if provided for the term. - if (fix) { - log(colors.blue(fix)); + // log the possible fix information if provided for the term. + if (fix) { + log(colors.blue(fix)); + } + log(colors.blue('==========')); } - log(colors.blue('==========')); - } - return hasTerm; - }).some(function(hasAnyTerm) { - return hasAnyTerm; - }); + return hasTerm; + }) + .some(function(hasAnyTerm) { + return hasAnyTerm; + }); } - /** * Test if a file's contents match any of the * forbidden terms @@ -1082,15 +1059,18 @@ function hasAnyTerms(file) { hasTerms = matchTerms(file, forbiddenTerms); - const isTestFile = /^test-/.test(basename) || /^_init_tests/.test(basename) - || /_test\.js$/.test(basename); + const isTestFile = + /^test-/.test(basename) || + /^_init_tests/.test(basename) || + /_test\.js$/.test(basename); if (!isTestFile) { hasSrcInclusiveTerms = matchTerms(file, forbiddenTermsSrcInclusive); } - const is3pFile = /\/(3p|ads)\//.test(pathname) || - basename == '3p.js' || - basename == 'style.js'; + const is3pFile = + /\/(3p|ads)\//.test(pathname) || + basename == '3p.js' || + basename == 'style.js'; // Yet another reason to move ads/google/a4a somewhere else const isA4A = /\/a4a\//.test(pathname); const isRecaptcha = basename == 'recaptcha.js'; @@ -1111,23 +1091,28 @@ function hasAnyTerms(file) { */ function isMissingTerms(file) { const contents = file.contents.toString(); - return Object.keys(requiredTerms).map(function(term) { - const filter = requiredTerms[term]; - if (!filter.test(file.path)) { - return false; - } + return Object.keys(requiredTerms) + .map(function(term) { + const filter = requiredTerms[term]; + if (!filter.test(file.path)) { + return false; + } - const matches = contents.match(new RegExp(term)); - if (!matches) { - log(colors.red('Did not find required: "' + term + - '" in ' + file.relative)); - log(colors.blue('==========')); - return true; - } - return false; - }).some(function(hasMissingTerm) { - return hasMissingTerm; - }); + const matches = contents.match(new RegExp(term)); + if (!matches) { + log( + colors.red( + 'Did not find required: "' + term + '" in ' + file.relative + ) + ); + log(colors.blue('==========')); + return true; + } + return false; + }) + .some(function(hasMissingTerm) { + return hasMissingTerm; + }); } /** @@ -1137,27 +1122,39 @@ function isMissingTerms(file) { function checkForbiddenAndRequiredTerms() { let forbiddenFound = false; let missingRequirements = false; - return gulp.src(srcGlobs) - .pipe(through2.obj(function(file, enc, cb) { + return gulp + .src(srcGlobs) + .pipe( + through2.obj(function(file, enc, cb) { forbiddenFound = hasAnyTerms(file) || forbiddenFound; missingRequirements = isMissingTerms(file) || missingRequirements; cb(); - })) - .on('end', function() { - if (forbiddenFound) { - log(colors.blue( - 'Please remove these usages or consult with the AMP team.')); - } - if (missingRequirements) { - log(colors.blue( - 'Adding these terms (e.g. by adding a required LICENSE ' + - 'to the file)')); - } - if (forbiddenFound || missingRequirements) { - process.exit(1); - } - }); + }) + ) + .on('end', function() { + if (forbiddenFound) { + log( + colors.blue( + 'Please remove these usages or consult with the AMP team.' + ) + ); + } + if (missingRequirements) { + log( + colors.blue( + 'Adding these terms (e.g. by adding a required LICENSE ' + + 'to the file)' + ) + ); + } + if (forbiddenFound || missingRequirements) { + process.exit(1); + } + }); } -gulp.task('presubmit', 'Run validation against files to check for forbidden ' + - 'and required terms', checkForbiddenAndRequiredTerms); +gulp.task( + 'presubmit', + 'Run validation against files to check for forbidden ' + 'and required terms', + checkForbiddenAndRequiredTerms +); diff --git a/build-system/tasks/process-3p-github-pr.js b/build-system/tasks/process-3p-github-pr.js index 5895c2552e6c4..1bf512a90e02c 100644 --- a/build-system/tasks/process-3p-github-pr.js +++ b/build-system/tasks/process-3p-github-pr.js @@ -79,16 +79,17 @@ const ANALYZE_OUTCOME = { AD: 1, // Ad integration PR, ping the ad onduty person }; -const AD_COMMENT = 'Dear contributor! Thank you for the pull request. ' + - 'It looks like this PR is trying to add support to an ad network. \n \n' + - 'If this is your first time adding support for ' + - 'a new third-party ad service, please make sure your follow our ' + - '[developer guideline](https://github.com/ampproject/amphtml/blob/master/' + - 'ads/README.md#developer-guidelines-for-a-pull-request). \n \n' + - 'If you have not implemented it, we also highly recommend implementing ' + - 'the [renderStart API](https://github.com/ampproject/amphtml/blob/master/' + - 'ads/README.md#available-apis) to provide better user experience. ' + - 'Please let us know if there is any question. \n \n'; +const AD_COMMENT = + 'Dear contributor! Thank you for the pull request. ' + + 'It looks like this PR is trying to add support to an ad network. \n \n' + + 'If this is your first time adding support for ' + + 'a new third-party ad service, please make sure your follow our ' + + '[developer guideline](https://github.com/ampproject/amphtml/blob/master/' + + 'ads/README.md#developer-guidelines-for-a-pull-request). \n \n' + + 'If you have not implemented it, we also highly recommend implementing ' + + 'the [renderStart API](https://github.com/ampproject/amphtml/blob/master/' + + 'ads/README.md#available-apis) to provide better user experience. ' + + 'Please let us know if there is any question. \n \n'; const defaultOption = { headers: { @@ -119,12 +120,15 @@ function calculateReviewer() { */ function processPRs() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log(colors.red('You have not set the ' + 'GITHUB_ACCESS_TOKEN env var.')); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } @@ -136,17 +140,18 @@ function processPRs() { arrayPromises.push(getIssues(batch)); } return BBPromise.all(arrayPromises) - .then(requests => [].concat.apply([], requests)) - .then(issues => { - const allIssues = issues; - const allTasks = []; - allIssues.forEach(function(issue) { - allTasks.push(handleIssue(issue)); - }); - return Promise.all(allTasks); - }).then(() => { - log(colors.blue('auto triaging succeed!')); + .then(requests => [].concat.apply([], requests)) + .then(issues => { + const allIssues = issues; + const allTasks = []; + allIssues.forEach(function(issue) { + allTasks.push(handleIssue(issue)); }); + return Promise.all(allTasks); + }) + .then(() => { + log(colors.blue('auto triaging succeed!')); + }); } function handleIssue(issue) { @@ -186,8 +191,9 @@ function getIssues(opt_page) { function getPullRequestFiles(pr) { const options = extend({}, defaultOption); const {number} = pr; - options.url = 'https://api.github.com/repos/ampproject/amphtml/pulls/' - + `${number}/files`; + options.url = + 'https://api.github.com/repos/ampproject/amphtml/pulls/' + + `${number}/files`; return request(options).then(res => { const files = JSON.parse(res.body); if (!Array.isArray(files)) { @@ -261,14 +267,16 @@ function isQualifiedPR(issue) { function replyToPR(pr, outcome) { let promise = Promise.resolve(); if (outcome == ANALYZE_OUTCOME.AD) { - promise = promise.then(() => { - // We should be good with rate limit given the number of - // 3p integration PRs today. - const comment = AD_COMMENT + `Thank you! Ping @${reviewer} for review`; - return applyComment(pr, comment); - }).then(() => { - return assignIssue(pr, [reviewer]); - }); + promise = promise + .then(() => { + // We should be good with rate limit given the number of + // 3p integration PRs today. + const comment = AD_COMMENT + `Thank you! Ping @${reviewer} for review`; + return applyComment(pr, comment); + }) + .then(() => { + return assignIssue(pr, [reviewer]); + }); } return promise; } @@ -280,17 +288,22 @@ function replyToPR(pr, outcome) { */ function applyComment(issue, comment) { const {number} = issue; - const options = extend({ - url: 'https://api.github.com/repos/ampproject/amphtml/issues/' - + `${number}/comments`, - method: 'POST', - body: JSON.stringify({ - 'body': comment, - }), - }, defaultOption); + const options = extend( + { + url: + 'https://api.github.com/repos/ampproject/amphtml/issues/' + + `${number}/comments`, + method: 'POST', + body: JSON.stringify({ + 'body': comment, + }), + }, + defaultOption + ); if (isDryrun) { - log(colors.blue(`apply comment to PR #${number}, ` + - `comment is ${comment}`)); + log( + colors.blue(`apply comment to PR #${number}, ` + `comment is ${comment}`) + ); return Promise.resolve(); } return request(options); @@ -303,29 +316,32 @@ function applyComment(issue, comment) { */ function assignIssue(issue, assignees) { const {number} = issue; - const options = extend({ - url: 'https://api.github.com/repos/ampproject/amphtml/issues/' - + `${number}/assignees`, - method: 'POST', - body: JSON.stringify({ - 'assignees': assignees, - }), - }, defaultOption); + const options = extend( + { + url: + 'https://api.github.com/repos/ampproject/amphtml/issues/' + + `${number}/assignees`, + method: 'POST', + body: JSON.stringify({ + 'assignees': assignees, + }), + }, + defaultOption + ); if (isDryrun) { - log(colors.blue(`assign PR #${number}, ` + - `to ${assignees}`)); + log(colors.blue(`assign PR #${number}, ` + `to ${assignees}`)); return Promise.resolve(); } return request(options); } gulp.task( - 'process-3p-github-pr', - 'Automatically triage 3P integration PRs', - processPRs, - { - options: { - dryrun: ' Generate process but don\'t push it out', - }, - } + 'process-3p-github-pr', + 'Automatically triage 3P integration PRs', + processPRs, + { + options: { + dryrun: " Generate process but don't push it out", + }, + } ); diff --git a/build-system/tasks/process-github-issues.js b/build-system/tasks/process-github-issues.js index 610eac1cbc9ce..1fe1893849310 100644 --- a/build-system/tasks/process-github-issues.js +++ b/build-system/tasks/process-github-issues.js @@ -73,12 +73,15 @@ const NUM_BATCHES = 14; // We start processing the issues by checking token first function processIssues() { if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('You have not set the ' + - 'GITHUB_ACCESS_TOKEN env var.')); - log(colors.green('See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.')); + log(colors.red('You have not set the ' + 'GITHUB_ACCESS_TOKEN env var.')); + log( + colors.green( + 'See https://help.github.com/articles/' + + 'creating-an-access-token-for-command-line-use/ ' + + 'for instructions on how to create a github access token. We only ' + + 'need `public_repo` scope.' + ) + ); return; } return updateGitHubIssues().then(function() { @@ -119,166 +122,214 @@ function updateGitHubIssues() { arrayPromises.push(getIssues(batch)); } return BBPromise.all(arrayPromises) - .then(requests => [].concat.apply([], requests)) - .then(issues => { - const allIssues = issues; - allIssues.forEach(function(issue) { - const { - labels, - milestone, - assignee, - 'pull_request': pullRequest, - 'updated_at': issueLastUpdate, - } = issue; - let issueType; - let milestoneTitle; - let milestoneState; - let hasPriority = false; - let hasCategory = false; - let issueNewMilestone = MILESTONE_PENDING_TRIAGE; - let assigneeName = ''; - let biweeklyUpdate = true; - let quartelyUpdate = true; - // if an issue is a pull request, we'll skip it - if (pullRequest) { - if (isDryrun) { - log(colors.red(issue.number + ' is a pull request')); - } - return; - } - if (getLastUpdate(issueLastUpdate) > QUARTERLY_DAYS) { - quartelyUpdate = false; - biweeklyUpdate = false; - } else if (getLastUpdate(issueLastUpdate) > BIWEEKLY_DAYS) { - biweeklyUpdate = false; - } - // Get the assignee - if (assignee) { - assigneeName = '@' + assignee.login; - } - // Get the title and state of the milestone - if (milestone) { - milestoneTitle = milestone.title; - milestoneState = milestone.state; - issueNewMilestone = milestone.number; + .then(requests => [].concat.apply([], requests)) + .then(issues => { + const allIssues = issues; + allIssues.forEach(function(issue) { + const { + labels, + milestone, + assignee, + 'pull_request': pullRequest, + 'updated_at': issueLastUpdate, + } = issue; + let issueType; + let milestoneTitle; + let milestoneState; + let hasPriority = false; + let hasCategory = false; + let issueNewMilestone = MILESTONE_PENDING_TRIAGE; + let assigneeName = ''; + let biweeklyUpdate = true; + let quartelyUpdate = true; + // if an issue is a pull request, we'll skip it + if (pullRequest) { + if (isDryrun) { + log(colors.red(issue.number + ' is a pull request')); } - // promise starts - promise = promise.then(function() { - log('Update ' + issue.number); - const updates = []; - // Get the labels we want to check - labels.forEach(function(label) { - if (label) { - // Check if the issues has type - if (label.name.startsWith('Type') || - label.name.startsWith('Related')) { - issueType = label.name; - } - // Check if the issues has Priority - if (label.name.startsWith('P0') || - label.name.startsWith('P1') || - label.name.startsWith('P2') || - label.name.startsWith('P3')) { - hasPriority = true; - if (label.name.startsWith('P0') || - label.name.startsWith('P1')) { - if (biweeklyUpdate == false) { - biweeklyUpdate = true; - updates.push(applyComment(issue, 'This is a high priority' - + ' issue but it hasn\'t been updated in awhile. ' + - assigneeName + ' Do you have any updates?')); - } - } else if (label.name.startsWith('P2') && - quartelyUpdate == false) { - quartelyUpdate = true; - updates.push(applyComment(issue, 'This issue hasn\'t been ' - + ' updated in awhile. ' + - assigneeName + ' Do you have any updates?')); + return; + } + if (getLastUpdate(issueLastUpdate) > QUARTERLY_DAYS) { + quartelyUpdate = false; + biweeklyUpdate = false; + } else if (getLastUpdate(issueLastUpdate) > BIWEEKLY_DAYS) { + biweeklyUpdate = false; + } + // Get the assignee + if (assignee) { + assigneeName = '@' + assignee.login; + } + // Get the title and state of the milestone + if (milestone) { + milestoneTitle = milestone.title; + milestoneState = milestone.state; + issueNewMilestone = milestone.number; + } + // promise starts + promise = promise.then(function() { + log('Update ' + issue.number); + const updates = []; + // Get the labels we want to check + labels.forEach(function(label) { + if (label) { + // Check if the issues has type + if ( + label.name.startsWith('Type') || + label.name.startsWith('Related') + ) { + issueType = label.name; + } + // Check if the issues has Priority + if ( + label.name.startsWith('P0') || + label.name.startsWith('P1') || + label.name.startsWith('P2') || + label.name.startsWith('P3') + ) { + hasPriority = true; + if ( + label.name.startsWith('P0') || + label.name.startsWith('P1') + ) { + if (biweeklyUpdate == false) { + biweeklyUpdate = true; + updates.push( + applyComment( + issue, + 'This is a high priority' + + " issue but it hasn't been updated in awhile. " + + assigneeName + + ' Do you have any updates?' + ) + ); } - } - if (label.name.startsWith('Category') || - label.name.startsWith('Related to') || - label.name.startsWith('GFI') || - label.name.startsWith('good first issue')) { - hasCategory = true; + } else if ( + label.name.startsWith('P2') && + quartelyUpdate == false + ) { + quartelyUpdate = true; + updates.push( + applyComment( + issue, + "This issue hasn't been " + + ' updated in awhile. ' + + assigneeName + + ' Do you have any updates?' + ) + ); } } - }); - // Milestone task: move issues from closed milestone - if (milestone) { - if (milestoneState === 'closed') { - issueNewMilestone = MILESTONE_BACKLOG_BUGS; - updates.push(applyMilestone(issue, issueNewMilestone)); + if ( + label.name.startsWith('Category') || + label.name.startsWith('Related to') || + label.name.startsWith('GFI') || + label.name.startsWith('good first issue') + ) { + hasCategory = true; } } - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE) { - if (quartelyUpdate == false) { - quartelyUpdate = true; - updates.push(applyComment(issue, 'This issue seems to be in ' + + }); + // Milestone task: move issues from closed milestone + if (milestone) { + if (milestoneState === 'closed') { + issueNewMilestone = MILESTONE_BACKLOG_BUGS; + updates.push(applyMilestone(issue, issueNewMilestone)); + } + } + if (issueNewMilestone === MILESTONE_PENDING_TRIAGE) { + if (quartelyUpdate == false) { + quartelyUpdate = true; + updates.push( + applyComment( + issue, + 'This issue seems to be in ' + ' Pending Triage for awhile. ' + - assigneeName + ' Please triage this to ' + - 'an appropriate milestone.')); - } + assigneeName + + ' Please triage this to ' + + 'an appropriate milestone.' + ) + ); } - // if issueType is not null, add correct milestones - if (issueType != null) { - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE || - milestone == null) { - if (issueType === 'Type: Feature Request') { - issueNewMilestone = MILESTONE_NEW_FRS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (issueType === 'Related to: Documentation' || - issueType === 'Type: Design Review' || - issueType === 'Type: Status Update') { - issueNewMilestone = MILESTONE_DOCS_UPDATES; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (issueType === 'Type: Bug' || - issueType === 'Related to: Flaky Tests') { - issueNewMilestone = MILESTONE_BACKLOG_BUGS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (milestone == null) { - updates.push(applyMilestone(issue, issueNewMilestone)); - } + } + // if issueType is not null, add correct milestones + if (issueType != null) { + if ( + issueNewMilestone === MILESTONE_PENDING_TRIAGE || + milestone == null + ) { + if (issueType === 'Type: Feature Request') { + issueNewMilestone = MILESTONE_NEW_FRS; + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if ( + issueType === 'Related to: Documentation' || + issueType === 'Type: Design Review' || + issueType === 'Type: Status Update' + ) { + issueNewMilestone = MILESTONE_DOCS_UPDATES; + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if ( + issueType === 'Type: Bug' || + issueType === 'Related to: Flaky Tests' + ) { + issueNewMilestone = MILESTONE_BACKLOG_BUGS; + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if (milestone == null) { + updates.push(applyMilestone(issue, issueNewMilestone)); } - } else if (milestone == null) { - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (issueNewMilestone === MILESTONE_PRIORITIZED_FRS || - issueNewMilestone === MILESTONE_NEW_FRS) { - updates.push(applyLabel(issue, 'Type: Feature Request')); - } else if (issueNewMilestone === MILESTONE_BACKLOG_BUGS || - milestoneTitle.startsWith('Sprint')) { - updates.push(applyLabel(issue, 'Type: Bug')); } - // Apply default priority if no priority - if (hasPriority == false && - issueNewMilestone != MILESTONE_NEW_FRS && - issueNewMilestone !== MILESTONE_3P_IMPLEMENTATION && - issueNewMilestone !== MILESTONE_PENDING_TRIAGE && - milestone != null) { - updates.push(applyLabel(issue, 'P2: Soon')); - } - // Add comment with missing Category - if (hasCategory == false) { - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE || - issueNewMilestone === MILESTONE_DOCS_UPDATES || - issueNewMilestone == null || - issueNewMilestone === MILESTONE_GREAT_ISSUES) { - if (isDryrun) { - log(colors.green('No comment needed ' - + ' for #' + issue.number)); - } - } else { - updates.push(applyComment(issue, - 'This issue doesn\'t have a category' - + ' which makes it harder for us to keep track of it. ' + - assigneeName + ' Please add an appropriate category.')); + } else if (milestone == null) { + updates.push(applyMilestone(issue, issueNewMilestone)); + } else if ( + issueNewMilestone === MILESTONE_PRIORITIZED_FRS || + issueNewMilestone === MILESTONE_NEW_FRS + ) { + updates.push(applyLabel(issue, 'Type: Feature Request')); + } else if ( + issueNewMilestone === MILESTONE_BACKLOG_BUGS || + milestoneTitle.startsWith('Sprint') + ) { + updates.push(applyLabel(issue, 'Type: Bug')); + } + // Apply default priority if no priority + if ( + hasPriority == false && + issueNewMilestone != MILESTONE_NEW_FRS && + issueNewMilestone !== MILESTONE_3P_IMPLEMENTATION && + issueNewMilestone !== MILESTONE_PENDING_TRIAGE && + milestone != null + ) { + updates.push(applyLabel(issue, 'P2: Soon')); + } + // Add comment with missing Category + if (hasCategory == false) { + if ( + issueNewMilestone === MILESTONE_PENDING_TRIAGE || + issueNewMilestone === MILESTONE_DOCS_UPDATES || + issueNewMilestone == null || + issueNewMilestone === MILESTONE_GREAT_ISSUES + ) { + if (isDryrun) { + log( + colors.green('No comment needed ' + ' for #' + issue.number) + ); } + } else { + updates.push( + applyComment( + issue, + "This issue doesn't have a category" + + ' which makes it harder for us to keep track of it. ' + + assigneeName + + ' Please add an appropriate category.' + ) + ); } - return Promise.all(updates); - }); + } + return Promise.all(updates); }); - return promise; }); + return promise; + }); } /** @@ -296,12 +347,19 @@ function applyMilestone(issue, milestoneNumber) { issue.milestone = milestoneNumber; if (isDryrun) { - log(colors.green('Milestone applied ' + milestoneNumber + - ' for #' + issue.number)); + log( + colors.green( + 'Milestone applied ' + milestoneNumber + ' for #' + issue.number + ) + ); return; } else { - return createGithubRequest('/issues/' + issue.number,'PATCH', - issue.milestone, 'milestone'); + return createGithubRequest( + '/issues/' + issue.number, + 'PATCH', + issue.milestone, + 'milestone' + ); } } @@ -318,14 +376,16 @@ function applyLabel(issue, label) { 'access_token': GITHUB_ACCESS_TOKEN, }; if (isDryrun) { - log(colors.green('Label applied ' + - label + ' for #' + issue.number)); + log(colors.green('Label applied ' + label + ' for #' + issue.number)); return; } else { - return createGithubRequest('/issues/' + issue.number + '/labels','POST', - [label], 'label'); + return createGithubRequest( + '/issues/' + issue.number + '/labels', + 'POST', + [label], + 'label' + ); } - } /** @@ -345,13 +405,23 @@ function applyComment(issue, comment) { return promise.then(function() { if (isDryrun) { log(colors.blue('waited 2 minutes to avoid gh rate limits')); - log(colors.green('Comment applied after ' + - 'waiting 2 minutes to avoid github rate limits: ' + comment + - ' for #' + issue.number)); + log( + colors.green( + 'Comment applied after ' + + 'waiting 2 minutes to avoid github rate limits: ' + + comment + + ' for #' + + issue.number + ) + ); return; } else { - createGithubRequest('/issues/' + issue.number + '/comments','POST', - comment, 'comment'); + createGithubRequest( + '/issues/' + issue.number + '/comments', + 'POST', + comment, + 'comment' + ); } }); } @@ -360,10 +430,11 @@ function getLastUpdate(issueLastUpdate) { const t = new Date(); const splits = issueLastUpdate.split('-', 3); const exactDay = splits[2].split('T', 1); - const firstDate = Date.UTC(splits[0],splits[1],exactDay[0]); - const secondDate = Date.UTC(t.getFullYear(),t.getMonth() + 1,t.getDate()); - const diff = Math.abs((firstDate.valueOf() - - secondDate.valueOf()) / (24 * 60 * 60 * 1000)); + const firstDate = Date.UTC(splits[0], splits[1], exactDay[0]); + const secondDate = Date.UTC(t.getFullYear(), t.getMonth() + 1, t.getDate()); + const diff = Math.abs( + (firstDate.valueOf() - secondDate.valueOf()) / (24 * 60 * 60 * 1000) + ); return diff; } @@ -404,13 +475,13 @@ function createGithubRequest(path, opt_method, opt_data, typeRequest) { } gulp.task( - 'process-github-issues', - 'Automatically updates the labels ' - + 'and milestones of all open issues at github.com/ampproject/amphtml.', - processIssues, - { - options: { - dryrun: ' Generate process but don\'t push it out', - }, - } + 'process-github-issues', + 'Automatically updates the labels ' + + 'and milestones of all open issues at github.com/ampproject/amphtml.', + processIssues, + { + options: { + dryrun: " Generate process but don't push it out", + }, + } ); diff --git a/build-system/tasks/release-tagging.js b/build-system/tasks/release-tagging.js index e02e68533abb6..69961d6ec2493 100644 --- a/build-system/tasks/release-tagging.js +++ b/build-system/tasks/release-tagging.js @@ -28,14 +28,13 @@ const {GITHUB_ACCESS_TOKEN} = process.env; const gitExec = BBPromise.promisify(git.exec); const isDryrun = argv.dryrun; -const verbose = (argv.verbose || argv.v); +const verbose = argv.verbose || argv.v; const LABELS = { 'canary': 'PR use: In Canary', 'prod': 'PR use: In Production', }; - /** * @param {string} type Either of "canary" or "prod". * @param {string} dir Working dir. @@ -48,19 +47,21 @@ function releaseTagFor(type, dir) { // Fetch tag. let tag; - promise = promise.then(function() { - return githubRequest('/releases'); - }).then(res => { - const array = JSON.parse(res.body); - for (let i = 0; i < array.length; i++) { - const release = array[i]; - const releaseType = release.prerelease ? 'canary' : 'prod'; - if (releaseType == type) { - tag = release.tag_name; - break; + promise = promise + .then(function() { + return githubRequest('/releases'); + }) + .then(res => { + const array = JSON.parse(res.body); + for (let i = 0; i < array.length; i++) { + const release = array[i]; + const releaseType = release.prerelease ? 'canary' : 'prod'; + if (releaseType == type) { + tag = release.tag_name; + break; + } } - } - }); + }); // Checkout tag. promise = promise.then(function() { @@ -73,29 +74,31 @@ function releaseTagFor(type, dir) { // Log. const pullRequests = []; - promise = promise.then(function() { - const date = new Date(); - date.setDate(date.getDate() - 15); - const dateIso = date.toISOString().split('T')[0]; - return gitExec({ - cwd: ampDir, - args: 'log --pretty=oneline --since=' + dateIso, - }); - }).then(function(output) { - const lines = output.split('\n'); - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - const paren = line.lastIndexOf('('); - line = paren != -1 ? line.substring(paren) : ''; - if (!line) { - continue; + promise = promise + .then(function() { + const date = new Date(); + date.setDate(date.getDate() - 15); + const dateIso = date.toISOString().split('T')[0]; + return gitExec({ + cwd: ampDir, + args: 'log --pretty=oneline --since=' + dateIso, + }); + }) + .then(function(output) { + const lines = output.split('\n'); + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + const paren = line.lastIndexOf('('); + line = paren != -1 ? line.substring(paren) : ''; + if (!line) { + continue; + } + const match = line.match(/\(\#(\d+)\)/); + if (match && match[1]) { + pullRequests.push(match[1]); + } } - const match = line.match(/\(\#(\d+)\)/); - if (match && match[1]) { - pullRequests.push(match[1]); - } - } - }); + }); // Update. const label = LABELS[type]; @@ -125,13 +128,11 @@ function applyLabel(pullRequest, label) { if (isDryrun) { return Promise.resolve(); } - return githubRequest( - '/issues/' + pullRequest + '/labels', - 'POST', - [label]).then(function() { + return githubRequest('/issues/' + pullRequest + '/labels', 'POST', [ + label, + ]).then(function() { if (verbose) { - log(colors.green( - 'Label applied ' + label + ' for #' + pullRequest)); + log(colors.green('Label applied ' + label + ' for #' + pullRequest)); } }); } @@ -213,7 +214,6 @@ function releaseTag() { return promise; } - gulp.task('release:tag', 'Tag the releases in pull requests', releaseTag, { options: { dryrun: ' Generate update log but dont push it out', diff --git a/build-system/tasks/runtime-test/helpers.js b/build-system/tasks/runtime-test/helpers.js index 85e78a9901bcd..3630971ffc384 100644 --- a/build-system/tasks/runtime-test/helpers.js +++ b/build-system/tasks/runtime-test/helpers.js @@ -37,8 +37,11 @@ const ROOT_DIR = path.resolve(__dirname, '../../../'); function extractCssJsFileMap() { //TODO(estherkim): consolidate arg validation logic if (!fs.existsSync(extensionsCssMapPath)) { - log(red('ERROR:'), 'Could not find the file', - cyan(extensionsCssMapPath) + '.'); + log( + red('ERROR:'), + 'Could not find the file', + cyan(extensionsCssMapPath) + '.' + ); log('Make sure', cyan('gulp css'), 'was run prior to this.'); process.exit(); } @@ -50,8 +53,9 @@ function extractCssJsFileMap() { // Adds an entry that maps a CSS file to a JS file function addCssJsEntry(cssData, cssBinaryName, cssJsFileMap) { - const cssFilePath = `extensions/${cssData['name']}/${cssData['version']}/` + - `${cssBinaryName}.css`; + const cssFilePath = + `extensions/${cssData['name']}/${cssData['version']}/` + + `${cssBinaryName}.css`; const jsFilePath = `build/${cssBinaryName}-${cssData['version']}.css.js`; cssJsFileMap[cssFilePath] = jsFilePath; } @@ -91,8 +95,11 @@ function getAdTypes() { // Add all other ad types const files = fs.readdirSync('./ads/'); for (let i = 0; i < files.length; i++) { - if (path.extname(files[i]) == '.js' - && files[i][0] != '_' && files[i] != 'ads.extern.js') { + if ( + path.extname(files[i]) == '.js' && + files[i][0] != '_' && + files[i] != 'ads.extern.js' + ) { const adType = path.basename(files[i], '.js'); const expanded = namingExceptions[adType]; if (expanded) { @@ -177,18 +184,22 @@ function unitTestsToRun(unitTestPaths) { function shouldRunTest(testFile, srcFiles) { const filesImported = getImports(testFile); - return filesImported.filter(function(file) { - return srcFiles.includes(file); - }).length > 0; + return ( + filesImported.filter(function(file) { + return srcFiles.includes(file); + }).length > 0 + ); } // Retrieves the set of unit tests that should be run // for a set of source files. function getTestsFor(srcFiles) { const allUnitTests = deglob.sync(unitTestPaths); - return allUnitTests.filter(testFile => { - return shouldRunTest(testFile, srcFiles); - }).map(fullPath => path.relative(ROOT_DIR, fullPath)); + return allUnitTests + .filter(testFile => { + return shouldRunTest(testFile, srcFiles); + }) + .map(fullPath => path.relative(ROOT_DIR, fullPath)); } filesChanged.forEach(file => { diff --git a/build-system/tasks/runtime-test/index.js b/build-system/tasks/runtime-test/index.js index cd1c34b802c2b..d60740018c7d3 100644 --- a/build-system/tasks/runtime-test/index.js +++ b/build-system/tasks/runtime-test/index.js @@ -40,8 +40,11 @@ const {isTravisBuild} = require('../../travis'); const {green, yellow, cyan, red} = colors; -const preTestTasks = argv.nobuild ? [] : ( - (argv.unit || argv.a4a || argv['local-changes']) ? ['css'] : ['build']); +const preTestTasks = argv.nobuild + ? [] + : argv.unit || argv.a4a || argv['local-changes'] + ? ['css'] + : ['build']; const batchSize = 4; // Number of Sauce Lab browsers @@ -65,13 +68,15 @@ function getConfig() { return Object.assign({}, karmaDefault, {browsers: ['Edge']}); } if (argv.ie) { - return Object.assign({}, karmaDefault, {browsers: ['IE'], + return Object.assign({}, karmaDefault, { + browsers: ['IE'], customLaunchers: { IeNoAddOns: { base: 'IE', flags: ['-extoff'], }, - }}); + }, + }); } if (argv.chrome_canary && !argv.chrome_flags) { return Object.assign({}, karmaDefault, {browsers: ['ChromeCanary']}); @@ -84,7 +89,8 @@ function getConfig() { const config = Object.assign({}, karmaDefault, { browsers: ['Chrome_flags'], customLaunchers: { - Chrome_flags: { // eslint-disable-line google-camelcase/google-camelcase + Chrome_flags: { + // eslint-disable-line google-camelcase/google-camelcase base: chromeBase, flags: formattedFlagList, }, @@ -93,8 +99,9 @@ function getConfig() { return config; } if (argv.headless) { - return Object.assign({}, karmaDefault, - {browsers: ['Chrome_no_extensions_headless']}); + return Object.assign({}, karmaDefault, { + browsers: ['Chrome_no_extensions_headless'], + }); } if (argv.saucelabs || argv.saucelabs_lite) { if (!process.env.SAUCE_USERNAME) { @@ -105,28 +112,29 @@ function getConfig() { } // Browser names are defined in `karma.conf.js`. - saucelabsBrowsers = argv.saucelabs ? - // With --saucelabs, integration tests are run on this set of browsers. - [ - 'SL_Chrome', - 'SL_Firefox', - // TODO(amp-infra): Restore this once tests are stable again. - // 'SL_Safari_11', - 'SL_Edge_17', - 'SL_Safari_12', - // TODO(amp-infra): Evaluate and add more platforms here. - //'SL_Chrome_Android_7', - //'SL_iOS_11', - //'SL_iOS_12', - //'SL_IE_11', - 'SL_Chrome_Beta', - 'SL_Firefox_Beta', - ] : [ - // With --saucelabs_lite, a subset of the unit tests are run. - // Only browsers that support chai-as-promised may be included below. - // TODO(rsimha): Add more browsers to this list. #6039. - 'SL_Safari_12', - ]; + saucelabsBrowsers = argv.saucelabs + ? // With --saucelabs, integration tests are run on this set of browsers. + [ + 'SL_Chrome', + 'SL_Firefox', + // TODO(amp-infra): Restore this once tests are stable again. + // 'SL_Safari_11', + 'SL_Edge_17', + 'SL_Safari_12', + // TODO(amp-infra): Evaluate and add more platforms here. + //'SL_Chrome_Android_7', + //'SL_iOS_11', + //'SL_iOS_12', + //'SL_IE_11', + 'SL_Chrome_Beta', + 'SL_Firefox_Beta', + ] + : [ + // With --saucelabs_lite, a subset of the unit tests are run. + // Only browsers that support chai-as-promised may be included below. + // TODO(rsimha): Add more browsers to this list. #6039. + 'SL_Safari_12', + ]; return Object.assign({}, karmaDefault, { reporters: ['super-dots', 'saucelabs', 'karmaSimpleReporter'], @@ -149,47 +157,74 @@ function printArgvMessages() { saucelabs: 'Running integration tests on Sauce Labs browsers.', saucelabs_lite: 'Running tests on a subset of Sauce Labs browsers.', // eslint-disable-line google-camelcase/google-camelcase nobuild: 'Skipping build.', - watch: 'Enabling watch mode. Editing and saving a file will cause the' + - ' tests for that file to be re-run in the same browser instance.', + watch: + 'Enabling watch mode. Editing and saving a file will cause the' + + ' tests for that file to be re-run in the same browser instance.', verbose: 'Enabling verbose mode. Expect lots of output!', testnames: 'Listing the names of all tests being run.', files: 'Running tests in the file(s): ' + cyan(argv.files), - integration: 'Running only the integration tests. Prerequisite: ' + - cyan('gulp build'), + integration: + 'Running only the integration tests. Prerequisite: ' + cyan('gulp build'), unit: 'Running only the unit tests. Prerequisite: ' + cyan('gulp css'), a4a: 'Running only A4A tests.', compiled: 'Running tests against minified code.', - grep: 'Only running tests that match the pattern "' + - cyan(argv.grep) + '".', + grep: + 'Only running tests that match the pattern "' + cyan(argv.grep) + '".', coverage: 'Running tests in code coverage mode.', headless: 'Running tests in a headless Chrome window.', - 'local-changes': 'Running unit tests directly affected by the files' + - ' changed in the local branch.', + 'local-changes': + 'Running unit tests directly affected by the files' + + ' changed in the local branch.', }; if (argv.chrome_flags) { - log(green('Launching'), cyan(chromeBase), green('with flags'), - cyan(formattedFlagList)); + log( + green('Launching'), + cyan(chromeBase), + green('with flags'), + cyan(formattedFlagList) + ); } if (!isTravisBuild()) { - log(green('Run'), cyan('gulp help'), - green('to see a list of all test flags.')); - log(green('⤷ Use'), cyan('--nohelp'), - green('to silence these messages.')); - if (!argv.unit && !argv.integration && !argv.files && !argv.a4a && - !argv['local-changes']) { + log( + green('Run'), + cyan('gulp help'), + green('to see a list of all test flags.') + ); + log(green('⤷ Use'), cyan('--nohelp'), green('to silence these messages.')); + if ( + !argv.unit && + !argv.integration && + !argv.files && + !argv.a4a && + !argv['local-changes'] + ) { log(green('Running all tests.')); - log(green('⤷ Use'), cyan('--unit'), green('or'), cyan('--integration'), - green('to run just the unit tests or integration tests.')); - log(green('⤷ Use'), cyan('--local-changes'), - green('to run unit tests from files commited to the local branch.')); + log( + green('⤷ Use'), + cyan('--unit'), + green('or'), + cyan('--integration'), + green('to run just the unit tests or integration tests.') + ); + log( + green('⤷ Use'), + cyan('--local-changes'), + green('to run unit tests from files commited to the local branch.') + ); } if (!argv.testnames && !argv.files && !argv['local-changes']) { - log(green('⤷ Use'), cyan('--testnames'), - green('to see the names of all tests being run.')); + log( + green('⤷ Use'), + cyan('--testnames'), + green('to see the names of all tests being run.') + ); } if (!argv.headless) { - log(green('⤷ Use'), cyan('--headless'), - green('to run tests in a headless Chrome window.')); + log( + green('⤷ Use'), + cyan('--headless'), + green('to run tests in a headless Chrome window.') + ); } if (!argv.compiled) { log(green('Running tests against unminified code.')); @@ -208,12 +243,15 @@ function printArgvMessages() { */ async function runTests() { if (!argv.integration && process.env.AMPSAUCE_REPO) { - console./* OK*/info('Deactivated for ampsauce repo'); + console./* OK*/ info('Deactivated for ampsauce repo'); } if (argv.saucelabs && !argv.integration) { - log(red('ERROR:'), 'Only integration tests may be run on the full set of', - 'Sauce Labs browsers'); + log( + red('ERROR:'), + 'Only integration tests may be run on the full set of', + 'Sauce Labs browsers' + ); log('Use', cyan('--saucelabs'), 'with', cyan('--integration')); process.exit(); } @@ -259,8 +297,10 @@ async function runTests() { } else if (argv['local-changes']) { const testsToRun = unitTestsToRun(config.unitTestPaths); if (testsToRun.length == 0) { - log(green('INFO:'), - 'No unit tests were directly affected by local changes.'); + log( + green('INFO:'), + 'No unit tests were directly affected by local changes.' + ); return reportTestSkipped(); } else { log(green('INFO:'), 'Running the following unit tests:'); @@ -272,14 +312,20 @@ async function runTests() { c.files = c.files.concat(config.commonUnitTestPaths, testsToRun); } else if (argv.integration) { c.files = c.files.concat( - config.commonIntegrationTestPaths, config.integrationTestPaths); + config.commonIntegrationTestPaths, + config.integrationTestPaths + ); } else if (argv.unit) { if (argv.saucelabs_lite) { c.files = c.files.concat( - config.commonUnitTestPaths, config.unitTestOnSaucePaths); + config.commonUnitTestPaths, + config.unitTestOnSaucePaths + ); } else { c.files = c.files.concat( - config.commonUnitTestPaths, config.unitTestPaths); + config.commonUnitTestPaths, + config.unitTestPaths + ); } } else if (argv.a4a) { c.files = c.files.concat(config.a4aTestPaths); @@ -290,7 +336,7 @@ async function runTests() { // c.client is available in test browser via window.parent.karma.config c.client.amp = { useCompiledJs: !!argv.compiled, - saucelabs: (!!argv.saucelabs) || (!!argv.saucelabs_lite), + saucelabs: !!argv.saucelabs || !!argv.saucelabs_lite, singlePass: !!argv.single_pass, adTypes: getAdTypes(), mochaTimeout: c.client.mocha.timeout, @@ -312,19 +358,25 @@ async function runTests() { if (argv.coverage) { c.browserify.transform = [ - ['babelify', { - plugins: [ - ['babel-plugin-istanbul', { - exclude: [ - './ads/**/*.js', - './third_party/**/*.js', - './test/**/*.js', - './extensions/**/test/**/*.js', - './testing/**/*.js', + [ + 'babelify', + { + plugins: [ + [ + 'babel-plugin-istanbul', + { + exclude: [ + './ads/**/*.js', + './third_party/**/*.js', + './test/**/*.js', + './extensions/**/test/**/*.js', + './testing/**/*.js', + ], + }, ], - }], - ], - }], + ], + }, + ], ]; c.plugins.push('karma-coverage-istanbul-reporter'); c.reporters = c.reporters.concat(['coverage-istanbul']); @@ -334,17 +386,27 @@ async function runTests() { }; } - const server = gulp.src(process.cwd(), {base: '.'}).pipe(webserver({ - port: karmaDefault.client.testServerPort, - host: 'localhost', - directoryListing: true, - middleware: [app], - }).on('kill', function() { - log(yellow('Shutting down test responses server on ' - + `localhost:${karmaDefault.client.testServerPort}`)); - })); - log(yellow('Started test responses server on ' - + `localhost:${karmaDefault.client.testServerPort}`)); + const server = gulp.src(process.cwd(), {base: '.'}).pipe( + webserver({ + port: karmaDefault.client.testServerPort, + host: 'localhost', + directoryListing: true, + middleware: [app], + }).on('kill', function() { + log( + yellow( + 'Shutting down test responses server on ' + + `localhost:${karmaDefault.client.testServerPort}` + ) + ); + }) + ); + log( + yellow( + 'Started test responses server on ' + + `localhost:${karmaDefault.client.testServerPort}` + ) + ); // Listen for Ctrl + C to cancel testing const handlerProcess = createCtrlcHandler('test'); @@ -371,8 +433,9 @@ async function runTests() { if (processExitCode != 0) { log( - red('ERROR:'), - yellow(`Karma test failed with exit code ${processExitCode}`)); + red('ERROR:'), + yellow(`Karma test failed with exit code ${processExitCode}`) + ); process.exitCode = processExitCode; } @@ -389,29 +452,43 @@ async function runTests() { async function runTestInBatches() { const browsers = {stable: [], beta: []}; for (const browserId of saucelabsBrowsers) { - browsers[browserId.toLowerCase().endsWith('_beta') ? 'beta' : 'stable'] - .push(browserId); + browsers[ + browserId.toLowerCase().endsWith('_beta') ? 'beta' : 'stable' + ].push(browserId); } if (browsers.stable.length) { const allBatchesExitCodes = await runTestInBatchesWithBrowsers( - 'stable', browsers.stable); + 'stable', + browsers.stable + ); if (allBatchesExitCodes) { - log(yellow('Some tests have failed on'), cyan('stable'), - yellow('browsers, so skipping running them on'), cyan('beta'), - yellow('browsers.')); + log( + yellow('Some tests have failed on'), + cyan('stable'), + yellow('browsers, so skipping running them on'), + cyan('beta'), + yellow('browsers.') + ); return allBatchesExitCodes; } } if (browsers.beta.length) { const allBatchesExitCodes = await runTestInBatchesWithBrowsers( - 'beta', browsers.beta); + 'beta', + browsers.beta + ); if (allBatchesExitCodes) { - log(yellow('Some tests have failed on'), cyan('beta'), - yellow('browsers.')); - log(yellow('This is not currently a fatal error, but will become an'), - yellow('error once the beta browsers are released as next stable'), - yellow('version!')); + log( + yellow('Some tests have failed on'), + cyan('beta'), + yellow('browsers.') + ); + log( + yellow('This is not currently a fatal error, but will become an'), + yellow('error once the beta browsers are released as next stable'), + yellow('version!') + ); } } @@ -432,13 +509,22 @@ async function runTests() { let endIndex = batchSize; const batchExitCodes = []; - log(green('Running tests on'), cyan(browsers.length), - green('Sauce Labs'), cyan(batchName), green('browser(s)...')); + log( + green('Running tests on'), + cyan(browsers.length), + green('Sauce Labs'), + cyan(batchName), + green('browser(s)...') + ); while (startIndex < endIndex) { const configBatch = Object.assign({}, c); configBatch.browsers = browsers.slice(startIndex, endIndex); - log(green('Batch'), cyan(`#${batch}`) + green(': Running tests on'), - cyan(configBatch.browsers.length), green('Sauce Labs browser(s)...')); + log( + green('Batch'), + cyan(`#${batch}`) + green(': Running tests on'), + cyan(configBatch.browsers.length), + green('Sauce Labs browser(s)...') + ); batchExitCodes.push(await createKarmaServer(configBatch)); startIndex = batch * batchSize; batch++; @@ -455,79 +541,101 @@ async function runTests() { */ function createKarmaServer(configBatch) { let resolver; - const deferred = new Promise(resolverIn => {resolver = resolverIn;}); + const deferred = new Promise(resolverIn => { + resolver = resolverIn; + }); new Karma(configBatch, function(exitCode) { if (argv.coverage) { if (isTravisBuild()) { const codecovCmd = - './node_modules/.bin/codecov --file=test/coverage/lcov.info'; + './node_modules/.bin/codecov --file=test/coverage/lcov.info'; let flags = ''; if (argv.unit) { flags = ' --flags=unit_tests'; } else if (argv.integration) { flags = ' --flags=integration_tests'; } - log(green('INFO:'), 'Uploading code coverage report to', - cyan('https://codecov.io/gh/ampproject/amphtml'), 'by running', - cyan(codecovCmd + flags) + '...'); + log( + green('INFO:'), + 'Uploading code coverage report to', + cyan('https://codecov.io/gh/ampproject/amphtml'), + 'by running', + cyan(codecovCmd + flags) + '...' + ); const output = getStdout(codecovCmd + flags); const viewReportPrefix = 'View report at: '; const viewReport = output.match(`${viewReportPrefix}.*`); if (viewReport && viewReport.length > 0) { - log(green('INFO:'), viewReportPrefix + - cyan(viewReport[0].replace(viewReportPrefix, ''))); + log( + green('INFO:'), + viewReportPrefix + + cyan(viewReport[0].replace(viewReportPrefix, '')) + ); } else { - log(yellow('WARNING:'), - 'Code coverage report upload may have failed:\n', - yellow(output)); + log( + yellow('WARNING:'), + 'Code coverage report upload may have failed:\n', + yellow(output) + ); } } else { const coverageReportUrl = - 'file://' + path.resolve('test/coverage/index.html'); - log(green('INFO:'), 'Generated code coverage report at', - cyan(coverageReportUrl)); + 'file://' + path.resolve('test/coverage/index.html'); + log( + green('INFO:'), + 'Generated code coverage report at', + cyan(coverageReportUrl) + ); opn(coverageReportUrl, {wait: false}); } } resolver(exitCode); - }).on('run_start', function() { - if (!argv.saucelabs && !argv.saucelabs_lite) { - log(green('Running tests locally...')); - } - reportTestStarted(); - }).on('browsers_ready', function() { - console./*OK*/log('\n'); - log(green('Done. Running tests...')); - }).on('browser_complete', function(browser) { - const result = browser.lastResult; - // Prevent cases where Karma detects zero tests and still passes. #16851. - if (result.total == 0) { - log(red('ERROR: Zero tests detected by Karma. Something went wrong.')); - reportTestErrored().finally(() => { - if (!argv.watch) { - process.exit(1); - } - }); - } - // Print a summary for each browser as soon as tests complete. - let message = `${browser.name}: Executed ` + + }) + .on('run_start', function() { + if (!argv.saucelabs && !argv.saucelabs_lite) { + log(green('Running tests locally...')); + } + reportTestStarted(); + }) + .on('browsers_ready', function() { + console./*OK*/ log('\n'); + log(green('Done. Running tests...')); + }) + .on('browser_complete', function(browser) { + const result = browser.lastResult; + // Prevent cases where Karma detects zero tests and still passes. #16851. + if (result.total == 0) { + log( + red('ERROR: Zero tests detected by Karma. Something went wrong.') + ); + reportTestErrored().finally(() => { + if (!argv.watch) { + process.exit(1); + } + }); + } + // Print a summary for each browser as soon as tests complete. + let message = + `${browser.name}: Executed ` + `${result.success + result.failed} of ${result.total} ` + `(Skipped ${result.skipped}) `; - if (result.failed === 0) { - message += green('SUCCESS'); - } else { - message += red(result.failed + ' FAILED'); - } - message += '\n'; - console./*OK*/log('\n'); - log(message); - }).on('run_complete', (browsers, results) => { - if (results.error) { - reportTestErrored(); - } else { - reportTestFinished(results.success, results.failed); - } - }).start(); + if (result.failed === 0) { + message += green('SUCCESS'); + } else { + message += red(result.failed + ' FAILED'); + } + message += '\n'; + console./*OK*/ log('\n'); + log(message); + }) + .on('run_complete', (browsers, results) => { + if (results.error) { + reportTestErrored(); + } else { + reportTestFinished(results.success, results.failed); + } + }) + .start(); return deferred; } } @@ -535,40 +643,47 @@ async function runTests() { /** * Run tests after applying the prod / canary AMP config to the runtime. */ -gulp.task('test', 'Runs tests', preTestTasks, function() { - // TODO(alanorozco): Come up with a more elegant check? - global.AMP_TESTING = true; - - if (!argv.nohelp) { - printArgvMessages(); - } - return runTests(); -}, { - options: { - 'verbose': ' With logging enabled', - 'testnames': ' Lists the name of each test being run', - 'watch': ' Watches for changes in files, runs corresponding test(s)', - 'saucelabs': ' Runs integration tests on saucelabs (requires setup)', - 'saucelabs_lite': ' Runs tests on a subset of saucelabs browsers ' + - '(requires setup)', - 'safari': ' Runs tests on Safari', - 'firefox': ' Runs tests on Firefox', - 'edge': ' Runs tests on Edge', - 'ie': ' Runs tests on IE', - 'chrome_canary': 'Runs tests on Chrome Canary', - 'chrome_flags': - 'Uses the given flags to launch Chrome', - 'unit': ' Run only unit tests.', - 'integration': ' Run only integration tests.', - 'compiled': ' Changes integration tests to use production JS ' + +gulp.task( + 'test', + 'Runs tests', + preTestTasks, + function() { + // TODO(alanorozco): Come up with a more elegant check? + global.AMP_TESTING = true; + + if (!argv.nohelp) { + printArgvMessages(); + } + return runTests(); + }, + { + options: { + 'verbose': ' With logging enabled', + 'testnames': ' Lists the name of each test being run', + 'watch': ' Watches for changes in files, runs corresponding test(s)', + 'saucelabs': ' Runs integration tests on saucelabs (requires setup)', + 'saucelabs_lite': + ' Runs tests on a subset of saucelabs browsers ' + '(requires setup)', + 'safari': ' Runs tests on Safari', + 'firefox': ' Runs tests on Firefox', + 'edge': ' Runs tests on Edge', + 'ie': ' Runs tests on IE', + 'chrome_canary': 'Runs tests on Chrome Canary', + 'chrome_flags': 'Uses the given flags to launch Chrome', + 'unit': ' Run only unit tests.', + 'integration': ' Run only integration tests.', + 'compiled': + ' Changes integration tests to use production JS ' + 'binaries for execution', - 'grep': ' Runs tests that match the pattern', - 'files': ' Runs tests for specific files', - 'nohelp': ' Silence help messages that are printed prior to test run', - 'a4a': ' Runs all A4A tests', - 'coverage': ' Run tests in code coverage mode', - 'headless': ' Run tests in a headless Chrome window', - 'local-changes': ' Run unit tests directly affected by the files ' + + 'grep': ' Runs tests that match the pattern', + 'files': ' Runs tests for specific files', + 'nohelp': ' Silence help messages that are printed prior to test run', + 'a4a': ' Runs all A4A tests', + 'coverage': ' Run tests in code coverage mode', + 'headless': ' Run tests in a headless Chrome window', + 'local-changes': + ' Run unit tests directly affected by the files ' + 'changed in the local branch', - }, -}); + }, + } +); diff --git a/build-system/tasks/runtime-test/status-report.js b/build-system/tasks/runtime-test/status-report.js index bf2afce707206..5ee90cd63952a 100644 --- a/build-system/tasks/runtime-test/status-report.js +++ b/build-system/tasks/runtime-test/status-report.js @@ -71,19 +71,33 @@ function postReport(type, action) { if (type !== null && isTravisPullRequestBuild()) { const commitHash = gitCommitHash(); const postUrl = `${reportBaseUrl}/${commitHash}/${type}/${action}`; - return requestPromise.post(postUrl) - .then(body => { - log(green('INFO:'), 'reported', cyan(`${type}/${action}`), - 'to the test-status GitHub App'); - if (body.length > 0) { - log(green('INFO:'), 'response from test-status was', - cyan(body.substr(0, 100))); - } - }).catch(error => { - log(yellow('WARNING:'), 'failed to report', cyan(`${type}/${action}`), - 'to the test-status GitHub App:\n', error.message.substr(0, 100)); - return; - }); + return requestPromise + .post(postUrl) + .then(body => { + log( + green('INFO:'), + 'reported', + cyan(`${type}/${action}`), + 'to the test-status GitHub App' + ); + if (body.length > 0) { + log( + green('INFO:'), + 'response from test-status was', + cyan(body.substr(0, 100)) + ); + } + }) + .catch(error => { + log( + yellow('WARNING:'), + 'failed to report', + cyan(`${type}/${action}`), + 'to the test-status GitHub App:\n', + error.message.substr(0, 100) + ); + return; + }); } return Promise.resolve(); } @@ -108,7 +122,8 @@ exports.reportAllExpectedTests = async buildTargets => { for (const [type, subTypes] of TEST_TYPE_SUBTYPES) { const testTypeBuildTargets = TEST_TYPE_BUILD_TARGETS.get(type); const action = testTypeBuildTargets.some(target => buildTargets.has(target)) - ? 'queued' : 'skipped'; + ? 'queued' + : 'skipped'; for (const subType of subTypes) { await postReport(`${type}/${subType}`, action); } diff --git a/build-system/tasks/serve.js b/build-system/tasks/serve.js index 02b0bb8a93268..2eedb47bb8563 100644 --- a/build-system/tasks/serve.js +++ b/build-system/tasks/serve.js @@ -86,20 +86,20 @@ process.on('SIGINT', function() { }); gulp.task( - 'serve', - 'Serves content in root dir over ' + getHost() + '/', - serve, - { - options: { - 'host': ' Hostname or IP address to bind to (default: localhost)', - 'port': ' Specifies alternative port (default: 8000)', - 'https': ' Use HTTPS server (default: false)', - 'quiet': ' Do not log HTTP requests (default: false)', - 'cache': ' Make local resources cacheable by the browser ' + - '(default: false)', - 'inspect': ' Run nodemon in `inspect` mode', - }, - } + 'serve', + 'Serves content in root dir over ' + getHost() + '/', + serve, + { + options: { + 'host': ' Hostname or IP address to bind to (default: localhost)', + 'port': ' Specifies alternative port (default: 8000)', + 'https': ' Use HTTPS server (default: false)', + 'quiet': ' Do not log HTTP requests (default: false)', + 'cache': + ' Make local resources cacheable by the browser ' + '(default: false)', + 'inspect': ' Run nodemon in `inspect` mode', + }, + } ); function getHost() { diff --git a/build-system/tasks/size.js b/build-system/tasks/size.js index 1c5cac0ed8701..b9551efe6a842 100644 --- a/build-system/tasks/size.js +++ b/build-system/tasks/size.js @@ -24,7 +24,6 @@ const prettyBytes = require('pretty-bytes'); const table = require('text-table'); const through = require('through2'); - const tempFolderName = '__size-temp'; const MIN_FILE_SIZE_POS = 0; @@ -97,11 +96,19 @@ function normalizeRows(rows) { // normalize integration.js normalizeRow(rows, 'current-min/f.js', 'current/integration.js', true); - normalizeRow(rows, 'current-min/ampcontext-v0.js', - 'current/ampcontext-lib.js', true); + normalizeRow( + rows, + 'current-min/ampcontext-v0.js', + 'current/ampcontext-lib.js', + true + ); - normalizeRow(rows, 'current-min/iframe-transport-client-v0.js', - 'current/iframe-transport-client-lib.js', true); + normalizeRow( + rows, + 'current-min/iframe-transport-client-v0.js', + 'current/iframe-transport-client-lib.js', + true + ); // normalize alp.js normalizeRow(rows, 'alp.js', 'alp.max.js', true); @@ -142,13 +149,17 @@ function normalizeRows(rows) { */ function normalizeExtension(rows, filename) { const isMax = /\.max\.js$/.test(filename); - const counterpartName = filename.replace(/(v0\/.*?)(\.max)?(\.js)$/, - function(full, grp1, grp2, grp3) { - if (isMax) { - return grp1 + grp3; - } - return full; - }); + const counterpartName = filename.replace(/(v0\/.*?)(\.max)?(\.js)$/, function( + full, + grp1, + grp2, + grp3 + ) { + if (isMax) { + return grp1 + grp3; + } + return full; + }); if (isMax) { normalizeRow(rows, counterpartName, filename, false); @@ -198,7 +209,8 @@ function onFileThroughEnd(rows, cb) { rows = normalizeRows(rows); rows.unshift.apply(rows, tableHeaders); const tbl = table(rows, tableOptions); - console/* OK*/.log(tbl); + console /* OK*/ + .log(tbl); fs.writeFileSync('test/size.txt', tbl); cb(); } @@ -211,8 +223,8 @@ function onFileThroughEnd(rows, cb) { function sizer() { const rows = []; return through.obj( - onFileThrough.bind(null, rows), - onFileThroughEnd.bind(null, rows) + onFileThrough.bind(null, rows), + onFileThroughEnd.bind(null, rows) ); } @@ -222,16 +234,17 @@ function sizer() { * output from the process. */ function sizeTask() { - gulp.src([ - 'dist/**/*.js', - '!dist/**/*-latest.js', - '!dist/**/*check-types.js', - '!dist/**/amp-viewer-host.max.js', - 'dist.3p/{current,current-min}/**/*.js', - ]) - .pipe(sizer()) - .pipe(gulp.dest(tempFolderName)) - .on('end', del.bind(null, [tempFolderName])); + gulp + .src([ + 'dist/**/*.js', + '!dist/**/*-latest.js', + '!dist/**/*check-types.js', + '!dist/**/amp-viewer-host.max.js', + 'dist.3p/{current,current-min}/**/*.js', + ]) + .pipe(sizer()) + .pipe(gulp.dest(tempFolderName)) + .on('end', del.bind(null, [tempFolderName])); } gulp.task('size', 'Runs a report on artifact size', sizeTask); diff --git a/build-system/tasks/todos.js b/build-system/tasks/todos.js index ce24fff6d0bf8..09f177f86c73e 100644 --- a/build-system/tasks/todos.js +++ b/build-system/tasks/todos.js @@ -28,7 +28,6 @@ const {GITHUB_ACCESS_TOKEN} = process.env; /** @type {!Object>} */ const issueCache = Object.create(null); - /** * Test if a file's contents contains closed TODOs. * @@ -56,17 +55,18 @@ function findClosedTodosInFile(file) { if (promises.length == 0) { return Promise.resolve(0); } - return Promise.all(promises).then(results => { - return results.reduce(function(acc, v) { - return acc + v; - }, 0); - }).catch(function(error) { - log(colors.red('Failed in', file.path, error, error.stack)); - return 0; - }); + return Promise.all(promises) + .then(results => { + return results.reduce(function(acc, v) { + return acc + v; + }, 0); + }) + .catch(function(error) { + log(colors.red('Failed in', file.path, error, error.stack)); + return 0; + }); } - /** * @param {!File} file file is a vinyl file object * @param {string} issueId @@ -77,18 +77,18 @@ function reportClosedIssue(file, issueId, todo) { if (issueCache[issueId] !== undefined) { return issueCache[issueId]; } - return issueCache[issueId] = githubRequest('/issues/' + issueId) - .then(response => { - const issue = JSON.parse(response.body); - const value = issue.state == 'closed' ? 1 : 0; - if (value) { - log(colors.red(todo, 'in', file.path)); - } - return value; - }); + return (issueCache[issueId] = githubRequest('/issues/' + issueId).then( + response => { + const issue = JSON.parse(response.body); + const value = issue.state == 'closed' ? 1 : 0; + if (value) { + log(colors.red(todo, 'in', file.path)); + } + return value; + } + )); } - /** * @param {string} path * @param {string=} opt_method @@ -116,26 +116,27 @@ function githubRequest(path, opt_method, opt_data) { return request(options); } - /** * todos:find-closed task. */ function findClosedTodosTask() { let foundCount = 0; - return gulp.src(srcGlobs) - .pipe(through2.obj(function(file, enc, cb) { + return gulp + .src(srcGlobs) + .pipe( + through2.obj(function(file, enc, cb) { findClosedTodosInFile(file).then(function(count) { foundCount += count; cb(); }); - })) - .on('end', function() { - if (foundCount > 0) { - log(colors.red('Found closed TODOs: ', foundCount)); - process.exit(1); - } - }); + }) + ) + .on('end', function() { + if (foundCount > 0) { + log(colors.red('Found closed TODOs: ', foundCount)); + process.exit(1); + } + }); } - gulp.task('todos:find-closed', 'Find closed TODOs', findClosedTodosTask); diff --git a/build-system/tasks/update-packages.js b/build-system/tasks/update-packages.js index 58421d2e0d2e6..9702b7e579fe2 100644 --- a/build-system/tasks/update-packages.js +++ b/build-system/tasks/update-packages.js @@ -30,8 +30,7 @@ const yarnExecutable = 'npx yarn'; * @param {string} file Contents to write */ function writeIfUpdated(patchedName, file) { - if (!fs.existsSync(patchedName) || - fs.readFileSync(patchedName) != file) { + if (!fs.existsSync(patchedName) || fs.readFileSync(patchedName) != file) { fs.writeFileSync(patchedName, file); if (!isTravisBuild()) { log(colors.green('Patched'), colors.cyan(patchedName)); @@ -64,11 +63,11 @@ function replaceInFile(filePath, newFilePath, ...args) { */ function patchWebAnimations() { // Copies web-animations-js into a new file that has an export. - const patchedName = 'node_modules/web-animations-js/' + - 'web-animations.install.js'; - let file = fs.readFileSync( - 'node_modules/web-animations-js/' + - 'web-animations.min.js').toString(); + const patchedName = + 'node_modules/web-animations-js/' + 'web-animations.install.js'; + let file = fs + .readFileSync('node_modules/web-animations-js/' + 'web-animations.min.js') + .toString(); // Replace |requestAnimationFrame| with |window|. file = file.replace(/requestAnimationFrame/g, function(a, b) { if (file.charAt(b - 1) == '.') { @@ -87,11 +86,12 @@ function patchWebAnimations() { file = file.replace(/this\._isFinished\s*=\s*\!0,/, ''); // Wrap the contents inside the install function. - file = 'export function installWebAnimations(window) {\n' + - 'var document = window.document;\n' + - file + - '\n' + - '}\n'; + file = + 'export function installWebAnimations(window) {\n' + + 'var document = window.document;\n' + + file + + '\n' + + '}\n'; writeIfUpdated(patchedName, file); } @@ -106,14 +106,15 @@ function patchRegisterElement() { // compilation: https://github.com/google/closure-compiler/issues/1831 const dir = 'node_modules/document-register-element/build/'; replaceInFile( - dir + 'document-register-element.node.js', - dir + 'document-register-element.patched.js', - // Elimate the immediate side effect. - 'installCustomElements(global);', - '', - // Replace CJS export with ES6 export. - 'module.exports = installCustomElements;', - 'export {installCustomElements};'); + dir + 'document-register-element.node.js', + dir + 'document-register-element.patched.js', + // Elimate the immediate side effect. + 'installCustomElements(global);', + '', + // Replace CJS export with ES6 export. + 'module.exports = installCustomElements;', + 'export {installCustomElements};' + ); } /** @@ -123,8 +124,9 @@ function patchRegisterElement() { function patchWorkerDom() { const dir = 'node_modules/@ampproject/worker-dom/dist/'; fs.copyFileSync( - dir + 'unminified.index.safe.mjs', - dir + 'unminified.index.safe.mjs.patched.js'); + dir + 'unminified.index.safe.mjs', + dir + 'unminified.index.safe.mjs.patched.js' + ); } /** @@ -148,8 +150,10 @@ function transformEs6Packages() { const updatedPackageJson = JSON.stringify(packageJson, null, 2); fs.writeFileSync(packageJsonFile, updatedPackageJson, 'utf8'); if (!isTravisBuild()) { - log(colors.green('Enabled ES6 transforms for runtime dependency'), - colors.cyan(es6Package)); + log( + colors.green('Enabled ES6 transforms for runtime dependency'), + colors.cyan(es6Package) + ); } } }); @@ -176,9 +180,13 @@ function installCustomEslintRules() { function runYarnCheck() { const integrityCmd = yarnExecutable + ' check --integrity'; if (getStderr(integrityCmd).trim() != '') { - log(colors.yellow('WARNING:'), 'The packages in', - colors.cyan('node_modules'), 'do not match', - colors.cyan('package.json.')); + log( + colors.yellow('WARNING:'), + 'The packages in', + colors.cyan('node_modules'), + 'do not match', + colors.cyan('package.json.') + ); const verifyTreeCmd = yarnExecutable + ' check --verify-tree'; exec(verifyTreeCmd); log('Running', colors.cyan('yarn'), 'to update packages...'); @@ -189,8 +197,11 @@ function runYarnCheck() { */ execOrDie(`${yarnExecutable} install --production=false`); // Stop execution when Ctrl + C is detected. } else { - log(colors.green('All packages in'), - colors.cyan('node_modules'), colors.green('are up to date.')); + log( + colors.green('All packages in'), + colors.cyan('node_modules'), + colors.green('are up to date.') + ); } } @@ -210,7 +221,7 @@ function updatePackages() { } gulp.task( - 'update-packages', - 'Runs yarn if node_modules is out of date, and patches web-animations-js', - updatePackages + 'update-packages', + 'Runs yarn if node_modules is out of date, and patches web-animations-js', + updatePackages ); diff --git a/build-system/tasks/validator.js b/build-system/tasks/validator.js index bbe95009f3a6b..9592be8508d75 100644 --- a/build-system/tasks/validator.js +++ b/build-system/tasks/validator.js @@ -39,5 +39,8 @@ function validatorWebui() { } gulp.task('validator', 'Builds and tests the AMP validator.', validator); -gulp.task('validator-webui', 'Builds and tests the AMP validator web UI.', - validatorWebui); +gulp.task( + 'validator-webui', + 'Builds and tests the AMP validator web UI.', + validatorWebui +); diff --git a/build-system/tasks/visual-diff/helpers.js b/build-system/tasks/visual-diff/helpers.js index fc9d249b7bab6..6099abefa05b1 100644 --- a/build-system/tasks/visual-diff/helpers.js +++ b/build-system/tasks/visual-diff/helpers.js @@ -23,7 +23,7 @@ const {isTravisBuild} = require('../../travis'); const CSS_SELECTOR_RETRY_MS = 200; const CSS_SELECTOR_RETRY_ATTEMPTS = 50; const CSS_SELECTOR_TIMEOUT_MS = - CSS_SELECTOR_RETRY_MS * CSS_SELECTOR_RETRY_ATTEMPTS; + CSS_SELECTOR_RETRY_MS * CSS_SELECTOR_RETRY_ATTEMPTS; /** * Logs a message to the console. @@ -70,13 +70,18 @@ function log(mode, ...messages) { * @throws {Error} an encountered error. */ async function verifySelectorsInvisible(page, testName, selectors) { - log('verbose', 'Waiting for invisibility of all:', - colors.cyan(selectors.join(', '))); + log( + 'verbose', + 'Waiting for invisibility of all:', + colors.cyan(selectors.join(', ')) + ); for (const selector of selectors) { if (!(await waitForElementVisibility(page, selector, {hidden: true}))) { - throw new Error(`${colors.cyan(testName)} | An element with the CSS ` + + throw new Error( + `${colors.cyan(testName)} | An element with the CSS ` + `selector ${colors.cyan(selector)} is still visible after ` + - `${CSS_SELECTOR_TIMEOUT_MS} ms`); + `${CSS_SELECTOR_TIMEOUT_MS} ms` + ); } } } @@ -91,22 +96,32 @@ async function verifySelectorsInvisible(page, testName, selectors) { * @throws {Error} an encountered error. */ async function verifySelectorsVisible(page, testName, selectors) { - log('verbose', 'Waiting for existence of all:', - colors.cyan(selectors.join(', '))); + log( + 'verbose', + 'Waiting for existence of all:', + colors.cyan(selectors.join(', ')) + ); for (const selector of selectors) { if (!(await waitForSelectorExistence(page, selector))) { - throw new Error(`${colors.cyan(testName)} | The CSS selector ` + - `${colors.cyan(selector)} does not match any elements in the page`); + throw new Error( + `${colors.cyan(testName)} | The CSS selector ` + + `${colors.cyan(selector)} does not match any elements in the page` + ); } } - log('verbose', 'Waiting for visibility of all:', - colors.cyan(selectors.join(', '))); + log( + 'verbose', + 'Waiting for visibility of all:', + colors.cyan(selectors.join(', ')) + ); for (const selector of selectors) { if (!(await waitForElementVisibility(page, selector, {visible: true}))) { - throw new Error(`${colors.cyan(testName)} | An element with the CSS ` + + throw new Error( + `${colors.cyan(testName)} | An element with the CSS ` + `selector ${colors.cyan(selector)} is still invisible after ` + - `${CSS_SELECTOR_TIMEOUT_MS} ms`); + `${CSS_SELECTOR_TIMEOUT_MS} ms` + ); } } } @@ -120,10 +135,15 @@ async function verifySelectorsVisible(page, testName, selectors) { */ async function waitForLoaderDots(page, testName) { const allLoaderDotsGone = await waitForElementVisibility( - page, '.i-amphtml-loader-dot', {hidden: true}); + page, + '.i-amphtml-loader-dot', + {hidden: true} + ); if (!allLoaderDotsGone) { - throw new Error(`${colors.cyan(testName)} still has the AMP loader dot ` + - `after ${CSS_SELECTOR_TIMEOUT_MS} ms`); + throw new Error( + `${colors.cyan(testName)} still has the AMP loader dot ` + + `after ${CSS_SELECTOR_TIMEOUT_MS} ms` + ); } } @@ -139,8 +159,11 @@ async function waitForElementVisibility(page, selector, options) { const waitForVisible = Boolean(options['visible']); const waitForHidden = Boolean(options['hidden']); if (waitForVisible == waitForHidden) { - log('fatal', 'waitForElementVisibility must be called with exactly one of', - "'visible' or 'hidden' set to true."); + log( + 'fatal', + 'waitForElementVisibility must be called with exactly one of', + "'visible' or 'hidden' set to true." + ); } let attempt = 0; @@ -149,24 +172,36 @@ async function waitForElementVisibility(page, selector, options) { for (const elementHandle of await page.$$(selector)) { const boundingBox = await elementHandle.boundingBox(); - const elementIsVisible = boundingBox != null && boundingBox.height > 0 && - boundingBox.width > 0; + const elementIsVisible = + boundingBox != null && boundingBox.height > 0 && boundingBox.width > 0; elementsAreVisible.push(elementIsVisible); } if (elementsAreVisible.length) { - log('verbose', 'Found', colors.cyan(elementsAreVisible.length), - 'element(s) matching the CSS selector', colors.cyan(selector)); - log('verbose', 'Expecting all element visibilities to be', - colors.cyan(waitForVisible), '; they are', - colors.cyan(elementsAreVisible)); + log( + 'verbose', + 'Found', + colors.cyan(elementsAreVisible.length), + 'element(s) matching the CSS selector', + colors.cyan(selector) + ); + log( + 'verbose', + 'Expecting all element visibilities to be', + colors.cyan(waitForVisible), + '; they are', + colors.cyan(elementsAreVisible) + ); } else { log('verbose', 'No', colors.cyan(selector), 'matches found'); } // Since we assert that waitForVisible == !waitForHidden, there is no need // to check equality to both waitForVisible and waitForHidden. - if (elementsAreVisible.every( - elementIsVisible => elementIsVisible == waitForVisible)) { + if ( + elementsAreVisible.every( + elementIsVisible => elementIsVisible == waitForVisible + ) + ) { return true; } diff --git a/build-system/tasks/visual-diff/index.js b/build-system/tasks/visual-diff/index.js index 1c5b64fbdca9f..6c114f4919d25 100644 --- a/build-system/tasks/visual-diff/index.js +++ b/build-system/tasks/visual-diff/index.js @@ -58,21 +58,30 @@ const NAVIGATE_RETRIES = 3; const NAVIGATE_RETRY_TIMEOUT_MS = 2000; const MAX_PARALLEL_TABS = 10; const WAIT_FOR_TABS_MS = 1000; -const BUILD_STATUS_URL = 'https://amphtml-percy-status-checker.appspot.com/status'; +const BUILD_STATUS_URL = + 'https://amphtml-percy-status-checker.appspot.com/status'; const ROOT_DIR = path.resolve(__dirname, '../../../'); // JavaScript snippets that execute inside the page. const WRAP_IN_IFRAME_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/iframe-wrapper.js'), 'utf8'); + path.resolve(__dirname, 'snippets/iframe-wrapper.js'), + 'utf8' +); const REMOVE_AMP_SCRIPTS_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/remove-amp-scripts.js'), 'utf8'); + path.resolve(__dirname, 'snippets/remove-amp-scripts.js'), + 'utf8' +); const FREEZE_FORM_VALUE_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/freeze-form-values.js'), 'utf8'); + path.resolve(__dirname, 'snippets/freeze-form-values.js'), + 'utf8' +); // HTML snippet to create an error page snapshot. const SNAPSHOT_ERROR_SNIPPET = fs.readFileSync( - path.resolve(__dirname, 'snippets/snapshot-error.html'), 'utf8'); + path.resolve(__dirname, 'snippets/snapshot-error.html'), + 'utf8' +); let browser_; let webServerProcess_; @@ -94,8 +103,7 @@ function maybeOverridePercyEnvironmentVariables() { * as baselines for future builds. */ function setPercyBranch() { - if (!process.env['PERCY_BRANCH'] && - (!argv.master || !isTravisBuild())) { + if (!process.env['PERCY_BRANCH'] && (!argv.master || !isTravisBuild())) { const userName = gitCommitterEmail(); const branchName = gitBranchName(); process.env['PERCY_BRANCH'] = userName + '-' + branchName; @@ -129,18 +137,22 @@ function setPercyTargetCommit() { */ async function launchWebServer() { webServerProcess_ = execScriptAsync( - `gulp serve --host ${HOST} --port ${PORT} ${process.env.WEBSERVER_QUIET}`, - { - stdio: argv.webserver_debug ? - ['ignore', process.stdout, process.stderr] : - 'ignore', - }); + `gulp serve --host ${HOST} --port ${PORT} ${process.env.WEBSERVER_QUIET}`, + { + stdio: argv.webserver_debug + ? ['ignore', process.stdout, process.stderr] + : 'ignore', + } + ); webServerProcess_.on('close', code => { code = code || 0; if (code != 0) { - log('fatal', colors.cyan("'serve'"), - `errored with code ${code}. Cannot continue with visual diff tests`); + log( + 'fatal', + colors.cyan("'serve'"), + `errored with code ${code}. Cannot continue with visual diff tests` + ); } }); @@ -153,9 +165,11 @@ async function launchWebServer() { host: HOST, port: PORT, retries: WEBSERVER_TIMEOUT_RETRIES, // retry timeout defaults to 1 sec - }).on('connected', () => { - return resolver(webServerProcess_); - }).on('timeout', rejecter); + }) + .on('connected', () => { + return resolver(webServerProcess_); + }) + .on('timeout', rejecter); return deferred; } @@ -215,8 +229,11 @@ async function newPage(browser, viewport = null) { const width = viewport ? viewport.width : VIEWPORT_WIDTH; const height = viewport ? viewport.height : VIEWPORT_HEIGHT; - log('verbose', 'Creating new page with viewport size of', - colors.yellow(`${width}×${height}`)); + log( + 'verbose', + 'Creating new page with viewport size of', + colors.yellow(`${width}×${height}`) + ); const page = await browser.newPage(); await page.setViewport({width, height}); @@ -226,22 +243,34 @@ async function newPage(browser, viewport = null) { page.on('request', interceptedRequest => { const requestUrl = new URL(interceptedRequest.url()); const mockedFilepath = path.join( - path.dirname(__filename), 'network-mocks', requestUrl.hostname, - encodeURIComponent( - `${requestUrl.pathname.substr(1)}${requestUrl.search}`) - .replace(/%2F/g, '/')); - - if (requestUrl.hostname == HOST || - requestUrl.hostname.endsWith(`.${HOST}`)) { + path.dirname(__filename), + 'network-mocks', + requestUrl.hostname, + encodeURIComponent( + `${requestUrl.pathname.substr(1)}${requestUrl.search}` + ).replace(/%2F/g, '/') + ); + + if ( + requestUrl.hostname == HOST || + requestUrl.hostname.endsWith(`.${HOST}`) + ) { return interceptedRequest.continue(); } else if (fs.existsSync(mockedFilepath)) { - log('verbose', 'Mocked network request for', - colors.yellow(requestUrl.href), 'with file', - colors.cyan(mockedFilepath)); + log( + 'verbose', + 'Mocked network request for', + colors.yellow(requestUrl.href), + 'with file', + colors.cyan(mockedFilepath) + ); return interceptedRequest.respond(fs.readFileSync(mockedFilepath)); } else { - log('verbose', 'Blocked external network request for', - colors.yellow(requestUrl.href)); + log( + 'verbose', + 'Blocked external network request for', + colors.yellow(requestUrl.href) + ); return interceptedRequest.abort('blockedbyclient'); } }); @@ -271,9 +300,15 @@ function addTestError(testErrors, name, message, error, fatal) { * @param {!JsonObject} testError object as created by addTestError. */ function logTestError(testError) { - log(testError.fatal ? 'error' : 'warning', 'Error in test', - colors.yellow(testError.name), '\n ', testError.message, '\n ', - testError.error); + log( + testError.fatal ? 'error' : 'warning', + 'Error in test', + colors.yellow(testError.name), + '\n ', + testError.message, + '\n ', + testError.error + ); } /** @@ -292,8 +327,11 @@ async function runVisualTests(assetGlobs, webpages) { fs.writeFileSync('PERCY_BUILD_ID', buildId); log('info', 'Started Percy build', colors.cyan(buildId)); if (process.env['PERCY_TARGET_COMMIT']) { - log('info', 'The Percy build is baselined on top of commit', - colors.cyan(shortSha(process.env['PERCY_TARGET_COMMIT']))); + log( + 'info', + 'The Percy build is baselined on top of commit', + colors.cyan(shortSha(process.env['PERCY_TARGET_COMMIT'])) + ); } try { @@ -309,8 +347,12 @@ async function runVisualTests(assetGlobs, webpages) { if (status.state == 'failed') { log('fatal', 'Build', colors.cyan(buildId), 'failed!'); } else { - log('info', 'Build', colors.cyan(buildId), - 'is now being processed by Percy.'); + log( + 'info', + 'Build', + colors.cyan(buildId), + 'is now being processed by Percy.' + ); } } @@ -348,13 +390,22 @@ async function generateSnapshots(percy, webpages) { const numUnfilteredPages = webpages.length; webpages = webpages.filter(webpage => !webpage.flaky); if (numUnfilteredPages != webpages.length) { - log('info', 'Skipping', colors.cyan(numUnfilteredPages - webpages.length), - 'flaky pages'); + log( + 'info', + 'Skipping', + colors.cyan(numUnfilteredPages - webpages.length), + 'flaky pages' + ); } if (argv.grep) { webpages = webpages.filter(webpage => argv.grep.test(webpage.name)); - log('info', colors.cyan(`--grep ${argv.grep}`), 'matched', - colors.cyan(webpages.length), 'pages'); + log( + 'info', + colors.cyan(`--grep ${argv.grep}`), + 'matched', + colors.cyan(webpages.length), + 'pages' + ); } // Expand all the interactive tests. Every test should have a base test with @@ -362,34 +413,51 @@ async function generateSnapshots(percy, webpages) { // load those tests here. for (const webpage of webpages) { webpage.tests_ = { - '': async() => {}, + '': async () => {}, }; if (webpage.interactive_tests) { try { - Object.assign(webpage.tests_, - require(path.resolve(ROOT_DIR, webpage.interactive_tests))); + Object.assign( + webpage.tests_, + require(path.resolve(ROOT_DIR, webpage.interactive_tests)) + ); } catch (error) { - log('fatal', 'Failed to load interactive test', - colors.cyan(webpage.interactive_tests), 'for test', - colors.cyan(webpage.name), '\nError:', error); + log( + 'fatal', + 'Failed to load interactive test', + colors.cyan(webpage.interactive_tests), + 'for test', + colors.cyan(webpage.name), + '\nError:', + error + ); } } } const totalTests = webpages.reduce( - (numTests, webpage) => numTests + Object.keys(webpage.tests_).length, 0); + (numTests, webpage) => numTests + Object.keys(webpage.tests_).length, + 0 + ); if (!totalTests) { log('fatal', 'No pages left to test!'); } else { - log('info', 'Executing', colors.cyan(totalTests), 'visual diff tests on', - colors.cyan(webpages.length), 'pages'); + log( + 'info', + 'Executing', + colors.cyan(totalTests), + 'visual diff tests on', + colors.cyan(webpages.length), + 'pages' + ); } const browser = await launchBrowser(); if (argv.master) { const page = await newPage(browser); await page.goto( - `http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html`); + `http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html` + ); await percy.snapshot('Blank page', page, SNAPSHOT_SINGLE_BUILD_OPTIONS); } @@ -440,7 +508,7 @@ async function snapshotWebpages(percy, browser, webpages) { // to wait until there are no more network requests. This method is flaky // since Puppeteer doesn't always understand Chrome's network activity, so // ignore timeouts again. - pagePromises[name] = (async() => { + pagePromises[name] = (async () => { let lastNavigationError; for (let attempt = 0; attempt < NAVIGATE_RETRIES; attempt++) { try { @@ -448,105 +516,138 @@ async function snapshotWebpages(percy, browser, webpages) { } catch (navigationError) { hasWarnings = true; lastNavigationError = navigationError; - addTestError(testErrors, name, - 'The browser test runner failed on attempt number ' + - (attempt + 1) + ' to navigate to the test page', - navigationError, /* fatal */ false); + addTestError( + testErrors, + name, + 'The browser test runner failed on attempt number ' + + (attempt + 1) + + ' to navigate to the test page', + navigationError, + /* fatal */ false + ); // Exponential backoff. if (attempt < NAVIGATE_RETRIES) { - await sleep(NAVIGATE_RETRY_TIMEOUT_MS * (Math.pow(2, attempt))); + await sleep(NAVIGATE_RETRY_TIMEOUT_MS * Math.pow(2, attempt)); } } } throw lastNavigationError; })() - .then(() => { - log('verbose', 'Page navigation of test', colors.yellow(name), - 'is done, verifying page'); - }) - .catch(navigationError => { - hasWarnings = true; - addTestError(testErrors, name, - 'The browser test runner failed to complete the navigation ' + - 'to the test page', navigationError, /* fatal */ false); - if (!isTravisBuild()) { - log('warning', 'Continuing to verify page regardless...'); - } - }) - .then(async() => { - // Visibility evaluations can only be performed on the active tab, - // even in the headless browser mode. - await page.bringToFront(); - - // Perform visibility checks: wait for all AMP built-in loader dots - // to disappear (i.e., all visible components are finished being - // layed out and external resources such as images are loaded and - // displayed), then, depending on the test configurations, wait for - // invisibility/visibility of specific elements that match the - // configured CSS selectors. - await waitForLoaderDots(page, name); - if (webpage.loading_incomplete_selectors) { - await verifySelectorsInvisible( - page, name, webpage.loading_incomplete_selectors); - } - if (webpage.loading_complete_selectors) { - await verifySelectorsVisible( - page, name, webpage.loading_complete_selectors); - } + .then(() => { + log( + 'verbose', + 'Page navigation of test', + colors.yellow(name), + 'is done, verifying page' + ); + }) + .catch(navigationError => { + hasWarnings = true; + addTestError( + testErrors, + name, + 'The browser test runner failed to complete the navigation ' + + 'to the test page', + navigationError, + /* fatal */ false + ); + if (!isTravisBuild()) { + log('warning', 'Continuing to verify page regardless...'); + } + }) + .then(async () => { + // Visibility evaluations can only be performed on the active tab, + // even in the headless browser mode. + await page.bringToFront(); + + // Perform visibility checks: wait for all AMP built-in loader dots + // to disappear (i.e., all visible components are finished being + // layed out and external resources such as images are loaded and + // displayed), then, depending on the test configurations, wait for + // invisibility/visibility of specific elements that match the + // configured CSS selectors. + await waitForLoaderDots(page, name); + if (webpage.loading_incomplete_selectors) { + await verifySelectorsInvisible( + page, + name, + webpage.loading_incomplete_selectors + ); + } + if (webpage.loading_complete_selectors) { + await verifySelectorsVisible( + page, + name, + webpage.loading_complete_selectors + ); + } - // Based on test configuration, wait for a specific amount of time. - if (webpage.loading_complete_delay_ms) { - log('verbose', 'Waiting', - colors.cyan(`${webpage.loading_complete_delay_ms}ms`), - 'for loading to complete'); - await sleep(webpage.loading_complete_delay_ms); - } + // Based on test configuration, wait for a specific amount of time. + if (webpage.loading_complete_delay_ms) { + log( + 'verbose', + 'Waiting', + colors.cyan(`${webpage.loading_complete_delay_ms}ms`), + 'for loading to complete' + ); + await sleep(webpage.loading_complete_delay_ms); + } - // Run any other custom code located in the test's interactive_tests - // file. If there is no interactive test, this defaults to an empty - // function. - await testFunction(page, name); - - // Execute post-scripts that clean up the page's HTML and send - // prepare it for snapshotting on Percy. See comments inside the - // snippet files for description of each. - await page.evaluate(REMOVE_AMP_SCRIPTS_SNIPPET); - await page.evaluate(FREEZE_FORM_VALUE_SNIPPET); - - // Create a default set of snapshot options for Percy and modify - // them based on the test's configuration. - const snapshotOptions = Object.assign({}, DEFAULT_SNAPSHOT_OPTIONS); - if (webpage.enable_percy_javascript) { - snapshotOptions.enableJavaScript = true; - } + // Run any other custom code located in the test's interactive_tests + // file. If there is no interactive test, this defaults to an empty + // function. + await testFunction(page, name); + + // Execute post-scripts that clean up the page's HTML and send + // prepare it for snapshotting on Percy. See comments inside the + // snippet files for description of each. + await page.evaluate(REMOVE_AMP_SCRIPTS_SNIPPET); + await page.evaluate(FREEZE_FORM_VALUE_SNIPPET); + + // Create a default set of snapshot options for Percy and modify + // them based on the test's configuration. + const snapshotOptions = Object.assign({}, DEFAULT_SNAPSHOT_OPTIONS); + if (webpage.enable_percy_javascript) { + snapshotOptions.enableJavaScript = true; + } - if (viewport) { - snapshotOptions.widths = [viewport.width]; - log('verbose', 'Wrapping viewport-constrained page in an iframe'); - await page.evaluate(WRAP_IN_IFRAME_SNIPPET - .replace(/__WIDTH__/g, viewport.width) - .replace(/__HEIGHT__/g, viewport.height)); - } + if (viewport) { + snapshotOptions.widths = [viewport.width]; + log('verbose', 'Wrapping viewport-constrained page in an iframe'); + await page.evaluate( + WRAP_IN_IFRAME_SNIPPET.replace( + /__WIDTH__/g, + viewport.width + ).replace(/__HEIGHT__/g, viewport.height) + ); + } - // Finally, send the snapshot to percy. - await percy.snapshot(name, page, snapshotOptions); - log('travis', hasWarnings ? colors.yellow('●') : colors.cyan('●')); - }) - .catch(async testError => { - log('travis', colors.red('●')); - addTestError(testErrors, name, 'Unknown failure in test page', - testError, /* fatal */ true); - - await page.setContent( - SNAPSHOT_ERROR_SNIPPET - .replace('__TEST_NAME__', name) - .replace('__TEST_ERROR__', testError)); - await percy.snapshot(name, page, SNAPSHOT_SINGLE_BUILD_OPTIONS); - }) - .finally(async() => { - await page.close(); - delete pagePromises[name]; - }); + // Finally, send the snapshot to percy. + await percy.snapshot(name, page, snapshotOptions); + log('travis', hasWarnings ? colors.yellow('●') : colors.cyan('●')); + }) + .catch(async testError => { + log('travis', colors.red('●')); + addTestError( + testErrors, + name, + 'Unknown failure in test page', + testError, + /* fatal */ true + ); + + await page.setContent( + SNAPSHOT_ERROR_SNIPPET.replace('__TEST_NAME__', name).replace( + '__TEST_ERROR__', + testError + ) + ); + await percy.snapshot(name, page, SNAPSHOT_SINGLE_BUILD_OPTIONS); + }) + .finally(async () => { + await page.close(); + delete pagePromises[name]; + }); } } @@ -556,11 +657,14 @@ async function snapshotWebpages(percy, browser, webpages) { log('travis', '\n'); if (isTravisBuild() && testErrors.length > 0) { testErrors.sort((a, b) => a.name.localeCompare(b.name)); - log('info', colors.yellow('Tests warnings and errors:'), - 'expand this section'); - console./*OK*/log('travis_fold:start:visual_tests\n'); + log( + 'info', + colors.yellow('Tests warnings and errors:'), + 'expand this section' + ); + console./*OK*/ log('travis_fold:start:visual_tests\n'); testErrors.forEach(logTestError); - console./*OK*/log('travis_fold:end:visual_tests'); + console./*OK*/ log('travis_fold:end:visual_tests'); } return testErrors.every(testError => !testError.fatal); } @@ -596,13 +700,15 @@ async function createEmptyBuild() { const percy = new Percy({ loaders: [ new PercyAssetsLoader( - [path.resolve(__dirname, blankAssetsDir)], ROOT_DIR), + [path.resolve(__dirname, blankAssetsDir)], + ROOT_DIR + ), ], }); await percy.startBuild(); - await page.goto( - `http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html`) - .then(() => {}, () => {}); + await page + .goto(`http://${HOST}:${PORT}/examples/visual-tests/blank-page/blank.html`) + .then(() => {}, () => {}); await percy.snapshot('Blank page', page, SNAPSHOT_SINGLE_BUILD_OPTIONS); await percy.finalizeBuild(); } @@ -633,10 +739,18 @@ async function visualDiff() { * Runs the AMP visual diff tests. */ async function performVisualTests() { - if (!argv.percy_disabled && - (!process.env.PERCY_PROJECT || !process.env.PERCY_TOKEN)) { - log('fatal', 'Could not find', colors.cyan('PERCY_PROJECT'), 'and', - colors.cyan('PERCY_TOKEN'), 'environment variables'); + if ( + !argv.percy_disabled && + (!process.env.PERCY_PROJECT || !process.env.PERCY_TOKEN) + ) { + log( + 'fatal', + 'Could not find', + colors.cyan('PERCY_PROJECT'), + 'and', + colors.cyan('PERCY_TOKEN'), + 'environment variables' + ); } setDebuggingLevel(); @@ -652,22 +766,32 @@ async function performVisualTests() { } else { // Load and parse the config. Use JSON5 due to JSON comments in file. const visualTestsConfig = JSON5.parse( - fs.readFileSync( - path.resolve(__dirname, '../../../test/visual-diff/visual-tests'), - 'utf8')); + fs.readFileSync( + path.resolve(__dirname, '../../../test/visual-diff/visual-tests'), + 'utf8' + ) + ); await runVisualTests( - visualTestsConfig.asset_globs, visualTestsConfig.webpages); + visualTestsConfig.asset_globs, + visualTestsConfig.webpages + ); } } async function ensureOrBuildAmpRuntimeInTestMode_() { if (argv.nobuild) { const isInTestMode = /AMP_CONFIG=\{(?:.+,)?"test":true\b/.test( - fs.readFileSync('dist/amp.js', 'utf8')); + fs.readFileSync('dist/amp.js', 'utf8') + ); if (!isInTestMode) { - log('fatal', 'The AMP runtime was not built in test mode. Run', - colors.cyan('gulp build --fortesting'), 'or remove the', - colors.cyan('--nobuild'), 'option from this command'); + log( + 'fatal', + 'The AMP runtime was not built in test mode. Run', + colors.cyan('gulp build --fortesting'), + 'or remove the', + colors.cyan('--nobuild'), + 'option from this command' + ); } } else { execOrDie('gulp build --fortesting'); @@ -677,8 +801,9 @@ async function ensureOrBuildAmpRuntimeInTestMode_() { function installPercy_() { if (!argv.noyarn) { log('info', 'Running', colors.cyan('yarn'), 'to install Percy...'); - execOrDie('npx yarn --cwd build-system/tasks/visual-diff', - {'stdio': 'ignore'}); + execOrDie('npx yarn --cwd build-system/tasks/visual-diff', { + 'stdio': 'ignore', + }); } puppeteer = require('puppeteer'); @@ -704,25 +829,20 @@ async function cleanup_() { } } -gulp.task( - 'visual-diff', - 'Runs the AMP visual diff tests.', - visualDiff, - { - options: { - 'master': ' Includes a blank snapshot (baseline for skipped builds)', - 'empty': ' Creates a dummy Percy build with only a blank snapshot', - 'chrome_debug': ' Prints debug info from Chrome', - 'webserver_debug': ' Prints debug info from the local gulp webserver', - 'debug': ' Prints all the above debug info', - 'grep': ' Runs tests that match the pattern', - 'percy_project': ' Override the PERCY_PROJECT environment variable', - 'percy_token': ' Override the PERCY_TOKEN environment variable', - 'percy_branch': ' Override the PERCY_BRANCH environment variable', - 'percy_disabled': - ' Disables Percy integration (for testing local changes only)', - 'nobuild': ' Skip build', - 'noyarn': ' Skip calling yarn to install dependencies', - }, - } -); +gulp.task('visual-diff', 'Runs the AMP visual diff tests.', visualDiff, { + options: { + 'master': ' Includes a blank snapshot (baseline for skipped builds)', + 'empty': ' Creates a dummy Percy build with only a blank snapshot', + 'chrome_debug': ' Prints debug info from Chrome', + 'webserver_debug': ' Prints debug info from the local gulp webserver', + 'debug': ' Prints all the above debug info', + 'grep': ' Runs tests that match the pattern', + 'percy_project': ' Override the PERCY_PROJECT environment variable', + 'percy_token': ' Override the PERCY_TOKEN environment variable', + 'percy_branch': ' Override the PERCY_BRANCH environment variable', + 'percy_disabled': + ' Disables Percy integration (for testing local changes only)', + 'nobuild': ' Skip build', + 'noyarn': ' Skip calling yarn to install dependencies', + }, +}); diff --git a/build-system/tasks/visual-diff/percy-assets-loader.js b/build-system/tasks/visual-diff/percy-assets-loader.js index 33e1a1f23a499..d4f96d2271815 100644 --- a/build-system/tasks/visual-diff/percy-assets-loader.js +++ b/build-system/tasks/visual-diff/percy-assets-loader.js @@ -65,14 +65,13 @@ class PercyAssetsLoader { assetFile = assetFile.replace(/\\/g, '/'); } - const content = fs.readFileSync(assetFile); resources.push( - percyClient.makeResource({ - resourceUrl: encodeURI(`/${assetFile}`), - content, - mimetype: mime.lookup(assetFile), - }), + percyClient.makeResource({ + resourceUrl: encodeURI(`/${assetFile}`), + content, + mimetype: mime.lookup(assetFile), + }) ); } } diff --git a/build-system/test-server.js b/build-system/test-server.js index c97626c999e84..a94907c160c21 100644 --- a/build-system/test-server.js +++ b/build-system/test-server.js @@ -30,10 +30,14 @@ function setCorsHeaders(req, res, next) { res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.query.__amp_source_origin) { - res.setHeader('Access-Control-Expose-Headers', - 'AMP-Access-Control-Allow-Source-Origin'); - res.setHeader('AMP-Access-Control-Allow-Source-Origin', - req.query.__amp_source_origin); + res.setHeader( + 'Access-Control-Expose-Headers', + 'AMP-Access-Control-Allow-Source-Origin' + ); + res.setHeader( + 'AMP-Access-Control-Allow-Source-Origin', + req.query.__amp_source_origin + ); } next(); } @@ -50,7 +54,6 @@ app.use('/redirect-to', function(req, res) { res.redirect(302, req.query.url); }); - app.use('/status/404', function(req, res) { res.status(404).end(); }); @@ -62,7 +65,7 @@ app.use('/status/500', function(req, res) { app.use('/cookies/set', function(req, res) { delete req.query.__amp_source_origin; for (const name in req.query) { - res./*OK*/cookie(name, req.query[name]); + res./*OK*/ cookie(name, req.query[name]); } res.json({ cookies: req.cookies || {}, @@ -88,11 +91,7 @@ app.use('/form/post/success', function(req, res) { delete req.query.__amp_source_origin; res.json({ name: 'John Miller', - interests: [ - {title: 'Football'}, - {title: 'Basketball'}, - {title: 'Writing'}, - ], + interests: [{title: 'Football'}, {title: 'Basketball'}, {title: 'Writing'}], }); }); @@ -118,5 +117,4 @@ app.use('/form/verify-error', function(req, res) { }); }); - exports.app = app; diff --git a/build-system/travis.js b/build-system/travis.js index b8d023338440e..50ef25ac49df7 100644 --- a/build-system/travis.js +++ b/build-system/travis.js @@ -37,8 +37,9 @@ exports.isTravisBuild = function() { * @return {boolean} */ exports.isTravisPullRequestBuild = function() { - return exports.isTravisBuild() && - process.env.TRAVIS_EVENT_TYPE === 'pull_request'; + return ( + exports.isTravisBuild() && process.env.TRAVIS_EVENT_TYPE === 'pull_request' + ); }; /** @@ -46,8 +47,7 @@ exports.isTravisPullRequestBuild = function() { * @return {boolean} */ exports.isTravisPushBuild = function() { - return exports.isTravisBuild() && - process.env.TRAVIS_EVENT_TYPE === 'push'; + return exports.isTravisBuild() && process.env.TRAVIS_EVENT_TYPE === 'push'; }; /** @@ -56,8 +56,11 @@ exports.isTravisPushBuild = function() { */ exports.travisBuildNumber = function() { if (!exports.isTravisBuild()) { - log(red('ERROR:'), 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_BUILD_NUMBER') + '.'); + log( + red('ERROR:'), + 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_BUILD_NUMBER') + '.' + ); } return process.env.TRAVIS_BUILD_NUMBER; }; @@ -68,8 +71,11 @@ exports.travisBuildNumber = function() { */ exports.travisJobNumber = function() { if (!exports.isTravisBuild()) { - log(red('ERROR:'), 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_JOB_NUMBER') + '.'); + log( + red('ERROR:'), + 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_JOB_NUMBER') + '.' + ); } return process.env.TRAVIS_JOB_NUMBER; }; @@ -80,8 +86,11 @@ exports.travisJobNumber = function() { */ exports.travisRepoSlug = function() { if (!exports.isTravisBuild()) { - log(red('ERROR:'), 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_REPO_SLUG') + '.'); + log( + red('ERROR:'), + 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_REPO_SLUG') + '.' + ); } return process.env.TRAVIS_REPO_SLUG; }; @@ -92,8 +101,11 @@ exports.travisRepoSlug = function() { */ exports.travisPullRequestSha = function() { if (!exports.isTravisPullRequestBuild()) { - log(red('ERROR:'), 'This is not a Travis PR build. Cannot get', - cyan('process.env.TRAVIS_PULL_REQUEST_SHA') + '.'); + log( + red('ERROR:'), + 'This is not a Travis PR build. Cannot get', + cyan('process.env.TRAVIS_PULL_REQUEST_SHA') + '.' + ); } return process.env.TRAVIS_PULL_REQUEST_SHA; }; @@ -104,8 +116,11 @@ exports.travisPullRequestSha = function() { */ exports.travisPullRequestBranch = function() { if (!exports.isTravisPullRequestBuild()) { - log(red('ERROR:'), 'This is not a Travis PR build. Cannot get', - cyan('process.env.TRAVIS_PULL_REQUEST_BRANCH') + '.'); + log( + red('ERROR:'), + 'This is not a Travis PR build. Cannot get', + cyan('process.env.TRAVIS_PULL_REQUEST_BRANCH') + '.' + ); } return process.env.TRAVIS_PULL_REQUEST_BRANCH; }; diff --git a/build-system/typescript.js b/build-system/typescript.js index 7524195b20762..610047430d4b6 100644 --- a/build-system/typescript.js +++ b/build-system/typescript.js @@ -31,10 +31,13 @@ const tsickle = require('tsickle'); */ exports.transpileTs = function(srcDir, srcFilename) { const tsEntry = path.join(srcDir, srcFilename).replace(/\.js$/, '.ts'); - const tsConfig = ts.convertCompilerOptionsFromJson({ - 'module': 'ES6', - 'target': 'ES6', - }, srcDir); + const tsConfig = ts.convertCompilerOptionsFromJson( + { + 'module': 'ES6', + 'target': 'ES6', + }, + srcDir + ); const tsOptions = tsConfig.options; if (tsConfig.errors.length) { log(colors.red('TSickle:'), tsickle.formatDiagnostics(tsConfig.errors)); @@ -61,13 +64,20 @@ exports.transpileTs = function(srcDir, srcFilename) { shouldSkipTsickleProcessing: () => false, transformTypesToClosure: true, }; - const emitResult = tsickle.emitWithTsickle(program, transformerHost, - compilerHost, tsOptions, undefined, (filePath, contents) => { - fs.writeFileSync(filePath, contents, {encoding: 'utf-8'}); - }); + const emitResult = tsickle.emitWithTsickle( + program, + transformerHost, + compilerHost, + tsOptions, + undefined, + (filePath, contents) => { + fs.writeFileSync(filePath, contents, {encoding: 'utf-8'}); + } + ); - const diagnostics = - ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); + const diagnostics = ts + .getPreEmitDiagnostics(program) + .concat(emitResult.diagnostics); if (diagnostics.length) { log(colors.red('TSickle:'), tsickle.formatDiagnostics(diagnostics)); } diff --git a/builtins/amp-img.js b/builtins/amp-img.js index 9c643a09c7c00..7e778575f9fa9 100644 --- a/builtins/amp-img.js +++ b/builtins/amp-img.js @@ -27,11 +27,19 @@ import {registerElement} from '../src/service/custom-element-registry'; * Attributes to propagate to internal image when changed externally. * @type {!Array} */ -const ATTRIBUTES_TO_PROPAGATE = ['alt', 'title', 'referrerpolicy', 'aria-label', - 'aria-describedby', 'aria-labelledby','srcset', 'src', 'sizes']; +const ATTRIBUTES_TO_PROPAGATE = [ + 'alt', + 'title', + 'referrerpolicy', + 'aria-label', + 'aria-describedby', + 'aria-labelledby', + 'srcset', + 'src', + 'sizes', +]; export class AmpImg extends BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -65,9 +73,13 @@ export class AmpImg extends BaseElement { mutatedAttributesCallback(mutations) { if (this.img_) { const attrs = ATTRIBUTES_TO_PROPAGATE.filter( - value => mutations[value] !== undefined); + value => mutations[value] !== undefined + ); this.propagateAttributes( - attrs, this.img_, /* opt_removeMissingAttrs */ true); + attrs, + this.img_, + /* opt_removeMissingAttrs */ true + ); guaranteeSrcForSrcsetUnsupportedBrowsers(this.img_); } } @@ -141,9 +153,11 @@ export class AmpImg extends BaseElement { if (this.element.getAttribute('role') == 'img') { this.element.removeAttribute('role'); this.user().error( - 'AMP-IMG', 'Setting role=img on amp-img elements breaks ' + - 'screen readers please just set alt or ARIA attributes, they will ' + - 'be correctly propagated for the underlying element.'); + 'AMP-IMG', + 'Setting role=img on amp-img elements breaks ' + + 'screen readers please just set alt or ARIA attributes, they will ' + + 'be correctly propagated for the underlying element.' + ); } this.propagateAttributes(ATTRIBUTES_TO_PROPAGATE, this.img_); @@ -189,7 +203,7 @@ export class AmpImg extends BaseElement { let defaultSize = width + 'px'; if (this.getLayout() !== Layout.FIXED) { - const ratio = Math.round(width * 100 / viewportWidth); + const ratio = Math.round((width * 100) / viewportWidth); defaultSize = Math.max(ratio, 100) + 'vw'; } @@ -250,9 +264,11 @@ export class AmpImg extends BaseElement { /** @override **/ firstLayoutCompleted() { const placeholder = this.getPlaceholder(); - if (placeholder && + if ( + placeholder && placeholder.classList.contains('i-amphtml-blurry-placeholder') && - isExperimentOn(this.win, 'blurry-placeholder')) { + isExperimentOn(this.win, 'blurry-placeholder') + ) { setImportantStyles(placeholder, {'opacity': 0}); } else { this.togglePlaceholder(false); @@ -263,8 +279,10 @@ export class AmpImg extends BaseElement { * @private */ hideFallbackImg_() { - if (!this.allowImgLoadFallback_ - && this.img_.classList.contains('i-amphtml-ghost')) { + if ( + !this.allowImgLoadFallback_ && + this.img_.classList.contains('i-amphtml-ghost') + ) { this.getVsync().mutate(() => { this.img_.classList.remove('i-amphtml-ghost'); this.toggleFallback(false); diff --git a/builtins/amp-layout.js b/builtins/amp-layout.js index 9bdd354970a8a..3379306725904 100644 --- a/builtins/amp-layout.js +++ b/builtins/amp-layout.js @@ -19,11 +19,9 @@ import {Layout, isLayoutSizeDefined} from '../src/layout'; import {registerElement} from '../src/service/custom-element-registry'; class AmpLayout extends BaseElement { - /** @override */ isLayoutSupported(layout) { - return layout == Layout.CONTAINER || - isLayoutSizeDefined(layout); + return layout == Layout.CONTAINER || isLayoutSizeDefined(layout); } /** @override */ @@ -52,5 +50,3 @@ class AmpLayout extends BaseElement { export function installLayout(win) { registerElement(win, 'amp-layout', AmpLayout); } - - diff --git a/builtins/amp-pixel.js b/builtins/amp-pixel.js index e7e4dcd1bfa60..655d3c1102329 100644 --- a/builtins/amp-pixel.js +++ b/builtins/amp-pixel.js @@ -22,12 +22,10 @@ import {registerElement} from '../src/service/custom-element-registry'; const TAG = 'amp-pixel'; - /** * A simple analytics instrument. Fires as an impression signal. */ export class AmpPixel extends BaseElement { - /** @override */ constructor(element) { super(element); @@ -53,12 +51,16 @@ export class AmpPixel extends BaseElement { // Safari doesn't support referrerPolicy yet. We're using an // iframe based trick to remove referrer, which apparently can // only do "no-referrer". - userAssert(this.referrerPolicy_ == 'no-referrer', - `${TAG}: invalid "referrerpolicy" value "${this.referrerPolicy_}".` - + ' Only "no-referrer" is supported'); + userAssert( + this.referrerPolicy_ == 'no-referrer', + `${TAG}: invalid "referrerpolicy" value "${this.referrerPolicy_}".` + + ' Only "no-referrer" is supported' + ); } - if (this.element.hasAttribute('i-amphtml-ssr') && - this.element.querySelector('img')) { + if ( + this.element.hasAttribute('i-amphtml-ssr') && + this.element.querySelector('img') + ) { dev().info(TAG, 'inabox img already present'); return; } @@ -79,19 +81,21 @@ export class AmpPixel extends BaseElement { } // Delay(1) provides a rudimentary "idle" signal. // TODO(dvoytenko): use an improved idle signal when available. - this.triggerPromise_ = Services.timerFor(this.win).promise(1).then(() => { - const src = this.element.getAttribute('src'); - if (!src) { - return; - } - return Services.urlReplacementsForDoc(this.element) + this.triggerPromise_ = Services.timerFor(this.win) + .promise(1) + .then(() => { + const src = this.element.getAttribute('src'); + if (!src) { + return; + } + return Services.urlReplacementsForDoc(this.element) .expandUrlAsync(this.assertSource_(src)) .then(src => { const pixel = createPixel(this.win, src, this.referrerPolicy_); dev().info(TAG, 'pixel triggered: ', src); return pixel; }); - }); + }); } /** @@ -101,9 +105,11 @@ export class AmpPixel extends BaseElement { */ assertSource_(src) { userAssert( - /^(https\:\/\/|\/\/)/i.test(src), - 'The src attribute must start with ' + - '"https://" or "//". Invalid value: ' + src); + /^(https\:\/\/|\/\/)/i.test(src), + 'The src attribute must start with ' + + '"https://" or "//". Invalid value: ' + + src + ); return /** @type {string} */ (src); } } diff --git a/bundles.config.js b/bundles.config.js index 9faae4e02387d..63b35111a3136 100644 --- a/bundles.config.js +++ b/bundles.config.js @@ -22,11 +22,11 @@ const log = require('fancy-log'); /** * @enum {string} */ -const TYPES = exports.TYPES = { +const TYPES = (exports.TYPES = { AD: '_base_ad', MEDIA: '_base_media', MISC: '_base_misc', -}; +}); exports.extensionBundles = [ { @@ -454,9 +454,7 @@ exports.extensionBundles = [ version: '0.1', latestVersion: '0.1', type: TYPES.MISC, - postPrepend: [ - 'third_party/inputmask/bundle.js', - ], + postPrepend: ['third_party/inputmask/bundle.js'], }, { name: 'amp-instagram', @@ -636,8 +634,7 @@ exports.extensionBundles = [ name: 'amp-story', version: '0.1', latestVersion: '1.0', - options: - { + options: { hasCss: true, cssBinaries: [ 'amp-story-bookend', @@ -657,8 +654,7 @@ exports.extensionBundles = [ name: 'amp-story', version: '1.0', latestVersion: '1.0', - options: - { + options: { hasCss: true, cssBinaries: [ 'amp-story-bookend', @@ -682,9 +678,7 @@ exports.extensionBundles = [ latestVersion: '0.1', options: { hasCss: true, - cssBinaries: [ - 'amp-story-auto-ads-attribution', - ], + cssBinaries: ['amp-story-auto-ads-attribution'], }, type: TYPES.MISC, }, @@ -726,9 +720,7 @@ exports.extensionBundles = [ latestVersion: '0.1', options: {hasCss: true}, type: TYPES.MISC, - postPrepend: [ - 'third_party/react-dates/bundle.js', - ], + postPrepend: ['third_party/react-dates/bundle.js'], }, { name: 'amp-image-viewer', @@ -842,8 +834,7 @@ exports.extensionBundles = [ name: 'amp-viewer-integration', version: '0.1', latestVersion: '0.1', - options: - { + options: { // The viewer integration code needs to run asap, so that viewers // can influence document state asap. Otherwise the document may take // a long time to learn that it should start process other extensions @@ -955,9 +946,13 @@ exports.altMainBundles = [ */ function verifyBundle_(condition, field, message, name, found) { if (!condition) { - log(colors.red('ERROR:'), - colors.cyan(field), message, colors.cyan(name), - '\n' + found); + log( + colors.red('ERROR:'), + colors.cyan(field), + message, + colors.cyan(name), + '\n' + found + ); process.exit(1); } } @@ -966,29 +961,53 @@ exports.verifyExtensionBundles = function() { exports.extensionBundles.forEach(bundle => { const bundleString = JSON.stringify(bundle, null, 2); verifyBundle_( - 'name' in bundle, - 'name', 'is missing from', '', bundleString); + 'name' in bundle, + 'name', + 'is missing from', + '', + bundleString + ); verifyBundle_( - 'version' in bundle, - 'version', 'is missing from', bundle.name, bundleString); + 'version' in bundle, + 'version', + 'is missing from', + bundle.name, + bundleString + ); verifyBundle_( - 'latestVersion' in bundle, - 'latestVersion', 'is missing from', bundle.name, bundleString); + 'latestVersion' in bundle, + 'latestVersion', + 'is missing from', + bundle.name, + bundleString + ); const duplicates = exports.extensionBundles.filter( - duplicate => duplicate.name === bundle.name); + duplicate => duplicate.name === bundle.name + ); verifyBundle_( - duplicates.every( - duplicate => duplicate.latestVersion === bundle.latestVersion), - 'latestVersion', 'is not the same for all versions of', bundle.name, - JSON.stringify(duplicates, null, 2)); + duplicates.every( + duplicate => duplicate.latestVersion === bundle.latestVersion + ), + 'latestVersion', + 'is not the same for all versions of', + bundle.name, + JSON.stringify(duplicates, null, 2) + ); verifyBundle_( - 'type' in bundle, - 'type', 'is missing from', bundle.name, bundleString); + 'type' in bundle, + 'type', + 'is missing from', + bundle.name, + bundleString + ); const validTypes = Object.keys(TYPES).map(x => TYPES[x]); verifyBundle_( - validTypes.some(validType => validType === bundle.type), - 'type', `is not one of ${validTypes.join(',')} in`, bundle.name, - bundleString); + validTypes.some(validType => validType === bundle.type), + 'type', + `is not one of ${validTypes.join(',')} in`, + bundle.name, + bundleString + ); }); }; @@ -996,13 +1015,25 @@ exports.verifyExtensionAliasBundles = function() { exports.aliasBundles.forEach(bundle => { const bundleString = JSON.stringify(bundle, null, 2); verifyBundle_( - 'name' in bundle, - 'name', 'is missing from', '', bundleString); + 'name' in bundle, + 'name', + 'is missing from', + '', + bundleString + ); verifyBundle_( - 'version' in bundle, - 'version', 'is missing from', bundle.name, bundleString); + 'version' in bundle, + 'version', + 'is missing from', + bundle.name, + bundleString + ); verifyBundle_( - 'latestVersion' in bundle, - 'latestVersion', 'is missing from', bundle.name, bundleString); + 'latestVersion' in bundle, + 'latestVersion', + 'is missing from', + bundle.name, + bundleString + ); }); }; diff --git a/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js b/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js index cc92dbd7cd66d..74cedb47e95fd 100644 --- a/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js +++ b/extensions/amp-3d-gltf/0.1/amp-3d-gltf.js @@ -27,13 +27,12 @@ const TAG = 'amp-3d-gltf'; const isWebGLSupported = () => { const canvas = document.createElement('canvas'); - const gl = canvas.getContext('webgl') - || canvas.getContext('experimental-webgl'); + const gl = + canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); return gl && gl instanceof WebGLRenderingContext; }; export class Amp3dGltf extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -60,9 +59,18 @@ export class Amp3dGltf extends AMP.BaseElement { */ preconnectCallback(opt_onLayout) { preloadBootstrap(this.win, this.preconnect); - this.preconnect.url('https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js', opt_onLayout); - this.preconnect.url('https://cdn.jsdelivr.net/npm/three@0.91/examples/js/loaders/GLTFLoader.js', opt_onLayout); - this.preconnect.url('https://cdn.jsdelivr.net/npm/three@0.91/examples/js/controls/OrbitControls.js', opt_onLayout); + this.preconnect.url( + 'https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js', + opt_onLayout + ); + this.preconnect.url( + 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/loaders/GLTFLoader.js', + opt_onLayout + ); + this.preconnect.url( + 'https://cdn.jsdelivr.net/npm/three@0.91/examples/js/controls/OrbitControls.js', + opt_onLayout + ); } /** @override */ @@ -92,9 +100,7 @@ export class Amp3dGltf extends AMP.BaseElement { const string = x => x; const number = x => parseFloat(x); - const src = assertHttpsUrl( - getOption('src', string, ''), - this.element); + const src = assertHttpsUrl(getOption('src', string, ''), this.element); const useAlpha = getOption('alpha', bool, false); @@ -106,21 +112,27 @@ export class Amp3dGltf extends AMP.BaseElement { }, 'rendererSettings': { 'clearAlpha': useAlpha ? 0 : 1, - 'clearColor': - getOption('clearColor', string, '#fff'), - 'maxPixelRatio': - getOption('maxPixelRatio', number, devicePixelRatio || 1), + 'clearColor': getOption('clearColor', string, '#fff'), + 'maxPixelRatio': getOption( + 'maxPixelRatio', + number, + devicePixelRatio || 1 + ), }, 'controls': { 'enableZoom': getOption('enableZoom', bool, true), 'autoRotate': getOption('autoRotate', bool, false), }, }); - this.registerAction('setModelRotation', invocation => { - this.sendCommandWhenReady_('setModelRotation', invocation.args) - .catch(e => dev() - .error('AMP-3D-GLTF', 'setModelRotation failed: %s', e)); - }, ActionTrust.LOW); + this.registerAction( + 'setModelRotation', + invocation => { + this.sendCommandWhenReady_('setModelRotation', invocation.args).catch( + e => dev().error('AMP-3D-GLTF', 'setModelRotation failed: %s', e) + ); + }, + ActionTrust.LOW + ); } /** @override */ @@ -130,9 +142,7 @@ export class Amp3dGltf extends AMP.BaseElement { return Promise.resolve(); } - const iframe = getIframe( - this.win, this.element, '3d-gltf', this.context_ - ); + const iframe = getIframe(this.win, this.element, '3d-gltf', this.context_); this.applyFillContent(iframe, true); this.iframe_ = iframe; @@ -149,11 +159,8 @@ export class Amp3dGltf extends AMP.BaseElement { return; } - const listenIframe = (evName, cb) => listenFor( - dev().assertElement(this.iframe_), - evName, - cb, - true); + const listenIframe = (evName, cb) => + listenFor(dev().assertElement(this.iframe_), evName, cb, true); const disposers = [ listenIframe('ready', this.willBeReady_.resolve), @@ -192,12 +199,7 @@ export class Amp3dGltf extends AMP.BaseElement { * @private */ postMessage_(type, message) { - postMessage( - dev().assertElement(this.iframe_), - type, - message, - '*', - true); + postMessage(dev().assertElement(this.iframe_), type, message, '*', true); } /** @@ -225,8 +227,9 @@ export class Amp3dGltf extends AMP.BaseElement { onLayoutMeasure() { const box = this.getLayoutBox(); this.sendCommandWhenReady_( - 'setSize', - dict({'width': box.width, 'height': box.height})); + 'setSize', + dict({'width': box.width, 'height': box.height}) + ); } /** @override */ diff --git a/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js b/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js index 280a30ded811d..1fd1d77553082 100644 --- a/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js +++ b/extensions/amp-3d-gltf/0.1/test/test-amp-3d-gltf.js @@ -17,96 +17,99 @@ import '../amp-3d-gltf'; import {createIframeWithMessageStub} from '../../../../testing/iframe'; -describes.realWin('amp-3d-gltf', { - amp: { - extensions: ['amp-3d-gltf'], +describes.realWin( + 'amp-3d-gltf', + { + amp: { + extensions: ['amp-3d-gltf'], + }, + allowExternalResources: true, }, - allowExternalResources: true, -}, env => { - let win; - let doc; - let iframe; - let testIndex = 0; - let sendFakeMessage = () => {}; + env => { + let win; + let doc; + let iframe; + let testIndex = 0; + let sendFakeMessage = () => {}; - beforeEach(() => { - win = env.win; - doc = win.document; - testIndex++; - const sentinel = 'amp3ptest' + testIndex; - iframe = createIframeWithMessageStub(win); - iframe.setAttribute('data-amp-3p-sentinel', sentinel); - iframe.name = 'test_nomaster'; + beforeEach(() => { + win = env.win; + doc = win.document; + testIndex++; + const sentinel = 'amp3ptest' + testIndex; + iframe = createIframeWithMessageStub(win); + iframe.setAttribute('data-amp-3p-sentinel', sentinel); + iframe.name = 'test_nomaster'; - sendFakeMessage = type => { - return new Promise(resolve => { - iframe.postMessageToParent({sentinel, type}); - setTimeout(resolve, 100); - }); - }; - }); + sendFakeMessage = type => { + return new Promise(resolve => { + iframe.postMessageToParent({sentinel, type}); + setTimeout(resolve, 100); + }); + }; + }); - const createElement = () => { - const amp3dGltfEl = doc.createElement('amp-3d-gltf'); - amp3dGltfEl.setAttribute('src', 'https://fake.com/fake.gltf'); - amp3dGltfEl.setAttribute('layout', 'fixed'); - amp3dGltfEl.setAttribute('width', '320'); - amp3dGltfEl.setAttribute('height', '240'); + const createElement = () => { + const amp3dGltfEl = doc.createElement('amp-3d-gltf'); + amp3dGltfEl.setAttribute('src', 'https://fake.com/fake.gltf'); + amp3dGltfEl.setAttribute('layout', 'fixed'); + amp3dGltfEl.setAttribute('width', '320'); + amp3dGltfEl.setAttribute('height', '240'); - doc.body.appendChild(amp3dGltfEl); + doc.body.appendChild(amp3dGltfEl); - return amp3dGltfEl.build() - .then(() => { - const amp3dGltf = amp3dGltfEl.implementation_; - sandbox.stub(amp3dGltf, 'iframe_') - .get(() => iframe) - .set(() => {}); + return amp3dGltfEl.build().then(() => { + const amp3dGltf = amp3dGltfEl.implementation_; + sandbox + .stub(amp3dGltf, 'iframe_') + .get(() => iframe) + .set(() => {}); - const willLayout = amp3dGltfEl.layoutCallback(); + const willLayout = amp3dGltfEl.layoutCallback(); - return sendFakeMessage('ready') - .then(() => sendFakeMessage('loaded')) - .then(() => willLayout) - .then(() => amp3dGltf); - }); - }; + return sendFakeMessage('ready') + .then(() => sendFakeMessage('loaded')) + .then(() => willLayout) + .then(() => amp3dGltf); + }); + }; - // TODO (#16080): this test keeps timing out for some reason. - // Unskip when we figure out root cause. - it.skip('renders iframe', () => { - return createElement() - .then(() => { - expect(!!doc.body.querySelector('amp-3d-gltf > iframe')).to.be.true; - }); - }); + // TODO (#16080): this test keeps timing out for some reason. + // Unskip when we figure out root cause. + it.skip('renders iframe', () => { + return createElement().then(() => { + expect(!!doc.body.querySelector('amp-3d-gltf > iframe')).to.be.true; + }); + }); - // TODO (#16080): this test times out on Travis. Re-enable when fixed. - it.skip('sends toggleAmpViewport(false) when exiting viewport', () => { - return createElement() - .then(amp3dGltf => { - const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); - return amp3dGltf.viewportCallback(false).then(() => { - expect(postMessageSpy.calledOnce).to.be.true; - expect(postMessageSpy.firstCall.args[0]).to.equal('action'); - expect(postMessageSpy.firstCall.args[1].action) - .to.equal('toggleAmpViewport'); - expect(postMessageSpy.firstCall.args[1].args).to.be.false; - }); + // TODO (#16080): this test times out on Travis. Re-enable when fixed. + it.skip('sends toggleAmpViewport(false) when exiting viewport', () => { + return createElement().then(amp3dGltf => { + const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); + return amp3dGltf.viewportCallback(false).then(() => { + expect(postMessageSpy.calledOnce).to.be.true; + expect(postMessageSpy.firstCall.args[0]).to.equal('action'); + expect(postMessageSpy.firstCall.args[1].action).to.equal( + 'toggleAmpViewport' + ); + expect(postMessageSpy.firstCall.args[1].args).to.be.false; }); - }); + }); + }); - // TODO (#16080): this test times out on Travis. Re-enable when fixed. - it.skip('sends toggleAmpViewport(true) when entering viewport', () => { - return createElement() - .then(amp3dGltf => { - const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); - return amp3dGltf.viewportCallback(true).then(() => { - expect(postMessageSpy.calledOnce).to.be.true; - expect(postMessageSpy.firstCall.args[0]).to.equal('action'); - expect(postMessageSpy.firstCall.args[1].action) - .to.equal('toggleAmpViewport'); - expect(postMessageSpy.firstCall.args[1].args).to.be.true; - }); + // TODO (#16080): this test times out on Travis. Re-enable when fixed. + it.skip('sends toggleAmpViewport(true) when entering viewport', () => { + return createElement().then(amp3dGltf => { + const postMessageSpy = sandbox.spy(amp3dGltf, 'postMessage_'); + return amp3dGltf.viewportCallback(true).then(() => { + expect(postMessageSpy.calledOnce).to.be.true; + expect(postMessageSpy.firstCall.args[0]).to.equal('action'); + expect(postMessageSpy.firstCall.args[1].action).to.equal( + 'toggleAmpViewport' + ); + expect(postMessageSpy.firstCall.args[1].args).to.be.true; }); - }); -}); + }); + }); + } +); diff --git a/extensions/amp-3q-player/0.1/amp-3q-player.js b/extensions/amp-3q-player/0.1/amp-3q-player.js index d015f243a03e8..b393c87459a68 100644 --- a/extensions/amp-3q-player/0.1/amp-3q-player.js +++ b/extensions/amp-3q-player/0.1/amp-3q-player.js @@ -30,18 +30,13 @@ import { removeElement, } from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; -import { - installVideoManagerForDoc, -} from '../../../src/service/video-manager-impl'; +import {installVideoManagerForDoc} from '../../../src/service/video-manager-impl'; import {isLayoutSizeDefined} from '../../../src/layout'; - const TAG = 'amp-3q-player'; - /** @implements {../../../src/video-interface.VideoInterface} */ class Amp3QPlayer extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -74,9 +69,10 @@ class Amp3QPlayer extends AMP.BaseElement { const {element: el} = this; this.dataId = userAssert( - el.getAttribute('data-id'), - 'The data-id attribute is required for %s', - el); + el.getAttribute('data-id'), + 'The data-id attribute is required for %s', + el + ); const deferred = new Deferred(); this.playerReadyPromise_ = deferred.promise; @@ -88,18 +84,21 @@ class Amp3QPlayer extends AMP.BaseElement { /** @override */ layoutCallback() { - const iframe = createFrameFor(this, - 'https://playout.3qsdn.com/' - + encodeURIComponent(dev().assertString(this.dataId)) - // Autoplay is handled by VideoManager - + '?autoplay=false&=true'); + const iframe = createFrameFor( + this, + 'https://playout.3qsdn.com/' + + encodeURIComponent(dev().assertString(this.dataId)) + + // Autoplay is handled by VideoManager + '?autoplay=false&=true' + ); this.iframe_ = iframe; this.unlistenMessage_ = listen( - this.win, - 'message', - this.sdnBridge_.bind(this)); + this.win, + 'message', + this.sdnBridge_.bind(this) + ); return this.loadPromise(this.iframe_).then(() => this.playerReadyPromise_); } @@ -178,7 +177,7 @@ class Amp3QPlayer extends AMP.BaseElement { sdnPostMessage_(message) { this.playerReadyPromise_.then(() => { if (this.iframe_ && this.iframe_.contentWindow) { - this.iframe_.contentWindow./*OK*/postMessage(message, '*'); + this.iframe_.contentWindow./*OK*/ postMessage(message, '*'); } }); } @@ -291,7 +290,6 @@ class Amp3QPlayer extends AMP.BaseElement { } } - AMP.extension(TAG, '0.1', AMP => { AMP.registerElement(TAG, Amp3QPlayer); }); diff --git a/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js b/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js index 19b4dce85f6ab..9f4f76f58fda7 100644 --- a/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js +++ b/extensions/amp-3q-player/0.1/test/test-amp-3q-player.js @@ -19,85 +19,101 @@ import {Services} from '../../../../src/services'; import {VideoEvents} from '../../../../src/video-interface'; import {listenOncePromise} from '../../../../src/event-helper'; - -describes.realWin('amp-3q-player', { - amp: { - extensions: ['amp-3q-player'], +describes.realWin( + 'amp-3q-player', + { + amp: { + extensions: ['amp-3q-player'], + }, }, -}, function(env) { - let win; - let doc; - let timer; + function(env) { + let win; + let doc; + let timer; - beforeEach(() => { - win = env.win; - doc = win.document; - timer = Services.timerFor(win); - }); + beforeEach(() => { + win = env.win; + doc = win.document; + timer = Services.timerFor(win); + }); - function get3QElement(playoutId) { - const player = doc.createElement('amp-3q-player'); - if (playoutId) { - player.setAttribute('data-id', playoutId); + function get3QElement(playoutId) { + const player = doc.createElement('amp-3q-player'); + if (playoutId) { + player.setAttribute('data-id', playoutId); + } + doc.body.appendChild(player); + return player + .build() + .then(() => { + player.layoutCallback(); + const iframe = player.querySelector('iframe'); + player.implementation_.sdnBridge_({ + source: iframe.contentWindow, + data: JSON.stringify({data: 'ready'}), + }); + }) + .then(() => { + return player; + }); } - doc.body.appendChild(player); - return player.build().then(() => { - player.layoutCallback(); - const iframe = player.querySelector('iframe'); - player.implementation_.sdnBridge_({ - source: iframe.contentWindow, - data: JSON.stringify({data: 'ready'}), - }); - }).then(() => { - return player; - }); - } - it('renders', () => { - return get3QElement( - 'c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then(player => { - const iframe = player.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.src).to.equal('https://playout.3qsdn.com/c8dbe7f4-7f7f-11e6-a407-0cc47a188158?autoplay=false&=true'); + it('renders', () => { + return get3QElement('c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then( + player => { + const iframe = player.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.src).to.equal( + 'https://playout.3qsdn.com/c8dbe7f4-7f7f-11e6-a407-0cc47a188158?autoplay=false&=true' + ); + } + ); }); - }); - it('requires data-id', () => { - return allowConsoleError(() => { - return get3QElement('').should.eventually.be.rejectedWith( - /The data-id attribute is required/); + it('requires data-id', () => { + return allowConsoleError(() => { + return get3QElement('').should.eventually.be.rejectedWith( + /The data-id attribute is required/ + ); + }); }); - }); - - it('should forward events from amp-3q-player to the amp element', () => { - return get3QElement( - 'c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then(player => { - const iframe = player.querySelector('iframe'); + it('should forward events from amp-3q-player to the amp element', () => { + return get3QElement('c8dbe7f4-7f7f-11e6-a407-0cc47a188158').then( + player => { + const iframe = player.querySelector('iframe'); - return Promise.resolve().then(() => { - const p = listenOncePromise(player, VideoEvents.MUTED); - sendFakeMessage(player, iframe, 'muted'); - return p; - }).then(() => { - const p = listenOncePromise(player, VideoEvents.PLAYING); - sendFakeMessage(player, iframe, 'playing'); - return p; - }).then(() => { - const p = listenOncePromise(player, VideoEvents.PAUSE); - sendFakeMessage(player, iframe, 'paused'); - return p; - }).then(() => { - const p = listenOncePromise(player, VideoEvents.UNMUTED); - sendFakeMessage(player, iframe, 'unmuted'); - const successTimeout = timer.promise(10); - return Promise.race([p, successTimeout]); - }); + return Promise.resolve() + .then(() => { + const p = listenOncePromise(player, VideoEvents.MUTED); + sendFakeMessage(player, iframe, 'muted'); + return p; + }) + .then(() => { + const p = listenOncePromise(player, VideoEvents.PLAYING); + sendFakeMessage(player, iframe, 'playing'); + return p; + }) + .then(() => { + const p = listenOncePromise(player, VideoEvents.PAUSE); + sendFakeMessage(player, iframe, 'paused'); + return p; + }) + .then(() => { + const p = listenOncePromise(player, VideoEvents.UNMUTED); + sendFakeMessage(player, iframe, 'unmuted'); + const successTimeout = timer.promise(10); + return Promise.race([p, successTimeout]); + }); + } + ); }); - }); - function sendFakeMessage(player, iframe, command) { - player.implementation_.sdnBridge_( - {source: iframe.contentWindow, data: JSON.stringify({data: command})}); + function sendFakeMessage(player, iframe, command) { + player.implementation_.sdnBridge_({ + source: iframe.contentWindow, + data: JSON.stringify({data: command}), + }); + } } -}); +); diff --git a/extensions/amp-a4a/0.1/a4a-variable-source.js b/extensions/amp-a4a/0.1/a4a-variable-source.js index ecc3043333b7a..6fa81cddc050f 100644 --- a/extensions/amp-a4a/0.1/a4a-variable-source.js +++ b/extensions/amp-a4a/0.1/a4a-variable-source.js @@ -23,7 +23,6 @@ import { } from '../../../src/service/variable-source'; import {user, userAssert} from '../../../src/log'; - const WHITELISTED_VARIABLES = [ 'AMPDOC_HOST', 'AMPDOC_HOSTNAME', @@ -102,19 +101,27 @@ export class A4AVariableSource extends VariableSource { } this.set('NAV_TIMING', (startAttribute, endAttribute) => { - userAssert(startAttribute, 'The first argument to NAV_TIMING, the' + - ' start attribute name, is required'); + userAssert( + startAttribute, + 'The first argument to NAV_TIMING, the' + + ' start attribute name, is required' + ); return getTimingDataSync( - this.win_, - /**@type {string}*/(startAttribute), - /**@type {string}*/(endAttribute)); + this.win_, + /**@type {string}*/ (startAttribute), + /**@type {string}*/ (endAttribute) + ); }).setAsync('NAV_TIMING', (startAttribute, endAttribute) => { - userAssert(startAttribute, 'The first argument to NAV_TIMING, the' + - ' start attribute name, is required'); + userAssert( + startAttribute, + 'The first argument to NAV_TIMING, the' + + ' start attribute name, is required' + ); return getTimingDataAsync( - this.win_, - /**@type {string}*/(startAttribute), - /**@type {string}*/(endAttribute)); + this.win_, + /**@type {string}*/ (startAttribute), + /**@type {string}*/ (endAttribute) + ); }); this.set('NAV_TYPE', () => { @@ -125,8 +132,10 @@ export class A4AVariableSource extends VariableSource { return getNavigationData(this.win_, 'redirectCount'); }); - this.set('HTML_ATTR', - /** @type {function(...*)} */(this.htmlAttributeBinding_.bind(this))); + this.set( + 'HTML_ATTR', + /** @type {function(...*)} */ (this.htmlAttributeBinding_.bind(this)) + ); this.set('CLIENT_ID', () => null); } @@ -179,20 +188,27 @@ export class A4AVariableSource extends VariableSource { return '[]'; } if (elements.length > HTML_ATTR_MAX_ELEMENTS_TO_TRAVERSE) { - user().error(TAG, 'CSS selector may match at most ' + - `${HTML_ATTR_MAX_ELEMENTS_TO_TRAVERSE} elements.`); + user().error( + TAG, + 'CSS selector may match at most ' + + `${HTML_ATTR_MAX_ELEMENTS_TO_TRAVERSE} elements.` + ); return '[]'; } const result = []; - for (let i = 0; i < elements.length && - result.length < HTML_ATTR_MAX_ELEMENTS_TO_RETURN; ++i) { + for ( + let i = 0; + i < elements.length && result.length < HTML_ATTR_MAX_ELEMENTS_TO_RETURN; + ++i + ) { const currentResult = {}; let foundAtLeastOneAttr = false; for (let j = 0; j < attributeNames.length; ++j) { const attributeName = attributeNames[j]; if (elements[i].hasAttribute(attributeName)) { - currentResult[attributeName] = - elements[i].getAttribute(attributeName); + currentResult[attributeName] = elements[i].getAttribute( + attributeName + ); foundAtLeastOneAttr = true; } } diff --git a/extensions/amp-a4a/0.1/amp-a4a.js b/extensions/amp-a4a/0.1/amp-a4a.js index 180d8b120ab36..a679ca1b59b98 100644 --- a/extensions/amp-a4a/0.1/amp-a4a.js +++ b/extensions/amp-a4a/0.1/amp-a4a.js @@ -30,10 +30,7 @@ import { generateSentinel, getDefaultBootstrapBaseUrl, } from '../../../src/3p-frame'; -import { - assertHttpsUrl, - tryDecodeUriComponent, -} from '../../../src/url'; +import {assertHttpsUrl, tryDecodeUriComponent} from '../../../src/url'; import {cancellation, isCancellation} from '../../../src/error'; import {createElementWithAttributes} from '../../../src/dom'; import { @@ -56,9 +53,7 @@ import { installFriendlyIframeEmbed, setFriendlyIframeEmbedVisible, } from '../../../src/friendly-iframe-embed'; -import { - installUrlReplacementsForEmbed, -} from '../../../src/service/url-replacements-impl'; +import {installUrlReplacementsForEmbed} from '../../../src/service/url-replacements-impl'; import {isAdPositionAllowed} from '../../../src/ad-helper'; import {isArray, isEnumValue, isObject} from '../../../src/types'; import {isExperimentOn} from '../../../src/experiments'; @@ -73,7 +68,8 @@ import {utf8Decode} from '../../../src/utils/bytes'; const METADATA_STRINGS = [ ''); if (metadataEnd < 0) { // Couldn't find a metadata blob. - dev().warn(TAG, this.element.getAttribute('type'), - 'Could not locate closing script tag for amp meta data in: %s', - creative); + dev().warn( + TAG, + this.element.getAttribute('type'), + 'Could not locate closing script tag for amp meta data in: %s', + creative + ); return null; } try { const metaDataObj = parseJson( - creative.slice(metadataStart + metadataString.length, metadataEnd)); + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); const ampRuntimeUtf16CharOffsets = metaDataObj['ampRuntimeUtf16CharOffsets']; - if (!isArray(ampRuntimeUtf16CharOffsets) || - ampRuntimeUtf16CharOffsets.length != 2 || - typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || - typeof ampRuntimeUtf16CharOffsets[1] !== 'number') { + if ( + !isArray(ampRuntimeUtf16CharOffsets) || + ampRuntimeUtf16CharOffsets.length != 2 || + typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || + typeof ampRuntimeUtf16CharOffsets[1] !== 'number' + ) { throw new Error('Invalid runtime offsets'); } const metaData = {}; @@ -1596,7 +1728,9 @@ export class AmpA4A extends AMP.BaseElement { metaDataObj['customElementExtensions']; if (!isArray(metaData.customElementExtensions)) { throw new Error( - 'Invalid extensions', metaData.customElementExtensions); + 'Invalid extensions', + metaData.customElementExtensions + ); } } else { metaData.customElementExtensions = []; @@ -1612,9 +1746,12 @@ export class AmpA4A extends AMP.BaseElement { const urls = Services.urlForDoc(this.element); metaData.customStylesheets.forEach(stylesheet => { - if (!isObject(stylesheet) || !stylesheet['href'] || - typeof stylesheet['href'] !== 'string' || - !urls.isSecure(stylesheet['href'])) { + if ( + !isObject(stylesheet) || + !stylesheet['href'] || + typeof stylesheet['href'] !== 'string' || + !urls.isSecure(stylesheet['href']) + ) { throw new Error(errorMsg); } }); @@ -1639,8 +1776,11 @@ export class AmpA4A extends AMP.BaseElement { return metaData; } catch (err) { dev().warn( - TAG, this.element.getAttribute('type'), 'Invalid amp metadata: %s', - creative.slice(metadataStart + metadataString.length, metadataEnd)); + TAG, + this.element.getAttribute('type'), + 'Invalid amp metadata: %s', + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); if (this.isSinglePageStoryAd) { throw err; } @@ -1652,8 +1792,10 @@ export class AmpA4A extends AMP.BaseElement { * @return {string} full url to safeframe implementation. */ getSafeframePath() { - return 'https://tpc.googlesyndication.com/safeframe/' + - `${this.safeframeVersion}/html/container.html`; + return ( + 'https://tpc.googlesyndication.com/safeframe/' + + `${this.safeframeVersion}/html/container.html` + ); } /** @@ -1667,11 +1809,13 @@ export class AmpA4A extends AMP.BaseElement { // No config exists that will listen to this event. return; } - const analyticsEvent = - devAssert(LIFECYCLE_STAGE_TO_ANALYTICS_TRIGGER[lifecycleStage]); + const analyticsEvent = devAssert( + LIFECYCLE_STAGE_TO_ANALYTICS_TRIGGER[lifecycleStage] + ); const analyticsVars = /** @type {!JsonObject} */ (Object.assign( - dict({'time': Math.round(this.getNow_())}), - this.getA4aAnalyticsVars(analyticsEvent))); + dict({'time': Math.round(this.getNow_())}), + this.getA4aAnalyticsVars(analyticsEvent) + )); triggerAnalyticsEvent(this.element, analyticsEvent, analyticsVars); } @@ -1694,7 +1838,9 @@ export class AmpA4A extends AMP.BaseElement { * added to this A4A element and no A4A triggers will be fired. * @return {?JsonObject} */ - getA4aAnalyticsConfig() { return null; } + getA4aAnalyticsConfig() { + return null; + } /** * Attempts to execute Real Time Config, if the ad network has enabled it. @@ -1706,16 +1852,19 @@ export class AmpA4A extends AMP.BaseElement { tryExecuteRealTimeConfig_(consentState) { if (!!AMP.RealTimeConfigManager) { try { - return new AMP.RealTimeConfigManager(this) - .maybeExecuteRealTimeConfig( - this.getCustomRealTimeConfigMacros_(), consentState); + return new AMP.RealTimeConfigManager(this).maybeExecuteRealTimeConfig( + this.getCustomRealTimeConfigMacros_(), + consentState + ); } catch (err) { user().error(TAG, 'Could not perform Real Time Config.', err); } } else if (this.element.getAttribute('rtc-config')) { - user().error(TAG, 'RTC not supported for ad network ' + - `${this.element.getAttribute('type')}`); - + user().error( + TAG, + 'RTC not supported for ad network ' + + `${this.element.getAttribute('type')}` + ); } } @@ -1745,13 +1894,16 @@ export class AmpA4A extends AMP.BaseElement { if (headerValue) { if (!isEnumValue(XORIGIN_MODE, headerValue)) { dev().error( - 'AMP-A4A', `cross-origin render mode header ${headerValue}`); + 'AMP-A4A', + `cross-origin render mode header ${headerValue}` + ); } else { return headerValue; } } - return Services.platformFor(this.win).isIos() ? - XORIGIN_MODE.NAMEFRAME : null; + return Services.platformFor(this.win).isIos() + ? XORIGIN_MODE.NAMEFRAME + : null; } /** @@ -1771,7 +1923,6 @@ export class AmpA4A extends AMP.BaseElement { return this.isVerifiedAmpCreative_; } - /** * Adds single pass experiment IDs if the javascript binary has * "singlePassType" mode. @@ -1779,11 +1930,15 @@ export class AmpA4A extends AMP.BaseElement { maybeAddSinglePassExperiment() { const type = getMode().singlePassType; if (type === 'sp') { - addExperimentIdToElement(SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS, - this.element); + addExperimentIdToElement( + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS, + this.element + ); } else if (type === 'mp') { - addExperimentIdToElement(SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS, - this.element); + addExperimentIdToElement( + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS, + this.element + ); } } } @@ -1801,8 +1956,10 @@ export function assignAdUrlToError(error, adUrl) { if (adQueryIdx == -1) { return; } - (error.args || (error.args = {}))['au'] = - adUrl.substring(adQueryIdx + 1, adQueryIdx + 251); + (error.args || (error.args = {}))['au'] = adUrl.substring( + adQueryIdx + 1, + adQueryIdx + 251 + ); } /** @@ -1819,6 +1976,8 @@ export function assignAdUrlToError(error, adUrl) { */ export function signatureVerifierFor(win) { const propertyName = 'AMP_FAST_FETCH_SIGNATURE_VERIFIER_'; - return win[propertyName] || - (win[propertyName] = new SignatureVerifier(win, signingServerURLs)); + return ( + win[propertyName] || + (win[propertyName] = new SignatureVerifier(win, signingServerURLs)) + ); } diff --git a/extensions/amp-a4a/0.1/amp-ad-network-base.js b/extensions/amp-a4a/0.1/amp-ad-network-base.js index 19c2d2b8c4183..f5cbcadfab9a0 100644 --- a/extensions/amp-a4a/0.1/amp-ad-network-base.js +++ b/extensions/amp-a4a/0.1/amp-ad-network-base.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - FailureType, - RecoveryModeType, -} from './amp-ad-type-defs'; +import {FailureType, RecoveryModeType} from './amp-ad-type-defs'; import {Services} from '../../../src/services'; import {dev, devAssert} from '../../../src/log'; import {isLayoutSizeDefined} from '../../../src/layout'; @@ -30,7 +27,6 @@ const TAG = 'amp-ad-network-base'; * @abstract */ export class AmpAdNetworkBase extends AMP.BaseElement { - /** * Creates an instance of AmpAdNetworkBase. * @param {!AmpElement} element @@ -77,12 +73,14 @@ export class AmpAdNetworkBase extends AMP.BaseElement { /** @override */ layoutCallback() { - devAssert(this.adResponsePromise_, - 'layoutCallback invoked before XHR request!'); + devAssert( + this.adResponsePromise_, + 'layoutCallback invoked before XHR request!' + ); return this.adResponsePromise_ - .then(response => this.invokeValidator_(response)) - .then(validatorResult => this.invokeRenderer_(validatorResult)) - .catch(error => this.handleFailure_(error.type, error.msg)); + .then(response => this.invokeValidator_(response)) + .then(validatorResult => this.invokeRenderer_(validatorResult)) + .catch(error => this.handleFailure_(error.type, error.msg)); } /** @@ -92,8 +90,10 @@ export class AmpAdNetworkBase extends AMP.BaseElement { */ onFailure(failure, recovery) { if (this.recoveryModes_[failure]) { - dev().warn(TAG, - `Recovery mode for failure type ${failure} already registered!`); + dev().warn( + TAG, + `Recovery mode for failure type ${failure} already registered!` + ); } this.recoveryModes_[failure] = recovery; } @@ -148,10 +148,12 @@ export class AmpAdNetworkBase extends AMP.BaseElement { * @private */ sendRequest_() { - Services.viewerForDoc(this.getAmpDoc()).whenFirstVisible().then(() => { - const url = this.getRequestUrl(); - this.adResponsePromise_ = sendXhrRequest(this.win, url); - }); + Services.viewerForDoc(this.getAmpDoc()) + .whenFirstVisible() + .then(() => { + const url = this.getRequestUrl(); + this.adResponsePromise_ = sendXhrRequest(this.win, url); + }); } /** @@ -165,14 +167,14 @@ export class AmpAdNetworkBase extends AMP.BaseElement { return Promise.reject(this.handleFailure_(FailureType.INVALID_RESPONSE)); } return response.arrayBuffer().then(unvalidatedBytes => { - const validatorType = response.headers.get('AMP-Ad-Response-Type') - || 'default'; - devAssert(this.validators_[validatorType], - 'Validator never registered!'); - return this.validators_[validatorType].validate( - this.context_, unvalidatedBytes, response.headers) - .catch(err => - Promise.reject({type: FailureType.VALIDATOR_ERROR, msg: err})); + const validatorType = + response.headers.get('AMP-Ad-Response-Type') || 'default'; + devAssert(this.validators_[validatorType], 'Validator never registered!'); + return this.validators_[validatorType] + .validate(this.context_, unvalidatedBytes, response.headers) + .catch(err => + Promise.reject({type: FailureType.VALIDATOR_ERROR, msg: err}) + ); }); } @@ -184,10 +186,11 @@ export class AmpAdNetworkBase extends AMP.BaseElement { invokeRenderer_(validatorOutput) { const renderer = this.renderers_[validatorOutput.type]; devAssert(renderer, 'Renderer for AMP creatives never registered!'); - return renderer.render( - this.context_, this.element, validatorOutput.creativeData) - .catch(err => - Promise.reject({type: FailureType.RENDERER_ERROR, msg: err})); + return renderer + .render(this.context_, this.element, validatorOutput.creativeData) + .catch(err => + Promise.reject({type: FailureType.RENDERER_ERROR, msg: err}) + ); } /** diff --git a/extensions/amp-a4a/0.1/amp-ad-template-helper.js b/extensions/amp-a4a/0.1/amp-ad-template-helper.js index 99fcd9b4b0b29..caed8bf00a43f 100644 --- a/extensions/amp-a4a/0.1/amp-ad-template-helper.js +++ b/extensions/amp-a4a/0.1/amp-ad-template-helper.js @@ -35,7 +35,6 @@ const TEMPLATE_CORS_CONFIG = { }; export class AmpAdTemplateHelper { - /** * @param {!Window} win */ @@ -54,15 +53,16 @@ export class AmpAdTemplateHelper { * @return {!Promise} */ fetch(templateUrl) { - const proxyUrl = getMode(this.win_).localDev && !isNaN(templateUrl) - ? `http://ads.localhost:${this.win_.location.port}` + + const proxyUrl = + getMode(this.win_).localDev && !isNaN(templateUrl) + ? `http://ads.localhost:${this.win_.location.port}` + `/a4a_template/adzerk/${templateUrl}` - : this.getTemplateProxyUrl_(templateUrl); + : this.getTemplateProxyUrl_(templateUrl); let templatePromise = this.cache_.get(proxyUrl); if (!templatePromise) { templatePromise = Services.xhrFor(this.win_) - .fetchText(proxyUrl, TEMPLATE_CORS_CONFIG) - .then(response => response.text()); + .fetchText(proxyUrl, TEMPLATE_CORS_CONFIG) + .then(response => response.text()); this.cache_.put(proxyUrl, templatePromise); } devAssert(templatePromise); @@ -75,8 +75,10 @@ export class AmpAdTemplateHelper { * @return {!Promise} Promise which resolves after rendering completes. */ render(templateValues, element) { - return Services.templatesFor(this.win_) - .findAndRenderTemplate(element, templateValues); + return Services.templatesFor(this.win_).findAndRenderTemplate( + element, + templateValues + ); } /** @@ -84,8 +86,9 @@ export class AmpAdTemplateHelper { * @param {!Array|!JsonObject} analyticsValue */ insertAnalytics(element, analyticsValue) { - analyticsValue = /**@type {!Array}*/ - (isArray(analyticsValue) ? analyticsValue : [analyticsValue]); + analyticsValue /**@type {!Array}*/ = isArray(analyticsValue) + ? analyticsValue + : [analyticsValue]; for (let i = 0; i < analyticsValue.length; i++) { const config = analyticsValue[i]; const analyticsEle = element.ownerDocument.createElement('amp-analytics'); @@ -97,10 +100,12 @@ export class AmpAdTemplateHelper { } if (config['inline']) { const scriptElem = createElementWithAttributes( - element.ownerDocument, - 'script', dict({ - 'type': 'application/json', - })); + element.ownerDocument, + 'script', + dict({ + 'type': 'application/json', + }) + ); scriptElem.textContent = JSON.stringify(config['inline']); analyticsEle.appendChild(scriptElem); } @@ -116,8 +121,14 @@ export class AmpAdTemplateHelper { getTemplateProxyUrl_(url) { const cdnUrlSuffix = urls.cdn.slice(8); const loc = parseUrlDeprecated(url); - return loc.origin.indexOf(cdnUrlSuffix) > 0 ? url : - 'https://' + loc.hostname.replace(/-/g, '--').replace(/\./g, '-') + - '.' + cdnUrlSuffix + '/ad/s/' + loc.hostname + loc.pathname; + return loc.origin.indexOf(cdnUrlSuffix) > 0 + ? url + : 'https://' + + loc.hostname.replace(/-/g, '--').replace(/\./g, '-') + + '.' + + cdnUrlSuffix + + '/ad/s/' + + loc.hostname + + loc.pathname; } } diff --git a/extensions/amp-a4a/0.1/amp-ad-utils.js b/extensions/amp-a4a/0.1/amp-ad-utils.js index d3d0eae873520..052eaf0f327c4 100644 --- a/extensions/amp-a4a/0.1/amp-ad-utils.js +++ b/extensions/amp-a4a/0.1/amp-ad-utils.js @@ -39,7 +39,8 @@ export function sendXhrRequest(win, url) { const METADATA_STRINGS = [ ''); if (metadataEnd < 0) { // Couldn't find a metadata blob. - dev().warn(TAG, - 'Could not locate closing script tag for amp meta data in: %s', - creative); + dev().warn( + TAG, + 'Could not locate closing script tag for amp meta data in: %s', + creative + ); return null; } try { const metaDataObj = parseJson( - creative.slice(metadataStart + metadataString.length, metadataEnd)); - let ampRuntimeUtf16CharOffsets = - metaDataObj['ampRuntimeUtf16CharOffsets']; + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); + let ampRuntimeUtf16CharOffsets = metaDataObj['ampRuntimeUtf16CharOffsets']; if (!isArray(ampRuntimeUtf16CharOffsets)) { const headStart = creative.indexOf(''); const headEnd = creative.indexOf(''); const headSubstring = creative.slice( - headStart, headEnd + ''.length); + headStart, + headEnd + ''.length + ); const scriptStart = headSubstring.indexOf(''); if (scriptStart < 0 || scriptEnd < 0) { @@ -96,18 +103,18 @@ export function getAmpAdMetadata(creative) { headStart + scriptStart, headStart + scriptEnd + ''.length, ]; - } else if (ampRuntimeUtf16CharOffsets.length != 2 || - typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || - typeof ampRuntimeUtf16CharOffsets[1] !== 'number') { + } else if ( + ampRuntimeUtf16CharOffsets.length != 2 || + typeof ampRuntimeUtf16CharOffsets[0] !== 'number' || + typeof ampRuntimeUtf16CharOffsets[1] !== 'number' + ) { throw new Error('Invalid runtime offsets'); } const metaData = {}; if (metaDataObj['customElementExtensions']) { - metaData.customElementExtensions = - metaDataObj['customElementExtensions']; + metaData.customElementExtensions = metaDataObj['customElementExtensions']; if (!isArray(metaData.customElementExtensions)) { - throw new Error( - 'Invalid extensions', metaData.customElementExtensions); + throw new Error('Invalid extensions', metaData.customElementExtensions); } } else { metaData.customElementExtensions = []; @@ -121,9 +128,12 @@ export function getAmpAdMetadata(creative) { throw new Error(errorMsg); } metaData.customStylesheets.forEach(stylesheet => { - if (!isObject(stylesheet) || !stylesheet['href'] || - typeof stylesheet['href'] !== 'string' || - !isSecureUrlDeprecated(stylesheet['href'])) { + if ( + !isObject(stylesheet) || + !stylesheet['href'] || + typeof stylesheet['href'] !== 'string' || + !isSecureUrlDeprecated(stylesheet['href']) + ) { throw new Error(errorMsg); } }); @@ -141,8 +151,10 @@ export function getAmpAdMetadata(creative) { return metaData; } catch (err) { dev().warn( - TAG, 'Invalid amp metadata: %s', - creative.slice(metadataStart + metadataString.length, metadataEnd)); + TAG, + 'Invalid amp metadata: %s', + creative.slice(metadataStart + metadataString.length, metadataEnd) + ); return null; } } diff --git a/extensions/amp-a4a/0.1/callout-vendors.js b/extensions/amp-a4a/0.1/callout-vendors.js index d95d8c4776d9a..f3345641f312b 100644 --- a/extensions/amp-a4a/0.1/callout-vendors.js +++ b/extensions/amp-a4a/0.1/callout-vendors.js @@ -37,36 +37,41 @@ let RtcVendorDef; /** @const {!Object} */ export const RTC_VENDORS = { -//////////////////////////////////////////////////////////////////// -// // -// !!! IMPORTANT NOTE !!! // -// // -// If you are adding a new vendor config object to this object, // -// make sure to also update the RTC documentation in these two // -// files under "supported vendors". // -// https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-documentation.md -// https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-publisher-implementation-guide.md -//////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////// + // // + // !!! IMPORTANT NOTE !!! // + // // + // If you are adding a new vendor config object to this object, // + // make sure to also update the RTC documentation in these two // + // files under "supported vendors". // + // https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-documentation.md + // https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-publisher-implementation-guide.md + //////////////////////////////////////////////////////////////////// // Add vendors here medianet: { - url: 'https://amprtc.media.net/rtb/getrtc?cid=CID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&tgt=TGT&curl=CANONICAL_URL&to=TIMEOUT&purl=HREF', + url: + 'https://amprtc.media.net/rtb/getrtc?cid=CID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&tgt=TGT&curl=CANONICAL_URL&to=TIMEOUT&purl=HREF', macros: ['CID'], - errorReportingUrl: 'https://qsearch-a.akamaihd.net/log?logid=kfk&evtid=projectevents&project=amprtc_error&error=ERROR_TYPE&rd=HREF', + errorReportingUrl: + 'https://qsearch-a.akamaihd.net/log?logid=kfk&evtid=projectevents&project=amprtc_error&error=ERROR_TYPE&rd=HREF', disableKeyAppend: true, }, prebidappnexus: { - url: 'https://prebid.adnxs.com/pbs/v1/openrtb2/amp?tag_id=PLACEMENT_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adcid=ADCID&purl=HREF', + url: + 'https://prebid.adnxs.com/pbs/v1/openrtb2/amp?tag_id=PLACEMENT_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adcid=ADCID&purl=HREF', macros: ['PLACEMENT_ID'], disableKeyAppend: true, }, prebidrubicon: { - url: 'https://prebid-server.rubiconproject.com/openrtb2/amp?tag_id=REQUEST_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adc=ADCID&purl=HREF', + url: + 'https://prebid-server.rubiconproject.com/openrtb2/amp?tag_id=REQUEST_ID&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&targeting=TGT&curl=CANONICAL_URL&timeout=TIMEOUT&adc=ADCID&purl=HREF', macros: ['REQUEST_ID'], disableKeyAppend: true, }, indexexchange: { - url: 'https://amp.casalemedia.com/amprtc?v=1&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&s=SITE_ID&p=CANONICAL_URL', + url: + 'https://amp.casalemedia.com/amprtc?v=1&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&s=SITE_ID&p=CANONICAL_URL', macros: ['SITE_ID'], disableKeyAppend: true, }, @@ -76,34 +81,40 @@ export const RTC_VENDORS = { disableKeyAppend: true, }, yieldbot: { - url: 'https://i.yldbt.com/m/YB_PSN/v1/amp/init?curl=CANONICAL_URL&sn=YB_SLOT&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&aup=ATTR(data-slot)&pvi=PAGEVIEWID&tgt=TGT&adcid=ADCID&href=HREF', + url: + 'https://i.yldbt.com/m/YB_PSN/v1/amp/init?curl=CANONICAL_URL&sn=YB_SLOT&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&aup=ATTR(data-slot)&pvi=PAGEVIEWID&tgt=TGT&adcid=ADCID&href=HREF', macros: ['YB_PSN', 'YB_SLOT'], disableKeyAppend: true, }, salesforcedmp: { - url: 'https://cdn.krxd.net/userdata/v2/amp/ORGANIZATION_ID?segments_key=SEGMENTS_KEY&kuid_key=USER_KEY', + url: + 'https://cdn.krxd.net/userdata/v2/amp/ORGANIZATION_ID?segments_key=SEGMENTS_KEY&kuid_key=USER_KEY', macros: ['ORGANIZATION_ID', 'SEGMENTS_KEY', 'USER_KEY'], disableKeyAppend: true, }, purch: { - url: 'https://ads.servebom.com/tmntag.js?v=1.2&fmt=amp&o={%22p%22%3APLACEMENT_ID}&div_id=DIV_ID', + url: + 'https://ads.servebom.com/tmntag.js?v=1.2&fmt=amp&o={%22p%22%3APLACEMENT_ID}&div_id=DIV_ID', macros: ['PLACEMENT_ID', 'DIV_ID'], disableKeyAppend: true, }, aps: { - url: 'https://aax.amazon-adsystem.com/e/dtb/bid?src=PUB_ID&pubid=PUB_UUID&=1&u=CANONICAL_URL&slots=%5B%7B%22sd%22%3A%22ATTR(data-slot)%22%2C%22s%22%3A%5B%22ATTR(width)xATTR(height)%22%5D%2C%22ms%22%3A%22ATTR(data-multi-size)%22%7D%5D&pj=PARAMS', + url: + 'https://aax.amazon-adsystem.com/e/dtb/bid?src=PUB_ID&pubid=PUB_UUID&=1&u=CANONICAL_URL&slots=%5B%7B%22sd%22%3A%22ATTR(data-slot)%22%2C%22s%22%3A%5B%22ATTR(width)xATTR(height)%22%5D%2C%22ms%22%3A%22ATTR(data-multi-size)%22%7D%5D&pj=PARAMS', macros: ['PUB_ID', 'PARAMS', 'PUB_UUID'], disableKeyAppend: true, }, openwrap: { // PubMatic OpenWrap - url: 'https://ow.pubmatic.com/amp?v=1&w=ATTR(width)&h=ATTR(height)&ms=ATTR(data-multi-size)&auId=ATTR(data-slot)&purl=HREF&pubId=PUB_ID&profId=PROFILE_ID', + url: + 'https://ow.pubmatic.com/amp?v=1&w=ATTR(width)&h=ATTR(height)&ms=ATTR(data-multi-size)&auId=ATTR(data-slot)&purl=HREF&pubId=PUB_ID&profId=PROFILE_ID', macros: ['PUB_ID', 'PROFILE_ID'], errorReportingUrl: 'https://ow.pubmatic.com/amp_error?e=ERROR_TYPE&h=HREF', disableKeyAppend: true, }, criteo: { - url: 'https://bidder.criteo.com/amp/rtc?zid=ZONE_ID&nid=NETWORK_ID&psubid=PUBLISHER_SUB_ID&lir=LINE_ITEM_RANGES&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&timeout=TIMEOUT&curl=CANONICAL_URL&href=HREF', + url: + 'https://bidder.criteo.com/amp/rtc?zid=ZONE_ID&nid=NETWORK_ID&psubid=PUBLISHER_SUB_ID&lir=LINE_ITEM_RANGES&w=ATTR(width)&h=ATTR(height)&ow=ATTR(data-override-width)&oh=ATTR(data-override-height)&ms=ATTR(data-multi-size)&slot=ATTR(data-slot)&timeout=TIMEOUT&curl=CANONICAL_URL&href=HREF', macros: ['ZONE_ID', 'NETWORK_ID', 'PUBLISHER_SUB_ID', 'LINE_ITEM_RANGES'], disableKeyAppend: true, }, @@ -112,7 +123,8 @@ export const RTC_VENDORS = { macros: ['NVG_ACC'], }, sonobi: { - url: 'https://apex.go.sonobi.com/trinity.json?key_maker=%7B%22_DIVIDER_ATTR(data-slot)%7C1%22%3A%22PLACEMENT_ID_DIVIDER_ATTR(width)xATTR(height)%2CATTR(data-multi-size)%22%7D&ref=CANONICAL_URL&lib_name=amp&lib_v=0.1&pv=PAGEVIEWID&=1', + url: + 'https://apex.go.sonobi.com/trinity.json?key_maker=%7B%22_DIVIDER_ATTR(data-slot)%7C1%22%3A%22PLACEMENT_ID_DIVIDER_ATTR(width)xATTR(height)%2CATTR(data-multi-size)%22%7D&ref=CANONICAL_URL&lib_name=amp&lib_v=0.1&pv=PAGEVIEWID&=1', disableKeyAppend: true, macros: ['PLACEMENT_ID', '_DIVIDER_'], }, @@ -120,12 +132,14 @@ export const RTC_VENDORS = { // DO NOT MODIFY: Setup for tests if (getMode().localDev || getMode().test) { - RTC_VENDORS['fakevendor'] = /** @type {RtcVendorDef} */({ - url: 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', + RTC_VENDORS['fakevendor'] = /** @type {RtcVendorDef} */ ({ + url: + 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', macros: ['SLOT_ID', 'PAGE_ID', 'FOO_ID'], }); - RTC_VENDORS['fakevendor2'] = /** @type {RtcVendorDef} */({ - url: 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', + RTC_VENDORS['fakevendor2'] = /** @type {RtcVendorDef} */ ({ + url: + 'https://localhost:8000/examples/rtcE1.json?slot_id=SLOT_ID&page_id=PAGE_ID&foo_id=FOO_ID', errorReportingUrl: 'https://localhost:8000/examples/ERROR_TYPE', disableKeyAppend: true, }); diff --git a/extensions/amp-a4a/0.1/cryptographic-validator.js b/extensions/amp-a4a/0.1/cryptographic-validator.js index 22911f7ff96cc..f33470bf623bd 100644 --- a/extensions/amp-a4a/0.1/cryptographic-validator.js +++ b/extensions/amp-a4a/0.1/cryptographic-validator.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - AdResponseType, - Validator, - ValidatorResult, -} from './amp-ad-type-defs'; +import {AdResponseType, Validator, ValidatorResult} from './amp-ad-type-defs'; import {SignatureVerifier, VerificationStatus} from './signature-verifier'; import {getAmpAdMetadata} from './amp-ad-utils'; import {signingServerURLs} from '../../../ads/_a4a-config'; @@ -26,7 +22,7 @@ import {user} from '../../../src/log'; import {utf8Decode} from '../../../src/utils/bytes'; export const SIGNATURE_VERIFIER_PROPERTY_NAME = - 'AMP_FAST_FETCH_SIGNATURE_VERIFIER_'; + 'AMP_FAST_FETCH_SIGNATURE_VERIFIER_'; const TAG = 'amp-ad-cryptographic-validator'; @@ -34,9 +30,13 @@ export class CryptographicValidator extends Validator { /** @param {!Window} win */ getSignatureVerifier_(win) { // TODO(levitzky) extract this into a service registered to ampdoc. - return win[SIGNATURE_VERIFIER_PROPERTY_NAME] || - (win[SIGNATURE_VERIFIER_PROPERTY_NAME] = - new SignatureVerifier(win, signingServerURLs)); + return ( + win[SIGNATURE_VERIFIER_PROPERTY_NAME] || + (win[SIGNATURE_VERIFIER_PROPERTY_NAME] = new SignatureVerifier( + win, + signingServerURLs + )) + ); } /** @@ -49,8 +49,10 @@ export class CryptographicValidator extends Validator { creativeMetadata: getAmpAdMetadata(utf8Decode(bytes)), }; return /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ - type: verificationSucceeded && !!creativeData.creativeMetadata ? - ValidatorResult.AMP : ValidatorResult.NON_AMP, + type: + verificationSucceeded && !!creativeData.creativeMetadata + ? ValidatorResult.AMP + : ValidatorResult.NON_AMP, adResponseType: AdResponseType.CRYPTO, creativeData, }); @@ -59,23 +61,28 @@ export class CryptographicValidator extends Validator { /** @override */ validate(context, unvalidatedBytes, headers) { return this.getSignatureVerifier_(context.win) - .verify(unvalidatedBytes, headers, /* lifecycleCallback */ - (unusedEventName, unusedExtraVariables) => {}) - .then(status => { - switch (status) { - case VerificationStatus.OK: - return this.createOutput_(true, unvalidatedBytes); - case VerificationStatus.UNVERIFIED: - // TODO(levitzky) Preferential render without crypto in some - // instances. - case VerificationStatus.CRYPTO_UNAVAILABLE: - // TODO(@taymonbeal, #9274): differentiate between these - case VerificationStatus.ERROR_KEY_NOT_FOUND: - case VerificationStatus.ERROR_SIGNATURE_MISMATCH: - user().error(TAG, - `Signature verification failed with status ${status}.`); - return this.createOutput_(false, unvalidatedBytes); - } - }); + .verify( + unvalidatedBytes, + headers /* lifecycleCallback */, + (unusedEventName, unusedExtraVariables) => {} + ) + .then(status => { + switch (status) { + case VerificationStatus.OK: + return this.createOutput_(true, unvalidatedBytes); + case VerificationStatus.UNVERIFIED: + // TODO(levitzky) Preferential render without crypto in some + // instances. + case VerificationStatus.CRYPTO_UNAVAILABLE: + // TODO(@taymonbeal, #9274): differentiate between these + case VerificationStatus.ERROR_KEY_NOT_FOUND: + case VerificationStatus.ERROR_SIGNATURE_MISMATCH: + user().error( + TAG, + `Signature verification failed with status ${status}.` + ); + return this.createOutput_(false, unvalidatedBytes); + } + }); } } diff --git a/extensions/amp-a4a/0.1/friendly-frame-renderer.js b/extensions/amp-a4a/0.1/friendly-frame-renderer.js index 02cb9eea527e3..c6fc637b93c92 100644 --- a/extensions/amp-a4a/0.1/friendly-frame-renderer.js +++ b/extensions/amp-a4a/0.1/friendly-frame-renderer.js @@ -29,7 +29,6 @@ export let CreativeData; * Render a validated AMP creative directly in the parent page. */ export class FriendlyFrameRenderer extends Renderer { - /** * Constructs a FriendlyFrameRenderer instance. The instance values here are * used by TemplateRenderer, which inherits from FriendlyFrameRenderer. @@ -40,7 +39,6 @@ export class FriendlyFrameRenderer extends Renderer { /** @override */ render(context, element, creativeData) { - creativeData = /** @type {CreativeData} */ (creativeData); const {size, adUrl} = context; @@ -50,6 +48,10 @@ export class FriendlyFrameRenderer extends Renderer { devAssert(adUrl, 'missing ad request url'); return renderCreativeIntoFriendlyFrame( - adUrl, size, element, creativeMetadata); + adUrl, + size, + element, + creativeMetadata + ); } } diff --git a/extensions/amp-a4a/0.1/friendly-frame-util.js b/extensions/amp-a4a/0.1/friendly-frame-util.js index 5eecd0b119237..a21c10bd95314 100644 --- a/extensions/amp-a4a/0.1/friendly-frame-util.js +++ b/extensions/amp-a4a/0.1/friendly-frame-util.js @@ -21,9 +21,7 @@ import { installFriendlyIframeEmbed, setFriendlyIframeEmbedVisible, } from '../../../src/friendly-iframe-embed'; -import { - installUrlReplacementsForEmbed, -} from '../../../src/service/url-replacements-impl'; +import {installUrlReplacementsForEmbed} from '../../../src/service/url-replacements-impl'; import {setStyle} from '../../../src/style'; /** @@ -39,22 +37,26 @@ import {setStyle} from '../../../src/style'; * @return {!Promise} The iframe into which the creative was rendered. */ export function renderCreativeIntoFriendlyFrame( - adUrl, size, element, creativeMetadata) { + adUrl, + size, + element, + creativeMetadata +) { // Create and setup friendly iframe. - const iframe = /** @type {!HTMLIFrameElement} */( - createElementWithAttributes( - /** @type {!Document} */(element.ownerDocument), - 'iframe', - dict({ - // NOTE: It is possible for either width or height to be 'auto', - // a non-numeric value. - 'height': size.height, - 'width': size.width, - 'frameborder': '0', - 'allowfullscreen': '', - 'allowtransparency': '', - 'scrolling': 'no', - }))); + const iframe = /** @type {!HTMLIFrameElement} */ (createElementWithAttributes( + /** @type {!Document} */ (element.ownerDocument), + 'iframe', + dict({ + // NOTE: It is possible for either width or height to be 'auto', + // a non-numeric value. + 'height': size.height, + 'width': size.width, + 'frameborder': '0', + 'allowfullscreen': '', + 'allowtransparency': '', + 'scrolling': 'no', + }) + )); // TODO(glevitzky): Ensure that applyFillContent or equivalent is called. const fontsArray = []; @@ -68,23 +70,29 @@ export function renderCreativeIntoFriendlyFrame( } return installFriendlyIframeEmbed( - iframe, element, { - host: element, - url: /** @type {string} */ (adUrl), - html: creativeMetadata.minifiedCreative, - extensionIds: creativeMetadata.customElementExtensions || [], - fonts: fontsArray, - }, embedWin => { - installUrlReplacementsForEmbed(element.getAmpDoc(), embedWin, - new A4AVariableSource(element.getAmpDoc(), embedWin)); - }) - .then(friendlyIframeEmbed => { - setFriendlyIframeEmbedVisible( - friendlyIframeEmbed, element.isInViewport()); - // Ensure visibility hidden has been removed (set by boilerplate). - const frameDoc = friendlyIframeEmbed.iframe.contentDocument || - friendlyIframeEmbed.win.document; - setStyle(frameDoc.body, 'visibility', 'visible'); - return iframe; - }); + iframe, + element, + { + host: element, + url: /** @type {string} */ (adUrl), + html: creativeMetadata.minifiedCreative, + extensionIds: creativeMetadata.customElementExtensions || [], + fonts: fontsArray, + }, + embedWin => { + installUrlReplacementsForEmbed( + element.getAmpDoc(), + embedWin, + new A4AVariableSource(element.getAmpDoc(), embedWin) + ); + } + ).then(friendlyIframeEmbed => { + setFriendlyIframeEmbedVisible(friendlyIframeEmbed, element.isInViewport()); + // Ensure visibility hidden has been removed (set by boilerplate). + const frameDoc = + friendlyIframeEmbed.iframe.contentDocument || + friendlyIframeEmbed.win.document; + setStyle(frameDoc.body, 'visibility', 'visible'); + return iframe; + }); } diff --git a/extensions/amp-a4a/0.1/name-frame-renderer.js b/extensions/amp-a4a/0.1/name-frame-renderer.js index 6f0650c224a18..9dd3c3f59ceba 100644 --- a/extensions/amp-a4a/0.1/name-frame-renderer.js +++ b/extensions/amp-a4a/0.1/name-frame-renderer.js @@ -27,27 +27,28 @@ import {utf8Decode} from '../../../src/utils/bytes'; export class NameFrameRenderer extends Renderer { /** @override */ render(context, element, crossDomainData) { - crossDomainData = /** @type {!./amp-ad-type-defs.CrossDomainDataDef} */ ( - crossDomainData); + crossDomainData = /** @type {!./amp-ad-type-defs.CrossDomainDataDef} */ (crossDomainData); if (!crossDomainData.creative && !crossDomainData.rawCreativeBytes) { // No creative, nothing to do. return Promise.resolve(); } - const creative = crossDomainData.creative || - // rawCreativeBytes must exist; if we're here, then `creative` must not - // exist, but the if-statement above guarantees that at least one of - // `creative` || `rawCreativeBytes` exists. - utf8Decode(/** @type {!ArrayBuffer} */ ( - crossDomainData.rawCreativeBytes)); - const srcPath = - getDefaultBootstrapBaseUrl(context.win, 'nameframe'); + const creative = + crossDomainData.creative || + // rawCreativeBytes must exist; if we're here, then `creative` must not + // exist, but the if-statement above guarantees that at least one of + // `creative` || `rawCreativeBytes` exists. + utf8Decode( + /** @type {!ArrayBuffer} */ (crossDomainData.rawCreativeBytes) + ); + const srcPath = getDefaultBootstrapBaseUrl(context.win, 'nameframe'); const contextMetadata = getContextMetadata( - context.win, - element, - context.sentinel, - crossDomainData.additionalContextMetadata); + context.win, + element, + context.sentinel, + crossDomainData.additionalContextMetadata + ); contextMetadata['creative'] = creative; const attributes = dict({ 'src': srcPath, @@ -65,8 +66,10 @@ export class NameFrameRenderer extends Renderer { attributes['data-amp-3p-sentinel'] = crossDomainData.sentinel; } const iframe = createElementWithAttributes( - /** @type {!Document} */ (element.ownerDocument), 'iframe', - /** @type {!JsonObject} */ (attributes)); + /** @type {!Document} */ (element.ownerDocument), + 'iframe', + /** @type {!JsonObject} */ (attributes) + ); // TODO(glevitzky): Ensure that applyFillContent or equivalent is called. element.appendChild(iframe); return Promise.resolve(); diff --git a/extensions/amp-a4a/0.1/real-time-config-manager.js b/extensions/amp-a4a/0.1/real-time-config-manager.js index abb6ddb8a3425..d83f9f18d39e9 100644 --- a/extensions/amp-a4a/0.1/real-time-config-manager.js +++ b/extensions/amp-a4a/0.1/real-time-config-manager.js @@ -32,8 +32,8 @@ const MAX_RTC_CALLOUTS = 5; const MAX_URL_LENGTH = 16384; /** @type {boolean} */ -const ERROR_REPORTING_ENABLED = getMode(window).localDev || - getMode(window).test || Math.random() < 0.01; +const ERROR_REPORTING_ENABLED = + getMode(window).localDev || getMode(window).test || Math.random() < 0.01; /** @typedef {{ urls: (undefined|Array| @@ -113,14 +113,14 @@ export class RealTimeConfigManager { * @return {!Promise} * @private */ - buildErrorResponse_( - error, callout, errorReportingUrl, opt_rtcTime) { + buildErrorResponse_(error, callout, errorReportingUrl, opt_rtcTime) { dev().warn(TAG, `RTC callout to ${callout} caused ${error}`); if (errorReportingUrl) { this.sendErrorMessage(error, errorReportingUrl); } - return Promise.resolve(/**@type {rtcResponseDef} */( - {error, callout, rtcTime: opt_rtcTime || 0})); + return Promise.resolve( + /**@type {rtcResponseDef} */ ({error, callout, rtcTime: opt_rtcTime || 0}) + ); } /** @@ -198,18 +198,26 @@ export class RealTimeConfigManager { if (isArray(sendRegardlessOfConsentState)) { for (let i = 0; i < sendRegardlessOfConsentState.length; i++) { - if (this.consentState_ == - CONSENT_POLICY_STATE[sendRegardlessOfConsentState[i]]) { + if ( + this.consentState_ == + CONSENT_POLICY_STATE[sendRegardlessOfConsentState[i]] + ) { return true; } else if (!CONSENT_POLICY_STATE[sendRegardlessOfConsentState[i]]) { - dev().warn(TAG, 'Invalid RTC consent state given: ' + - `${sendRegardlessOfConsentState[i]}`); + dev().warn( + TAG, + 'Invalid RTC consent state given: ' + + `${sendRegardlessOfConsentState[i]}` + ); } } return false; } - user().warn(TAG, 'Invalid value for sendRegardlessOfConsentState:' + - `${sendRegardlessOfConsentState}`); + user().warn( + TAG, + 'Invalid value for sendRegardlessOfConsentState:' + + `${sendRegardlessOfConsentState}` + ); return !!optIsGloballyValid; } @@ -228,23 +236,29 @@ export class RealTimeConfigManager { * custom URL. */ modifyRtcConfigForConsentStateSettings() { - if (this.consentState_ == undefined || - this.consentState_ == CONSENT_POLICY_STATE.SUFFICIENT || - this.consentState_ == CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED) { + if ( + this.consentState_ == undefined || + this.consentState_ == CONSENT_POLICY_STATE.SUFFICIENT || + this.consentState_ == CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED + ) { return; } const isGloballyValid = this.isValidCalloutForConsentState(this.rtcConfig_); - this.rtcConfig_.urls = (this.rtcConfig_.urls || []).filter( - url => this.isValidCalloutForConsentState(url, isGloballyValid)); + this.rtcConfig_.urls = (this.rtcConfig_.urls || []).filter(url => + this.isValidCalloutForConsentState(url, isGloballyValid) + ); Object.keys(this.rtcConfig_.vendors || {}).forEach(vendor => { - if (!this.isValidCalloutForConsentState( - this.rtcConfig_.vendors[vendor], isGloballyValid)) { + if ( + !this.isValidCalloutForConsentState( + this.rtcConfig_.vendors[vendor], + isGloballyValid + ) + ) { delete this.rtcConfig_.vendors[vendor]; } }); - } /** @@ -275,9 +289,7 @@ export class RealTimeConfigManager { } else { dev().warn(TAG, `Invalid url: ${urlObj}`); } - this.inflateAndSendRtc_(url, - customMacros, - errorReportingUrl); + this.inflateAndSendRtc_(url, customMacros, errorReportingUrl); }); } @@ -291,36 +303,44 @@ export class RealTimeConfigManager { Object.keys(this.rtcConfig_.vendors || []).forEach(vendor => { const vendorObject = RTC_VENDORS[vendor.toLowerCase()]; const url = vendorObject ? vendorObject.url : ''; - const errorReportingUrl = vendorObject && vendorObject.errorReportingUrl ? - vendorObject.errorReportingUrl : ''; + const errorReportingUrl = + vendorObject && vendorObject.errorReportingUrl + ? vendorObject.errorReportingUrl + : ''; if (!url) { return this.promiseArray_.push( - this.buildErrorResponse_( - RTC_ERROR_ENUM.UNKNOWN_VENDOR, vendor, errorReportingUrl)); + this.buildErrorResponse_( + RTC_ERROR_ENUM.UNKNOWN_VENDOR, + vendor, + errorReportingUrl + ) + ); } // There are two valid configurations of the vendor object. // It can either be an object of macros mapping string to string, // or it can be an object with sub-objects, one of which can be // 'macros'. This is for backwards compatability. - const vendorMacros = - isObject(this.rtcConfig_.vendors[vendor]['macros']) ? - this.rtcConfig_.vendors[vendor]['macros'] : - this.rtcConfig_.vendors[vendor]; + const vendorMacros = isObject(this.rtcConfig_.vendors[vendor]['macros']) + ? this.rtcConfig_.vendors[vendor]['macros'] + : this.rtcConfig_.vendors[vendor]; const validVendorMacros = {}; Object.keys(vendorMacros).forEach(macro => { if (!(vendorObject.macros && vendorObject.macros.includes(macro))) { user().error(TAG, `Unknown macro: ${macro} for vendor: ${vendor}`); } else { const value = vendorMacros[macro]; - validVendorMacros[macro] = isObject(value) || isArray(value) ? - JSON.stringify(value) : value; + validVendorMacros[macro] = + isObject(value) || isArray(value) ? JSON.stringify(value) : value; } }); // The ad network defined macros override vendor defined/pub specifed. const macros = Object.assign(validVendorMacros, customMacros); - this.inflateAndSendRtc_(url, - macros, errorReportingUrl, - vendor.toLowerCase()); + this.inflateAndSendRtc_( + url, + macros, + errorReportingUrl, + vendor.toLowerCase() + ); }); } @@ -331,12 +351,12 @@ export class RealTimeConfigManager { * @param {string=} opt_vendor * @private */ - inflateAndSendRtc_(url, - macros, errorReportingUrl, opt_vendor) { + inflateAndSendRtc_(url, macros, errorReportingUrl, opt_vendor) { let {timeoutMillis} = this.rtcConfig_; const callout = opt_vendor || this.getCalloutParam_(url); const checkStillCurrent = this.a4aElement_.verifyStillCurrent.bind( - this.a4aElement_)(); + this.a4aElement_ + )(); /** * The time that it takes to substitute the macros into the URL can vary * depending on what the url requires to be substituted, i.e. a long @@ -347,45 +367,66 @@ export class RealTimeConfigManager { const send = url => { if (Object.keys(this.seenUrls_).length == MAX_RTC_CALLOUTS) { return this.buildErrorResponse_( - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, - callout, errorReportingUrl); + RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, + callout, + errorReportingUrl + ); } - if (!Services.urlForDoc( - this.a4aElement_.element).isSecure(url)) { - return this.buildErrorResponse_(RTC_ERROR_ENUM.INSECURE_URL, - callout, errorReportingUrl); + if (!Services.urlForDoc(this.a4aElement_.element).isSecure(url)) { + return this.buildErrorResponse_( + RTC_ERROR_ENUM.INSECURE_URL, + callout, + errorReportingUrl + ); } if (this.seenUrls_[url]) { - return this.buildErrorResponse_(RTC_ERROR_ENUM.DUPLICATE_URL, - callout, errorReportingUrl); + return this.buildErrorResponse_( + RTC_ERROR_ENUM.DUPLICATE_URL, + callout, + errorReportingUrl + ); } this.seenUrls_[url] = true; if (url.length > MAX_URL_LENGTH) { url = this.truncUrl_(url); } return this.sendRtcCallout_( - url, timeoutMillis, callout, checkStillCurrent, - errorReportingUrl); + url, + timeoutMillis, + callout, + checkStillCurrent, + errorReportingUrl + ); }; const whitelist = {}; - Object.keys(macros).forEach(key => whitelist[key] = true); + Object.keys(macros).forEach(key => (whitelist[key] = true)); const urlReplacementStartTime = Date.now(); - this.promiseArray_.push(Services.timerFor(this.win_).timeoutPromise( - timeoutMillis, - Services.urlReplacementsForDoc(this.a4aElement_.element).expandUrlAsync( - url, macros, whitelist)).then(url => { - checkStillCurrent(); - timeoutMillis -= (urlReplacementStartTime - Date.now()); - return send(url); - }).catch(error => { - return isCancellation(error) ? undefined : - this.buildErrorResponse_(RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT, - callout, errorReportingUrl); - })); + this.promiseArray_.push( + Services.timerFor(this.win_) + .timeoutPromise( + timeoutMillis, + Services.urlReplacementsForDoc( + this.a4aElement_.element + ).expandUrlAsync(url, macros, whitelist) + ) + .then(url => { + checkStillCurrent(); + timeoutMillis -= urlReplacementStartTime - Date.now(); + return send(url); + }) + .catch(error => { + return isCancellation(error) + ? undefined + : this.buildErrorResponse_( + RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT, + callout, + errorReportingUrl + ); + }) + ); } - /** * @param {string} url * @return {string} @@ -404,45 +445,66 @@ export class RealTimeConfigManager { * @return {!Promise} * @private */ - sendRtcCallout_(url, timeoutMillis, callout, checkStillCurrent, - errorReportingUrl) { + sendRtcCallout_( + url, + timeoutMillis, + callout, + checkStillCurrent, + errorReportingUrl + ) { /** * Note: Timeout is enforced by timerFor, not the value of * rtcTime. There are situations where rtcTime could thus * end up being greater than timeoutMillis. */ - return Services.timerFor(this.win_).timeoutPromise( + return Services.timerFor(this.win_) + .timeoutPromise( timeoutMillis, - Services.xhrFor(this.win_).fetchJson( + Services.xhrFor(this.win_) + .fetchJson( // NOTE(bradfrizzell): we could include ampCors:false allowing // the request to be cached across sites but for now assume that // is not a required feature. - url, {credentials: 'include'}).then(res => { - checkStillCurrent(); - return res.text().then(text => { + url, + {credentials: 'include'} + ) + .then(res => { checkStillCurrent(); - const rtcTime = Date.now() - this.rtcStartTime_; - // An empty text response is allowed, not an error. - if (!text) { - return {rtcTime, callout}; - } - const response = tryParseJson(text); - return response ? {response, rtcTime, callout} : - this.buildErrorResponse_( - RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE, callout, - errorReportingUrl, rtcTime); - }); - })).catch(error => { - return isCancellation(error) ? undefined : - this.buildErrorResponse_( - // The relevant error message for timeout looks like it is - // just 'message' but is in fact 'messageXXX' where the - // X's are hidden special characters. That's why we use - // match here. - (/^timeout/.test(error.message)) ? - RTC_ERROR_ENUM.TIMEOUT : RTC_ERROR_ENUM.NETWORK_FAILURE, - callout, errorReportingUrl, Date.now() - this.rtcStartTime_); - }); + return res.text().then(text => { + checkStillCurrent(); + const rtcTime = Date.now() - this.rtcStartTime_; + // An empty text response is allowed, not an error. + if (!text) { + return {rtcTime, callout}; + } + const response = tryParseJson(text); + return response + ? {response, rtcTime, callout} + : this.buildErrorResponse_( + RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE, + callout, + errorReportingUrl, + rtcTime + ); + }); + }) + ) + .catch(error => { + return isCancellation(error) + ? undefined + : this.buildErrorResponse_( + // The relevant error message for timeout looks like it is + // just 'message' but is in fact 'messageXXX' where the + // X's are hidden special characters. That's why we use + // match here. + /^timeout/.test(error.message) + ? RTC_ERROR_ENUM.TIMEOUT + : RTC_ERROR_ENUM.NETWORK_FAILURE, + callout, + errorReportingUrl, + Date.now() - this.rtcStartTime_ + ); + }); } /** @@ -471,8 +533,10 @@ export class RealTimeConfigManager { let timeout; try { - userAssert(rtcConfig['vendors'] || rtcConfig['urls'], - 'RTC Config must specify vendors or urls'); + userAssert( + rtcConfig['vendors'] || rtcConfig['urls'], + 'RTC Config must specify vendors or urls' + ); Object.keys(rtcConfig).forEach(key => { switch (key) { case 'vendors': @@ -484,12 +548,18 @@ export class RealTimeConfigManager { case 'timeoutMillis': timeout = parseInt(rtcConfig[key], 10); if (isNaN(timeout)) { - user().warn(TAG, 'Invalid RTC timeout is NaN, ' + - `using default timeout ${defaultTimeoutMillis}ms`); + user().warn( + TAG, + 'Invalid RTC timeout is NaN, ' + + `using default timeout ${defaultTimeoutMillis}ms` + ); timeout = undefined; } else if (timeout >= defaultTimeoutMillis || timeout < 0) { - user().warn(TAG, `Invalid RTC timeout: ${timeout}ms, ` + - `using default timeout ${defaultTimeoutMillis}ms`); + user().warn( + TAG, + `Invalid RTC timeout: ${timeout}ms, ` + + `using default timeout ${defaultTimeoutMillis}ms` + ); timeout = undefined; } break; @@ -498,14 +568,18 @@ export class RealTimeConfigManager { break; } }); - if (!Object.keys(rtcConfig['vendors'] || {}).length - && !(rtcConfig['urls'] || []).length) { + if ( + !Object.keys(rtcConfig['vendors'] || {}).length && + !(rtcConfig['urls'] || []).length + ) { return false; } const validateErrorReportingUrl = urlObj => { const errorUrl = urlObj['errorReportingUrl']; - if (errorUrl && !Services.urlForDoc( - this.a4aElement_.element).isSecure(errorUrl)) { + if ( + errorUrl && + !Services.urlForDoc(this.a4aElement_.element).isSecure(errorUrl) + ) { dev().warn(TAG, `Insecure RTC errorReportingUrl: ${errorUrl}`); urlObj['errorReportingUrl'] = undefined; } @@ -520,9 +594,9 @@ export class RealTimeConfigManager { // This error would be due to the asserts above. return false; } - rtcConfig['timeoutMillis'] = timeout !== undefined ? - timeout : defaultTimeoutMillis; - this.rtcConfig_ = /** @type {RtcConfigDef} */(rtcConfig); + rtcConfig['timeoutMillis'] = + timeout !== undefined ? timeout : defaultTimeoutMillis; + this.rtcConfig_ = /** @type {RtcConfigDef} */ (rtcConfig); return true; } } diff --git a/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js b/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js index c54be42490037..b9fc94259a1e7 100644 --- a/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js +++ b/extensions/amp-a4a/0.1/refresh-intersection-observer-wrapper.js @@ -14,9 +14,7 @@ * limitations under the License. */ -import { - IntersectionObserverPolyfill, -} from '../../../src/intersection-observer-polyfill'; +import {IntersectionObserverPolyfill} from '../../../src/intersection-observer-polyfill'; import {devAssert} from '../../../src/log'; export class RefreshIntersectionObserverWrapper { @@ -28,12 +26,13 @@ export class RefreshIntersectionObserverWrapper { * @param {Object} config */ constructor(callback, baseElement, config) { - /** * @private @const {!IntersectionObserverPolyfill} */ this.intersectionObserver_ = new IntersectionObserverPolyfill( - callback, config); + callback, + config + ); /** * Stores elements and their original viewportCallback functions so that diff --git a/extensions/amp-a4a/0.1/refresh-manager.js b/extensions/amp-a4a/0.1/refresh-manager.js index 9fbba86b94c89..b19cb02c388bc 100644 --- a/extensions/amp-a4a/0.1/refresh-manager.js +++ b/extensions/amp-a4a/0.1/refresh-manager.js @@ -14,9 +14,7 @@ * limitations under the License. */ -import { - RefreshIntersectionObserverWrapper, -} from './refresh-intersection-observer-wrapper'; +import {RefreshIntersectionObserverWrapper} from './refresh-intersection-observer-wrapper'; import {Services} from '../../../src/services'; import {devAssert, user, userAssert} from '../../../src/log'; import {dict} from '../../../src/utils/object'; @@ -56,18 +54,21 @@ export function getPublisherSpecifiedRefreshInterval(element, win) { return checkAndSanitizeRefreshInterval(refreshInterval); } let metaTag; - const metaTagContent = ((metaTag = win.document - .getElementsByName(METATAG_NAME)) - && metaTag[0] - && metaTag[0].getAttribute('content')); + const metaTagContent = + (metaTag = win.document.getElementsByName(METATAG_NAME)) && + metaTag[0] && + metaTag[0].getAttribute('content'); if (!metaTagContent) { return null; } const networkIntervalPairs = metaTagContent.split(','); for (let i = 0; i < networkIntervalPairs.length; i++) { const pair = networkIntervalPairs[i].split('='); - userAssert(pair.length == 2, 'refresh metadata config must be of ' + - 'the form `network_type=refresh_interval`'); + userAssert( + pair.length == 2, + 'refresh metadata config must be of ' + + 'the form `network_type=refresh_interval`' + ); if (pair[0].toLowerCase() == element.getAttribute('type').toLowerCase()) { return checkAndSanitizeRefreshInterval(pair[1]); } @@ -85,11 +86,12 @@ export function getPublisherSpecifiedRefreshInterval(element, win) { */ function checkAndSanitizeRefreshInterval(refreshInterval) { const refreshIntervalNum = Number(refreshInterval); - if (isNaN(refreshIntervalNum) || - refreshIntervalNum < MIN_REFRESH_INTERVAL) { - user().warn(TAG, - 'invalid refresh interval, must be a number no less than ' + - `${MIN_REFRESH_INTERVAL}: ${refreshInterval}`); + if (isNaN(refreshIntervalNum) || refreshIntervalNum < MIN_REFRESH_INTERVAL) { + user().warn( + TAG, + 'invalid refresh interval, must be a number no less than ' + + `${MIN_REFRESH_INTERVAL}: ${refreshInterval}` + ); return null; } return refreshIntervalNum * 1000; @@ -163,27 +165,30 @@ let refreshManagerIdCounter = 0; * @return {?RefreshManager} */ export function getRefreshManager(a4a, opt_predicate) { - const refreshInterval = - getPublisherSpecifiedRefreshInterval(a4a.element, a4a.win); + const refreshInterval = getPublisherSpecifiedRefreshInterval( + a4a.element, + a4a.win + ); if (!refreshInterval || (opt_predicate && !opt_predicate())) { return null; } - return new RefreshManager(a4a, dict({ - 'visiblePercentageMin': 50, - 'continuousTimeMin': 1, - }), refreshInterval); + return new RefreshManager( + a4a, + dict({ + 'visiblePercentageMin': 50, + 'continuousTimeMin': 1, + }), + refreshInterval + ); } - export class RefreshManager { - /** * @param {!./amp-a4a.AmpA4A} a4a The AmpA4A instance to be refreshed. * @param {!JsonObject} config * @param {number} refreshInterval */ constructor(a4a, config, refreshInterval) { - /** @private {string} */ this.state_ = RefreshLifecycleState.INITIAL; @@ -228,13 +233,18 @@ export class RefreshManager { * @return {(!IntersectionObserver|!RefreshIntersectionObserverWrapper)} */ getIntersectionObserverWithThreshold_(threshold) { - const thresholdString = String(threshold); - return observers[thresholdString] || - (observers[thresholdString] = 'IntersectionObserver' in this.win_ + return ( + observers[thresholdString] || + (observers[thresholdString] = + 'IntersectionObserver' in this.win_ ? new this.win_['IntersectionObserver'](this.ioCallback_, {threshold}) : new RefreshIntersectionObserverWrapper( - this.ioCallback_, this.a4a_, {threshold})); + this.ioCallback_, + this.a4a_, + {threshold} + )) + ); } /** @@ -260,21 +270,27 @@ export class RefreshManager { // threshold. If this timer runs out without interruption, then all // viewability conditions have been met, and we can begin the refresh // timer. - if (entry.intersectionRatio >= - refreshManager.config_['visiblePercentageMin']) { + if ( + entry.intersectionRatio >= + refreshManager.config_['visiblePercentageMin'] + ) { refreshManager.state_ = RefreshLifecycleState.VIEW_PENDING; refreshManager.visibilityTimeoutId_ = refreshManager.timer_.delay( - () => { - refreshManager.state_ = RefreshLifecycleState.REFRESH_PENDING; - refreshManager.startRefreshTimer_(); - }, refreshManager.config_['continuousTimeMin']); + () => { + refreshManager.state_ = RefreshLifecycleState.REFRESH_PENDING; + refreshManager.startRefreshTimer_(); + }, + refreshManager.config_['continuousTimeMin'] + ); } break; case RefreshLifecycleState.VIEW_PENDING: // If the element goes off screen before the minimum on screen time // duration elapses, place it back into INITIAL state. - if (entry.intersectionRatio < - refreshManager.config_['visiblePercentageMin']) { + if ( + entry.intersectionRatio < + refreshManager.config_['visiblePercentageMin'] + ) { refreshManager.timer_.cancel(refreshManager.visibilityTimeoutId_); refreshManager.visibilityTimeoutId_ = null; refreshManager.state_ = RefreshLifecycleState.INITIAL; @@ -295,13 +311,13 @@ export class RefreshManager { switch (this.state_) { case RefreshLifecycleState.INITIAL: this.getIntersectionObserverWithThreshold_( - this.config_['visiblePercentageMin']).observe(this.element_); + this.config_['visiblePercentageMin'] + ).observe(this.element_); break; case RefreshLifecycleState.REFRESH_PENDING: case RefreshLifecycleState.VIEW_PENDING: default: break; - } } @@ -329,9 +345,11 @@ export class RefreshManager { * @return {!JsonObject} */ convertAndSanitizeConfiguration_(config) { - devAssert(config['visiblePercentageMin'] >= 0 && + devAssert( + config['visiblePercentageMin'] >= 0 && config['visiblePercentageMin'] <= 100, - 'visiblePercentageMin for refresh must be in the range [0, 100]'); + 'visiblePercentageMin for refresh must be in the range [0, 100]' + ); // Convert seconds to milliseconds. config['continuousTimeMin'] *= 1000; config['visiblePercentageMin'] /= 100; @@ -343,7 +361,7 @@ export class RefreshManager { */ unobserve() { this.getIntersectionObserverWithThreshold_( - this.config_['visiblePercentageMin']).unobserve(this.element_); + this.config_['visiblePercentageMin'] + ).unobserve(this.element_); } } - diff --git a/extensions/amp-a4a/0.1/signature-verifier.js b/extensions/amp-a4a/0.1/signature-verifier.js index a9d65690e5e70..6f62e143ec312 100644 --- a/extensions/amp-a4a/0.1/signature-verifier.js +++ b/extensions/amp-a4a/0.1/signature-verifier.js @@ -29,7 +29,6 @@ export const AMP_SIGNATURE_HEADER = 'AMP-Fast-Fetch-Signature'; * @enum {number} */ export const VerificationStatus = { - /** The ad was successfully verified as AMP. */ OK: 0, @@ -58,7 +57,6 @@ export const VerificationStatus = { * i.e. is not SSL. */ CRYPTO_UNAVAILABLE: 4, - }; /** @@ -75,7 +73,6 @@ export const VerificationStatus = { * introduced as an experiment. */ export class SignatureVerifier { - /** * @param {!Window} win * @param {!Object} signingServerURLs a map from the name of @@ -134,8 +131,10 @@ export class SignatureVerifier { * * @private @const {function(): number} */ - this.getNow_ = (win.performance && win.performance.now) ? - win.performance.now.bind(win.performance) : Date.now; + this.getNow_ = + win.performance && win.performance.now + ? win.performance.now.bind(win.performance) + : Date.now; } /** @@ -168,8 +167,7 @@ export class SignatureVerifier { * @return {!Promise} */ verify(creative, headers) { - const signatureFormat = - /^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+):([A-Za-z0-9+/]{341}[AQgw]==)$/; + const signatureFormat = /^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+):([A-Za-z0-9+/]{341}[AQgw]==)$/; if (!headers.has(AMP_SIGNATURE_HEADER)) { return Promise.resolve(VerificationStatus.UNVERIFIED); } @@ -178,11 +176,17 @@ export class SignatureVerifier { if (!match) { // TODO(@taymonbeal, #9274): replace this with real error reporting user().error( - 'AMP-A4A', `Invalid signature header: ${headerValue.split(':')[0]}`); + 'AMP-A4A', + `Invalid signature header: ${headerValue.split(':')[0]}` + ); return Promise.resolve(VerificationStatus.ERROR_SIGNATURE_MISMATCH); } return this.verifyCreativeAndSignature( - match[1], match[2], base64DecodeToBytes(match[3]), creative); + match[1], + match[2], + base64DecodeToBytes(match[3]), + creative + ); } /** @@ -207,15 +211,21 @@ export class SignatureVerifier { * @visibleForTesting */ verifyCreativeAndSignature( - signingServiceName, keypairId, signature, creative) { + signingServiceName, + keypairId, + signature, + creative + ) { if (!this.signers_) { // Web Cryptography isn't available. return Promise.resolve(VerificationStatus.CRYPTO_UNAVAILABLE); } const signer = this.signers_[signingServiceName]; devAssert( - signer, 'Keyset for service %s not loaded before verification', - signingServiceName); + signer, + 'Keyset for service %s not loaded before verification', + signingServiceName + ); return signer.promise.then(success => { if (!success) { // The public keyset couldn't be fetched and imported. Probably a @@ -226,19 +236,25 @@ export class SignatureVerifier { if (keyPromise === undefined) { // We don't have this key, but maybe the cache is stale; try // cachebusting. - signer.promise = - this.fetchAndAddKeys_(signer.keys, signingServiceName, keypairId) - .then(success => { - if (signer.keys[keypairId] === undefined) { - // We still don't have this key; make sure we never try - // again. - signer.keys[keypairId] = null; - } - return success; - }); + signer.promise = this.fetchAndAddKeys_( + signer.keys, + signingServiceName, + keypairId + ).then(success => { + if (signer.keys[keypairId] === undefined) { + // We still don't have this key; make sure we never try + // again. + signer.keys[keypairId] = null; + } + return success; + }); // This "recursive" call can recurse at most once. return this.verifyCreativeAndSignature( - signingServiceName, keypairId, signature, creative); + signingServiceName, + keypairId, + signature, + creative + ); } else if (keyPromise === null) { // We don't have this key and we already tried cachebusting. return VerificationStatus.ERROR_KEY_NOT_FOUND; @@ -251,19 +267,21 @@ export class SignatureVerifier { } const crypto = Services.cryptoFor(this.win_); return crypto.verifyPkcs(key, signature, creative).then( - result => result ? VerificationStatus.OK : - VerificationStatus.ERROR_SIGNATURE_MISMATCH, - err => { - // Web Cryptography rejected the verification attempt. This - // hopefully won't happen in the wild, but browsers can be weird - // about this, so we need to guard against the possibility. - // Phone home to the AMP Project so that we can understand why - // this occurred. - const message = err && err.message; - dev().error( - 'AMP-A4A', `Failed to verify signature: ${message}`); - return VerificationStatus.UNVERIFIED; - }); + result => + result + ? VerificationStatus.OK + : VerificationStatus.ERROR_SIGNATURE_MISMATCH, + err => { + // Web Cryptography rejected the verification attempt. This + // hopefully won't happen in the wild, but browsers can be weird + // about this, so we need to guard against the possibility. + // Phone home to the AMP Project so that we can understand why + // this occurred. + const message = err && err.message; + dev().error('AMP-A4A', `Failed to verify signature: ${message}`); + return VerificationStatus.UNVERIFIED; + } + ); }); } }); @@ -292,89 +310,97 @@ export class SignatureVerifier { } // TODO(@taymonbeal, #11088): consider a timeout on this fetch return Services.xhrFor(this.win_) - .fetchJson(url, { - mode: 'cors', - method: 'GET', - // This should be cached across publisher domains, so don't append - // __amp_source_origin to the URL. - ampCors: false, - credentials: 'omit', - }).then( - response => { - // These are assertions on signing service behavior required by - // the spec. However, nothing terrible happens if they aren't met - // and there's no meaningful error recovery to be done if they - // fail, so we don't need to do them at runtime in production. - // They're included in dev mode as a debugging aid. - devAssert( - response.status === 200, - 'Fast Fetch keyset spec requires status code 200'); - devAssert( - response.headers.get('Content-Type') == - 'application/jwk-set+json', - 'Fast Fetch keyset spec requires Content-Type: ' + - 'application/jwk-set+json'); - return response.json().then( - jsonResponse => { - const jwkSet = /** @type {!JsonObject} */ (jsonResponse); - // This is supposed to be a JSON Web Key Set, as defined in - // Section 5 of RFC 7517. However, the signing service could - // misbehave and send an arbitrary JSON value, so we have to - // type-check at runtime. - if (!jwkSet || !isArray(jwkSet['keys'])) { + .fetchJson(url, { + mode: 'cors', + method: 'GET', + // This should be cached across publisher domains, so don't append + // __amp_source_origin to the URL. + ampCors: false, + credentials: 'omit', + }) + .then( + response => { + // These are assertions on signing service behavior required by + // the spec. However, nothing terrible happens if they aren't met + // and there's no meaningful error recovery to be done if they + // fail, so we don't need to do them at runtime in production. + // They're included in dev mode as a debugging aid. + devAssert( + response.status === 200, + 'Fast Fetch keyset spec requires status code 200' + ); + devAssert( + response.headers.get('Content-Type') == 'application/jwk-set+json', + 'Fast Fetch keyset spec requires Content-Type: ' + + 'application/jwk-set+json' + ); + return response.json().then( + jsonResponse => { + const jwkSet = /** @type {!JsonObject} */ (jsonResponse); + // This is supposed to be a JSON Web Key Set, as defined in + // Section 5 of RFC 7517. However, the signing service could + // misbehave and send an arbitrary JSON value, so we have to + // type-check at runtime. + if (!jwkSet || !isArray(jwkSet['keys'])) { + signingServiceError( + signingServiceName, + `Key set (${JSON.stringify(jwkSet)}) has no "keys"` + ); + return false; + } + jwkSet['keys'].forEach(jwk => { + if (!jwk || typeof jwk['kid'] != 'string') { + signingServiceError( + signingServiceName, + `Key (${JSON.stringify(jwk)}) has no "kid"` + ); + } else if (keys[jwk['kid']] === undefined) { + // We haven't seen this keypair ID before. + keys[jwk['kid']] = Services.cryptoFor(this.win_) + .importPkcsKey(jwk) + .catch(err => { + // Web Cryptography rejected the key + // import attempt. Either the signing + // service sent a malformed key or the + // browser is doing something weird. + const jwkData = JSON.stringify(jwk); + const message = err && err.message; signingServiceError( - signingServiceName, - `Key set (${JSON.stringify(jwkSet)}) has no "keys"`); - return false; - } - jwkSet['keys'].forEach(jwk => { - if (!jwk || typeof jwk['kid'] != 'string') { - signingServiceError( - signingServiceName, - `Key (${JSON.stringify(jwk)}) has no "kid"`); - } else if (keys[jwk['kid']] === undefined) { - // We haven't seen this keypair ID before. - keys[jwk['kid']] = - Services.cryptoFor(this.win_).importPkcsKey(jwk) - .catch(err => { - // Web Cryptography rejected the key - // import attempt. Either the signing - // service sent a malformed key or the - // browser is doing something weird. - const jwkData = JSON.stringify(jwk); - const message = err && err.message; - signingServiceError( - signingServiceName, - `Failed to import key (${ - jwkData - }): ${message}`); - return null; - }); - } - }); - return true; - }, - err => { - // The signing service didn't send valid JSON. - signingServiceError( signingServiceName, - `Failed to parse JSON: ${err && err.response}`); - return false; - }); + `Failed to import key (${jwkData}): ${message}` + ); + return null; + }); + } + }); + return true; }, err => { - // Some kind of error occurred during the XHR. This could be a lot - // of things (and we have no type information), but if there's no - // `response` it's probably a network connectivity failure, so we - // ignore it. Unfortunately, we can't distinguish this from a CORS - // problem. - if (err && err.response) { - // This probably indicates a non-2xx HTTP status code. - signingServiceError( - signingServiceName, `Status code ${err.response.status}`); - } + // The signing service didn't send valid JSON. + signingServiceError( + signingServiceName, + `Failed to parse JSON: ${err && err.response}` + ); return false; - }); + } + ); + }, + err => { + // Some kind of error occurred during the XHR. This could be a lot + // of things (and we have no type information), but if there's no + // `response` it's probably a network connectivity failure, so we + // ignore it. Unfortunately, we can't distinguish this from a CORS + // problem. + if (err && err.response) { + // This probably indicates a non-2xx HTTP status code. + signingServiceError( + signingServiceName, + `Status code ${err.response.status}` + ); + } + return false; + } + ); } } @@ -389,5 +415,7 @@ export class SignatureVerifier { */ function signingServiceError(signingServiceName, message) { dev().error( - 'AMP-A4A', `Signing service error for ${signingServiceName}: ${message}`); + 'AMP-A4A', + `Signing service error for ${signingServiceName}: ${message}` + ); } diff --git a/extensions/amp-a4a/0.1/template-renderer.js b/extensions/amp-a4a/0.1/template-renderer.js index 6bad018f8476a..eac63ebffd304 100644 --- a/extensions/amp-a4a/0.1/template-renderer.js +++ b/extensions/amp-a4a/0.1/template-renderer.js @@ -29,12 +29,10 @@ import {renderCreativeIntoFriendlyFrame} from './friendly-frame-util'; */ export let CreativeData; - /** * Render AMP creative into FriendlyFrame via templatization. */ export class TemplateRenderer extends Renderer { - /** * Constructs a TemplateRenderer instance. */ @@ -44,7 +42,6 @@ export class TemplateRenderer extends Renderer { /** @override */ render(context, element, creativeData) { - creativeData = /** @type {CreativeData} */ (creativeData); const {size, adUrl} = context; @@ -54,29 +51,34 @@ export class TemplateRenderer extends Renderer { devAssert(adUrl, 'missing ad request url'); return renderCreativeIntoFriendlyFrame( - adUrl, size, element, creativeMetadata) - .then(iframe => { - const templateData = - /** @type {!./amp-ad-type-defs.AmpTemplateCreativeDef} */ ( - creativeData.templateData); - const {data} = templateData; - if (!data) { - return Promise.resolve(); + adUrl, + size, + element, + creativeMetadata + ).then(iframe => { + const templateData = + /** @type {!./amp-ad-type-defs.AmpTemplateCreativeDef} */ (creativeData.templateData); + const {data} = templateData; + if (!data) { + return Promise.resolve(); + } + const templateHelper = getAmpAdTemplateHelper(context.win); + return templateHelper + .render(data, iframe.contentWindow.document.body) + .then(renderedElement => { + const {analytics} = templateData; + if (analytics) { + templateHelper.insertAnalytics(renderedElement, analytics); } - const templateHelper = getAmpAdTemplateHelper(context.win); - return templateHelper - .render(data, iframe.contentWindow.document.body) - .then(renderedElement => { - const {analytics} = templateData; - if (analytics) { - templateHelper.insertAnalytics(renderedElement, analytics); - } - // This element must exist, or #render() would have thrown. - const templateElement = iframe.contentWindow.document - .querySelector('template'); - templateElement.parentNode - .replaceChild(renderedElement, templateElement); - }); + // This element must exist, or #render() would have thrown. + const templateElement = iframe.contentWindow.document.querySelector( + 'template' + ); + templateElement.parentNode.replaceChild( + renderedElement, + templateElement + ); }); + }); } } diff --git a/extensions/amp-a4a/0.1/template-validator.js b/extensions/amp-a4a/0.1/template-validator.js index f82e139c394dc..426c0c8c1dfa1 100644 --- a/extensions/amp-a4a/0.1/template-validator.js +++ b/extensions/amp-a4a/0.1/template-validator.js @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - AdResponseType, - Validator, - ValidatorResult, -} from './amp-ad-type-defs'; +import {AdResponseType, Validator, ValidatorResult} from './amp-ad-type-defs'; import {AmpAdTemplateHelper} from '../../amp-a4a/0.1/amp-ad-template-helper'; import {Services} from '../../../src/services'; import {getAmpAdMetadata} from './amp-ad-utils'; @@ -40,8 +36,9 @@ let ampAdTemplateHelper; * @return {!AmpAdTemplateHelper} */ export function getAmpAdTemplateHelper(win) { - return ampAdTemplateHelper || - (ampAdTemplateHelper = new AmpAdTemplateHelper(win)); + return ( + ampAdTemplateHelper || (ampAdTemplateHelper = new AmpAdTemplateHelper(win)) + ); } /** @@ -50,55 +47,64 @@ export function getAmpAdTemplateHelper(win) { export class TemplateValidator extends Validator { /** @override */ validate(context, unvalidatedBytes, headers) { - const body = utf8Decode(/** @type {!ArrayBuffer} */ (unvalidatedBytes)); - const parsedResponseBody = - /** @type {./amp-ad-type-defs.AmpTemplateCreativeDef} */ ( - tryParseJson(body)); + const parsedResponseBody = /** @type {./amp-ad-type-defs.AmpTemplateCreativeDef} */ (tryParseJson( + body + )); // If we're missing the relevant header, or headers altogether, we cannot // proceed. In this case, we return a NON_AMP response, since we cannot // ensure this template will be valid AMP. We will pass the body of the // response as the creative, and downstream renderers may attempt to render // it as a non-AMP creative within a cross-domain iframe. - if (!parsedResponseBody || !headers || - (headers.get(AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== 'amp-mustache' && - headers.get(DEPRECATED_AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== - 'amp-mustache')) { + if ( + !parsedResponseBody || + !headers || + (headers.get(AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== 'amp-mustache' && + headers.get(DEPRECATED_AMP_TEMPLATED_CREATIVE_HEADER_NAME) !== + 'amp-mustache') + ) { return Promise.resolve( - /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ - creativeData: { - creative: body, - }, - adResponseType: AdResponseType.TEMPLATE, - type: ValidatorResult.NON_AMP, - })); + /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ + creativeData: { + creative: body, + }, + adResponseType: AdResponseType.TEMPLATE, + type: ValidatorResult.NON_AMP, + }) + ); } return getAmpAdTemplateHelper(context.win) - .fetch(parsedResponseBody.templateUrl) - .then(template => { - const creativeMetadata = getAmpAdMetadata(template); - if (parsedResponseBody.analytics) { - pushIfNotExist( - creativeMetadata['customElementExtensions'], 'amp-analytics'); - } + .fetch(parsedResponseBody.templateUrl) + .then(template => { + const creativeMetadata = getAmpAdMetadata(template); + if (parsedResponseBody.analytics) { pushIfNotExist( - creativeMetadata['customElementExtensions'], 'amp-mustache'); + creativeMetadata['customElementExtensions'], + 'amp-analytics' + ); + } + pushIfNotExist( + creativeMetadata['customElementExtensions'], + 'amp-mustache' + ); - const extensions = Services.extensionsFor(context.win); - creativeMetadata.customElementExtensions.forEach( - extensionId => extensions./*OK*/preloadExtension(extensionId)); - // TODO(levitzky) Add preload logic for fonts / images. - return Promise.resolve( - /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ - creativeData: { - templateData: parsedResponseBody, - creativeMetadata, - }, - adResponseType: AdResponseType.TEMPLATE, - type: ValidatorResult.AMP, - })); - }); + const extensions = Services.extensionsFor(context.win); + creativeMetadata.customElementExtensions.forEach(extensionId => + extensions./*OK*/ preloadExtension(extensionId) + ); + // TODO(levitzky) Add preload logic for fonts / images. + return Promise.resolve( + /** @type {!./amp-ad-type-defs.ValidatorOutput} */ ({ + creativeData: { + templateData: parsedResponseBody, + creativeMetadata, + }, + adResponseType: AdResponseType.TEMPLATE, + type: ValidatorResult.AMP, + }) + ); + }); } } diff --git a/extensions/amp-a4a/0.1/test/fetch-mock.js b/extensions/amp-a4a/0.1/test/fetch-mock.js index 1a07d00822a64..8ed3abb914c09 100644 --- a/extensions/amp-a4a/0.1/test/fetch-mock.js +++ b/extensions/amp-a4a/0.1/test/fetch-mock.js @@ -41,7 +41,6 @@ export let MockResponse; * it. The window is stubbed when this class's constructor is called. */ export class FetchMock { - /** @param {!Window} win */ constructor(win) { /** @private {!Window} */ @@ -116,17 +115,18 @@ export class FetchMock { } route.called = true; return Promise.resolve( - typeof route.response == 'function' ? - route.response() : route.response) - .then(data => { - if (data === null || typeof data == 'string') { - return new Response(data); - } else { - const {body, status, headers} = data; - return new Response(body, /** @type {!ResponseInit} */ ( - {status, headers})); - } - }); + typeof route.response == 'function' ? route.response() : route.response + ).then(data => { + if (data === null || typeof data == 'string') { + return new Response(data); + } else { + const {body, status, headers} = data; + return new Response( + body, + /** @type {!ResponseInit} */ ({status, headers}) + ); + } + }); } } diff --git a/extensions/amp-a4a/0.1/test/test-a4a-integration.js b/extensions/amp-a4a/0.1/test/test-a4a-integration.js index 10c413ea13495..e64030a27d1c9 100644 --- a/extensions/amp-a4a/0.1/test/test-a4a-integration.js +++ b/extensions/amp-a4a/0.1/test/test-a4a-integration.js @@ -32,9 +32,7 @@ import { resetScheduledElementForTesting, upgradeOrRegisterElement, } from '../../../../src/service/custom-element-registry'; -import { - data as validCSSAmp, -} from './testdata/valid_css_at_rules_amp.reserialized'; +import {data as validCSSAmp} from './testdata/valid_css_at_rules_amp.reserialized'; // Integration tests for A4A. These stub out accesses to the outside world // (e.g., XHR requests and interfaces to ad network-specific code), but @@ -70,8 +68,10 @@ function expectRenderedInXDomainIframe(element, src) { // Note: Unlike expectRenderedInXDomainIframe, this doesn't return a Promise // because it doesn't (cannot) inspect the contents of the iframe. expect(element, 'ad element').to.be.ok; - expect(element.querySelector('iframe[srcdoc]'), - 'does not have a friendly iframe child').to.not.be.ok; + expect( + element.querySelector('iframe[srcdoc]'), + 'does not have a friendly iframe child' + ).to.not.be.ok; const child = element.querySelector('iframe[src]'); expect(child, 'iframe child').to.be.ok; expect(child.getAttribute('src')).to.contain.string(src); @@ -89,7 +89,9 @@ describe('integration test: a4a', () => { beforeEach(() => { sandbox = sinon.sandbox; a4aRegistry = getA4ARegistry(); - a4aRegistry['mock'] = () => {return true;}; + a4aRegistry['mock'] = () => { + return true; + }; return createIframePromise().then(f => { fixture = f; fetchMock = new FetchMock(fixture.win); @@ -100,8 +102,10 @@ describe('integration test: a4a', () => { }); } fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); adResponse = { headers: {'AMP-Access-Control-Allow-Source-Origin': 'about:srcdoc'}, body: validCSSAmp.reserialized, @@ -119,7 +123,7 @@ describe('integration test: a4a', () => { }); afterEach(() => { - fetchMock./*OK*/restore(); + fetchMock./*OK*/ restore(); sandbox.restore(); resetScheduledElementForTesting(window, 'amp-a4a'); delete a4aRegistry['mock']; @@ -151,8 +155,10 @@ describe('integration test: a4a', () => { // TODO(tdrl) Currently layoutCallback rejects, even though something *is* // rendered. This should be fixed in a refactor, and we should change this // .catch to a .then. - const forceCollapseStub = - sandbox.spy(MockA4AImpl.prototype, 'forceCollapse'); + const forceCollapseStub = sandbox.spy( + MockA4AImpl.prototype, + 'forceCollapse' + ); return fixture.addElement(a4aElement).catch(error => { expect(error.message).to.contain.string('Testing network error'); expect(error.message).to.contain.string('AMP-A4A-'); @@ -164,49 +170,57 @@ describe('integration test: a4a', () => { it('should collapse slot when creative response has code 204', () => { adResponse.status = 204; adResponse.body = null; - const forceCollapseStub = - sandbox.spy(MockA4AImpl.prototype, 'forceCollapse'); + const forceCollapseStub = sandbox.spy( + MockA4AImpl.prototype, + 'forceCollapse' + ); return fixture.addElement(a4aElement).then(() => { expect(forceCollapseStub).to.be.calledOnce; }); }); - it('should collapse slot when creative response.arrayBuffer() is empty', - () => { - adResponse.body = ''; - const forceCollapseStub = - sandbox.spy(MockA4AImpl.prototype, 'forceCollapse'); - return fixture.addElement(a4aElement).then(unusedElement => { - expect(forceCollapseStub).to.be.calledOnce; - }); - }); + it('should collapse slot when creative response.arrayBuffer() is empty', () => { + adResponse.body = ''; + const forceCollapseStub = sandbox.spy( + MockA4AImpl.prototype, + 'forceCollapse' + ); + return fixture.addElement(a4aElement).then(unusedElement => { + expect(forceCollapseStub).to.be.calledOnce; + }); + }); it('should continue to show old creative after refresh and no fill', () => { return fixture.addElement(a4aElement).then(() => { - return expectRenderedInFriendlyIframe(a4aElement, 'Hello, world.') - .then(() => { - const a4a = new MockA4AImpl(a4aElement); - const initiateAdRequestMock = sandbox.stub( - MockA4AImpl.prototype, 'initiateAdRequest').callsFake( - () => { - a4a.adPromise_ = Promise.resolve(); - // This simulates calling forceCollapse, without tripping - // up any unrelated asserts. - a4a.isRefreshing = false; - }); - const tearDownSlotMock = - sandbox.stub(MockA4AImpl.prototype, 'tearDownSlot'); - tearDownSlotMock.returns(undefined); - const destroyFrameSpy = - sandbox.spy(MockA4AImpl.prototype, 'destroyFrame'); - const callback = sandbox.spy(); - return a4a.refresh(callback).then(() => { - expect(initiateAdRequestMock).to.be.called; - expect(tearDownSlotMock).to.be.called; - expect(destroyFrameSpy).to.not.be.called; - expect(callback).to.be.called; + return expectRenderedInFriendlyIframe(a4aElement, 'Hello, world.').then( + () => { + const a4a = new MockA4AImpl(a4aElement); + const initiateAdRequestMock = sandbox + .stub(MockA4AImpl.prototype, 'initiateAdRequest') + .callsFake(() => { + a4a.adPromise_ = Promise.resolve(); + // This simulates calling forceCollapse, without tripping + // up any unrelated asserts. + a4a.isRefreshing = false; }); + const tearDownSlotMock = sandbox.stub( + MockA4AImpl.prototype, + 'tearDownSlot' + ); + tearDownSlotMock.returns(undefined); + const destroyFrameSpy = sandbox.spy( + MockA4AImpl.prototype, + 'destroyFrame' + ); + const callback = sandbox.spy(); + return a4a.refresh(callback).then(() => { + expect(initiateAdRequestMock).to.be.called; + expect(tearDownSlotMock).to.be.called; + expect(destroyFrameSpy).to.not.be.called; + expect(callback).to.be.called; }); + } + ); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-a4a-var-source.js b/extensions/amp-a4a/0.1/test/test-a4a-var-source.js index 40112a8534c44..82059773b9b6f 100644 --- a/extensions/amp-a4a/0.1/test/test-a4a-var-source.js +++ b/extensions/amp-a4a/0.1/test/test-a4a-var-source.js @@ -16,12 +16,9 @@ import {A4AVariableSource} from '../a4a-variable-source'; import {createIframePromise} from '../../../../testing/iframe'; -import {installDocumentInfoServiceForDoc} from - '../../../../src/service/document-info-impl'; - +import {installDocumentInfoServiceForDoc} from '../../../../src/service/document-info-impl'; describe('A4AVariableSource', () => { - let varSource; beforeEach(() => { @@ -49,8 +46,9 @@ describe('A4AVariableSource', () => { }); it('should replace CANONICAL_URL', () => { - expect(expandSync('CANONICAL_URL')) - .to.equal('https://pinterest.com:8080/pin1'); + expect(expandSync('CANONICAL_URL')).to.equal( + 'https://pinterest.com:8080/pin1' + ); }); it('should replace NAV_TIMING', () => { @@ -70,7 +68,8 @@ describe('A4AVariableSource', () => { it('should replace HTML_ATTR', () => { expect(expandSync('HTML_ATTR', ['div', 'id'])).to.equal( - '[{\"id\":\"parent\"}]'); + '[{"id":"parent"}]' + ); }); it('should replace CLIENT_ID with null', () => { diff --git a/extensions/amp-a4a/0.1/test/test-amp-a4a.js b/extensions/amp-a4a/0.1/test/test-amp-a4a.js index 1a03a93917203..4201be86278d3 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-a4a.js +++ b/extensions/amp-a4a/0.1/test/test-amp-a4a.js @@ -42,9 +42,7 @@ import {FetchMock, networkFailure} from './fetch-mock'; import {FriendlyIframeEmbed} from '../../../../src/friendly-iframe-embed'; import {LayoutPriority} from '../../../../src/layout'; import {MockA4AImpl, TEST_URL} from './utils'; -import { - RealTimeConfigManager, -} from '../real-time-config-manager'; +import {RealTimeConfigManager} from '../real-time-config-manager'; import { SINGLE_PASS_EXPERIMENT_IDS, isInExperiment, @@ -63,20 +61,22 @@ import { } from '../../../amp-ad/0.1/concurrent-load'; import {installDocService} from '../../../../src/service/ampdoc-impl'; import {layoutRectLtwh} from '../../../../src/layout-rect'; -import { - resetScheduledElementForTesting, -} from '../../../../src/service/custom-element-registry'; +import {resetScheduledElementForTesting} from '../../../../src/service/custom-element-registry'; import {data as testFragments} from './testdata/test_fragments'; import {toggleExperiment} from '../../../../src/experiments'; -import { - data as validCSSAmp, -} from './testdata/valid_css_at_rules_amp.reserialized'; +import {data as validCSSAmp} from './testdata/valid_css_at_rules_amp.reserialized'; describe('amp-a4a', () => { - const IFRAME_SANDBOXING_FLAGS = ['allow-forms', 'allow-modals', - 'allow-pointer-lock', 'allow-popups', 'allow-popups-to-escape-sandbox', - 'allow-same-origin', 'allow-scripts', - 'allow-top-navigation-by-user-activation']; + const IFRAME_SANDBOXING_FLAGS = [ + 'allow-forms', + 'allow-modals', + 'allow-pointer-lock', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-same-origin', + 'allow-scripts', + 'allow-top-navigation-by-user-activation', + ]; let sandbox; let fetchMock; @@ -89,10 +89,11 @@ describe('amp-a4a', () => { beforeEach(() => { sandbox = sinon.sandbox; fetchMock = null; - getSigningServiceNamesMock = sandbox.stub(AmpA4A.prototype, - 'getSigningServiceNames'); - onCreativeRenderSpy = - sandbox.spy(AmpA4A.prototype, 'onCreativeRender'); + getSigningServiceNamesMock = sandbox.stub( + AmpA4A.prototype, + 'getSigningServiceNames' + ); + onCreativeRenderSpy = sandbox.spy(AmpA4A.prototype, 'onCreativeRender'); getSigningServiceNamesMock.returns(['google']); viewerWhenVisibleMock = sandbox.stub(Viewer.prototype, 'whenFirstVisible'); viewerWhenVisibleMock.returns(Promise.resolve()); @@ -112,7 +113,7 @@ describe('amp-a4a', () => { afterEach(() => { if (fetchMock) { - fetchMock./*OK*/restore(); + fetchMock./*OK*/ restore(); fetchMock = null; } sandbox.restore(); @@ -127,11 +128,13 @@ describe('amp-a4a', () => { expect(fetchMock).to.be.null; fetchMock = new FetchMock(fixture.win); fetchMock.getOnce( - 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', { - body: validCSSAmp.publicKeyset, - status: 200, - headers: {'Content-Type': 'application/jwk-set+json'}, - }); + 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', + { + body: validCSSAmp.publicKeyset, + status: 200, + headers: {'Content-Type': 'application/jwk-set+json'}, + } + ); installDocService(fixture.win, /* isSingleDoc */ true); const {doc} = fixture; // TODO(a4a-cam@): This is necessary in the short term, until A4A is @@ -158,14 +161,18 @@ describe('amp-a4a', () => { const ampdocService = Services.ampdocServiceFor(doc.defaultView); return ampdocService.getAmpDoc(element); }; - element.isBuilt = () => {return true;}; + element.isBuilt = () => { + return true; + }; element.getLayoutBox = () => { return opt_rect || layoutRectLtwh(0, 0, 200, 50); }; element.getPageLayoutBox = () => { return element.getLayoutBox.apply(element, arguments); }; - element.getIntersectionChangeEntry = () => {return null;}; + element.getIntersectionChangeEntry = () => { + return null; + }; const signals = new Signals(); element.signals = () => signals; element.renderStarted = () => { @@ -187,10 +194,13 @@ describe('amp-a4a', () => { baseTestDoc.lastIndexOf('') + ''.length, ]; const splicePoint = baseTestDoc.indexOf(''); - return baseTestDoc.slice(0, splicePoint) + - '' + - baseTestDoc.slice(splicePoint); + return ( + baseTestDoc.slice(0, splicePoint) + + '' + + baseTestDoc.slice(splicePoint) + ); } /** @@ -205,7 +215,8 @@ describe('amp-a4a', () => { const friendlyChild = element.querySelector('iframe[srcdoc]'); expect(friendlyChild).to.be.ok; expect(friendlyChild.getAttribute('srcdoc')).to.have.string( - ''); + '' + ); expect(element).to.be.visible; expect(friendlyChild).to.be.visible; } @@ -220,7 +231,8 @@ describe('amp-a4a', () => { expect(!!sandboxAttribute).to.equal(shouldSandbox); if (shouldSandbox) { expect(sandboxAttribute.split(' ').sort()).to.jsonEqual( - IFRAME_SANDBOXING_FLAGS); + IFRAME_SANDBOXING_FLAGS + ); } } @@ -234,8 +246,10 @@ describe('amp-a4a', () => { expect(element.tagName.toLowerCase()).to.equal('amp-a4a'); expect(element).to.be.visible; expect(element.querySelectorAll('iframe')).to.have.lengthOf(1); - const safeFrameUrl = 'https://tpc.googlesyndication.com/safeframe/' + - sfVersion + '/html/container.html'; + const safeFrameUrl = + 'https://tpc.googlesyndication.com/safeframe/' + + sfVersion + + '/html/container.html'; const child = element.querySelector(`iframe[src^="${safeFrameUrl}"][name]`); expect(child).to.be.ok; const name = child.getAttribute('name'); @@ -283,8 +297,11 @@ describe('amp-a4a', () => { * @param {string} srcUrl * @param {boolean=} shouldSandbox */ - function verifyCachedContentIframeRender(element, srcUrl, - shouldSandbox = false) { + function verifyCachedContentIframeRender( + element, + srcUrl, + shouldSandbox = false + ) { expect(element.tagName.toLowerCase()).to.equal('amp-a4a'); expect(element).to.be.visible; expect(element.querySelectorAll('iframe')).to.have.lengthOf(1); @@ -301,7 +318,9 @@ describe('amp-a4a', () => { /** @param {string} nameData */ function verifyNameData(nameData) { let attributes; - expect(() => {attributes = JSON.parse(nameData);}).not.to.throw(Error); + expect(() => { + attributes = JSON.parse(nameData); + }).not.to.throw(Error); expect(attributes).to.be.ok; verifyContext(attributes._context); } @@ -312,28 +331,44 @@ describe('amp-a4a', () => { * @param {(string|Array)=} additionalEvents */ function verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, additionalEvents = []) { - ['ad-request-start', 'ad-response-end', 'ad-render-start', - 'ad-render-end', 'ad-iframe-loaded'].concat(additionalEvents) - .forEach(evnt => expect(triggerAnalyticsEventSpy).to.be.calledWith( - a4a.element, evnt, {'time': sinon.match.number})); + a4a, + triggerAnalyticsEventSpy, + additionalEvents = [] + ) { + [ + 'ad-request-start', + 'ad-response-end', + 'ad-render-start', + 'ad-render-end', + 'ad-iframe-loaded', + ] + .concat(additionalEvents) + .forEach(evnt => + expect(triggerAnalyticsEventSpy).to.be.calledWith(a4a.element, evnt, { + 'time': sinon.match.number, + }) + ); } describe('ads are visible', () => { let a4aElement; let a4a; let fixture; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - a4a.releaseType_ = '0'; - return fixture; - })); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + a4a.releaseType_ = '0'; + return fixture; + }) + ); it('for SafeFrame rendering case', () => { // Make sure there's no signature, so that we go down the 3p iframe path. @@ -342,8 +377,10 @@ describe('amp-a4a', () => { // If rendering type is safeframe, we SHOULD attach a SafeFrame. adResponse.headers[RENDERING_TYPE_HEADER] = 'safeframe'; a4a.buildCallback(); - const lifecycleEventStub = - sandbox.stub(a4a, 'maybeTriggerAnalyticsEvent_'); + const lifecycleEventStub = sandbox.stub( + a4a, + 'maybeTriggerAnalyticsEvent_' + ); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { const child = a4aElement.querySelector('iframe[name]'); @@ -395,8 +432,9 @@ describe('amp-a4a', () => { return a4a.layoutCallback().then(() => { const child = a4aElement.querySelector('iframe[srcdoc]'); expect(child).to.be.ok; - expect(child.srcdoc.indexOf('meta http-equiv=Content-Security-Policy')) - .to.not.equal(-1); + expect( + child.srcdoc.indexOf('meta http-equiv=Content-Security-Policy') + ).to.not.equal(-1); }); }); @@ -416,8 +454,10 @@ describe('amp-a4a', () => { }); it('detachedCallback should destroy FIE and detach frame', () => { - const fieDestroySpy = - sandbox./*OK*/spy(FriendlyIframeEmbed.prototype, 'destroy'); + const fieDestroySpy = sandbox./*OK*/ spy( + FriendlyIframeEmbed.prototype, + 'destroy' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -432,8 +472,11 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); // Never resolve - sandbox.stub/*OK*/(FriendlyIframeEmbed.prototype,'whenIniLoaded') - .callsFake(() => {return new Promise(() => {});}); + sandbox + .stub(/*OK*/ FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .callsFake(() => { + return new Promise(() => {}); + }); const creativeString = buildCreativeString(); const metaData = a4a.getAmpAdMetadata(creativeString); return a4a.renderAmpCreative_(metaData).then(() => { @@ -447,13 +490,14 @@ describe('amp-a4a', () => { const iniLoadPromise = new Promise(resolve => { iniLoadResolver = resolve; }); - const whenIniLoadedStub = sandbox.stub( - FriendlyIframeEmbed.prototype, - 'whenIniLoaded').callsFake( - () => iniLoadPromise); + const whenIniLoadedStub = sandbox + .stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .callsFake(() => iniLoadPromise); a4a.buildCallback(); - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); a4a.onLayoutMeasure(); const layoutPromise = a4a.layoutCallback(); expect(whenIniLoadedStub).to.not.be.called; @@ -483,8 +527,9 @@ describe('amp-a4a', () => { }); it('for requests from insecure HTTP pages', () => { - sandbox.stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') - .returns(false); + sandbox + .stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') + .returns(false); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -496,23 +541,29 @@ describe('amp-a4a', () => { }); it('should fire amp-analytics triggers', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); - sandbox.stub/*OK*/(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .callsFake(() => Promise.resolve()); + sandbox + .stub(/*OK*/ FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .callsFake(() => Promise.resolve()); return a4a.layoutCallback().then(() => { verifyA4aAnalyticsTriggersWereFired(a4a, triggerAnalyticsEventSpy); }); }); it('should not fire amp-analytics triggers without config', () => { - sandbox.stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig').callsFake( - () => null); + sandbox + .stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig') + .callsFake(() => null); a4a = new MockA4AImpl(a4aElement); - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -521,22 +572,31 @@ describe('amp-a4a', () => { }); it('should insert an amp-analytics element', () => { - sandbox.stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig').callsFake( - () => ({'foo': 'bar'})); + sandbox + .stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig') + .callsFake(() => ({'foo': 'bar'})); a4a = new MockA4AImpl(a4aElement); - const insertAnalyticsElementSpy = - sandbox.spy(analyticsExtension, 'insertAnalyticsElement'); + const insertAnalyticsElementSpy = sandbox.spy( + analyticsExtension, + 'insertAnalyticsElement' + ); a4a.buildCallback(); expect(insertAnalyticsElementSpy).to.be.calledWith( - a4a.element, {'foo': 'bar'}, true /* loadAnalytics */); + a4a.element, + {'foo': 'bar'}, + true /* loadAnalytics */ + ); }); it('should not insert an amp-analytics element if config is null', () => { - sandbox.stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig').callsFake( - () => null); + sandbox + .stub(MockA4AImpl.prototype, 'getA4aAnalyticsConfig') + .callsFake(() => null); a4a = new MockA4AImpl(a4aElement); - const insertAnalyticsElementSpy = - sandbox.spy(analyticsExtension, 'insertAnalyticsElement'); + const insertAnalyticsElementSpy = sandbox.spy( + analyticsExtension, + 'insertAnalyticsElement' + ); a4a.buildCallback(); expect(insertAnalyticsElementSpy).not.to.be.called; }); @@ -546,16 +606,20 @@ describe('amp-a4a', () => { let a4aElement; let a4a; let fixture; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - return fixture; - })); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + return fixture; + }) + ); it('when unlayoutCallback called after adPromise', () => { a4a.buildCallback(); @@ -567,16 +631,20 @@ describe('amp-a4a', () => { const layoutCallbackPromise = a4a.layoutCallback(); a4a.unlayoutCallback(); const renderNonAmpCreativeSpy = sandbox.spy( - AmpA4A.prototype, 'renderNonAmpCreative'); + AmpA4A.prototype, + 'renderNonAmpCreative' + ); promiseResolver(); - layoutCallbackPromise.then(() => { - // We should never get in here. - expect(false).to.be.true; - }).catch(err => { - expect(renderNonAmpCreativeSpy).to.not.be.called; - expect(err).to.be.ok; - expect(err.message).to.equal('CANCELLED'); - }); + layoutCallbackPromise + .then(() => { + // We should never get in here. + expect(false).to.be.true; + }) + .catch(err => { + expect(renderNonAmpCreativeSpy).to.not.be.called; + expect(err).to.be.ok; + expect(err.message).to.equal('CANCELLED'); + }); }); it('when unlayoutCallback called before renderAmpCreative_', () => { @@ -590,13 +658,15 @@ describe('amp-a4a', () => { a4a.unlayoutCallback(); promiseResolver(); - layoutCallbackPromise.then(() => { - // We should never get in here. - expect(false).to.be.true; - }).catch(err => { - expect(err).to.be.ok; - expect(err.message).to.equal('CANCELLED'); - }); + layoutCallbackPromise + .then(() => { + // We should never get in here. + expect(false).to.be.true; + }) + .catch(err => { + expect(err).to.be.ok; + expect(err.message).to.equal('CANCELLED'); + }); }); }); @@ -612,8 +682,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; a4aElement = createA4aElement(doc); a4a = new MockA4AImpl(a4aElement); @@ -643,8 +715,11 @@ describe('amp-a4a', () => { a4a.sandboxHTMLCreativeFrame = () => true; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifyCachedContentIframeRender(a4aElement, TEST_URL, - true /* shouldSandbox */); + verifyCachedContentIframeRender( + a4aElement, + TEST_URL, + true /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); @@ -653,13 +728,16 @@ describe('amp-a4a', () => { a4a.sandboxHTMLCreativeFrame = () => false; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifyCachedContentIframeRender(a4aElement, TEST_URL, - false /* shouldSandbox */); + verifyCachedContentIframeRender( + a4aElement, + TEST_URL, + false /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); - it('shouldn\'t set feature policy for sync-xhr with exp off-a4a', () => { + it("shouldn't set feature policy for sync-xhr with exp off-a4a", () => { a4a.sandboxHTMLCreativeFrame = () => true; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -674,8 +752,7 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { verifyCachedContentIframeRender(a4aElement, TEST_URL, true); - expect(a4a.iframe.getAttribute('allow')) - .to.equal('sync-xhr \'none\';'); + expect(a4a.iframe.getAttribute('allow')).to.equal("sync-xhr 'none';"); }); }); }); @@ -691,26 +768,40 @@ describe('amp-a4a', () => { }); it('should render via cached iframe', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); return a4a.layoutCallback().then(() => { verifyCachedContentIframeRender(a4aElement, TEST_URL); // Should have reported an error. expect(devErrLogStub).to.be.calledOnce; expect(devErrLogStub.getCall(0).args[1]).to.have.string( - 'random illegal value'); + 'random illegal value' + ); expect(fetchMock.called('ad')).to.be.true; verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded'); + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ); }); }); it('should fire amp-analytics triggers for illegal render modes', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); - return a4a.layoutCallback().then(() => - verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded')); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); + return a4a + .layoutCallback() + .then(() => + verifyA4aAnalyticsTriggersWereFired( + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ) + ); }); }); @@ -728,17 +819,20 @@ describe('amp-a4a', () => { }); }); - it('should make only one NameFrame even if onLayoutMeasure called ' + - 'multiple times', () => { - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - verifyNameFrameRender(a4aElement); - expect(fetchMock.called('ad')).to.be.true; - }); - }); + it( + 'should make only one NameFrame even if onLayoutMeasure called ' + + 'multiple times', + () => { + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + verifyNameFrameRender(a4aElement); + expect(fetchMock.called('ad')).to.be.true; + }); + } + ); it('should apply sandbox when sandboxHTMLCreativeFrame is true', () => { a4a.sandboxHTMLCreativeFrame = () => true; @@ -759,46 +853,59 @@ describe('amp-a4a', () => { }); ['', 'client_cache', 'safeframe', 'some_random_thing'].forEach( - headerVal => { - it(`should not attach a NameFrame when header is ${headerVal}`, - () => { - const devStub = sandbox.stub(dev(), 'error'); - // Make sure there's no signature, so that we go down the 3p - // iframe path. - delete adResponse.headers['AMP-Fast-Fetch-Signature']; - delete adResponse.headers[AMP_SIGNATURE_HEADER]; - // If rendering type is anything but nameframe, we SHOULD NOT - // attach a NameFrame. - adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - if (headerVal == 'some_random_thing') { - expect(devStub.withArgs('AMP-A4A', - `cross-origin render mode header ${headerVal}`)) - .to.be.calledOnce; - } else { - expect(devStub).to.not.be.called; - } - const nameChild = a4aElement.querySelector( - 'iframe[src^="nameframe"]'); - expect(nameChild).to.not.be.ok; - if (headerVal != 'safeframe') { - const unsafeChild = a4aElement.querySelector('iframe'); - expect(unsafeChild).to.be.ok; - expect(unsafeChild.getAttribute('src')).to.have.string( - TEST_URL); - } - expect(fetchMock.called('ad')).to.be.true; - }); - }); + headerVal => { + it(`should not attach a NameFrame when header is ${headerVal}`, () => { + const devStub = sandbox.stub(dev(), 'error'); + // Make sure there's no signature, so that we go down the 3p + // iframe path. + delete adResponse.headers['AMP-Fast-Fetch-Signature']; + delete adResponse.headers[AMP_SIGNATURE_HEADER]; + // If rendering type is anything but nameframe, we SHOULD NOT + // attach a NameFrame. + adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + if (headerVal == 'some_random_thing') { + expect( + devStub.withArgs( + 'AMP-A4A', + `cross-origin render mode header ${headerVal}` + ) + ).to.be.calledOnce; + } else { + expect(devStub).to.not.be.called; + } + const nameChild = a4aElement.querySelector( + 'iframe[src^="nameframe"]' + ); + expect(nameChild).to.not.be.ok; + if (headerVal != 'safeframe') { + const unsafeChild = a4aElement.querySelector('iframe'); + expect(unsafeChild).to.be.ok; + expect(unsafeChild.getAttribute('src')).to.have.string( + TEST_URL + ); + } + expect(fetchMock.called('ad')).to.be.true; + }); }); + } + ); it('should fire amp-analytics triggers for lifecycle stages', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); - return a4a.layoutCallback().then(() => - verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded')); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); + return a4a + .layoutCallback() + .then(() => + verifyA4aAnalyticsTriggersWereFired( + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ) + ); }); }); @@ -824,24 +931,30 @@ describe('amp-a4a', () => { }); }); - it('should make only one SafeFrame even if onLayoutMeasure called ' + - 'multiple times', () => { - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION); - expect(fetchMock.called('ad')).to.be.true; - }); - }); + it( + 'should make only one SafeFrame even if onLayoutMeasure called ' + + 'multiple times', + () => { + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION); + expect(fetchMock.called('ad')).to.be.true; + }); + } + ); it('should apply sandbox when sandboxHTMLCreativeFrame is true', () => { a4a.sandboxHTMLCreativeFrame = () => true; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION, - true /* shouldSandbox */); + verifySafeFrameRender( + a4aElement, + DEFAULT_SAFEFRAME_VERSION, + true /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); @@ -850,49 +963,60 @@ describe('amp-a4a', () => { a4a.sandboxHTMLCreativeFrame = () => false; a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifySafeFrameRender(a4aElement, DEFAULT_SAFEFRAME_VERSION, - false /* shouldSandbox */); + verifySafeFrameRender( + a4aElement, + DEFAULT_SAFEFRAME_VERSION, + false /* shouldSandbox */ + ); expect(fetchMock.called('ad')).to.be.true; }); }); ['', 'client_cache', 'nameframe', 'some_random_thing'].forEach( - headerVal => { - it(`should not attach a SafeFrame when header is ${headerVal}`, - () => { - const devStub = sandbox.stub(dev(), 'error'); - // If rendering type is anything but safeframe, we SHOULD NOT - // attach a SafeFrame. - adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - if (headerVal == 'some_random_thing') { - expect(devStub.withArgs('AMP-A4A', - `cross-origin render mode header ${headerVal}`)) - .to.be.calledOnce; - } else { - expect(devStub).to.not.be.called; - } - const safeframeUrl = 'https://tpc.googlesyndication.com/safeframe/' + - DEFAULT_SAFEFRAME_VERSION + '/html/container.html'; - const safeChild = a4aElement.querySelector( - `iframe[src^="${safeframeUrl}"]`); - expect(safeChild).to.not.be.ok; - if (headerVal != 'nameframe') { - const unsafeChild = a4aElement.querySelector('iframe'); - expect(unsafeChild).to.be.ok; - expect(unsafeChild.getAttribute('src')).to.have.string( - TEST_URL); - } - expect(fetchMock.called('ad')).to.be.true; - }); - }); + headerVal => { + it(`should not attach a SafeFrame when header is ${headerVal}`, () => { + const devStub = sandbox.stub(dev(), 'error'); + // If rendering type is anything but safeframe, we SHOULD NOT + // attach a SafeFrame. + adResponse.headers[RENDERING_TYPE_HEADER] = headerVal; + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + if (headerVal == 'some_random_thing') { + expect( + devStub.withArgs( + 'AMP-A4A', + `cross-origin render mode header ${headerVal}` + ) + ).to.be.calledOnce; + } else { + expect(devStub).to.not.be.called; + } + const safeframeUrl = + 'https://tpc.googlesyndication.com/safeframe/' + + DEFAULT_SAFEFRAME_VERSION + + '/html/container.html'; + const safeChild = a4aElement.querySelector( + `iframe[src^="${safeframeUrl}"]` + ); + expect(safeChild).to.not.be.ok; + if (headerVal != 'nameframe') { + const unsafeChild = a4aElement.querySelector('iframe'); + expect(unsafeChild).to.be.ok; + expect(unsafeChild.getAttribute('src')).to.have.string( + TEST_URL + ); + } + expect(fetchMock.called('ad')).to.be.true; + }); }); + } + ); it('should reset state to null on unlayoutCallback', () => { return a4a.layoutCallback().then(() => { - expect(a4a.experimentalNonAmpCreativeRenderMethod_) - .to.equal('safeframe'); + expect(a4a.experimentalNonAmpCreativeRenderMethod_).to.equal( + 'safeframe' + ); a4a.unlayoutCallback(); expect(a4a.experimentalNonAmpCreativeRenderMethod_).to.be.null; expect(fetchMock.called('ad')).to.be.true; @@ -900,11 +1024,19 @@ describe('amp-a4a', () => { }); it('should fire amp-analytics triggers for lifecycle stages', () => { - const triggerAnalyticsEventSpy = - sandbox.spy(analytics, 'triggerAnalyticsEvent'); - return a4a.layoutCallback().then(() => - verifyA4aAnalyticsTriggersWereFired( - a4a, triggerAnalyticsEventSpy, 'ad-iframe-loaded')); + const triggerAnalyticsEventSpy = sandbox.spy( + analytics, + 'triggerAnalyticsEvent' + ); + return a4a + .layoutCallback() + .then(() => + verifyA4aAnalyticsTriggersWereFired( + a4a, + triggerAnalyticsEventSpy, + 'ad-iframe-loaded' + ) + ); }); }); }); @@ -912,15 +1044,19 @@ describe('amp-a4a', () => { describe('cross-domain vs A4A', () => { let a4a; let a4aElement; - beforeEach(() => createIframePromise().then(fixture => { - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - const {doc} = fixture; - a4aElement = createA4aElement(doc); - a4a = new MockA4AImpl(a4aElement); - })); + beforeEach(() => + createIframePromise().then(fixture => { + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + const {doc} = fixture; + a4aElement = createA4aElement(doc); + a4a = new MockA4AImpl(a4aElement); + }) + ); afterEach(() => { expect(fetchMock.called('ad')).to.be.true; }); @@ -935,25 +1071,29 @@ describe('amp-a4a', () => { }); }); - it(`should not use ${renderType} even if onLayoutMeasure called ` + - 'multiple times', () => { - adResponse.headers[RENDERING_TYPE_HEADER] = renderType; - a4a.buildCallback(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - const safeChild = a4aElement.querySelector('iframe[name]'); - expect(safeChild).to.not.be.ok; - const crossDomainChild = a4aElement.querySelector('iframe[src]'); - expect(crossDomainChild).to.not.be.ok; - const friendlyChild = a4aElement.querySelector('iframe[srcdoc]'); - expect(friendlyChild).to.be.ok; - expect(friendlyChild.getAttribute('srcdoc')).to.have.string( - ''); - }); - }); + it( + `should not use ${renderType} even if onLayoutMeasure called ` + + 'multiple times', + () => { + adResponse.headers[RENDERING_TYPE_HEADER] = renderType; + a4a.buildCallback(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + const safeChild = a4aElement.querySelector('iframe[name]'); + expect(safeChild).to.not.be.ok; + const crossDomainChild = a4aElement.querySelector('iframe[src]'); + expect(crossDomainChild).to.not.be.ok; + const friendlyChild = a4aElement.querySelector('iframe[srcdoc]'); + expect(friendlyChild).to.be.ok; + expect(friendlyChild.getAttribute('srcdoc')).to.have.string( + '' + ); + }); + } + ); }); }); @@ -965,8 +1105,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); a4aElement.setAttribute('width', 480); @@ -994,26 +1136,33 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); s.textContent = '.fixed {position:fixed;}'; doc.head.appendChild(s); const a4a = new MockA4AImpl(a4aElement); - const renderNonAmpCreativeSpy = - sandbox.spy(a4a, 'renderNonAmpCreative'); + const renderNonAmpCreativeSpy = sandbox.spy( + a4a, + 'renderNonAmpCreative' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderNonAmpCreativeSpy.calledOnce, - 'renderNonAmpCreative called exactly once').to.be.true; + expect( + renderNonAmpCreativeSpy.calledOnce, + 'renderNonAmpCreative called exactly once' + ).to.be.true; a4a.unlayoutCallback(); getResourceStub.returns({ 'hasBeenMeasured': () => true, - 'isMeasureRequested': () => false}); + 'isMeasureRequested': () => false, + }); const onLayoutMeasureSpy = sandbox.spy(a4a, 'onLayoutMeasure'); a4a.resumeCallback(); expect(onLayoutMeasureSpy).to.be.calledOnce; @@ -1025,8 +1174,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); @@ -1038,8 +1189,10 @@ describe('amp-a4a', () => { a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderAmpCreativeSpy.calledOnce, - 'renderAmpCreative_ called exactly once').to.be.true; + expect( + renderAmpCreativeSpy.calledOnce, + 'renderAmpCreative_ called exactly once' + ).to.be.true; sandbox.stub(a4a, 'unlayoutCallback').callsFake(() => false); const onLayoutMeasureSpy = sandbox.spy(a4a, 'onLayoutMeasure'); a4a.resumeCallback(); @@ -1055,22 +1208,28 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); s.textContent = '.fixed {position:fixed;}'; doc.head.appendChild(s); const a4a = new MockA4AImpl(a4aElement); - const renderNonAmpCreativeSpy = - sandbox.spy(a4a, 'renderNonAmpCreative'); + const renderNonAmpCreativeSpy = sandbox.spy( + a4a, + 'renderNonAmpCreative' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderNonAmpCreativeSpy.calledOnce, - 'renderNonAmpCreative called exactly once').to.be.true; + expect( + renderNonAmpCreativeSpy.calledOnce, + 'renderNonAmpCreative called exactly once' + ).to.be.true; a4a.unlayoutCallback(); const onLayoutMeasureSpy = sandbox.spy(a4a, 'onLayoutMeasure'); getResourceStub.returns({'hasBeenMeasured': () => false}); @@ -1084,28 +1243,41 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.releaseType_ = '0'; const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); - const rtcResponse = Promise.resolve( - [{response: 'a', rtcTime: 1, callout: 'https://a.com'}]); - const maybeExecuteRealTimeConfigStub = sandbox.stub().returns( - rtcResponse); + const rtcResponse = Promise.resolve([ + {response: 'a', rtcTime: 1, callout: 'https://a.com'}, + ]); + const maybeExecuteRealTimeConfigStub = sandbox + .stub() + .returns(rtcResponse); AMP.RealTimeConfigManager = RealTimeConfigManager; - sandbox.stub(AMP.RealTimeConfigManager.prototype, - 'maybeExecuteRealTimeConfig').callsFake( - maybeExecuteRealTimeConfigStub); - const tryExecuteRealTimeConfigSpy = - sandbox.spy(a4a, 'tryExecuteRealTimeConfig_'); + sandbox + .stub( + AMP.RealTimeConfigManager.prototype, + 'maybeExecuteRealTimeConfig' + ) + .callsFake(maybeExecuteRealTimeConfigStub); + const tryExecuteRealTimeConfigSpy = sandbox.spy( + a4a, + 'tryExecuteRealTimeConfig_' + ); const updateLayoutPriorityStub = sandbox.stub( - a4a, 'updateLayoutPriority'); + a4a, + 'updateLayoutPriority' + ); const renderAmpCreativeSpy = sandbox.spy(a4a, 'renderAmpCreative_'); - const preloadExtensionSpy = - sandbox.spy(Extensions.prototype, 'preloadExtension'); + const preloadExtensionSpy = sandbox.spy( + Extensions.prototype, + 'preloadExtension' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); @@ -1115,49 +1287,62 @@ describe('amp-a4a', () => { expect(a4a.isVerifiedAmpCreative()).to.be.true; expect(tryExecuteRealTimeConfigSpy.calledOnce).to.be.true; expect(maybeExecuteRealTimeConfigStub.calledOnce).to.be.true; - expect(maybeExecuteRealTimeConfigStub.calledWith( - {}, null)).to.be.true; - expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once') - .to.be.true; + expect(maybeExecuteRealTimeConfigStub.calledWith({}, null)).to.be + .true; + expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once').to.be + .true; expect(getAdUrlSpy.calledWith(null, rtcResponse)).to.be.true; expect(fetchMock.called('ad')).to.be.true; expect(preloadExtensionSpy.withArgs('amp-font')).to.be.calledOnce; - expect(doc.querySelector('link[rel=preload]' + - '[href="https://fonts.googleapis.com/css?family=Questrial"]')) - .to.be.ok; + expect( + doc.querySelector( + 'link[rel=preload]' + + '[href="https://fonts.googleapis.com/css?family=Questrial"]' + ) + ).to.be.ok; return a4a.layoutCallback().then(() => { - expect(renderAmpCreativeSpy.calledOnce, - 'renderAmpCreative_ called exactly once').to.be.true; - expect(a4aElement.getElementsByTagName('iframe').length) - .to.equal(1); + expect( + renderAmpCreativeSpy.calledOnce, + 'renderAmpCreative_ called exactly once' + ).to.be.true; + expect(a4aElement.getElementsByTagName('iframe').length).to.equal( + 1 + ); const friendlyIframe = a4aElement.querySelector('iframe[srcdoc]'); expect(friendlyIframe).to.not.be.null; expect(friendlyIframe.getAttribute('src')).to.be.null; const expectedAttributes = { - 'frameborder': '0', 'allowfullscreen': '', - 'allowtransparency': '', 'scrolling': 'no'}; + 'frameborder': '0', + 'allowfullscreen': '', + 'allowtransparency': '', + 'scrolling': 'no', + }; Object.keys(expectedAttributes).forEach(key => { expect(friendlyIframe.getAttribute(key)).to.equal( - expectedAttributes[key]); + expectedAttributes[key] + ); }); // Should not contain v0.js, any extensions, or amp-boilerplate. const iframeDoc = friendlyIframe.contentDocument; expect(iframeDoc.querySelector('script[src]')).to.not.be.ok; - expect(iframeDoc.querySelector('script[custom-element]')) - .to.not.be.ok; - expect(iframeDoc.querySelector('style[amp-boilerplate]')) - .to.not.be.ok; + expect(iframeDoc.querySelector('script[custom-element]')).to.not.be + .ok; + expect(iframeDoc.querySelector('style[amp-boilerplate]')).to.not.be + .ok; expect(iframeDoc.querySelector('noscript')).to.not.be.ok; // Should contain font link and extension in main document. - expect(iframeDoc.querySelector( - 'link[href="https://fonts.googleapis.com/css?family=Questrial"]')) - .to.be.ok; + expect( + iframeDoc.querySelector( + 'link[href="https://fonts.googleapis.com/css?family=Questrial"]' + ) + ).to.be.ok; expect(doc.querySelector('script[src*="amp-font-0.1"]')).to.be.ok; - expect(onCreativeRenderSpy.withArgs(sinon.match.object)) - .to.be.calledOnce; + expect(onCreativeRenderSpy.withArgs(sinon.match.object)).to.be + .calledOnce; expect(updateLayoutPriorityStub).to.be.calledOnce; expect(updateLayoutPriorityStub.args[0][0]).to.equal( - LayoutPriority.CONTENT); + LayoutPriority.CONTENT + ); }); }); }); @@ -1168,26 +1353,36 @@ describe('amp-a4a', () => { delete adResponse.headers['AMP-Fast-Fetch-Signature']; delete adResponse.headers[AMP_SIGNATURE_HEADER]; adResponse.headers[EXPERIMENT_FEATURE_HEADER_NAME] = - 'pref_neutral_enabled=1,'; + 'pref_neutral_enabled=1,'; adResponse.headers[CREATIVE_SIZE_HEADER] = '123x456'; fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const element = createA4aElement(fixture.doc); element.setAttribute('type', 'adsense'); const a4a = new MockA4AImpl(element); const updateLayoutPriorityStub = sandbox.stub( - a4a, 'updateLayoutPriority'); - const renderNonAmpCreativeSpy = - sandbox.spy(a4a, 'renderNonAmpCreative'); - sandbox.stub(a4a, 'maybeValidateAmpCreative').returns( - Promise.resolve()); + a4a, + 'updateLayoutPriority' + ); + const renderNonAmpCreativeSpy = sandbox.spy( + a4a, + 'renderNonAmpCreative' + ); + sandbox + .stub(a4a, 'maybeValidateAmpCreative') + .returns(Promise.resolve()); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - expect(renderNonAmpCreativeSpy.calledOnce, - 'renderNonAmpCreative_ called exactly once').to.be.true; + expect( + renderNonAmpCreativeSpy.calledOnce, + 'renderNonAmpCreative_ called exactly once' + ).to.be.true; expect(updateLayoutPriorityStub.args[0][0]).to.equal( - LayoutPriority.CONTENT); + LayoutPriority.CONTENT + ); expect(is3pThrottled(a4a.win)).to.be.false; }); }); @@ -1198,14 +1393,18 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); sandbox.stub(a4a, 'getAmpAdMetadata').callsFake(creative => { - const metaData = AmpA4A.prototype.getAmpAdMetadata.call(a4a, - creative); + const metaData = AmpA4A.prototype.getAmpAdMetadata.call( + a4a, + creative + ); metaData.images = [ 'https://prefetch.me.com?a=b', 'http://do.not.prefetch.me.com?c=d', @@ -1216,12 +1415,21 @@ describe('amp-a4a', () => { a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - expect(doc.querySelector('link[rel=preload]' + - '[href="https://prefetch.me.com?a=b"]')).to.be.ok; - 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; + expect( + doc.querySelector( + 'link[rel=preload]' + '[href="https://prefetch.me.com?a=b"]' + ) + ).to.be.ok; + 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; }); }); }); @@ -1229,8 +1437,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const s = doc.createElement('style'); @@ -1246,8 +1456,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const rect = layoutRectLtwh(0, 0, 200, 0); const a4aElement = createA4aElement(doc, rect); @@ -1276,29 +1488,34 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); const updateLayoutPriorityStub = sandbox.stub( - a4a, 'updateLayoutPriority'); + a4a, + 'updateLayoutPriority' + ); if (!isValidCreative) { delete adResponse.headers['AMP-Fast-Fetch-Signature']; delete adResponse.headers[AMP_SIGNATURE_HEADER]; } a4a.promiseErrorHandler_ = () => {}; if (opt_failAmpRender) { - sandbox.stub(a4a, 'renderAmpCreative_').returns( - Promise.reject('amp render failure')); + sandbox + .stub(a4a, 'renderAmpCreative_') + .returns(Promise.reject('amp render failure')); } a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); return a4a.adPromise_.then(promiseResult => { - expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once') - .to.be.true; + expect(getAdUrlSpy.calledOnce, 'getAdUrl called exactly once').to.be + .true; expect(fetchMock.called('ad')).to.be.true; expect(a4a.isVerifiedAmpCreative()).to.equal(isValidCreative); if (isValidCreative) { @@ -1308,21 +1525,23 @@ describe('amp-a4a', () => { expect(promiseResult).to.not.be.ok; } return a4a.layoutCallback().then(() => { - expect(a4aElement.getElementsByTagName('iframe').length) - .to.not.equal(0); + expect( + a4aElement.getElementsByTagName('iframe').length + ).to.not.equal(0); const iframe = a4aElement.getElementsByTagName('iframe')[0]; if (isValidCreative && !opt_failAmpRender) { expect(iframe.getAttribute('src')).to.be.null; - expect(onCreativeRenderSpy.withArgs(sinon.match.object)) - .to.be.calledOnce; + expect(onCreativeRenderSpy.withArgs(sinon.match.object)).to.be + .calledOnce; expect(updateLayoutPriorityStub).to.be.calledOnce; expect(updateLayoutPriorityStub.args[0][0]).to.equal( - LayoutPriority.CONTENT); + LayoutPriority.CONTENT + ); } else { expect(iframe.getAttribute('srcdoc')).to.be.null; - expect(iframe.src, 'verify iframe src w/ origin').to - .equal(TEST_URL + - '&__amp_source_origin=about%3Asrcdoc'); + expect(iframe.src, 'verify iframe src w/ origin').to.equal( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc' + ); expect(onCreativeRenderSpy.withArgs(null)).to.be.called; if (!opt_failAmpRender) { expect(updateLayoutPriorityStub).to.not.be.called; @@ -1345,8 +1564,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1357,8 +1578,8 @@ describe('amp-a4a', () => { expect(a4a.adPromise_).to.be.instanceof(Promise); return a4a.layoutCallback().then(() => { expect(getAdUrlSpy, 'getAdUrl called exactly once').to.be.calledOnce; - expect(onNetworkFailureSpy, - 'onNetworkFailureSpy called exactly once').to.be.calledOnce; + expect(onNetworkFailureSpy, 'onNetworkFailureSpy called exactly once') + .to.be.calledOnce; // Verify iframe presence and lack of visibility hidden const iframe = a4aElement.querySelector('iframe[src]'); expect(iframe).to.be.ok; @@ -1372,17 +1593,24 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); - sandbox.stub(a4a, 'onNetworkFailure') - .withArgs(sinon.match(val => - val.message && val.message.indexOf('XHR Failed fetching') == 0), - TEST_URL) - .returns({adUrl: TEST_URL + '&err=true'}); + sandbox + .stub(a4a, 'onNetworkFailure') + .withArgs( + sinon.match( + val => + val.message && val.message.indexOf('XHR Failed fetching') == 0 + ), + TEST_URL + ) + .returns({adUrl: TEST_URL + '&err=true'}); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); @@ -1402,18 +1630,25 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.promiseErrorHandler_ = () => {}; const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); - sandbox.stub(a4a, 'onNetworkFailure') - .withArgs(sinon.match(val => - val.message && val.message.indexOf('XHR Failed fetching') == 0), - TEST_URL) - .returns({frameGetDisabled: true}); + sandbox + .stub(a4a, 'onNetworkFailure') + .withArgs( + sinon.match( + val => + val.message && val.message.indexOf('XHR Failed fetching') == 0 + ), + TEST_URL + ) + .returns({frameGetDisabled: true}); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -1427,21 +1662,25 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - Promise.reject(networkFailure()), {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + Promise.reject(networkFailure()), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.buildCallback(); a4a.onLayoutMeasure(); - return a4a.adPromise_.then(() => a4a.layoutCallback().then(() => { - // Verify iframe presence and lack of visibility hidden - expect(a4aElement.querySelectorAll('iframe').length).to.equal(1); - const iframe = a4aElement.querySelectorAll('iframe')[0]; - expect(iframe.src.indexOf(TEST_URL)).to.equal(0); - expect(iframe).to.be.visible; - expect(onCreativeRenderSpy.withArgs(null)).to.be.called; - })); + return a4a.adPromise_.then(() => + a4a.layoutCallback().then(() => { + // Verify iframe presence and lack of visibility hidden + expect(a4aElement.querySelectorAll('iframe').length).to.equal(1); + const iframe = a4aElement.querySelectorAll('iframe')[0]; + expect(iframe.src.indexOf(TEST_URL)).to.equal(0); + expect(iframe).to.be.visible; + expect(onCreativeRenderSpy.withArgs(null)).to.be.called; + }) + ); }); }); it('should handle XHR error when resolves after layoutCallback', () => { @@ -1449,11 +1688,12 @@ describe('amp-a4a', () => { setupForAdTesting(fixture); let rejectXhr; fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - new Promise((unusedResolve, reject) => { - rejectXhr = reject; - }), - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + new Promise((unusedResolve, reject) => { + rejectXhr = reject; + }), + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1491,11 +1731,11 @@ describe('amp-a4a', () => { }, { name: 'empty body', - fn: () => adResponse.body = '', + fn: () => (adResponse.body = ''), }, { name: 'no fill header', - fn: () => adResponse.headers['amp-ff-empty-creative'] = '', + fn: () => (adResponse.headers['amp-ff-empty-creative'] = ''), }, ].forEach(test => { it(`should collapse ${test.name}`, () => { @@ -1503,9 +1743,10 @@ describe('amp-a4a', () => { setupForAdTesting(fixture); test.fn(); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1514,11 +1755,14 @@ describe('amp-a4a', () => { const noContentUISpy = sandbox.spy(); const unlayoutUISpy = sandbox.spy(); a4a.uiHandler = { - applyNoContentUI: () => {noContentUISpy();}, - applyUnlayoutUI: () => {unlayoutUISpy();}, + applyNoContentUI: () => { + noContentUISpy(); + }, + applyUnlayoutUI: () => { + unlayoutUISpy(); + }, }; - sandbox.stub(a4a, 'getLayoutBox').returns( - {width: 123, height: 456}); + sandbox.stub(a4a, 'getLayoutBox').returns({width: 123, height: 456}); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.ok; return a4a.adPromise_.then(() => { @@ -1529,21 +1773,26 @@ describe('amp-a4a', () => { expect(a4aElement.querySelector('iframe')).to.not.be.ok; expect(onCreativeRenderSpy).to.not.be.called; // call unlayout callback & verify it attempts to revert size - expect(a4a.originalSlotSize_).to.deep - .equal({width: 123, height: 456}); + expect(a4a.originalSlotSize_).to.deep.equal({ + width: 123, + height: 456, + }); let attemptChangeSizeResolver; const attemptChangeSizePromise = new Promise(resolve => { attemptChangeSizeResolver = resolve; }); - sandbox.stub(AMP.BaseElement.prototype, 'attemptChangeSize') - .returns(attemptChangeSizePromise); + sandbox + .stub(AMP.BaseElement.prototype, 'attemptChangeSize') + .returns(attemptChangeSizePromise); a4a.unlayoutCallback(); expect(unlayoutUISpy).to.be.calledOnce; expect(a4a.originalSlotSize_).to.be.ok; attemptChangeSizeResolver(); - return Services.timerFor(a4a.win).promise(1).then(() => { - expect(a4a.originalSlotSize_).to.not.be.ok; - }); + return Services.timerFor(a4a.win) + .promise(1) + .then(() => { + expect(a4a.originalSlotSize_).to.not.be.ok; + }); }); }); }); @@ -1558,8 +1807,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - () => adResponse, {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1570,9 +1821,13 @@ describe('amp-a4a', () => { return a4a.layoutCallback().then(() => { verifySafeFrameRender(a4aElement, '1-2-3'); // Verify preload to safeframe with header version. - expect(doc.querySelector('link[rel=preload]' + - '[href="https://tpc.googlesyndication.com/safeframe/' + - '1-2-3/html/container.html"]')).to.be.ok; + expect( + doc.querySelector( + 'link[rel=preload]' + + '[href="https://tpc.googlesyndication.com/safeframe/' + + '1-2-3/html/container.html"]' + ) + ).to.be.ok; }); }); }); @@ -1585,65 +1840,69 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', - () => adResponse, {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); a4a = new MockA4AImpl(a4aElement); getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); }); }); - it('should delay request until within renderOutsideViewport',() => { + it('should delay request until within renderOutsideViewport', () => { sandbox.stub(a4a, 'delayAdRequestEnabled').returns(true); let whenWithinViewportResolve; - getResourceStub.returns( - { - getUpgradeDelayMs: () => 1, - renderOutsideViewport: () => 3, - whenWithinViewport: viewport => { - expect(viewport).to.equal(3); - return new Promise(resolve => { - whenWithinViewportResolve = resolve; - }); - }, + getResourceStub.returns({ + getUpgradeDelayMs: () => 1, + renderOutsideViewport: () => 3, + whenWithinViewport: viewport => { + expect(viewport).to.equal(3); + return new Promise(resolve => { + whenWithinViewportResolve = resolve; }); + }, + }); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); // Delay to all getAdUrl to potentially execute. - return Services.timerFor(a4a.win).promise(1).then(() => { - expect(getAdUrlSpy).to.not.be.called; - whenWithinViewportResolve(); - return a4a.adPromise_.then(() => { - expect(getAdUrlSpy).to.be.calledOnce; + return Services.timerFor(a4a.win) + .promise(1) + .then(() => { + expect(getAdUrlSpy).to.not.be.called; + whenWithinViewportResolve(); + return a4a.adPromise_.then(() => { + expect(getAdUrlSpy).to.be.calledOnce; + }); }); - }); }); - it('should delay request until numeric value',() => { + it('should delay request until numeric value', () => { sandbox.stub(a4a, 'delayAdRequestEnabled').returns(6); let whenWithinViewportResolve; - getResourceStub.returns( - { - getUpgradeDelayMs: () => 1, - renderOutsideViewport: () => 3, - whenWithinViewport: viewport => { - expect(viewport).to.equal(6); - return new Promise(resolve => { - whenWithinViewportResolve = resolve; - }); - }, + getResourceStub.returns({ + getUpgradeDelayMs: () => 1, + renderOutsideViewport: () => 3, + whenWithinViewport: viewport => { + expect(viewport).to.equal(6); + return new Promise(resolve => { + whenWithinViewportResolve = resolve; }); + }, + }); a4a.buildCallback(); a4a.onLayoutMeasure(); expect(a4a.adPromise_).to.be.instanceof(Promise); // Delay to all getAdUrl to potentially execute. - return Services.timerFor(a4a.win).promise(1).then(() => { - expect(getAdUrlSpy).to.not.be.called; - whenWithinViewportResolve(); - return a4a.adPromise_.then(() => { - expect(getAdUrlSpy).to.be.calledOnce; + return Services.timerFor(a4a.win) + .promise(1) + .then(() => { + expect(getAdUrlSpy).to.not.be.called; + whenWithinViewportResolve(); + return a4a.adPromise_.then(() => { + expect(getAdUrlSpy).to.be.calledOnce; + }); }); - }); }); }); it('should ignore invalid safeframe version header', () => { @@ -1654,8 +1913,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1688,7 +1949,9 @@ describe('amp-a4a', () => { expect(preconnects).to.have.lengthOf(1); // AdSense origin. expect(preconnects[0]).to.have.property( - 'href', 'https://googleads.g.doubleclick.net/'); + 'href', + 'https://googleads.g.doubleclick.net/' + ); }); }); }); @@ -1723,28 +1986,36 @@ describe('amp-a4a', () => { // fixed. it('should parse metadata with wrong opening tag', () => { const creative = buildCreativeString(metaData).replace( - '' + - baseTestDoc.slice(splicePoint))).to.be.null; + '' + + baseTestDoc.slice(splicePoint) + ) + ).to.be.null; }); it('should return null if invalid extensions', () => { metaData.customElementExtensions = 'amp-vine'; @@ -1764,18 +2035,24 @@ describe('amp-a4a', () => { 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); + 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); + const expected = Object.assign( + { + minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, + }, + metaData + ); delete expected.images; expect(actual).to.deep.equal(expected); }); @@ -1783,22 +2060,25 @@ describe('amp-a4a', () => { while (metaData.images.length < 10) { metaData.images.push('https://another.image.com?abc=def'); } - expect(a4a.getAmpAdMetadata(buildCreativeString(metaData)).images.length) - .to.equal(5); + expect( + a4a.getAmpAdMetadata(buildCreativeString(metaData)).images.length + ).to.equal(5); }); it('should throw due to missing CTA type', () => { metaData.ctaUrl = 'http://foo.com'; a4a.isSinglePageStoryAd = true; - expect(() => a4a.getAmpAdMetadata(buildCreativeString(metaData))) - .to.throw(new RegExp(INVALID_SPSA_RESPONSE)); + expect(() => + a4a.getAmpAdMetadata(buildCreativeString(metaData)) + ).to.throw(new RegExp(INVALID_SPSA_RESPONSE)); }); it('should throw due to missing outlink', () => { metaData.ctaType = '0'; a4a.isSinglePageStoryAd = true; - expect(() => a4a.getAmpAdMetadata(buildCreativeString(metaData))) - .to.throw(new RegExp(INVALID_SPSA_RESPONSE)); + expect(() => + a4a.getAmpAdMetadata(buildCreativeString(metaData)) + ).to.throw(new RegExp(INVALID_SPSA_RESPONSE)); }); it('should set appropriate attributes and return metadata object', () => { @@ -1806,9 +2086,12 @@ describe('amp-a4a', () => { metaData.ctaUrl = 'http://foo.com'; a4a.isSinglePageStoryAd = true; const actual = a4a.getAmpAdMetadata(buildCreativeString(metaData)); - const expected = Object.assign({ - minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, - }, metaData); + const expected = Object.assign( + { + minifiedCreative: testFragments.minimalDocOneStyleSrcDoc, + }, + metaData + ); delete expected.ctaType; delete expected.ctaUrl; expect(actual).to.deep.equal(expected); @@ -1820,7 +2103,6 @@ describe('amp-a4a', () => { }); describe('#maybeValidateAmpCreative', () => { - let a4a; let verifier; @@ -1876,7 +2158,9 @@ describe('amp-a4a', () => { }); it('should return 1.25 if prefer-viewability-over-views', () => { a4aElement.setAttribute( - 'data-loading-strategy', 'prefer-viewability-over-views'); + 'data-loading-strategy', + 'prefer-viewability-over-views' + ); expect(a4a.renderOutsideViewport()).to.equal(1.25); a4a.isVerifiedAmpCreative_ = true; expect(a4a.renderOutsideViewport()).to.equal(1.25); @@ -1893,7 +2177,9 @@ describe('amp-a4a', () => { const {doc} = fixture; a4aElement = createA4aElement(doc); a4a = new AmpA4A(a4aElement); - sandbox.stub(a4a, 'getFallback').callsFake(() => {return true;}); + sandbox.stub(a4a, 'getFallback').callsFake(() => { + return true; + }); a4a.buildCallback(); a4a.adUrl_ = 'https://nowhere.org'; }); @@ -1908,44 +2194,54 @@ describe('amp-a4a', () => { expect(friendlyIframe.srcdoc).to.be.ok; const frameDoc = friendlyIframe.contentDocument; const styles = frameDoc.querySelectorAll('style[amp-custom]'); - expect(Array.prototype.some.call(styles, - s => { - return s.innerHTML == 'p { background: green }'; - }), - 'Some style is "background: green"').to.be.true; + expect( + Array.prototype.some.call(styles, s => { + return s.innerHTML == 'p { background: green }'; + }), + 'Some style is "background: green"' + ).to.be.true; expect(frameDoc.body.innerHTML.trim()).to.equal('

    some text

    '); - expect(Services.urlReplacementsForDoc(frameDoc.documentElement)) - .to.not.equal(Services.urlReplacementsForDoc(a4aElement)); + expect( + Services.urlReplacementsForDoc(frameDoc.documentElement) + ).to.not.equal(Services.urlReplacementsForDoc(a4aElement)); }); }); }); describe('#getLayoutPriority', () => { - describes.realWin('with shadow AmpDoc', { - amp: { - ampdoc: 'shadow', + describes.realWin( + 'with shadow AmpDoc', + { + amp: { + ampdoc: 'shadow', + }, }, - }, env => { - it('should return priority of 1', () => { - const body = env.ampdoc.getBody(); - const a4aElement = createA4aElement(env.win.document, null, body); - const a4a = new MockA4AImpl(a4aElement); - expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.METADATA); - }); - }); + env => { + it('should return priority of 1', () => { + const body = env.ampdoc.getBody(); + const a4aElement = createA4aElement(env.win.document, null, body); + const a4a = new MockA4AImpl(a4aElement); + expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.METADATA); + }); + } + ); - describes.realWin('with single AmpDoc', { - amp: { - ampdoc: 'single', + describes.realWin( + 'with single AmpDoc', + { + amp: { + ampdoc: 'single', + }, }, - }, env => { - it('should return priority of 2', () => { - const body = env.ampdoc.getBody(); - const a4aElement = createA4aElement(env.win.document, null, body); - const a4a = new MockA4AImpl(a4aElement); - expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.ADS); - }); - }); + env => { + it('should return priority of 2', () => { + const body = env.ampdoc.getBody(); + const a4aElement = createA4aElement(env.win.document, null, body); + const a4a = new MockA4AImpl(a4aElement); + expect(a4a.getLayoutPriority()).to.equal(LayoutPriority.ADS); + }); + } + ); }); describe('#unlayoutCallback', () => { @@ -1953,8 +2249,10 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -1970,15 +2268,19 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); a4a.buildCallback(); a4a.onLayoutMeasure(); - const attemptChangeSizeStub = - sandbox.stub(AMP.BaseElement.prototype, 'attemptChangeSize'); + const attemptChangeSizeStub = sandbox.stub( + AMP.BaseElement.prototype, + 'attemptChangeSize' + ); // Expect called twice: one for resize and second for reverting. attemptChangeSizeStub.withArgs(123, 456).returns(Promise.resolve()); attemptChangeSizeStub.withArgs(200, 50).returns(Promise.resolve()); @@ -1994,9 +2296,11 @@ describe('amp-a4a', () => { return createIframePromise().then(fixture => { setupForAdTesting(fixture); let whenFirstVisibleResolve = null; - viewerWhenVisibleMock.returns(new Promise(resolve => { - whenFirstVisibleResolve = resolve; - })); + viewerWhenVisibleMock.returns( + new Promise(resolve => { + whenFirstVisibleResolve = resolve; + }) + ); const {doc} = fixture; const a4aElement = createA4aElement(doc); const a4a = new MockA4AImpl(a4aElement); @@ -2009,96 +2313,114 @@ describe('amp-a4a', () => { a4a.uiHandler.state = 0; a4a.unlayoutCallback(); whenFirstVisibleResolve(); - return adPromise.then(unusedError => { - assert.fail('cancelled ad promise should not succeed'); - }).catch(reason => { - expect(getAdUrlSpy.called, 'getAdUrl never called') - .to.be.false; - expect(reason.message).to.equal(cancellation().message); - expect(errorHandlerSpy).to.be.calledOnce; - }); + return adPromise + .then(unusedError => { + assert.fail('cancelled ad promise should not succeed'); + }) + .catch(reason => { + expect(getAdUrlSpy.called, 'getAdUrl never called').to.be.false; + expect(reason.message).to.equal(cancellation().message); + expect(errorHandlerSpy).to.be.calledOnce; + }); }); }); describe('consent integration', () => { let fixture, a4aElement, a4a; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - toggleExperiment(a4a.win, 'amp-consent', true); - return fixture; - })); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + toggleExperiment(a4a.win, 'amp-consent', true); + return fixture; + }) + ); it('should delay ad url by getConsentPolicyState', () => { - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns('default'); + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns('default'); let inResolver; - const policyPromise = new Promise(resolver => inResolver = resolver); - sandbox.stub(Services, 'consentPolicyServiceForDocOrNull') - .returns(Promise.resolve({ - whenPolicyResolved: () => policyPromise, - })); + const policyPromise = new Promise(resolver => (inResolver = resolver)); + sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( + Promise.resolve({ + whenPolicyResolved: () => policyPromise, + }) + ); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); a4a.buildCallback(); a4a.onLayoutMeasure(); // allow ad promise to start execution, unfortunately timer is only way. - return Services.timerFor(a4a.win).promise(50).then(() => { - expect(getAdUrlSpy).to.not.be.called; - inResolver(CONSENT_POLICY_STATE.SUFFICIENT); - return a4a.layoutCallback().then(() => { - expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.SUFFICIENT)) + return Services.timerFor(a4a.win) + .promise(50) + .then(() => { + expect(getAdUrlSpy).to.not.be.called; + inResolver(CONSENT_POLICY_STATE.SUFFICIENT); + return a4a.layoutCallback().then(() => { + expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.SUFFICIENT)) .calledOnce; + }); }); - }); }); it('should not wait on consent if no policy', () => { - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns(null); - const consentServiceSpy = - sandbox.spy(Services, 'consentPolicyServiceForDocOrNull'); + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns(null); + const consentServiceSpy = sandbox.spy( + Services, + 'consentPolicyServiceForDocOrNull' + ); a4a.buildCallback(); a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => - expect(consentServiceSpy).to.not.be.called); + return a4a + .layoutCallback() + .then(() => expect(consentServiceSpy).to.not.be.called); }); it('should pass consent state to getAdUrl', () => { - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns('default'); - sandbox.stub(Services, 'consentPolicyServiceForDocOrNull') - .returns(Promise.resolve({ - whenPolicyResolved: () => - Promise.resolve(CONSENT_POLICY_STATE.SUFFICIENT), - })); + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns('default'); + sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( + Promise.resolve({ + whenPolicyResolved: () => + Promise.resolve(CONSENT_POLICY_STATE.SUFFICIENT), + }) + ); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.SUFFICIENT)) - .calledOnce; + .calledOnce; }); }); it('should return UNKNOWN if consent exception', () => { expectAsyncConsoleError(/Error determining consent state.*consent err/); - sandbox.stub(AMP.BaseElement.prototype, 'getConsentPolicy') - .returns('default'); - sandbox.stub(Services, 'consentPolicyServiceForDocOrNull') - .returns(Promise.resolve({ - whenPolicyResolved: () => {throw new Error('consent err!');}, - })); + sandbox + .stub(AMP.BaseElement.prototype, 'getConsentPolicy') + .returns('default'); + sandbox.stub(Services, 'consentPolicyServiceForDocOrNull').returns( + Promise.resolve({ + whenPolicyResolved: () => { + throw new Error('consent err!'); + }, + }) + ); const getAdUrlSpy = sandbox.spy(a4a, 'getAdUrl'); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.UNKNOWN)) - .calledOnce; + expect(getAdUrlSpy.withArgs(CONSENT_POLICY_STATE.UNKNOWN)).calledOnce; }); }); }); @@ -2106,38 +2428,58 @@ describe('amp-a4a', () => { describe('protectFunctionWrapper', () => { it('works properly with no error', () => { let errorCalls = 0; - expect(protectFunctionWrapper(name => { - return `hello ${name}`; - }, null, () => {errorCalls++;})('world')).to.equal('hello world'); + expect( + protectFunctionWrapper( + name => { + return `hello ${name}`; + }, + null, + () => { + errorCalls++; + } + )('world') + ).to.equal('hello world'); expect(errorCalls).to.equal(0); }); it('handles error properly', () => { const err = new Error('test fail'); - expect(protectFunctionWrapper((name, suffix) => { - expect(name).to.equal('world'); - expect(suffix).to.equal('!'); - throw err; - }, null, (currErr, name, suffix) => { - expect(currErr).to.equal(err); - expect(name).to.equal('world'); - expect(suffix).to.equal('!'); - return 'pass'; - })('world', '!')).to.equal('pass'); + expect( + protectFunctionWrapper( + (name, suffix) => { + expect(name).to.equal('world'); + expect(suffix).to.equal('!'); + throw err; + }, + null, + (currErr, name, suffix) => { + expect(currErr).to.equal(err); + expect(name).to.equal('world'); + expect(suffix).to.equal('!'); + return 'pass'; + } + )('world', '!') + ).to.equal('pass'); }); it('returns undefined if error thrown in error handler', () => { const err = new Error('test fail within fn'); - expect(protectFunctionWrapper((name, suffix) => { - expect(name).to.equal('world'); - expect(suffix).to.be.undefined; - throw err; - }, null, (currErr, name, suffix) => { - expect(currErr).to.equal(err); - expect(name).to.equal('world'); - expect(suffix).to.be.undefined; - throw new Error('test fail within error fn'); - })('world')).to.be.undefined; + expect( + protectFunctionWrapper( + (name, suffix) => { + expect(name).to.equal('world'); + expect(suffix).to.be.undefined; + throw err; + }, + null, + (currErr, name, suffix) => { + expect(currErr).to.equal(err); + expect(name).to.equal('world'); + expect(suffix).to.be.undefined; + throw new Error('test fail within error fn'); + } + )('world') + ).to.be.undefined; }); }); }); @@ -2228,7 +2570,6 @@ describe('amp-a4a', () => { }); describe('#assignAdUrlToError', () => { - it('should attach info to error correctly', () => { const error = new Error('foo'); let queryString = ''; @@ -2251,11 +2592,14 @@ describe('amp-a4a', () => { }); describe('#extractSize', () => { - it('should return a size', () => { - expect(AmpA4A.prototype.extractSize(new Headers({ - 'X-CreativeSize': '320x50', - }))).to.deep.equal({width: 320, height: 50}); + expect( + AmpA4A.prototype.extractSize( + new Headers({ + 'X-CreativeSize': '320x50', + }) + ) + ).to.deep.equal({width: 320, height: 50}); }); it('should return no size', () => { @@ -2294,8 +2638,10 @@ describe('amp-a4a', () => { // long as they're called the appropriate number of times. We stub them // out here because they would otherwise throw errors unrelated to the // behavior actually being tested. - const initiateAdRequestMock = - sandbox.stub(AmpA4A.prototype, 'initiateAdRequest'); + const initiateAdRequestMock = sandbox.stub( + AmpA4A.prototype, + 'initiateAdRequest' + ); initiateAdRequestMock.returns(undefined); const tearDownSlotMock = sandbox.stub(AmpA4A.prototype, 'tearDownSlot'); tearDownSlotMock.returns(undefined); @@ -2304,17 +2650,18 @@ describe('amp-a4a', () => { sandbox.stub(analytics, 'triggerAnalyticsEvent'); expect(a4a.isRefreshing).to.be.false; - return a4a.refresh(() => {}).then(() => { - expect(initiateAdRequestMock).to.be.calledOnce; - expect(tearDownSlotMock).to.be.calledOnce; - expect(a4a.togglePlaceholder).to.be.calledOnce; - expect(a4a.isRefreshing).to.be.true; - expect(a4a.isRelayoutNeededFlag).to.be.true; - }); + return a4a + .refresh(() => {}) + .then(() => { + expect(initiateAdRequestMock).to.be.calledOnce; + expect(tearDownSlotMock).to.be.calledOnce; + expect(a4a.togglePlaceholder).to.be.calledOnce; + expect(a4a.isRefreshing).to.be.true; + expect(a4a.isRelayoutNeededFlag).to.be.true; + }); }); }); - it('should fire an analytics event when refreshing', () => { return createIframePromise().then(f => { const fixture = f; @@ -2335,22 +2682,30 @@ describe('amp-a4a', () => { // long as they're called the appropriate number of times. We stub them // out here because they would otherwise throw errors unrelated to the // behavior actually being tested. - const initiateAdRequestMock = - sandbox.stub(AmpA4A.prototype, 'initiateAdRequest'); + const initiateAdRequestMock = sandbox.stub( + AmpA4A.prototype, + 'initiateAdRequest' + ); initiateAdRequestMock.returns(undefined); const tearDownSlotMock = sandbox.stub(AmpA4A.prototype, 'tearDownSlot'); tearDownSlotMock.returns(undefined); const destroyFrameMock = sandbox.stub(AmpA4A.prototype, 'destroyFrame'); destroyFrameMock.returns(undefined); - const triggerAnalyticsEventStub = sandbox.stub(analytics, - 'triggerAnalyticsEvent'); + const triggerAnalyticsEventStub = sandbox.stub( + analytics, + 'triggerAnalyticsEvent' + ); expect(a4a.isRefreshing).to.be.false; - return a4a.refresh(() => {}).then(() => { - expect(triggerAnalyticsEventStub) - .calledWith(a4a.element, 'ad-refresh'); - }); + return a4a + .refresh(() => {}) + .then(() => { + expect(triggerAnalyticsEventStub).calledWith( + a4a.element, + 'ad-refresh' + ); + }); }); }); @@ -2416,52 +2771,58 @@ describe('amp-a4a', () => { let a4aElement; let a4a; let fixture; - beforeEach(() => createIframePromise().then(f => { - fixture = f; - setupForAdTesting(fixture); - fetchMock.getOnce( - TEST_URL + '&__amp_source_origin=about%3Asrcdoc', () => adResponse, - {name: 'ad'}); - a4aElement = createA4aElement(fixture.doc); - a4a = new MockA4AImpl(a4aElement); - a4a.releaseType_ = '0'; - return fixture; - })); - - it('by default not allowed if crypto signature present but no SSL', - () => { - sandbox.stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') - .returns(false); - a4a.buildCallback(); - a4a.onLayoutMeasure(); - return a4a.layoutCallback().then(() => { - expect(a4aElement.querySelector('iframe[src]')).to.be.ok; - expect(a4aElement.querySelector('iframe[srcdoc]')).to.not.be.ok; - }); - }); - - it('allowed if crypto signature present, no SSL, and overrided' + - ' shouldPreferentialRenderWithoutCrypto', () => { - sandbox.stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') - .returns(false); - sandbox.stub( - AmpA4A.prototype, - 'shouldPreferentialRenderWithoutCrypto').callsFake( - () => true); + beforeEach(() => + createIframePromise().then(f => { + fixture = f; + setupForAdTesting(fixture); + fetchMock.getOnce( + TEST_URL + '&__amp_source_origin=about%3Asrcdoc', + () => adResponse, + {name: 'ad'} + ); + a4aElement = createA4aElement(fixture.doc); + a4a = new MockA4AImpl(a4aElement); + a4a.releaseType_ = '0'; + return fixture; + }) + ); + + it('by default not allowed if crypto signature present but no SSL', () => { + sandbox + .stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') + .returns(false); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { - verifyA4ARender(a4aElement); + expect(a4aElement.querySelector('iframe[src]')).to.be.ok; + expect(a4aElement.querySelector('iframe[srcdoc]')).to.not.be.ok; }); }); + it( + 'allowed if crypto signature present, no SSL, and overrided' + + ' shouldPreferentialRenderWithoutCrypto', + () => { + sandbox + .stub(Services.cryptoFor(fixture.win), 'isPkcsAvailable') + .returns(false); + sandbox + .stub(AmpA4A.prototype, 'shouldPreferentialRenderWithoutCrypto') + .callsFake(() => true); + a4a.buildCallback(); + a4a.onLayoutMeasure(); + return a4a.layoutCallback().then(() => { + verifyA4ARender(a4aElement); + }); + } + ); + it('not allowed if no crypto signature present', () => { delete adResponse.headers['AMP-Fast-Fetch-Signature']; delete adResponse.headers[AMP_SIGNATURE_HEADER]; - sandbox.stub( - AmpA4A.prototype, - 'shouldPreferentialRenderWithoutCrypto').callsFake( - () => true); + sandbox + .stub(AmpA4A.prototype, 'shouldPreferentialRenderWithoutCrypto') + .callsFake(() => true); a4a.buildCallback(); a4a.onLayoutMeasure(); return a4a.layoutCallback().then(() => { @@ -2494,7 +2855,6 @@ describe('amp-a4a', () => { // - All }); - describes.realWin('AmpA4a-RTC', {amp: true}, env => { let element; let a4a; @@ -2526,14 +2886,19 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { expect(a4a.tryExecuteRealTimeConfig_()).to.be.undefined; }); it('should log user error if RTC Config set but RTC not supported', () => { - element.setAttribute('rtc-config', - JSON.stringify({'urls': ['https://a.com']})); - expect(allowConsoleError(() => a4a.tryExecuteRealTimeConfig_())) - .to.be.undefined; + element.setAttribute( + 'rtc-config', + JSON.stringify({'urls': ['https://a.com']}) + ); + expect(allowConsoleError(() => a4a.tryExecuteRealTimeConfig_())).to.be + .undefined; expect(errorSpy.calledOnce).to.be.true; - expect(errorSpy.calledWith( + expect( + errorSpy.calledWith( 'amp-a4a', - 'RTC not supported for ad network doubleclick')).to.be.true; + 'RTC not supported for ad network doubleclick' + ) + ).to.be.true; }); }); @@ -2562,7 +2927,8 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { } a4a.postAdResponseExperimentFeatures['pref_neutral_enabled'] = prefVal; expect(a4a.inNonAmpPreferenceExp()).to.equal(!!expected); - })); + }) + ); }); describe('#sandboxHTMLCreativeFrame', () => { @@ -2583,9 +2949,13 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { a4a.maybeAddSinglePassExperiment(); const isInSinglePass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS + ); const isInMultiPass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS + ); expect(isInSinglePass).to.be.true; expect(isInMultiPass).to.be.false; @@ -2598,9 +2968,13 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { a4a.maybeAddSinglePassExperiment(); const isInSinglePass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS + ); const isInMultiPass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS + ); expect(isInSinglePass).to.be.false; expect(isInMultiPass).to.be.true; @@ -2613,9 +2987,13 @@ describes.realWin('AmpA4a-RTC', {amp: true}, env => { a4a.maybeAddSinglePassExperiment(); const isInSinglePass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.SINGLE_PASS + ); const isInMultiPass = isInExperiment( - a4a.element, SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS); + a4a.element, + SINGLE_PASS_EXPERIMENT_IDS.MULTI_PASS + ); expect(isInSinglePass).to.be.false; expect(isInMultiPass).to.be.false; diff --git a/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js b/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js index 8c0c9e22989b7..afad52d41ed0c 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js +++ b/extensions/amp-a4a/0.1/test/test-amp-ad-template-helper.js @@ -18,11 +18,10 @@ import {AmpAdTemplateHelper} from '../amp-ad-template-helper'; import {AmpMustache} from '../../../amp-mustache/0.1/amp-mustache'; import {Xhr} from '../../../../src/service/xhr-impl'; - describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { - - const cdnUrl = 'https://adserver-com.cdn.ampproject.org/ad/s/' + - 'adserver.com/amp_template_1'; + const cdnUrl = + 'https://adserver-com.cdn.ampproject.org/ad/s/' + + 'adserver.com/amp_template_1'; const canonicalUrl = 'https://adserver.com/amp_template_1'; let win, doc; @@ -39,21 +38,22 @@ describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { it('should return a promise resolving to a string template', () => { const template = 'content not important here'; - fetchTextMock.withArgs( - cdnUrl, - { - mode: 'cors', - method: 'GET', - ampCors: false, - credentials: 'omit', + fetchTextMock + .withArgs(cdnUrl, { + mode: 'cors', + method: 'GET', + ampCors: false, + credentials: 'omit', + }) + .returns( + Promise.resolve({ + headers: {}, + text: () => template, }) - .returns(Promise.resolve( - { - headers: {}, - text: () => template, - })); - return ampAdTemplateHelper.fetch(canonicalUrl) - .then(fetchedTemplate => expect(fetchedTemplate).to.equal(template)); + ); + return ampAdTemplateHelper + .fetch(canonicalUrl) + .then(fetchedTemplate => expect(fetchedTemplate).to.equal(template)); }); it('should use CDN url if one is supplied', () => { @@ -61,8 +61,9 @@ describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { }); it('should convert canonical to CDN', () => { - expect(ampAdTemplateHelper.getTemplateProxyUrl_(canonicalUrl)) - .to.equal(cdnUrl); + expect(ampAdTemplateHelper.getTemplateProxyUrl_(canonicalUrl)).to.equal( + cdnUrl + ); }); it('should render a template with correct values', () => { @@ -72,35 +73,38 @@ describes.fakeWin('AmpAdTemplateHelper', {amp: true}, env => { it('should render a template with correct values', () => { win.AMP.registerTemplate('amp-mustache', AmpMustache); const parentDiv = doc.createElement('div'); - parentDiv./*OK*/innerHTML = - ''; + parentDiv./*OK*/ innerHTML = + ''; doc.body.appendChild(parentDiv); return ampAdTemplateHelper.render({foo: 'bar'}, parentDiv).then(result => { expect(result).to.not.be.null; - expect(result./*OK*/innerHTML).to.equal('bar'); + expect(result./*OK*/ innerHTML).to.equal('bar'); }); }); it('should insert analytics component', () => { const parentDiv = doc.createElement('div'); - parentDiv./*OK*/innerHTML = - '

    123

    '; + parentDiv./*OK*/ innerHTML = '

    123

    '; doc.body.appendChild(parentDiv); - const analytics = [{ - 'remote': 'remoteUrl', - 'inline': { - 'requests': 'r', + const analytics = [ + { + 'remote': 'remoteUrl', + 'inline': { + 'requests': 'r', + }, }, - }, { - 'type': 'googleanalytics', - }]; + { + 'type': 'googleanalytics', + }, + ]; ampAdTemplateHelper.insertAnalytics(parentDiv, analytics); expect(parentDiv.childNodes.length).to.equal(3); - expect(parentDiv.innerHTML).to.equal('

    123

    ' + + expect(parentDiv.innerHTML).to.equal( + '

    123

    ' + '' + '' + '' + - ''); + '' + ); }); }); - diff --git a/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js b/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js index a408d0d6a5bb9..47cde5ec42b25 100644 --- a/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js +++ b/extensions/amp-a4a/0.1/test/test-amp-ad-utils.js @@ -18,7 +18,6 @@ import {data} from './testdata/valid_css_at_rules_amp.reserialized'; import {getAmpAdMetadata} from '../amp-ad-utils'; describe('getAmpAdMetadata', () => { - it('should parse metadata successfully', () => { const creativeMetadata = getAmpAdMetadata(data.reserialized); expect(creativeMetadata).to.be.ok; @@ -52,8 +51,8 @@ describe('getAmpAdMetadata', () => { it('should return null -- missing closing script tag', () => { const creativeMetadata = getAmpAdMetadata( - data.reserializedMissingScriptTag); + data.reserializedMissingScriptTag + ); expect(creativeMetadata).to.be.null; }); - }); diff --git a/extensions/amp-a4a/0.1/test/test-callout-vendors.js b/extensions/amp-a4a/0.1/test/test-callout-vendors.js index 9b95a5cb08148..67193a00e1a06 100644 --- a/extensions/amp-a4a/0.1/test/test-callout-vendors.js +++ b/extensions/amp-a4a/0.1/test/test-callout-vendors.js @@ -21,13 +21,15 @@ import {isSecureUrlDeprecated} from '../../../../src/url'; // This test acts as a presubmit to enforce that. describe('RTC_VENDORS', () => { it('should have all lowercase keys', () => - Object.keys(RTC_VENDORS).forEach( - key => expect(key).to.equal(key.toLowerCase()) + Object.keys(RTC_VENDORS).forEach(key => + expect(key).to.equal(key.toLowerCase()) )); it('should all use https', () => Object.keys(RTC_VENDORS).forEach(key => { expect(isSecureUrlDeprecated(RTC_VENDORS[key].url)).to.be.true; - expect(!RTC_VENDORS[key].errorReportingUrl || isSecureUrlDeprecated( - RTC_VENDORS[key].errorReportingUrl)).to.be.true; + expect( + !RTC_VENDORS[key].errorReportingUrl || + isSecureUrlDeprecated(RTC_VENDORS[key].errorReportingUrl) + ).to.be.true; })); }); diff --git a/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js b/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js index 85ef549c488aa..9b8df7b278b31 100644 --- a/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js +++ b/extensions/amp-a4a/0.1/test/test-cryptographic-validator.js @@ -31,7 +31,6 @@ const realWinConfig = { }; describes.realWin('CryptographicValidator', realWinConfig, env => { - const headers = {'Content-Type': 'application/jwk-set+json'}; let sandbox; let userErrorStub; @@ -53,56 +52,59 @@ describes.realWin('CryptographicValidator', realWinConfig, env => { env.win[SIGNATURE_VERIFIER_PROPERTY_NAME] = { verify: () => Promise.resolve(VerificationStatus.OK), }; - return validator.validate( - {win: env.win}, utf8Encode(data.reserialized), headers) - .then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.AMP); - expect(validatorOutput.adResponseType).to.equal( - AdResponseType.CRYPTO); - expect(validatorOutput.creativeData).to.be.ok; + return validator + .validate({win: env.win}, utf8Encode(data.reserialized), headers) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.AMP); + expect(validatorOutput.adResponseType).to.equal(AdResponseType.CRYPTO); + expect(validatorOutput.creativeData).to.be.ok; - const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.minifiedCreative).to.equal( - data.minifiedCreative); - }); + const {creativeMetadata} = validatorOutput.creativeData; + expect(creativeMetadata.minifiedCreative).to.equal( + data.minifiedCreative + ); + }); }); it('should have non-AMP validator result', () => { env.win[SIGNATURE_VERIFIER_PROPERTY_NAME] = { verify: () => Promise.resolve(VerificationStatus.UNVERIFIED), }; - return validator.validate( - {win: env.win}, utf8Encode(data.reserialized), headers) - .then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - expect(validatorOutput.adResponseType).to.equal( - AdResponseType.CRYPTO); - expect(validatorOutput.creativeData).to.be.ok; + return validator + .validate({win: env.win}, utf8Encode(data.reserialized), headers) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); + expect(validatorOutput.adResponseType).to.equal(AdResponseType.CRYPTO); + expect(validatorOutput.creativeData).to.be.ok; - const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.minifiedCreative).to.equal( - data.minifiedCreative); - expect(userErrorStub).to.be.calledOnce; - }); + const {creativeMetadata} = validatorOutput.creativeData; + expect(creativeMetadata.minifiedCreative).to.equal( + data.minifiedCreative + ); + expect(userErrorStub).to.be.calledOnce; + }); }); it('should have non-AMP validator result due to bad metadata', () => { env.win[SIGNATURE_VERIFIER_PROPERTY_NAME] = { verify: () => Promise.resolve(VerificationStatus.UNVERIFIED), }; - return validator.validate( - {win: env.win}, utf8Encode(data.reserializedInvalidOffset), headers) - .then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - expect(validatorOutput.adResponseType).to.equal( - AdResponseType.CRYPTO); - expect(validatorOutput.creativeData).to.be.ok; - expect(validatorOutput.creativeData.creativeMetadata).to.not.be.ok; + return validator + .validate( + {win: env.win}, + utf8Encode(data.reserializedInvalidOffset), + headers + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); + expect(validatorOutput.adResponseType).to.equal(AdResponseType.CRYPTO); + expect(validatorOutput.creativeData).to.be.ok; + expect(validatorOutput.creativeData.creativeMetadata).to.not.be.ok; - expect(userErrorStub).to.be.calledOnce; - }); + expect(userErrorStub).to.be.calledOnce; + }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js b/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js index b685d574f030f..4cf13a2214e0e 100644 --- a/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js +++ b/extensions/amp-a4a/0.1/test/test-friendly-frame-renderer.js @@ -23,7 +23,6 @@ const realWinConfig = { }; describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { - const minifiedCreative = '

    Hello, World!

    '; let containerElement; @@ -52,7 +51,10 @@ describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { }); containerElement.renderStarted = () => {}; containerElement.getLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.isInViewport = () => true; containerElement.getAmpDoc = () => env.ampdoc; @@ -69,16 +71,18 @@ describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { return renderPromise.then(() => { const iframe = containerElement.querySelector('iframe'); expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.innerHTML) - .to.equal(minifiedCreative); + expect(iframe.contentWindow.document.body.innerHTML).to.equal( + minifiedCreative + ); }); }); it('should set the correct srcdoc on the iframe', () => { - const srcdoc = '' - + '' - + '

    Hello, World!

    '; + const srcdoc = + '' + + '" + + '

    Hello, World!

    '; return renderPromise.then(() => { const iframe = containerElement.querySelector('iframe'); expect(iframe).to.be.ok; @@ -103,8 +107,9 @@ describes.realWin('FriendlyFrameRenderer', realWinConfig, env => { return renderPromise.then(() => { const iframe = containerElement.querySelector('iframe'); expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.style.visibility) - .to.equal('visible'); + expect(iframe.contentWindow.document.body.style.visibility).to.equal( + 'visible' + ); }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js b/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js index 9ca3e610c4c71..f1eff93b587bb 100644 --- a/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js +++ b/extensions/amp-a4a/0.1/test/test-friendly-frame-util.js @@ -23,7 +23,6 @@ const realWinConfig = { }; describes.realWin('FriendlyFrameUtil', realWinConfig, env => { - const minifiedCreative = '

    Hello, World!

    '; let containerElement; @@ -36,7 +35,10 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { }); containerElement.renderStarted = () => {}; containerElement.getLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.isInViewport = () => true; containerElement.getAmpDoc = () => env.ampdoc; @@ -51,7 +53,11 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { }; renderPromise = renderCreativeIntoFriendlyFrame( - adUrl, size, containerElement, creativeMetadata); + adUrl, + size, + containerElement, + creativeMetadata + ); }); afterEach(() => { @@ -61,16 +67,18 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { it('should append iframe child', () => { return renderPromise.then(iframe => { expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.innerHTML) - .to.equal(minifiedCreative); + expect(iframe.contentWindow.document.body.innerHTML).to.equal( + minifiedCreative + ); }); }); it('should set the correct srcdoc on the iframe', () => { - const srcdoc = '' - + '' - + '

    Hello, World!

    '; + const srcdoc = + '' + + '" + + '

    Hello, World!

    '; return renderPromise.then(iframe => { expect(iframe).to.be.ok; expect(iframe.getAttribute('srcdoc')).to.equal(srcdoc); @@ -92,9 +100,9 @@ describes.realWin('FriendlyFrameUtil', realWinConfig, env => { it('should style body of iframe document to be visible', () => { return renderPromise.then(iframe => { expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.style.visibility) - .to.equal('visible'); + expect(iframe.contentWindow.document.body.style.visibility).to.equal( + 'visible' + ); }); }); }); - diff --git a/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js b/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js index e79193b80f723..e4a05c736e4dd 100644 --- a/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js +++ b/extensions/amp-a4a/0.1/test/test-name-frame-renderer.js @@ -25,7 +25,6 @@ const realWinConfig = { }; describes.realWin('NameFrameRenderer', realWinConfig, env => { - const minifiedCreative = '

    Hello, World!

    '; let containerElement; @@ -50,7 +49,10 @@ describes.realWin('NameFrameRenderer', realWinConfig, env => { containerElement.setAttribute('height', 50); containerElement.setAttribute('width', 320); containerElement.getPageLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.getIntersectionChangeEntry = () => ({}); document.body.appendChild(containerElement); diff --git a/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js b/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js index f8a9a7b1e86d5..fe999677fc711 100644 --- a/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js +++ b/extensions/amp-a4a/0.1/test/test-real-time-config-manager.js @@ -28,10 +28,7 @@ import { import {Services} from '../../../../src/services'; import {Xhr} from '../../../../src/service/xhr-impl'; import {createElementWithAttributes} from '../../../../src/dom'; -import { - dev, - user, -} from '../../../../src/log'; +import {dev, user} from '../../../../src/log'; import {isFiniteNumber} from '../../../../src/types'; describes.realWin('real-time-config-manager', {amp: true}, env => { @@ -63,8 +60,10 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { // RealTimeConfigManager uses the UrlReplacements service scoped to the A4A // (FIE), but for testing stub in the parent service for simplicity. const urlReplacements = Services.urlReplacementsForDoc(element); - sandbox.stub(Services, 'urlReplacementsForDoc') - .withArgs(a4aElement.element).returns(urlReplacements); + sandbox + .stub(Services, 'urlReplacementsForDoc') + .withArgs(a4aElement.element) + .returns(urlReplacements); rtc = new RealTimeConfigManager(a4aElement); maybeExecuteRealTimeConfig_ = rtc.maybeExecuteRealTimeConfig.bind(rtc); @@ -84,13 +83,16 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { fetchJsonStub.withArgs(params).returns(Promise.reject('FAIL')); } else { const textFunction = () => { - return !isString ? Promise.resolve(JSON.stringify(response)) : - Promise.resolve(response); + return !isString + ? Promise.resolve(JSON.stringify(response)) + : Promise.resolve(response); }; - fetchJsonStub.withArgs(params).returns(Promise.resolve({ - status: 200, - text: textFunction, - })); + fetchJsonStub.withArgs(params).returns( + Promise.resolve({ + status: 200, + text: textFunction, + }) + ); } } @@ -119,27 +121,40 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { }); it('should convert & trunc url when parseable', () => { - const url = 'https://www.example.com/thisIsTooMany' + - 'Characters1234567891011121314.php'; + const url = + 'https://www.example.com/thisIsTooMany' + + 'Characters1234567891011121314.php'; const ard = getCalloutParam_(url); expect(ard).to.equal( - 'www.example.com/thisIsTooManyCharacters12345678910'); + 'www.example.com/thisIsTooManyCharacters12345678910' + ); }); }); describe('#maybeExecuteRealTimeConfig_', () => { function executeTest(args) { - const {urls, vendors, timeoutMillis, rtcCalloutResponses, - expectedCalloutUrls, responseIsString, failXhr, - expectedRtcArray, calloutCount} = args; + const { + urls, + vendors, + timeoutMillis, + rtcCalloutResponses, + expectedCalloutUrls, + responseIsString, + failXhr, + expectedRtcArray, + calloutCount, + } = args; setRtcConfig({urls, vendors, timeoutMillis}); (expectedCalloutUrls || []).forEach((expectedUrl, i) => { - setFetchJsonStubBehavior(expectedUrl, rtcCalloutResponses[i], - responseIsString, failXhr); + setFetchJsonStubBehavior( + expectedUrl, + rtcCalloutResponses[i], + responseIsString, + failXhr + ); }); const customMacros = args['customMacros'] || {}; - const rtcResponsePromiseArray = maybeExecuteRealTimeConfig_( - customMacros); + const rtcResponsePromiseArray = maybeExecuteRealTimeConfig_(customMacros); return rtcResponsePromiseArray.then(rtcResponseArray => { expect(rtcResponseArray.length).to.equal(expectedRtcArray.length); expect(fetchJsonStub.callCount).to.equal(calloutCount); @@ -148,19 +163,23 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { }); rtcResponseArray.forEach((rtcResponse, i) => { expect(rtcResponse.response).to.deep.equal( - expectedRtcArray[i].response); - expect(rtcResponse.callout).to.equal( - expectedRtcArray[i].callout); + expectedRtcArray[i].response + ); + expect(rtcResponse.callout).to.equal(expectedRtcArray[i].callout); expect(rtcResponse.error).to.equal(expectedRtcArray[i].error); expect(Object.keys(rtcResponse).sort()).to.deep.equal( - Object.keys(expectedRtcArray[i]).sort()); + Object.keys(expectedRtcArray[i]).sort() + ); expect(isFiniteNumber(rtcResponse.rtcTime)).to.be.true; }); }); } const urlMacros = [ - 'slot_id=SLOT_ID', 'page_id=PAGE_ID', 'adx=ADX', 'ady=ADY', + 'slot_id=SLOT_ID', + 'page_id=PAGE_ID', + 'adx=ADX', + 'ady=ADY', ]; function generateUrls(numUrls, numMacroUrls) { @@ -169,7 +188,11 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { urls.push(`https://www.${i}.com/`); } for (let i = 0; i < numMacroUrls; i++) { - urls.push(`https://www.${i + numUrls}.com/?${urlMacros.slice(0,i + 1).join('&')}`); + urls.push( + `https://www.${i + numUrls}.com/?${urlMacros + .slice(0, i + 1) + .join('&')}` + ); } return urls; } @@ -178,8 +201,9 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { // If this is an entry for a vendor, then the callout is just // the vendor name passed in to url here. const callout = !!isVendor ? url : getCalloutParam_(url); - return response ? {response, callout, rtcTime: 10} : - {callout, error, rtcTime: 10}; + return response + ? {response, callout, rtcTime: 10} + : {callout, error, rtcTime: 10}; } function generateCalloutResponses(numGoodResponses) { @@ -188,7 +212,7 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { for (let i = 0; i < numGoodResponses; i++) { response = {}; response[`response${i}`] = {}; - response[`response${i}`][`foo${i}`] = [`a${i}`,`b${i}`,`c${i}`]; + response[`response${i}`][`foo${i}`] = [`a${i}`, `b${i}`, `c${i}`]; rtcCalloutResponses.push(response); } return rtcCalloutResponses; @@ -201,19 +225,30 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { {'response1': {'fooArray': ['foo']}}, {'response2': {'test': 'test2'}}, {'response3': {'apple': 'banana'}}, - {'response4': {'animalArray': ['cat', 'dog'], - 'foodObject': {'apple': true, 'car': false}}}, + { + 'response4': { + 'animalArray': ['cat', 'dog'], + 'foodObject': {'apple': true, 'car': false}, + }, + }, {'response5': [1, 2, 3]}, ]; const expectedRtcArray = []; urls.forEach((url, i) => { expectedRtcArray.push({ - callout: getCalloutParam_(url), rtcTime: 10, - response: rtcCalloutResponses[i]}); + callout: getCalloutParam_(url), + rtcTime: 10, + response: rtcCalloutResponses[i], + }); }); return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls: urls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: urls, + expectedRtcArray, + }); }); it('should send only 5 RTC callouts for all URLS without macros', () => { @@ -225,17 +260,24 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { for (let i = 0; i < 5; i++) { expectedRtcArray.push(rtcEntry(rtcCalloutResponses[i], urls[i])); } - expectedRtcArray.push(rtcEntry(null, urls[5], - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED)); - expectedRtcArray.push(rtcEntry(null, urls[6], - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED)); + expectedRtcArray.push( + rtcEntry(null, urls[5], RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED) + ); + expectedRtcArray.push( + rtcEntry(null, urls[6], RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED) + ); return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls, + expectedRtcArray, + }); }); it('should send RTC callouts to inflated publisher URLs', () => { - const urls = generateUrls(1,2); + const urls = generateUrls(1, 2); const inflatedUrls = [ 'https://www.0.com/', 'https://www.1.com/?slot_id=1', @@ -252,8 +294,15 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { expectedRtcArray.push(rtcEntry(rtcResponse, inflatedUrls[i])); }); const calloutCount = 3; - return executeTest({urls, customMacros, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + return executeTest({ + urls, + customMacros, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should send RTC callouts to inflated vendor URLs', () => { const vendors = { @@ -262,46 +311,65 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const inflatedUrls = [ 'https://localhost:8000/examples/rtcE1.json?slot_id=1&page_id=3&foo_id=4', ]; - const rtcCalloutResponses = [ - {'response1': {'fooArray': ['foo']}}, - ]; + const rtcCalloutResponses = [{'response1': {'fooArray': ['foo']}}]; const customMacros = { PAGE_ID: () => 3, FOO_ID: () => 4, }; const calloutCount = 1; const expectedRtcArray = []; - expectedRtcArray.push(rtcEntry(rtcCalloutResponses[0], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + expectedRtcArray.push( + rtcEntry( + rtcCalloutResponses[0], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); return executeTest({ - vendors, customMacros, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + vendors, + customMacros, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should send callouts to vendor URLs with object/array macros', () => { const vendors = { 'fAkeVeNdOR': { SLOT_ID: {'key': 'value'}, - PAGE_ID: [1,2,3], + PAGE_ID: [1, 2, 3], FOO_ID: 'String', }, }; const inflatedUrls = [ 'https://localhost:8000/examples/rtcE1.json?slot_id=%7B%22key%22%3A%22' + - 'value%22%7D&page_id=%5B1%2C2%2C3%5D&foo_id=String', - ]; - const rtcCalloutResponses = [ - {'response1': {'fooArray': ['foo']}}, + 'value%22%7D&page_id=%5B1%2C2%2C3%5D&foo_id=String', ]; + const rtcCalloutResponses = [{'response1': {'fooArray': ['foo']}}]; const calloutCount = 1; const expectedRtcArray = []; - expectedRtcArray.push(rtcEntry(rtcCalloutResponses[0], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + expectedRtcArray.push( + rtcEntry( + rtcCalloutResponses[0], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); return executeTest({ - vendors, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + vendors, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should send RTC callouts to inflated publisher and vendor URLs', () => { - const urls = generateUrls(2,2); + const urls = generateUrls(2, 2); const vendors = { 'fAkeVeNdOR': {SLOT_ID: 0, PAGE_ID: 1}, }; @@ -321,14 +389,28 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; for (let i = 0; i < 4; i++) { expectedRtcArray.push( - rtcEntry(rtcCalloutResponses[i], inflatedUrls[i])); + rtcEntry(rtcCalloutResponses[i], inflatedUrls[i]) + ); } - expectedRtcArray.push(rtcEntry(rtcCalloutResponses[4], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + expectedRtcArray.push( + rtcEntry( + rtcCalloutResponses[4], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); const calloutCount = 5; return executeTest({ - urls, vendors, customMacros, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + urls, + vendors, + customMacros, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should ignore bad macros for vendor urls', () => { const vendors = { @@ -341,18 +423,28 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; for (let i = 0; i < 1; i++) { expectedRtcArray.push( - rtcEntry(rtcCalloutResponses[i], - Object.keys(vendors)[0].toLowerCase(), undefined, true)); + rtcEntry( + rtcCalloutResponses[i], + Object.keys(vendors)[0].toLowerCase(), + undefined, + true + ) + ); } const calloutCount = 1; sandbox.stub(user(), 'error').callsFake(() => {}); return executeTest({ - vendors, inflatedUrls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + vendors, + inflatedUrls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should favor publisher URLs over vendor URLs', () => { - const urls = generateUrls(3,2); + const urls = generateUrls(3, 2); const vendors = { 'fAkeVeNdOR': {SLOT_ID: 0, PAGE_ID: 1}, }; @@ -372,35 +464,53 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; for (let i = 0; i < 5; i++) { expectedRtcArray.push( - rtcEntry(rtcCalloutResponses[i], inflatedUrls[i])); + rtcEntry(rtcCalloutResponses[i], inflatedUrls[i]) + ); } expectedRtcArray.push( - rtcEntry(null, Object.keys(vendors)[0].toLowerCase(), - RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, true)); + rtcEntry( + null, + Object.keys(vendors)[0].toLowerCase(), + RTC_ERROR_ENUM.MAX_CALLOUTS_EXCEEDED, + true + ) + ); const calloutCount = 5; return executeTest({ - urls, vendors, customMacros, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: inflatedUrls, expectedRtcArray}); + urls, + vendors, + customMacros, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: inflatedUrls, + expectedRtcArray, + }); }); it('should not send more than one RTC callout to the same url', () => { - const urls = [ - 'https://www.0.com/', - 'https://www.0.com/', - ]; + const urls = ['https://www.0.com/', 'https://www.0.com/']; const rtcCalloutResponses = generateCalloutResponses(1); const calloutCount = 1; - const expectedCalloutUrls = [ - 'https://www.0.com/', - ]; + const expectedCalloutUrls = ['https://www.0.com/']; const expectedRtcArray = [ - {response: rtcCalloutResponses[0], - callout: getCalloutParam_(urls[0]), rtcTime: 10}, - {callout: getCalloutParam_(urls[1]), - error: RTC_ERROR_ENUM.DUPLICATE_URL, rtcTime: 10}, + { + response: rtcCalloutResponses[0], + callout: getCalloutParam_(urls[0]), + rtcTime: 10, + }, + { + callout: getCalloutParam_(urls[1]), + error: RTC_ERROR_ENUM.DUPLICATE_URL, + rtcTime: 10, + }, ]; return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls, + expectedRtcArray, + }); }); it('should not send an RTC callout to an insecure url', () => { @@ -414,21 +524,32 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { {'response2': {'insecure': ['virus']}}, ]; const calloutCount = 2; - const expectedCalloutUrls = [ - 'https://www.1.com/', - 'https://www.2.com', - ]; + const expectedCalloutUrls = ['https://www.1.com/', 'https://www.2.com']; const expectedRtcArray = [ - {response: rtcCalloutResponses[0], - callout: getCalloutParam_(urls[0]), rtcTime: 10}, - {response: rtcCalloutResponses[1], - callout: getCalloutParam_(urls[1]), rtcTime: 10}, - {callout: getCalloutParam_(urls[2]), - error: RTC_ERROR_ENUM.INSECURE_URL, rtcTime: 10}, + { + response: rtcCalloutResponses[0], + callout: getCalloutParam_(urls[0]), + rtcTime: 10, + }, + { + response: rtcCalloutResponses[1], + callout: getCalloutParam_(urls[1]), + rtcTime: 10, + }, + { + callout: getCalloutParam_(urls[2]), + error: RTC_ERROR_ENUM.INSECURE_URL, + rtcTime: 10, + }, ]; return executeTest({ - urls, inflatedUrls: urls, rtcCalloutResponses, calloutCount, - expectedCalloutUrls, expectedRtcArray}); + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls, + expectedRtcArray, + }); }); it('should not send RTC callout to unknown vendor', () => { const vendors = { @@ -437,8 +558,13 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const calloutCount = 0; const expectedRtcArray = []; expectedRtcArray.push( - rtcEntry(null, Object.keys(vendors)[0].toLowerCase(), - RTC_ERROR_ENUM.UNKNOWN_VENDOR, true)); + rtcEntry( + null, + Object.keys(vendors)[0].toLowerCase(), + RTC_ERROR_ENUM.UNKNOWN_VENDOR, + true + ) + ); return executeTest({vendors, calloutCount, expectedRtcArray}); }); it('should handle bad JSON response', () => { @@ -447,12 +573,19 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; rtcCalloutResponses.forEach((rtcResponse, i) => { expectedRtcArray.push( - rtcEntry(null, urls[i], RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE)); + rtcEntry(null, urls[i], RTC_ERROR_ENUM.MALFORMED_JSON_RESPONSE) + ); }); const calloutCount = 1; - return executeTest({urls, inflatedUrls: urls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: urls, expectedRtcArray, - responseIsString: true}); + return executeTest({ + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: urls, + expectedRtcArray, + responseIsString: true, + }); }); it('should catch errors due to network failure', () => { const urls = generateUrls(1); @@ -460,24 +593,32 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const expectedRtcArray = []; rtcCalloutResponses.forEach((rtcResponse, i) => { expectedRtcArray.push( - rtcEntry(null, urls[i], RTC_ERROR_ENUM.NETWORK_FAILURE)); + rtcEntry(null, urls[i], RTC_ERROR_ENUM.NETWORK_FAILURE) + ); }); const calloutCount = 1; - return executeTest({urls, inflatedUrls: urls, rtcCalloutResponses, - calloutCount, expectedCalloutUrls: urls, expectedRtcArray, - failXhr: true}); + return executeTest({ + urls, + inflatedUrls: urls, + rtcCalloutResponses, + calloutCount, + expectedCalloutUrls: urls, + expectedRtcArray, + failXhr: true, + }); }); for (const consentState in CONSENT_POLICY_STATE) { it(`should handle consentState ${consentState}`, () => { setRtcConfig({urls: ['https://foo.com']}); const rtcResult = maybeExecuteRealTimeConfig_( - {}, CONSENT_POLICY_STATE[consentState]); + {}, + CONSENT_POLICY_STATE[consentState] + ); switch (CONSENT_POLICY_STATE[consentState]) { case CONSENT_POLICY_STATE.SUFFICIENT: case CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED: expect(rtcResult).to.be.ok; - return rtcResult.then(() => - expect(fetchJsonStub).to.be.calledOnce); + return rtcResult.then(() => expect(fetchJsonStub).to.be.calledOnce); case CONSENT_POLICY_STATE.UNKNOWN: case CONSENT_POLICY_STATE.INSUFFICIENT: return rtcResult.then(result => { @@ -500,12 +641,17 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should return parsed rtcConfig for valid rtcConfig', () => { const rtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 500}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 500, + }; setRtcConfig(rtcConfig); validateRtcConfig_(element); expect(rtc.rtcConfig_).to.be.ok; @@ -514,12 +660,17 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should allow timeout of 0', () => { const rtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 0}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 0, + }; setRtcConfig(rtcConfig); validateRtcConfig_(element); expect(rtc.rtcConfig_).to.be.ok; @@ -528,19 +679,29 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should not allow timeout greater than default', () => { const rtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 1000000}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 1000000, + }; const expectedRtcConfig = { - 'vendors': {'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + 'vendors': { + 'fakeVendor': {'SLOT_ID': '1', 'PAGE_ID': '1'}, 'nonexistent-vendor': {'SLOT_ID': '1'}, - 'fakeVendor2': {'SLOT_ID': '1'}}, - 'urls': ['https://localhost:4443/posts?slot_id=SLOT_ID', - 'https://broken.zzzzzzz'], - 'timeoutMillis': 1000}; + 'fakeVendor2': {'SLOT_ID': '1'}, + }, + 'urls': [ + 'https://localhost:4443/posts?slot_id=SLOT_ID', + 'https://broken.zzzzzzz', + ], + 'timeoutMillis': 1000, + }; setRtcConfig(rtcConfig); validateRtcConfig_(element); expect(rtc.rtcConfig_).to.be.ok; @@ -552,9 +713,13 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { }); // Test various misconfigurations that are missing vendors or urls. - [{'timeoutMillis': 500}, {'vendors': {}}, {'urls': []}, + [ + {'timeoutMillis': 500}, + {'vendors': {}}, + {'urls': []}, {'vendors': {}, 'urls': []}, - {'vendors': 'incorrect', 'urls': 'incorrect'}].forEach(rtcConfig => { + {'vendors': 'incorrect', 'urls': 'incorrect'}, + ].forEach(rtcConfig => { it('should return false for rtcConfig missing required values', () => { setRtcConfig(rtcConfig); allowConsoleError(() => { @@ -582,15 +747,18 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { const macroDelay = 20; const macros = { DUMMY: () => { - return Services.timerFor(env.win).promise(macroDelay).then( - () => {return 'foo';} - ); + return Services.timerFor(env.win) + .promise(macroDelay) + .then(() => { + return 'foo'; + }); }, }; inflateAndSendRtc_(url, macros); return rtc.promiseArray_[0].then(errorResponse => { expect(errorResponse.error).to.equal( - RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT); + RTC_ERROR_ENUM.MACRO_EXPAND_TIMEOUT + ); }); }); @@ -606,12 +774,10 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { return rtc.promiseArray_[0].then(errorResponse => { expect(errorResponse).to.be.undefined; }); - }); }); describe('modifyRtcConfigForConsentStateSettings', () => { - beforeEach(() => { rtc.rtcConfig_ = { 'vendors': { @@ -623,7 +789,8 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { 'https://www.rtc.com/example1', 'https://www.other-rtc.com/example2', ], - 'timeoutMillis': 500}; + 'timeoutMillis': 500, + }; }); it('should not modify rtcConfig if consent state is valid', () => { @@ -669,12 +836,18 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should clear just invalid custom URLs', () => { rtc.rtcConfig_.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, - 'vendorB': {'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, - 'vendorC': {'sendRegardlessOfConsentState': ['UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, + 'vendorB': { + 'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, + 'vendorC': { + 'sendRegardlessOfConsentState': ['UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, }; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.urls = []; @@ -685,10 +858,14 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should clear just invalid vendor callouts', () => { rtc.rtcConfig_.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, - {'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], - 'url': 'https://www.other-rtc.com/example2'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, + { + 'sendRegardlessOfConsentState': ['INSUFFICIENT', 'UNKNOWN'], + 'url': 'https://www.other-rtc.com/example2', + }, ]; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.vendors = {}; @@ -699,25 +876,35 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should not clear callouts if per-callout setting valid', () => { rtc.rtcConfig_.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, - 'vendorB': {'sendRegardlessOfConsentState': ['UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, + 'vendorB': { + 'sendRegardlessOfConsentState': ['UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, 'vendorC': {'SLOT_ID': '1'}, }; rtc.rtcConfig_.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, 'https://www.other-rtc.com/example2', ]; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, }; expectedRtcConfig.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, ]; rtc.consentState_ = CONSENT_POLICY_STATE.INSUFFICIENT; rtc.modifyRtcConfigForConsentStateSettings(); @@ -726,28 +913,39 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { it('should handle mix of global and individual consent settings', () => { rtc.rtcConfig_.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, - 'vendorB': {'sendRegardlessOfConsentState': ['UNKNOWN'], - 'macros': {'SLOT_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, + 'vendorB': { + 'sendRegardlessOfConsentState': ['UNKNOWN'], + 'macros': {'SLOT_ID': '1'}, + }, 'vendorC': {'SLOT_ID': '1'}, }; rtc.rtcConfig_.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, 'https://www.other-rtc.com/example2', ]; rtc.rtcConfig_.sendRegardlessOfConsentState = ['INSUFFICIENT']; const expectedRtcConfig = Object.assign({}, rtc.rtcConfig_); expectedRtcConfig.vendors = { - 'vendorA': {'sendRegardlessOfConsentState': true, - 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}}, + 'vendorA': { + 'sendRegardlessOfConsentState': true, + 'macros': {'SLOT_ID': '1', 'PAGE_ID': '1'}, + }, 'vendorC': {'SLOT_ID': '1'}, }; expectedRtcConfig.urls = [ - {'sendRegardlessOfConsentState': true, - 'url': 'https://www.rtc.com/example1'}, - 'https://www.other-rtc.com/example2']; + { + 'sendRegardlessOfConsentState': true, + 'url': 'https://www.rtc.com/example1', + }, + 'https://www.other-rtc.com/example2', + ]; rtc.consentState_ = CONSENT_POLICY_STATE.INSUFFICIENT; rtc.modifyRtcConfigForConsentStateSettings(); expect(rtc.rtcConfig_).to.deep.equal(expectedRtcConfig); @@ -769,7 +967,6 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { rtc.modifyRtcConfigForConsentStateSettings(); expect(rtc.rtcConfig_).to.deep.equal(expectedRtcConfig); }); - }); describe('sendErrorMessage', () => { @@ -793,8 +990,9 @@ describes.realWin('real-time-config-manager', {amp: true}, env => { HREF: env.win.location.href, }; - requestUrl = Services.urlReplacementsForDoc(a4aElement.element) - .expandUrlSync(errorReportingUrl, macros, whitelist); + requestUrl = Services.urlReplacementsForDoc( + a4aElement.element + ).expandUrlSync(errorReportingUrl, macros, whitelist); }); it('should send error message pingback to correct url', () => { diff --git a/extensions/amp-a4a/0.1/test/test-refresh.js b/extensions/amp-a4a/0.1/test/test-refresh.js index 80cf14ea1b397..0cd21cd91664b 100644 --- a/extensions/amp-a4a/0.1/test/test-refresh.js +++ b/extensions/amp-a4a/0.1/test/test-refresh.js @@ -21,13 +21,10 @@ import { RefreshManager, getPublisherSpecifiedRefreshInterval, } from '../refresh-manager'; -import { - RefreshIntersectionObserverWrapper, -} from '../refresh-intersection-observer-wrapper'; +import {RefreshIntersectionObserverWrapper} from '../refresh-intersection-observer-wrapper'; import {Services} from '../../../../src/services'; describe('refresh', () => { - let mockA4a; let sandbox; const config = { @@ -45,7 +42,6 @@ describe('refresh', () => { return div; } - beforeEach(() => { sandbox = sinon.sandbox; mockA4a = { @@ -60,16 +56,25 @@ describe('refresh', () => { }); describe('refresh-manager', () => { - it('should get null refreshInterval', () => { mockA4a.element.removeAttribute(DATA_ATTR_NAME); - expect(getPublisherSpecifiedRefreshInterval( - mockA4a.element, window, 'doubleclick')).to.be.null; + expect( + getPublisherSpecifiedRefreshInterval( + mockA4a.element, + window, + 'doubleclick' + ) + ).to.be.null; }); it('should get refreshInterval from slot', () => { - expect(getPublisherSpecifiedRefreshInterval( - mockA4a.element, window, 'doubleclick')).to.equal(35000); + expect( + getPublisherSpecifiedRefreshInterval( + mockA4a.element, + window, + 'doubleclick' + ) + ).to.equal(35000); }); it('should get refreshInterval from meta tag', () => { @@ -78,17 +83,28 @@ describe('refresh', () => { meta.setAttribute('name', METATAG_NAME); meta.setAttribute('content', 'doubleclick=40'); window.document.head.appendChild(meta); - expect(getPublisherSpecifiedRefreshInterval( - mockA4a.element, window, 'doubleclick')).to.equal(40000); + expect( + getPublisherSpecifiedRefreshInterval( + mockA4a.element, + window, + 'doubleclick' + ) + ).to.equal(40000); }); it('should call convertConfiguration_ and set proper units', () => { const getConfigurationSpy = sandbox.spy( - RefreshManager.prototype, 'convertAndSanitizeConfiguration_'); - const refreshManager = new RefreshManager(mockA4a, { - visiblePercentageMin: 50, - continuousTimeMin: 1, - }, 30000); + RefreshManager.prototype, + 'convertAndSanitizeConfiguration_' + ); + const refreshManager = new RefreshManager( + mockA4a, + { + visiblePercentageMin: 50, + continuousTimeMin: 1, + }, + 30000 + ); expect(getConfigurationSpy).to.be.calledOnce; expect(refreshManager.config_).to.not.be.null; expect(refreshManager.config_.visiblePercentageMin).to.equal(0.5); @@ -98,12 +114,13 @@ describe('refresh', () => { describe('#ioCallback_', () => { let refreshManager; beforeEach( - () => refreshManager = new RefreshManager(mockA4a, config, 30000)); + () => (refreshManager = new RefreshManager(mockA4a, config, 30000)) + ); it('should stay in INITIAL state', () => { const ioEntry = { target: { - getAttribute: name => name == DATA_MANAGER_ID_NAME ? '0' : null, + getAttribute: name => (name == DATA_MANAGER_ID_NAME ? '0' : null), }, intersectionRatio: refreshManager.config_.visiblePercentageMin, }; @@ -158,15 +175,16 @@ describe('refresh', () => { }; refreshManager.refreshInterval_ = 0; refreshManager.initiateRefreshCycle(); - return Services.timerFor(window).promise(500).then(() => { - expect(refreshSpy).to.be.calledOnce; - window.document.body.removeChild(mockA4a.element); - }); + return Services.timerFor(window) + .promise(500) + .then(() => { + expect(refreshSpy).to.be.calledOnce; + window.document.body.removeChild(mockA4a.element); + }); }); }); describe('RefreshIntersectionObserverWrapper', () => { - let callback; let callbackPromise; let getRect; @@ -191,9 +209,13 @@ describe('refresh', () => { }); sandbox.stub(Services, 'ampdoc').callsFake(() => { return { - getRootNode: () => {return window.document;}, + getRootNode: () => { + return window.document; + }, win: window, - isSingleDoc: () => {return true;}, + isSingleDoc: () => { + return true; + }, }; }); @@ -209,7 +231,10 @@ describe('refresh', () => { }); callback = entries => resolver(entries); observerWrapper = new RefreshIntersectionObserverWrapper( - callback, mockA4a, {threshold: 0.5}); + callback, + mockA4a, + {threshold: 0.5} + ); }); it('should invoke callback with intersection ratio 1', () => { @@ -257,9 +282,11 @@ describe('refresh', () => { }), }; observerWrapper.observe(mockA4a.element); - return Services.timerFor(window).promise(500).then(() => { - expect(callbackSpy).to.not.be.called; - }); + return Services.timerFor(window) + .promise(500) + .then(() => { + expect(callbackSpy).to.not.be.called; + }); }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-signature-verifier.js b/extensions/amp-a4a/0.1/test/test-signature-verifier.js index 19b44cb64f5a6..82961149abbe5 100644 --- a/extensions/amp-a4a/0.1/test/test-signature-verifier.js +++ b/extensions/amp-a4a/0.1/test/test-signature-verifier.js @@ -16,7 +16,6 @@ // TODO(@jridgewell, #11081): fix linter to allow fixing weird indentation - import {SignatureVerifier, VerificationStatus} from '../signature-verifier'; import {base64EncodeFromBytes} from '../../../../src/utils/base64'; import {dev, user} from '../../../../src/log'; @@ -25,7 +24,6 @@ import {utf8Encode} from '../../../../src/utils/bytes'; const networkFailure = {throws: new TypeError('Failed to fetch')}; describes.fakeWin('SignatureVerifier', {amp: true}, env => { - let mockDevError; beforeEach(() => { mockDevError = env.sandbox.mock(dev()).expects('error'); @@ -37,8 +35,9 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { /** @param {string} signingServiceName */ const expectSigningServiceError = signingServiceName => { - mockDevError.withExactArgs('AMP-A4A', sinon.match(signingServiceName)) - .once(); + mockDevError + .withExactArgs('AMP-A4A', sinon.match(signingServiceName)) + .once(); }; const creative1 = utf8Encode('Hello world!'); @@ -51,12 +50,16 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { const verifier = new SignatureVerifier(env.win); verifier.loadKeyset('service-1'); return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', creative1, new Uint8Array(256)) - .then(status => { - expect(status).to.equal(VerificationStatus.CRYPTO_UNAVAILABLE); - expect(env.fetchMock.called()).to.be.false; - }); + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + creative1, + new Uint8Array(256) + ) + .then(status => { + expect(status).to.equal(VerificationStatus.CRYPTO_UNAVAILABLE); + expect(env.fetchMock.called()).to.be.false; + }); }); const crypto = window.crypto || window.msCrypto; @@ -76,13 +79,15 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { this.kid = kid; /** @const {!Promise<{privateKey: !webCrypto.CryptoKey, publicKey: !webCrypto.CryptoKey}>} */ this.keysPromise = subtle.generateKey( - { - name: 'RSASSA-PKCS1-v1_5', - modulusLength: 2048, - publicExponent: Uint8Array.of(1, 0, 1), - hash: {name: 'SHA-256'}, - }, - true, ['sign', 'verify']); + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: Uint8Array.of(1, 0, 1), + hash: {name: 'SHA-256'}, + }, + true, + ['sign', 'verify'] + ); } /** @@ -91,20 +96,24 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { */ sign(data) { return this.keysPromise - .then(({privateKey}) => subtle.sign( - {name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'}}, - privateKey, data)) - .then(signature => new Uint8Array(signature)); + .then(({privateKey}) => + subtle.sign( + {name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'}}, + privateKey, + data + ) + ) + .then(signature => new Uint8Array(signature)); } /** @return {!Promise} */ jwk() { return this.keysPromise - .then(({publicKey}) => subtle.exportKey('jwk', publicKey)) - .then(jwk => { - jwk['kid'] = this.kid; - return jwk; - }); + .then(({publicKey}) => subtle.exportKey('jwk', publicKey)) + .then(jwk => { + jwk['kid'] = this.kid; + return jwk; + }); } }; @@ -115,8 +124,10 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { * @return {!Promise} */ const jwkSet = keys => - Promise.all(keys.map(key => key.jwk())) - .then(jwks => ({body: {'keys': jwks}, headers})); + Promise.all(keys.map(key => key.jwk())).then(jwks => ({ + body: {'keys': jwks}, + headers, + })); const key1 = new Keypair('key-1'); const key2 = new Keypair('key-2'); @@ -134,322 +145,456 @@ describes.fakeWin('SignatureVerifier', {amp: true}, env => { expect(env.fetchMock.done()).to.be.true; }); - it('should verify a signature', - () => key1.sign(creative1).then(signature => { + it('should verify a signature', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.OK); + })); + + it('should verify multiple signatures with only one network request', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.OK); - })); - - it('should verify multiple signatures with only one network request', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.OK); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.OK); - }); - }))); - - it('should verify signatures from multiple signing services', - () => key1.sign(creative1).then( - signature1 => key2.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.OK); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.OK); - env.fetchMock.getOnce( - 'https://signingservice2.net/keyset.json', - jwkSet([key2])); - verifier.loadKeyset('service-2'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-2', 'key-2', signature2, creative2)) - .to.eventually.equal(VerificationStatus.OK); - }); - }))); - - it('should verify signatures when different signing services share a kid', - () => key1.sign(creative1).then(signature1 => { - const key1FromService2 = new Keypair('key-1'); - return key1FromService2.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.OK); - env.fetchMock.getOnce( - 'https://signingservice2.net/keyset.json', - jwkSet([key1FromService2])); - verifier.loadKeyset('service-2'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-2', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.OK); - }); - }); - })); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.OK); + }); + }) + )); - it('should verify a signature from a newly added key', - () => key2.sign(creative1).then(signature => { + it('should verify signatures from multiple signing services', () => + key1.sign(creative1).then(signature1 => + key2.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', () => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json?kid=key-2', - jwkSet([key2])); - return jwkSet([key1]); - }); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-2', signature, creative1)) - .to.eventually.equal(VerificationStatus.OK); - })); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.OK); + env.fetchMock.getOnce( + 'https://signingservice2.net/keyset.json', + jwkSet([key2]) + ); + verifier.loadKeyset('service-2'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-2', + 'key-2', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.OK); + }); + }) + )); - it('should return ERROR_KEY_NOT_FOUND for a nonexistent kid', - () => key1.sign(creative1).then(signature => { + it('should verify signatures when different signing services share a kid', () => + key1.sign(creative1).then(signature1 => { + const key1FromService2 = new Keypair('key-1'); + return key1FromService2.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', () => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json?kid=key-1', - jwkSet([])); - return jwkSet([]); - }); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); - })); - - it('should not make more network requests retrying a nonexistent kid', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.OK); env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', () => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json?kid=key-1', - jwkSet([])); - return jwkSet([]); - }); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal( - VerificationStatus.ERROR_KEY_NOT_FOUND); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal( - VerificationStatus.ERROR_KEY_NOT_FOUND); - }); - }))); - - it('should return ERROR_SIGNATURE_MISMATCH for a wrong signature', - () => key2.sign(creative1).then(signature => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); - })); + 'https://signingservice2.net/keyset.json', + jwkSet([key1FromService2]) + ); + verifier.loadKeyset('service-2'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-2', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.OK); + }); + }); + })); - it('should return UNVERIFIED and report on Web Cryptography error', - () => key1.sign(creative1).then(signature => { + it('should verify a signature from a newly added key', () => + key2.sign(creative1).then(signature => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', () => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - const errorMessage = 'Web Cryptography failed'; - env.stubService('crypto', 'verifyPkcs') - .returns(Promise.reject(new Error(errorMessage))); - mockDevError.withExactArgs('AMP-A4A', sinon.match(errorMessage)) - .once(); + 'https://signingservice1.net/keyset.json?kid=key-2', + jwkSet([key2]) + ); + return jwkSet([key1]); + }); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-2', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.OK); + })); + + it('should return ERROR_KEY_NOT_FOUND for a nonexistent kid', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', () => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json?kid=key-1', + jwkSet([]) + ); + return jwkSet([]); + }); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); + })); + + it('should not make more network requests retrying a nonexistent kid', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + () => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json?kid=key-1', + jwkSet([]) + ); + return jwkSet([]); + } + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - })); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.ERROR_KEY_NOT_FOUND); + }); + }) + )); - it('should return UNVERIFIED on network connectivity error', - () => key1.sign(creative1).then(signature => { + it('should return ERROR_SIGNATURE_MISMATCH for a wrong signature', () => + key2.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); + })); + + it('should return UNVERIFIED and report on Web Cryptography error', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + const errorMessage = 'Web Cryptography failed'; + env + .stubService('crypto', 'verifyPkcs') + .returns(Promise.reject(new Error(errorMessage))); + mockDevError.withExactArgs('AMP-A4A', sinon.match(errorMessage)).once(); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + })); + + it('should return UNVERIFIED on network connectivity error', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + networkFailure + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + })); + + it('should not retry for same service on network connectivity error', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', networkFailure); + 'https://signingservice1.net/keyset.json', + networkFailure + ); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - })); - - it('should not retry for same service on network connectivity error', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', networkFailure); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); - - it('should return UNVERIFIED, report, and not retry on malformed JSON', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - {body: '{"keys":', headers}); - expectSigningServiceError('service-1'); - verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); - - it('should return UNVERIFIED, report, and not retry on non-JWK Set JSON', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - {body: '{"foo":"bar"}', headers}); - expectSigningServiceError('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); + + it('should return UNVERIFIED, report, and not retry on malformed JSON', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', { + body: '{"keys":', + headers, + }); + expectSigningServiceError('service-1'); + verifier.loadKeyset('service-1'); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); - - it('should report on extraneous malformed data', - () => key1.sign(creative1).then(signature => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - jwkSet([key1]).then(keyset => { - keyset.body['keys'].push({'foo': 'bar'}); - return keyset; - })); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); + + it('should return UNVERIFIED, report, and not retry on non-JWK Set JSON', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', { + body: '{"foo":"bar"}', + headers, + }); expectSigningServiceError('service-1'); verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature, creative1)) - .to.eventually.equal(VerificationStatus.OK); - })); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); - it('should return UNVERIFIED, report, and not retry on malformed key', - () => key1.sign(creative1).then( - signature1 => key1.sign(creative2).then(signature2 => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', - {body: {'keys': [{'kid': 'key-1', 'foo': 'bar'}]}, headers}); - expectSigningServiceError('service-1'); + it('should report on extraneous malformed data', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]).then(keyset => { + keyset.body['keys'].push({'foo': 'bar'}); + return keyset; + }) + ); + expectSigningServiceError('service-1'); + verifier.loadKeyset('service-1'); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature, + creative1 + ) + ).to.eventually.equal(VerificationStatus.OK); + })); + + it('should return UNVERIFIED, report, and not retry on malformed key', () => + key1.sign(creative1).then(signature1 => + key1.sign(creative2).then(signature2 => { + env.fetchMock.getOnce('https://signingservice1.net/keyset.json', { + body: {'keys': [{'kid': 'key-1', 'foo': 'bar'}]}, + headers, + }); + expectSigningServiceError('service-1'); + verifier.loadKeyset('service-1'); + return verifier + .verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature1, + creative1 + ) + .then(status => { + expect(status).to.equal(VerificationStatus.UNVERIFIED); verifier.loadKeyset('service-1'); - return verifier - .verifyCreativeAndSignature( - 'service-1', 'key-1', signature1, creative1) - .then(status => { - expect(status).to.equal(VerificationStatus.UNVERIFIED); - verifier.loadKeyset('service-1'); - return expect( - verifier.verifyCreativeAndSignature( - 'service-1', 'key-1', signature2, creative2)) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); - }))); + return expect( + verifier.verifyCreativeAndSignature( + 'service-1', + 'key-1', + signature2, + creative2 + ) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); + }) + )); describe('#verify', () => { - it('should verify a signature header', - () => key1.sign(creative1).then(signature => { - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return expect( - verifier.verify( - creative1, - new Headers({ - 'AMP-Fast-Fetch-Signature': - `service-1:key-1:${base64EncodeFromBytes(signature)}`, - }))) - .to.eventually.equal(VerificationStatus.OK); - })); + it('should verify a signature header', () => + key1.sign(creative1).then(signature => { + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verify( + creative1, + new Headers({ + 'AMP-Fast-Fetch-Signature': `service-1:key-1:${base64EncodeFromBytes( + signature + )}`, + }) + ) + ).to.eventually.equal(VerificationStatus.OK); + })); it('should return UNVERIFIED on no header', () => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); verifier.loadKeyset('service-1'); - return expect(verifier.verify(creative1, new Headers())) - .to.eventually.equal(VerificationStatus.UNVERIFIED); + return expect( + verifier.verify(creative1, new Headers()) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); }); - it('should return UNVERIFIED on no header when crypto unavailable', - () => { - env.sandbox.stub(env.win, 'crypto').callsFake(undefined); - env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); - verifier.loadKeyset('service-1'); - return expect(verifier.verify(creative1, new Headers())) - .to.eventually.equal(VerificationStatus.UNVERIFIED); - }); + it('should return UNVERIFIED on no header when crypto unavailable', () => { + env.sandbox.stub(env.win, 'crypto').callsFake(undefined); + env.fetchMock.getOnce( + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); + verifier.loadKeyset('service-1'); + return expect( + verifier.verify(creative1, new Headers()) + ).to.eventually.equal(VerificationStatus.UNVERIFIED); + }); it('should return ERROR_SIGNATURE_MISMATCH on malformed header', () => { env.fetchMock.getOnce( - 'https://signingservice1.net/keyset.json', jwkSet([key1])); + 'https://signingservice1.net/keyset.json', + jwkSet([key1]) + ); env.sandbox.stub(user(), 'error'); verifier.loadKeyset('service-1'); return expect( - verifier.verify( - creative1, - new Headers({ - 'AMP-Fast-Fetch-Signature': 'service-1:key-1:Invalid base64!', - }))) - .to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); + verifier.verify( + creative1, + new Headers({ + 'AMP-Fast-Fetch-Signature': 'service-1:key-1:Invalid base64!', + }) + ) + ).to.eventually.equal(VerificationStatus.ERROR_SIGNATURE_MISMATCH); }); }); }); diff --git a/extensions/amp-a4a/0.1/test/test-template-renderer.js b/extensions/amp-a4a/0.1/test/test-template-renderer.js index 619d81034651d..aaf99812b350a 100644 --- a/extensions/amp-a4a/0.1/test/test-template-renderer.js +++ b/extensions/amp-a4a/0.1/test/test-template-renderer.js @@ -32,7 +32,6 @@ const realWinConfig = { }; describes.realWin('TemplateRenderer', realWinConfig, env => { - const templateUrl = 'https://adnetwork.com/amp-template.html'; const headers = { get: name => { @@ -61,10 +60,16 @@ describes.realWin('TemplateRenderer', realWinConfig, env => { }); containerElement.renderStarted = () => {}; containerElement.getPageLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.getLayoutBox = () => ({ - left: 0, top: 0, width: 0, height: 0, + left: 0, + top: 0, + width: 0, + height: 0, }); containerElement.getIntersectionChangeEntry = () => ({}); containerElement.isInViewport = () => true; @@ -84,12 +89,17 @@ describes.realWin('TemplateRenderer', realWinConfig, env => { return Promise.resolve(data.adTemplate); }); - validatorPromise = validator.validate(context, - utf8Encode(JSON.stringify({ + validatorPromise = validator.validate( + context, + utf8Encode( + JSON.stringify({ templateUrl, data: {url: 'https://buy.com/buy-1'}, analytics: {foo: 'bar'}, - })), headers); + }) + ), + headers + ); }); afterEach(() => { @@ -100,86 +110,89 @@ describes.realWin('TemplateRenderer', realWinConfig, env => { it('should append iframe child with correct template values', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - const iframe = containerElement.querySelector('iframe'); - expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.innerHTML.trim()).to - .equal('
    \n

    ipsum lorem

    \n Click for ad!' + - '\n
    '); - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + const iframe = containerElement.querySelector('iframe'); + expect(iframe).to.be.ok; + expect(iframe.contentWindow.document.body.innerHTML.trim()).to.equal( + '
    \n

    ipsum lorem

    \n Click for ad!' + + '\n
    ' + ); + }); }); }); it('should set correct attributes on the iframe', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - const iframe = containerElement.querySelector('iframe'); - expect(iframe).to.be.ok; - expect(iframe.getAttribute('width')).to.equal('320'); - expect(iframe.getAttribute('height')).to.equal('50'); - expect(iframe.getAttribute('frameborder')).to.equal('0'); - expect(iframe.getAttribute('allowfullscreen')).to.equal(''); - expect(iframe.getAttribute('allowtransparency')).to.equal(''); - expect(iframe.getAttribute('scrolling')).to.equal('no'); - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + const iframe = containerElement.querySelector('iframe'); + expect(iframe).to.be.ok; + expect(iframe.getAttribute('width')).to.equal('320'); + expect(iframe.getAttribute('height')).to.equal('50'); + expect(iframe.getAttribute('frameborder')).to.equal('0'); + expect(iframe.getAttribute('allowfullscreen')).to.equal(''); + expect(iframe.getAttribute('allowtransparency')).to.equal(''); + expect(iframe.getAttribute('scrolling')).to.equal('no'); + }); }); }); it('should style body of iframe document to be visible', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - const iframe = containerElement.querySelector('iframe'); - expect(iframe).to.be.ok; - expect(iframe.contentWindow.document.body.style.visibility) - .to.equal('visible'); - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + const iframe = containerElement.querySelector('iframe'); + expect(iframe).to.be.ok; + expect(iframe.contentWindow.document.body.style.visibility).to.equal( + 'visible' + ); + }); }); }); it('should insert analytics', () => { env.win.AMP.registerTemplate('amp-mustache', AmpMustache); const insertAnalyticsSpy = sandbox.spy( - getAmpAdTemplateHelper(env.win), 'insertAnalytics'); + getAmpAdTemplateHelper(env.win), + 'insertAnalytics' + ); return validatorPromise.then(validatorOutput => { - // Sanity check. This behavior is tested in test-template-validator.js. expect(validatorOutput).to.be.ok; expect(validatorOutput.type).to.equal(ValidatorResult.AMP); const {creativeData} = validatorOutput; expect(creativeData).to.be.ok; - return renderer.render(context, containerElement, creativeData) - .then(() => { - expect(insertAnalyticsSpy).to.be.calledOnce; - }); + return renderer + .render(context, containerElement, creativeData) + .then(() => { + expect(insertAnalyticsSpy).to.be.calledOnce; + }); }); }); }); - diff --git a/extensions/amp-a4a/0.1/test/test-template-validator.js b/extensions/amp-a4a/0.1/test/test-template-validator.js index 066cc80a515a3..d38aba0d94b20 100644 --- a/extensions/amp-a4a/0.1/test/test-template-validator.js +++ b/extensions/amp-a4a/0.1/test/test-template-validator.js @@ -31,7 +31,6 @@ const realWinConfig = { }; describes.realWin('TemplateValidator', realWinConfig, env => { - const templateUrl = 'https://adnetwork.com/amp-template.html'; const headers = { get: name => { @@ -47,7 +46,6 @@ describes.realWin('TemplateValidator', realWinConfig, env => { }); describe('AMP Result', () => { - let sandbox; let validatorPromise; @@ -58,12 +56,17 @@ describes.realWin('TemplateValidator', realWinConfig, env => { return Promise.resolve(data.adTemplate); }); - validatorPromise = validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ + validatorPromise = validator.validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ templateUrl, data: {url: 'https://buy.com/buy-1'}, analytics: {foo: 'bar'}, - })), headers); + }) + ), + headers + ); }); afterEach(() => sandbox.restore()); @@ -76,28 +79,36 @@ describes.realWin('TemplateValidator', realWinConfig, env => { }); it('should have AMP validator result w/ deprecated header name', () => { - validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - })), { + validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ), + { get: name => { if (name == DEPRECATED_AMP_TEMPLATED_CREATIVE_HEADER_NAME) { return 'amp-mustache'; } }, - }).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.AMP); - }); + } + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.AMP); + }); }); it('should have TEMPLATE ad response type', () => { return validatorPromise.then(validatorOutput => { expect(validatorOutput).to.be.ok; expect(validatorOutput.adResponseType).to.equal( - AdResponseType.TEMPLATE); + AdResponseType.TEMPLATE + ); }); }); @@ -106,79 +117,99 @@ describes.realWin('TemplateValidator', realWinConfig, env => { expect(validatorOutput).to.be.ok; expect(validatorOutput.creativeData).to.be.ok; const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.minifiedCreative) - .to.equal(data.minifiedTemplateCreative); + expect(creativeMetadata.minifiedCreative).to.equal( + data.minifiedTemplateCreative + ); }); }); - it('should have amp-analytics and mustache in customElementExtensions', - () => { - return validatorPromise.then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.creativeData).to.be.ok; - const {creativeMetadata} = validatorOutput.creativeData; - expect(creativeMetadata.customElementExtensions) - .to.deep.equal(['amp-analytics', 'amp-mustache']); - }); - }); + it('should have amp-analytics and mustache in customElementExtensions', () => { + return validatorPromise.then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.creativeData).to.be.ok; + const {creativeMetadata} = validatorOutput.creativeData; + expect(creativeMetadata.customElementExtensions).to.deep.equal([ + 'amp-analytics', + 'amp-mustache', + ]); + }); + }); }); describe('Non-AMP Result', () => { it('should have NON_AMP validator result due to lack of headers', () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - }))).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - }); + return validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ) + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); + }); }); - it('should have NON_AMP validator result due to lack of mustache header', - () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - })), - { - get: () => null, - }).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); - }); + it('should have NON_AMP validator result due to lack of mustache header', () => { + return validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ), + { + get: () => null, + } + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.type).to.equal(ValidatorResult.NON_AMP); }); + }); it('should have TEMPLATE ad response type', () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({ - templateUrl, - data: {url: 'https://buy.com/buy-1'}, - analytics: {foo: 'bar'}, - }))).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.adResponseType) - .to.equal(AdResponseType.TEMPLATE); - }); + return validator + .validate( + {win: env.win}, + utf8Encode( + JSON.stringify({ + templateUrl, + data: {url: 'https://buy.com/buy-1'}, + analytics: {foo: 'bar'}, + }) + ) + ) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.adResponseType).to.equal( + AdResponseType.TEMPLATE + ); + }); }); it('should have the response body as the creative in creativeData', () => { - return validator.validate({win: env.win}, - utf8Encode(JSON.stringify({templateUrl})), - { - get: () => null, - }).then(validatorOutput => { - expect(validatorOutput).to.be.ok; - expect(validatorOutput.creativeData).to.be.ok; - const {creativeData} = validatorOutput; - expect(creativeData).to.be.ok; - expect(creativeData.creative).to.deep.equal( - '{"templateUrl":"https://adnetwork.com/amp-template.html"}'); - }); + return validator + .validate({win: env.win}, utf8Encode(JSON.stringify({templateUrl})), { + get: () => null, + }) + .then(validatorOutput => { + expect(validatorOutput).to.be.ok; + expect(validatorOutput.creativeData).to.be.ok; + const {creativeData} = validatorOutput; + expect(creativeData).to.be.ok; + expect(creativeData.creative).to.deep.equal( + '{"templateUrl":"https://adnetwork.com/amp-template.html"}' + ); + }); }); }); }); - diff --git a/extensions/amp-a4a/0.1/test/utils.js b/extensions/amp-a4a/0.1/test/utils.js index 0c8e61d80d17d..0060b8842df6d 100644 --- a/extensions/amp-a4a/0.1/test/utils.js +++ b/extensions/amp-a4a/0.1/test/utils.js @@ -18,8 +18,10 @@ import {AmpA4A} from '../amp-a4a'; import {dict} from '../../../../src/utils/object'; /** @type {string} @private */ -export const TEST_URL = 'http://iframe.localhost:' + location.port + - '/test/fixtures/served/iframe.html?args'; +export const TEST_URL = + 'http://iframe.localhost:' + + location.port + + '/test/fixtures/served/iframe.html?args'; export class MockA4AImpl extends AmpA4A { getAdUrl() { diff --git a/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js b/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js index 40f42e744370c..3944b8dd4f785 100644 --- a/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.1/amp-access-laterpay.js @@ -19,18 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-laterpay', '0.1', function(AMP) { AMP.registerServiceForDoc( - 'laterpay', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('laterpay'); - const vendor = new LaterpayVendor(accessService, source); - const adapter = /** @type { + 'laterpay', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('laterpay'); + const vendor = new LaterpayVendor(accessService, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); + adapter.registerVendor(vendor); + return vendor; }); + } + ); }); diff --git a/extensions/amp-access-laterpay/0.1/laterpay-impl.js b/extensions/amp-access-laterpay/0.1/laterpay-impl.js index 5811007f43137..2af62e28d22b0 100644 --- a/extensions/amp-access-laterpay/0.1/laterpay-impl.js +++ b/extensions/amp-access-laterpay/0.1/laterpay-impl.js @@ -38,10 +38,11 @@ const CONFIG_URLS = { const DEFAULT_REGION = 'eu'; -const CONFIG_BASE_PATH = '/api/v1/public/amp?' + - 'article_url=CANONICAL_URL' + - '&_reader_id=READER_ID' + - '&return_url=RETURN_URL'; +const CONFIG_BASE_PATH = + '/api/v1/public/amp?' + + 'article_url=CANONICAL_URL' + + '&_reader_id=READER_ID' + + '&return_url=RETURN_URL'; const AUTHORIZATION_TIMEOUT = 3000; const DEFAULT_MESSAGES = { @@ -91,12 +92,10 @@ let PurchaseOptionDef; */ let PurchaseConfigDef; - /** * @implements {../../amp-access/0.1/access-vendor.AccessVendor} */ export class LaterpayVendor { - /** * @param {!../../amp-access/0.1/amp-access.AccessService} accessService * @param {!../../amp-access/0.1/amp-access-source.AccessSource} accessSource @@ -142,8 +141,11 @@ export class LaterpayVendor { this.currentLocale_ = this.laterpayConfig_['locale'] || 'en'; /** @private {!JsonObject} */ - this.i18n_ = /** @type {!JsonObject} */ (Object.assign(dict(), - DEFAULT_MESSAGES, this.laterpayConfig_['localeMessages'] || dict())); + this.i18n_ = /** @type {!JsonObject} */ (Object.assign( + dict(), + DEFAULT_MESSAGES, + this.laterpayConfig_['localeMessages'] || dict() + )); /** @private {string} */ this.purchaseConfigBaseUrl_ = this.getConfigUrl_() + CONFIG_BASE_PATH; @@ -188,36 +190,41 @@ export class LaterpayVendor { * @return {!Promise} */ authorize() { - return this.getPurchaseConfig_() - .then(response => { - if (response.status === 204) { - throw user() - .createError('No merchant domains have been matched for this ' + - 'article, or no paid content configurations are setup.'); - } - - if (this.laterpayConfig_['scrollToTopAfterAuth']) { - this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); - } - this.emptyContainer_(); - return {access: response.access}; - }, err => { - if (!err || !err.response) { - throw err; - } - const {response} = err; - if (response.status !== 402) { - throw err; - } - return response.json().catch(() => undefined).then(responseJson => { + return this.getPurchaseConfig_().then( + response => { + if (response.status === 204) { + throw user().createError( + 'No merchant domains have been matched for this ' + + 'article, or no paid content configurations are setup.' + ); + } + + if (this.laterpayConfig_['scrollToTopAfterAuth']) { + this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); + } + this.emptyContainer_(); + return {access: response.access}; + }, + err => { + if (!err || !err.response) { + throw err; + } + const {response} = err; + if (response.status !== 402) { + throw err; + } + return response + .json() + .catch(() => undefined) + .then(responseJson => { this.purchaseConfig_ = responseJson; // empty before rendering, in case authorization is being called // again with the same state - this.emptyContainer_() - .then(this.renderPurchaseOverlay_.bind(this)); + this.emptyContainer_().then(this.renderPurchaseOverlay_.bind(this)); return {access: false}; }); - }); + } + ); } /** @@ -225,20 +232,29 @@ export class LaterpayVendor { * @private */ getPurchaseConfig_() { - const url = this.purchaseConfigBaseUrl_ + - '&article_title=' + encodeURIComponent(this.getArticleTitle_()); + const url = + this.purchaseConfigBaseUrl_ + + '&article_title=' + + encodeURIComponent(this.getArticleTitle_()); const urlPromise = this.accessSource_.buildUrl( - url, /* useAuthData */ false); - return urlPromise.then(url => { - return this.accessSource_.getLoginUrl(url); - }).then(url => { - dev().info(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( - AUTHORIZATION_TIMEOUT, - this.xhr_.fetchJson(url, { - credentials: 'include', - })).then(res => res.json()); - }); + url, + /* useAuthData */ false + ); + return urlPromise + .then(url => { + return this.accessSource_.getLoginUrl(url); + }) + .then(url => { + dev().info(TAG, 'Authorization URL: ', url); + return this.timer_ + .timeoutPromise( + AUTHORIZATION_TIMEOUT, + this.xhr_.fetchJson(url, { + credentials: 'include', + }) + ) + .then(res => res.json()); + }); } /** @@ -255,11 +271,14 @@ export class LaterpayVendor { * @private */ getArticleTitle_() { - const title = this.ampdoc.getRootNode().querySelector( - this.laterpayConfig_['articleTitleSelector']); + const title = this.ampdoc + .getRootNode() + .querySelector(this.laterpayConfig_['articleTitleSelector']); userAssert( - title, 'No article title element found with selector %s', - this.laterpayConfig_['articleTitleSelector']); + title, + 'No article title element found with selector %s', + this.laterpayConfig_['articleTitleSelector'] + ); return title.textContent.trim(); } @@ -271,8 +290,8 @@ export class LaterpayVendor { const id = TAG + '-dialog'; const dialogContainer = this.ampdoc.getElementById(id); return user().assertElement( - dialogContainer, - 'No element found with id ' + id + dialogContainer, + 'No element found with id ' + id ); } @@ -316,12 +335,14 @@ export class LaterpayVendor { } this.renderTextBlock_('header'); const listContainer = this.createElement_('ul'); - this.purchaseConfig_['premiumcontent']['title'] = - this.i18n_['premiumContentTitle']; - this.purchaseConfig_['premiumcontent']['description'] = - this.getArticleTitle_(); + this.purchaseConfig_['premiumcontent']['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 => { listContainer.appendChild(this.createPurchaseOption_(timepass)); @@ -341,13 +362,15 @@ export class LaterpayVendor { 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_( - dev().assertElement(listContainer.firstElementChild)); + dev().assertElement(listContainer.firstElementChild) + ); } /** @@ -426,15 +449,14 @@ export class LaterpayVendor { radio.type = 'radio'; radio.id = option['title']; radio.value = option['purchase_url']; - const purchaseType = option['purchase_type'] === 'ppu' ? - 'payLater' : - 'payNow'; + const purchaseType = + option['purchase_type'] === 'ppu' ? 'payLater' : 'payNow'; const purchaseActionLabel = this.i18n_[purchaseType + 'Button']; radio.setAttribute('data-purchase-action-label', purchaseActionLabel); radio.setAttribute('data-purchase-type', purchaseType); - this.purchaseOptionListeners_.push(listen( - radio, 'change', this.handlePurchaseOptionSelection_.bind(this) - )); + this.purchaseOptionListeners_.push( + listen(radio, 'change', this.handlePurchaseOptionSelection_.bind(this)) + ); return radio; } @@ -465,7 +487,7 @@ export class LaterpayVendor { * @private */ formatPrice_(priceValue) { - const value = (priceValue / 100); + const value = priceValue / 100; const props = { style: 'decimal', minimumFractionDigits: 0, @@ -476,7 +498,7 @@ export class LaterpayVendor { /** * @param {string} href * @return {!Element} - */ + */ createAlreadyPurchasedLink_(href) { const p = this.createElement_('p'); p.className = TAG + '-already-purchased-link-container'; @@ -507,8 +529,10 @@ export class LaterpayVendor { const selectedOptionClassname = TAG + '-selected'; const prevPurchaseOption = this.selectedPurchaseOption_; const purchaseActionLabel = target.dataset['purchaseActionLabel']; - if (prevPurchaseOption && - prevPurchaseOption.classList.contains(selectedOptionClassname)) { + if ( + prevPurchaseOption && + prevPurchaseOption.classList.contains(selectedOptionClassname) + ) { prevPurchaseOption.classList.remove(selectedOptionClassname); } this.selectedPurchaseOption_ = target; @@ -525,7 +549,9 @@ export class LaterpayVendor { handlePurchase_(ev, purchaseUrl, purchaseType) { ev.preventDefault(); const urlPromise = this.accessSource_.buildUrl( - purchaseUrl, /* useAuthData */ false); + purchaseUrl, + /* useAuthData */ false + ); return urlPromise.then(url => { dev().fine(TAG, 'Authorization URL: ', url); this.accessSource_.loginWithUrl(url, purchaseType); diff --git a/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js b/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js index ceb2259139dee..a0df170c8547a 100644 --- a/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.1/test/test-amp-access-laterpay.js @@ -18,276 +18,290 @@ import {LaterpayVendor} from '../laterpay-impl'; const TAG = 'amp-access-laterpay'; -describes.fakeWin('LaterpayVendor', { - amp: true, - location: 'https://pub.com/doc1', -}, env => { - let win, document, ampdoc; - let accessSource; - let accessService; - let accessSourceMock; - let xhrMock; - let articleTitle; - let laterpayConfig; - let vendor; +describes.fakeWin( + 'LaterpayVendor', + { + amp: true, + location: 'https://pub.com/doc1', + }, + env => { + let win, document, ampdoc; + let accessSource; + let accessService; + let accessSourceMock; + let xhrMock; + let articleTitle; + let laterpayConfig; + let vendor; - beforeEach(() => { - win = env.win; - ampdoc = env.ampdoc; - document = win.document; - - laterpayConfig = { - articleTitleSelector: '#laterpay-test-title', - region: 'us', - }; + beforeEach(() => { + win = env.win; + ampdoc = env.ampdoc; + document = win.document; - accessSource = { - getAdapterConfig: () => { return laterpayConfig; }, - buildUrl: () => {}, - loginWithUrl: () => {}, - getLoginUrl: () => {}, - }; + laterpayConfig = { + articleTitleSelector: '#laterpay-test-title', + region: 'us', + }; - accessService = { - ampdoc, - getSource: () => accessSource, - }; + accessSource = { + getAdapterConfig: () => { + return laterpayConfig; + }, + buildUrl: () => {}, + loginWithUrl: () => {}, + getLoginUrl: () => {}, + }; - accessSourceMock = sandbox.mock(accessSource); + accessService = { + ampdoc, + getSource: () => accessSource, + }; - articleTitle = document.createElement('h1'); - articleTitle.id = 'laterpay-test-title'; - articleTitle.textContent = 'test title'; - document.body.appendChild(articleTitle); + accessSourceMock = sandbox.mock(accessSource); - vendor = new LaterpayVendor(accessService, accessSource); - xhrMock = sandbox.mock(vendor.xhr_); - }); + articleTitle = document.createElement('h1'); + articleTitle.id = 'laterpay-test-title'; + articleTitle.textContent = 'test title'; + document.body.appendChild(articleTitle); - afterEach(() => { - articleTitle.parentNode.removeChild(articleTitle); - accessSourceMock.verify(); - xhrMock.verify(); - }); + vendor = new LaterpayVendor(accessService, accessSource); + xhrMock = sandbox.mock(vendor.xhr_); + }); - describe('authorize', () => { - let emptyContainerStub; - beforeEach(() => { - emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); - sandbox.stub(vendor, 'renderPurchaseOverlay_'); + afterEach(() => { + articleTitle.parentNode.removeChild(articleTitle); + accessSourceMock.verify(); + xhrMock.verify(); }); - it('uses a non default region', () => { - const buildUrl = accessSourceMock.expects('buildUrl') + describe('authorize', () => { + let emptyContainerStub; + beforeEach(() => { + emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); + sandbox.stub(vendor, 'renderPurchaseOverlay_'); + }); + + it('uses a non default region', () => { + const buildUrl = accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(() => { - expect( + return vendor.authorize().then(() => { + expect( /connector\.uselaterpay\.com/g.test(buildUrl.firstCall.args[0]) - ).to.be.true; + ).to.be.true; + }); }); - }); - it('successful authorization', () => { - vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; - accessSourceMock.expects('buildUrl') - .withExactArgs('https://baseurl?param&article_title=test%20title', false) + it('successful authorization', () => { + vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; + accessSourceMock + .expects('buildUrl') + .withExactArgs( + 'https://baseurl?param&article_title=test%20title', + false + ) .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(resp => { - expect(resp.access).to.be.true; - expect(emptyContainerStub.called).to.be.true; + return vendor.authorize().then(resp => { + expect(resp.access).to.be.true; + expect(emptyContainerStub.called).to.be.true; + }); }); - }); - it('authorization fails due to lack of server config', () => { - accessSourceMock.expects('buildUrl') + it('authorization fails due to lack of server config', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) .returns(Promise.resolve({status: 204})) .once(); - return vendor.authorize().catch(err => { - expect(err.message).to.exist; + return vendor.authorize().catch(err => { + expect(err.message).to.exist; + }); }); - }); - it('authorization response from server fails', () => { - accessSourceMock.expects('buildUrl') + it('authorization response from server fails', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.reject({ - response: { - status: 402, - json() { - return Promise.resolve({access: false}); + .returns( + Promise.reject({ + response: { + status: 402, + json() { + return Promise.resolve({access: false}); + }, }, - }, - })) + }) + ) .once(); - emptyContainerStub.returns(Promise.resolve()); - return vendor.authorize().then(err => { - expect(err.access).to.be.false; + emptyContainerStub.returns(Promise.resolve()); + return vendor.authorize().then(err => { + expect(err.access).to.be.false; + }); }); }); - }); - - describe('create purchase overlay', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - premiumcontent: { - price: {}, - }, - subscriptions: [ - {price: {}}, - ], - timepasses: [ - {price: {}}, - ], - }; - vendor.renderPurchaseOverlay_(); - }); + describe('create purchase overlay', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + premiumcontent: { + price: {}, + }, + subscriptions: [{price: {}}], + timepasses: [{price: {}}], + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('renders list', () => { - expect(container.querySelector('ul')).to.not.be.null; - }); + it('renders list', () => { + expect(container.querySelector('ul')).to.not.be.null; + }); - it('renders 3 purchase options', () => { - expect(container.querySelector('ul').childNodes.length).to.equal(3); + it('renders 3 purchase options', () => { + expect(container.querySelector('ul').childNodes.length).to.equal(3); + }); }); - }); - - describe('purchase option selection', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - premiumcontent: { - price: {}, - }, - subscriptions: [ - {price: {}}, - ], - timepasses: [ - {price: {}}, - ], - }; - vendor.renderPurchaseOverlay_(); - const ev = new Event('change'); - container.querySelector('input').dispatchEvent(ev); - }); + describe('purchase option selection', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + premiumcontent: { + price: {}, + }, + subscriptions: [{price: {}}], + timepasses: [{price: {}}], + }; + vendor.renderPurchaseOverlay_(); + const ev = new Event('change'); + container.querySelector('input').dispatchEvent(ev); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('purchase option is selected', () => { - expect(vendor.selectedPurchaseOption_).to.not.be.null; - expect(vendor.selectedPurchaseOption_.classList - .contains(TAG + '-selected')).to.be.true; + it('purchase option is selected', () => { + expect(vendor.selectedPurchaseOption_).to.not.be.null; + expect( + vendor.selectedPurchaseOption_.classList.contains(TAG + '-selected') + ).to.be.true; + }); }); - }); - - describe('purchase', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - premiumcontent: { - price: {}, - }, - subscriptions: [ - {price: {}}, - ], - timepasses: [ - {price: {}}, - ], - apl: 'http://apllink', - }; - vendor.renderPurchaseOverlay_(); - }); + describe('purchase', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + premiumcontent: { + price: {}, + }, + subscriptions: [{price: {}}], + timepasses: [{price: {}}], + apl: 'http://apllink', + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('sends request for purchase', done => { - const changeEv = new Event('change'); - container.querySelector('input').dispatchEvent(changeEv); - accessSourceMock.expects('buildUrl') + it('sends request for purchase', done => { + const changeEv = new Event('change'); + container.querySelector('input').dispatchEvent(changeEv); + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container.querySelector('button').dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); - }); + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container.querySelector('button').dispatchEvent(clickEv); + setTimeout(() => { + done(); + }, 500); + }); - it('sends request for already purchased', done => { - accessSourceMock.expects('buildUrl') + it('sends request for already purchased', done => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://apllink')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container .querySelector('.' + TAG + '-already-purchased-link-container > a') .dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); + setTimeout(() => { + done(); + }, 500); + }); }); - - - }); -}); + } +); diff --git a/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js b/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js index eebc46238de54..caa806d8b7c5d 100644 --- a/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.2/amp-access-laterpay.js @@ -19,18 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-laterpay', '0.2', function(AMP) { AMP.registerServiceForDoc( - 'laterpay', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('laterpay'); - const vendor = new LaterpayVendor(accessService, source); - const adapter = /** @type { + 'laterpay', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('laterpay'); + const vendor = new LaterpayVendor(accessService, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); + adapter.registerVendor(vendor); + return vendor; }); + } + ); }); diff --git a/extensions/amp-access-laterpay/0.2/laterpay-impl.js b/extensions/amp-access-laterpay/0.2/laterpay-impl.js index e21038cc8b34b..642c66774194d 100644 --- a/extensions/amp-access-laterpay/0.2/laterpay-impl.js +++ b/extensions/amp-access-laterpay/0.2/laterpay-impl.js @@ -36,14 +36,16 @@ const CONFIG_URLS = { }, }; -const LATERPAY_BADGE_URL = 'https://blog.laterpay.net/laterpay-academy/what-is-laterpay'; +const LATERPAY_BADGE_URL = + 'https://blog.laterpay.net/laterpay-academy/what-is-laterpay'; const DEFAULT_REGION = 'eu'; -const CONFIG_BASE_PATH = '/api/v2/fetch/amp/?' + - 'article_url=CANONICAL_URL' + - '&_reader_id=READER_ID' + - '&return_url=RETURN_URL'; +const CONFIG_BASE_PATH = + '/api/v2/fetch/amp/?' + + 'article_url=CANONICAL_URL' + + '&_reader_id=READER_ID' + + '&return_url=RETURN_URL'; const AUTHORIZATION_TIMEOUT = 3000; const DEFAULT_MESSAGES = { @@ -116,12 +118,10 @@ let PurchaseConfigDef; */ let PurchaseOptionsDef; - /** * @implements {../../amp-access/0.1/access-vendor.AccessVendor} */ export class LaterpayVendor { - /** * @param {!../../amp-access/0.1/amp-access.AccessService} accessService * @param {!../../amp-access/0.1/amp-access-source.AccessSource} accessSource @@ -170,8 +170,11 @@ export class LaterpayVendor { this.currentLocale_ = this.laterpayConfig_['locale'] || 'en'; /** @private {!JsonObject} */ - this.i18n_ = /** @type {!JsonObject} */ (Object.assign(dict(), - DEFAULT_MESSAGES, this.laterpayConfig_['localeMessages'] || dict())); + this.i18n_ = /** @type {!JsonObject} */ (Object.assign( + dict(), + DEFAULT_MESSAGES, + this.laterpayConfig_['localeMessages'] || dict() + )); /** @private {string} */ this.purchaseConfigBaseUrl_ = this.getConfigUrl_() + CONFIG_BASE_PATH; @@ -182,11 +185,9 @@ export class LaterpayVendor { } const jwt = this.laterpayConfig_['jwt']; if (jwt) { - this.purchaseConfigBaseUrl_ += - '&jwt=' + encodeURIComponent(jwt); + this.purchaseConfigBaseUrl_ += '&jwt=' + encodeURIComponent(jwt); } - /** @const @private {!../../../src/service/timer-impl.Timer} */ this.timer_ = Services.timerFor(this.ampdoc.win); @@ -222,38 +223,44 @@ export class LaterpayVendor { * @return {!Promise} */ authorize() { - return this.getPurchaseConfig_() - .then(response => { - if (response.status === 204) { - throw user() - .createError('No merchant domains have been matched for this ' + - 'article, or no paid content configurations are setup.'); - } - - if (this.laterpayConfig_['scrollToTopAfterAuth']) { - this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); - } - this.emptyContainer_(); - return {access: response.access}; - }, err => { - if (!err || !err.response) { - throw err; - } - const {response} = err; - if (response.status !== 402) { - throw err; - } - return response.json().catch(() => undefined).then(responseJson => { + return this.getPurchaseConfig_().then( + response => { + if (response.status === 204) { + throw user().createError( + 'No merchant domains have been matched for this ' + + 'article, or no paid content configurations are setup.' + ); + } + + if (this.laterpayConfig_['scrollToTopAfterAuth']) { + this.vsync_.mutate(() => this.viewport_.setScrollTop(0)); + } + this.emptyContainer_(); + return {access: response.access}; + }, + err => { + if (!err || !err.response) { + throw err; + } + const {response} = err; + if (response.status !== 402) { + throw err; + } + return response + .json() + .catch(() => undefined) + .then(responseJson => { this.purchaseConfig_ = responseJson; this.purchaseOptions_ = this.parseConfigIntoOptions_( - responseJson.purchase_options); + responseJson.purchase_options + ); // empty before rendering, in case authorization is being called // again with the same state - this.emptyContainer_() - .then(this.renderPurchaseOverlay_.bind(this)); + this.emptyContainer_().then(this.renderPurchaseOverlay_.bind(this)); return {access: false}; }); - }); + } + ); } /** @@ -261,20 +268,29 @@ export class LaterpayVendor { * @private */ getPurchaseConfig_() { - const url = this.purchaseConfigBaseUrl_ + - '&article_title=' + encodeURIComponent(this.getArticleTitle_()); + const url = + this.purchaseConfigBaseUrl_ + + '&article_title=' + + encodeURIComponent(this.getArticleTitle_()); const urlPromise = this.accessSource_.buildUrl( - url, /* useAuthData */ false); - return urlPromise.then(url => { - return this.accessSource_.getLoginUrl(url); - }).then(url => { - dev().info(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( - AUTHORIZATION_TIMEOUT, - this.xhr_.fetchJson(url, { - credentials: 'include', - })).then(res => res.json()); - }); + url, + /* useAuthData */ false + ); + return urlPromise + .then(url => { + return this.accessSource_.getLoginUrl(url); + }) + .then(url => { + dev().info(TAG, 'Authorization URL: ', url); + return this.timer_ + .timeoutPromise( + AUTHORIZATION_TIMEOUT, + this.xhr_.fetchJson(url, { + credentials: 'include', + }) + ) + .then(res => res.json()); + }); } /** @@ -286,15 +302,16 @@ export class LaterpayVendor { const articleTitle = this.getArticleTitle_(); const purchaseOptions = {}; purchaseOptions['singlePurchases'] = purchaseOptionsList.filter( - option => option['sales_model'] === 'single_purchase' + option => option['sales_model'] === 'single_purchase' ); purchaseOptions['singlePurchases'].forEach( - option => option.description = articleTitle); + option => (option.description = articleTitle) + ); purchaseOptions['timepasses'] = purchaseOptionsList.filter( - option => option['sales_model'] === 'timepass' + option => option['sales_model'] === 'timepass' ); purchaseOptions['subscriptions'] = purchaseOptionsList.filter( - option => option['sales_model'] === 'subscription' + option => option['sales_model'] === 'subscription' ); return purchaseOptions; } @@ -313,11 +330,14 @@ export class LaterpayVendor { * @private */ getArticleTitle_() { - const title = this.ampdoc.getRootNode().querySelector( - this.laterpayConfig_['articleTitleSelector']); + const title = this.ampdoc + .getRootNode() + .querySelector(this.laterpayConfig_['articleTitleSelector']); userAssert( - title, 'No article title element found with selector %s', - this.laterpayConfig_['articleTitleSelector']); + title, + 'No article title element found with selector %s', + this.laterpayConfig_['articleTitleSelector'] + ); return title.textContent.trim(); } @@ -329,8 +349,8 @@ export class LaterpayVendor { const id = TAG + '-dialog'; const dialogContainer = this.ampdoc.getElementById(id); return user().assertElement( - dialogContainer, - 'No element found with id ' + id + dialogContainer, + 'No element found with id ' + id ); } @@ -395,13 +415,15 @@ export class LaterpayVendor { this.innerContainer_.appendChild(listContainer); this.innerContainer_.appendChild(purchaseButton); this.innerContainer_.appendChild( - this.createAlreadyPurchasedLink_(this.purchaseConfig_['identify_url'])); + this.createAlreadyPurchasedLink_(this.purchaseConfig_['identify_url']) + ); this.renderTextBlock_('footer'); dialogContainer.appendChild(this.innerContainer_); dialogContainer.appendChild(this.createLaterpayBadge_()); this.containerEmpty_ = false; this.preselectFirstOption_( - dev().assertElement(listContainer.firstElementChild)); + dev().assertElement(listContainer.firstElementChild) + ); } /** @@ -485,15 +507,14 @@ export class LaterpayVendor { radio.type = 'radio'; radio.id = option['title']; radio.value = option['purchase_url']; - const purchaseType = option['price']['payment_model'] === 'pay_later' ? - 'payLater' : - 'payNow'; + const purchaseType = + option['price']['payment_model'] === 'pay_later' ? 'payLater' : 'payNow'; const purchaseActionLabel = this.i18n_[purchaseType + 'Button']; radio.setAttribute('data-purchase-action-label', purchaseActionLabel); radio.setAttribute('data-purchase-type', purchaseType); - this.purchaseOptionListeners_.push(listen( - radio, 'change', this.handlePurchaseOptionSelection_.bind(this) - )); + this.purchaseOptionListeners_.push( + listen(radio, 'change', this.handlePurchaseOptionSelection_.bind(this)) + ); return radio; } @@ -523,7 +544,7 @@ export class LaterpayVendor { * @private */ formatPrice_(priceValue) { - const value = (priceValue / 100); + const value = priceValue / 100; const props = { style: 'decimal', minimumFractionDigits: 0, @@ -534,7 +555,7 @@ export class LaterpayVendor { /** * @param {string} href * @return {!Element} - */ + */ createAlreadyPurchasedLink_(href) { const p = this.createElement_('p'); p.className = TAG + '-already-purchased-link-container'; @@ -565,8 +586,10 @@ export class LaterpayVendor { const selectedOptionClassname = TAG + '-selected'; const prevPurchaseOption = this.selectedPurchaseOption_; const purchaseActionLabel = target.dataset['purchaseActionLabel']; - if (prevPurchaseOption && - prevPurchaseOption.classList.contains(selectedOptionClassname)) { + if ( + prevPurchaseOption && + prevPurchaseOption.classList.contains(selectedOptionClassname) + ) { prevPurchaseOption.classList.remove(selectedOptionClassname); } this.selectedPurchaseOption_ = target; @@ -583,7 +606,9 @@ export class LaterpayVendor { handlePurchase_(ev, purchaseUrl, purchaseType) { ev.preventDefault(); const urlPromise = this.accessSource_.buildUrl( - purchaseUrl, /* useAuthData */ false); + purchaseUrl, + /* useAuthData */ false + ); return urlPromise.then(url => { dev().fine(TAG, 'Authorization URL: ', url); this.accessSource_.loginWithUrl(url, purchaseType); diff --git a/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js b/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js index ab5e225e1a58b..f5eafad0b88d0 100644 --- a/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js +++ b/extensions/amp-access-laterpay/0.2/test/test-amp-access-laterpay.js @@ -18,304 +18,351 @@ import {LaterpayVendor} from '../laterpay-impl'; const TAG = 'amp-access-laterpay'; -describes.fakeWin('LaterpayVendor', { - amp: true, - location: 'https://pub.com/doc1', -}, env => { - let win, document, ampdoc; - let accessSource; - let accessService; - let accessSourceMock; - let xhrMock; - let articleTitle; - let laterpayConfig; - let vendor; - let priceData; +describes.fakeWin( + 'LaterpayVendor', + { + amp: true, + location: 'https://pub.com/doc1', + }, + env => { + let win, document, ampdoc; + let accessSource; + let accessService; + let accessSourceMock; + let xhrMock; + let articleTitle; + let laterpayConfig; + let vendor; + let priceData; - beforeEach(() => { - win = env.win; - ampdoc = env.ampdoc; - document = win.document; - - laterpayConfig = { - articleTitleSelector: '#laterpay-test-title', - region: 'us', - }; + beforeEach(() => { + win = env.win; + ampdoc = env.ampdoc; + document = win.document; - accessSource = { - getAdapterConfig: () => { return laterpayConfig; }, - buildUrl: () => {}, - loginWithUrl: () => {}, - getLoginUrl: () => {}, - }; + laterpayConfig = { + articleTitleSelector: '#laterpay-test-title', + region: 'us', + }; - accessService = { - ampdoc, - getSource: () => accessSource, - }; + accessSource = { + getAdapterConfig: () => { + return laterpayConfig; + }, + buildUrl: () => {}, + loginWithUrl: () => {}, + getLoginUrl: () => {}, + }; - priceData = { - price: 123, - currency: 'EUR', - 'payment_model': 'pay_later', - }; + accessService = { + ampdoc, + getSource: () => accessSource, + }; - accessSourceMock = sandbox.mock(accessSource); + priceData = { + price: 123, + currency: 'EUR', + 'payment_model': 'pay_later', + }; - articleTitle = document.createElement('h1'); - articleTitle.id = 'laterpay-test-title'; - articleTitle.textContent = 'test title'; - document.body.appendChild(articleTitle); + accessSourceMock = sandbox.mock(accessSource); - vendor = new LaterpayVendor(accessService, accessSource); - xhrMock = sandbox.mock(vendor.xhr_); - }); + articleTitle = document.createElement('h1'); + articleTitle.id = 'laterpay-test-title'; + articleTitle.textContent = 'test title'; + document.body.appendChild(articleTitle); - afterEach(() => { - articleTitle.parentNode.removeChild(articleTitle); - accessSourceMock.verify(); - xhrMock.verify(); - }); + vendor = new LaterpayVendor(accessService, accessSource); + xhrMock = sandbox.mock(vendor.xhr_); + }); - describe('authorize', () => { - let emptyContainerStub; - beforeEach(() => { - emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); - sandbox.stub(vendor, 'renderPurchaseOverlay_'); + afterEach(() => { + articleTitle.parentNode.removeChild(articleTitle); + accessSourceMock.verify(); + xhrMock.verify(); }); - it('uses a non default region', () => { - const buildUrl = accessSourceMock.expects('buildUrl') + describe('authorize', () => { + let emptyContainerStub; + beforeEach(() => { + emptyContainerStub = sandbox.stub(vendor, 'emptyContainer_'); + sandbox.stub(vendor, 'renderPurchaseOverlay_'); + }); + + it('uses a non default region', () => { + const buildUrl = accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(() => { - expect( + return vendor.authorize().then(() => { + expect( /connector\.uselaterpay\.com/g.test(buildUrl.firstCall.args[0]) - ).to.be.true; + ).to.be.true; + }); }); - }); - it('successful authorization', () => { - vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; - accessSourceMock.expects('buildUrl') - .withExactArgs('https://baseurl?param&article_title=test%20title', false) + it('successful authorization', () => { + vendor.purchaseConfigBaseUrl_ = 'https://baseurl?param'; + accessSourceMock + .expects('buildUrl') + .withExactArgs( + 'https://baseurl?param&article_title=test%20title', + false + ) .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(resp => { - expect(resp.access).to.be.true; - expect(emptyContainerStub.called).to.be.true; + return vendor.authorize().then(resp => { + expect(resp.access).to.be.true; + expect(emptyContainerStub.called).to.be.true; + }); }); - }); - it('authorization fails due to lack of server config', () => { - accessSourceMock.expects('buildUrl') + it('authorization fails due to lack of server config', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) .returns(Promise.resolve({status: 204})) .once(); - return vendor.authorize().catch(err => { - expect(err.message).to.exist; + return vendor.authorize().catch(err => { + expect(err.message).to.exist; + }); }); - }); - it('authorization response from server fails', () => { - accessSourceMock.expects('buildUrl') + it('authorization response from server fails', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') + xhrMock + .expects('fetchJson') .withExactArgs('https://builturl', { credentials: 'include', }) - .returns(Promise.reject({ - response: { - status: 402, - json() { - return Promise.resolve({'purchase_options': [ - {'sales_model': 'single_purchase'}, - {'sales_model': 'timepass'}, - {'sales_model': 'subscription'}, - ]}); + .returns( + Promise.reject({ + response: { + status: 402, + json() { + return Promise.resolve({ + 'purchase_options': [ + {'sales_model': 'single_purchase'}, + {'sales_model': 'timepass'}, + {'sales_model': 'subscription'}, + ], + }); + }, }, - }, - })) + }) + ) .once(); - emptyContainerStub.returns(Promise.resolve()); - return vendor.authorize().then(err => { - expect(err.access).to.be.false; - expect(vendor.purchaseOptions_.singlePurchases).to.have.lengthOf(1); - expect(vendor.purchaseOptions_.timepasses).to.have.lengthOf(1); - expect(vendor.purchaseOptions_.subscriptions).to.have.lengthOf(1); + emptyContainerStub.returns(Promise.resolve()); + return vendor.authorize().then(err => { + expect(err.access).to.be.false; + expect(vendor.purchaseOptions_.singlePurchases).to.have.lengthOf(1); + expect(vendor.purchaseOptions_.timepasses).to.have.lengthOf(1); + expect(vendor.purchaseOptions_.subscriptions).to.have.lengthOf(1); + }); }); }); - }); - - describe('create purchase overlay', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - 'identify_url': 'http://id.url', - }; - vendor.purchaseOptions_ = { - singlePurchases: [{ - price: priceData, - }], - timepasses: [{ - price: priceData, - }], - subscriptions: [{ - price: priceData, - }], - }; - vendor.renderPurchaseOverlay_(); - }); + describe('create purchase overlay', () => { + let container; + + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + 'identify_url': 'http://id.url', + }; + vendor.purchaseOptions_ = { + singlePurchases: [ + { + price: priceData, + }, + ], + timepasses: [ + { + price: priceData, + }, + ], + subscriptions: [ + { + price: priceData, + }, + ], + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('renders list', () => { - expect(container.querySelector('ul')).to.not.be.null; - }); + it('renders list', () => { + expect(container.querySelector('ul')).to.not.be.null; + }); - it('renders 3 purchase options', () => { - expect(container.querySelector('ul').childNodes.length).to.equal(3); - }); + it('renders 3 purchase options', () => { + expect(container.querySelector('ul').childNodes.length).to.equal(3); + }); - it('renders identify url link', () => { - expect(container.querySelector('p > a').href).to - .match(/^http\:\/\/id\.url/); + it('renders identify url link', () => { + expect(container.querySelector('p > a').href).to.match( + /^http\:\/\/id\.url/ + ); + }); }); - }); - - describe('purchase option selection', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - 'identify_url': 'http://id.url', - }; - vendor.purchaseOptions_ = { - singlePurchases: [{ - price: priceData, - }], - timepasses: [{ - price: priceData, - }], - subscriptions: [{ - price: priceData, - }], - }; - vendor.renderPurchaseOverlay_(); - const ev = new Event('change'); - container.querySelector('input').dispatchEvent(ev); - }); + describe('purchase option selection', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + 'identify_url': 'http://id.url', + }; + vendor.purchaseOptions_ = { + singlePurchases: [ + { + price: priceData, + }, + ], + timepasses: [ + { + price: priceData, + }, + ], + subscriptions: [ + { + price: priceData, + }, + ], + }; + vendor.renderPurchaseOverlay_(); + const ev = new Event('change'); + container.querySelector('input').dispatchEvent(ev); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('purchase option is selected', () => { - expect(vendor.selectedPurchaseOption_).to.not.be.null; - expect(vendor.selectedPurchaseOption_.classList - .contains(TAG + '-selected')).to.be.true; + it('purchase option is selected', () => { + expect(vendor.selectedPurchaseOption_).to.not.be.null; + expect( + vendor.selectedPurchaseOption_.classList.contains(TAG + '-selected') + ).to.be.true; + }); }); - }); - - describe('purchase', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.id = TAG + '-dialog'; - document.body.appendChild(container); - vendor.i18n_ = {}; - vendor.purchaseConfig_ = { - 'identify_url': 'http://id.url', - }; - vendor.purchaseOptions_ = { - singlePurchases: [{ - price: priceData, - }], - timepasses: [{ - price: priceData, - }], - subscriptions: [{ - price: priceData, - }], - }; - vendor.renderPurchaseOverlay_(); - }); + describe('purchase', () => { + let container; + beforeEach(() => { + container = document.createElement('div'); + container.id = TAG + '-dialog'; + document.body.appendChild(container); + vendor.i18n_ = {}; + vendor.purchaseConfig_ = { + 'identify_url': 'http://id.url', + }; + vendor.purchaseOptions_ = { + singlePurchases: [ + { + price: priceData, + }, + ], + timepasses: [ + { + price: priceData, + }, + ], + subscriptions: [ + { + price: priceData, + }, + ], + }; + vendor.renderPurchaseOverlay_(); + }); - afterEach(() => { - container.parentNode.removeChild(container); - }); + afterEach(() => { + container.parentNode.removeChild(container); + }); - it('sends request for purchase', done => { - const changeEv = new Event('change'); - container.querySelector('input').dispatchEvent(changeEv); - accessSourceMock.expects('buildUrl') + it('sends request for purchase', done => { + const changeEv = new Event('change'); + container.querySelector('input').dispatchEvent(changeEv); + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container.querySelector('button').dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); - }); + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container.querySelector('button').dispatchEvent(clickEv); + setTimeout(() => { + done(); + }, 500); + }); - it('sends request for already purchased', done => { - accessSourceMock.expects('buildUrl') + it('sends request for already purchased', done => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://apllink')) .once(); - accessSourceMock.expects('loginWithUrl') - .once(); - const clickEv = new Event('click'); - container + accessSourceMock.expects('loginWithUrl').once(); + const clickEv = new Event('click'); + container .querySelector('.' + TAG + '-already-purchased-link-container > a') .dispatchEvent(clickEv); - setTimeout(() => {done();}, 500); + setTimeout(() => { + done(); + }, 500); + }); }); - - - }); -}); + } +); diff --git a/extensions/amp-access-poool/0.1/amp-access-poool.js b/extensions/amp-access-poool/0.1/amp-access-poool.js index 59b624cb5e3df..662d138b55d8b 100644 --- a/extensions/amp-access-poool/0.1/amp-access-poool.js +++ b/extensions/amp-access-poool/0.1/amp-access-poool.js @@ -19,18 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-poool', '0.1', function(AMP) { AMP.registerServiceForDoc( - 'poool', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('poool'); - const vendor = new PooolVendor(accessService, source); - const adapter = /** @type { + 'poool', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('poool'); + const vendor = new PooolVendor(accessService, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); + adapter.registerVendor(vendor); + return vendor; }); + } + ); }); diff --git a/extensions/amp-access-poool/0.1/poool-impl.js b/extensions/amp-access-poool/0.1/poool-impl.js index 9c713b478c948..76e1fec1bab9f 100644 --- a/extensions/amp-access-poool/0.1/poool-impl.js +++ b/extensions/amp-access-poool/0.1/poool-impl.js @@ -24,14 +24,13 @@ import {resetStyles, setStyle, setStyles} from '../../../src/style'; const TAG = 'amp-access-poool'; const ACCESS_CONFIG = { - 'authorization': - 'https://api.poool.fr/api/v2/amp/access?rid=READER_ID', + 'authorization': 'https://api.poool.fr/api/v2/amp/access?rid=READER_ID', 'iframe': - 'https://assets.poool.fr/amp.html' - + '?rid=READER_ID' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&r=DOCUMENT_REFERRER', + 'https://assets.poool.fr/amp.html' + + '?rid=READER_ID' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&r=DOCUMENT_REFERRER', }; const AUTHORIZATION_TIMEOUT = 3000; @@ -59,7 +58,6 @@ export class PooolVendor { * @param {!../../amp-access/0.1/amp-access-source.AccessSource} accessSource */ constructor(accessService, accessSource) { - /** @const */ this.ampdoc = accessService.ampdoc; @@ -102,20 +100,22 @@ export class PooolVendor { * @return {!Promise} */ authorize() { - return this.getPooolAccess_() - .then(response => { - return {access: response.access}; - }, err => { - if (!err || !err.response) { - throw err; - } - const {response} = err; - if (response.status !== 402) { - throw err; - } - this.renderPoool_(); - return {access: false}; - }); + return this.getPooolAccess_().then( + response => { + return {access: response.access}; + }, + err => { + if (!err || !err.response) { + throw err; + } + const {response} = err; + if (response.status !== 402) { + throw err; + } + this.renderPoool_(); + return {access: false}; + } + ); } /** @@ -154,16 +154,18 @@ export class PooolVendor { * @private */ getPooolAccess_() { - const url = addParamToUrl(this.accessUrl_ , 'iid', this.itemID_); + const url = addParamToUrl(this.accessUrl_, 'iid', this.itemID_); const urlPromise = this.accessSource_.buildUrl(url, false); - return urlPromise.then(url => { - return this.accessSource_.getLoginUrl(url); - }).then(url => { - dev().info(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( - AUTHORIZATION_TIMEOUT, - this.xhr_.fetchJson(url)).then(res => res.json()); - }); + return urlPromise + .then(url => { + return this.accessSource_.getLoginUrl(url); + }) + .then(url => { + dev().info(TAG, 'Authorization URL: ', url); + return this.timer_ + .timeoutPromise(AUTHORIZATION_TIMEOUT, this.xhr_.fetchJson(url)) + .then(res => res.json()); + }); } /** @@ -172,18 +174,23 @@ export class PooolVendor { renderPoool_() { const pooolContainer = document.getElementById('poool'); const urlPromise = this.accessSource_.buildUrl( - addParamsToUrl(this.iframeUrl_, dict({ + addParamsToUrl( + this.iframeUrl_, + dict({ 'bi': this.pooolConfig_['bundleID'], 'iid': this.pooolConfig_['itemID'], 'ce': this.pooolConfig_['cookiesEnabled'], - 'd': typeof this.pooolConfig_['debug'] !== 'undefined' && + 'd': + typeof this.pooolConfig_['debug'] !== 'undefined' && this.pooolConfig_['debug'] !== null - ? this.pooolConfig_['debug'] - : getMode().development || getMode().localDev, + ? this.pooolConfig_['debug'] + : getMode().development || getMode().localDev, 'fw': this.pooolConfig_['forceWidget'], 'cs': this.pooolConfig_['customSegment'], - })), - false); + }) + ), + false + ); return urlPromise.then(url => { this.iframe_.src = url; diff --git a/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js b/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js index 6f0a6f424fb36..6491c49c92d9e 100644 --- a/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js +++ b/extensions/amp-access-poool/0.1/test/test-amp-access-poool.js @@ -15,120 +15,140 @@ */ import {PooolVendor} from '../poool-impl'; -describes.fakeWin('PooolVendor', { - amp: true, - location: 'https://pub.com/doc1', -}, env => { - let win, document, ampdoc; - let accessSource; - let accessService; - let accessSourceMock; - let xhrMock; - let pooolConfig; - let vendor; +describes.fakeWin( + 'PooolVendor', + { + amp: true, + location: 'https://pub.com/doc1', + }, + env => { + let win, document, ampdoc; + let accessSource; + let accessService; + let accessSourceMock; + let xhrMock; + let pooolConfig; + let vendor; - beforeEach(() => { - win = env.win; - ampdoc = env.ampdoc; - document = win.document; - - pooolConfig = { - bundleID: 'ZRGA3EYZ4GRBTSHREG345HGGZRTHZEGEH', - pageType: 'premium', - itemID: 'amp-test-article', - }; - - accessSource = { - getAdapterConfig: () => { return pooolConfig; }, - buildUrl: () => {}, - getReaderId_: () => {}, - getLoginUrl: () => {}, - }; - - accessService = { - ampdoc, - getSource: () => accessSource, - }; + beforeEach(() => { + win = env.win; + ampdoc = env.ampdoc; + document = win.document; - accessSourceMock = sandbox.mock(accessSource); + pooolConfig = { + bundleID: 'ZRGA3EYZ4GRBTSHREG345HGGZRTHZEGEH', + pageType: 'premium', + itemID: 'amp-test-article', + }; - vendor = new PooolVendor(accessService, accessSource); - xhrMock = sandbox.mock(vendor.xhr_); - }); + accessSource = { + getAdapterConfig: () => { + return pooolConfig; + }, + buildUrl: () => {}, + getReaderId_: () => {}, + getLoginUrl: () => {}, + }; - afterEach(() => { - accessSourceMock.verify(); - xhrMock.verify(); - }); + accessService = { + ampdoc, + getSource: () => accessSource, + }; - describe('authorize', () => { - let container; + accessSourceMock = sandbox.mock(accessSource); - beforeEach(() => { - container = document.createElement('div'); - container.id = 'poool-widget'; - document.body.appendChild(container); - sandbox.stub(vendor, 'renderPoool_'); + vendor = new PooolVendor(accessService, accessSource); + xhrMock = sandbox.mock(vendor.xhr_); }); afterEach(() => { - container.parentNode.removeChild(container); + accessSourceMock.verify(); + xhrMock.verify(); }); - it('successful authorization', () => { - vendor.accessUrl_ = 'https://baseurl?param'; - accessSourceMock.expects('buildUrl') + describe('authorize', () => { + let container; + + beforeEach(() => { + container = document.createElement('div'); + container.id = 'poool-widget'; + document.body.appendChild(container); + sandbox.stub(vendor, 'renderPoool_'); + }); + + afterEach(() => { + container.parentNode.removeChild(container); + }); + + it('successful authorization', () => { + vendor.accessUrl_ = 'https://baseurl?param'; + accessSourceMock + .expects('buildUrl') .withExactArgs('https://baseurl?param&iid=amp-test-article', false) .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().then(resp => { - expect(resp.access).to.be.true; + return vendor.authorize().then(resp => { + expect(resp.access).to.be.true; + }); }); - }); - it('authorization fails because of wrong or missing server config', () => { - accessSourceMock.expects('buildUrl') + it('authorization fails because of wrong or missing server config', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - accessSourceMock.expects('getLoginUrl') + accessSourceMock + .expects('getLoginUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.resolve({ - json() { - return Promise.resolve({access: true}); - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.resolve({ + json() { + return Promise.resolve({access: true}); + }, + }) + ) .once(); - return vendor.authorize().catch(err => { - expect(err.message).to.exist; + return vendor.authorize().catch(err => { + expect(err.message).to.exist; + }); }); - }); - it('authorization response fails - 402 error', () => { - accessSourceMock.expects('buildUrl') + it('authorization response fails - 402 error', () => { + accessSourceMock + .expects('buildUrl') .returns(Promise.resolve('https://builturl')) .once(); - xhrMock.expects('fetchJson') - .returns(Promise.reject({ - response: { - status: 402, - }, - })) + xhrMock + .expects('fetchJson') + .returns( + Promise.reject({ + response: { + status: 402, + }, + }) + ) .once(); - return vendor.authorize().then(err => { - expect(err.access).to.be.false; + return vendor.authorize().then(err => { + expect(err.access).to.be.false; + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-access-scroll/0.1/amp-access-scroll.js b/extensions/amp-access-scroll/0.1/amp-access-scroll.js index dfeabd362dcff..0b8b341af0b3e 100644 --- a/extensions/amp-access-scroll/0.1/amp-access-scroll.js +++ b/extensions/amp-access-scroll/0.1/amp-access-scroll.js @@ -19,19 +19,19 @@ import {Services} from '../../../src/services'; AMP.extension('amp-access-scroll', '0.1', function(AMP) { AMP.registerServiceForDoc( - 'scroll', - /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ - function(ampdoc) { - const element = ampdoc.getHeadNode(); - return Services.accessServiceForDoc(element).then(accessService => { - const source = accessService.getVendorSource('scroll'); - const vendor = new ScrollAccessVendor(ampdoc, source); - const adapter = /** @type { + 'scroll', + /** @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc */ + function(ampdoc) { + const element = ampdoc.getHeadNode(); + return Services.accessServiceForDoc(element).then(accessService => { + const source = accessService.getVendorSource('scroll'); + const vendor = new ScrollAccessVendor(ampdoc, source); + const adapter = /** @type { !../../amp-access/0.1/amp-access-vendor.AccessVendorAdapter } */ (source.getAdapter()); - adapter.registerVendor(vendor); - return vendor; - }); - } + adapter.registerVendor(vendor); + return vendor; + }); + } ); }); diff --git a/extensions/amp-access-scroll/0.1/read-depth-tracker.js b/extensions/amp-access-scroll/0.1/read-depth-tracker.js index b47323f210df1..b6cc63c73280a 100644 --- a/extensions/amp-access-scroll/0.1/read-depth-tracker.js +++ b/extensions/amp-access-scroll/0.1/read-depth-tracker.js @@ -39,9 +39,9 @@ export class ReadDepthTracker { /** @private {function()} */ const debouncedFindTopParagraph_ = debounce( - ampdoc.win, - this.findTopParagraph_.bind(this), - 1000 + ampdoc.win, + this.findTopParagraph_.bind(this), + 1000 ); this.viewport_ = Services.viewportForDoc(ampdoc); @@ -59,24 +59,27 @@ export class ReadDepthTracker { * @private */ findTopParagraph_() { - return Promise.all([].slice.call(this.paragraphs_) - .map(p => this.viewport_.getClientRectAsync(p))) - .then(rects => { - let lastIdxAboveViewport = null; - let lastPosition = null; - for (let i = rects.length - 1; i >= 0; i--) { - const bottomPosition = rects[i].bottom; - if (bottomPosition <= 0 && - (lastPosition === null || lastPosition < bottomPosition) - ) { - lastIdxAboveViewport = i; - lastPosition = bottomPosition; - } - } - if (lastIdxAboveViewport !== null) { - this.recordLatestRead_(lastIdxAboveViewport); - } - }); + return Promise.all( + [].slice + .call(this.paragraphs_) + .map(p => this.viewport_.getClientRectAsync(p)) + ).then(rects => { + let lastIdxAboveViewport = null; + let lastPosition = null; + for (let i = rects.length - 1; i >= 0; i--) { + const bottomPosition = rects[i].bottom; + if ( + bottomPosition <= 0 && + (lastPosition === null || lastPosition < bottomPosition) + ) { + lastIdxAboveViewport = i; + lastPosition = bottomPosition; + } + } + if (lastIdxAboveViewport !== null) { + this.recordLatestRead_(lastIdxAboveViewport); + } + }); } /** @@ -88,7 +91,7 @@ export class ReadDepthTracker { if (this.lastReadIndex_ !== paragraphIdx) { this.lastReadIndex_ = paragraphIdx; this.updateLastRead_( - this.paragraphs_[paragraphIdx]./*OK*/innerText.substring(0, 50) + this.paragraphs_[paragraphIdx]./*OK*/ innerText.substring(0, 50) ); } } @@ -99,16 +102,19 @@ export class ReadDepthTracker { * @private */ updateLastRead_(snippet) { - this.accessSource_.buildUrl(( - `${this.connectHostname_}/amp/updatedepth` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&rd=' + encodeURIComponent(snippet) - ), false).then(url => { - Services.xhrFor(this.ampdoc_.win) - .fetch(url); - }); + this.accessSource_ + .buildUrl( + `${this.connectHostname_}/amp/updatedepth` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&rd=' + + encodeURIComponent(snippet), + false + ) + .then(url => { + Services.xhrFor(this.ampdoc_.win).fetch(url); + }); } } diff --git a/extensions/amp-access-scroll/0.1/scroll-impl.js b/extensions/amp-access-scroll/0.1/scroll-impl.js index aaa9ba485caee..9128b0272e7c3 100644 --- a/extensions/amp-access-scroll/0.1/scroll-impl.js +++ b/extensions/amp-access-scroll/0.1/scroll-impl.js @@ -33,21 +33,23 @@ const TAG = 'amp-access-scroll-elt'; const accessConfig = connectHostname => { /** @const {!JsonObject} */ const ACCESS_CONFIG = /** @type {!JsonObject} */ ({ - 'authorization': `${connectHostname}/amp/access` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&x=QUERY_PARAM(scrollx)', - 'pingback': `${connectHostname}/amp/pingback` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&r=DOCUMENT_REFERRER' - + '&x=QUERY_PARAM(scrollx)' - + '&d=AUTHDATA(scroll)' - + '&v=AUTHDATA(visitId)', + 'authorization': + `${connectHostname}/amp/access` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&x=QUERY_PARAM(scrollx)', + 'pingback': + `${connectHostname}/amp/pingback` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&r=DOCUMENT_REFERRER' + + '&x=QUERY_PARAM(scrollx)' + + '&d=AUTHDATA(scroll)' + + '&v=AUTHDATA(visitId)', 'namespace': 'scroll', }); return ACCESS_CONFIG; @@ -60,17 +62,18 @@ const accessConfig = connectHostname => { const analyticsConfig = connectHostname => { const ANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'scroll': `${connectHostname}/amp/analytics` - + '?rid=ACCESS_READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&r=DOCUMENT_REFERRER' - + '&x=QUERY_PARAM(scrollx)' - + '&d=AUTHDATA(scroll.scroll)' - + '&v=AUTHDATA(scroll.visitId)' - + '&h=SOURCE_HOSTNAME' - + '&s=${totalEngagedTime}', + 'scroll': + `${connectHostname}/amp/analytics` + + '?rid=ACCESS_READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&r=DOCUMENT_REFERRER' + + '&x=QUERY_PARAM(scrollx)' + + '&d=AUTHDATA(scroll.scroll)' + + '&v=AUTHDATA(scroll.visitId)' + + '&h=SOURCE_HOSTNAME' + + '&s=${totalEngagedTime}', }, 'triggers': { 'trackInterval': { @@ -150,35 +153,37 @@ export class ScrollAccessVendor extends AccessClientAdapter { /** @override */ authorize() { // TODO(dbow): Handle timeout? - return super.authorize() - .then(response => { - const isStory = this.ampdoc.getRootNode().querySelector( - 'amp-story[standalone]'); - if (response && response['scroll']) { - if (!isStory) { - const config = this.accessSource_.getAdapterConfig(); - new ScrollElement(this.ampdoc).handleScrollUser( - this.accessSource_, config); - addAnalytics(this.ampdoc, config); - if (response['features'] && response['features']['readDepth']) { - new ReadDepthTracker( - this.ampdoc, - this.accessSource_, - connectHostname(config) - ); - } - } - } else { - if ( - response && - response['blocker'] && - ScrollContentBlocker.shouldCheck(this.ampdoc) - ) { - new ScrollContentBlocker(this.ampdoc, this.accessSource_).check(); - } + return super.authorize().then(response => { + const isStory = this.ampdoc + .getRootNode() + .querySelector('amp-story[standalone]'); + if (response && response['scroll']) { + if (!isStory) { + const config = this.accessSource_.getAdapterConfig(); + new ScrollElement(this.ampdoc).handleScrollUser( + this.accessSource_, + config + ); + addAnalytics(this.ampdoc, config); + if (response['features'] && response['features']['readDepth']) { + new ReadDepthTracker( + this.ampdoc, + this.accessSource_, + connectHostname(config) + ); } - return response; - }); + } + } else { + if ( + response && + response['blocker'] && + ScrollContentBlocker.shouldCheck(this.ampdoc) + ) { + new ScrollContentBlocker(this.ampdoc, this.accessSource_).check(); + } + } + return response; + }); } } @@ -213,17 +218,19 @@ class ScrollContentBlocker { */ check() { Services.xhrFor(this.ampdoc_.win) - .fetchJson('https://block.scroll.com/check.json') - .then(() => false, e => this.blockedByScrollApp_(e.message)) - .then(blockedByScrollApp => { - if (blockedByScrollApp === true) { - // TODO(dbow): Ideally we would automatically redirect to the page - // here, but for now we are adding a button so we redirect on user - // action. - new ScrollElement(this.ampdoc_).addActivateButton( - this.accessSource_, this.accessSource_.getAdapterConfig()); - } - }); + .fetchJson('https://block.scroll.com/check.json') + .then(() => false, e => this.blockedByScrollApp_(e.message)) + .then(blockedByScrollApp => { + if (blockedByScrollApp === true) { + // TODO(dbow): Ideally we would automatically redirect to the page + // here, but for now we are adding a button so we redirect on user + // action. + new ScrollElement(this.ampdoc_).addActivateButton( + this.accessSource_, + this.accessSource_.getAdapterConfig() + ); + } + }); } /** @@ -237,7 +244,7 @@ class ScrollContentBlocker { blockedByScrollApp_(message) { return ( message.indexOf( - 'XHR Failed fetching (https://block.scroll.com/...): ' + + 'XHR Failed fetching (https://block.scroll.com/...): ' + 'Resource blocked by content blocker' ) === 0 ); @@ -271,9 +278,12 @@ class ScrollElement { this.iframe_.setAttribute('title', 'Scroll'); this.iframe_.setAttribute('width', '100%'); this.iframe_.setAttribute('height', '100%'); - this.iframe_.setAttribute('sandbox', 'allow-scripts allow-same-origin ' + - 'allow-top-navigation allow-popups ' + - 'allow-popups-to-escape-sandbox'); + this.iframe_.setAttribute( + 'sandbox', + 'allow-scripts allow-same-origin ' + + 'allow-top-navigation allow-popups ' + + 'allow-popups-to-escape-sandbox' + ); this.scrollBar_.appendChild(this.iframe_); ampdoc.getBody().appendChild(this.scrollBar_); @@ -293,8 +303,10 @@ class ScrollElement { placeholder.classList.add('amp-access-scroll-bar'); placeholder.classList.add('amp-access-scroll-placeholder'); const img = document.createElement('img'); - img.setAttribute('src', - 'https://static.scroll.com/assets/icn-scroll-logo32-9f4ceef399905139bbd26b87bfe94542.svg'); + img.setAttribute( + 'src', + 'https://static.scroll.com/assets/icn-scroll-logo32-9f4ceef399905139bbd26b87bfe94542.svg' + ); img.setAttribute('layout', 'fixed'); img.setAttribute('width', 32); img.setAttribute('height', 32); @@ -302,19 +314,22 @@ class ScrollElement { this.ampdoc_.getBody().appendChild(placeholder); // Set iframe to scrollbar URL. - accessSource.buildUrl(( - `${connectHostname(vendorConfig)}/amp/scrollbar` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - ), false).then(scrollbarUrl => { - this.iframe_.onload = () => { - // On iframe load, remove placeholder element. - this.ampdoc_.getBody().removeChild(placeholder); - }; - this.iframe_.setAttribute('src', scrollbarUrl); - }); + accessSource + .buildUrl( + `${connectHostname(vendorConfig)}/amp/scrollbar` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL', + false + ) + .then(scrollbarUrl => { + this.iframe_.onload = () => { + // On iframe load, remove placeholder element. + this.ampdoc_.getBody().removeChild(placeholder); + }; + this.iframe_.setAttribute('src', scrollbarUrl); + }); } /** @@ -324,16 +339,19 @@ class ScrollElement { * @param {!JsonObject} vendorConfig */ addActivateButton(accessSource, vendorConfig) { - accessSource.buildUrl(( - `${scrollHostname(vendorConfig)}/activateamp` - + '?rid=READER_ID' - + '&cid=CLIENT_ID(scroll1)' - + '&c=CANONICAL_URL' - + '&o=AMPDOC_URL' - + '&x=QUERY_PARAM(scrollx)' - ), false).then(url => { - this.iframe_.setAttribute('src', url); - }); + accessSource + .buildUrl( + `${scrollHostname(vendorConfig)}/activateamp` + + '?rid=READER_ID' + + '&cid=CLIENT_ID(scroll1)' + + '&c=CANONICAL_URL' + + '&o=AMPDOC_URL' + + '&x=QUERY_PARAM(scrollx)', + false + ) + .then(url => { + this.iframe_.setAttribute('src', url); + }); } } @@ -353,13 +371,18 @@ function addAnalytics(ampdoc, vendorConfig) { if (vendorConfig['dataConsentId']) { attributes['data-block-on-consent'] = ''; } - const analyticsElem = createElementWithAttributes(doc, 'amp-analytics', - attributes); + const analyticsElem = createElementWithAttributes( + doc, + 'amp-analytics', + attributes + ); const scriptElem = createElementWithAttributes( - doc, - 'script', dict({ - 'type': 'application/json', - })); + doc, + 'script', + dict({ + 'type': 'application/json', + }) + ); const ANALYTICS_CONFIG = analyticsConfig(connectHostname(vendorConfig)); scriptElem.textContent = JSON.stringify(ANALYTICS_CONFIG); analyticsElem.appendChild(scriptElem); @@ -367,7 +390,7 @@ function addAnalytics(ampdoc, vendorConfig) { // Get extensions service and force load analytics extension const extensions = Services.extensionsFor(ampdoc.win); - extensions./*OK*/installExtensionForDoc(ampdoc, 'amp-analytics'); + extensions./*OK*/ installExtensionForDoc(ampdoc, 'amp-analytics'); // Append ampdoc.getBody().appendChild(analyticsElem); diff --git a/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js b/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js index 7537b82bdfc15..30e78b29e56e0 100644 --- a/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js +++ b/extensions/amp-access-scroll/0.1/test/read-depth-tracker.js @@ -16,27 +16,30 @@ import {AccessSource} from '../../../amp-access/0.1/amp-access-source'; import {ReadDepthTracker} from '../read-depth-tracker'; -describes.realWin('ReadDepthTracker', { - amp: { - extensions: ['amp-access-scroll'], +describes.realWin( + 'ReadDepthTracker', + { + amp: { + extensions: ['amp-access-scroll'], + }, }, -}, env => { - let win; - let doc; - let ampdoc; - let sandbox; - let accessSource; - let readDepthTracker; + env => { + let win; + let doc; + let ampdoc; + let sandbox; + let accessSource; + let readDepthTracker; - beforeEach(() => { - win = env.win; - doc = win.document; - ampdoc = env.ampdoc; - sandbox = env.sandbox; + beforeEach(() => { + win = env.win; + doc = win.document; + ampdoc = env.ampdoc; + sandbox = env.sandbox; - // Undefined initialization params for AccessSource - let scheduleViewFn, onReauthorizeFn; - accessSource = new AccessSource( + // Undefined initialization params for AccessSource + let scheduleViewFn, onReauthorizeFn; + accessSource = new AccessSource( ampdoc, { 'authorization': 'https://acme.com/a', @@ -50,54 +53,55 @@ describes.realWin('ReadDepthTracker', { scheduleViewFn, onReauthorizeFn, doc.documentElement - ); + ); - for (let i = 0; i < 5; i++) { - const elem = doc.createElement('p'); - elem./*OK*/innerText = `Scroll amp test paragraph ${i}`; - elem.id = `${i}`; - doc.body.appendChild(elem); - } + for (let i = 0; i < 5; i++) { + const elem = doc.createElement('p'); + elem./*OK*/ innerText = `Scroll amp test paragraph ${i}`; + elem.id = `${i}`; + doc.body.appendChild(elem); + } - readDepthTracker = new ReadDepthTracker( + readDepthTracker = new ReadDepthTracker( ampdoc, accessSource, 'api.test.com' - ); + ); - // Stub viewport to fake paragraph positions - sandbox.stub(readDepthTracker.viewport_,'getClientRectAsync') + // Stub viewport to fake paragraph positions + sandbox + .stub(readDepthTracker.viewport_, 'getClientRectAsync') .callsFake(returnRectPosition); - // Stub updateLastRead_ call to check content sent - sandbox.stub(readDepthTracker, 'updateLastRead_'); - }); + // Stub updateLastRead_ call to check content sent + sandbox.stub(readDepthTracker, 'updateLastRead_'); + }); - function returnRectPosition(paragraph) { - if (paragraph.id === '0') { - return {bottom: -50}; - } else if (paragraph.id === '1') { - return {bottom: -30}; - } else { - return {bottom: 10}; + function returnRectPosition(paragraph) { + if (paragraph.id === '0') { + return {bottom: -50}; + } else if (paragraph.id === '1') { + return {bottom: -30}; + } else { + return {bottom: 10}; + } } - } - it('updates last read position to API with correct snippet', () => { - readDepthTracker.findTopParagraph_() - .then(() => { - expect(readDepthTracker.updateLastRead_.calledOnce).to.be.true; - expect(readDepthTracker.updateLastRead_.getCall(0).args[0]) - .to.equal('Scroll amp test paragraph 1'); - }); - }); + it('updates last read position to API with correct snippet', () => { + readDepthTracker.findTopParagraph_().then(() => { + expect(readDepthTracker.updateLastRead_.calledOnce).to.be.true; + expect(readDepthTracker.updateLastRead_.getCall(0).args[0]).to.equal( + 'Scroll amp test paragraph 1' + ); + }); + }); - it('does not update last read position if position has not changed', () => { - readDepthTracker.lastReadIndex_ = 1; + it('does not update last read position if position has not changed', () => { + readDepthTracker.lastReadIndex_ = 1; - readDepthTracker.findTopParagraph_() - .then(() => { - expect(readDepthTracker.updateLastRead_.calledOnce).to.be.false; - }); - }); -}); + readDepthTracker.findTopParagraph_().then(() => { + expect(readDepthTracker.updateLastRead_.calledOnce).to.be.false; + }); + }); + } +); diff --git a/extensions/amp-access/0.1/access-expr.js b/extensions/amp-access/0.1/access-expr.js index 5f67c03921709..b6339220726b1 100644 --- a/extensions/amp-access/0.1/access-expr.js +++ b/extensions/amp-access/0.1/access-expr.js @@ -16,7 +16,6 @@ import {accessParser as parser} from './access-expr-impl'; - /** * Evaluates access expression. * diff --git a/extensions/amp-access/0.1/access-vars.js b/extensions/amp-access/0.1/access-vars.js index 49cdda89e143a..85f3a77f2ca77 100644 --- a/extensions/amp-access/0.1/access-vars.js +++ b/extensions/amp-access/0.1/access-vars.js @@ -14,14 +14,12 @@ * limitations under the License. */ - /** * Exports the substitution variables for access services. * * @interface */ export class AccessVars { - /** * Returns the promise that will yield the access READER_ID. * diff --git a/extensions/amp-access/0.1/access-vendor.js b/extensions/amp-access/0.1/access-vendor.js index f780967926716..ec7f730143224 100644 --- a/extensions/amp-access/0.1/access-vendor.js +++ b/extensions/amp-access/0.1/access-vendor.js @@ -14,14 +14,12 @@ * limitations under the License. */ - /** * This interface is intended to be implemented by AMP Access vendors to * provide authorization and pingback. * @interface */ export class AccessVendor { - /** * Requests authorization from the vendor. Returns a promise that yields * a JSON authorization response. diff --git a/extensions/amp-access/0.1/amp-access-client.js b/extensions/amp-access/0.1/amp-access-client.js index 92b98c0927222..df6ffa585255c 100644 --- a/extensions/amp-access/0.1/amp-access-client.js +++ b/extensions/amp-access/0.1/amp-access-client.js @@ -26,10 +26,8 @@ const TAG = 'amp-access-client'; /** @const {number} */ const DEFAULT_AUTHORIZATION_TIMEOUT = 3000; - /** @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessClientAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -43,8 +41,10 @@ export class AccessClientAdapter { this.context_ = context; /** @const @private {string} */ - this.authorizationUrl_ = userAssert(configJson['authorization'], - '"authorization" URL must be specified'); + this.authorizationUrl_ = userAssert( + configJson['authorization'], + '"authorization" URL must be specified' + ); assertHttpsUrl(this.authorizationUrl_, '"authorization"'); /** @const @private {boolean} */ @@ -59,7 +59,8 @@ export class AccessClientAdapter { /** @const @private {number} */ this.authorizationTimeout_ = this.buildConfigAuthorizationTimeout_( - configJson); + configJson + ); /** @const @private {!../../../src/service/xhr-impl.Xhr} */ this.xhr_ = Services.xhrFor(ampdoc.win); @@ -78,8 +79,10 @@ export class AccessClientAdapter { } let timeout = configJson['authorizationTimeout']; - userAssert(typeof timeout == 'number', - '"authorizationTimeout" must be a number'); + userAssert( + typeof timeout == 'number', + '"authorizationTimeout" must be a number' + ); if (!(getMode().localDev || getMode().development)) { timeout = Math.min(timeout, DEFAULT_AUTHORIZATION_TIMEOUT); } @@ -118,15 +121,20 @@ export class AccessClientAdapter { /** @override */ authorize() { dev().fine(TAG, 'Start authorization via ', this.authorizationUrl_); - const urlPromise = this.context_.buildUrl(this.authorizationUrl_, - /* useAuthData */ false); + const urlPromise = this.context_.buildUrl( + this.authorizationUrl_, + /* useAuthData */ false + ); return urlPromise.then(url => { dev().fine(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( + return this.timer_ + .timeoutPromise( this.authorizationTimeout_, this.xhr_.fetchJson(url, { credentials: 'include', - })).then(res => res.json()); + }) + ) + .then(res => res.json()); }); } @@ -137,8 +145,10 @@ export class AccessClientAdapter { /** @override */ pingback() { - const promise = this.context_.buildUrl(devAssert(this.pingbackUrl_), - /* useAuthData */ true); + const promise = this.context_.buildUrl( + devAssert(this.pingbackUrl_), + /* useAuthData */ true + ); return promise.then(url => { dev().fine(TAG, 'Pingback URL: ', url); return this.xhr_.sendSignal(url, { diff --git a/extensions/amp-access/0.1/amp-access-iframe.js b/extensions/amp-access/0.1/amp-access-iframe.js index 7909911be5fbe..8904b5c4138a3 100644 --- a/extensions/amp-access/0.1/amp-access-iframe.js +++ b/extensions/amp-access/0.1/amp-access-iframe.js @@ -29,10 +29,8 @@ const AUTHORIZATION_TIMEOUT = 3000; const EXPIRATION_TIMEOUT = 1000 * 60 * 60 * 24 * 7; // 7 days const TAG = 'amp-access-iframe'; - /** @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessIframeAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -52,20 +50,23 @@ export class AccessIframeAdapter { this.timer_ = Services.timerFor(ampdoc.win); /** @const @private {string} */ - this.iframeSrc_ = userAssert(configJson['iframeSrc'], - '"iframeSrc" URL must be specified'); + this.iframeSrc_ = userAssert( + configJson['iframeSrc'], + '"iframeSrc" URL must be specified' + ); assertHttpsUrl(this.iframeSrc_, '"iframeSrc"'); /** @const @private {?Array} */ this.iframeVars_ = configJson['iframeVars'] || null; if (this.iframeVars_) { - userAssert(isArray(this.iframeVars_), - '"iframeVars" must be an array'); + userAssert(isArray(this.iframeVars_), '"iframeVars" must be an array'); } /** @const @private {!JsonObject} */ - this.defaultResponse_ = userAssert(configJson['defaultResponse'], - '"defaultResponse" must be specified'); + this.defaultResponse_ = userAssert( + configJson['defaultResponse'], + '"defaultResponse" must be specified' + ); /** @private @const {string} */ this.targetOrigin_ = parseUrlDeprecated(this.iframeSrc_).origin; @@ -82,9 +83,10 @@ export class AccessIframeAdapter { /** @private @const {!Messenger} */ this.messenger_ = new Messenger( - this.ampdoc.win, - () => this.iframe_.contentWindow, - this.targetOrigin_); + this.ampdoc.win, + () => this.iframe_.contentWindow, + this.targetOrigin_ + ); /** @private {?Promise} */ this.configPromise_ = null; @@ -113,10 +115,7 @@ export class AccessIframeAdapter { /** @override */ authorize() { - return Promise.race([ - this.authorizeLocal_(), - this.authorizeRemote_(), - ]); + return Promise.race([this.authorizeLocal_(), this.authorizeRemote_()]); } /** @override */ @@ -166,12 +165,15 @@ export class AccessIframeAdapter { if (this.iframeVars_) { const varsString = this.iframeVars_.join('&'); const varsPromise = this.context_.collectUrlVars( - varsString, - /* useAuthData */ false); - resolve(varsPromise.then(vars => { - configJson['iframeVars'] = vars; - return configJson; - })); + varsString, + /* useAuthData */ false + ); + resolve( + varsPromise.then(vars => { + configJson['iframeVars'] = vars; + return configJson; + }) + ); } else { resolve(configJson); } @@ -194,15 +196,17 @@ export class AccessIframeAdapter { * @private */ authorizeRemote_() { - return this.connect().then(() => { - return this.messenger_.sendCommandRsvp('authorize', {}); - }).then(data => { - if (data) { - // Store the value in a non-blocking microtask. - Promise.resolve().then(() => this.store_(data)); - } - return data; - }); + return this.connect() + .then(() => { + return this.messenger_.sendCommandRsvp('authorize', {}); + }) + .then(data => { + if (data) { + // Store the value in a non-blocking microtask. + Promise.resolve().then(() => this.store_(data)); + } + return data; + }); } /** @@ -222,7 +226,7 @@ export class AccessIframeAdapter { } const parsed = parseJson(raw); const time = parsed['t']; - if ((time + EXPIRATION_TIMEOUT) < this.ampdoc.win.Date.now()) { + if (time + EXPIRATION_TIMEOUT < this.ampdoc.win.Date.now()) { // Already expired. return null; } @@ -251,10 +255,15 @@ export class AccessIframeAdapter { } try { if (data) { - storage.setItem(TAG, JSON.stringify(dict({ - 't': this.ampdoc.win.Date.now(), - 'd': data, - }))); + storage.setItem( + TAG, + JSON.stringify( + dict({ + 't': this.ampdoc.win.Date.now(), + 'd': data, + }) + ) + ); } else { storage.removeItem(TAG); } @@ -273,16 +282,18 @@ export class AccessIframeAdapter { if (cmd == 'connect') { // First ever message. Indicates that the receiver is listening. this.configPromise_.then(configJson => { - this.messenger_.sendCommandRsvp('start', { - 'protocol': 'amp-access', - 'config': configJson, - }).then(() => { - // Confirmation that connection has been successful. - if (this.connectedResolver_) { - this.connectedResolver_(); - this.connectedResolver_ = null; - } - }); + this.messenger_ + .sendCommandRsvp('start', { + 'protocol': 'amp-access', + 'config': configJson, + }) + .then(() => { + // Confirmation that connection has been successful. + if (this.connectedResolver_) { + this.connectedResolver_(); + this.connectedResolver_ = null; + } + }); }); return; } diff --git a/extensions/amp-access/0.1/amp-access-other.js b/extensions/amp-access/0.1/amp-access-other.js index fb9c767b6fd66..f729b9c6194c2 100644 --- a/extensions/amp-access/0.1/amp-access-other.js +++ b/extensions/amp-access/0.1/amp-access-other.js @@ -20,10 +20,8 @@ import {isProxyOrigin} from '../../../src/url'; /** @const {string} */ const TAG = 'amp-access-other'; - /** @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessOtherAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -38,7 +36,7 @@ export class AccessOtherAdapter { /** @private {?JsonObject} */ this.authorizationResponse_ = - configJson['authorizationFallbackResponse'] || null; + configJson['authorizationFallbackResponse'] || null; /** @const @private {boolean} */ this.isProxyOrigin_ = isProxyOrigin(ampdoc.win.location); @@ -55,7 +53,7 @@ export class AccessOtherAdapter { isAuthorizationEnabled() { // The `type=other` is allowed to use the authorization fallback, but // only if it's not on `cdn.ampproject.org`. - return (!!this.authorizationResponse_ && !this.isProxyOrigin_); + return !!this.authorizationResponse_ && !this.isProxyOrigin_; } /** @override */ diff --git a/extensions/amp-access/0.1/amp-access-server-jwt.js b/extensions/amp-access/0.1/amp-access-server-jwt.js index ddd5ad6fa11b6..8079ef7b88c91 100644 --- a/extensions/amp-access/0.1/amp-access-server-jwt.js +++ b/extensions/amp-access/0.1/amp-access-server-jwt.js @@ -40,7 +40,6 @@ const AUTHORIZATION_TIMEOUT = 3000; /** @const {string} */ const AMP_AUD = 'ampproject.org'; - /** * This class implements server-side authorization protocol with JWT. In this * approach only immediately visible sections are downloaded. For authorization, @@ -75,7 +74,6 @@ const AMP_AUD = 'ampproject.org'; * @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessServerJwtAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -103,24 +101,27 @@ export class AccessServerJwtAdapter { /** @const @private {!../../../src/service/vsync-impl.Vsync} */ this.vsync_ = Services.vsyncFor(ampdoc.win); - const stateElement = ampdoc.getRootNode().querySelector( - 'meta[name="i-amphtml-access-state"]'); + const stateElement = ampdoc + .getRootNode() + .querySelector('meta[name="i-amphtml-access-state"]'); /** @private @const {?string} */ - this.serverState_ = stateElement ? - stateElement.getAttribute('content') : null; + this.serverState_ = stateElement + ? stateElement.getAttribute('content') + : null; const isInExperiment = isExperimentOn(ampdoc.win, 'amp-access-server-jwt'); /** @private @const {boolean} */ this.isProxyOrigin_ = isProxyOrigin(ampdoc.win.location) || isInExperiment; - const serviceUrlOverride = isInExperiment ? - this.viewer_.getParam('serverAccessService') : null; + const serviceUrlOverride = isInExperiment + ? this.viewer_.getParam('serverAccessService') + : null; /** @private @const {string} */ - this.serviceUrl_ = serviceUrlOverride || - removeFragment(ampdoc.win.location.href); + this.serviceUrl_ = + serviceUrlOverride || removeFragment(ampdoc.win.location.href); /** @const @private {?string} */ this.key_ = configJson['publicKey'] || null; @@ -128,16 +129,20 @@ export class AccessServerJwtAdapter { /** @const @private {?string} */ this.keyUrl_ = configJson['publicKeyUrl'] || null; - userAssert(this.key_ || this.keyUrl_, - '"publicKey" or "publicKeyUrl" must be specified'); + userAssert( + this.key_ || this.keyUrl_, + '"publicKey" or "publicKeyUrl" must be specified' + ); if (this.keyUrl_) { assertHttpsUrl(this.keyUrl_, '"publicKeyUrl"'); } if (this.key_ && this.keyUrl_) { // TODO(dvoytenko): Remove "publicKey" option eventually. - user().warn(TAG, - 'Both "publicKey" and "publicKeyUrl" specified. ' + - 'The "publicKeyUrl" will be ignored.'); + user().warn( + TAG, + 'Both "publicKey" and "publicKeyUrl" specified. ' + + 'The "publicKeyUrl" will be ignored.' + ); } /** @private @const {!JwtHelper} */ @@ -162,10 +167,13 @@ export class AccessServerJwtAdapter { /** @override */ authorize() { - dev().fine(TAG, 'Start authorization with ', - this.isProxyOrigin_ ? 'proxy' : 'non-proxy', - this.serverState_, - this.clientAdapter_.getAuthorizationUrl()); + dev().fine( + TAG, + 'Start authorization with ', + this.isProxyOrigin_ ? 'proxy' : 'non-proxy', + this.serverState_, + this.clientAdapter_.getAuthorizationUrl() + ); if (!this.isProxyOrigin_ || !this.serverState_) { return this.authorizeOnClient_(); } @@ -193,34 +201,44 @@ export class AccessServerJwtAdapter { */ fetchJwt_() { const urlPromise = this.context_.buildUrl( - this.clientAdapter_.getAuthorizationUrl(), - /* useAuthData */ false); - let jwtPromise = urlPromise.then(url => { - dev().fine(TAG, 'Authorization URL: ', url); - return this.timer_.timeoutPromise( + this.clientAdapter_.getAuthorizationUrl(), + /* useAuthData */ false + ); + let jwtPromise = urlPromise + .then(url => { + dev().fine(TAG, 'Authorization URL: ', url); + return this.timer_.timeoutPromise( AUTHORIZATION_TIMEOUT, this.xhr_.fetchText(url, { credentials: 'include', - })); - }).then(resp => { - return resp.text(); - }).then(encoded => { - const jwt = this.jwtHelper_.decode(encoded); - userAssert(jwt['amp_authdata'], - '"amp_authdata" must be present in JWT'); - return {encoded, jwt}; - }); + }) + ); + }) + .then(resp => { + return resp.text(); + }) + .then(encoded => { + const jwt = this.jwtHelper_.decode(encoded); + userAssert( + jwt['amp_authdata'], + '"amp_authdata" must be present in JWT' + ); + return {encoded, jwt}; + }); if (this.shouldBeValidated_()) { // Validate JWT in the development mode. if (this.jwtHelper_.isVerificationSupported()) { jwtPromise = jwtPromise.then(resp => { return this.jwtHelper_ - .decodeAndVerify(resp.encoded, this.loadKeyPem_()) - .then(() => resp); + .decodeAndVerify(resp.encoded, this.loadKeyPem_()) + .then(() => resp); }); } else { - user().warn(TAG, 'Cannot verify signature on this browser since' + - ' it doesn\'t support WebCrypto APIs'); + user().warn( + TAG, + 'Cannot verify signature on this browser since' + + " it doesn't support WebCrypto APIs" + ); } jwtPromise = jwtPromise.then(resp => { this.validateJwt_(resp.jwt); @@ -240,8 +258,9 @@ export class AccessServerJwtAdapter { if (this.key_) { return Promise.resolve(this.key_); } - return this.xhr_.fetchText(dev().assertString(this.keyUrl_)) - .then(res => res.text()); + return this.xhr_ + .fetchText(dev().assertString(this.keyUrl_)) + .then(res => res.text()); } /** @@ -262,8 +281,7 @@ export class AccessServerJwtAdapter { // exp: expiration time. const exp = jwt['exp']; userAssert(exp, '"exp" field must be specified'); - userAssert(parseFloat(exp) * 1000 > now, - 'token has expired: %s', exp); + userAssert(parseFloat(exp) * 1000 > now, 'token has expired: %s', exp); // aud: audience. const aud = jwt['aud']; @@ -277,7 +295,7 @@ export class AccessServerJwtAdapter { } } } else { - audForAmp = (aud == AMP_AUD); + audForAmp = aud == AMP_AUD; } userAssert(audForAmp, '"aud" must be "%s": %s', AMP_AUD, aud); } @@ -287,8 +305,11 @@ export class AccessServerJwtAdapter { * @private */ authorizeOnClient_() { - dev().fine(TAG, 'Proceed via client protocol via ', - this.clientAdapter_.getAuthorizationUrl()); + dev().fine( + TAG, + 'Proceed via client protocol via ', + this.clientAdapter_.getAuthorizationUrl() + ); return this.fetchJwt_().then(resp => { return resp.jwt['amp_authdata']; }); @@ -303,16 +324,19 @@ export class AccessServerJwtAdapter { return this.fetchJwt_().then(resp => { const {encoded, jwt} = resp; const accessData = jwt['amp_authdata']; - const request = serializeQueryString(dict({ - 'url': removeFragment(this.ampdoc.win.location.href), - 'state': this.serverState_, - 'jwt': encoded, - })); + const request = serializeQueryString( + dict({ + 'url': removeFragment(this.ampdoc.win.location.href), + 'state': this.serverState_, + 'jwt': encoded, + }) + ); dev().fine(TAG, 'Authorization request: ', this.serviceUrl_, request); dev().fine(TAG, '- access data: ', accessData); // Note that `application/x-www-form-urlencoded` is used to avoid // CORS preflight request. - return this.timer_.timeoutPromise( + return this.timer_ + .timeoutPromise( AUTHORIZATION_TIMEOUT, fetchDocument(this.ampdoc.win, this.serviceUrl_, { method: 'POST', @@ -321,10 +345,13 @@ export class AccessServerJwtAdapter { 'Content-Type': 'application/x-www-form-urlencoded', }), requireAmpResponseSourceOrigin: false, - })).then(response => { - dev().fine(TAG, 'Authorization response: ', response); - return this.replaceSections_(response); - }).then(() => accessData); + }) + ) + .then(response => { + dev().fine(TAG, 'Authorization response: ', response); + return this.replaceSections_(response); + }) + .then(() => accessData); }); } @@ -339,15 +366,19 @@ export class AccessServerJwtAdapter { for (let i = 0; i < sections.length; i++) { const section = sections[i]; const sectionId = section.getAttribute('i-amphtml-access-id'); - const target = this.ampdoc.getRootNode().querySelector( - `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]`); + const target = this.ampdoc + .getRootNode() + .querySelector( + `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]` + ); if (!target) { dev().warn(TAG, 'Section not found: ', sectionId); continue; } target.parentElement.replaceChild( - this.ampdoc.win.document.importNode(section, /* deep */ true), - target); + this.ampdoc.win.document.importNode(section, /* deep */ true), + target + ); } }); } diff --git a/extensions/amp-access/0.1/amp-access-server.js b/extensions/amp-access/0.1/amp-access-server.js index 4ffe4f62420b0..5a20caae8ed5a 100644 --- a/extensions/amp-access/0.1/amp-access-server.js +++ b/extensions/amp-access/0.1/amp-access-server.js @@ -27,7 +27,6 @@ import {parseJson} from '../../../src/json'; /** @const {string} */ const TAG = 'amp-access-server'; - /** * This class implements server-side authorization protocol. In this approach * only immediately visible sections are downloaded. For authorization, the @@ -58,7 +57,6 @@ const TAG = 'amp-access-server'; * @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessServerAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -86,24 +84,27 @@ export class AccessServerAdapter { /** @const @private {!../../../src/service/vsync-impl.Vsync} */ this.vsync_ = Services.vsyncFor(ampdoc.win); - const stateElement = ampdoc.getRootNode().querySelector( - 'meta[name="i-amphtml-access-state"]'); + const stateElement = ampdoc + .getRootNode() + .querySelector('meta[name="i-amphtml-access-state"]'); /** @private @const {?string} */ - this.serverState_ = stateElement ? - stateElement.getAttribute('content') : null; + this.serverState_ = stateElement + ? stateElement.getAttribute('content') + : null; const isInExperiment = isExperimentOn(ampdoc.win, 'amp-access-server'); /** @private @const {boolean} */ this.isProxyOrigin_ = isProxyOrigin(ampdoc.win.location) || isInExperiment; - const serviceUrlOverride = isInExperiment ? - this.viewer_.getParam('serverAccessService') : null; + const serviceUrlOverride = isInExperiment + ? this.viewer_.getParam('serverAccessService') + : null; /** @private @const {string} */ - this.serviceUrl_ = serviceUrlOverride || - removeFragment(ampdoc.win.location.href); + this.serviceUrl_ = + serviceUrlOverride || removeFragment(ampdoc.win.location.href); } /** @override */ @@ -122,10 +123,13 @@ export class AccessServerAdapter { /** @override */ authorize() { - dev().fine(TAG, 'Start authorization with ', - this.isProxyOrigin_ ? 'proxy' : 'non-proxy', - this.serverState_, - this.clientAdapter_.getAuthorizationUrl()); + dev().fine( + TAG, + 'Start authorization with ', + this.isProxyOrigin_ ? 'proxy' : 'non-proxy', + this.serverState_, + this.clientAdapter_.getAuthorizationUrl() + ); if (!this.isProxyOrigin_ || !this.serverState_) { dev().fine(TAG, 'Proceed via client protocol'); return this.clientAdapter_.authorize(); @@ -134,24 +138,26 @@ export class AccessServerAdapter { dev().fine(TAG, 'Proceed via server protocol'); const varsPromise = this.context_.collectUrlVars( - this.clientAdapter_.getAuthorizationUrl(), - /* useAuthData */ false); - return varsPromise.then(vars => { - const requestVars = {}; - for (const k in vars) { - if (vars[k] != null) { - requestVars[k] = String(vars[k]); + this.clientAdapter_.getAuthorizationUrl(), + /* useAuthData */ false + ); + return varsPromise + .then(vars => { + const requestVars = {}; + for (const k in vars) { + if (vars[k] != null) { + requestVars[k] = String(vars[k]); + } } - } - const request = dict({ - 'url': removeFragment(this.ampdoc.win.location.href), - 'state': this.serverState_, - 'vars': requestVars, - }); - dev().fine(TAG, 'Authorization request: ', this.serviceUrl_, request); - // Note that `application/x-www-form-urlencoded` is used to avoid - // CORS preflight request. - return this.timer_.timeoutPromise( + const request = dict({ + 'url': removeFragment(this.ampdoc.win.location.href), + 'state': this.serverState_, + 'vars': requestVars, + }); + dev().fine(TAG, 'Authorization request: ', this.serviceUrl_, request); + // Note that `application/x-www-form-urlencoded` is used to avoid + // CORS preflight request. + return this.timer_.timeoutPromise( this.clientAdapter_.getAuthorizationTimeout(), fetchDocument(this.ampdoc.win, this.serviceUrl_, { method: 'POST', @@ -160,19 +166,22 @@ export class AccessServerAdapter { 'Content-Type': 'application/x-www-form-urlencoded', }), requireAmpResponseSourceOrigin: false, - })); - }).then(responseDoc => { - dev().fine(TAG, 'Authorization response: ', responseDoc); - const accessDataString = devAssert( + }) + ); + }) + .then(responseDoc => { + dev().fine(TAG, 'Authorization response: ', responseDoc); + const accessDataString = devAssert( responseDoc.querySelector('script[id="amp-access-data"]'), - 'No authorization data available').textContent; - const accessData = parseJson(accessDataString); - dev().fine(TAG, '- access data: ', accessData); - - return this.replaceSections_(responseDoc).then(() => { - return accessData; + 'No authorization data available' + ).textContent; + const accessData = parseJson(accessDataString); + dev().fine(TAG, '- access data: ', accessData); + + return this.replaceSections_(responseDoc).then(() => { + return accessData; + }); }); - }); } /** @override */ @@ -201,15 +210,19 @@ export class AccessServerAdapter { for (let i = 0; i < sections.length; i++) { const section = sections[i]; const sectionId = section.getAttribute('i-amphtml-access-id'); - const target = this.ampdoc.getRootNode().querySelector( - `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]`); + const target = this.ampdoc + .getRootNode() + .querySelector( + `[i-amphtml-access-id="${escapeCssSelectorIdent(sectionId)}"]` + ); if (!target) { dev().warn(TAG, 'Section not found: ', sectionId); continue; } target.parentElement.replaceChild( - this.ampdoc.win.document.importNode(section, /* deep */ true), - target); + this.ampdoc.win.document.importNode(section, /* deep */ true), + target + ); } }); } diff --git a/extensions/amp-access/0.1/amp-access-source.js b/extensions/amp-access/0.1/amp-access-source.js index 67b7873b5c6ad..702318c76e204 100644 --- a/extensions/amp-access/0.1/amp-access-source.js +++ b/extensions/amp-access/0.1/amp-access-source.js @@ -22,10 +22,7 @@ import {AccessServerJwtAdapter} from './amp-access-server-jwt'; import {AccessVendorAdapter} from './amp-access-vendor'; import {Deferred} from '../../../src/utils/promise'; import {Services} from '../../../src/services'; -import { - assertHttpsUrl, - parseQueryString, -} from '../../../src/url'; +import {assertHttpsUrl, parseQueryString} from '../../../src/url'; import {dev, user, userAssert} from '../../../src/log'; import {dict} from '../../../src/utils/object'; import {getLoginUrl, openLoginDialog} from './login-dialog'; @@ -34,7 +31,6 @@ import {isExperimentOn} from '../../../src/experiments'; import {isObject} from '../../../src/types'; import {triggerAnalyticsEvent} from '../../../src/analytics'; - /** @const */ const TAG = 'amp-access'; @@ -50,7 +46,6 @@ export const AccessType = { OTHER: 'other', }; - /** * AccessSource represents a single source of authentication information for a * page. These sources are constructed, unified and attached to the document by @@ -65,9 +60,14 @@ export class AccessSource { * @param {function(!Promise)} onReauthorizeFn * @param {!Element} accessElement */ - constructor(ampdoc, configJson, readerIdFn, scheduleViewFn, - onReauthorizeFn, accessElement) { - + constructor( + ampdoc, + configJson, + readerIdFn, + scheduleViewFn, + onReauthorizeFn, + accessElement + ) { /** @const */ this.ampdoc = ampdoc; @@ -97,7 +97,7 @@ export class AccessSource { /** @const {?JsonObject} */ this.authorizationFallbackResponse_ = - configJson['authorizationFallbackResponse']; + configJson['authorizationFallbackResponse']; /** @const {?string} */ this.namespace_ = configJson['namespace'] || null; @@ -168,7 +168,7 @@ export class AccessSource { buildUrl: this.buildUrl.bind(this), collectUrlVars: this.collectUrlVars.bind(this), }); - const isJwt = (this.isJwtEnabled_ && configJson['jwt'] === true); + const isJwt = this.isJwtEnabled_ && configJson['jwt'] === true; switch (this.type_) { case AccessType.CLIENT: if (isJwt) { @@ -197,7 +197,6 @@ export class AccessSource { return this.adapter_.getConfig(); } - /** * @return {!Promise} Returns a promise for the initial authorization. */ @@ -210,9 +209,9 @@ export class AccessSource { * @return {!AccessType} */ buildConfigType_(configJson) { - let type = configJson['type'] ? - user().assertEnumValue(AccessType, configJson['type'], 'access type') : - null; + let type = configJson['type'] + ? user().assertEnumValue(AccessType, configJson['type'], 'access type') + : null; if (!type) { if (configJson['vendor']) { type = AccessType.VENDOR; @@ -228,8 +227,10 @@ export class AccessSource { user().info(TAG, 'Forcing access type: SERVER'); type = AccessType.SERVER; } - if (type == AccessType.IFRAME && - !isExperimentOn(this.ampdoc.win, 'amp-access-iframe')) { + if ( + type == AccessType.IFRAME && + !isExperimentOn(this.ampdoc.win, 'amp-access-iframe') + ) { user().error(TAG, 'Experiment "amp-access-iframe" is not enabled.'); type = AccessType.CLIENT; } @@ -253,8 +254,7 @@ export class AccessSource { loginMap[k] = loginConfig[k]; } } else { - userAssert(false, - '"login" must be either a single URL or a map of URLs'); + userAssert(false, '"login" must be either a single URL or a map of URLs'); } // Check that all URLs are valid. @@ -285,8 +285,13 @@ export class AccessSource { * Do some initial setup. */ start() { - dev().fine(TAG, 'config:', this.type_, this.loginConfig_, - this.adapter_.getConfig()); + dev().fine( + TAG, + 'config:', + this.type_, + this.loginConfig_, + this.adapter_.getConfig() + ); // Calculate login URLs right away. this.buildLoginUrls_(); @@ -350,30 +355,31 @@ export class AccessSource { return Promise.resolve(); } - const responsePromise = - this.adapter_.authorize().catch(error => { - this.analyticsEvent_('access-authorization-failed'); - if (this.authorizationFallbackResponse_ && !opt_disableFallback) { - // Use fallback. - user().error(TAG, 'Authorization failed: ', error); - return this.authorizationFallbackResponse_; - } else { - // Rethrow the error, it will be processed in the bottom `catch`. - throw error; - } - }); - - const promise = responsePromise.then(response => { - dev().fine(TAG, 'Authorization response: ', response); - this.setAuthResponse_(response); - this.buildLoginUrls_(); - return response; - }).catch(error => { - user().error(TAG, 'Authorization failed: ', error); - this.firstAuthorizationResolver_(); - throw error; + const responsePromise = this.adapter_.authorize().catch(error => { + this.analyticsEvent_('access-authorization-failed'); + if (this.authorizationFallbackResponse_ && !opt_disableFallback) { + // Use fallback. + user().error(TAG, 'Authorization failed: ', error); + return this.authorizationFallbackResponse_; + } else { + // Rethrow the error, it will be processed in the bottom `catch`. + throw error; + } }); + const promise = responsePromise + .then(response => { + dev().fine(TAG, 'Authorization response: ', response); + this.setAuthResponse_(response); + this.buildLoginUrls_(); + return response; + }) + .catch(error => { + user().error(TAG, 'Authorization failed: ', error); + this.firstAuthorizationResolver_(); + throw error; + }); + return promise; } @@ -390,13 +396,16 @@ export class AccessSource { * @return {!Promise} */ reportViewToServer() { - return this.adapter_.pingback().then(() => { - dev().fine(TAG, 'Pingback complete'); - this.analyticsEvent_('access-pingback-sent'); - }).catch(error => { - this.analyticsEvent_('access-pingback-failed'); - throw user().createError('Pingback failed: ', error); - }); + return this.adapter_ + .pingback() + .then(() => { + dev().fine(TAG, 'Pingback complete'); + this.analyticsEvent_('access-pingback-sent'); + }) + .catch(error => { + this.analyticsEvent_('access-pingback-failed'); + throw user().createError('Pingback failed: ', error); + }); } /** @@ -416,11 +425,17 @@ export class AccessSource { * @return {!Promise} */ loginWithType(type) { - userAssert(this.loginConfig_[type], - 'Login URL is not configured: %s', type); + userAssert( + this.loginConfig_[type], + 'Login URL is not configured: %s', + type + ); // Login URL should always be available at this time. - const loginUrl = userAssert(this.loginUrlMap_[type], - 'Login URL is not ready: %s', type); + const loginUrl = userAssert( + this.loginUrlMap_[type], + 'Login URL is not ready: %s', + type + ); return this.login_(loginUrl, type); } @@ -455,7 +470,7 @@ export class AccessSource { // 1 second, however, the new login request will be allowed to proceed, // given that we cannot always determine fully if the previous attempt is // "stuck". - if (this.loginPromise_ && (now - this.loginStartTime_ < 1000)) { + if (this.loginPromise_ && now - this.loginStartTime_ < 1000) { return this.loginPromise_; } @@ -463,38 +478,41 @@ export class AccessSource { this.loginAnalyticsEvent_(eventLabel, 'started'); const dialogPromise = this.openLoginDialog_(loginUrl); - const loginPromise = dialogPromise.then(result => { - dev().fine(TAG, 'Login dialog completed: ', eventLabel, result); - this.loginPromise_ = null; - const query = parseQueryString(result); - const s = query['success']; - const success = (s == 'true' || s == 'yes' || s == '1'); - if (success) { - this.loginAnalyticsEvent_(eventLabel, 'success'); - } else { - this.loginAnalyticsEvent_(eventLabel, 'rejected'); - } - if (success || !s) { - // In case of a success, repeat the authorization and pingback flows. - // Also do this for an empty response to avoid false negatives. - // Pingback is repeated in this case since this could now be a new - // "view" with a different access profile. - this.adapter_.postAction(); - const authorizationPromise = this.runAuthorization( - /* disableFallback */ true); - this.onReauthorize_(authorizationPromise); - return authorizationPromise.then(() => { - this.scheduleView_(/* timeToView */ 0); - }); - } - }).catch(reason => { - dev().fine(TAG, 'Login dialog failed: ', eventLabel, reason); - this.loginAnalyticsEvent_(eventLabel, 'failed'); - if (this.loginPromise_ == loginPromise) { + const loginPromise = dialogPromise + .then(result => { + dev().fine(TAG, 'Login dialog completed: ', eventLabel, result); this.loginPromise_ = null; - } - throw reason; - }); + const query = parseQueryString(result); + const s = query['success']; + const success = s == 'true' || s == 'yes' || s == '1'; + if (success) { + this.loginAnalyticsEvent_(eventLabel, 'success'); + } else { + this.loginAnalyticsEvent_(eventLabel, 'rejected'); + } + if (success || !s) { + // In case of a success, repeat the authorization and pingback flows. + // Also do this for an empty response to avoid false negatives. + // Pingback is repeated in this case since this could now be a new + // "view" with a different access profile. + this.adapter_.postAction(); + const authorizationPromise = this.runAuthorization( + /* disableFallback */ true + ); + this.onReauthorize_(authorizationPromise); + return authorizationPromise.then(() => { + this.scheduleView_(/* timeToView */ 0); + }); + } + }) + .catch(reason => { + dev().fine(TAG, 'Login dialog failed: ', eventLabel, reason); + this.loginAnalyticsEvent_(eventLabel, 'failed'); + if (this.loginPromise_ == loginPromise) { + this.loginPromise_ = null; + } + throw reason; + }); this.loginPromise_ = loginPromise; this.loginStartTime_ = now; return this.loginPromise_; @@ -523,11 +541,13 @@ export class AccessSource { const promises = []; for (const k in this.loginConfig_) { promises.push( - this.buildUrl(this.loginConfig_[k], /* useAuthData */ true) - .then(url => { - this.loginUrlMap_[k] = url; - return {type: k, url}; - })); + this.buildUrl(this.loginConfig_[k], /* useAuthData */ true).then( + url => { + this.loginUrlMap_[k] = url; + return {type: k, url}; + } + ) + ); } return Promise.all(promises); } @@ -542,12 +562,10 @@ export class AccessSource { */ export let AccessTypeAdapterContextDef; - /** * @interface */ export class AccessTypeAdapterDef { - /** * @return {!JsonObject} */ diff --git a/extensions/amp-access/0.1/amp-access-vendor.js b/extensions/amp-access/0.1/amp-access-vendor.js index bea0f630bfe05..21bb90daa1138 100644 --- a/extensions/amp-access/0.1/amp-access-vendor.js +++ b/extensions/amp-access/0.1/amp-access-vendor.js @@ -21,7 +21,6 @@ import {dev, userAssert} from '../../../src/log'; /** @const {string} */ const TAG = 'amp-access-vendor'; - /** * The adapter for a vendor implementation that implements `AccessVendor` * interface and delivered via a separate extension. The vendor implementation @@ -30,7 +29,6 @@ const TAG = 'amp-access-vendor'; * @implements {./amp-access-source.AccessTypeAdapterDef} */ export class AccessVendorAdapter { - /** * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {!JsonObject} configJson @@ -40,8 +38,10 @@ export class AccessVendorAdapter { this.ampdoc = ampdoc; /** @const @private {string} */ - this.vendorName_ = userAssert(configJson['vendor'], - '"vendor" name must be specified'); + this.vendorName_ = userAssert( + configJson['vendor'], + '"vendor" name must be specified' + ); /** @const @private {!JsonObject} */ this.vendorConfig_ = configJson[this.vendorName_] || {}; diff --git a/extensions/amp-access/0.1/amp-access.js b/extensions/amp-access/0.1/amp-access.js index ba3d60a18b75f..8dd477147c3ec 100644 --- a/extensions/amp-access/0.1/amp-access.js +++ b/extensions/amp-access/0.1/amp-access.js @@ -34,7 +34,6 @@ import {listenOnce} from '../../../src/event-helper'; import {startsWith} from '../../../src/string'; import {triggerAnalyticsEvent} from '../../../src/analytics'; - /** @const */ const TAG = 'amp-access'; @@ -44,7 +43,6 @@ const VIEW_TIMEOUT = 2000; /** @const {string} */ const TEMPLATE_PROP = '__AMP_ACCESS__TEMPLATE'; - /** * AccessService implements the complete lifecycle of the AMP Access system. * @implements {AccessVars} @@ -138,8 +136,9 @@ export class AccessService { }); // Re-authorize newly added sections. - ampdoc.getRootNode().addEventListener(AmpEvents.DOM_UPDATE, - this.onDomUpdate_.bind(this)); + ampdoc + .getRootNode() + .addEventListener(AmpEvents.DOM_UPDATE, this.onDomUpdate_.bind(this)); } /** @override from AccessVars */ @@ -159,8 +158,10 @@ export class AccessService { // No consent - an essential part of the access system. const consent = Promise.resolve(); this.readerIdPromise_ = this.cid_.then(cid => { - return cid.get({scope: 'amp-access', createCookieIfNotPresent: true}, - consent); + return cid.get( + {scope: 'amp-access', createCookieIfNotPresent: true}, + consent + ); }); } return this.readerIdPromise_; @@ -186,9 +187,11 @@ export class AccessService { * @private */ parseConfig_() { - userAssert(isJsonScriptTag(this.accessElement_), - `${TAG} config should ` + - 'be inside a '); - expect(creative).to.not.contain( - ''); - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-mustache'], - extensions: [], - }); + () => {} + ) + .then(buffer => Promise.resolve(utf8Decode(buffer))) + .then(creative => { + expect(creative).to.not.contain( + '' + ); + expect(creative).to.not.contain( + '' + ); + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-mustache'], + extensions: [], }); + }); }); }); @@ -152,18 +164,22 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => {

    {{foo}}

    `; - fetchTextMock.withArgs( + fetchTextMock + .withArgs( 'https://www-adzerk-com.cdn.ampproject.org/ad/s/www.adzerk.com/456', { mode: 'cors', method: 'GET', ampCors: false, credentials: 'omit', - }).returns(Promise.resolve( - { + } + ) + .returns( + Promise.resolve({ headers: {}, text: () => template, - })); + }) + ); }); it('should auto add amp-analytics if required', () => { @@ -171,7 +187,8 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { templateUrl: 'https://www.adzerk.com/456', analytics: {'type': 'googleanalytics'}, }; - return impl.maybeValidateAmpCreative( + return impl + .maybeValidateAmpCreative( utf8Encode(JSON.stringify(adResponseBody)).buffer, { get: name => { @@ -179,21 +196,22 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { return 'amp-mustache'; }, }, - () => {}) - .then(buffer => utf8Decode(buffer)) - .then(creative => { - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-analytics', 'amp-mustache'], - extensions: [], - }); - // Won't insert duplicate - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-analytics', 'amp-mustache'], - extensions: [], - }); + () => {} + ) + .then(buffer => utf8Decode(buffer)) + .then(creative => { + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-analytics', 'amp-mustache'], + extensions: [], + }); + // Won't insert duplicate + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-analytics', 'amp-mustache'], + extensions: [], }); + }); }); it('should not add amp-analytics if not', () => { @@ -201,7 +219,8 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { templateUrl: 'https://www.adzerk.com/456', analytics: undefined, }; - return impl.maybeValidateAmpCreative( + return impl + .maybeValidateAmpCreative( utf8Encode(JSON.stringify(adResponseBody)).buffer, { get: name => { @@ -209,15 +228,16 @@ describes.fakeWin('amp-ad-network-adzerk-impl', {amp: true}, env => { return 'amp-mustache'; }, }, - () => {}) - .then(buffer => utf8Decode(buffer)) - .then(creative => { - expect(impl.getAmpAdMetadata()).to.jsonEqual({ - minifiedCreative: creative, - customElementExtensions: ['amp-mustache'], - extensions: [], - }); + () => {} + ) + .then(buffer => utf8Decode(buffer)) + .then(creative => { + expect(impl.getAmpAdMetadata()).to.jsonEqual({ + minifiedCreative: creative, + customElementExtensions: ['amp-mustache'], + extensions: [], }); + }); }); }); }); diff --git a/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js b/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js index 1ec0377a134e6..533bfa488795c 100644 --- a/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js +++ b/extensions/amp-ad-network-cloudflare-impl/0.1/amp-ad-network-cloudflare-impl.js @@ -27,7 +27,6 @@ import {startsWith} from '../../../src/string'; * additional guidance on other implementation details. */ export class AmpAdNetworkCloudflareImpl extends AmpA4A { - /** * Validate the tag parameters. If invalid, ad ad will not be displayed. * @override @@ -103,10 +102,16 @@ export class AmpAdNetworkCloudflareImpl extends AmpA4A { let pre = url.indexOf('?') < 0 ? '?' : '&'; for (let i = 0; i < el.attributes.length; i++) { const attrib = el.attributes[i]; - if (attrib.specified && startsWith(attrib.name, 'data-') - && !startsWith(attrib.name, 'data-cf-')) { - url += pre + encodeURIComponent(attrib.name.substring(5)) + - '=' + encodeURIComponent(this.replacements(attrib.value, values)); + if ( + attrib.specified && + startsWith(attrib.name, 'data-') && + !startsWith(attrib.name, 'data-cf-') + ) { + url += + pre + + encodeURIComponent(attrib.name.substring(5)) + + '=' + + encodeURIComponent(this.replacements(attrib.value, values)); pre = '&'; } } @@ -115,8 +120,9 @@ export class AmpAdNetworkCloudflareImpl extends AmpA4A { } } - AMP.extension('amp-ad-network-cloudflare-impl', '0.1', AMP => { - AMP.registerElement('amp-ad-network-cloudflare-impl', - AmpAdNetworkCloudflareImpl); + AMP.registerElement( + 'amp-ad-network-cloudflare-impl', + AmpAdNetworkCloudflareImpl + ); }); diff --git a/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js b/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js index 7a017f27562a3..6f80ab193410b 100644 --- a/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js +++ b/extensions/amp-ad-network-cloudflare-impl/0.1/test/test-amp-ad-network-cloudflare-impl.js @@ -23,155 +23,168 @@ import { import {cloudflareIsA4AEnabled} from '../cloudflare-a4a-config'; import {createElementWithAttributes} from '../../../../src/dom'; - -describes.realWin('cloudflare-a4a-config', { - amp: { - extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], +describes.realWin( + 'cloudflare-a4a-config', + { + amp: { + extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], + }, }, -}, env => { - let doc; - let win; - beforeEach(() => { - win = env.win; - doc = env.win.document; - }); - it('should pass a4a config predicate', () => { - const el = createElementWithAttributes(doc, 'amp-ad', { - 'data-cf-network': 'cloudflare', - src: '/ad.html', - 'data-cf-a4a': 'true', - }); - expect(cloudflareIsA4AEnabled(win, el)).to.be.true; - }); - - it('should not pass a4a config predicate when useRemoteHtml is true', () => { - const el = createElementWithAttributes(doc, 'amp-ad', { - 'data-cf-network': 'cloudflare', - src: '/ad.html', - 'data-cf-a4a': 'true', + env => { + let doc; + let win; + beforeEach(() => { + win = env.win; + doc = env.win.document; }); - const useRemoteHtml = true; - expect(cloudflareIsA4AEnabled(win, el, useRemoteHtml)).to.be.false; - }); -}); - -describes.realWin('amp-ad-network-cloudflare-impl', { - amp: { - extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], - }, -}, env => { - - let cloudflareImpl; - let el; - let doc; - - beforeEach(() => { - doc = env.win.document; - el = doc.createElement('amp-ad'); - el.setAttribute('type', 'cloudflare'); - el.setAttribute('data-cf-network', 'cloudflare'); - el.setAttribute('src', - 'https://firebolt.cloudflaredemo.com/a4a-ad.html'); - sandbox.stub( - AmpAdNetworkCloudflareImpl.prototype, - 'getSigningServiceNames').callsFake( - () => { - return ['cloudflare','cloudflare-dev']; - }); - sandbox.stub(vendors, 'NETWORKS').callsFake({ - cloudflare: { - base: 'https://firebolt.cloudflaredemo.com', - }, - - 'cf-test': { - base: 'https://cf-test.com', - src: 'https://cf-test.com/path/ad?width=SLOT_WIDTH&height=SLOT_HEIGHT', - }, + it('should pass a4a config predicate', () => { + const el = createElementWithAttributes(doc, 'amp-ad', { + 'data-cf-network': 'cloudflare', + src: '/ad.html', + 'data-cf-a4a': 'true', + }); + expect(cloudflareIsA4AEnabled(win, el)).to.be.true; }); - sandbox.stub(el, 'tryUpgrade_').callsFake(() => {}); - doc.body.appendChild(el); - cloudflareImpl = new AmpAdNetworkCloudflareImpl(el); - }); - - describe('#isValidElement', () => { - it('should be valid', () => { - expect(cloudflareImpl.isValidElement()).to.be.true; + + it('should not pass a4a config predicate when useRemoteHtml is true', () => { + const el = createElementWithAttributes(doc, 'amp-ad', { + 'data-cf-network': 'cloudflare', + src: '/ad.html', + 'data-cf-a4a': 'true', + }); + const useRemoteHtml = true; + expect(cloudflareIsA4AEnabled(win, el, useRemoteHtml)).to.be.false; }); - it('should NOT be valid (impl tag name)', () => { - el = doc.createElement('amp-ad-network-cloudflare-impl'); + } +); + +describes.realWin( + 'amp-ad-network-cloudflare-impl', + { + amp: { + extensions: ['amp-ad', 'amp-ad-network-cloudflare-impl'], + }, + }, + env => { + let cloudflareImpl; + let el; + let doc; + + beforeEach(() => { + doc = env.win.document; + el = doc.createElement('amp-ad'); el.setAttribute('type', 'cloudflare'); + el.setAttribute('data-cf-network', 'cloudflare'); + el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/a4a-ad.html'); + sandbox + .stub(AmpAdNetworkCloudflareImpl.prototype, 'getSigningServiceNames') + .callsFake(() => { + return ['cloudflare', 'cloudflare-dev']; + }); + sandbox.stub(vendors, 'NETWORKS').callsFake({ + cloudflare: { + base: 'https://firebolt.cloudflaredemo.com', + }, + + 'cf-test': { + base: 'https://cf-test.com', + src: + 'https://cf-test.com/path/ad?width=SLOT_WIDTH&height=SLOT_HEIGHT', + }, + }); + sandbox.stub(el, 'tryUpgrade_').callsFake(() => {}); + doc.body.appendChild(el); cloudflareImpl = new AmpAdNetworkCloudflareImpl(el); - expect(cloudflareImpl.isValidElement()).to.be.false; }); - }); - describe('#getAdUrl', () => { - - it('should be valid', () => { - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html'); + describe('#isValidElement', () => { + it('should be valid', () => { + expect(cloudflareImpl.isValidElement()).to.be.true; + }); + it('should NOT be valid (impl tag name)', () => { + el = doc.createElement('amp-ad-network-cloudflare-impl'); + el.setAttribute('type', 'cloudflare'); + cloudflareImpl = new AmpAdNetworkCloudflareImpl(el); + expect(cloudflareImpl.isValidElement()).to.be.false; + }); }); - it('should handle non-a4a URLs', () => { - el.setAttribute('data-cf-a4a', 'false'); - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/a4a-ad.html'); - }); + describe('#getAdUrl', () => { + it('should be valid', () => { + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html' + ); + }); - it('should accept a4a src', () => { - el.setAttribute('src', - 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html'); - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html'); - }); + it('should handle non-a4a URLs', () => { + el.setAttribute('data-cf-a4a', 'false'); + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/a4a-ad.html' + ); + }); - it('should handle additional templated width/height', () => { - el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/' - + 'ad?width=SLOT_WIDTH&height=SLOT_HEIGHT'); - expect(cloudflareImpl.getAdUrl()).to.equal( - 'https://firebolt.cloudflaredemo.com/_a4a/ad?width=0&height=0'); - }); + it('should accept a4a src', () => { + el.setAttribute( + 'src', + 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html' + ); + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/_a4a/a4a-ad.html' + ); + }); - function parseQuery(query) { - const kvs = query.split(/&/); - const params = {}; - for (let i = 0; i < kvs.length; i++) { - const parts = kvs[i].match(/^([^=]+)=?(.*)/); - params[parts[1]] = parts[2]; + it('should handle additional templated width/height', () => { + el.setAttribute( + 'src', + 'https://firebolt.cloudflaredemo.com/' + + 'ad?width=SLOT_WIDTH&height=SLOT_HEIGHT' + ); + expect(cloudflareImpl.getAdUrl()).to.equal( + 'https://firebolt.cloudflaredemo.com/_a4a/ad?width=0&height=0' + ); + }); + + function parseQuery(query) { + const kvs = query.split(/&/); + const params = {}; + for (let i = 0; i < kvs.length; i++) { + const parts = kvs[i].match(/^([^=]+)=?(.*)/); + params[parts[1]] = parts[2]; + } + return params; } - return params; - } - - it('should handle data parameters', () => { - el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/ad'); - el.setAttribute('data-key', 'value'); - el.setAttribute('data-another', 'more'); - - const url = cloudflareImpl.getAdUrl(); - const base = 'https://firebolt.cloudflaredemo.com/_a4a/ad?'; - expect(url.substring(0, base.length)).to.equal(base); - expect(parseQuery(url.substring(base.length))).to.deep.equal({ - another: 'more', - key: 'value', + + it('should handle data parameters', () => { + el.setAttribute('src', 'https://firebolt.cloudflaredemo.com/ad'); + el.setAttribute('data-key', 'value'); + el.setAttribute('data-another', 'more'); + + const url = cloudflareImpl.getAdUrl(); + const base = 'https://firebolt.cloudflaredemo.com/_a4a/ad?'; + expect(url.substring(0, base.length)).to.equal(base); + expect(parseQuery(url.substring(base.length))).to.deep.equal({ + another: 'more', + key: 'value', + }); }); - }); - // TODO(bradfrizzell, #12476): Make this test work with sinon 4.0. - it.skip('should handle default src with data parameters', () => { - el.setAttribute('data-cf-network', 'cf-test'); - el.removeAttribute('src'); - el.setAttribute('data-key', 'value'); - el.setAttribute('data-another', 'more'); - - const url = cloudflareImpl.getAdUrl(); - const base = 'https://cf-test.com/_a4a/path/ad?'; - expect(url.substring(0, base.length)).to.equal(base); - expect(parseQuery(url.substring(base.length))).to.deep.equal({ - another: 'more', - height: '0', - key: 'value', - width: '0', + // TODO(bradfrizzell, #12476): Make this test work with sinon 4.0. + it.skip('should handle default src with data parameters', () => { + el.setAttribute('data-cf-network', 'cf-test'); + el.removeAttribute('src'); + el.setAttribute('data-key', 'value'); + el.setAttribute('data-another', 'more'); + + const url = cloudflareImpl.getAdUrl(); + const base = 'https://cf-test.com/_a4a/path/ad?'; + expect(url.substring(0, base.length)).to.equal(base); + expect(parseQuery(url.substring(base.length))).to.deep.equal({ + another: 'more', + height: '0', + key: 'value', + width: '0', + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js b/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js index e3d33c6174f61..45fc3064c6f4b 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js @@ -106,7 +106,7 @@ const TAG = 'amp-ad-network-doubleclick-impl'; /** @const {string} */ const DOUBLECLICK_BASE_URL = - 'https://securepubads.g.doubleclick.net/gampad/ads'; + 'https://securepubads.g.doubleclick.net/gampad/ads'; /** @const {string} */ const RTC_SUCCESS = '2'; @@ -159,7 +159,6 @@ let LayoutRectOrDimsDef; /** @final */ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { - /** * @param {!Element} element */ @@ -252,8 +251,11 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { this.forceSafeframe = false; if ('forceSafeframe' in this.element.dataset) { if (!/^(1|(true))$/i.test(this.element.dataset['forceSafeframe'])) { - user().warn(TAG, 'Ignoring invalid data-force-safeframe attribute: ' + - this.element.dataset['forceSafeframe']); + user().warn( + TAG, + 'Ignoring invalid data-force-safeframe attribute: ' + + this.element.dataset['forceSafeframe'] + ); } else { this.forceSafeframe = true; } @@ -322,8 +324,9 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // being schedule due to being beyond viewport max offset. If slot // comes within standard outside viewport range, then ensure throttling // will not be applied. - this.getResource().whenWithinViewport(renderOutsideViewport).then( - () => this.isIdleRender_ = false); + this.getResource() + .whenWithinViewport(renderOutsideViewport) + .then(() => (this.isIdleRender_ = false)); return vpRange; } @@ -346,8 +349,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * @visibleForTesting */ setPageLevelExperiments(urlExperimentId) { - if (!isCdnProxy(this.win) && !isExperimentOn( - this.win, 'expDfpInvOrigDeprecated')) { + if ( + !isCdnProxy(this.win) && + !isExperimentOn(this.win, 'expDfpInvOrigDeprecated') + ) { this.experimentIds.push('21060933'); } let forcedExperimentId; @@ -357,41 +362,44 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { '7': DOUBLECLICK_SRA_EXP_BRANCHES.SRA_CONTROL, '8': DOUBLECLICK_SRA_EXP_BRANCHES.SRA, '9': DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER, - }[urlExperimentId]; if (forcedExperimentId) { this.experimentIds.push(forcedExperimentId); } } - const experimentInfoMap = - /** @type {!Object} */ ({ - // Only select into SRA experiments if SRA not already explicitly - // enabled and refresh is not being used by any slot. - [DOUBLECLICK_SRA_EXP]: { - isTrafficEligible: () => !forcedExperimentId && - !this.win.document./*OK*/querySelector( - 'meta[name=amp-ad-enable-refresh], ' + - 'amp-ad[type=doubleclick][data-enable-refresh], ' + - 'meta[name=amp-ad-doubleclick-sra]'), - branches: Object.keys(DOUBLECLICK_SRA_EXP_BRANCHES).map( - key => DOUBLECLICK_SRA_EXP_BRANCHES[key]), - }, - [FLEXIBLE_AD_SLOTS_EXP]: { - isTrafficEligible: () => true, - branches: Object.values(FLEXIBLE_AD_SLOTS_BRANCHES), - }, - [[ADX_ADY_EXP.branch]]: { - isTrafficEligible: () => true, - branches: [[ADX_ADY_EXP.control], [ADX_ADY_EXP.experiment]], - }, - }); + // Only select into SRA experiments if SRA not already explicitly + // enabled and refresh is not being used by any slot. + [DOUBLECLICK_SRA_EXP]: { + isTrafficEligible: () => + !forcedExperimentId && + !this.win.document./*OK*/ querySelector( + 'meta[name=amp-ad-enable-refresh], ' + + 'amp-ad[type=doubleclick][data-enable-refresh], ' + + 'meta[name=amp-ad-doubleclick-sra]' + ), + branches: Object.keys(DOUBLECLICK_SRA_EXP_BRANCHES).map( + key => DOUBLECLICK_SRA_EXP_BRANCHES[key] + ), + }, + [FLEXIBLE_AD_SLOTS_EXP]: { + isTrafficEligible: () => true, + branches: Object.values(FLEXIBLE_AD_SLOTS_BRANCHES), + }, + [[ADX_ADY_EXP.branch]]: { + isTrafficEligible: () => true, + branches: [[ADX_ADY_EXP.control], [ADX_ADY_EXP.experiment]], + }, + }); const setExps = this.randomlySelectUnsetExperiments_(experimentInfoMap); - Object.keys(setExps).forEach(expName => - setExps[expName] && this.experimentIds.push(setExps[expName])); - if (setExps[FLEXIBLE_AD_SLOTS_EXP] && - setExps[FLEXIBLE_AD_SLOTS_EXP] == - FLEXIBLE_AD_SLOTS_BRANCHES.EXPERIMENT) { + Object.keys(setExps).forEach( + expName => setExps[expName] && this.experimentIds.push(setExps[expName]) + ); + if ( + setExps[FLEXIBLE_AD_SLOTS_EXP] && + setExps[FLEXIBLE_AD_SLOTS_EXP] == FLEXIBLE_AD_SLOTS_BRANCHES.EXPERIMENT + ) { this.sendFlexibleAdSlotParams_ = true; } } @@ -415,19 +423,24 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { /** @private */ maybeDeprecationWarn_() { - const warnDeprecation = feature => user().warn( - TAG, `${feature} is no longer supported for DoubleClick.` + + const warnDeprecation = feature => + user().warn( + TAG, + `${feature} is no longer supported for DoubleClick.` + 'Please refer to ' + 'https://github.com/ampproject/amphtml/issues/11834 ' + - 'for more information'); + 'for more information' + ); const usdrd = 'useSameDomainRenderingUntilDeprecated'; - const hasUSDRD = usdrd in this.element.dataset || - (tryParseJson(this.element.getAttribute('json')) || {})[usdrd]; + const hasUSDRD = + usdrd in this.element.dataset || + (tryParseJson(this.element.getAttribute('json')) || {})[usdrd]; if (hasUSDRD) { warnDeprecation(usdrd); } - const useRemoteHtml = - !!this.win.document.querySelector('meta[name=amp-3p-iframe-src]'); + const useRemoteHtml = !!this.win.document.querySelector( + 'meta[name=amp-3p-iframe-src]' + ); if (useRemoteHtml) { warnDeprecation('remote.html'); } @@ -438,24 +451,27 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { super.buildCallback(); this.maybeDeprecationWarn_(); this.setPageLevelExperiments(this.extractUrlExperimentId_()); - this.useSra = (getMode().localDev && /(\?|&)force_sra=true(&|$)/.test( - this.win.location.search)) || - !!this.win.document.querySelector( - 'meta[name=amp-ad-doubleclick-sra]') || - [DOUBLECLICK_SRA_EXP_BRANCHES.SRA, - DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER].some( - eid => this.experimentIds.indexOf(eid) >= 0); + this.useSra = + (getMode().localDev && + /(\?|&)force_sra=true(&|$)/.test(this.win.location.search)) || + !!this.win.document.querySelector('meta[name=amp-ad-doubleclick-sra]') || + [ + DOUBLECLICK_SRA_EXP_BRANCHES.SRA, + DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER, + ].some(eid => this.experimentIds.indexOf(eid) >= 0); this.identityTokenPromise_ = Services.viewerForDoc(this.getAmpDoc()) - .whenFirstVisible().then(() => - getIdentityToken( - this.win, this.getAmpDoc(), super.getConsentPolicy())); + .whenFirstVisible() + .then(() => + getIdentityToken(this.win, this.getAmpDoc(), super.getConsentPolicy()) + ); this.troubleshootData_.slotId = this.element.getAttribute('data-slot'); - this.troubleshootData_.slotIndex = - this.element.getAttribute('data-amp-slot-index'); + this.troubleshootData_.slotIndex = this.element.getAttribute( + 'data-amp-slot-index' + ); if (!this.isFluidRequest_) { const multiSizeStr = this.element.getAttribute('data-multi-size'); - this.isFluidRequest_ = !!multiSizeStr && - multiSizeStr.indexOf('fluid') != -1; + this.isFluidRequest_ = + !!multiSizeStr && multiSizeStr.indexOf('fluid') != -1; } this.maybeAddSinglePassExperiment(); } @@ -476,8 +492,11 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { instances = instances || [this]; const tokens = getPageviewStateTokensForAdRequest(instances); return { - 'npa': consentState == CONSENT_POLICY_STATE.INSUFFICIENT || - consentState == CONSENT_POLICY_STATE.UNKNOWN ? 1 : null, + 'npa': + consentState == CONSENT_POLICY_STATE.INSUFFICIENT || + consentState == CONSENT_POLICY_STATE.UNKNOWN + ? 1 + : null, 'gdfp_req': '1', 'sfv': DEFAULT_SAFEFRAME_VERSION, 'u_sd': this.win.devicePixelRatio, @@ -496,47 +515,58 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { devAssert(this.jsonTargeting); const tfcd = this.jsonTargeting && this.jsonTargeting[TFCD]; this.win['ampAdGoogleIfiCounter'] = this.win['ampAdGoogleIfiCounter'] || 1; - this.ifi_ = (this.isRefreshing && this.ifi_) || - this.win['ampAdGoogleIfiCounter']++; - const pageLayoutBox = this.isSinglePageStoryAd ? - this.element.getPageLayoutBox() : null; + this.ifi_ = + (this.isRefreshing && this.ifi_) || this.win['ampAdGoogleIfiCounter']++; + const pageLayoutBox = this.isSinglePageStoryAd + ? this.element.getPageLayoutBox() + : null; let psz = null; let msz = null; if (this.sendFlexibleAdSlotParams_) { const parentWidth = getContainerWidth( - this.win, this.element.parentElement); + this.win, + this.element.parentElement + ); let slotWidth = getContainerWidth( - this.win, this.element, 1 /* maxDepth */); + this.win, + this.element, + 1 /* maxDepth */ + ); slotWidth = slotWidth == -1 ? parentWidth : slotWidth; psz = `${parentWidth}x-1`; msz = `${slotWidth}x-1`; } - return Object.assign({ - 'iu': this.element.getAttribute('data-slot'), - 'co': this.jsonTargeting && - this.jsonTargeting['cookieOptOut'] ? '1' : null, - 'adk': this.adKey, - 'sz': this.isSinglePageStoryAd ? '1x1' : this.parameterSize, - 'output': 'html', - 'impl': 'ifr', - 'tfcd': tfcd == undefined ? null : tfcd, - 'adtest': isInManualExperiment(this.element) ? 'on' : null, - 'ifi': this.ifi_, - 'rc': this.refreshCount_ || null, - 'frc': Number(this.fromResumeCallback) || null, - 'fluid': this.isFluidRequest_ ? 'height' : null, - 'fsf': this.forceSafeframe ? '1' : null, - // Both msz/psz send a height of -1 because height expansion is - // disallowed in AMP. - 'msz': msz, - 'psz': psz, - 'scp': serializeTargeting( + return Object.assign( + { + 'iu': this.element.getAttribute('data-slot'), + 'co': + this.jsonTargeting && this.jsonTargeting['cookieOptOut'] ? '1' : null, + 'adk': this.adKey, + 'sz': this.isSinglePageStoryAd ? '1x1' : this.parameterSize, + 'output': 'html', + 'impl': 'ifr', + 'tfcd': tfcd == undefined ? null : tfcd, + 'adtest': isInManualExperiment(this.element) ? 'on' : null, + 'ifi': this.ifi_, + 'rc': this.refreshCount_ || null, + 'frc': Number(this.fromResumeCallback) || null, + 'fluid': this.isFluidRequest_ ? 'height' : null, + 'fsf': this.forceSafeframe ? '1' : null, + // Both msz/psz send a height of -1 because height expansion is + // disallowed in AMP. + 'msz': msz, + 'psz': psz, + 'scp': serializeTargeting( (this.jsonTargeting && this.jsonTargeting['targeting']) || null, - (this.jsonTargeting && - this.jsonTargeting['categoryExclusions']) || null), - 'spsa': this.isSinglePageStoryAd ? - `${pageLayoutBox.width}x${pageLayoutBox.height}` : null, - }, googleBlockParameters(this)); + (this.jsonTargeting && this.jsonTargeting['categoryExclusions']) || + null + ), + 'spsa': this.isSinglePageStoryAd + ? `${pageLayoutBox.width}x${pageLayoutBox.height}` + : null, + }, + googleBlockParameters(this) + ); } /** @@ -547,38 +577,42 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { populateAdUrlState(consentState) { this.consentState = consentState; // Allow for pub to override height/width via override attribute. - const width = Number(this.element.getAttribute('data-override-width')) || + const width = + Number(this.element.getAttribute('data-override-width')) || Number(this.element.getAttribute('width')); - const height = Number(this.element.getAttribute('data-override-height')) || + const height = + Number(this.element.getAttribute('data-override-height')) || Number(this.element.getAttribute('height')); - this.initialSize_ = this.isFluidPrimaryRequest_ ? {width: 0, height: 0} : - (width && height ? - // width/height could be 'auto' in which case we fallback to measured. - {width, height} : this.getIntersectionElementLayoutBox()); - this.jsonTargeting = - tryParseJson(this.element.getAttribute('json')) || {}; + this.initialSize_ = this.isFluidPrimaryRequest_ + ? {width: 0, height: 0} + : width && height + ? // width/height could be 'auto' in which case we fallback to measured. + {width, height} + : this.getIntersectionElementLayoutBox(); + this.jsonTargeting = tryParseJson(this.element.getAttribute('json')) || {}; this.adKey = this.generateAdKey_( - `${this.initialSize_.width}x${this.initialSize_.height}`); + `${this.initialSize_.width}x${this.initialSize_.height}` + ); this.parameterSize = this.isFluidPrimaryRequest_ ? DUMMY_FLUID_SIZE : `${this.initialSize_.width}x${this.initialSize_.height}`; const multiSizeDataStr = this.element.getAttribute('data-multi-size'); if (multiSizeDataStr) { - const multiSizeValidation = this.element - .getAttribute('data-multi-size-validation') || 'true'; + const multiSizeValidation = + this.element.getAttribute('data-multi-size-validation') || 'true'; // The following call will check all specified multi-size dimensions, // verify that they meet all requirements, and then return all the valid // dimensions in an array. const dimensions = getMultiSizeDimensions( - multiSizeDataStr, - this.initialSize_.width, - this.initialSize_.height, - multiSizeValidation == 'true', - this.isFluidPrimaryRequest_); + multiSizeDataStr, + this.initialSize_.width, + this.initialSize_.height, + multiSizeValidation == 'true', + this.isFluidPrimaryRequest_ + ); if (dimensions.length) { - this.parameterSize += '|' + dimensions - .map(dimension => dimension.join('x')) - .join('|'); + this.parameterSize += + '|' + dimensions.map(dimension => dimension.join('x')).join('|'); } } } @@ -595,8 +629,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (this.useSra) { this.sraDeferred = this.sraDeferred || new Deferred(); } - if (consentState == CONSENT_POLICY_STATE.UNKNOWN && - this.element.getAttribute('data-npa-on-unknown-consent') != 'true') { + if ( + consentState == CONSENT_POLICY_STATE.UNKNOWN && + this.element.getAttribute('data-npa-on-unknown-consent') != 'true' + ) { user().info(TAG, 'Ad request suppressed due to unknown consent'); this.getAdUrlDeferred.resolve(''); return Promise.resolve(''); @@ -615,24 +651,29 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // validateData, from 3p/3p/js, after noving it someplace common. const startTime = Date.now(); const identityPromise = Services.timerFor(this.win) - .timeoutPromise(1000, this.identityTokenPromise_) - .catch(() => { - // On error/timeout, proceed. - return /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({}); - }); + .timeoutPromise(1000, this.identityTokenPromise_) + .catch(() => { + // On error/timeout, proceed. + return /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/ ({}); + }); const checkStillCurrent = this.verifyStillCurrent(); - Promise.all([opt_rtcResponsesPromise, identityPromise]) - .then(results => { - checkStillCurrent(); - const rtcParams = this.mergeRtcResponses_(results[0]); - this.identityToken = results[1]; - googleAdUrl( - this, DOUBLECLICK_BASE_URL, startTime, Object.assign( - this.getBlockParameters_(), this.buildIdentityParams(), - this.getPageParameters(consentState), rtcParams), - this.experimentIds) - .then(adUrl => this.getAdUrlDeferred.resolve(adUrl)); - }); + Promise.all([opt_rtcResponsesPromise, identityPromise]).then(results => { + checkStillCurrent(); + const rtcParams = this.mergeRtcResponses_(results[0]); + this.identityToken = results[1]; + googleAdUrl( + this, + DOUBLECLICK_BASE_URL, + startTime, + Object.assign( + this.getBlockParameters_(), + this.buildIdentityParams(), + this.getPageParameters(consentState), + rtcParams + ), + this.experimentIds + ).then(adUrl => this.getAdUrlDeferred.resolve(adUrl)); + }); this.troubleshootData_.adUrl = this.getAdUrlDeferred.promise; return this.getAdUrlDeferred.promise; } @@ -642,11 +683,13 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * @return {!Object} */ buildIdentityParams() { - return this.identityToken ? { - adsid: this.identityToken.token || null, - jar: this.identityToken.jar || null, - pucrd: this.identityToken.pucrd || null, - } : {}; + return this.identityToken + ? { + adsid: this.identityToken.token || null, + jar: this.identityToken.jar || null, + pucrd: this.identityToken.pucrd || null, + } + : {}; } /** @@ -673,13 +716,12 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (rtcResponse.response) { if (rtcResponse.response['targeting']) { const rewrittenResponse = this.rewriteRtcKeys_( - rtcResponse.response['targeting'], - rtcResponse.callout); - this.jsonTargeting['targeting'] = - !!this.jsonTargeting['targeting'] ? - deepMerge(this.jsonTargeting['targeting'], - rewrittenResponse) : - rewrittenResponse; + rtcResponse.response['targeting'], + rtcResponse.callout + ); + this.jsonTargeting['targeting'] = !!this.jsonTargeting['targeting'] + ? deepMerge(this.jsonTargeting['targeting'], rewrittenResponse) + : rewrittenResponse; } if (rtcResponse.response['categoryExclusions']) { if (!exclusions) { @@ -724,11 +766,15 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { REFERRER: opt_timeout => this.getReferrer_(opt_timeout), TGT: () => JSON.stringify( - (tryParseJson( - this.element.getAttribute('json')) || {})['targeting']), - ADCID: opt_timeout => getOrCreateAdCid( - this.getAmpDoc(), 'AMP_ECID_GOOGLE', '_ga', - parseInt(opt_timeout, 10)), + (tryParseJson(this.element.getAttribute('json')) || {})['targeting'] + ), + ADCID: opt_timeout => + getOrCreateAdCid( + this.getAmpDoc(), + 'AMP_ECID_GOOGLE', + '_ga', + parseInt(opt_timeout, 10) + ), ATTR: name => { if (!whitelist[name.toLowerCase()]) { dev().warn('TAG', `Invalid attribute ${name}`); @@ -751,14 +797,15 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { */ getReferrer_(opt_timeout) { const timeoutInt = parseInt(opt_timeout, 10); - const referrerPromise = Services.viewerForDoc(this.getAmpDoc()) - .getReferrerUrl(); + const referrerPromise = Services.viewerForDoc( + this.getAmpDoc() + ).getReferrerUrl(); if (isNaN(timeoutInt) || timeoutInt < 0) { return referrerPromise; } return Services.timerFor(this.win) - .timeoutPromise(timeoutInt, referrerPromise) - .catch(() => undefined); + .timeoutPromise(timeoutInt, referrerPromise) + .catch(() => undefined); } /** @@ -794,7 +841,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } const checksum = headers.get('AMP-Verification-Checksum'); return Promise.resolve( - checksum && stringHash32(utf8Decode(bytes)) == checksum ? bytes : null); + checksum && stringHash32(utf8Decode(bytes)) == checksum ? bytes : null + ); } /** @override */ @@ -802,14 +850,18 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { this.ampAnalyticsConfig_ = extractAmpAnalyticsConfig(this, responseHeaders); this.qqid_ = responseHeaders.get(QQID_HEADER); this.shouldSandbox_ = responseHeaders.get(SANDBOX_HEADER) == 'true'; - this.troubleshootData_.creativeId = - dev().assertString(responseHeaders.get('google-creative-id') || '-1'); - this.troubleshootData_.lineItemId = - dev().assertString(responseHeaders.get('google-lineitem-id') || '-1'); + this.troubleshootData_.creativeId = dev().assertString( + responseHeaders.get('google-creative-id') || '-1' + ); + this.troubleshootData_.lineItemId = dev().assertString( + responseHeaders.get('google-lineitem-id') || '-1' + ); if (this.ampAnalyticsConfig_) { // Load amp-analytics extensions - this.extensions_./*OK*/installExtensionForDoc( - this.getAmpDoc(), 'amp-analytics'); + this.extensions_./*OK*/ installExtensionForDoc( + this.getAmpDoc(), + 'amp-analytics' + ); } // If the server returned a size, use that, otherwise use the size that we // sent in the ad request. @@ -831,7 +883,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (responseHeaders.get('amp-ff-pageview-tokens')) { this.removePageviewStateToken(); this.setPageviewStateToken( - dev().assertString(responseHeaders.get('amp-ff-pageview-tokens'))); + dev().assertString(responseHeaders.get('amp-ff-pageview-tokens')) + ); } return size; @@ -853,15 +906,17 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { const height = Number(this.element.getAttribute('height')); return width && height ? {width, height} - // width/height could be 'auto' in which case we fallback to measured. - : this.getIntersectionElementLayoutBox(); + : // width/height could be 'auto' in which case we fallback to measured. + this.getIntersectionElementLayoutBox(); } /** @override */ tearDownSlot() { super.tearDownSlot(); - this.element.setAttribute('data-amp-slot-index', - this.win.ampAdSlotIdCounter++); + this.element.setAttribute( + 'data-amp-slot-index', + this.win.ampAdSlotIdCounter++ + ); if (this.ampAnalyticsElement_) { removeElement(this.ampAnalyticsElement_); this.ampAnalyticsElement_ = null; @@ -891,8 +946,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // non-AMP creatives. This is not done in the scheduler to ensure as many // slots as possible are marked for layout given scheduler imposes 5 seconds // past previous execution. - if (this.postAdResponseExperimentFeatures['render-idle-throttle'] && - this.isIdleRender_) { + if ( + this.postAdResponseExperimentFeatures['render-idle-throttle'] && + this.isIdleRender_ + ) { if (is3pThrottled(this.win)) { return waitFor3pThrottle().then(() => super.renderNonAmpCreative()); } else { @@ -951,27 +1008,35 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { onCreativeRender(creativeMetaData, opt_onLoadPromise) { super.onCreativeRender(creativeMetaData); this.isAmpCreative_ = !!creativeMetaData; - if (creativeMetaData && - !creativeMetaData.customElementExtensions.includes('amp-ad-exit')) { + if ( + creativeMetaData && + !creativeMetaData.customElementExtensions.includes('amp-ad-exit') + ) { // Capture phase click handlers on the ad if amp-ad-exit not present // (assume it will handle capture). devAssert(this.iframe); Navigation.installAnchorClickInterceptor( - this.getAmpDoc(), this.iframe.contentWindow); + this.getAmpDoc(), + this.iframe.contentWindow + ); } if (this.ampAnalyticsConfig_) { devAssert(!this.ampAnalyticsElement_); if (isReportingEnabled(this)) { addCsiSignalsToAmpAnalyticsConfig( - this.win, - this.element, - this.ampAnalyticsConfig_, - this.qqid_, - !!creativeMetaData); + this.win, + this.element, + this.ampAnalyticsConfig_, + this.qqid_, + !!creativeMetaData + ); } this.ampAnalyticsElement_ = insertAnalyticsElement( - this.element, this.ampAnalyticsConfig_, /*loadAnalytics*/ true, - !!this.postAdResponseExperimentFeatures['avr_disable_immediate']); + this.element, + this.ampAnalyticsConfig_, + /*loadAnalytics*/ true, + !!this.postAdResponseExperimentFeatures['avr_disable_immediate'] + ); } if (this.isRefreshing) { devAssert(this.refreshManager_); @@ -984,11 +1049,13 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // the slot. This ensures that the creative is centered in the former case, // and not truncated in the latter. const size = this.returnedSize_ || this.getSlotSize(); - const isMultiSizeFluid = this.isFluidRequest_ && this.returnedSize_ && - // TODO(@glevitzky, 11583) Remove this clause once we stop sending back - // the size header for fluid ads. Fluid size headers always come back as - // 0x0. - !(size.width == 0 && size.height == 0); + const isMultiSizeFluid = + this.isFluidRequest_ && + this.returnedSize_ && + // TODO(@glevitzky, 11583) Remove this clause once we stop sending back + // the size header for fluid ads. Fluid size headers always come back as + // 0x0. + !(size.width == 0 && size.height == 0); setStyles(dev().assertElement(this.iframe), { width: `${size.width}px`, height: `${size.height}px`, @@ -1012,22 +1079,29 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { }); } - this.refreshManager_ = this.refreshManager_ || - getRefreshManager(this, () => { - if (this.useSra) { - user().warn(TAG, 'Refresh not compatible with SRA.'); - return false; - } - if (getEnclosingContainerTypes(this.element).filter(container => - container != ValidAdContainerTypes['AMP-CAROUSEL'] && - container != ValidAdContainerTypes['AMP-STICKY-AD']).length) { - user().warn(TAG, - 'Refresh not compatible with ad-containers, except for ' + - 'AMP-CAROUSEL and AMP-STICKY-AD'); - return false; - } - return true; - }); + this.refreshManager_ = + this.refreshManager_ || + getRefreshManager(this, () => { + if (this.useSra) { + user().warn(TAG, 'Refresh not compatible with SRA.'); + return false; + } + if ( + getEnclosingContainerTypes(this.element).filter( + container => + container != ValidAdContainerTypes['AMP-CAROUSEL'] && + container != ValidAdContainerTypes['AMP-STICKY-AD'] + ).length + ) { + user().warn( + TAG, + 'Refresh not compatible with ad-containers, except for ' + + 'AMP-CAROUSEL and AMP-STICKY-AD' + ); + return false; + } + return true; + }); this.postTroubleshootMessage(); } @@ -1043,30 +1117,39 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * testing. */ expandFluidCreative_() { - if (this.isFluidRequest_ && - // If a size was returned in the response, then this is a multi-size - // response, not a fluid response. - !this.returnedSize_ && - this.isVerifiedAmpCreative()) { + if ( + this.isFluidRequest_ && + // If a size was returned in the response, then this is a multi-size + // response, not a fluid response. + !this.returnedSize_ && + this.isVerifiedAmpCreative() + ) { // This is an AMP fluid creative that will be rendered in a friendly // frame. - if (!this.iframe || !this.iframe.contentWindow || - !this.iframe.contentWindow.document || - !this.iframe.contentWindow.document.body) { - dev().error(TAG, 'Attempting to expand fluid creative without ' + + if ( + !this.iframe || + !this.iframe.contentWindow || + !this.iframe.contentWindow.document || + !this.iframe.contentWindow.document.body + ) { + dev().error( + TAG, + 'Attempting to expand fluid creative without ' + 'a properly set up friendly frame. Slot id: ' + - this.element.getAttribute('data-amp-slot-index')); + this.element.getAttribute('data-amp-slot-index') + ); return Promise.reject('Cannot access body of friendly frame'); } return this.attemptChangeHeight( - this.iframe.contentWindow.document.body./*OK*/clientHeight) - .then(() => { - this.fireFluidDelayedImpression(); - this.reattemptToExpandFluidCreative_ = false; - }) - .catch(() => { - this.reattemptToExpandFluidCreative_ = true; - }); + this.iframe.contentWindow.document.body./*OK*/ clientHeight + ) + .then(() => { + this.fireFluidDelayedImpression(); + this.reattemptToExpandFluidCreative_ = false; + }) + .catch(() => { + this.reattemptToExpandFluidCreative_ = true; + }); } return Promise.resolve(); } @@ -1098,9 +1181,11 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // We want to resize only if neither returned dimension is larger than its // primary counterpart, and if at least one of the returned dimensions // differ from its primary counterpart. - if ((this.isFluidRequest_ && width && height) || - ((width != pWidth || height != pHeight) && - (width <= pWidth && height <= pHeight))) { + if ( + (this.isFluidRequest_ && width && height) || + ((width != pWidth || height != pHeight) && + (width <= pWidth && height <= pHeight)) + ) { this.attemptChangeSize(height, width).catch(() => {}); } } @@ -1119,10 +1204,19 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { if (!this.sraDeferred) { dev().warn(TAG, `SRA failed to include element ${this.ifi_}`); if (isExperimentOn(this.win, 'doubleclickSraReportExcludedBlock')) { - this.getAmpDoc().getBody().appendChild(createElementWithAttributes( - this.win.document, 'amp-pixel', dict({'src': - 'https://pagead2.googlesyndication.com/pagead/gen_204?' + - `id=${encodeURIComponent('a4a::sra')}&ifi=${this.ifi_}`}))); + this.getAmpDoc() + .getBody() + .appendChild( + createElementWithAttributes( + this.win.document, + 'amp-pixel', + dict({ + 'src': + 'https://pagead2.googlesyndication.com/pagead/gen_204?' + + `id=${encodeURIComponent('a4a::sra')}&ifi=${this.ifi_}`, + }) + ) + ); } } }); @@ -1153,13 +1247,15 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } // Create amp-pixel and append to document to send impression. this.win.document.body.appendChild( - createElementWithAttributes( - this.win.document, - 'amp-pixel', - dict({ - 'src': url, - 'referrerpolicy': scrubReferer ? 'no-referrer' : '', - }))); + createElementWithAttributes( + this.win.document, + 'amp-pixel', + dict({ + 'src': url, + 'referrerpolicy': scrubReferer ? 'no-referrer' : '', + }) + ) + ); } catch (unusedError) {} }); } @@ -1182,7 +1278,10 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { */ groupSlotsForSra() { return groupAmpAdsByType( - this.win, this.element.getAttribute('type'), getNetworkId); + this.win, + this.element.getAttribute('type'), + getNetworkId + ); } /** @@ -1201,33 +1300,38 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // be called for all and we should cancel SRA execution. const checkStillCurrent = this.verifyStillCurrent(); const noFallbackExp = this.experimentIds.includes( - DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER); - sraRequests = sraRequests || this.groupSlotsForSra() - .then(groupIdToBlocksAry => { - checkStillCurrent(); - const sraRequestPromises = []; - Object.keys(groupIdToBlocksAry).forEach(networkId => { - const blocks = devAssert(groupIdToBlocksAry[networkId]); - // TODO: filter blocks with SRA disabled? - sraRequestPromises.push(Promise.all(blocks).then(instances => { + DOUBLECLICK_SRA_EXP_BRANCHES.SRA_NO_RECOVER + ); + sraRequests = + sraRequests || + this.groupSlotsForSra().then(groupIdToBlocksAry => { + checkStillCurrent(); + const sraRequestPromises = []; + Object.keys(groupIdToBlocksAry).forEach(networkId => { + const blocks = devAssert(groupIdToBlocksAry[networkId]); + // TODO: filter blocks with SRA disabled? + sraRequestPromises.push( + Promise.all(blocks).then(instances => { devAssert(instances.length); checkStillCurrent(); // Exclude any instances that do not have an adPromise_ as this // indicates they were invalid. - const typeInstances = - /** @type {!Array}*/(instances) - .filter(instance => { - const isValid = instance.hasAdPromise(); - if (!isValid) { - dev().info(TAG, - 'Ignoring instance without ad promise as ' + - 'likely invalid', - instance.element); - } - return isValid; - }); + const typeInstances = /** @type {!Array}*/ (instances).filter( + instance => { + const isValid = instance.hasAdPromise(); + if (!isValid) { + dev().info( + TAG, + 'Ignoring instance without ad promise as ' + + 'likely invalid', + instance.element + ); + } + return isValid; + } + ); if (!typeInstances.length) { - // Only contained invalid elements. + // Only contained invalid elements. return; } // If not within no recovery SRA experiment, determine if more @@ -1238,8 +1342,8 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { dev().info(TAG, `single block in network ${networkId}`); // Ensure deferred exists, may not if getAdUrl did not yet // execute. - typeInstances[0].sraDeferred = typeInstances[0].sraDeferred || - new Deferred(); + typeInstances[0].sraDeferred = + typeInstances[0].sraDeferred || new Deferred(); typeInstances[0].sraDeferred.resolve(null); return; } @@ -1247,71 +1351,87 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { // Construct and send SRA request. // TODO(keithwrightbos) - how do we handle per slot 204 response? return constructSRARequest_(this, typeInstances) - .then(sraUrlIn => { - checkStillCurrent(); - sraUrl = sraUrlIn; - return Services.xhrFor(this.win).fetch(sraUrl, { - mode: 'cors', - method: 'GET', - credentials: 'include', - }); - }) - .then(response => { - checkStillCurrent(); - // Chunk handler called with metadata and creative for each - // slot in order of URLs given which is then passed to - // resolver used for sendXhrRequest. - const sraRequestAdUrlResolvers = - typeInstances.map(instance => instance.sraDeferred.resolve); - const slotCallback = metaJsonCreativeGrouper( - (creative, headersObj, done) => { - checkStillCurrent(); - sraBlockCallbackHandler(creative, headersObj, done, - sraRequestAdUrlResolvers, sraUrl); - }); - lineDelimitedStreamer(this.win, response, slotCallback); - return Promise.all(typeInstances.map( - instance => instance.sraDeferred.promise)); - }) - .catch(error => { - if (isCancellation(error)) { - // Cancellation should be propagated to slot promises - // causing their adPromise chains within A4A to handle - // appropriately. - typeInstances.forEach(instance => instance.sraDeferred && - instance.sraDeferred.reject(error)); - } else if (noFallbackExp || - !!this.win.document.querySelector( - 'meta[name=amp-ad-doubleclick-sra]')) { - // If publisher has explicitly enabled SRA mode (not - // experiment), then assume error is network failure, - // collapse slot, reset url to empty string to ensure - // no fallback to frame GET (given expectation of SRA - // consistency), and propagate error to A4A ad promise - // chain. - assignAdUrlToError(/** @type {!Error} */(error), sraUrl); - this.warnOnError('SRA request failure', error); - // Publisher explicitly wants SRA so do not attempt to - // recover as SRA guarantees cannot be enforced. - typeInstances.forEach(instance => { - // Reset ad url to ensure layoutCallback does not - // fallback to frame get which would lose SRA - // guarantees. - instance.resetAdUrl(); - instance.attemptCollapse(); - instance.sraDeferred.reject(error); - }); - } else { - // Opportunistic SRA used so fallback to individual - // XHR requests. - typeInstances.forEach(instance => - instance.sraDeferred.resolve(null)); - } + .then(sraUrlIn => { + checkStillCurrent(); + sraUrl = sraUrlIn; + return Services.xhrFor(this.win).fetch(sraUrl, { + mode: 'cors', + method: 'GET', + credentials: 'include', }); - })); - }); - return Promise.all(sraRequestPromises); + }) + .then(response => { + checkStillCurrent(); + // Chunk handler called with metadata and creative for each + // slot in order of URLs given which is then passed to + // resolver used for sendXhrRequest. + const sraRequestAdUrlResolvers = typeInstances.map( + instance => instance.sraDeferred.resolve + ); + const slotCallback = metaJsonCreativeGrouper( + (creative, headersObj, done) => { + checkStillCurrent(); + sraBlockCallbackHandler( + creative, + headersObj, + done, + sraRequestAdUrlResolvers, + sraUrl + ); + } + ); + lineDelimitedStreamer(this.win, response, slotCallback); + return Promise.all( + typeInstances.map(instance => instance.sraDeferred.promise) + ); + }) + .catch(error => { + if (isCancellation(error)) { + // Cancellation should be propagated to slot promises + // causing their adPromise chains within A4A to handle + // appropriately. + typeInstances.forEach( + instance => + instance.sraDeferred && + instance.sraDeferred.reject(error) + ); + } else if ( + noFallbackExp || + !!this.win.document.querySelector( + 'meta[name=amp-ad-doubleclick-sra]' + ) + ) { + // If publisher has explicitly enabled SRA mode (not + // experiment), then assume error is network failure, + // collapse slot, reset url to empty string to ensure + // no fallback to frame GET (given expectation of SRA + // consistency), and propagate error to A4A ad promise + // chain. + assignAdUrlToError(/** @type {!Error} */ (error), sraUrl); + this.warnOnError('SRA request failure', error); + // Publisher explicitly wants SRA so do not attempt to + // recover as SRA guarantees cannot be enforced. + typeInstances.forEach(instance => { + // Reset ad url to ensure layoutCallback does not + // fallback to frame get which would lose SRA + // guarantees. + instance.resetAdUrl(); + instance.attemptCollapse(); + instance.sraDeferred.reject(error); + }); + } else { + // Opportunistic SRA used so fallback to individual + // XHR requests. + typeInstances.forEach(instance => + instance.sraDeferred.resolve(null) + ); + } + }); + }) + ); }); + return Promise.all(sraRequestPromises); + }); return sraRequests; } @@ -1343,8 +1463,9 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { * @visibleForTesting */ getLocationQueryParameterValue(parameterName) { - windowLocationQueryParameters = windowLocationQueryParameters || - parseQueryString((this.win.location && this.win.location.search) || ''); + windowLocationQueryParameters = + windowLocationQueryParameters || + parseQueryString((this.win.location && this.win.location.search) || ''); return windowLocationQueryParameters[parameterName]; } @@ -1355,10 +1476,13 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } const creativeSize = this.getCreativeSize(); devAssert(creativeSize, 'this.getCreativeSize returned null'); - this.safeframeApi_ = this.safeframeApi_ || - new SafeframeHostApi( - this, this.isFluidRequest_, - /** @type {{height, width}} */(creativeSize)); + this.safeframeApi_ = + this.safeframeApi_ || + new SafeframeHostApi( + this, + this.isFluidRequest_, + /** @type {{height, width}} */ (creativeSize) + ); return this.safeframeApi_.getSafeframeNameAttr(); } @@ -1377,29 +1501,35 @@ export class AmpAdNetworkDoubleclickImpl extends AmpA4A { } devAssert(this.troubleshootData_.adUrl, 'ad URL does not exist yet'); return this.troubleshootData_.adUrl.then(adUrl => { - const slotId = this.troubleshootData_.slotId + '_' + - this.troubleshootData_.slotIndex; + const slotId = + this.troubleshootData_.slotId + '_' + this.troubleshootData_.slotIndex; const payload = dict({ - 'gutData': JSON.stringify(dict({ - 'events': [{ - 'timestamp': Date.now(), - 'slotid': slotId, - 'messageId': 4, - }], - 'slots': [{ - 'contentUrl': adUrl || '', - 'id': slotId, - 'leafAdUnitName': this.troubleshootData_.slotId, - 'domId': slotId, - 'lineItemId': this.troubleshootData_.lineItemId, - 'creativeId': this.troubleshootData_.creativeId, - }], - })), + 'gutData': JSON.stringify( + dict({ + 'events': [ + { + 'timestamp': Date.now(), + 'slotid': slotId, + 'messageId': 4, + }, + ], + 'slots': [ + { + 'contentUrl': adUrl || '', + 'id': slotId, + 'leafAdUnitName': this.troubleshootData_.slotId, + 'domId': slotId, + 'lineItemId': this.troubleshootData_.lineItemId, + 'creativeId': this.troubleshootData_.creativeId, + }, + ], + }) + ), 'userAgent': navigator.userAgent, 'referrer': this.win.location.href, 'messageType': 'LOAD', }); - this.win.opener./*OK*/postMessage(payload, '*'); + this.win.opener./*OK*/ postMessage(payload, '*'); }); } @@ -1465,12 +1595,12 @@ export function resetLocationQueryParametersForTesting() { */ export function getNetworkId(element) { const networkId = /^(?:\/)?(\d+)/.exec( - dev().assertString(element.getAttribute('data-slot'))); + dev().assertString(element.getAttribute('data-slot')) + ); // TODO: guarantee data-ad-slot format as part of isValidElement? return networkId ? networkId[1] : ''; } - /** * @param {!../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a * @param {!Array} instances @@ -1481,15 +1611,21 @@ function constructSRARequest_(a4a, instances) { devAssert(instances && instances.length); const startTime = Date.now(); return Promise.all( - instances.map(instance => instance.getAdUrlDeferred.promise)) - .then(() => googlePageParameters(a4a, startTime)) - .then(googPageLevelParameters => { - const blockParameters = constructSRABlockParameters(instances); - return truncAndTimeUrl(DOUBLECLICK_BASE_URL, - Object.assign(blockParameters, googPageLevelParameters, - instances[0].getPageParameters(instances[0].consentState, - instances)), startTime); - }); + instances.map(instance => instance.getAdUrlDeferred.promise) + ) + .then(() => googlePageParameters(a4a, startTime)) + .then(googPageLevelParameters => { + const blockParameters = constructSRABlockParameters(instances); + return truncAndTimeUrl( + DOUBLECLICK_BASE_URL, + Object.assign( + blockParameters, + googPageLevelParameters, + instances[0].getPageParameters(instances[0].consentState, instances) + ), + startTime + ); + }); } /** @@ -1502,8 +1638,7 @@ function constructSRARequest_(a4a, instances) { export function getPageviewStateTokensForAdRequest(instancesInAdRequest) { const pageviewStateTokensInAdRequest = []; for (const token in tokensToInstances) { - if (!instancesInAdRequest.includes( - tokensToInstances[token])) { + if (!instancesInAdRequest.includes(tokensToInstances[token])) { pageviewStateTokensInAdRequest.push(token); } } diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js b/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js index 93bea028fef2b..7e45e0b47b9e4 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/safeframe-host.js @@ -91,8 +91,10 @@ export function safeframeListener(event) { safeframeHost.connectMessagingChannel(data[MESSAGE_FIELDS.CHANNEL]); } else if (payload) { // Currently we do not expect a payload on initial connection messages. - safeframeHost.processMessage(/** @type {!JsonObject} */(payload), - data[MESSAGE_FIELDS.SERVICE]); + safeframeHost.processMessage( + /** @type {!JsonObject} */ (payload), + data[MESSAGE_FIELDS.SERVICE] + ); } } @@ -117,7 +119,6 @@ export function safeframeListener(event) { * then calls the instance of send() below whenever an update occurs. */ export class SafeframeHostApi { - /** * @param {!./amp-ad-network-doubleclick-impl.AmpAdNetworkDoubleclickImpl} baseInstance * @param {boolean} isFluid @@ -129,7 +130,8 @@ export class SafeframeHostApi { /** @private {!Function} */ this.checkStillCurrent_ = this.baseInstance_.verifyStillCurrent.bind( - this.baseInstance_)(); + this.baseInstance_ + )(); /** @private {!Window} */ this.win_ = this.baseInstance_.win; @@ -161,7 +163,7 @@ export class SafeframeHostApi { /** @private {{width:number, height:number}} */ this.initialCreativeSize_ = /** @private {{width:number, height:number}} */ - (Object.assign({}, creativeSize)); + Object.assign({}, creativeSize); /** @private {?Promise} */ this.delay_ = null; @@ -176,16 +178,20 @@ export class SafeframeHostApi { this.isRegistered_ = false; // TODO: Make this page-level. - const sfConfig = Object(tryParseJson( - this.baseInstance_.element.getAttribute( - 'data-safeframe-config')) || {}); + const sfConfig = Object( + tryParseJson( + this.baseInstance_.element.getAttribute('data-safeframe-config') + ) || {} + ); /** @private {boolean} */ - this.expandByOverlay_ = hasOwn(sfConfig, 'expandByOverlay') ? - sfConfig['expandByOverlay'] : true; + this.expandByOverlay_ = hasOwn(sfConfig, 'expandByOverlay') + ? sfConfig['expandByOverlay'] + : true; /** @private {boolean} */ - this.expandByPush_ = hasOwn(sfConfig, 'expandByPush') ? - sfConfig['expandByPush'] : true; + this.expandByPush_ = hasOwn(sfConfig, 'expandByPush') + ? sfConfig['expandByPush'] + : true; /** @private {?Function} */ this.unlisten_ = null; @@ -204,26 +210,28 @@ export class SafeframeHostApi { attributes['hostPeerName'] = this.win_.location.origin; attributes['initialGeometry'] = this.getInitialGeometry(); attributes['permissions'] = JSON.stringify( - dict({ - 'expandByOverlay': this.expandByOverlay_, - 'expandByPush': this.expandByPush_, - 'readCookie': false, - 'writeCookie': false, - })); + dict({ + 'expandByOverlay': this.expandByOverlay_, + 'expandByPush': this.expandByPush_, + 'readCookie': false, + 'writeCookie': false, + }) + ); attributes['metadata'] = JSON.stringify( - dict({ - 'shared': { - 'sf_ver': this.baseInstance_.safeframeVersion, - 'ck_on': 1, - 'flash_ver': '26.0.0', - // Once GPT Safeframe is updated to look in amp object, - // remove this canonical_url here. + dict({ + 'shared': { + 'sf_ver': this.baseInstance_.safeframeVersion, + 'ck_on': 1, + 'flash_ver': '26.0.0', + // Once GPT Safeframe is updated to look in amp object, + // remove this canonical_url here. + 'canonical_url': this.maybeGetCanonicalUrl(), + 'amp': { 'canonical_url': this.maybeGetCanonicalUrl(), - 'amp': { - 'canonical_url': this.maybeGetCanonicalUrl(), - }, }, - })); + }, + }) + ); attributes['reportCreativeGeometry'] = this.isFluid_; attributes['isDifferentSourceWindow'] = false; attributes['sentinel'] = this.sentinel_; @@ -241,9 +249,11 @@ export class SafeframeHostApi { // as Safeframe will always be a different origin. // Don't allow for no-referrer. const {canonicalUrl} = Services.documentInfoForDoc( - this.baseInstance_.getAmpDoc()); + this.baseInstance_.getAmpDoc() + ); const metaReferrer = this.win_.document.querySelector( - "meta[name='referrer']"); + "meta[name='referrer']" + ); if (!metaReferrer) { return canonicalUrl; } @@ -311,10 +321,13 @@ export class SafeframeHostApi { this.iframe_ = this.baseInstance_.iframe; this.channel = channel; this.setupGeom_(); - this.sendMessage_({ - 'message': 'connect', - 'c': this.channel, - }, ''); + this.sendMessage_( + { + 'message': 'connect', + 'c': this.channel, + }, + '' + ); } /** @@ -326,10 +339,12 @@ export class SafeframeHostApi { * @private */ setupGeom_() { - devAssert(this.iframe_.contentWindow, - 'Frame contentWindow unavailable.'); + devAssert(this.iframe_.contentWindow, 'Frame contentWindow unavailable.'); const throttledUpdate = throttle( - this.win_, this.updateGeometry_.bind(this), 1000); + this.win_, + this.updateGeometry_.bind(this), + 1000 + ); const scrollUnlistener = this.viewport_.onScroll(throttledUpdate); const changedUnlistener = this.viewport_.onChanged(throttledUpdate); this.unlisten_ = () => { @@ -347,14 +362,20 @@ export class SafeframeHostApi { if (!this.iframe_) { return; } - this.viewport_.getClientRectAsync(this.iframe_).then(iframeBox => { - this.checkStillCurrent_(); - const formattedGeom = this.formatGeom_(iframeBox); - this.sendMessage_({ - newGeometry: formattedGeom, - uid: this.uid_, - }, SERVICE.GEOMETRY_UPDATE); - }).catch(err => dev().error(TAG, err)); + this.viewport_ + .getClientRectAsync(this.iframe_) + .then(iframeBox => { + this.checkStillCurrent_(); + const formattedGeom = this.formatGeom_(iframeBox); + this.sendMessage_( + { + newGeometry: formattedGeom, + uid: this.uid_, + }, + SERVICE.GEOMETRY_UPDATE + ); + }) + .catch(err => dev().error(TAG, err)); } /** @@ -368,7 +389,7 @@ export class SafeframeHostApi { const viewportSize = this.viewport_.getSize(); const scrollLeft = this.viewport_.getScrollLeft(); const scrollTop = this.viewport_.getScrollTop(); - const currentGeometry = /** @type {JsonObject} */({ + const currentGeometry = /** @type {JsonObject} */ ({ 'windowCoords_t': 0, 'windowCoords_r': viewportSize.width, 'windowCoords_b': viewportSize.height, @@ -384,16 +405,20 @@ export class SafeframeHostApi { 'styleZIndex': getStyle(this.baseInstance_.element, 'zIndex'), // AMP's built in resize methodology that we use only allows expansion // to the right and bottom, so we enforce that here. - 'allowedExpansion_r': viewportSize.width - - iframeBox.width, - 'allowedExpansion_b': viewportSize.height - - iframeBox.height, + 'allowedExpansion_r': viewportSize.width - iframeBox.width, + 'allowedExpansion_b': viewportSize.height - iframeBox.height, 'allowedExpansion_t': 0, 'allowedExpansion_l': 0, - 'yInView': this.getPercInView(viewportSize.height, - iframeBox.top, iframeBox.bottom), - 'xInView': this.getPercInView(viewportSize.width, - iframeBox.left, iframeBox.right), + 'yInView': this.getPercInView( + viewportSize.height, + iframeBox.top, + iframeBox.bottom + ), + 'xInView': this.getPercInView( + viewportSize.width, + iframeBox.left, + iframeBox.right + ), }); this.currentGeometry_ = currentGeometry; return JSON.stringify(currentGeometry); @@ -411,8 +436,10 @@ export class SafeframeHostApi { * @return {number} */ getPercInView(rootBoundEnd, boundingRectStart, boundingRectEnd) { - const lengthInView = (boundingRectEnd >= rootBoundEnd) ? - rootBoundEnd - boundingRectStart : boundingRectEnd; + const lengthInView = + boundingRectEnd >= rootBoundEnd + ? rootBoundEnd - boundingRectStart + : boundingRectEnd; const percInView = lengthInView / (boundingRectEnd - boundingRectStart); return Math.max(0, Math.min(1, percInView)) || 0; } @@ -431,12 +458,15 @@ export class SafeframeHostApi { const message = dict(); message[MESSAGE_FIELDS.CHANNEL] = this.channel; message[MESSAGE_FIELDS.PAYLOAD] = JSON.stringify( - /** @type {!JsonObject} */(payload)); + /** @type {!JsonObject} */ (payload) + ); message[MESSAGE_FIELDS.SERVICE] = serviceName; message[MESSAGE_FIELDS.SENTINEL] = this.sentinel_; message[MESSAGE_FIELDS.ENDPOINT_IDENTITY] = this.endpointIdentity_; - this.iframe_.contentWindow./*OK*/postMessage( - JSON.stringify(message), SAFEFRAME_ORIGIN); + this.iframe_.contentWindow./*OK*/ postMessage( + JSON.stringify(message), + SAFEFRAME_ORIGIN + ); } /** @@ -467,7 +497,6 @@ export class SafeframeHostApi { } } - /** * @param {!JsonObject} payload * @private @@ -476,33 +505,39 @@ export class SafeframeHostApi { if (!this.isRegistered_) { return; } - const expandHeight = Number(this.creativeSize_.height) + - payload['expand_b'] + payload['expand_t']; - const expandWidth = Number(this.creativeSize_.width) + - payload['expand_r'] + payload['expand_l']; + const expandHeight = + Number(this.creativeSize_.height) + + payload['expand_b'] + + payload['expand_t']; + const expandWidth = + Number(this.creativeSize_.width) + + payload['expand_r'] + + payload['expand_l']; // Verify that if expanding by push, that expandByPush is allowed. // If expanding by overlay, verify that expandByOverlay is allowed, // and that we are only expanding within the bounds of the amp-ad. - if (isNaN(expandHeight) || isNaN(expandWidth) || - (payload['push'] && !this.expandByPush_) || - (!payload['push'] && !this.expandByOverlay_ && - (expandWidth > this.creativeSize_.width || - expandHeight > this.creativeSize_.height))) { + if ( + isNaN(expandHeight) || + isNaN(expandWidth) || + (payload['push'] && !this.expandByPush_) || + (!payload['push'] && + !this.expandByOverlay_ && + (expandWidth > this.creativeSize_.width || + expandHeight > this.creativeSize_.height)) + ) { dev().error(TAG, 'Invalid expand values.'); - this.sendResizeResponse( - /* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); + this.sendResizeResponse(/* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); return; } // Can't expand to greater than the viewport size - if (expandHeight > this.viewport_.getSize().height || - expandWidth > this.viewport_.getSize().width) { - this.sendResizeResponse( - /* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); + if ( + expandHeight > this.viewport_.getSize().height || + expandWidth > this.viewport_.getSize().width + ) { + this.sendResizeResponse(/* SUCCESS? */ false, SERVICE.EXPAND_RESPONSE); return; } - this.handleSizeChange(expandHeight, - expandWidth, - SERVICE.EXPAND_RESPONSE); + this.handleSizeChange(expandHeight, expandWidth, SERVICE.EXPAND_RESPONSE); } /** @@ -511,14 +546,15 @@ export class SafeframeHostApi { handleCollapseRequest_() { // Only collapse if expanded. if (this.isCollapsed_ || !this.isRegistered_) { - this.sendResizeResponse( - /* SUCCESS? */ false, SERVICE.COLLAPSE_RESPONSE); + this.sendResizeResponse(/* SUCCESS? */ false, SERVICE.COLLAPSE_RESPONSE); return; } - this.handleSizeChange(this.initialCreativeSize_.height, - this.initialCreativeSize_.width, - SERVICE.COLLAPSE_RESPONSE, - /** isCollapse */ true); + this.handleSizeChange( + this.initialCreativeSize_.height, + this.initialCreativeSize_.width, + SERVICE.COLLAPSE_RESPONSE, + /** isCollapse */ true + ); } /** @@ -529,21 +565,21 @@ export class SafeframeHostApi { resizeSafeframe(height, width, messageType) { this.isCollapsed_ = messageType == SERVICE.COLLAPSE_RESPONSE; this.baseInstance_.measureMutateElement( - /** MEASURER */ () => { - this.baseInstance_.getResource().measure(); - }, - /** MUTATOR */ () => { - if (this.iframe_) { - setStyles(this.iframe_, { - 'height': height + 'px', - 'width': width + 'px', - }); - this.creativeSize_.height = height; - this.creativeSize_.width = width; - } - this.sendResizeResponse(/** SUCCESS */ true, messageType); - }, - this.iframe_ + /** MEASURER */ () => { + this.baseInstance_.getResource().measure(); + }, + /** MUTATOR */ () => { + if (this.iframe_) { + setStyles(this.iframe_, { + 'height': height + 'px', + 'width': width + 'px', + }); + this.creativeSize_.height = height; + this.creativeSize_.width = width; + } + this.sendResizeResponse(/** SUCCESS */ true, messageType); + }, + this.iframe_ ); } @@ -565,15 +601,20 @@ export class SafeframeHostApi { * @param {boolean=} optIsCollapse Whether this is a collapse attempt. */ handleSizeChange(height, width, messageType, optIsCollapse) { - return this.viewport_.getClientRectAsync( - this.baseInstance_.element).then(box => { - if (!optIsCollapse && width <= box.width && height <= box.height) { - this.resizeSafeframe(height, width, messageType); - } else { - this.resizeAmpAdAndSafeframe(height, width, messageType, - optIsCollapse); - } - }); + return this.viewport_ + .getClientRectAsync(this.baseInstance_.element) + .then(box => { + if (!optIsCollapse && width <= box.width && height <= box.height) { + this.resizeSafeframe(height, width, messageType); + } else { + this.resizeAmpAdAndSafeframe( + height, + width, + messageType, + optIsCollapse + ); + } + }); } /** @@ -584,10 +625,12 @@ export class SafeframeHostApi { if (!this.isRegistered_) { return; } - const resizeHeight = Number(this.creativeSize_.height) + - (payload['resize_b'] + payload['resize_t']); - const resizeWidth = Number(this.creativeSize_.width) + - (payload['resize_r'] + payload['resize_l']); + const resizeHeight = + Number(this.creativeSize_.height) + + (payload['resize_b'] + payload['resize_t']); + const resizeWidth = + Number(this.creativeSize_.width) + + (payload['resize_r'] + payload['resize_l']); // Make sure we are actually resizing here. if (isNaN(resizeWidth) || isNaN(resizeHeight)) { @@ -595,8 +638,12 @@ export class SafeframeHostApi { return; } - this.resizeAmpAdAndSafeframe(resizeHeight, resizeWidth, - SERVICE.RESIZE_RESPONSE, true); + this.resizeAmpAdAndSafeframe( + resizeHeight, + resizeWidth, + SERVICE.RESIZE_RESPONSE, + true + ); } /** @@ -607,20 +654,26 @@ export class SafeframeHostApi { if (!this.iframe_) { return; } - this.viewport_.getClientRectAsync(this.iframe_).then(iframeBox => { - this.checkStillCurrent_(); - const formattedGeom = this.formatGeom_(iframeBox); - this.sendMessage_({ - uid: this.uid_, - success, - newGeometry: formattedGeom, - 'expand_t': this.currentGeometry_['allowedExpansion_t'], - 'expand_b': this.currentGeometry_['allowedExpansion_b'], - 'expand_r': this.currentGeometry_['allowedExpansion_r'], - 'expand_l': this.currentGeometry_['allowedExpansion_l'], - push: true, - }, messageType); - }).catch(err => dev().error(TAG, err)); + this.viewport_ + .getClientRectAsync(this.iframe_) + .then(iframeBox => { + this.checkStillCurrent_(); + const formattedGeom = this.formatGeom_(iframeBox); + this.sendMessage_( + { + uid: this.uid_, + success, + newGeometry: formattedGeom, + 'expand_t': this.currentGeometry_['allowedExpansion_t'], + 'expand_b': this.currentGeometry_['allowedExpansion_b'], + 'expand_r': this.currentGeometry_['allowedExpansion_r'], + 'expand_l': this.currentGeometry_['allowedExpansion_l'], + push: true, + }, + messageType + ); + }) + .catch(err => dev().error(TAG, err)); } /** @@ -635,38 +688,44 @@ export class SafeframeHostApi { resizeAmpAdAndSafeframe(height, width, messageType, opt_isShrinking) { // First, attempt to resize the Amp-Ad that is the parent of the // safeframe - this.baseInstance_.attemptChangeSize(height, width).then(() => { - this.checkStillCurrent_(); - // If this resize succeeded, we always resize the safeframe. - // resizeSafeframe also sends the resize response. - this.resizeSafeframe(height, width, messageType); - }, /** REJECT CALLBACK */ () => { - // If the resize initially failed, it may have been queued - // as a pendingChangeSize, which will cause the size change - // to execute upon the next user interaction. We don't want - // that for safeframe, so we reset it here. - this.baseInstance_.getResource().resetPendingChangeSize(); - if (opt_isShrinking) { - // If this is a collapse or resize request, then even if resizing - // the amp-ad failed, still resize the iframe. - // resizeSafeframe also sends the resize response. - // Only register as collapsed if explicitly a collapse request. - this.resizeSafeframe(height, width, messageType); - } else { - // We were attempting to - // expand past the bounds of the amp-ad, and it failed. Thus, - // we need to send a failure message, and the safeframe is - // not resized. + this.baseInstance_ + .attemptChangeSize(height, width) + .then( + () => { + this.checkStillCurrent_(); + // If this resize succeeded, we always resize the safeframe. + // resizeSafeframe also sends the resize response. + this.resizeSafeframe(height, width, messageType); + }, + /** REJECT CALLBACK */ () => { + // If the resize initially failed, it may have been queued + // as a pendingChangeSize, which will cause the size change + // to execute upon the next user interaction. We don't want + // that for safeframe, so we reset it here. + this.baseInstance_.getResource().resetPendingChangeSize(); + if (opt_isShrinking) { + // If this is a collapse or resize request, then even if resizing + // the amp-ad failed, still resize the iframe. + // resizeSafeframe also sends the resize response. + // Only register as collapsed if explicitly a collapse request. + this.resizeSafeframe(height, width, messageType); + } else { + // We were attempting to + // expand past the bounds of the amp-ad, and it failed. Thus, + // we need to send a failure message, and the safeframe is + // not resized. + this.sendResizeResponse(false, messageType); + } + } + ) + .catch(err => { + if (err.message == 'CANCELLED') { + dev().error(TAG, err); + return; + } + dev().error(TAG, `Resizing failed: ${err}`); this.sendResizeResponse(false, messageType); - } - }).catch(err => { - if (err.message == 'CANCELLED') { - dev().error(TAG, err); - return; - } - dev().error(TAG, `Resizing failed: ${err}`); - this.sendResizeResponse(false, messageType); - }); + }); } /** @@ -679,16 +738,18 @@ export class SafeframeHostApi { if (!payload || !(newHeight = parseInt(payload['height'], 10))) { return; } - this.baseInstance_.attemptChangeHeight(newHeight) - .then(() => { - this.checkStillCurrent_(); - this.onFluidResize_(newHeight); - }).catch(err => { - if (err.message == 'CANCELLED') { - dev().error(TAG, err); - return; - } - }); + this.baseInstance_ + .attemptChangeHeight(newHeight) + .then(() => { + this.checkStillCurrent_(); + this.onFluidResize_(newHeight); + }) + .catch(err => { + if (err.message == 'CANCELLED') { + dev().error(TAG, err); + return; + } + }); } /** @@ -704,9 +765,10 @@ export class SafeframeHostApi { setStyles(iframe, {height: `${newHeight}px`}); } this.baseInstance_.fireFluidDelayedImpression(); - this.iframe_.contentWindow./*OK*/postMessage( - JSON.stringify(dict({'message': 'resize-complete', 'c': this.channel})), - SAFEFRAME_ORIGIN); + this.iframe_.contentWindow./*OK*/ postMessage( + JSON.stringify(dict({'message': 'resize-complete', 'c': this.channel})), + SAFEFRAME_ORIGIN + ); } /** diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js b/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js index 6d07ec7fb94a8..2755fbd34e8a7 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/sra-utils.js @@ -17,9 +17,7 @@ import {RENDERING_TYPE_HEADER, XORIGIN_MODE} from '../../amp-a4a/0.1/amp-a4a'; import {dev, devAssert} from '../../../src/log'; import {getEnclosingContainerTypes} from '../../../ads/google/a4a/utils'; -import { - isInManualExperiment, -} from '../../../ads/google/a4a/traffic-experiments'; +import {isInManualExperiment} from '../../../ads/google/a4a/traffic-experiments'; import {isObject} from '../../../src/types'; import {tryResolve} from '../../../src/utils/promise'; import {utf8Encode} from '../../../src/utils/bytes'; @@ -35,14 +33,25 @@ export const TFCD = 'tagForChildDirectedTreatment'; /** @private {!Array):?Object>} */ const SRA_JOINERS = [ - combineInventoryUnits, getCookieOptOut, getAdks, getSizes, getTfcd, isAdTest, - getTargetingAndExclusions, getExperimentIds, getIdentity, getForceSafeframe, - getPageOffsets, getContainers, getIsFluid]; + combineInventoryUnits, + getCookieOptOut, + getAdks, + getSizes, + getTfcd, + isAdTest, + getTargetingAndExclusions, + getExperimentIds, + getIdentity, + getForceSafeframe, + getPageOffsets, + getContainers, + getIsFluid, +]; /** - * @param {!Array} impls - * @return {!Object} - */ + * @param {!Array} impls + * @return {!Object} + */ export function constructSRABlockParameters(impls) { const parameters = {'output': 'ldjh', 'impl': 'fifs'}; SRA_JOINERS.forEach(joiner => Object.assign(parameters, joiner(impls))); @@ -95,7 +104,7 @@ export function combineInventoryUnits(impls) { let index = uniqueIuNames[componentNames[i]]; if (index == undefined) { iuNamesOutput.push(componentNames[i]); - uniqueIuNames[componentNames[i]] = (index = uniqueIuNamesCount++); + uniqueIuNames[componentNames[i]] = index = uniqueIuNamesCount++; } encodedNames.push(index); } @@ -116,8 +125,10 @@ export function combineInventoryUnits(impls) { */ export function getCookieOptOut(impls) { return getFirstInstanceValue_(impls, impl => - impl.jsonTargeting && - impl.jsonTargeting['cookieOptOut'] ? {'co': '1'} : null); + impl.jsonTargeting && impl.jsonTargeting['cookieOptOut'] + ? {'co': '1'} + : null + ); } /** @@ -127,7 +138,7 @@ export function getCookieOptOut(impls) { * @visibleForTesting */ export function getAdks(impls) { - return ({'adks': impls.map(impl => devAssert(impl.adKey)).join()}); + return {'adks': impls.map(impl => devAssert(impl.adKey)).join()}; } /** @@ -137,8 +148,9 @@ export function getAdks(impls) { * @visibleForTesting */ export function getSizes(impls) { - return ({'prev_iu_szs': impls.map(impl => - devAssert(impl.parameterSize)).join()}); + return { + 'prev_iu_szs': impls.map(impl => devAssert(impl.parameterSize)).join(), + }; } /** @@ -150,8 +162,10 @@ export function getSizes(impls) { */ export function getTfcd(impls) { return getFirstInstanceValue_(impls, impl => - impl.jsonTargeting && impl.jsonTargeting[TFCD] ? - {'tfcd': impl.jsonTargeting[TFCD]} : null); + impl.jsonTargeting && impl.jsonTargeting[TFCD] + ? {'tfcd': impl.jsonTargeting[TFCD]} + : null + ); } /** @@ -163,7 +177,8 @@ export function getTfcd(impls) { */ export function isAdTest(impls) { return getFirstInstanceValue_(impls, impl => - isInManualExperiment(impl.element) ? {'adtest': 'on'} : null); + isInManualExperiment(impl.element) ? {'adtest': 'on'} : null + ); } /** @@ -178,12 +193,18 @@ export function getTargetingAndExclusions(impls) { let hasScp = false; const scps = []; impls.forEach(impl => { - if (impl.jsonTargeting && (impl.jsonTargeting['targeting'] || - impl.jsonTargeting['categoryExclusions'])) { + if ( + impl.jsonTargeting && + (impl.jsonTargeting['targeting'] || + impl.jsonTargeting['categoryExclusions']) + ) { hasScp = true; - scps.push(serializeTargeting( + scps.push( + serializeTargeting( impl.jsonTargeting['targeting'] || null, - impl.jsonTargeting['categoryExclusions'] || null)); + impl.jsonTargeting['categoryExclusions'] || null + ) + ); } else { scps.push(''); } @@ -202,10 +223,12 @@ export function getTargetingAndExclusions(impls) { */ export function getExperimentIds(impls) { const eids = {}; - const deid = (impls.length && - /(?:#|,)deid=([\d,]+)/i.exec(impls[0].win.location.hash)) || []; + const deid = + (impls.length && + /(?:#|,)deid=([\d,]+)/i.exec(impls[0].win.location.hash)) || + []; (deid[1] || '').split(',').forEach(eid => eid && (eids[eid] = 1)); - impls.forEach(impl => impl.experimentIds.forEach(eid => eids[eid] = 1)); + impls.forEach(impl => impl.experimentIds.forEach(eid => (eids[eid] = 1))); const eidKeys = Object.keys(eids).join(); return eidKeys ? {'eid': eidKeys} : null; } @@ -302,9 +325,9 @@ export function getIsFluid(impls) { * @return {?string} */ export function serializeTargeting(targeting, categoryExclusions) { - const serialized = targeting ? - Object.keys(targeting).map(key => serializeItem_(key, targeting[key])) : - []; + const serialized = targeting + ? Object.keys(targeting).map(key => serializeItem_(key, targeting[key])) + : []; if (categoryExclusions) { serialized.push(serializeItem_('excl_cat', categoryExclusions)); } @@ -318,8 +341,9 @@ export function serializeTargeting(targeting, categoryExclusions) { * @private */ function serializeItem_(key, value) { - const serializedValue = - (Array.isArray(value) ? value : [value]).map(encodeURIComponent).join(); + const serializedValue = (Array.isArray(value) ? value : [value]) + .map(encodeURIComponent) + .join(); return `${encodeURIComponent(key)}=${serializedValue}`; } @@ -337,45 +361,46 @@ function serializeItem_(key, value) { * @param {string} sraUrl url of SRA request for error reporting */ export function sraBlockCallbackHandler( - creative, headersObj, done, sraRequestAdUrlResolvers, sraUrl) { + creative, + headersObj, + done, + sraRequestAdUrlResolvers, + sraUrl +) { const headerNames = Object.keys(headersObj); - if (headerNames.length == 1 && - isObject(headersObj[headerNames[0]])) { + if (headerNames.length == 1 && isObject(headersObj[headerNames[0]])) { // TODO(keithwrightbos) - fix upstream so response does // not improperly place headers under key. - headersObj = - /** @type {!Object} */(headersObj)[headerNames[0]]; - headersObj = Object.keys(headersObj).reduce( - (newObj, key) => { - newObj[key.toLowerCase()] = headersObj[key]; - return newObj; - }, {}); + headersObj = /** @type {!Object} */ (headersObj)[headerNames[0]]; + headersObj = Object.keys(headersObj).reduce((newObj, key) => { + newObj[key.toLowerCase()] = headersObj[key]; + return newObj; + }, {}); } // Force safeframe rendering method. - headersObj[RENDERING_TYPE_HEADER.toLowerCase()] = - XORIGIN_MODE.SAFEFRAME; + headersObj[RENDERING_TYPE_HEADER.toLowerCase()] = XORIGIN_MODE.SAFEFRAME; // Construct pseudo fetch response to be passed down the A4A // promise chain for this block. const headers = -/** @type {?Headers} */ -({ - get: name => { - // TODO(keithwrightbos) - fix upstream so response writes - // all metadata values as strings. - let header = headersObj[name.toLowerCase()]; - if (header && typeof header != 'string') { - header = JSON.stringify(header); - } - return header; - }, - has: name => !!headersObj[name.toLowerCase()], -}); + /** @type {?Headers} */ + ({ + get: name => { + // TODO(keithwrightbos) - fix upstream so response writes + // all metadata values as strings. + let header = headersObj[name.toLowerCase()]; + if (header && typeof header != 'string') { + header = JSON.stringify(header); + } + return header; + }, + has: name => !!headersObj[name.toLowerCase()], + }); const fetchResponse = -/** @type {?Response} */ -({ - headers, - arrayBuffer: () => tryResolve(() => utf8Encode(creative)), -}); + /** @type {?Response} */ + ({ + headers, + arrayBuffer: () => tryResolve(() => utf8Encode(creative)), + }); // Pop head off of the array of resolvers as the response // should match the order of blocks declared in the ad url. // This allows the block to start rendering while the SRA @@ -384,7 +409,11 @@ export function sraBlockCallbackHandler( // If done, expect array to be empty (ensures ad response // included data for all slots). if (done && sraRequestAdUrlResolvers.length) { - dev().warn(TAG, 'Premature end of SRA response', - sraRequestAdUrlResolvers.length, sraUrl); + dev().warn( + TAG, + 'Premature end of SRA response', + sraRequestAdUrlResolvers.length, + sraUrl + ); } } diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js index 3d0ee7a98e8ec..4e39cbc5e14b6 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-amp-ad-network-doubleclick-impl.js @@ -41,9 +41,7 @@ import {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; import {Deferred} from '../../../../src/utils/promise'; import {FriendlyIframeEmbed} from '../../../../src/friendly-iframe-embed'; import {Layout} from '../../../../src/layout'; -import { - QQID_HEADER, -} from '../../../../ads/google/a4a/utils'; +import {QQID_HEADER} from '../../../../ads/google/a4a/utils'; import {Services} from '../../../../src/services'; import {createElementWithAttributes} from '../../../../src/dom'; import {toggleExperiment} from '../../../../src/experiments'; @@ -83,8 +81,10 @@ function createImplTag(config, element, impl, env) { config.type = 'doubleclick'; element = createElementWithAttributes(env.win.document, 'amp-ad', config); // To trigger CSS styling. - element.setAttribute('data-a4a-upgrade-type', - 'amp-ad-network-doubleclick-impl'); + element.setAttribute( + 'data-a4a-upgrade-type', + 'amp-ad-network-doubleclick-impl' + ); // Used to test styling which is targetted at first iframe child of // amp-ad. const iframe = env.win.document.createElement('iframe'); @@ -122,8 +122,7 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { expect(impl.isValidElement()).to.be.true; }); it('should NOT be valid (impl tag name)', () => { - element = - doc.createElement('amp-ad-network-doubleclick-impl'); + element = doc.createElement('amp-ad-network-doubleclick-impl'); element.setAttribute('type', 'doubleclick'); element.setAttribute('data-ad-client', 'doubleclick'); impl = new AmpAdNetworkDoubleclickImpl(element); @@ -144,7 +143,6 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); }); - describe('#extractSize', () => { let preloadExtensionSpy; const size = {width: 200, height: 50}; @@ -171,44 +169,50 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { impl.isFluid_ = true; impl.element.setAttribute('height', 'fluid'); const resizeSpy = sandbox.spy(impl, 'attemptChangeSize'); - expect(impl.extractSize({ - get(name) { - return name == CREATIVE_SIZE_HEADER ? '0x0' : undefined; - }, - has(name) { - return name == CREATIVE_SIZE_HEADER; - }, - })).to.deep.equal({width: 0, height: 0}); + expect( + impl.extractSize({ + get(name) { + return name == CREATIVE_SIZE_HEADER ? '0x0' : undefined; + }, + has(name) { + return name == CREATIVE_SIZE_HEADER; + }, + }) + ).to.deep.equal({width: 0, height: 0}); expect(resizeSpy).to.not.be.called; }); it('should not load amp-analytics without an analytics header', () => { - expect(impl.extractSize({ - get() { - return undefined; - }, - has() { - return false; - }, - })).to.deep.equal(size); + expect( + impl.extractSize({ + get() { + return undefined; + }, + has() { + return false; + }, + }) + ).to.deep.equal(size); expect(preloadExtensionSpy.withArgs('amp-analytics')).to.not.be.called; }); it('should load amp-analytics with an analytics header', () => { const url = ['https://foo.com?a=b', 'https://blah.com?lsk=sdk&sld=vj']; - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'X-AmpAnalytics': - return JSON.stringify({url}); - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'X-AmpAnalytics': + return JSON.stringify({url}); + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); expect(preloadExtensionSpy.withArgs('amp-analytics')).to.be.called; // exact value of ampAnalyticsConfig covered in // ads/google/test/test-utils.js @@ -216,66 +220,74 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('should load delayed impression amp-pixels with fluid', () => { impl.isFluidRequest_ = true; - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'X-AmpImps': - return 'https://a.com?a=b,https://b.com?c=d'; - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); - expect(impl.fluidImpressionUrl_).to - .equal('https://a.com?a=b,https://b.com?c=d'); + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'X-AmpImps': + return 'https://a.com?a=b,https://b.com?c=d'; + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); + expect(impl.fluidImpressionUrl_).to.equal( + 'https://a.com?a=b,https://b.com?c=d' + ); + }); + it('should not load delayed impression amp-pixels with fluid + multi-size', () => { + sandbox.stub(impl, 'handleResize_'); + impl.isFluid_ = true; + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'X-AmpImps': + return 'https://a.com?a=b,https://b.com?c=d'; + case 'X-CreativeSize': + return '200x50'; + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); + expect(impl.fluidImpressionUrl_).to.not.be.ok; + }); + it('should consume pageview state tokens when header is present', () => { + const removePageviewStateTokenSpy = sandbox.spy( + impl, + 'removePageviewStateToken' + ); + const setPageviewStateTokenSpy = sandbox.spy( + impl, + 'setPageviewStateToken' + ); + expect( + impl.extractSize({ + get(name) { + switch (name) { + case 'amp-ff-pageview-tokens': + return 'DUMMY_TOKEN'; + default: + return undefined; + } + }, + has(name) { + return !!this.get(name); + }, + }) + ).to.deep.equal(size); + expect(removePageviewStateTokenSpy).to.be.calledOnce; + expect(setPageviewStateTokenSpy.withArgs('DUMMY_TOKEN')).to.be.calledOnce; }); - it('should not load delayed impression amp-pixels with fluid + multi-size', - () => { - sandbox.stub(impl, 'handleResize_'); - impl.isFluid_ = true; - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'X-AmpImps': - return 'https://a.com?a=b,https://b.com?c=d'; - case 'X-CreativeSize': - return '200x50'; - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); - expect(impl.fluidImpressionUrl_).to.not.be.ok; - }); - it('should consume pageview state tokens when header is present', - () => { - const removePageviewStateTokenSpy = - sandbox.spy(impl, 'removePageviewStateToken'); - const setPageviewStateTokenSpy = - sandbox.spy(impl, 'setPageviewStateToken'); - expect(impl.extractSize({ - get(name) { - switch (name) { - case 'amp-ff-pageview-tokens': - return 'DUMMY_TOKEN'; - default: - return undefined; - } - }, - has(name) { - return !!this.get(name); - }, - })).to.deep.equal(size); - expect(removePageviewStateTokenSpy).to.be.calledOnce; - expect(setPageviewStateTokenSpy.withArgs('DUMMY_TOKEN')).to.be - .calledOnce; - }); it('should consume sandbox header', () => { impl.extractSize({ @@ -327,65 +339,74 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); [true, false].forEach(exp => { - it('injects amp analytics' + - (exp ? ', trigger immediate disable exp' : ''), () => { - impl.ampAnalyticsConfig_ = { - transport: {beacon: false, xhrpost: false}, - requests: { - visibility1: 'https://foo.com?hello=world', - visibility2: 'https://bar.com?a=b', - }, - triggers: { - continuousVisible: { - on: 'visible', - request: ['visibility1', 'visibility2'], - visibilitySpec: { + it( + 'injects amp analytics' + + (exp ? ', trigger immediate disable exp' : ''), + () => { + impl.ampAnalyticsConfig_ = { + transport: {beacon: false, xhrpost: false}, + requests: { + visibility1: 'https://foo.com?hello=world', + visibility2: 'https://bar.com?a=b', + }, + triggers: { + continuousVisible: { + on: 'visible', + request: ['visibility1', 'visibility2'], + visibilitySpec: { + selector: 'amp-ad', + selectionMethod: 'closest', + visiblePercentageMin: 50, + continuousTimeMin: 1000, + }, + }, + continuousVisibleIniLoad: { + on: 'ini-load', + selector: 'amp-ad', + selectionMethod: 'closest', + }, + continuousVisibleRenderStart: { + on: 'render-start', selector: 'amp-ad', selectionMethod: 'closest', - visiblePercentageMin: 50, - continuousTimeMin: 1000, }, }, - continuousVisibleIniLoad: { - on: 'ini-load', - selector: 'amp-ad', - selectionMethod: 'closest', + }; + // To placate assertion. + impl.responseHeaders_ = { + get: function(name) { + if (name == 'X-QQID') { + return 'qqid_string'; + } }, - continuousVisibleRenderStart: { - on: 'render-start', - selector: 'amp-ad', - selectionMethod: 'closest', + has: function(name) { + if (name == 'X-QQID') { + return true; + } }, - }, - }; - // To placate assertion. - impl.responseHeaders_ = { - get: function(name) { - if (name == 'X-QQID') { - return 'qqid_string'; - } - }, - has: function(name) { - if (name == 'X-QQID') { - return true; - } - }, - }; - if (exp) { - impl.postAdResponseExperimentFeatures['avr_disable_immediate'] = '1'; + }; + if (exp) { + impl.postAdResponseExperimentFeatures['avr_disable_immediate'] = + '1'; + } + impl.onCreativeRender(false); + const ampAnalyticsElement = impl.element.querySelector( + 'amp-analytics' + ); + expect(ampAnalyticsElement).to.be.ok; + expect(ampAnalyticsElement.CONFIG).jsonEqual( + impl.ampAnalyticsConfig_ + ); + expect(ampAnalyticsElement.getAttribute('sandbox')).to.equal('true'); + expect(ampAnalyticsElement.getAttribute('trigger')).to.equal( + exp ? '' : 'immediate' + ); + expect(impl.ampAnalyticsElement_).to.be.ok; + // Exact format of amp-analytics element covered in + // test/unit/test-analytics.js. + // Just ensure extensions is loaded, and analytics element appended. } - impl.onCreativeRender(false); - const ampAnalyticsElement = impl.element.querySelector('amp-analytics'); - expect(ampAnalyticsElement).to.be.ok; - expect(ampAnalyticsElement.CONFIG).jsonEqual(impl.ampAnalyticsConfig_); - expect(ampAnalyticsElement.getAttribute('sandbox')).to.equal('true'); - expect(ampAnalyticsElement.getAttribute('trigger')).to.equal( - exp ? '' : 'immediate'); - expect(impl.ampAnalyticsElement_).to.be.ok; - // Exact format of amp-analytics element covered in - // test/unit/test-analytics.js. - // Just ensure extensions is loaded, and analytics element appended. - }); + ); }); it('should register click listener', () => { @@ -399,23 +420,25 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { e.preventDefault(); // Make the test not actually navigate. clickHandlerCalled++; }; - adBody.innerHTML = '' + - '
    '; + adBody.innerHTML = + '' + + '
    '; const button = adBody.querySelector('#target'); const a = adBody.querySelector('a'); const ev1 = new Event('click', {bubbles: true}); ev1.pageX = 10; ev1.pageY = 20; - sandbox.stub(impl, 'getResource').returns( - { - getUpgradeDelayMs: () => 1, - }); + sandbox.stub(impl, 'getResource').returns({ + getUpgradeDelayMs: () => 1, + }); // Make sure the ad iframe (FIE) has a local URL replacements service. const urlReplacements = Services.urlReplacementsForDoc(element); - sandbox.stub(Services, 'urlReplacementsForDoc') - .withArgs(a).returns(urlReplacements); + sandbox + .stub(Services, 'urlReplacementsForDoc') + .withArgs(a) + .returns(urlReplacements); impl.buildCallback(); impl.size_ = {width: 123, height: 456}; @@ -436,18 +459,18 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { e.preventDefault(); // Make the test not actually navigate. clickHandlerCalled++; }; - adBody.innerHTML = '' + - '
    '; + adBody.innerHTML = + '' + + '
    '; const button = adBody.querySelector('#target'); const a = adBody.querySelector('a'); const ev1 = new Event('click', {bubbles: true}); ev1.pageX = 10; ev1.pageY = 20; - sandbox.stub(impl, 'getResource').returns( - { - getUpgradeDelayMs: () => 1, - }); + sandbox.stub(impl, 'getResource').returns({ + getUpgradeDelayMs: () => 1, + }); impl.buildCallback(); impl.size_ = {width: 123, height: 456}; impl.onCreativeRender({customElementExtensions: ['amp-ad-exit']}); @@ -494,7 +517,12 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { // to fix failures when this is run after 'gulp build', without a 'dist'. sandbox.stub(impl, 'getPageLayoutBox').callsFake(() => { return { - top: 11, left: 12, right: 0, bottom: 0, width: 0, height: 0, + top: 11, + left: 12, + right: 0, + bottom: 0, + width: 0, + height: 0, }; }); }); @@ -594,31 +622,44 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { describe('data-force-safeframe', () => { const fsfRegexp = /(\?|&)fsf=1(&|$)/; - it('handles default', () => expect( - new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.not.match(fsfRegexp)); + it('handles default', () => + expect( + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.not.match(fsfRegexp)); it('case insensitive attribute name', () => { element.setAttribute('data-FORCE-SafeFraMe', '1'); return expect( - new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.match(fsfRegexp); + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.match(fsfRegexp); }); ['tRuE', 'true', 'TRUE', '1'].forEach(val => { it(`valid attribute: ${val}`, () => { element.setAttribute('data-force-safeframe', val); - return expect(new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.match(fsfRegexp); + return expect( + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.match(fsfRegexp); }); }); - ['aTrUe', 'trueB', '0', '01', '10', 'false', '', ' true', 'true ', - ' true '].forEach(val => { + [ + 'aTrUe', + 'trueB', + '0', + '01', + '10', + 'false', + '', + ' true', + 'true ', + ' true ', + ].forEach(val => { it(`invalid attribute: ${val}`, () => { element.setAttribute('data-force-safeframe', val); - return expect(new AmpAdNetworkDoubleclickImpl(element).getAdUrl()) - .to.eventually.not.match(fsfRegexp); + return expect( + new AmpAdNetworkDoubleclickImpl(element).getAdUrl() + ).to.eventually.not.match(fsfRegexp); }); }); }); @@ -640,54 +681,53 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { return impl.getAdUrl().then(url => // With exp dc-use-attr-for-format off, we can't test for specific // numbers, but we know that the values should be numeric. - expect(url).to.match(/sz=[0-9]+x[0-9]+/)); + expect(url).to.match(/sz=[0-9]+x[0-9]+/) + ); + }); + it('has correct format when width == "auto"', () => { + element.setAttribute('width', 'auto'); + new AmpAd(element).upgradeCallback(); + expect(impl.element.getAttribute('width')).to.equal('auto'); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl.getAdUrl().then(url => + // Ensure that "auto" doesn't appear anywhere here: + expect(url).to.match(/sz=[0-9]+x[0-9]+/) + ); + }); + it('has correct format with height/width override', () => { + element.setAttribute('data-override-width', '123'); + element.setAttribute('data-override-height', '456'); + new AmpAd(element).upgradeCallback(); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl.getAdUrl().then(url => expect(url).to.contain('sz=123x456&')); + }); + it('has correct format with height/width override and multiSize', () => { + element.setAttribute('data-override-width', '123'); + element.setAttribute('data-override-height', '456'); + element.setAttribute('data-multi-size', '1x2,3x4'); + element.setAttribute('data-multi-size-validation', 'false'); + new AmpAd(element).upgradeCallback(); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl + .getAdUrl() + .then(url => expect(url).to.contain('sz=123x456%7C1x2%7C3x4&')); + }); + it('has correct format with auto height/width and multiSize', () => { + element.setAttribute('data-override-width', '123'); + element.setAttribute('data-override-height', '456'); + element.setAttribute('data-multi-size', '1x2,3x4'); + element.setAttribute('data-multi-size-validation', 'false'); + new AmpAd(element).upgradeCallback(); + impl.buildCallback(); + impl.onLayoutMeasure(); + return impl.getAdUrl().then(url => + // Ensure that "auto" doesn't appear anywhere here: + expect(url).to.match(/sz=[0-9]+x[0-9]+%7C1x2%7C3x4&/) + ); }); - it('has correct format when width == "auto"', - () => { - element.setAttribute('width', 'auto'); - new AmpAd(element).upgradeCallback(); - expect(impl.element.getAttribute('width')).to.equal('auto'); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - // Ensure that "auto" doesn't appear anywhere here: - expect(url).to.match(/sz=[0-9]+x[0-9]+/)); - }); - it('has correct format with height/width override', - () => { - element.setAttribute('data-override-width', '123'); - element.setAttribute('data-override-height', '456'); - new AmpAd(element).upgradeCallback(); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - expect(url).to.contain('sz=123x456&')); - }); - it('has correct format with height/width override and multiSize', - () => { - element.setAttribute('data-override-width', '123'); - element.setAttribute('data-override-height', '456'); - element.setAttribute('data-multi-size', '1x2,3x4'); - element.setAttribute('data-multi-size-validation', 'false'); - new AmpAd(element).upgradeCallback(); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - expect(url).to.contain('sz=123x456%7C1x2%7C3x4&')); - }); - it('has correct format with auto height/width and multiSize', - () => { - element.setAttribute('data-override-width', '123'); - element.setAttribute('data-override-height', '456'); - element.setAttribute('data-multi-size', '1x2,3x4'); - element.setAttribute('data-multi-size-validation', 'false'); - new AmpAd(element).upgradeCallback(); - impl.buildCallback(); - impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - // Ensure that "auto" doesn't appear anywhere here: - expect(url).to.match(/sz=[0-9]+x[0-9]+%7C1x2%7C3x4&/)); - }); it('has correct sz with fluid as multi-size', () => { element.setAttribute('width', '300'); element.setAttribute('height', '250'); @@ -695,8 +735,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { new AmpAd(element).upgradeCallback(); impl.buildCallback(); impl.onLayoutMeasure(); - return impl.getAdUrl().then(url => - expect(url).to.match(/sz=300x250%7C320x50&/)); + return impl + .getAdUrl() + .then(url => expect(url).to.match(/sz=300x250%7C320x50&/)); }); it('should have the correct ifi numbers - no refresh', function() { // When ran locally, this test tends to exceed 2000ms timeout. @@ -718,8 +759,10 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); }); it('should have google_preview parameter', () => { - sandbox.stub(impl, 'getLocationQueryParameterValue') - .withArgs('google_preview').returns('abcdef'); + sandbox + .stub(impl, 'getLocationQueryParameterValue') + .withArgs('google_preview') + .returns('abcdef'); new AmpAd(element).upgradeCallback(); expect(impl.getAdUrl()).to.eventually.contain('&gct=abcdef'); }); @@ -734,8 +777,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { // We don't really care about the behavior of the following methods, so // we'll just stub them out so that refresh() can run without tripping any // unrelated errors. - sandbox.stub(AmpA4A.prototype, 'initiateAdRequest').callsFake( - () => impl.adPromise_ = Promise.resolve()); + sandbox + .stub(AmpA4A.prototype, 'initiateAdRequest') + .callsFake(() => (impl.adPromise_ = Promise.resolve())); const tearDownSlotMock = sandbox.stub(AmpA4A.prototype, 'tearDownSlot'); tearDownSlotMock.returns(undefined); const destroyFrameMock = sandbox.stub(AmpA4A.prototype, 'destroyFrame'); @@ -753,13 +797,15 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { return impl.getAdUrl().then(url1 => { expect(url1).to.not.match(/(\?|&)rc=[0-9]+(&|$)/); expect(url1).to.match(/(\?|&)ifi=1(&|$)/); - return impl.refresh(() => {}).then(() => { - return impl.getAdUrl().then(url2 => { - expect(url2).to.match(/(\?|&)rc=1(&|$)/); - expect(url1).to.match(/(\?|&)ifi=1(&|$)/); - expect(url2).to.not.match(/(\?|&)frc=1(&|$)/); + return impl + .refresh(() => {}) + .then(() => { + return impl.getAdUrl().then(url2 => { + expect(url2).to.match(/(\?|&)rc=1(&|$)/); + expect(url1).to.match(/(\?|&)ifi=1(&|$)/); + expect(url2).to.not.match(/(\?|&)frc=1(&|$)/); + }); }); - }); }); }); it('has correct frc value', () => { @@ -770,16 +816,19 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should include identity', () => { // Force get identity result by overloading window variable. - const token = /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({ - token: 'abcdef', jar: 'some_jar', pucrd: 'some_pucrd', + const token = /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/ ({ + token: 'abcdef', + jar: 'some_jar', + pucrd: 'some_pucrd', }); impl.win['goog_identity_prom'] = Promise.resolve(token); impl.buildCallback(); return impl.getAdUrl().then(url => { - [/(\?|&)adsid=abcdef(&|$)/, + [ + /(\?|&)adsid=abcdef(&|$)/, /(\?|&)jar=some_jar(&|$)/, - /(\?|&)pucrd=some_pucrd(&|$)/].forEach( - regexp => expect(url).to.match(regexp)); + /(\?|&)pucrd=some_pucrd(&|$)/, + ].forEach(regexp => expect(url).to.match(regexp)); }); }); @@ -812,8 +861,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { })); it('should include msz/psz if in experiment', () => { - sandbox.stub(impl, 'randomlySelectUnsetExperiments_').returns( - {flexAdSlots: '21063174'}); + sandbox + .stub(impl, 'randomlySelectUnsetExperiments_') + .returns({flexAdSlots: '21063174'}); impl.setPageLevelExperiments(); return impl.getAdUrl().then(url => { expect(url).to.match(/(\?|&)msz=[0-9]+x-1(&|$)/); @@ -823,8 +873,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should not include msz/psz if not in flexAdSlots control', () => { - sandbox.stub(impl, 'randomlySelectUnsetExperiments_').returns( - {flexAdSlots: '21063173'}); + sandbox + .stub(impl, 'randomlySelectUnsetExperiments_') + .returns({flexAdSlots: '21063173'}); impl.setPageLevelExperiments(); return impl.getAdUrl().then(url => { expect(url).to.not.match(/(\?|&)msz=/); @@ -851,8 +902,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { 'data-slot': '/1234/abc/def', }); const impl = new AmpAdNetworkDoubleclickImpl(element); - expect(impl.getPageParameters( - CONSENT_POLICY_STATE.INSUFFICIENT).npa).to.equal(1); + expect( + impl.getPageParameters(CONSENT_POLICY_STATE.INSUFFICIENT).npa + ).to.equal(1); }); }); @@ -860,10 +912,15 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { let sandbox; beforeEach(() => { - const setup = createImplTag({ - width: '300', - height: '150', - }, element, impl, env); + const setup = createImplTag( + { + width: '300', + height: '150', + }, + element, + impl, + env + ); element = setup[0]; impl = setup[1]; env = setup[2]; @@ -896,28 +953,27 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { expect(impl.iframe).is.ok; }); - it('should call #resetSlot, remove child iframe, but keep other children', - () => { - impl.ampAnalyticsConfig_ = {}; - impl.ampAnalyticsElement_ = - doc.createElement('amp-analytics'); - impl.element.appendChild(impl.ampAnalyticsElement_); - expect(impl.iframe).to.be.ok; - expect(impl.element.querySelector('iframe')).to.be.ok; - impl.unlayoutCallback(); - expect(impl.element.querySelector('div[placeholder]')).to.be.ok; - expect(impl.element.querySelector('div[fallback]')).to.be.ok; - expect(impl.element.querySelector('iframe')).to.be.null; - expect(impl.element.querySelectorAll('amp-analytics')) - .to.have.lengthOf(1); - expect(impl.element.querySelector('amp-analytics')).to.equal( - impl.a4aAnalyticsElement_); - expect(impl.iframe).to.be.null; - expect(impl.ampAnalyticsConfig_).to.be.null; - expect(impl.ampAnalyticsElement_).to.be.null; - expect(impl.element.getAttribute('data-amp-slot-index')) - .to.equal('1'); - }); + it('should call #resetSlot, remove child iframe, but keep other children', () => { + impl.ampAnalyticsConfig_ = {}; + impl.ampAnalyticsElement_ = doc.createElement('amp-analytics'); + impl.element.appendChild(impl.ampAnalyticsElement_); + expect(impl.iframe).to.be.ok; + expect(impl.element.querySelector('iframe')).to.be.ok; + impl.unlayoutCallback(); + expect(impl.element.querySelector('div[placeholder]')).to.be.ok; + expect(impl.element.querySelector('div[fallback]')).to.be.ok; + expect(impl.element.querySelector('iframe')).to.be.null; + expect(impl.element.querySelectorAll('amp-analytics')).to.have.lengthOf( + 1 + ); + expect(impl.element.querySelector('amp-analytics')).to.equal( + impl.a4aAnalyticsElement_ + ); + expect(impl.iframe).to.be.null; + expect(impl.ampAnalyticsConfig_).to.be.null; + expect(impl.ampAnalyticsElement_).to.be.null; + expect(impl.element.getAttribute('data-amp-slot-index')).to.equal('1'); + }); it('should call #unobserve on refreshManager', () => { impl.refreshManager_ = { @@ -967,9 +1023,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { * it has an AMP creative. */ function stubForAmpCreative() { - sandbox.stub( - signatureVerifierFor(impl.win), 'verify').callsFake( - () => Promise.resolve(VerificationStatus.OK)); + sandbox + .stub(signatureVerifierFor(impl.win), 'verify') + .callsFake(() => Promise.resolve(VerificationStatus.OK)); } /** @@ -978,8 +1034,10 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { */ function mockSendXhrRequest(size, isAmpCreative) { return { - arrayBuffer: () => Promise.resolve(utf8Encode( - 'Hello, World!')), + arrayBuffer: () => + Promise.resolve( + utf8Encode('Hello, World!') + ), headers: { get(prop) { switch (prop) { @@ -1042,20 +1100,24 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { headers: {'Content-Type': 'application/jwk-set+json'}, }; env.expectFetch( - 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', - keyResponse); + 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', + keyResponse + ); env.expectFetch( - 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', - keyResponse); + 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', + keyResponse + ); }); it('amp creative - should force iframe to match size of creative', () => { stubForAmpCreative(); - sandbox.stub(impl, 'sendXhrRequest').returns( - mockSendXhrRequest('150x50', true)); + sandbox + .stub(impl, 'sendXhrRequest') + .returns(mockSendXhrRequest('150x50', true)); // Stub ini load otherwise FIE could delay test - sandbox./*OK*/stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .returns(Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .returns(Promise.resolve()); impl.buildCallback(); impl.onLayoutMeasure(); return impl.layoutCallback().then(() => { @@ -1067,8 +1129,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should force iframe to match size of creative', () => { - sandbox.stub(impl, 'sendXhrRequest').returns( - mockSendXhrRequest('150x50', false)); + sandbox + .stub(impl, 'sendXhrRequest') + .returns(mockSendXhrRequest('150x50', false)); impl.buildCallback(); impl.onLayoutMeasure(); return impl.layoutCallback().then(() => { @@ -1081,13 +1144,18 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('amp creative - should force iframe to match size of slot', () => { stubForAmpCreative(); - sandbox.stub(impl, 'sendXhrRequest').callsFake( - () => mockSendXhrRequest(undefined, true)); - sandbox.stub(impl, 'renderViaIframeGet_').callsFake( - () => impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'})); + sandbox + .stub(impl, 'sendXhrRequest') + .callsFake(() => mockSendXhrRequest(undefined, true)); + sandbox + .stub(impl, 'renderViaIframeGet_') + .callsFake(() => + impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'}) + ); // Stub ini load otherwise FIE could delay test - sandbox./*OK*/stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .returns(Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .returns(Promise.resolve()); // This would normally be set in AmpA4a#buildCallback. impl.creativeSize_ = {width: 200, height: 50}; impl.buildCallback(); @@ -1101,10 +1169,14 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }); it('should force iframe to match size of slot', () => { - sandbox.stub(impl, 'sendXhrRequest').callsFake( - () => mockSendXhrRequest(undefined, false)); - sandbox.stub(impl, 'renderViaIframeGet_').callsFake( - () => impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'})); + sandbox + .stub(impl, 'sendXhrRequest') + .callsFake(() => mockSendXhrRequest(undefined, false)); + sandbox + .stub(impl, 'renderViaIframeGet_') + .callsFake(() => + impl.iframeRenderHelper_({src: impl.adUrl_, name: 'name'}) + ); // This would normally be set in AmpA4a#buildCallback. impl.creativeSize_ = {width: 200, height: 50}; impl.buildCallback(); @@ -1119,12 +1191,14 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('should issue an ad request even with bad multi-size data attr', () => { stubForAmpCreative(); - sandbox.stub(impl, 'sendXhrRequest').callsFake( - () => mockSendXhrRequest(undefined, true)); + sandbox + .stub(impl, 'sendXhrRequest') + .callsFake(() => mockSendXhrRequest(undefined, true)); impl.element.setAttribute('data-multi-size', '201x50'); // Stub ini load otherwise FIE could delay test - sandbox./*OK*/stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') - .returns(Promise.resolve()); + sandbox + ./*OK*/ stub(FriendlyIframeEmbed.prototype, 'whenIniLoaded') + .returns(Promise.resolve()); impl.buildCallback(); impl.onLayoutMeasure(); return impl.layoutCallback().then(() => { @@ -1173,8 +1247,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { expect(gutData.events[0].slotid).to.equal(slotId + '_0'); expect(gutData.events[0].messageId).to.equal(4); - expect(gutData.slots[0].contentUrl).to - .equal('http://www.getmesomeads.com'); + expect(gutData.slots[0].contentUrl).to.equal( + 'http://www.getmesomeads.com' + ); expect(gutData.slots[0].id).to.equal(slotId + '_0'); expect(gutData.slots[0].leafAdUnitName).to.equal(slotId); expect(gutData.slots[0].domId).to.equal(slotId + '_0'); @@ -1185,8 +1260,9 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { }; const postMessageSpy = sandbox.spy(env.win.opener, 'postMessage'); impl.win = env.win; - return impl.postTroubleshootMessage().then(() => - expect(postMessageSpy).to.be.calledOnce); + return impl + .postTroubleshootMessage() + .then(() => expect(postMessageSpy).to.be.calledOnce); }); it('should not emit post message', () => { @@ -1221,375 +1297,431 @@ describes.realWin('amp-ad-network-doubleclick-impl', realWinConfig, env => { it('should return safeframe if fluid', () => { impl.isLayoutSupported(Layout.FLUID); - expect(impl.getNonAmpCreativeRenderingMethod()).to - .equal(XORIGIN_MODE.SAFEFRAME); + expect(impl.getNonAmpCreativeRenderingMethod()).to.equal( + XORIGIN_MODE.SAFEFRAME + ); }); it('should return safeframe if force safeframe', () => { element.setAttribute('data-force-safeframe', '1'); - expect(new AmpAdNetworkDoubleclickImpl(element) - .getNonAmpCreativeRenderingMethod()).to.equal(XORIGIN_MODE.SAFEFRAME); + expect( + new AmpAdNetworkDoubleclickImpl( + element + ).getNonAmpCreativeRenderingMethod() + ).to.equal(XORIGIN_MODE.SAFEFRAME); }); }); }); +describes.realWin( + 'additional amp-ad-network-doubleclick-impl', + realWinConfigAmpAd, + env => { + let doc; + let impl; + let element; -describes.realWin('additional amp-ad-network-doubleclick-impl', - realWinConfigAmpAd, env => { - let doc; - let impl; - let element; + beforeEach(() => { + doc = env.win.document; + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', + }); + doc.body.appendChild(element); + impl = new AmpAdNetworkDoubleclickImpl(element); + }); + describe('#onNetworkFailure', () => { beforeEach(() => { - doc = env.win.document; element = createElementWithAttributes(doc, 'amp-ad', { 'width': '200', 'height': '50', 'type': 'doubleclick', }); - doc.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl(element); }); - describe('#onNetworkFailure', () => { - - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - }); - - it('should append error parameter', () => { - const TEST_URL = 'https://somenetwork.com/foo?hello=world&a=b'; - expect(impl.onNetworkFailure(new Error('xhr failure'), TEST_URL)) - .to.jsonEqual({adUrl: TEST_URL + '&aet=n'}); - }); + it('should append error parameter', () => { + const TEST_URL = 'https://somenetwork.com/foo?hello=world&a=b'; + expect( + impl.onNetworkFailure(new Error('xhr failure'), TEST_URL) + ).to.jsonEqual({adUrl: TEST_URL + '&aet=n'}); }); + }); - describe('centering', () => { - const size = {width: '300px', height: '150px'}; - - /** - * @param {!Element} iframe - * @param {{width: number, height: number}} expectedSize - */ - function verifyCss(iframe, expectedSize) { - expect(iframe).to.be.ok; - const style = env.win.getComputedStyle(iframe); - expect(style.top).to.equal('50%'); - expect(style.left).to.equal('50%'); - expect(style.width).to.equal(expectedSize.width); - expect(style.height).to.equal(expectedSize.height); - // We don't know the exact values by which the frame will be - // translated, as this can vary depending on whether we use the - // height/width attributes, or the actual size of the frame. To make - // this less of a hassle, we'll just match against regexp. - expect(style.transform).to.match(new RegExp( - 'matrix\\(1, 0, 0, 1, -[0-9]+, -[0-9]+\\)')); - } - - afterEach(() => env.win.document.body.removeChild(impl.element)); + describe('centering', () => { + const size = {width: '300px', height: '150px'}; - it('centers iframe in slot when height && width', () => { - const setup = createImplTag({ + /** + * @param {!Element} iframe + * @param {{width: number, height: number}} expectedSize + */ + function verifyCss(iframe, expectedSize) { + expect(iframe).to.be.ok; + const style = env.win.getComputedStyle(iframe); + expect(style.top).to.equal('50%'); + expect(style.left).to.equal('50%'); + expect(style.width).to.equal(expectedSize.width); + expect(style.height).to.equal(expectedSize.height); + // We don't know the exact values by which the frame will be + // translated, as this can vary depending on whether we use the + // height/width attributes, or the actual size of the frame. To make + // this less of a hassle, we'll just match against regexp. + expect(style.transform).to.match( + new RegExp('matrix\\(1, 0, 0, 1, -[0-9]+, -[0-9]+\\)') + ); + } + + afterEach(() => env.win.document.body.removeChild(impl.element)); + + it('centers iframe in slot when height && width', () => { + const setup = createImplTag( + { width: '300', height: '150', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.equal('300'); - expect(impl.element.getAttribute('height')).to.equal('150'); - verifyCss(impl.iframe, size); - }); - it('centers iframe in slot when !height && !width', () => { - const setup = createImplTag({ + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.equal('300'); + expect(impl.element.getAttribute('height')).to.equal('150'); + verifyCss(impl.iframe, size); + }); + it('centers iframe in slot when !height && !width', () => { + const setup = createImplTag( + { layout: 'fixed', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.be.null; - expect(impl.element.getAttribute('height')).to.be.null; - verifyCss(impl.iframe, size); - }); - it('centers iframe in slot when !height && width', () => { - const setup = createImplTag({ + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.be.null; + expect(impl.element.getAttribute('height')).to.be.null; + verifyCss(impl.iframe, size); + }); + it('centers iframe in slot when !height && width', () => { + const setup = createImplTag( + { width: '300', layout: 'fixed', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.equal('300'); - expect(impl.element.getAttribute('height')).to.be.null; - verifyCss(impl.iframe, size); - }); - it('centers iframe in slot when height && !width', () => { - const setup = createImplTag({ + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.equal('300'); + expect(impl.element.getAttribute('height')).to.be.null; + verifyCss(impl.iframe, size); + }); + it('centers iframe in slot when height && !width', () => { + const setup = createImplTag( + { height: '150', layout: 'fixed', - }, element, impl, env); - element = setup[0]; - impl = setup[1]; - env = setup[2]; - expect(impl.element.getAttribute('width')).to.be.null; - expect(impl.element.getAttribute('height')).to.equal('150'); - verifyCss(impl.iframe, size); - }); + }, + element, + impl, + env + ); + element = setup[0]; + impl = setup[1]; + env = setup[2]; + expect(impl.element.getAttribute('width')).to.be.null; + expect(impl.element.getAttribute('height')).to.equal('150'); + verifyCss(impl.iframe, size); }); + }); - describe('#fireDelayedImpressions', () => { - let isSecureStub; - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - impl.getAmpDoc = () => {}; - isSecureStub = sandbox.stub(); - sandbox.stub(Services, 'urlForDoc').returns({isSecure: isSecureStub}); - }); - - it('should handle null impressions', () => { - impl.fireDelayedImpressions(null); - expect(env.win.document.querySelectorAll('amp-pixel').length) - .to.equal(0); + describe('#fireDelayedImpressions', () => { + let isSecureStub; + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + impl = new AmpAdNetworkDoubleclickImpl(element); + impl.getAmpDoc = () => {}; + isSecureStub = sandbox.stub(); + sandbox.stub(Services, 'urlForDoc').returns({isSecure: isSecureStub}); + }); - it('should not include non-https', () => { - const urls = ['http://f.com?a=b', 'https://b.net?c=d']; - isSecureStub.withArgs(urls[0]).returns(false); - isSecureStub.withArgs(urls[1]).returns(true); - impl.fireDelayedImpressions(urls.join()); - expect(env.win.document.querySelectorAll('amp-pixel').length) - .to.equal(1); - expect(env.win.document.querySelector( - `amp-pixel[src="${urls[1]}"][referrerpolicy=""]`)) - .to.be.ok; - }); + it('should handle null impressions', () => { + impl.fireDelayedImpressions(null); + expect(env.win.document.querySelectorAll('amp-pixel').length).to.equal( + 0 + ); + }); - it('should append amp-pixel w/o scrubReferer', () => { - const urls = ['https://f.com?a=b', 'https://b.net?c=d']; - isSecureStub.returns(true); - impl.fireDelayedImpressions(urls.join()); - urls.forEach(url => expect(env.win.document.querySelector( - `amp-pixel[src="${url}"][referrerpolicy=""]`)).to.be.ok); - }); + it('should not include non-https', () => { + const urls = ['http://f.com?a=b', 'https://b.net?c=d']; + isSecureStub.withArgs(urls[0]).returns(false); + isSecureStub.withArgs(urls[1]).returns(true); + impl.fireDelayedImpressions(urls.join()); + expect(env.win.document.querySelectorAll('amp-pixel').length).to.equal( + 1 + ); + expect( + env.win.document.querySelector( + `amp-pixel[src="${urls[1]}"][referrerpolicy=""]` + ) + ).to.be.ok; + }); - it('should append amp-pixel with scrubReferer', () => { - const urls = ['https://f.com?a=b', 'https://b.net?c=d']; - isSecureStub.returns(true); - impl.fireDelayedImpressions(urls.join(), true); - urls.forEach(url => expect(env.win.document.querySelector( - `amp-pixel[src="${url}"][referrerpolicy="no-referrer"]`)) - .to.be.ok); - }); + it('should append amp-pixel w/o scrubReferer', () => { + const urls = ['https://f.com?a=b', 'https://b.net?c=d']; + isSecureStub.returns(true); + impl.fireDelayedImpressions(urls.join()); + urls.forEach( + url => + expect( + env.win.document.querySelector( + `amp-pixel[src="${url}"][referrerpolicy=""]` + ) + ).to.be.ok + ); }); - describe('#idleRenderOutsideViewport', () => { - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - sandbox.stub(impl, 'getResource').returns( - {whenWithinViewport: () => Promise.resolve()}); - }); + it('should append amp-pixel with scrubReferer', () => { + const urls = ['https://f.com?a=b', 'https://b.net?c=d']; + isSecureStub.returns(true); + impl.fireDelayedImpressions(urls.join(), true); + urls.forEach( + url => + expect( + env.win.document.querySelector( + `amp-pixel[src="${url}"][referrerpolicy="no-referrer"]` + ) + ).to.be.ok + ); + }); + }); - it('should use experiment value', () => { - impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; - expect(impl.idleRenderOutsideViewport()).to.equal(4); - expect(impl.isIdleRender_).to.be.true; + describe('#idleRenderOutsideViewport', () => { + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + impl = new AmpAdNetworkDoubleclickImpl(element); + sandbox + .stub(impl, 'getResource') + .returns({whenWithinViewport: () => Promise.resolve()}); + }); - it('should return false if using loading strategy', () => { - impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; - impl.element.setAttribute('data-loading-strategy', - 'prefer-viewability-over-views'); - expect(impl.idleRenderOutsideViewport()).to.be.false; - expect(impl.isIdleRender_).to.be.false; - }); + it('should use experiment value', () => { + impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; + expect(impl.idleRenderOutsideViewport()).to.equal(4); + expect(impl.isIdleRender_).to.be.true; + }); - it('should return false if invalid experiment value', () => { - impl.postAdResponseExperimentFeatures['render-idle-vp'] = 'abc'; - expect(impl.idleRenderOutsideViewport()).to.be.false; - }); + it('should return false if using loading strategy', () => { + impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; + impl.element.setAttribute( + 'data-loading-strategy', + 'prefer-viewability-over-views' + ); + expect(impl.idleRenderOutsideViewport()).to.be.false; + expect(impl.isIdleRender_).to.be.false; + }); - it('should return 12 if no experiment header', () => { - expect(impl.idleRenderOutsideViewport()).to.equal(12); - }); + it('should return false if invalid experiment value', () => { + impl.postAdResponseExperimentFeatures['render-idle-vp'] = 'abc'; + expect(impl.idleRenderOutsideViewport()).to.be.false; + }); - it('should return renderOutsideViewport boolean', () => { - sandbox.stub(impl, 'renderOutsideViewport').returns(false); - expect(impl.idleRenderOutsideViewport()).to.be.false; - }); + it('should return 12 if no experiment header', () => { + expect(impl.idleRenderOutsideViewport()).to.equal(12); }); - describe('idle renderNonAmpCreative', () => { + it('should return renderOutsideViewport boolean', () => { + sandbox.stub(impl, 'renderOutsideViewport').returns(false); + expect(impl.idleRenderOutsideViewport()).to.be.false; + }); + }); - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - impl = new AmpAdNetworkDoubleclickImpl(element); - impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; - impl.postAdResponseExperimentFeatures['render-idle-throttle'] = - 'true'; - sandbox.stub(AmpA4A.prototype, 'renderNonAmpCreative') - .returns(Promise.resolve()); + describe('idle renderNonAmpCreative', () => { + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + impl = new AmpAdNetworkDoubleclickImpl(element); + impl.postAdResponseExperimentFeatures['render-idle-vp'] = '4'; + impl.postAdResponseExperimentFeatures['render-idle-throttle'] = 'true'; + sandbox + .stub(AmpA4A.prototype, 'renderNonAmpCreative') + .returns(Promise.resolve()); + }); - // TODO(jeffkaufman, #13422): this test was silently failing - it.skip('should throttle if idle render and non-AMP creative', () => { - impl.win['3pla'] = 1; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.least(1000); - }); + // TODO(jeffkaufman, #13422): this test was silently failing + it.skip('should throttle if idle render and non-AMP creative', () => { + impl.win['3pla'] = 1; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.least(1000); }); + }); - it('should NOT throttle if idle experiment not enabled', () => { - impl.win['3pla'] = 1; - delete impl.postAdResponseExperimentFeatures['render-idle-vp']; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.most(50); - }); + it('should NOT throttle if idle experiment not enabled', () => { + impl.win['3pla'] = 1; + delete impl.postAdResponseExperimentFeatures['render-idle-vp']; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.most(50); }); + }); - it('should NOT throttle if experiment throttle not enabled', () => { - impl.win['3pla'] = 1; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.most(50); - }); + it('should NOT throttle if experiment throttle not enabled', () => { + impl.win['3pla'] = 1; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.most(50); }); + }); - it('should NOT throttle if idle render and no previous', () => { - impl.win['3pla'] = 0; - const startTime = Date.now(); - return impl.renderNonAmpCreative().then(() => { - expect(Date.now() - startTime).to.be.at.most(50); - }); + it('should NOT throttle if idle render and no previous', () => { + impl.win['3pla'] = 0; + const startTime = Date.now(); + return impl.renderNonAmpCreative().then(() => { + expect(Date.now() - startTime).to.be.at.most(50); }); }); + }); - describe('#preconnect', () => { - beforeEach(() => { - element = createElementWithAttributes(doc, 'amp-ad', { - 'width': '200', - 'height': '50', - 'type': 'doubleclick', - }); - doc.body.appendChild(element); - impl = new AmpAdNetworkDoubleclickImpl(element); + describe('#preconnect', () => { + beforeEach(() => { + element = createElementWithAttributes(doc, 'amp-ad', { + 'width': '200', + 'height': '50', + 'type': 'doubleclick', }); + doc.body.appendChild(element); + impl = new AmpAdNetworkDoubleclickImpl(element); }); + }); - describe('#getConsentPolicy', () => { - it('should return null', () => expect( - AmpAdNetworkDoubleclickImpl.prototype.getConsentPolicy()) - .to.be.null); + describe('#getConsentPolicy', () => { + it('should return null', () => + expect(AmpAdNetworkDoubleclickImpl.prototype.getConsentPolicy()).to.be + .null); + }); + + describe('#setPageLevelExperiments', () => { + let randomlySelectUnsetExperimentsStub; + let extractUrlExperimentIdStub; + beforeEach(() => { + randomlySelectUnsetExperimentsStub = sandbox.stub( + impl, + 'randomlySelectUnsetExperiments_' + ); + extractUrlExperimentIdStub = sandbox.stub( + impl, + 'extractUrlExperimentId_' + ); + sandbox.stub(AmpA4A.prototype, 'buildCallback').callsFake(() => {}); + sandbox.stub(impl, 'getAmpDoc').returns({}); + sandbox + .stub(Services, 'viewerForDoc') + .returns({whenFirstVisible: () => new Deferred().promise}); + }); + afterEach(() => { + toggleExperiment(env.win, 'envDfpInvOrigDeprecated', false); }); - describe('#setPageLevelExperiments', () => { - let randomlySelectUnsetExperimentsStub; - let extractUrlExperimentIdStub; - beforeEach(() => { - randomlySelectUnsetExperimentsStub = - sandbox.stub(impl, 'randomlySelectUnsetExperiments_'); - extractUrlExperimentIdStub = - sandbox.stub(impl, 'extractUrlExperimentId_'); - sandbox.stub(AmpA4A.prototype, 'buildCallback').callsFake(() => {}); - sandbox.stub(impl, 'getAmpDoc').returns({}); - sandbox.stub(Services, 'viewerForDoc').returns( - {whenFirstVisible: () => new Deferred().promise}); - }); - afterEach(() => { - toggleExperiment(env.win, 'envDfpInvOrigDeprecated', false); - }); + it('should set invalid origin fix experiment if on canonical', () => { + randomlySelectUnsetExperimentsStub.returns({}); + impl.setPageLevelExperiments(); + expect(impl.experimentIds.includes('21060933')).to.be.true; + }); - it('should set invalid origin fix experiment if on canonical', () => { - randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments(); - expect(impl.experimentIds.includes('21060933')).to.be.true; - }); + it('should not set invalid origin fix if exp on', () => { + toggleExperiment(env.win, 'envDfpInvOrigDeprecated', true); + randomlySelectUnsetExperimentsStub.returns({}); + impl.setPageLevelExperiments(); + expect(impl.experimentIds.includes('21060933')).to.be.true; + }); - it('should not set invalid origin fix if exp on', () => { - toggleExperiment(env.win, 'envDfpInvOrigDeprecated', true); - randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments(); - expect(impl.experimentIds.includes('21060933')).to.be.true; + it('should select SRA experiments', () => { + randomlySelectUnsetExperimentsStub.returns({ + doubleclickSraExp: '117152667', }); + extractUrlExperimentIdStub.returns(undefined); + impl.buildCallback(); + expect(impl.experimentIds.includes('117152667')).to.be.true; + expect(impl.useSra).to.be.true; + }); - it('should select SRA experiments', () => { - randomlySelectUnsetExperimentsStub.returns( - {doubleclickSraExp: '117152667'}); - extractUrlExperimentIdStub.returns(undefined); - impl.buildCallback(); - expect(impl.experimentIds.includes('117152667')).to.be.true; - expect(impl.useSra).to.be.true; - }); + it('should force-select SRA experiment from URL experiment ID', () => { + randomlySelectUnsetExperimentsStub.returns({}); + impl.setPageLevelExperiments('8'); + expect(impl.experimentIds.includes('117152667')).to.be.true; + }); - it('should force-select SRA experiment from URL experiment ID', () => { + describe('should properly limit SRA traffic', () => { + let experimentInfoMap; + beforeEach(() => { randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments('8'); - expect(impl.experimentIds.includes('117152667')).to.be.true; + impl.setPageLevelExperiments(); + // args format is call array followed by parameter array so expect + // first call, first param. + experimentInfoMap = + randomlySelectUnsetExperimentsStub.args[0][0]['doubleclickSraExp']; + expect(experimentInfoMap).to.be.ok; + expect(impl.useSra).to.be.false; }); - describe('should properly limit SRA traffic', () => { - let experimentInfoMap; - beforeEach(() => { - randomlySelectUnsetExperimentsStub.returns({}); - impl.setPageLevelExperiments(); - // args format is call array followed by parameter array so expect - // first call, first param. - experimentInfoMap = randomlySelectUnsetExperimentsStub - .args[0][0]['doubleclickSraExp']; - expect(experimentInfoMap).to.be.ok; - expect(impl.useSra).to.be.false; - }); - - it('should allow by default', () => - expect(experimentInfoMap.isTrafficEligible()).to.be.true); + it('should allow by default', () => + expect(experimentInfoMap.isTrafficEligible()).to.be.true); - it('should not allow if refresh meta', () => { - doc.head.appendChild(createElementWithAttributes( - doc, 'meta', {name: 'amp-ad-enable-refresh'})); - expect(experimentInfoMap.isTrafficEligible()).to.be.false; - }); + it('should not allow if refresh meta', () => { + doc.head.appendChild( + createElementWithAttributes(doc, 'meta', { + name: 'amp-ad-enable-refresh', + }) + ); + expect(experimentInfoMap.isTrafficEligible()).to.be.false; + }); - it('should not allow if sra meta', () => { - doc.head.appendChild(createElementWithAttributes( - doc, 'meta', {name: 'amp-ad-doubleclick-sra'})); - expect(experimentInfoMap.isTrafficEligible()).to.be.false; - }); + it('should not allow if sra meta', () => { + doc.head.appendChild( + createElementWithAttributes(doc, 'meta', { + name: 'amp-ad-doubleclick-sra', + }) + ); + expect(experimentInfoMap.isTrafficEligible()).to.be.false; + }); - it('should not allow if block level refresh', () => { - impl.element.setAttribute('data-enable-refresh', ''); - expect(experimentInfoMap.isTrafficEligible()).to.be.false; - }); + it('should not allow if block level refresh', () => { + impl.element.setAttribute('data-enable-refresh', ''); + expect(experimentInfoMap.isTrafficEligible()).to.be.false; }); }); + }); - describe('#getPageviewStateTokensForAdRequest', () => { - - beforeEach(() => { - resetTokensToInstancesMap(); - }); + describe('#getPageviewStateTokensForAdRequest', () => { + beforeEach(() => { + resetTokensToInstancesMap(); + }); - it('should return the tokens associated with instances that are not ' + - 'passed to it as an argument', () => { + it( + 'should return the tokens associated with instances that are not ' + + 'passed to it as an argument', + () => { const element1 = doc.createElement('amp-ad'); element1.setAttribute('type', 'doubleclick'); element1.setAttribute('data-ad-client', 'doubleclick'); @@ -1601,69 +1733,82 @@ describes.realWin('additional amp-ad-network-doubleclick-impl', const impl2 = new AmpAdNetworkDoubleclickImpl(element2); impl2.setPageviewStateToken('DUMMY_TOKEN_2'); const instances = [impl1]; - expect(getPageviewStateTokensForAdRequest(instances)).to.deep.equal( - ['DUMMY_TOKEN_2']); - }); - }); + expect(getPageviewStateTokensForAdRequest(instances)).to.deep.equal([ + 'DUMMY_TOKEN_2', + ]); + } + ); + }); - describe('#checksumVerification', () => { - it('should call super if missing Algorithm header', () => { - sandbox.stub(AmpA4A.prototype, 'maybeValidateAmpCreative') - .returns(Promise.resolve('foo')); - const creative = 'This is some text'; - const mockHeaders = { - get: key => { - switch (key) { - case 'AMP-Verification-Checksum-Algorithm': - return 'unknown'; - case 'AMP-Verification-Checksum': - return '2569076912'; - default: - throw new Error(`unexpected header: ${key}`); - } - }, - }; - expect(AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( - utf8Encode(creative), mockHeaders)).to.eventually.equal('foo'); - }); + describe('#checksumVerification', () => { + it('should call super if missing Algorithm header', () => { + sandbox + .stub(AmpA4A.prototype, 'maybeValidateAmpCreative') + .returns(Promise.resolve('foo')); + const creative = 'This is some text'; + const mockHeaders = { + get: key => { + switch (key) { + case 'AMP-Verification-Checksum-Algorithm': + return 'unknown'; + case 'AMP-Verification-Checksum': + return '2569076912'; + default: + throw new Error(`unexpected header: ${key}`); + } + }, + }; + expect( + AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( + utf8Encode(creative), + mockHeaders + ) + ).to.eventually.equal('foo'); + }); - it('should properly validate checksum', () => { - const creative = 'This is some text'; - const mockHeaders = { - get: key => { - switch (key) { - case 'AMP-Verification-Checksum-Algorithm': - return 'djb2a-32'; - case 'AMP-Verification-Checksum': - return '2569076912'; - default: - throw new Error(`unexpected header: ${key}`); - } - }, - }; - return AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( - utf8Encode(creative), mockHeaders).then(result => { + it('should properly validate checksum', () => { + const creative = 'This is some text'; + const mockHeaders = { + get: key => { + switch (key) { + case 'AMP-Verification-Checksum-Algorithm': + return 'djb2a-32'; + case 'AMP-Verification-Checksum': + return '2569076912'; + default: + throw new Error(`unexpected header: ${key}`); + } + }, + }; + return AmpAdNetworkDoubleclickImpl.prototype + .maybeValidateAmpCreative(utf8Encode(creative), mockHeaders) + .then(result => { expect(result).to.be.ok; expect(utf8Decode(result)).to.equal(creative); }); - }); + }); - it('should fail validation if invalid checksum', () => { - const creative = 'This is some text'; - const mockHeaders = { - get: key => { - switch (key) { - case 'AMP-Verification-Checksum-Algorithm': - return 'djb2a-32'; - case 'AMP-Verification-Checksum': - return '12345'; - default: - throw new Error(`unexpected header: ${key}`); - } - }, - }; - expect(AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( - utf8Encode(creative), mockHeaders)).to.eventually.not.be.ok; - }); + it('should fail validation if invalid checksum', () => { + const creative = 'This is some text'; + const mockHeaders = { + get: key => { + switch (key) { + case 'AMP-Verification-Checksum-Algorithm': + return 'djb2a-32'; + case 'AMP-Verification-Checksum': + return '12345'; + default: + throw new Error(`unexpected header: ${key}`); + } + }, + }; + expect( + AmpAdNetworkDoubleclickImpl.prototype.maybeValidateAmpCreative( + utf8Encode(creative), + mockHeaders + ) + ).to.eventually.not.be.ok; }); }); + } +); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js index 8fb510976c2dc..63208cbff67db 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-fluid.js @@ -78,15 +78,16 @@ function createScaffoldingForFluidRendering(impl, sandbox) { }; impl.buildCallback(); impl.attemptChangeHeight = () => Promise.resolve(); - sandbox.stub(impl, 'sendXhrRequest').returns(Promise.resolve({ - arrayBuffer: () => Promise.resolve(utf8Encode(rawCreative)), - headers: {has: () => false, get: () => undefined}, - })); + sandbox.stub(impl, 'sendXhrRequest').returns( + Promise.resolve({ + arrayBuffer: () => Promise.resolve(utf8Encode(rawCreative)), + headers: {has: () => false, get: () => undefined}, + }) + ); impl.sentinel = 'sentinel'; impl.initiateAdRequest(); - impl.safeframeApi_ = new SafeframeHostApi( - impl, true, impl.creativeSize_); - sandbox./*OK*/stub(impl.safeframeApi_, 'setupGeom_'); + impl.safeframeApi_ = new SafeframeHostApi(impl, true, impl.creativeSize_); + sandbox./*OK*/ stub(impl.safeframeApi_, 'setupGeom_'); } describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { @@ -109,10 +110,11 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { const ampStyle = doc.createElement('style'); ampStyle.setAttribute('amp-runtime', 'scratch-fortesting'); doc.head.appendChild(ampStyle); - doc.body.appendChild(createElementWithAttributes( - env.win.document, 'div', { - 'style': 'width: 1px; height: 1000px;', - })); + doc.body.appendChild( + createElementWithAttributes(env.win.document, 'div', { + 'style': 'width: 1px; height: 1000px;', + }) + ); element = createElementWithAttributes(env.win.document, 'amp-ad', { 'height': 'fluid', 'type': 'doubleclick', @@ -126,7 +128,10 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { }); doc.body.appendChild(multiSizeElement); multiSizeImpl = new AmpAdNetworkDoubleclickImpl( - multiSizeElement, env.win.document, env.win); + multiSizeElement, + env.win.document, + env.win + ); const getLayout = () => 'fluid'; impl.getLayout = getLayout; @@ -158,8 +163,10 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { }); it('should NOT load delayed impression amp-pixels', () => { - const fireDelayedImpressionsSpy = - sandbox.spy(impl, 'fireDelayedImpressions'); + const fireDelayedImpressionsSpy = sandbox.spy( + impl, + 'fireDelayedImpressions' + ); const size = impl.extractSize({ get(name) { switch (name) { @@ -192,8 +199,7 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { multiSizeImpl.initiateAdRequest(); return multiSizeImpl.adPromise_.then(() => { expect(multiSizeImpl.adUrl_).to.be.ok; - expect(multiSizeImpl.adUrl_).to.match( - /[&?]sz=320x50%7C300x200%7C150x50/); + expect(multiSizeImpl.adUrl_).to.match(/[&?]sz=320x50%7C300x200%7C150x50/); }); }); @@ -226,11 +232,14 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { it('should fire delayed impression ping', () => { createScaffoldingForFluidRendering(impl, sandbox); - const connectMessagingChannelSpy = - sandbox./*OK*/spy(impl.safeframeApi_, - 'connectMessagingChannel'); - const onFluidResizeSpy = sandbox./*OK*/spy(impl.safeframeApi_, - 'onFluidResize_'); + const connectMessagingChannelSpy = sandbox./*OK*/ spy( + impl.safeframeApi_, + 'connectMessagingChannel' + ); + const onFluidResizeSpy = sandbox./*OK*/ spy( + impl.safeframeApi_, + 'onFluidResize_' + ); return impl.adPromise_.then(() => { return impl.layoutCallback().then(() => { expect(connectMessagingChannelSpy).to.be.calledOnce; @@ -258,8 +267,8 @@ describes.realWin('DoubleClick Fast Fetch Fluid', realWinConfig, env => { impl.isVerifiedAmpCreative_ = true; impl.fluidImpressionUrl_ = 'http://www.foo.co.uk'; impl.onCreativeRender(null, mockPromise); - expect(delayedImpressionSpy.withArgs('http://www.foo.co.uk')) - .to.be.calledOnce; + expect(delayedImpressionSpy.withArgs('http://www.foo.co.uk')).to.be + .calledOnce; }); it('should set expansion re-attempt flag after initial failure', () => { diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js index 08bc383caea4c..70e51622482b6 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-rtc.js @@ -19,9 +19,7 @@ // always available for them. However, when we test an impl in isolation, // AmpAd is not loaded already, so we need to load it separately. import '../../../amp-ad/0.1/amp-ad'; -import { - AmpAdNetworkDoubleclickImpl, -} from '../amp-ad-network-doubleclick-impl'; +import {AmpAdNetworkDoubleclickImpl} from '../amp-ad-network-doubleclick-impl'; import {RTC_ERROR_ENUM} from '../../../amp-a4a/0.1/real-time-config-manager'; import {RTC_VENDORS} from '../../../amp-a4a/0.1/callout-vendors'; import {Services} from '../../../../src/services'; @@ -60,7 +58,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { describe('#mergeRtcResponses_', () => { function testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting) { + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ) { const rtcUrlParams = impl.mergeRtcResponses_(rtcResponseArray); expect(rtcUrlParams).to.deep.equal(expectedParams); expect(impl.jsonTargeting).to.deep.equal(expectedJsonTargeting); @@ -71,17 +72,29 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { const expectedParams = {'artc': null, 'ati': '', 'ard': ''}; const expectedJsonTargeting = {}; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge RTC responses into jsonTargeting on impl', () => { const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3], 'b': {c: 'd'}}}, - callout: 'www.exampleA.com', rtcTime: 100}, - {response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, - callout: 'www.exampleB.com', rtcTime: 500}, - {response: {targeting: {'z': [{a: 'b'}, {c: 'd'}], 'b': {c: 'd'}}}, - callout: 'www.exampleC.com', rtcTime: 100}, + { + response: {targeting: {'a': [1, 2, 3], 'b': {c: 'd'}}}, + callout: 'www.exampleA.com', + rtcTime: 100, + }, + { + response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, + callout: 'www.exampleB.com', + rtcTime: 500, + }, + { + response: {targeting: {'z': [{a: 'b'}, {c: 'd'}], 'b': {c: 'd'}}}, + callout: 'www.exampleC.com', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2,2,2', @@ -90,10 +103,16 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }; const expectedJsonTargeting = { targeting: { - 'a': 'foo', 'b': {c: 'd', e: 'f'}, 'z': [{a: 'b'}, {c: 'd'}]}, + 'a': 'foo', + 'b': {c: 'd', e: 'f'}, + 'z': [{a: 'b'}, {c: 'd'}], + }, }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge RTC responses from vendors', () => { @@ -101,12 +120,21 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'url': 'https://fakevendor2.biz', }; const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3], 'b': {c: 'd'}}}, - callout: 'fakevendor', rtcTime: 100}, - {response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, - callout: 'www.exampleB.com', rtcTime: 500}, - {response: {targeting: {'a': 'bar'}}, - callout: 'TEMP_VENDOR', rtcTime: 100}, + { + response: {targeting: {'a': [1, 2, 3], 'b': {c: 'd'}}}, + callout: 'fakevendor', + rtcTime: 100, + }, + { + response: {targeting: {'a': 'foo', 'b': {e: 'f'}}}, + callout: 'www.exampleB.com', + rtcTime: 500, + }, + { + response: {targeting: {'a': 'bar'}}, + callout: 'TEMP_VENDOR', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2,2,2', @@ -115,21 +143,34 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }; const expectedJsonTargeting = { targeting: { - 'a': 'foo', 'b': {e: 'f'}, 'a_fakevendor': [1,2,3], - 'b_fakevendor': {c: 'd'}, 'a_TEMP_VENDOR': 'bar'}, + 'a': 'foo', + 'b': {e: 'f'}, + 'a_fakevendor': [1, 2, 3], + 'b_fakevendor': {c: 'd'}, + 'a_TEMP_VENDOR': 'bar', + }, }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge into existing json', () => { element.setAttribute('json', '{"targeting":{"a":"foo"}}'); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3]}}, - callout: 'fakevendor', rtcTime: 100}, + { + response: {targeting: {'a': [1, 2, 3]}}, + callout: 'fakevendor', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2', @@ -137,19 +178,32 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { ard: 'fakevendor', }; const expectedJsonTargeting = { - targeting: {'a': 'foo', 'a_fakevendor': [1,2,3]}}; + targeting: {'a': 'foo', 'a_fakevendor': [1, 2, 3]}, + }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should properly merge into existing categoryExclusions', () => { element.setAttribute('json', '{"categoryExclusions": ["sports"]}'); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const rtcResponseArray = [ - {response: {targeting: {'a': [1,2,3]}, categoryExclusions: ['health']}, - callout: 'fakevendor', rtcTime: 100}, + { + response: { + targeting: {'a': [1, 2, 3]}, + categoryExclusions: ['health'], + }, + callout: 'fakevendor', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2', @@ -157,21 +211,30 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { ard: 'fakevendor', }; const expectedJsonTargeting = { - targeting: {'a_fakevendor': [1,2,3]}, + targeting: {'a_fakevendor': [1, 2, 3]}, categoryExclusions: ['sports', 'health'], }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should not allow duplicate categoryExclusions', () => { element.setAttribute('json', '{"categoryExclusions": ["health"]}'); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const rtcResponseArray = [ - {response: {categoryExclusions: ['health']}, - callout: 'fakevendor', rtcTime: 100}, + { + response: {categoryExclusions: ['health']}, + callout: 'fakevendor', + rtcTime: 100, + }, ]; const expectedParams = { ati: '2', @@ -182,14 +245,20 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { categoryExclusions: ['health'], }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); Object.keys(RTC_ERROR_ENUM).forEach(errorName => { it(`should send correct error value for ${errorName}`, () => { const rtcResponseArray = [ - {error: RTC_ERROR_ENUM[errorName], - callout: 'www.exampleA.com', rtcTime: 100}, + { + error: RTC_ERROR_ENUM[errorName], + callout: 'www.exampleA.com', + rtcTime: 100, + }, ]; const expectedParams = { ati: `${RTC_ERROR_ENUM[errorName]}`, @@ -198,50 +267,78 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }; const expectedJsonTargeting = {}; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); }); - it('should properly merge mix of success and errors', () => { - impl.jsonTargeting = {targeting: - {'abc': [1,2,3], 'b': {n: 'm'}, 'a': 'TEST'}, - categoryExclusions: ['sports']}; + impl.jsonTargeting = { + targeting: {'abc': [1, 2, 3], 'b': {n: 'm'}, 'a': 'TEST'}, + categoryExclusions: ['sports'], + }; const rtcResponseArray = [ - {error: RTC_ERROR_ENUM.TIMEOUT, - callout: 'www.exampleA.com', rtcTime: 1500}, - {response: {targeting: {'a': 'foo', 'b': {e: 'f'}}, - categoryExclusions: ['health']}, - callout: 'VendorFoo', rtcTime: 500}, - {response: {targeting: {'a': [1,2,3], 'b': {c: 'd'}}}, - callout: 'www.exampleB.com', rtcTime: 100}, - {response: {targeting: {'a': [4,5,6], 'b': {x: [1,2]}}}, - callout: 'VendCom', rtcTime: 500}, - {error: RTC_ERROR_ENUM.DUPLICATE_URL, - callout: 'www.exampleB.com', rtcTime: 0}, - {error: RTC_ERROR_ENUM.NETWORK_FAILURE, - callout: '3PVend', rtcTime: 100}, + { + error: RTC_ERROR_ENUM.TIMEOUT, + callout: 'www.exampleA.com', + rtcTime: 1500, + }, + { + response: { + targeting: {'a': 'foo', 'b': {e: 'f'}}, + categoryExclusions: ['health'], + }, + callout: 'VendorFoo', + rtcTime: 500, + }, + { + response: {targeting: {'a': [1, 2, 3], 'b': {c: 'd'}}}, + callout: 'www.exampleB.com', + rtcTime: 100, + }, + { + response: {targeting: {'a': [4, 5, 6], 'b': {x: [1, 2]}}}, + callout: 'VendCom', + rtcTime: 500, + }, + { + error: RTC_ERROR_ENUM.DUPLICATE_URL, + callout: 'www.exampleB.com', + rtcTime: 0, + }, + { + error: RTC_ERROR_ENUM.NETWORK_FAILURE, + callout: '3PVend', + rtcTime: 100, + }, ]; const expectedParams = { ati: '10,2,2,2,5,8', artc: '1500,500,100,500,0,100', - ard: 'www.exampleA.com,VendorFoo,www.exampleB.com,' + - 'VendCom,www.exampleB.com,3PVend', + ard: + 'www.exampleA.com,VendorFoo,www.exampleB.com,' + + 'VendCom,www.exampleB.com,3PVend', }; const expectedJsonTargeting = { targeting: { - 'a': [4,5,6], 'b': {n: 'm', e: 'f', c: 'd', x: [1,2]}, - abc: [1,2,3]}, + 'a': [4, 5, 6], + 'b': {n: 'm', e: 'f', c: 'd', x: [1, 2]}, + abc: [1, 2, 3], + }, categoryExclusions: ['sports', 'health'], }; testMergeRtcResponses( - rtcResponseArray, expectedParams, expectedJsonTargeting); + rtcResponseArray, + expectedParams, + expectedJsonTargeting + ); }); it('should return null for empty array', () => { expect(impl.mergeRtcResponses_()).to.be.null; }); - }); describe('rewriteRtcKeys', () => { @@ -254,8 +351,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'a_fakevendor': '1', 'b_fakevendor': '2', }; - expect(impl.rewriteRtcKeys_(response, 'fakevendor')) - .to.deep.equal(rewrittenResponse); + expect(impl.rewriteRtcKeys_(response, 'fakevendor')).to.deep.equal( + rewrittenResponse + ); }); it('should not rewrite key names if vendor has disableKeyAppend', () => { @@ -264,8 +362,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'b': '2', }; // fakevendor2 has disableKeyAppend set to true, see callout-vendors.js - expect(impl.rewriteRtcKeys_(response, 'fakevendor2')) - .to.deep.equal(response); + expect(impl.rewriteRtcKeys_(response, 'fakevendor2')).to.deep.equal( + response + ); }); it('should not rewrite key names if custom url callout', () => { @@ -273,8 +372,9 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'a': '1', 'b': '2', }; - expect(impl.rewriteRtcKeys_(response, 'www.customurl.biz')) - .to.deep.equal(response); + expect(impl.rewriteRtcKeys_(response, 'www.customurl.biz')).to.deep.equal( + response + ); }); }); @@ -306,11 +406,15 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { 'json': JSON.stringify(json), }); env.win.document.body.appendChild(element); - Object.defineProperty( - env.win.document, 'referrer', {value: 'https://www.google.com/'}); + Object.defineProperty(env.win.document, 'referrer', { + value: 'https://www.google.com/', + }); const docInfo = Services.documentInfoForDoc(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); expect(customMacros.PAGEVIEWID()).to.equal(docInfo.pageViewId); @@ -335,7 +439,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); let adcid; @@ -354,7 +461,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); return customMacros.ADCID(0).then(adcid => { @@ -368,7 +478,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const viewer = Services.viewerForDoc(impl.getAmpDoc()); sandbox.stub(viewer, 'getReferrerUrl').returns(new Promise(() => {})); @@ -385,7 +498,10 @@ describes.realWin('DoubleClick Fast Fetch RTC', {amp: true}, env => { }); env.win.document.body.appendChild(element); impl = new AmpAdNetworkDoubleclickImpl( - element, env.win.document, env.win); + element, + env.win.document, + env.win + ); impl.populateAdUrlState(); const customMacros = impl.getCustomRealTimeConfigMacros_(); expect(customMacros.TGT()).to.equal(JSON.stringify(json['targeting'])); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js index 33ba2c7c3620e..63423945612db 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-safeframe.js @@ -46,7 +46,6 @@ const realWinConfig = { allowExternalResources: true, }; - describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { let doubleclickImpl; let ampAd; @@ -80,8 +79,7 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { width: creativeWidth, height: creativeHeight, }; - safeframeHost = new SafeframeHostApi( - doubleclickImpl, false, creativeSize); + safeframeHost = new SafeframeHostApi(doubleclickImpl, false, creativeSize); doubleclickImpl.upgradeCallback(); doubleclickImpl.layoutCallback(); } @@ -125,9 +123,13 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { ampAd.appendChild(safeframeMock); doubleclickImpl.iframe = safeframeMock; const connectMessagingChannelSpy = sandbox.spy( - safeframeHost, 'connectMessagingChannel'); - const postMessageStub = sandbox./*OK*/stub( - safeframeMock.contentWindow, 'postMessage'); + safeframeHost, + 'connectMessagingChannel' + ); + const postMessageStub = sandbox./*OK*/ stub( + safeframeMock.contentWindow, + 'postMessage' + ); sendSetupMessage(); // Verify that the channel was set up @@ -138,36 +140,59 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { const firstPostMessageArgs = postMessageStub.firstCall.args; let connectMessage = JSON.parse(firstPostMessageArgs[0]); let payload = JSON.parse(connectMessage[MESSAGE_FIELDS.PAYLOAD]); - expect(payload).to.deep.equal({'c': safeframeChannel, - 'message': 'connect'}); + expect(payload).to.deep.equal({ + 'c': safeframeChannel, + 'message': 'connect', + }); expect(connectMessage[MESSAGE_FIELDS.CHANNEL]).to.equal(safeframeChannel); expect(connectMessage[MESSAGE_FIELDS.SENTINEL]).to.equal( - doubleclickImpl.sentinel); + doubleclickImpl.sentinel + ); expect(connectMessage[MESSAGE_FIELDS.ENDPOINT_IDENTITY]).to.equal( - safeframeHost.endpointIdentity_); + safeframeHost.endpointIdentity_ + ); // Verify that the initial geometry update was sent - return Services.timerFor(env.win).promise(500).then(() => { - const secondPostMessageArgs = postMessageStub.secondCall.args; - connectMessage = JSON.parse(secondPostMessageArgs[0]); - expect(connectMessage[MESSAGE_FIELDS.CHANNEL]).to.equal( - safeframeChannel); - expect(connectMessage[MESSAGE_FIELDS.SENTINEL]).to.equal( - doubleclickImpl.sentinel); - expect(connectMessage[MESSAGE_FIELDS.ENDPOINT_IDENTITY]).to.equal( - safeframeHost.endpointIdentity_); - payload = JSON.parse(connectMessage[MESSAGE_FIELDS.PAYLOAD]); - expect(Object.keys(payload)).to.deep.equal(['newGeometry', 'uid']); - expect(Object.keys(JSON.parse(payload['newGeometry']))).to.deep.equal([ - 'windowCoords_t', 'windowCoords_r', 'windowCoords_b', - 'windowCoords_l', 'frameCoords_t', 'frameCoords_r', - 'frameCoords_b', 'frameCoords_l', - 'posCoords_t', 'posCoords_b', 'posCoords_r', 'posCoords_l', - 'styleZIndex', - 'allowedExpansion_r', 'allowedExpansion_b', 'allowedExpansion_t', - 'allowedExpansion_l', 'yInView', 'xInView', - ]); - }); + return Services.timerFor(env.win) + .promise(500) + .then(() => { + const secondPostMessageArgs = postMessageStub.secondCall.args; + connectMessage = JSON.parse(secondPostMessageArgs[0]); + expect(connectMessage[MESSAGE_FIELDS.CHANNEL]).to.equal( + safeframeChannel + ); + expect(connectMessage[MESSAGE_FIELDS.SENTINEL]).to.equal( + doubleclickImpl.sentinel + ); + expect(connectMessage[MESSAGE_FIELDS.ENDPOINT_IDENTITY]).to.equal( + safeframeHost.endpointIdentity_ + ); + payload = JSON.parse(connectMessage[MESSAGE_FIELDS.PAYLOAD]); + expect(Object.keys(payload)).to.deep.equal(['newGeometry', 'uid']); + expect(Object.keys(JSON.parse(payload['newGeometry']))).to.deep.equal( + [ + 'windowCoords_t', + 'windowCoords_r', + 'windowCoords_b', + 'windowCoords_l', + 'frameCoords_t', + 'frameCoords_r', + 'frameCoords_b', + 'frameCoords_l', + 'posCoords_t', + 'posCoords_b', + 'posCoords_r', + 'posCoords_l', + 'styleZIndex', + 'allowedExpansion_r', + 'allowedExpansion_b', + 'allowedExpansion_t', + 'allowedExpansion_l', + 'yInView', + 'xInView', + ] + ); + }); }); }); @@ -177,20 +202,40 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { canonicalUrl: 'http://example.org/canonical', }); const attrs = safeframeHost.getSafeframeNameAttr(); - expect(Object.keys(attrs)).to.deep.equal( - ['uid', 'hostPeerName', 'initialGeometry', 'permissions', - 'metadata', 'reportCreativeGeometry', 'isDifferentSourceWindow', - 'sentinel']); + expect(Object.keys(attrs)).to.deep.equal([ + 'uid', + 'hostPeerName', + 'initialGeometry', + 'permissions', + 'metadata', + 'reportCreativeGeometry', + 'isDifferentSourceWindow', + 'sentinel', + ]); // Check the geometry const initialGeometry = JSON.parse(attrs['initialGeometry']); - expect(Object.keys(initialGeometry)).to.deep.equal( - ['windowCoords_t', 'windowCoords_r', 'windowCoords_b', - 'windowCoords_l', 'frameCoords_t', 'frameCoords_r', - 'frameCoords_b', 'frameCoords_l', 'posCoords_t', - 'posCoords_b', 'posCoords_r', 'posCoords_l', 'styleZIndex', - 'allowedExpansion_r', 'allowedExpansion_b', 'allowedExpansion_t', - 'allowedExpansion_l', 'yInView', 'xInView']); + expect(Object.keys(initialGeometry)).to.deep.equal([ + 'windowCoords_t', + 'windowCoords_r', + 'windowCoords_b', + 'windowCoords_l', + 'frameCoords_t', + 'frameCoords_r', + 'frameCoords_b', + 'frameCoords_l', + 'posCoords_t', + 'posCoords_b', + 'posCoords_r', + 'posCoords_l', + 'styleZIndex', + 'allowedExpansion_r', + 'allowedExpansion_b', + 'allowedExpansion_t', + 'allowedExpansion_l', + 'yInView', + 'xInView', + ]); Object.keys(initialGeometry).forEach(key => { if (key != 'styleZIndex') { expect(typeof initialGeometry[key]).to.equal('number'); @@ -283,44 +328,50 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { describe('getCurrentGeometry', () => { beforeEach(() => { - sandbox./*OK*/stub(safeframeHost.viewport_, 'getSize').returns({ + sandbox./*OK*/ stub(safeframeHost.viewport_, 'getSize').returns({ height: 1000, width: 500, }); }); it('should get current geometry when safeframe fills amp-ad', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 300, - bottom: 250, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 300, + bottom: 250, + }); const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); // Set the size of the safeframe to be the same as its containing // amp-ad element const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:250px!important;' + - 'width:300px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:250px!important;' + + 'width:300px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; safeframeHost.iframe_ = safeframeMock; - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); - return Services.timerFor(env.win).promise(100).then(() => { - const payload = sendMessageStub.firstCall.args[0]; - const messageType = sendMessageStub.firstCall.args[1]; - expect(payload['newGeometry']).to.equal( + return Services.timerFor(env.win) + .promise(100) + .then(() => { + const payload = sendMessageStub.firstCall.args[0]; + const messageType = sendMessageStub.firstCall.args[1]; + expect(payload['newGeometry']).to.equal( '{"windowCoords_t":0,"windowCoords_r":500,"windowCoords_b":1000,' + '"windowCoords_l":0,"frameCoords_t":0,"frameCoords_r":300,' + '"frameCoords_b":250,"frameCoords_l":0,' + @@ -328,44 +379,51 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { '"posCoords_l":0,"styleZIndex":"",' + '"allowedExpansion_r":200,"allowedExpansion_b":750,' + '"allowedExpansion_t":0,"allowedExpansion_l":0,"yInView":1,' + - '"xInView":1}'); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - }); + '"xInView":1}' + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); it('should get geometry when safeframe does not fill amp-ad', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 50, - bottom: 50, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 50, + bottom: 50, + }); // In this case, the safeframe is smaller than its containing // amp-ad element. const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:10px!important;' + - 'width:10px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:10px!important;' + + 'width:10px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; safeframeHost.iframe_ = safeframeMock; - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); - return Services.timerFor(env.win).promise(100).then(() => { - const payload = sendMessageStub.firstCall.args[0]; - const messageType = sendMessageStub.firstCall.args[1]; - expect(payload['newGeometry']).to.equal( + return Services.timerFor(env.win) + .promise(100) + .then(() => { + const payload = sendMessageStub.firstCall.args[0]; + const messageType = sendMessageStub.firstCall.args[1]; + expect(payload['newGeometry']).to.equal( '{"windowCoords_t":0,"windowCoords_r":500,"windowCoords_b":1000,' + '"windowCoords_l":0,"frameCoords_t":0,"frameCoords_r":10,' + '"frameCoords_b":10,"frameCoords_l":0,' + @@ -373,65 +431,72 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { '"posCoords_l":0,"styleZIndex":"",' + '"allowedExpansion_r":490,"allowedExpansion_b":990,' + '"allowedExpansion_t":0,"allowedExpansion_l":0,"yInView":1,' + - '"xInView":1}'); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - }); + '"xInView":1}' + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); - it('should handle cancellation', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 50, - bottom: 50, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 50, + bottom: 50, + }); // In this case, the safeframe is smaller than its containing // amp-ad element. const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + + css.innerHTML = + '.safeframe' + '{height:10px!important;' + - 'width:10px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + 'width:10px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; safeframeHost.iframe_ = safeframeMock; - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(1000).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(1000) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); it('should get geometry when scrolled', () => { - - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 0, - left: 0, - right: 50, - bottom: 50, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 0, + left: 0, + right: 50, + bottom: 50, + }); // In this case, the safeframe is smaller than its containing // amp-ad element. const safeframeMock = createElementWithAttributes(doc, 'iframe', { 'class': 'safeframe', }); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:100px!important;' + - 'width:100px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:100px!important;' + + 'width:100px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe_ = safeframeMock; @@ -440,24 +505,29 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { // Scroll 100 px safeframeHost.viewport_.setScrollTop(50); - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.updateGeometry_(); - return Services.timerFor(env.win).promise(100).then(() => { - const payload = sendMessageStub.firstCall.args[0]; - const messageType = sendMessageStub.firstCall.args[1]; - expect(payload['newGeometry']).to.equal( + return Services.timerFor(env.win) + .promise(100) + .then(() => { + const payload = sendMessageStub.firstCall.args[0]; + const messageType = sendMessageStub.firstCall.args[1]; + expect(payload['newGeometry']).to.equal( '{"windowCoords_t":0,"windowCoords_r":500,"windowCoords_b":1000,' + '"windowCoords_l":0,"frameCoords_t":0,"frameCoords_r":100,' + '"frameCoords_b":100,"frameCoords_l":0,"posCoords_t":-50,' + '"posCoords_b":50,"posCoords_r":100,"posCoords_l":0,' + '"styleZIndex":"","allowedExpansion_r":400,' + '"allowedExpansion_b":900,"allowedExpansion_t":0,' + - '"allowedExpansion_l":0,"yInView":0.5,"xInView":1}'); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - }); + '"allowedExpansion_l":0,"yInView":0.5,"xInView":1}' + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); }); @@ -467,51 +537,60 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { ampAd.appendChild(safeframeMock); doubleclickImpl.iframe = safeframeMock; - const onScrollStub = sandbox./*OK*/stub( - safeframeHost.viewport_, 'onScroll'); - const onChangedStub = sandbox./*OK*/stub( - safeframeHost.viewport_, 'onChanged'); + const onScrollStub = sandbox./*OK*/ stub( + safeframeHost.viewport_, + 'onScroll' + ); + const onChangedStub = sandbox./*OK*/ stub( + safeframeHost.viewport_, + 'onChanged' + ); safeframeMock.contentWindow.postMessage = () => {}; sendSetupMessage(); const maybeUpdateGeometry1 = onScrollStub.firstCall.args[0]; const maybeUpdateGeometry2 = onChangedStub.firstCall.args[0]; - const sendMessageStub = sandbox./*OK*/spy(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ spy(safeframeHost, 'sendMessage_'); maybeUpdateGeometry1(); maybeUpdateGeometry2(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendMessageStub).to.be.calledTwice; - const payload = sendMessageStub.secondCall.args[0]; - const messageType = sendMessageStub.secondCall.args[1]; - expect(JSON.parse(payload['newGeometry'])).to.deep.equal( - safeframeHost.currentGeometry_); - expect(payload['uid']).to.equal(safeframeHost.uid_); - expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); - return Services.timerFor(env.win).promise(1000).then(() => { - expect(sendMessageStub).to.be.calledThrice; - const payload = sendMessageStub.thirdCall.args[0]; - const messageType = sendMessageStub.thirdCall.args[1]; + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendMessageStub).to.be.calledTwice; + const payload = sendMessageStub.secondCall.args[0]; + const messageType = sendMessageStub.secondCall.args[1]; expect(JSON.parse(payload['newGeometry'])).to.deep.equal( - safeframeHost.currentGeometry_); + safeframeHost.currentGeometry_ + ); expect(payload['uid']).to.equal(safeframeHost.uid_); expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + return Services.timerFor(env.win) + .promise(1000) + .then(() => { + expect(sendMessageStub).to.be.calledThrice; + const payload = sendMessageStub.thirdCall.args[0]; + const messageType = sendMessageStub.thirdCall.args[1]; + expect(JSON.parse(payload['newGeometry'])).to.deep.equal( + safeframeHost.currentGeometry_ + ); + expect(payload['uid']).to.equal(safeframeHost.uid_); + expect(messageType).to.equal(SERVICE.GEOMETRY_UPDATE); + }); }); - }); - }); }); describe('formatGeom', () => { it('should build proper geometry update', () => { - sandbox./*OK*/stub(safeframeHost.baseInstance_, - 'getPageLayoutBox').returns({ - top: 200, - left: 100, - right: 400, - bottom: 800, - }); + sandbox + ./*OK*/ stub(safeframeHost.baseInstance_, 'getPageLayoutBox') + .returns({ + top: 200, + left: 100, + right: 400, + bottom: 800, + }); const iframeBox = { top: 300, left: 200, @@ -520,63 +599,83 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { width: 300, height: 700, }; - sandbox./*OK*/stub(safeframeHost.viewport_, 'getSize').returns({ + sandbox./*OK*/ stub(safeframeHost.viewport_, 'getSize').returns({ width: 500, height: 1000, }); const expectedParsedSfGU = { - 'windowCoords_t': 0, 'windowCoords_r': 500, 'windowCoords_b': 1000, - 'windowCoords_l': 0, 'frameCoords_t': 300, 'frameCoords_r': 500, - 'frameCoords_b': 1000, 'frameCoords_l': 200, - 'posCoords_b': 1000, 'posCoords_l': 200, 'posCoords_r': 500, - 'posCoords_t': 300, 'styleZIndex': '', - 'allowedExpansion_r': 200, 'allowedExpansion_b': 300, - 'allowedExpansion_t': 0, 'allowedExpansion_l': 0, 'yInView': 1, + 'windowCoords_t': 0, + 'windowCoords_r': 500, + 'windowCoords_b': 1000, + 'windowCoords_l': 0, + 'frameCoords_t': 300, + 'frameCoords_r': 500, + 'frameCoords_b': 1000, + 'frameCoords_l': 200, + 'posCoords_b': 1000, + 'posCoords_l': 200, + 'posCoords_r': 500, + 'posCoords_t': 300, + 'styleZIndex': '', + 'allowedExpansion_r': 200, + 'allowedExpansion_b': 300, + 'allowedExpansion_t': 0, + 'allowedExpansion_l': 0, + 'yInView': 1, 'xInView': 1, }; - const safeframeGeometryUpdate = safeframeHost.formatGeom_( - iframeBox); + const safeframeGeometryUpdate = safeframeHost.formatGeom_(iframeBox); const parsedSfGU = JSON.parse(safeframeGeometryUpdate); expect(parsedSfGU).to.deep.equal(expectedParsedSfGU); expect(safeframeHost.currentGeometry_).to.deep.equal(expectedParsedSfGU); - }); }); describe('sendResizeResponse', () => { it('should handle cancellation', () => { - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.sendResizeResponse(true, SERVICE.COLLAPSE_REQUEST); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(0).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(0) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); }); describe('resizeAmpAdAndSafeframe', () => { it('should handle cancellation', () => { - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); - safeframeHost.resizeAmpAdAndSafeframe( - 100, 100, SERVICE.COLLAPSE_REQUEST); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); + safeframeHost.resizeAmpAdAndSafeframe(100, 100, SERVICE.COLLAPSE_REQUEST); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(0).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(0) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); }); describe('handleFluidMessage', () => { it('should handle cancellation', () => { - const sendMessageStub = sandbox./*OK*/stub(safeframeHost, - 'sendMessage_'); + const sendMessageStub = sandbox./*OK*/ stub( + safeframeHost, + 'sendMessage_' + ); safeframeHost.handleFluidMessage_({height: 10}); safeframeHost.baseInstance_.promiseId_++; - return Services.timerFor(env.win).promise(0).then(() => { - expect(sendMessageStub).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(0) + .then(() => { + expect(sendMessageStub).to.not.be.called; + }); }); }); @@ -593,11 +692,12 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { function setupForResize() { sandbox.restore(); const css = createElementWithAttributes(doc, 'style'); - css.innerHTML = '.safeframe' + - '{height:50px!important;' + - 'width:50px!important;' + - 'background-color:blue!important;' + - 'display:block!important;}'; + css.innerHTML = + '.safeframe' + + '{height:50px!important;' + + 'width:50px!important;' + + 'background-color:blue!important;' + + 'display:block!important;}'; doc.head.appendChild(css); safeframeMock = createElementWithAttributes(doc, 'iframe', { height: 50, @@ -605,19 +705,21 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { }); ampAd.appendChild(safeframeMock); doubleclickImpl.iframe = safeframeMock; - resizeSafeframeSpy = sandbox.spy( - safeframeHost, 'resizeSafeframe'); - sendResizeResponseSpy = sandbox.spy( - safeframeHost, 'sendResizeResponse'); + resizeSafeframeSpy = sandbox.spy(safeframeHost, 'resizeSafeframe'); + sendResizeResponseSpy = sandbox.spy(safeframeHost, 'sendResizeResponse'); resizeAmpAdAndSafeframeSpy = sandbox.spy( - safeframeHost, 'resizeAmpAdAndSafeframe'); + safeframeHost, + 'resizeAmpAdAndSafeframe' + ); safeframeHost.initialHeight_ = ampAdHeight; safeframeHost.initialWidth_ = ampAdWidth; sendSetupMessage(); sendRegisterDoneMessage(); attemptChangeSizeStub = sandbox.stub( - doubleclickImpl, 'attemptChangeSize'); - sandbox./*OK*/stub(safeframeHost.viewport_, 'getSize').returns({ + doubleclickImpl, + 'attemptChangeSize' + ); + sandbox./*OK*/ stub(safeframeHost.viewport_, 'getSize').returns({ height: 1000, width: 1000, }); @@ -658,15 +760,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { sendExpandMessage(50, 50); // Verify that we can immediately resize the safeframe, and don't // need to call any of the fancy AMP element resize things. - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(300, 350); - expect(safeframeMock.style.height).to.equal('300px'); - expect(safeframeMock.style.width).to.equal('350px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(300, 350); + expect(safeframeMock.style.height).to.equal('300px'); + expect(safeframeMock.style.width).to.equal('350px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; + }); }); /** @@ -688,15 +794,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { sendExpandMessage(50, 50); // Verify that we can immediately resize the safeframe, and don't // need to call any of the fancy AMP element resize things. - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(300, 350); - expect(safeframeMock.style.height).to.equal('300px'); - expect(safeframeMock.style.width).to.equal('350px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(300, 350); + expect(safeframeMock.style.height).to.equal('300px'); + expect(safeframeMock.style.width).to.equal('350px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.not.be.called; + }); }); /** @@ -705,31 +815,38 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { * using element.attemptChangeSize. If that succeeds, then we also * resize the safeframe. */ - it('expand_request should succeed if expanding past amp-ad bounds and' + - ' does not create reflow', () => { - const expandWidthBy = 550; - const expandHeightBy = 600; - // Sneaky hack to do a synchronous mock of attemptChangeSize - // Resize the ampAd to simulate a success. - const then = f => { - ampAd.style.height = '850px'; - ampAd.style.width = '850px'; - f(); - return {'catch': () => {}}; - }; - attemptChangeSizeStub.returns({then}); - sendExpandMessage(expandHeightBy, expandWidthBy); - - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(850, 850); - expect(safeframeMock.style.height).to.equal('850px'); - expect(safeframeMock.style.width).to.equal('850px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); - }); + it( + 'expand_request should succeed if expanding past amp-ad bounds and' + + ' does not create reflow', + () => { + const expandWidthBy = 550; + const expandHeightBy = 600; + // Sneaky hack to do a synchronous mock of attemptChangeSize + // Resize the ampAd to simulate a success. + const then = f => { + ampAd.style.height = '850px'; + ampAd.style.width = '850px'; + f(); + return {'catch': () => {}}; + }; + attemptChangeSizeStub.returns({then}); + sendExpandMessage(expandHeightBy, expandWidthBy); + + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(850, 850); + expect(safeframeMock.style.height).to.equal('850px'); + expect(safeframeMock.style.width).to.equal('850px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); + } + ); /** * If the safeframed creative asks to resize outside the bounds of @@ -740,10 +857,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { it('resizeAmpAdAndSafeframe should send error on rejection', () => { attemptChangeSizeStub.rejects(); safeframeHost.resizeAmpAdAndSafeframe(550, 550, SERVICE.EXPAND_RESPONSE); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + }); }); /** @@ -752,10 +873,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { */ it('expand_request fails if expanding larger than viewport', () => { sendExpandMessage(5000, 5000); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + }); }); /** @@ -779,10 +904,14 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { allowConsoleError(() => { receiveMessage(expandMessage); }); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + }); }); /** @@ -790,21 +919,28 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { * try to expand that element, and in this test it fails. Thus, we also * fail resizing the safeframe. */ - it('expand_request should fail if expanding past amp-ad bounds and would ' + - 'create reflow', () => { - attemptChangeSizeStub.rejects(); - - sendExpandMessage(550, 550); - - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.not.be.called; - expect(safeframeMock.height).to.equal('50'); - expect(safeframeMock.width).to.equal('50'); - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.EXPAND_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); - }); + it( + 'expand_request should fail if expanding past amp-ad bounds and would ' + + 'create reflow', + () => { + attemptChangeSizeStub.rejects(); + + sendExpandMessage(550, 550); + + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.not.be.called; + expect(safeframeMock.height).to.equal('50'); + expect(safeframeMock.width).to.equal('50'); + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.EXPAND_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); + } + ); function sendCollapseMessage() { const collapseMessage = {}; @@ -828,15 +964,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { attemptChangeSizeStub.rejects(); sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(250, 300); - expect(safeframeMock.style.height).to.equal('250px'); - expect(safeframeMock.style.width).to.equal('300px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.COLLAPSE_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(250, 300); + expect(safeframeMock.style.height).to.equal('250px'); + expect(safeframeMock.style.width).to.equal('300px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.COLLAPSE_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); }); it('should collapse safeframe on amp-ad resize success', () => { @@ -856,34 +996,46 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { attemptChangeSizeStub.returns({then}); sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(250, 300); - expect(safeframeMock.style.height).to.equal('250px'); - expect(safeframeMock.style.width).to.equal('300px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.COLLAPSE_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(250, 300); + expect(safeframeMock.style.height).to.equal('250px'); + expect(safeframeMock.style.width).to.equal('300px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.COLLAPSE_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); }); it('should send collapse failure message if already collapsed', () => { safeframeHost.isCollapsed_ = true; sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.COLLAPSE_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.COLLAPSE_RESPONSE + ); + }); }); it('should send collapse failure message if not registered', () => { safeframeHost.isCollapsed_ = false; safeframeHost.isRegistered_ = false; sendCollapseMessage(); - return Services.timerFor(env.win).promise(100).then(() => { - expect(sendResizeResponseSpy).to.be.calledWith( - false, SERVICE.COLLAPSE_RESPONSE); - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(sendResizeResponseSpy).to.be.calledWith( + false, + SERVICE.COLLAPSE_RESPONSE + ); + }); }); /** @@ -922,15 +1074,19 @@ describes.realWin('DoubleClick Fast Fetch - Safeframe', realWinConfig, env => { attemptChangeSizeStub.returns({then}); sendResizeMessage(-5, -5, -5, -5); - return Services.timerFor(env.win).promise(100).then(() => { - expect(resizeSafeframeSpy).to.be.calledOnce; - expect(resizeSafeframeSpy).to.be.calledWith(240, 290); - expect(safeframeMock.style.height).to.equal('240px'); - expect(safeframeMock.style.width).to.equal('290px'); - expect(sendResizeResponseSpy).to.be.calledWith( - true, SERVICE.RESIZE_RESPONSE); - expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; - }); + return Services.timerFor(env.win) + .promise(100) + .then(() => { + expect(resizeSafeframeSpy).to.be.calledOnce; + expect(resizeSafeframeSpy).to.be.calledWith(240, 290); + expect(safeframeMock.style.height).to.equal('240px'); + expect(safeframeMock.style.width).to.equal('290px'); + expect(sendResizeResponseSpy).to.be.calledWith( + true, + SERVICE.RESIZE_RESPONSE + ); + expect(resizeAmpAdAndSafeframeSpy).to.be.calledOnce; + }); }); }); }); diff --git a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js index 537d9aeedc37a..60f6cf134df35 100644 --- a/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js +++ b/extensions/amp-ad-network-doubleclick-impl/0.1/test/test-doubleclick-sra.js @@ -27,12 +27,8 @@ import { } from '../amp-ad-network-doubleclick-impl'; import {BaseElement} from '../../../../src/base-element'; import {Deferred} from '../../../../src/utils/promise'; -import { - EXPERIMENT_ATTRIBUTE, -} from '../../../../ads/google/a4a/utils'; -import { - MANUAL_EXPERIMENT_ID, -} from '../../../../ads/google/a4a/traffic-experiments'; +import {EXPERIMENT_ATTRIBUTE} from '../../../../ads/google/a4a/utils'; +import {MANUAL_EXPERIMENT_ID} from '../../../../ads/google/a4a/traffic-experiments'; import {SignatureVerifier} from '../../../amp-a4a/0.1/signature-verifier'; import { TFCD, @@ -67,7 +63,7 @@ const config = {amp: true, allowExternalResources: true}; calling setAttribute('src', 'foo') on the iframe, which will cause all these tests to fail. */ -describes.realWin('Doubleclick SRA', config , env => { +describes.realWin('Doubleclick SRA', config, env => { let sandbox; let doc; @@ -80,9 +76,13 @@ describes.realWin('Doubleclick SRA', config , env => { function createAndAppendAdElement(opt_attributes, opt_type, opt_domElement) { const element = createElementWithAttributes( - doc, opt_type || 'amp-ad', - Object.assign( - {type: 'doubleclick', height: 320, width: 50}, opt_attributes)); + doc, + opt_type || 'amp-ad', + Object.assign( + {type: 'doubleclick', height: 320, width: 50}, + opt_attributes + ) + ); (opt_domElement || doc.body).appendChild(element); return element; } @@ -99,7 +99,10 @@ describes.realWin('Doubleclick SRA', config , env => { // layoutCallback is executed. it('should be enabled if meta tag present, and force refresh off', () => { createAndAppendAdElement( - {name: 'amp-ad-doubleclick-sra'}, 'meta', doc.head); + {name: 'amp-ad-doubleclick-sra'}, + 'meta', + doc.head + ); const element = createAndAppendAdElement({'data-enable-refresh': 30}); const impl = new AmpAdNetworkDoubleclickImpl(element); impl.buildCallback(); @@ -111,7 +114,7 @@ describes.realWin('Doubleclick SRA', config , env => { describe('block parameter joining', () => { let impls; - beforeEach(() => impls = []); + beforeEach(() => (impls = [])); it('should join IUs', () => { for (let i = 0; i < 2; i++) { @@ -154,8 +157,7 @@ describes.realWin('Doubleclick SRA', config , env => { impls.push({parameterSize: i}); expected.push(i); } - expect(getSizes(impls)).to.jsonEqual( - {'prev_iu_szs': expected.join()}); + expect(getSizes(impls)).to.jsonEqual({'prev_iu_szs': expected.join()}); }); it('should determine tagForChildDirectedTreatment', () => { expect(getTfcd(impls)).to.be.null; @@ -192,35 +194,49 @@ describes.realWin('Doubleclick SRA', config , env => { impls[0] = {jsonTargeting: {}}; expect(getTargetingAndExclusions(impls)).to.be.null; impls[1] = {jsonTargeting: {targeting: {a: 1, b: 2}}}; - expect(getTargetingAndExclusions(impls)).to.jsonEqual( - {'prev_scp': '|a=1&b=2'}); - impls[2] = {jsonTargeting: {targeting: {c: 1, d: 'l=d'}, - categoryExclusions: ['a','b']}}; + expect(getTargetingAndExclusions(impls)).to.jsonEqual({ + 'prev_scp': '|a=1&b=2', + }); + impls[2] = { + jsonTargeting: { + targeting: {c: 1, d: 'l=d'}, + categoryExclusions: ['a', 'b'], + }, + }; impls[3] = {}; - expect(getTargetingAndExclusions(impls)).to.jsonEqual( - {'prev_scp': '|a=1&b=2|c=1&d=l%3Dd&excl_cat=a,b|'}); + expect(getTargetingAndExclusions(impls)).to.jsonEqual({ + 'prev_scp': '|a=1&b=2|c=1&d=l%3Dd&excl_cat=a,b|', + }); }); it('should determine experiment ids', () => { expect(getExperimentIds(impls)).to.be.null; - impls[0] = {win: {location: {hash: '#deid=123,456,7'}}, - experimentIds: []}; + impls[0] = { + win: {location: {hash: '#deid=123,456,7'}}, + experimentIds: [], + }; // NOTE(keithwrightbos): let's hope this doesn't flake given eids // are stored in object. expect(getExperimentIds(impls)).to.jsonEqual({'eid': '7,123,456'}); impls[0].experimentIds = ['901', '902']; - expect(getExperimentIds(impls)).to.jsonEqual( - {'eid': '7,123,456,901,902'}); + expect(getExperimentIds(impls)).to.jsonEqual({ + 'eid': '7,123,456,901,902', + }); impls[1] = {experimentIds: ['902', '903']}; - expect(getExperimentIds(impls)).to.jsonEqual( - {'eid': '7,123,456,901,902,903'}); + expect(getExperimentIds(impls)).to.jsonEqual({ + 'eid': '7,123,456,901,902,903', + }); }); it('should determine identity', () => { impls[0] = new AmpAdNetworkDoubleclickImpl( - env.win.document.createElement('span')); + env.win.document.createElement('span') + ); impls[0].identityToken = {token: 'foo', jar: 'bar', pucrd: 'oof'}; impls[1] = {}; - expect(getIdentity(impls)).to.jsonEqual( - {adsid: 'foo', jar: 'bar', pucrd: 'oof'}); + expect(getIdentity(impls)).to.jsonEqual({ + adsid: 'foo', + jar: 'bar', + pucrd: 'oof', + }); }); it('should combine force safeframe', () => { expect(getForceSafeframe(impls)).to.be.null; @@ -233,11 +249,15 @@ describes.realWin('Doubleclick SRA', config , env => { }); it('should combine page offsets', () => { impls[0] = {getPageLayoutBox: () => ({left: 123, top: 456})}; - expect(getPageOffsets(impls)).to.jsonEqual( - {'adxs': '123', 'adys': '456'}); + expect(getPageOffsets(impls)).to.jsonEqual({ + 'adxs': '123', + 'adys': '456', + }); impls[1] = {getPageLayoutBox: () => ({left: 123, top: 789})}; - expect(getPageOffsets(impls)).to.jsonEqual( - {'adxs': '123,123', 'adys': '456,789'}); + expect(getPageOffsets(impls)).to.jsonEqual({ + 'adxs': '123,123', + 'adys': '456,789', + }); }); it('should combine contained state', () => { expect(getContainers(impls)).to.be.null; @@ -245,8 +265,14 @@ describes.realWin('Doubleclick SRA', config , env => { expect(getContainers(impls)).to.be.null; impls[1] = {element: {parentElement: {tagName: 'AMP-CAROUSEL'}}}; expect(getContainers(impls)).to.jsonEqual({'acts': '|ac'}); - impls[2] = {element: {parentElement: {tagName: 'AMP-CAROUSEL', - parentElement: {tagName: 'AMP-STICKY-AD'}}}}; + impls[2] = { + element: { + parentElement: { + tagName: 'AMP-CAROUSEL', + parentElement: {tagName: 'AMP-STICKY-AD'}, + }, + }, + }; expect(getContainers(impls)).to.jsonEqual({'acts': '|ac|ac,sa'}); }); it('should combine fluid state', () => { @@ -261,17 +287,21 @@ describes.realWin('Doubleclick SRA', config , env => { let impl; beforeEach(() => { - const element = createAndAppendAdElement( - {'data-a4a-upgrade-type': 'amp-ad-network-doubleclick-impl'}); + const element = createAndAppendAdElement({ + 'data-a4a-upgrade-type': 'amp-ad-network-doubleclick-impl', + }); // Testing competitive exclusion when we have an AMP ad and a non-AMP ad // on the same page. Need to add the child frame of the element to stand // in as the non-AMP ad. createAndAppendAdElement( - { - src: 'https://foo.com', - height: 320, - width: 50, - }, 'iframe', element); + { + src: 'https://foo.com', + height: 320, + width: 50, + }, + 'iframe', + element + ); impl = new AmpAdNetworkDoubleclickImpl(element); impl.buildCallback(); impl.isAmpCreative_ = true; @@ -305,18 +335,20 @@ describes.realWin('Doubleclick SRA', config , env => { 'data-force-safeframe': forceSafeFrame ? '1' : '0', 'data-multi-size': '9999x9999', }; - const element1 = - createElementWithAttributes(doc, 'amp-ad', config1); + const element1 = createElementWithAttributes(doc, 'amp-ad', config1); const impl1 = new AmpAdNetworkDoubleclickImpl(element1); sandbox.stub(impl1, 'getPageLayoutBox').returns({top: 123, left: 456}); impl1.experimentIds = [MANUAL_EXPERIMENT_ID]; - sandbox.stub(impl1, 'generateAdKey_').withArgs('50x320') - .returns('13579'); + sandbox + .stub(impl1, 'generateAdKey_') + .withArgs('50x320') + .returns('13579'); impl1.populateAdUrlState(); - impl1.identityToken = - /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({ - token: 'abcdef', jar: 'some_jar', pucrd: 'some_pucrd', - }); + impl1.identityToken = /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/ ({ + token: 'abcdef', + jar: 'some_jar', + pucrd: 'some_pucrd', + }); const targeting2 = { cookieOptOut: 1, categoryExclusions: 'food', @@ -332,12 +364,13 @@ describes.realWin('Doubleclick SRA', config , env => { 'data-multi-size-validation': 'false', 'data-multi-size': '1x2,3x4', }; - const element2 = - createElementWithAttributes(doc, 'amp-ad', config2); + const element2 = createElementWithAttributes(doc, 'amp-ad', config2); const impl2 = new AmpAdNetworkDoubleclickImpl(element2); sandbox.stub(impl2, 'getPageLayoutBox').returns({top: 789, left: 101}); - sandbox.stub(impl2, 'generateAdKey_').withArgs('250x300') - .returns('2468'); + sandbox + .stub(impl2, 'generateAdKey_') + .withArgs('250x300') + .returns('2468'); element2.setAttribute(EXPERIMENT_ATTRIBUTE, MANUAL_EXPERIMENT_ID); impl2.populateAdUrlState(); const exp = { @@ -386,7 +419,12 @@ describes.realWin('Doubleclick SRA', config , env => { } function generateSraXhrMockCall( - validInstances, networkId, responses, opt_xhrFail, opt_allInvalid) { + validInstances, + networkId, + responses, + opt_xhrFail, + opt_allInvalid + ) { devAssert(validInstances.length > 1); devAssert(!(opt_xhrFail && opt_allInvalid)); // Start with nameframe method, SRA will override to use safeframe. @@ -394,39 +432,55 @@ describes.realWin('Doubleclick SRA', config , env => { headers[RENDERING_TYPE_HEADER] = XORIGIN_MODE.NAMEFRAME; // Assume all implementations have same data slot. const iuParts = encodeURIComponent( - validInstances[0].element.getAttribute('data-slot').split(/\//) - .splice(1).join()); - sandbox.stub(validInstances[0], 'getLocationQueryParameterValue') - .withArgs('google_preview').returns('abcdef'); + validInstances[0].element + .getAttribute('data-slot') + .split(/\//) + .splice(1) + .join() + ); + sandbox + .stub(validInstances[0], 'getLocationQueryParameterValue') + .withArgs('google_preview') + .returns('abcdef'); const xhrWithArgs = xhrMock.withArgs( - sinon.match(new RegExp( - '^https:\/\/securepubads\\.g\\.doubleclick\\.net' + - '\/gampad\/ads\\?output=ldjh&impl=fifs&iu_parts=' + - `${iuParts}&enc_prev_ius=.*&gct=abcdef`)), - { - mode: 'cors', - method: 'GET', - credentials: 'include', - }); + sinon.match( + new RegExp( + '^https://securepubads\\.g\\.doubleclick\\.net' + + '/gampad/ads\\?output=ldjh&impl=fifs&iu_parts=' + + `${iuParts}&enc_prev_ius=.*&gct=abcdef` + ) + ), + { + mode: 'cors', + method: 'GET', + credentials: 'include', + } + ); if (opt_xhrFail) { - xhrWithArgs.returns(Promise.reject( - new TypeError('some random network error'))); + xhrWithArgs.returns( + Promise.reject(new TypeError('some random network error')) + ); } else if (opt_allInvalid) { xhrWithArgs.throws(new Error('invalid should not make xhr!')); } else { - xhrWithArgs.returns(Promise.resolve({ - arrayBuffer: () => { throw new Error('Expected SRA!'); }, - bodyUsed: false, - text: () => { - let slotDataString = ''; - responses.forEach(slot => { - slotDataString += - `${JSON.stringify(slot.headers)}\n${slot.creative}\n`; - }); - return Promise.resolve(slotDataString); - }, - headers, - })); + xhrWithArgs.returns( + Promise.resolve({ + arrayBuffer: () => { + throw new Error('Expected SRA!'); + }, + bodyUsed: false, + text: () => { + let slotDataString = ''; + responses.forEach(slot => { + slotDataString += `${JSON.stringify(slot.headers)}\n${ + slot.creative + }\n`; + }); + return Promise.resolve(slotDataString); + }, + headers, + }) + ); } } @@ -438,25 +492,28 @@ describes.realWin('Doubleclick SRA', config , env => { }; const iu = encodeURIComponent(impl.element.getAttribute('data-slot')); const urlRegexp = new RegExp( - '^https:\/\/securepubads\\.g\\.doubleclick\\.net' + - `\/gampad\/ads\\?iu=${iu}&`); - xhrMock.withArgs( - sinon.match(urlRegexp), - { - mode: 'cors', - method: 'GET', - credentials: 'include', - }).returns(Promise.resolve({ - arrayBuffer: () => Promise.resolve(utf8Encode(creative)), - bodyUsed: false, - headers: { - get: header => headers[header], - has: header => header in headers, - }, - text: () => { - throw new Error('should not be SRA!'); - }, - })); + '^https://securepubads\\.g\\.doubleclick\\.net' + + `\/gampad\/ads\\?iu=${iu}&` + ); + xhrMock + .withArgs(sinon.match(urlRegexp), { + mode: 'cors', + method: 'GET', + credentials: 'include', + }) + .returns( + Promise.resolve({ + arrayBuffer: () => Promise.resolve(utf8Encode(creative)), + bodyUsed: false, + headers: { + get: header => headers[header], + has: header => header in headers, + }, + text: () => { + throw new Error('should not be SRA!'); + }, + }) + ); } /** @@ -479,7 +536,10 @@ describes.realWin('Doubleclick SRA', config , env => { function executeTest(items, opt_implicitSra) { if (!opt_implicitSra) { createAndAppendAdElement( - {name: 'amp-ad-doubleclick-sra'}, 'meta', doc.head); + {name: 'amp-ad-doubleclick-sra'}, + 'meta', + doc.head + ); } // Store if XHR will fail by networkId. const networkXhrFailure = {}; @@ -487,8 +547,10 @@ describes.realWin('Doubleclick SRA', config , env => { const networkValidity = {}; const doubleclickInstances = []; const networkNestHeaders = []; - const attemptCollapseSpy = - sandbox.spy(BaseElement.prototype, 'attemptCollapse'); + const attemptCollapseSpy = sandbox.spy( + BaseElement.prototype, + 'attemptCollapse' + ); const expIds = []; let expectedAttemptCollapseCalls = 0; items.forEach(network => { @@ -515,20 +577,21 @@ describes.realWin('Doubleclick SRA', config , env => { networkXhrFailure[network.networkId] = !!network.xhrFail; networkNestHeaders[network.networkId] = network.nestHeaders; expectedAttemptCollapseCalls += - network.xhrFail && !opt_implicitSra ? network.instances : 0; + network.xhrFail && !opt_implicitSra ? network.instances : 0; expIds[network.networkId] = network.expIds || []; }); const grouping = {}; const groupingPromises = {}; doubleclickInstances.forEach(impl => { const networkId = getNetworkId(impl.element); - (grouping[networkId] || (grouping[networkId] = [])) - .push(impl); - (groupingPromises[networkId] || (groupingPromises[networkId] = [])) - .push(Promise.resolve(impl)); + (grouping[networkId] || (grouping[networkId] = [])).push(impl); + ( + groupingPromises[networkId] || (groupingPromises[networkId] = []) + ).push(Promise.resolve(impl)); }); - sandbox.stub(AmpAdNetworkDoubleclickImpl.prototype, 'groupSlotsForSra') - .returns(Promise.resolve(groupingPromises)); + sandbox + .stub(AmpAdNetworkDoubleclickImpl.prototype, 'groupSlotsForSra') + .returns(Promise.resolve(groupingPromises)); let idx = 0; const layoutCallbacks = []; const getLayoutCallback = (impl, creative, isSra, noRender) => { @@ -543,7 +606,8 @@ describes.realWin('Doubleclick SRA', config , env => { if (opt_implicitSra) { expect(impl.iframe).to.be.ok; expect(impl.iframe.src).to.match( - /securepubads\.g\.doubleclick\.net/); + /securepubads\.g\.doubleclick\.net/ + ); return; } expect(impl.postAdResponseExperimentFeatures['foo']).to.equal('bar'); @@ -552,7 +616,8 @@ describes.realWin('Doubleclick SRA', config , env => { if (isSra) { // Expect safeframe. expect(name).to.match( - new RegExp(`^\\d+-\\d+-\\d+;\\d+;${creative}`)); + new RegExp(`^\\d+-\\d+-\\d+;\\d+;${creative}`) + ); } else { // Expect nameframe render. expect(JSON.parse(name).creative).to.equal(creative); @@ -560,10 +625,12 @@ describes.realWin('Doubleclick SRA', config , env => { }); }; Object.keys(grouping).forEach(networkId => { - const validInstances = grouping[networkId].filter(impl => - impl.element.getAttribute('data-test-invalid') != 'true'); - const isSra = validInstances.length > 1 && - !validInstances[0].experimentIds.includes('21062235'); + const validInstances = grouping[networkId].filter( + impl => impl.element.getAttribute('data-test-invalid') != 'true' + ); + const isSra = + validInstances.length > 1 && + !validInstances[0].experimentIds.includes('21062235'); const sraResponses = []; validInstances.forEach(impl => { const creative = `slot${idx++}`; @@ -579,26 +646,41 @@ describes.realWin('Doubleclick SRA', config , env => { } else { generateNonSraXhrMockCall(impl, creative); } - layoutCallbacks.push(getLayoutCallback( - impl, creative, isSra, + layoutCallbacks.push( + getLayoutCallback( + impl, + creative, + isSra, (!opt_implicitSra && networkXhrFailure[networkId]) || - impl.element.getAttribute('data-test-invalid') == 'true')); + impl.element.getAttribute('data-test-invalid') == 'true' + ) + ); }); if (isSra) { - generateSraXhrMockCall(validInstances, networkId, sraResponses, - networkXhrFailure[networkId], networkValidity[networkId]); + generateSraXhrMockCall( + validInstances, + networkId, + sraResponses, + networkXhrFailure[networkId], + networkValidity[networkId] + ); } }); - return Promise.all(layoutCallbacks).then(() => expect( - attemptCollapseSpy.callCount).to.equal(expectedAttemptCollapseCalls)); + return Promise.all(layoutCallbacks).then(() => + expect(attemptCollapseSpy.callCount).to.equal( + expectedAttemptCollapseCalls + ) + ); } beforeEach(() => { xhrMock = sandbox.stub(Xhr.prototype, 'fetch'); - sandbox.stub(AmpA4A.prototype, - 'getSigningServiceNames').returns(['google']); - sandbox.stub(SignatureVerifier.prototype, 'loadKeyset') - .callsFake(() => {}); + sandbox + .stub(AmpA4A.prototype, 'getSigningServiceNames') + .returns(['google']); + sandbox + .stub(SignatureVerifier.prototype, 'loadKeyset') + .callsFake(() => {}); }); afterEach(() => { @@ -607,17 +689,17 @@ describes.realWin('Doubleclick SRA', config , env => { it('should not use SRA if single slot', () => executeTest([1234])); - it('should not use SRA if single slot, multiple networks', - () => executeTest([1234, 4567])); + it('should not use SRA if single slot, multiple networks', () => + executeTest([1234, 4567])); - it('should correctly use SRA for multiple slots', - () => executeTest([1234, 1234])); + it('should correctly use SRA for multiple slots', () => + executeTest([1234, 1234])); it('should correctly handle SRA response with nested headers', () => executeTest([{networkId: 1234, instances: 2, nestHeaders: true}])); - it('should not send SRA request if slots are invalid', - () => executeTest([{networkId: 1234, invalidInstances: 2}])); + it('should not send SRA request if slots are invalid', () => + executeTest([{networkId: 1234, invalidInstances: 2}])); it('should send SRA request if more than 1 slot is valid', () => executeTest([{networkId: 1234, instances: 2, invalidInstances: 2}])); @@ -628,22 +710,31 @@ describes.realWin('Doubleclick SRA', config , env => { it('should send SRA request if only 1 slot and no recovery exp', () => executeTest([{networkId: 1234, instances: 1, expIds: ['21062235']}])); - it('should handle xhr failure by not sending subsequent request', - () => executeTest([{networkId: 1234, instances: 2, xhrFail: true}])); - - it('should handle xhr failure by via subsequent request if implicit', - () => executeTest([{networkId: 1234, instances: 2, xhrFail: true}], - true)); - - it('should handle mixture of xhr and non xhr failures', () => executeTest( - [{networkId: 1234, instances: 2, xhrFail: true}, 4567, 4567])); - - it('should correctly use SRA for multiple slots. multiple networks', - () => executeTest([1234, 4567, 1234, 4567])); - - it('should handle mixture of all possible scenarios', () => executeTest( - [1234, 1234, 101, {networkId: 4567, instances: 2, xhrFail: true}, 202, - {networkId: 8901, instances: 3, invalidInstances: 1}])); + it('should handle xhr failure by not sending subsequent request', () => + executeTest([{networkId: 1234, instances: 2, xhrFail: true}])); + + it('should handle xhr failure by via subsequent request if implicit', () => + executeTest([{networkId: 1234, instances: 2, xhrFail: true}], true)); + + it('should handle mixture of xhr and non xhr failures', () => + executeTest([ + {networkId: 1234, instances: 2, xhrFail: true}, + 4567, + 4567, + ])); + + it('should correctly use SRA for multiple slots. multiple networks', () => + executeTest([1234, 4567, 1234, 4567])); + + it('should handle mixture of all possible scenarios', () => + executeTest([ + 1234, + 1234, + 101, + {networkId: 4567, instances: 2, xhrFail: true}, + 202, + {networkId: 8901, instances: 3, invalidInstances: 1}, + ])); }); describe('#sraBlockCallbackHandler', () => { @@ -652,18 +743,30 @@ describes.realWin('Doubleclick SRA', config , env => { const headerObj = {a: 'b', c: 123}; const slotDeferred = new Deferred(); const sraRequestAdUrlResolvers = [ - slotDeferred.resolve, {resolve: () => {throw new Error();}}]; + slotDeferred.resolve, + { + resolve: () => { + throw new Error(); + }, + }, + ]; sraBlockCallbackHandler( - creative, headerObj, /* done */false, sraRequestAdUrlResolvers); + creative, + headerObj, + /* done */ false, + sraRequestAdUrlResolvers + ); expect(sraRequestAdUrlResolvers.length).to.equal(1); return slotDeferred.promise.then(fetchResponse => { expect(fetchResponse.headers.get('a')).to.equal('b'); expect(fetchResponse.headers.get('c')).to.equal('123'); - expect(fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase())) - .to.equal(XORIGIN_MODE.SAFEFRAME); + expect( + fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase()) + ).to.equal(XORIGIN_MODE.SAFEFRAME); expect(fetchResponse.headers.has('unknown')).to.be.false; - return fetchResponse.arrayBuffer().then(buffer => - expect(utf8Decode(buffer)).to.equal(creative)); + return fetchResponse + .arrayBuffer() + .then(buffer => expect(utf8Decode(buffer)).to.equal(creative)); }); }); @@ -690,17 +793,28 @@ describes.realWin('Doubleclick SRA', config , env => { for (let i = 1; i <= blocks.length; i++) { const {creative, headers, deferred} = blocks[i - 1]; sraBlockCallbackHandler( - creative, headers, resolvers.length == 1, resolvers); + creative, + headers, + resolvers.length == 1, + resolvers + ); expect(resolvers.length).to.equal(blocks.length - i); - promises.push(deferred.promise.then(fetchResponse => { - Object.keys(headers).forEach(name => expect( - fetchResponse.header.get(name)).to.equal(String(headers[name]))); - expect(fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase())) - .to.equal(XORIGIN_MODE.SAFEFRAME); - expect(fetchResponse.headers.has('unknown')).to.be.false; - return fetchResponse.arrayBuffer().then(buffer => - expect(utf8Decode(buffer)).to.equal(creative)); - })); + promises.push( + deferred.promise.then(fetchResponse => { + Object.keys(headers).forEach(name => + expect(fetchResponse.header.get(name)).to.equal( + String(headers[name]) + ) + ); + expect( + fetchResponse.headers.get(RENDERING_TYPE_HEADER.toLowerCase()) + ).to.equal(XORIGIN_MODE.SAFEFRAME); + expect(fetchResponse.headers.has('unknown')).to.be.false; + return fetchResponse + .arrayBuffer() + .then(buffer => expect(utf8Decode(buffer)).to.equal(creative)); + }) + ); } return promises; }); diff --git a/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js b/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js index 35df58b2948b5..8d3b47dfaaf35 100644 --- a/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js +++ b/extensions/amp-ad-network-fake-impl/0.1/amp-ad-network-fake-impl.js @@ -21,7 +21,6 @@ import {user, userAssert} from '../../../src/log'; const TAG = 'AMP-AD-NETWORK-FAKE-IMPL'; export class AmpAdNetworkFakeImpl extends AmpA4A { - /** * @param {!Element} element */ @@ -31,8 +30,11 @@ export class AmpAdNetworkFakeImpl extends AmpA4A { /** @override */ buildCallback() { - userAssert(this.element.hasAttribute('src'), - 'Attribute src required for : %s', this.element); + userAssert( + this.element.hasAttribute('src'), + 'Attribute src required for : %s', + this.element + ); super.buildCallback(); } @@ -60,17 +62,22 @@ export class AmpAdNetworkFakeImpl extends AmpA4A { if (!response) { return null; } - const {status, headers} = - /** @type {{status: number, headers: !Headers}} */ (response); + const { + status, + headers, + } = /** @type {{status: number, headers: !Headers}} */ (response); // In the convert creative mode the content is the plain AMP HTML. // This mode is primarily used for A4A Envelop for testing. // See DEVELOPING.md for more info. if (this.element.getAttribute('a4a-conversion') == 'true') { return response.text().then( - responseText => new Response( - this.transformCreative_(responseText), - {status, headers})); + responseText => + new Response(this.transformCreative_(responseText), { + status, + headers, + }) + ); } // Normal mode: Expect the creative is written in AMP4ADS doc. @@ -119,7 +126,7 @@ export class AmpAdNetworkFakeImpl extends AmpA4A { style.parentNode.removeChild(style); } - let creative = root./*OK*/outerHTML; + let creative = root./*OK*/ outerHTML; // Metadata creative += '' + - '' + - 'Non-Performant Fake Iframe' + - '' + - ''; - const frameUrl2 = addParamsToUrl('http://ads.localhost:' + - document.location.port + '/amp4test/compose-doc', {body}); - sandbox.stub(env.ampdoc.win.document.body, 'appendChild'); - new IframeTransport(env.ampdoc.win, 'some_other_vendor_type', - {iframe: frameUrl2}, frameUrl2 + '-3'); - sandbox.restore(); - const errorSpy = sandbox.spy(user(), 'error'); - const {frame} = IframeTransport.getFrameData('some_other_vendor_type'); - frame.setAttribute('style', ''); - env.ampdoc.win.document.body.appendChild(frame); - return new Promise((resolve,unused) => { - expectPostMessage(frame.contentWindow, env.ampdoc.win, 'doneSleeping') - .then(() => { - expect(errorSpy).to.be.called; - expect(errorSpy.args[0][1]).to.match( - /Long Task: Vendor: "some_other_vendor_type"/); - resolve(); - }); +describes.realWin( + 'amp-analytics.iframe-transport', + {amp: true, allowExternalResources: true}, + env => { + it('logs poor performance of vendor iframe', () => { + const body = + '' + + '' + + 'Non-Performant Fake Iframe' + + '' + + ''; + const frameUrl2 = addParamsToUrl( + 'http://ads.localhost:' + + document.location.port + + '/amp4test/compose-doc', + {body} + ); + sandbox.stub(env.ampdoc.win.document.body, 'appendChild'); + new IframeTransport( + env.ampdoc.win, + 'some_other_vendor_type', + {iframe: frameUrl2}, + frameUrl2 + '-3' + ); + sandbox.restore(); + const errorSpy = sandbox.spy(user(), 'error'); + const {frame} = IframeTransport.getFrameData('some_other_vendor_type'); + frame.setAttribute('style', ''); + env.ampdoc.win.document.body.appendChild(frame); + return new Promise((resolve, unused) => { + expectPostMessage( + frame.contentWindow, + env.ampdoc.win, + 'doneSleeping' + ).then(() => { + expect(errorSpy).to.be.called; + expect(errorSpy.args[0][1]).to.match( + /Long Task: Vendor: "some_other_vendor_type"/ + ); + resolve(); }); - }).timeout(10000); - }); + }); + }).timeout(10000); + } +); diff --git a/extensions/amp-analytics/0.1/test/test-instrumentation.js b/extensions/amp-analytics/0.1/test/test-instrumentation.js index 9ac05daf3fb5f..42e5b6a568184 100644 --- a/extensions/amp-analytics/0.1/test/test-instrumentation.js +++ b/extensions/amp-analytics/0.1/test/test-instrumentation.js @@ -16,9 +16,7 @@ import {CustomEventTracker} from '../events'; -import { - InstrumentationService, -} from '../instrumentation.js'; +import {InstrumentationService} from '../instrumentation.js'; describes.realWin('InstrumentationService', {amp: 1}, env => { let win; @@ -77,48 +75,50 @@ describes.realWin('InstrumentationService', {amp: 1}, env => { }); }); - -describes.realWin('InstrumentationService in FIE', { - amp: {ampdoc: 'fie'}, -}, env => { - let win; - let embed; - let ampdoc; - let service; - let root; - let analyticsElement; - let target; - - beforeEach(() => { - win = env.win; - embed = env.embed; - ampdoc = env.ampdoc; - service = new InstrumentationService(ampdoc); - root = service.ampdocRoot_; - - analyticsElement = win.document.createElement('amp-analytics'); - win.document.body.appendChild(analyticsElement); - - target = win.document.createElement('div'); - win.document.body.appendChild(target); - }); - - it('should create and reuse embed root', () => { - expect(root.ampdoc).to.equal(ampdoc); - expect(root.parent).to.be.null; - - const group1 = service.createAnalyticsGroup(analyticsElement); - const embedRoot = group1.root_; - expect(embedRoot).to.not.equal(root); - expect(embedRoot.parent).to.equal(root); - expect(embedRoot.ampdoc).to.equal(ampdoc); - expect(embedRoot.embed).to.equal(embed); - - // Reuse the previously created instance. - const analyticsElement2 = win.document.createElement('amp-analytics'); - win.document.body.appendChild(analyticsElement2); - const group2 = service.createAnalyticsGroup(analyticsElement2); - expect(group2.root_).to.equal(embedRoot); - }); -}); - +describes.realWin( + 'InstrumentationService in FIE', + { + amp: {ampdoc: 'fie'}, + }, + env => { + let win; + let embed; + let ampdoc; + let service; + let root; + let analyticsElement; + let target; + + beforeEach(() => { + win = env.win; + embed = env.embed; + ampdoc = env.ampdoc; + service = new InstrumentationService(ampdoc); + root = service.ampdocRoot_; + + analyticsElement = win.document.createElement('amp-analytics'); + win.document.body.appendChild(analyticsElement); + + target = win.document.createElement('div'); + win.document.body.appendChild(target); + }); + + it('should create and reuse embed root', () => { + expect(root.ampdoc).to.equal(ampdoc); + expect(root.parent).to.be.null; + + const group1 = service.createAnalyticsGroup(analyticsElement); + const embedRoot = group1.root_; + expect(embedRoot).to.not.equal(root); + expect(embedRoot.parent).to.equal(root); + expect(embedRoot.ampdoc).to.equal(ampdoc); + expect(embedRoot.embed).to.equal(embed); + + // Reuse the previously created instance. + const analyticsElement2 = win.document.createElement('amp-analytics'); + win.document.body.appendChild(analyticsElement2); + const group2 = service.createAnalyticsGroup(analyticsElement2); + expect(group2.root_).to.equal(embedRoot); + }); + } +); diff --git a/extensions/amp-analytics/0.1/test/test-linker-manager.js b/extensions/amp-analytics/0.1/test/test-linker-manager.js index 5fee17cfc82d8..8429cdb54b7b0 100644 --- a/extensions/amp-analytics/0.1/test/test-linker-manager.js +++ b/extensions/amp-analytics/0.1/test/test-linker-manager.js @@ -1,4 +1,3 @@ - /** * Copyright 2018 The AMP HTML Authors. All Rights Reserved. * @@ -52,20 +51,24 @@ describes.realWin('Linker Manager', {amp: true}, env => { beforeSubmit: beforeSubmitStub, }); - sandbox.stub(Services, 'documentInfoForDoc') - .returns({ - sourceUrl: 'https://amp.source.com/some/path?q=123', - canonicalUrl: 'https://www.canonical.com/some/path?q=123', - }); + sandbox.stub(Services, 'documentInfoForDoc').returns({ + sourceUrl: 'https://amp.source.com/some/path?q=123', + canonicalUrl: 'https://www.canonical.com/some/path?q=123', + }); // LinkerManager uses Url/UrlReplacements services scoped to the element, // but for testing stub in the top-level ampdoc service for simplicity. element = {}; const urlReplacements = Services.urlReplacementsForDoc(doc.documentElement); - sandbox.stub(Services, 'urlReplacementsForDoc') - .withArgs(element).returns(urlReplacements); + sandbox + .stub(Services, 'urlReplacementsForDoc') + .withArgs(element) + .returns(urlReplacements); const url = Services.urlForDoc(doc.documentElement); - sandbox.stub(Services, 'urlForDoc').withArgs(element).returns(url); + sandbox + .stub(Services, 'urlForDoc') + .withArgs(element) + .returns(url); handlers = []; sandbox.stub(Services, 'navigationForDoc').returns({ @@ -84,16 +87,21 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('registers anchor mutator if given valid linkers config', () => { - new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(1); }); @@ -109,20 +117,25 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('does not register anchor mutator if no linkers enabled', () => { - new LinkerManager(ampdoc, { - linkers: { - testLinker1: { - ids: { - bar: 'foo', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker1: { + ids: { + bar: 'foo', + }, }, - }, - testLinker2: { - ids: { - foo: 'bar', + testLinker2: { + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(0); }); @@ -130,16 +143,21 @@ describes.realWin('Linker Manager', {amp: true}, env => { windowInterface.getLocation.returns({ origin: 'https://amp.source.com', }); - new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - bar: 'foo', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + bar: 'foo', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(0); }); @@ -147,24 +165,31 @@ describes.realWin('Linker Manager', {amp: true}, env => { windowInterface.getLocation.returns({ origin: 'https://amp.source.com', }); - new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - proxyOnly: false, - ids: { - bar: 'foo', + new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + proxyOnly: false, + ids: { + bar: 'foo', + }, }, }, }, - }, /* type */ null, element).init(); + /* type */ null, + element + ).init(); expect(handlers.length).to.equal(1); }); it('should resolve vars and append to matching anchor', () => { - windowInterface.getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) ' + + windowInterface.getUserAgent.returns( + 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 ' + - 'Safari/537.36'); + 'Safari/537.36' + ); windowInterface.getUserLanguage.returns('en-US'); sandbox.useFakeTimers(1533329483292); sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); @@ -193,10 +218,11 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { expect(handlers.length).to.equal(1); expect(clickAnchor('https://www.source.com/dest?a=1')).to.equal( - 'https://www.source.com/dest' + + 'https://www.source.com/dest' + '?a=1' + '&testLinker1=1*1pgvkob*_key*VEVTVCUyMFRJVExF*gclid*MjM0' + - '&testLinker2=1*1u4ugj3*foo*YmFy'); + '&testLinker2=1*1u4ugj3*foo*YmFy' + ); }); }); @@ -217,7 +243,7 @@ describes.realWin('Linker Manager', {amp: true}, env => { return lm.init().then(() => { expect(handlers.length).to.equal(1); expect(clickAnchor('https://www.source.com/dest?a=1')).to.equal( - 'https://www.source.com/dest?a=1' + 'https://www.source.com/dest?a=1' ); }); }); @@ -611,10 +637,18 @@ describes.realWin('Linker Manager', {amp: true}, env => { }, }, }; - const p1 = - new LinkerManager(ampdoc, config, 'googleanalytics', element).init(); - const p2 = - new LinkerManager(ampdoc, config, 'googleanalytics', element).init(); + const p1 = new LinkerManager( + ampdoc, + config, + 'googleanalytics', + element + ).init(); + const p2 = new LinkerManager( + ampdoc, + config, + 'googleanalytics', + element + ).init(); return Promise.all([p1, p2]).then(() => { const a = clickAnchor('https://www.source.com/path'); expect(a).to.not.match(/(testLinker1=.*){2}/); @@ -714,16 +748,21 @@ describes.realWin('Linker Manager', {amp: true}, env => { describe('form support', () => { it('should register the `beforeSubmit` callback', () => { toggleExperiment(win, 'linker-form', true); - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { expect(beforeSubmitStub.calledOnce).to.be.true; @@ -733,17 +772,22 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('should add hidden elements to form if not action-xhr', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -763,17 +807,22 @@ describes.realWin('Linker Manager', {amp: true}, env => { }); it('if action-xhr and method=GET it should add linker-xhr attr', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -785,24 +834,30 @@ describes.realWin('Linker Manager', {amp: true}, env => { expect(setterSpy.calledOnce).to.be.true; - const calledWithLinkerUrl = setterSpy - .calledWith(sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/)); + const calledWithLinkerUrl = setterSpy.calledWith( + sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/) + ); return expect(calledWithLinkerUrl).to.be.true; }); }); it('if action-xhr and method=POST it should add linker-xhr attr', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -814,25 +869,30 @@ describes.realWin('Linker Manager', {amp: true}, env => { expect(setterSpy.calledOnce).to.be.true; - const calledWithLinkerUrl = setterSpy - .calledWith(sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/)); + const calledWithLinkerUrl = setterSpy.calledWith( + sinon.match(/testLinker=1\*\w{5,7}\*foo*\w+/) + ); return expect(calledWithLinkerUrl).to.be.true; }); }); - it('should not add linker if no domain match', () => { - const linkerManager = new LinkerManager(ampdoc, { - linkers: { - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const linkerManager = new LinkerManager( + ampdoc, + { + linkers: { + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, + destinationDomains: ['www.ampproject.com'], }, - destinationDomains: ['www.ampproject.com'], }, - }, /* type */ null, element); + /* type */ null, + element + ); return linkerManager.init().then(() => { const form = createForm(); @@ -849,29 +909,39 @@ describes.realWin('Linker Manager', {amp: true}, env => { origin: 'https://www.ampbyexample.com', }); - const manager1 = new LinkerManager(ampdoc, { - linkers: { - proxyOnly: false, - testLinker: { - enabled: true, - ids: { - foo: 'bar', + const manager1 = new LinkerManager( + ampdoc, + { + linkers: { + proxyOnly: false, + testLinker: { + enabled: true, + ids: { + foo: 'bar', + }, }, }, }, - }, /* type */ null, element); + /* type */ null, + element + ); - const manager2 = new LinkerManager(ampdoc, { - linkers: { - proxyOnly: false, - testLinker2: { - enabled: true, - ids: { - hello: 'world', + const manager2 = new LinkerManager( + ampdoc, + { + linkers: { + proxyOnly: false, + testLinker2: { + enabled: true, + ids: { + hello: 'world', + }, }, }, }, - }, /* type */ null, element); + /* type */ null, + element + ); const p1 = manager1.init(); const p2 = manager2.init(); @@ -915,12 +985,11 @@ describe('areFriendlyDomains', () => { expect(areFriendlyDomains('amp.source.com', 'www.source.com')).to.be.true; expect(areFriendlyDomains('m.source.com', 'www.source.com')).to.be.true; expect(areFriendlyDomains('amp.www.source.com', 'source.com')).to.be.true; - expect(areFriendlyDomains('amp.source.com', 'm.www.source.com')) - .to.be.true; + expect(areFriendlyDomains('amp.source.com', 'm.www.source.com')).to.be.true; expect(areFriendlyDomains('amp.source.com', 'amp.google.com')).to.be.false; - expect(areFriendlyDomains('web.amp.source.com', 'web.m.source.com')) - .to.be.false; + expect(areFriendlyDomains('web.amp.source.com', 'web.m.source.com')).to.be + .false; }); }); diff --git a/extensions/amp-analytics/0.1/test/test-linker-reader.js b/extensions/amp-analytics/0.1/test/test-linker-reader.js index 3ee61515b546d..ff3fd2cc96214 100644 --- a/extensions/amp-analytics/0.1/test/test-linker-reader.js +++ b/extensions/amp-analytics/0.1/test/test-linker-reader.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import { installLinkerReaderService, linkerReaderServiceFor, @@ -33,9 +32,11 @@ describe('LinkerReader', () => { sandbox.useFakeTimers(1533329483292); sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); mockWin = mockWindowInterface(sandbox); - mockWin.getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) ' + + mockWin.getUserAgent.returns( + 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 ' + - 'Safari/537.36'); + 'Safari/537.36' + ); mockWin.getUserLanguage.returns('en-US'); mockWin.location = { href: 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx', @@ -58,13 +59,15 @@ describe('LinkerReader', () => { expectAsyncConsoleError(/LINKER_PARAM requires two params, name and id/); expect(linkerReader.get('testlinker')).to.be.null; expect(mockWin.location.href).to.equal( - 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'); + 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx' + ); }); it('return null when no linker name', () => { expect(linkerReader.get('nolinker', 'id')).to.be.null; expect(mockWin.location.href).to.equal( - 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'); + 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx' + ); }); it('return null when linker name value is invalid', () => { @@ -76,37 +79,43 @@ describe('LinkerReader', () => { it('return null when no linker id value', () => { mockWin.location.href = - 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'; + 'https://example.com?testlinker=1*1f66u1p*key1*dmFsdWUx'; expect(linkerReader.get('testlinker', 'key2')).to.be.null; expect(mockWin.location.href).to.equal('https://example.com/'); }); it('remove linker_param from url', () => { - mockWin.location.href = 'https://example.com?a=1&b=2&' + - 'testlinker=1*1f66u1p*key1*dmFsdWUx&c&' + - 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash'; + mockWin.location.href = + 'https://example.com?a=1&b=2&' + + 'testlinker=1*1f66u1p*key1*dmFsdWUx&c&' + + 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash'; linkerReader.get('testlinker', 'id'); - expect(mockWin.location.href).to.equal('https://example.com/?a=1&b=2&c&' + - 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash'); + expect(mockWin.location.href).to.equal( + 'https://example.com/?a=1&b=2&c&' + + 'testlinker2=1*1f66u1p*key1*dmFsdWUx&d=2#hash' + ); linkerReader.get('testlinker2', 'key1'); - expect(mockWin.location.href).to.equal('https://example.com/?a=1&b=2&c&' + - 'd=2#hash'); + expect(mockWin.location.href).to.equal( + 'https://example.com/?a=1&b=2&c&' + 'd=2#hash' + ); }); it('return correct id value', () => { - mockWin.location.href = 'https://example.com?' + + mockWin.location.href = + 'https://example.com?' + 'test=1*1f66u1p*key1*dmFsdWUx&var=foo&' + 'test2=1*1m48hbv*cid*MTIzNDU.*ref*aHR0cHM6Ly93d3cuZXhhbXBsZS5jb20.'; expect(linkerReader.get('test', 'key1')).to.equal('value1'); expect(linkerReader.get('test2', 'cid')).to.equal('12345'); expect(linkerReader.get('test2', 'ref')).to.equal( - 'https://www.example.com'); + 'https://www.example.com' + ); expect(mockWin.location.href).to.equal('https://example.com/?var=foo'); }); it('returns same value when reading the same id', () => { - mockWin.location.href = 'https://example.com?' + - 'test=1*1f66u1p*key1*dmFsdWUx&var=foo'; + mockWin.location.href = + 'https://example.com?' + 'test=1*1f66u1p*key1*dmFsdWUx&var=foo'; expect(linkerReader.get('test', 'key1')).to.equal('value1'); expect(linkerReader.get('test', 'key1')).to.equal('value1'); }); diff --git a/extensions/amp-analytics/0.1/test/test-linker.js b/extensions/amp-analytics/0.1/test/test-linker.js index 8c6c4c9641f22..d1a933399ef61 100644 --- a/extensions/amp-analytics/0.1/test/test-linker.js +++ b/extensions/amp-analytics/0.1/test/test-linker.js @@ -223,10 +223,12 @@ const createLinkerTests = [ description: 'works for AMP CID API generated Client ID', version: '1', pairs: { - '_ga': 'amp-' + + '_ga': + 'amp-' + 'oRg8vByriPdstwLgkz-UNWbp2P13vNFsnhES5vW8s5WodTOoea0mTiY7X62utLyz', }, - output: '1*1fkd1zz*_ga*' + + output: + '1*1fkd1zz*_ga*' + 'YW1wLW9SZzh2QnlyaVBkc3R3TGdrei1VTldicDJQMT' + 'N2TkZzbmhFUzV2VzhzNVdvZFRPb2VhMG1UaVk3WDYydXRMeXo.', }, @@ -234,10 +236,10 @@ const createLinkerTests = [ description: 'works for AMP Viewer generated Client ID', version: '1', pairs: { - '_ga': - 'WgcaAD4XN2lydhQVNFruk6X8zwoUg6K2RnaRlhjs6CXvTv4aJV-3oVLdI1WxxvJb', + '_ga': 'WgcaAD4XN2lydhQVNFruk6X8zwoUg6K2RnaRlhjs6CXvTv4aJV-3oVLdI1WxxvJb', }, - output: '1*19eaxqc*_ga*' + + output: + '1*19eaxqc*_ga*' + 'V2djYUFENFhOMmx5ZGhRVk5GcnVrNlg4endvVWc2Sz' + 'JSbmFSbGhqczZDWHZUdjRhSlYtM29WTGRJMVd4eHZKYg..', }, @@ -252,9 +254,11 @@ describe('Linker', () => { sandbox.useFakeTimers(BASE_TIME); sandbox.stub(Date.prototype, 'getTimezoneOffset').returns(420); mockWin = mockWindowInterface(sandbox); - mockWin.getUserAgent.returns('Mozilla/5.0 (X11; Linux x86_64) ' + + mockWin.getUserAgent.returns( + 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 ' + - 'Safari/537.36'); + 'Safari/537.36' + ); mockWin.getUserLanguage.returns('en-US'); }); diff --git a/extensions/amp-analytics/0.1/test/test-opacity.js b/extensions/amp-analytics/0.1/test/test-opacity.js index 95f20e92d2444..4cc17720992d1 100644 --- a/extensions/amp-analytics/0.1/test/test-opacity.js +++ b/extensions/amp-analytics/0.1/test/test-opacity.js @@ -27,7 +27,7 @@ describes.realWin('getMinOpacity', {amp: true}, env => { win = env.win; doc = win.document; style = doc.createElement('style'); - style.setAttribute('amp-custom',''); + style.setAttribute('amp-custom', ''); style.innerHTML = ` #img { opacity: 0.5; @@ -67,7 +67,7 @@ describes.realWin('getMinOpacity', {amp: true}, env => { expect(getMinOpacity(ampElement)).to.equal(0); }); - it('amp element\'s parent opacity value lower than amp element', () => { + it("amp element's parent opacity value lower than amp element", () => { parent.style.opacity = 0; expect(getMinOpacity(ampElement)).to.equal(0); diff --git a/extensions/amp-analytics/0.1/test/test-requests.js b/extensions/amp-analytics/0.1/test/test-requests.js index 4cbc2bd0fe278..7791b5e631a3d 100644 --- a/extensions/amp-analytics/0.1/test/test-requests.js +++ b/extensions/amp-analytics/0.1/test/test-requests.js @@ -49,12 +49,17 @@ describes.realWin('Requests', {amp: 1}, env => { function createRequestHandler(request, spy) { return new RequestHandler( - analyticsElement, request, preconnect, {sendRequest: spy}, false); + analyticsElement, + request, + preconnect, + {sendRequest: spy}, + false + ); } describe('RequestHandler', () => { describe('batch', () => { - it('should batch multiple send', function* () { + it('should batch multiple send', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r2', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); @@ -68,7 +73,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledOnce; }); - it('should work properly with no batch', function* () { + it('should work properly with no batch', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1'}; const handler = createRequestHandler(r, spy); @@ -79,15 +84,15 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledTwice; }); - - it('should preconnect', function* () { + it('should preconnect', function*() { const r = {'baseUrl': 'r2?cid=CLIENT_ID(scope)&var=${test}'}; const handler = createRequestHandler(r, sandbox.spy()); const expansionOptions = new ExpansionOptions({'test': 'expanded'}); handler.send({}, {}, expansionOptions, {}); yield macroTask(); expect(preconnectSpy).to.be.calledWith( - 'r2?cid=CLIENT_ID(scope)&var=expanded'); + 'r2?cid=CLIENT_ID(scope)&var=expanded' + ); }); }); @@ -154,7 +159,7 @@ describes.realWin('Requests', {amp: 1}, env => { } }); - it('should schedule send request with interval array', function* () { + it('should schedule send request with interval array', function*() { const r = {'baseUrl': 'r', 'batchInterval': [1, 2]}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); @@ -182,7 +187,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledOnce; }); - it('should not schedule send request w/o trigger', function* () { + it('should not schedule send request w/o trigger', function*() { const r = {'baseUrl': 'r', 'batchInterval': [1]}; createRequestHandler(r, spy); clock.tick(1000); @@ -190,7 +195,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.not.be.called; }); - it('should schedule send independent of trigger immediate', function* () { + it('should schedule send independent of trigger immediate', function*() { const r = {'baseUrl': 'r', 'batchInterval': [1, 2]}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); @@ -228,7 +233,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(handler3.reportWindow_).to.be.null; }); - it('should stop bathInterval outside batch report window', function* () { + it('should stop bathInterval outside batch report window', function*() { const r = {'baseUrl': 'r', 'batchInterval': 0.5, 'reportWindow': 1}; const handler = createRequestHandler(r, spy); @@ -246,7 +251,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.not.be.called; }); - it('should stop send request outside batch report window', function* () { + it('should stop send request outside batch report window', function*() { const r = {'baseUrl': 'r', 'reportWindow': 1}; const handler = createRequestHandler(r, spy); @@ -261,7 +266,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.not.be.called; }); - it('should flush batch queue after batch report window', function* () { + it('should flush batch queue after batch report window', function*() { const r = {'baseUrl': 'r', 'batchInterval': 5, 'reportWindow': 1}; const handler = createRequestHandler(r, spy); @@ -272,7 +277,7 @@ describes.realWin('Requests', {amp: 1}, env => { expect(spy).to.be.calledOnce; }); - it('should respect immediate trigger', function* () { + it('should respect immediate trigger', function*() { const r = {'baseUrl': 'r', 'batchInterval': 0.2, 'reportWindow': 0.5}; const handler = createRequestHandler(r, spy); @@ -287,7 +292,7 @@ describes.realWin('Requests', {amp: 1}, env => { }); describe('batch segments', () => { - it('should respect config extraUrlParam', function* () { + it('should respect config extraUrlParam', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); @@ -303,39 +308,59 @@ describes.realWin('Requests', {amp: 1}, env => { ]); }); - it('should respect trigger extraUrlParam', function* () { + it('should respect trigger extraUrlParam', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({'v2': '中'}); - handler.send({}, { - 'extraUrlParams': { - 'e1': 'e1', - 'e2': '${v2}', // check vars are used and not double encoded + handler.send( + {}, + { + 'extraUrlParams': { + 'e1': 'e1', + 'e2': '${v2}', // check vars are used and not double encoded + }, }, - }, expansionOptions, {}); + expansionOptions, + {} + ); handler.send( - {}, {'extraUrlParams': {'e1': 'e1'}}, expansionOptions, {}); + {}, + {'extraUrlParams': {'e1': 'e1'}}, + expansionOptions, + {} + ); clock.tick(1000); yield macroTask(); expect(spy).to.be.calledWith('r1', [ - {extraUrlParams: {e1: 'e1', e2: '中'}, - timestamp: 0, trigger: undefined}, + { + extraUrlParams: {e1: 'e1', e2: '中'}, + timestamp: 0, + trigger: undefined, + }, {extraUrlParams: {e1: 'e1'}, timestamp: 0, trigger: undefined}, ]); }); - it('should keep extraUrlParam', function* () { + it('should keep extraUrlParam', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1&${extraUrlParams}&r2', 'batchInterval': 1}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({}); handler.send( - {}, {'extraUrlParams': {'e1': 'e1'}}, expansionOptions, {}); + {}, + {'extraUrlParams': {'e1': 'e1'}}, + expansionOptions, + {} + ); handler.send( - {}, {'extraUrlParams': {'e2': 'e2'}}, expansionOptions, {}); + {}, + {'extraUrlParams': {'e2': 'e2'}}, + expansionOptions, + {} + ); clock.tick(1000); yield macroTask(); expect(spy).to.be.calledWith('r1&${extraUrlParams}&r2', [ @@ -353,14 +378,18 @@ describes.realWin('Requests', {amp: 1}, env => { createRequestHandler(r, spy); } catch (e) { expect(e).to.match( - /batchPlugin cannot be set on non-batched request/); + /batchPlugin cannot be set on non-batched request/ + ); } }); it('should throw error with unsupported batchPlugin', () => { const spy = sandbox.spy(); - const r = - {'baseUrl': 'r', 'batchInterval': 1, 'batchPlugin': 'invalid'}; + const r = { + 'baseUrl': 'r', + 'batchInterval': 1, + 'batchPlugin': 'invalid', + }; try { createRequestHandler(r, spy); } catch (e) { @@ -370,21 +399,22 @@ describes.realWin('Requests', {amp: 1}, env => { }); }); - it('should replace dynamic bindings RESOURCE_TIMING', function* () { + it('should replace dynamic bindings RESOURCE_TIMING', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1&${resourceTiming}'}; const handler = createRequestHandler(r, spy); const expansionOptions = new ExpansionOptions({ 'resourceTiming': 'RESOURCE_TIMING', }); - sandbox.stub(ResourceTiming, 'getResourceTiming') - .returns(Promise.resolve('resource-timing')); + sandbox + .stub(ResourceTiming, 'getResourceTiming') + .returns(Promise.resolve('resource-timing')); handler.send({}, {}, expansionOptions); yield macroTask(); expect(spy).to.be.calledWith('r1&resource-timing'); }); - it('should replace dynamic bindings CONSENT_STATE', function* () { + it('should replace dynamic bindings CONSENT_STATE', function*() { const spy = sandbox.spy(); const r = {'baseUrl': 'r1&$CONSENT_STATEtest&${consentState}test2'}; const handler = createRequestHandler(r, spy); @@ -416,31 +446,34 @@ describes.realWin('Requests', {amp: 1}, env => { it('should expand', () => { return expandPostMessage( - ampdoc, - 'test foo 123 ... ${teste1}', - undefined, - {}, - expansionOptions, - element).then(msg => { + ampdoc, + 'test foo 123 ... ${teste1}', + undefined, + {}, + expansionOptions, + element + ).then(msg => { expect(msg).to.equal('test foo 123 ... TESTE1'); }); }); it('should replace not append ${extraUrlParams}', () => { const replacePromise = expandPostMessage( - ampdoc, - 'test ${extraUrlParams} foo', - params, /* configParams */ - {}, /* trigger */ - expansionOptions, - element); + ampdoc, + 'test ${extraUrlParams} foo', + params /* configParams */, + {} /* trigger */, + expansionOptions, + element + ); const appendPromise = expandPostMessage( - ampdoc, - 'test foo', - params, /* configParams */ - {}, /* trigger */ - expansionOptions, - element); + ampdoc, + 'test foo', + params /* configParams */, + {} /* trigger */, + expansionOptions, + element + ); return replacePromise.then(replace => { expect(replace).to.equal('test e1=TESTE1&e2=teste2 foo'); expect(appendPromise).to.eventually.equal('test foo'); diff --git a/extensions/amp-analytics/0.1/test/test-resource-timing.js b/extensions/amp-analytics/0.1/test/test-resource-timing.js index 14f45ff2c771e..9152136e7efeb 100644 --- a/extensions/amp-analytics/0.1/test/test-resource-timing.js +++ b/extensions/amp-analytics/0.1/test/test-resource-timing.js @@ -36,7 +36,7 @@ export function newResourceTimingSpec() { }, 'encoding': { 'entry': - '${key}-${initiatorType}-${startTime}-${duration}-${transferSize}', + '${key}-${initiatorType}-${startTime}-${duration}-${transferSize}', 'delim': '~', }, }; @@ -55,7 +55,13 @@ export function newResourceTimingSpec() { * @return {!JsonObject} */ export function newPerformanceResourceTiming( - url, initiatorType, startTime, duration, bodySize, cached) { + url, + initiatorType, + startTime, + duration, + bodySize, + cached +) { const dnsTime = cached ? 0 : duration * 0.1; const tcpTime = cached ? 0 : duration * 0.2; const serverTime = cached ? duration : duration * 0.4; @@ -90,12 +96,16 @@ describes.realWin('resourceTiming', {amp: true}, env => { * @return {!Promise} */ const runSerializeTest = function( - fakeEntries, resourceTimingSpec, expectedResult) { + fakeEntries, + resourceTimingSpec, + expectedResult + ) { sandbox.stub(win.performance, 'getEntriesByType').returns(fakeEntries); - return getResourceTiming(win, resourceTimingSpec, Date.now()) - .then(result => { - expect(result).to.equal(expectedResult); - }); + return getResourceTiming(win, resourceTimingSpec, Date.now()).then( + result => { + expect(result).to.equal(expectedResult); + } + ); }; beforeEach(() => { @@ -106,44 +116,60 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if the performance API is not supported', () => { const fakeWin = {}; - return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()) - .then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()).then( + result => { + expect(result).to.equal(''); + } + ); }); it('should return empty when resource timing is not supported', () => { // Performance API (fakeWin.performance) doesn't support resource timing. const fakeWin = {performance: {}}; - return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()) - .then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(fakeWin, newResourceTimingSpec(), Date.now()).then( + result => { + expect(result).to.equal(''); + } + ); }); it('should return empty when start time has passed 1s', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); sandbox.stub(win.performance, 'getEntriesByType').returns([entry]); - return getResourceTiming(win, spec, Date.now() - 60 * 1000) - .then(result => { - expect(result).to.equal(''); - }); + return getResourceTiming(win, spec, Date.now() - 60 * 1000).then(result => { + expect(result).to.equal(''); + }); }); it('should return empty if resourceTimingSpec is empty', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); return runSerializeTest([entry], {}, ''); }); it('should return empty if encoding spec is empty', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); delete spec['encoding']; return runSerializeTest([entry], spec, ''); @@ -151,8 +177,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if encoding spec is missing delim', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); delete spec['encoding']['delim']; return runSerializeTest([entry], spec, ''); @@ -160,8 +191,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should return empty if encoding spec is missing entry', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); delete spec['encoding']['entry']; return runSerializeTest([entry], spec, ''); @@ -169,8 +205,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should serialize matching entries', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); const expect = 'foo_bar-script-100-500-7200'; return runSerializeTest([entry], spec, expect); @@ -178,19 +219,37 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should serialize multiple matching entries', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); return runSerializeTest( - [entry1, entry2], newResourceTimingSpec(), - 'foo_bar-script-100-500-7200~foo_bar-script-700-100-0'); + [entry1, entry2], + newResourceTimingSpec(), + 'foo_bar-script-100-500-7200~foo_bar-script-700-100-0' + ); }); it('should match against the first spec', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); // Note that both spec'd resources match. @@ -207,8 +266,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should accept empty per-resource specs', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); // Note that both spec'd resources match. @@ -225,9 +289,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should should only report resources if the host matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js', 'script', 100, 500, 10 * 1000, false); + 'http://foo.example.com/lib.js', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://baz.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://baz.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec.resources = {'foo': {'host': 'foo.example.com'}}; @@ -236,9 +312,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should should only report resources if the path matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js', 'script', 100, 500, 10 * 1000, false); + 'http://foo.example.com/lib.js', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://foo.example.com/extra.js', 'script', 700, 100, 80 * 1000, true); + 'http://foo.example.com/extra.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec.resources = { @@ -249,11 +337,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should should only report resources if the query matches', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=200', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=200', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=test', 'script', 700, 100, 80 * 1000, - true); + 'http://foo.example.com/lib.js?v=test', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec.resources = { @@ -268,8 +366,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${key} and ${initiatorType}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${key}.${initiatorType}'; return runSerializeTest([entry], spec, 'foo_style.link'); @@ -277,8 +380,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${startTime} and ${duration}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${startTime}.${duration}'; return runSerializeTest([entry], spec, '100.500'); @@ -286,8 +394,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${domainLookupTime} and ${tcpConnectTime}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${domainLookupTime}.${tcpConnectTime}'; return runSerializeTest([entry], spec, '50.100'); @@ -295,28 +408,42 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should replace ${serverResponseTime} and ${networkTransferTime}', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${serverResponseTime}.${networkTransferTime}'; return runSerializeTest([entry], spec, '200.150'); }); - it('should replace ${transferSize}, ${encodedBodySize}, ${decodedBodySize}', - () => { - const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, - 10 * 1000, false); - const spec = newResourceTimingSpec(); - spec['encoding']['entry'] = - '${transferSize}.${encodedBodySize}.${decodedBodySize}'; - return runSerializeTest([entry], spec, '7200.7000.10000'); - }); + it('should replace ${transferSize}, ${encodedBodySize}, ${decodedBodySize}', () => { + const entry = newPerformanceResourceTiming( + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); + const spec = newResourceTimingSpec(); + spec['encoding']['entry'] = + '${transferSize}.${encodedBodySize}.${decodedBodySize}'; + return runSerializeTest([entry], spec, '7200.7000.10000'); + }); it('should use the base specified in encoding', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${decodedBodySize}'; spec['encoding']['base'] = 36; @@ -326,8 +453,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should reject invalid bases (over 36)', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${decodedBodySize}'; spec['encoding']['base'] = 40; @@ -338,10 +470,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should not replace other analytics variables', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${startTime}.${random}.${undefinedVariable}'; // The counter is incremented for each entry. @@ -350,37 +493,76 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should URL-encode the results', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 700, 100, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 700, + 100, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${key}?${startTime},${duration}'; spec['encoding']['delim'] = ':'; return runSerializeTest( - [entry1, entry2], spec, 'foo_bar?100,500:foo_bar?700,100'); + [entry1, entry2], + spec, + 'foo_bar?100,500:foo_bar?700,100' + ); }); it('should only include resources downloaded after `responseAfter`', () => { const entry1 = newPerformanceResourceTiming( - 'http://foo.example.com/lib.js?v=123', 'script', 100, 200, 10 * 1000, - false); + 'http://foo.example.com/lib.js?v=123', + 'script', + 100, + 200, + 10 * 1000, + false + ); const entry2 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 200, 200, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 200, + 200, + 80 * 1000, + true + ); const entry3 = newPerformanceResourceTiming( - 'http://bar.example.com/lib.js', 'script', 300, 200, 80 * 1000, true); + 'http://bar.example.com/lib.js', + 'script', + 300, + 200, + 80 * 1000, + true + ); const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${key}.${startTime}'; spec['encoding']['delim'] = '-'; spec['responseAfter'] = 350; return runSerializeTest( - [entry1, entry2, entry3], spec, 'foo_bar.200-foo_bar.300'); + [entry1, entry2, entry3], + spec, + 'foo_bar.200-foo_bar.300' + ); }); it('should reject invalid (non-numeric) responseAfter fields', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['responseAfter'] = '100'; return runSerializeTest([entry], spec, '').then(() => { @@ -390,9 +572,21 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should update responseAfter', () => { const initialEntry = newPerformanceResourceTiming( - 'https://example.com/lib.css', 'link', 100, 400, 5 * 1000, false); + 'https://example.com/lib.css', + 'link', + 100, + 400, + 5 * 1000, + false + ); const laterEntry = newPerformanceResourceTiming( - 'https://bar.example.com/lib.js', 'script', 200, 500, 10 * 1000, false); + 'https://bar.example.com/lib.js', + 'script', + 200, + 500, + 10 * 1000, + false + ); // Stub performance.now so that it returns a timestamp after the resource // timing entry. @@ -407,16 +601,18 @@ describes.realWin('resourceTiming', {amp: true}, env => { const spec = newResourceTimingSpec(); spec['encoding']['entry'] = '${initiatorType}.${startTime}.${duration}'; - return getResourceTiming(win, spec, Date.now()).then(result => { - expect(result).to.equal('link.100.400'); - expect(spec['responseAfter']).to.equal(600); - - // Check resource timings a second time. - return getResourceTiming(win, spec, Date.now()); - }).then(result => { - expect(result).to.equal('script.200.500'); - expect(spec['responseAfter']).to.equal(800); - }); + return getResourceTiming(win, spec, Date.now()) + .then(result => { + expect(result).to.equal('link.100.400'); + expect(spec['responseAfter']).to.equal(600); + + // Check resource timings a second time. + return getResourceTiming(win, spec, Date.now()); + }) + .then(result => { + expect(result).to.equal('script.200.500'); + expect(spec['responseAfter']).to.equal(800); + }); }); it('should not update responseAfter if greater', () => { @@ -432,8 +628,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should stop reporting after reaching the buffer limit', () => { const entry = newPerformanceResourceTiming( - 'http://does_not_match.com/lib.js', 'script', 100, 500, 10 * 1000, - false); + 'http://does_not_match.com/lib.js', + 'script', + 100, + 500, + 10 * 1000, + false + ); const entries = new Array(150).fill(entry); // Stub performance.now so that it returns a timestamp after the resource // timing entry. @@ -446,8 +647,13 @@ describes.realWin('resourceTiming', {amp: true}, env => { it('should not report if resourceTimingSpec is done', () => { const entry = newPerformanceResourceTiming( - 'http://foo.example.com/style.css?v=200', 'link', 100, 500, 10 * 1000, - false); + 'http://foo.example.com/style.css?v=200', + 'link', + 100, + 500, + 10 * 1000, + false + ); const spec = newResourceTimingSpec(); spec['done'] = true; return runSerializeTest([entry], spec, ''); diff --git a/extensions/amp-analytics/0.1/test/test-scroll-manager.js b/extensions/amp-analytics/0.1/test/test-scroll-manager.js index 3fb60d2204637..7b0f67c4bd16b 100644 --- a/extensions/amp-analytics/0.1/test/test-scroll-manager.js +++ b/extensions/amp-analytics/0.1/test/test-scroll-manager.js @@ -17,7 +17,6 @@ import {AmpdocAnalyticsRoot} from '../analytics-root'; import {ScrollManager} from '../scroll-manager'; - describes.realWin('ScrollManager', {amp: 1}, env => { let win; let ampdoc; @@ -50,8 +49,9 @@ describes.realWin('ScrollManager', {amp: 1}, env => { scrollManager = new ScrollManager(ampdoc); root.scrollManager_ = scrollManager; fakeViewport = { - 'getSize': sandbox.stub().returns( - {top: 0, left: 0, height: 200, width: 200}), + 'getSize': sandbox + .stub() + .returns({top: 0, left: 0, height: 200, width: 200}), 'getScrollTop': sandbox.stub().returns(0), 'getScrollLeft': sandbox.stub().returns(0), 'getScrollHeight': sandbox.stub().returns(500), @@ -61,7 +61,6 @@ describes.realWin('ScrollManager', {amp: 1}, env => { }, }; scrollManager.viewport_ = fakeViewport; - }); it('should initalize, add listeners and dispose', () => { @@ -74,21 +73,24 @@ describes.realWin('ScrollManager', {amp: 1}, env => { expect(scrollManager.scrollObservable_.getHandlerCount()).to.equal(0); }); - it('should add a viewport onChanged listener with scroll handlers, ' - + 'and dispose when there are none', () => { - expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; + it( + 'should add a viewport onChanged listener with scroll handlers, ' + + 'and dispose when there are none', + () => { + expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; - const fn1 = sandbox.stub(); - scrollManager.addScrollHandler(fn1); + const fn1 = sandbox.stub(); + scrollManager.addScrollHandler(fn1); - expect(scrollManager.viewportOnChangedUnlistener_).to.be.ok; - const unlistenStub = scrollManager.viewportOnChangedUnlistener_; + expect(scrollManager.viewportOnChangedUnlistener_).to.be.ok; + const unlistenStub = scrollManager.viewportOnChangedUnlistener_; - scrollManager.removeScrollHandler(fn1); + scrollManager.removeScrollHandler(fn1); - expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; - expect(unlistenStub).to.have.callCount(1); - }); + expect(scrollManager.viewportOnChangedUnlistener_).to.not.be.ok; + expect(unlistenStub).to.have.callCount(1); + } + ); it('fires on scroll', () => { const fn1 = sandbox.stub(); @@ -124,16 +126,13 @@ describes.realWin('ScrollManager', {amp: 1}, env => { expect(fn1).to.have.callCount(2); expect( - fn1.getCall(1) - .calledWithMatch(sinon.match(matcher(expectedScrollEvent))) + fn1.getCall(1).calledWithMatch(sinon.match(matcher(expectedScrollEvent))) ).to.be.true; expect(fn2).to.have.callCount(2); expect( - fn2.getCall(1) - .calledWithMatch(sinon.match(matcher(expectedScrollEvent))) + fn2.getCall(1).calledWithMatch(sinon.match(matcher(expectedScrollEvent))) ).to.be.true; - }); it('can remove specifc handlers', () => { @@ -152,7 +151,6 @@ describes.realWin('ScrollManager', {amp: 1}, env => { fakeViewport.getScrollLeft.returns(500); scrollManager.onScroll_({top: 500, left: 500, height: 250, width: 250}); - expect(fn1).to.have.callCount(2); expect(fn2).to.have.callCount(1); }); diff --git a/extensions/amp-analytics/0.1/test/test-transport-serializers.js b/extensions/amp-analytics/0.1/test/test-transport-serializers.js index 10c1234e3a21c..257ce4fd8f493 100644 --- a/extensions/amp-analytics/0.1/test/test-transport-serializers.js +++ b/extensions/amp-analytics/0.1/test/test-transport-serializers.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {TransportSerializers} from '../transport-serializer'; import {dict} from '../../../../src/utils/object'; import {isArray} from '../../../../src/types'; @@ -36,59 +35,69 @@ import {isArray} from '../../../../src/types'; * } */ const defaultTestData = { - 'in': [{ - 'baseUrl': 'https://base.com', - 'batchSegments': [{ - 'trigger': 'click', - 'timestamp': 0, - 'extraUrlParams': { - a: 1, - b: 'xyz', - }, - }], - }, { - 'baseUrl': 'https://base.com?${extraUrlParams}&z=1', - 'batchSegments': [{ - 'trigger': 'click', - 'timestamp': 0, - 'extraUrlParams': { - a: 1, - b: 'xyz', - }, - }], - }], - - 'out': [{ - generateRequest: { - url: 'https://base.com?a=1&b=xyz', - }, - generateBatchRequest: { - url: 'https://base.com?a=1&b=xyz', - }, - generateRequestWithPayload: { - url: 'https://base.com', - payload: '{"a":1,"b":"xyz"}', - }, - generateBatchRequestWithPayload: { - url: 'https://base.com', - payload: '[{"a":1,"b":"xyz"}]', - }, - }, { - generateRequest: { - url: 'https://base.com?a=1&b=xyz&z=1', + 'in': [ + { + 'baseUrl': 'https://base.com', + 'batchSegments': [ + { + 'trigger': 'click', + 'timestamp': 0, + 'extraUrlParams': { + a: 1, + b: 'xyz', + }, + }, + ], }, - generateBatchRequest: { - url: 'https://base.com?a=1&b=xyz&z=1', + { + 'baseUrl': 'https://base.com?${extraUrlParams}&z=1', + 'batchSegments': [ + { + 'trigger': 'click', + 'timestamp': 0, + 'extraUrlParams': { + a: 1, + b: 'xyz', + }, + }, + ], }, - generateRequestWithPayload: { - url: 'https://base.com?&z=1', - payload: '{"a":1,"b":"xyz"}', + ], + + 'out': [ + { + generateRequest: { + url: 'https://base.com?a=1&b=xyz', + }, + generateBatchRequest: { + url: 'https://base.com?a=1&b=xyz', + }, + generateRequestWithPayload: { + url: 'https://base.com', + payload: '{"a":1,"b":"xyz"}', + }, + generateBatchRequestWithPayload: { + url: 'https://base.com', + payload: '[{"a":1,"b":"xyz"}]', + }, }, - generateBatchRequestWithPayload: { - url: 'https://base.com?&z=1', - payload: '[{"a":1,"b":"xyz"}]', + { + generateRequest: { + url: 'https://base.com?a=1&b=xyz&z=1', + }, + generateBatchRequest: { + url: 'https://base.com?a=1&b=xyz&z=1', + }, + generateRequestWithPayload: { + url: 'https://base.com?&z=1', + payload: '{"a":1,"b":"xyz"}', + }, + generateBatchRequestWithPayload: { + url: 'https://base.com?&z=1', + payload: '[{"a":1,"b":"xyz"}]', + }, }, - }], + ], }; /** @@ -127,8 +136,9 @@ describe('Transport serializers', () => { 'extraUrlParams': null, }); try { - const output = - serializer.generateBatchRequest('base', [batchSegment]); + const output = serializer.generateBatchRequest('base', [ + batchSegment, + ]); expect(output.url).to.be.ok; } catch (e) { throw e; @@ -142,8 +152,9 @@ describe('Transport serializers', () => { 'extraUrlParams': '12?3', }); try { - const output = - serializer.generateBatchRequest('base', [batchSegment]); + const output = serializer.generateBatchRequest('base', [ + batchSegment, + ]); expect(output.url).to.not.contain('12?3'); } catch (e) { throw e; @@ -157,8 +168,9 @@ describe('Transport serializers', () => { 'extraUrlParams': '123', }); try { - const output = - serializer.generateBatchRequest('base', [batchSegment]); + const output = serializer.generateBatchRequest('base', [ + batchSegment, + ]); expect(typeof output.url).to.equal('string'); } catch (e) { throw e; @@ -166,7 +178,6 @@ describe('Transport serializers', () => { }); }); - describe('custom test', () => { let testData; let input; @@ -197,14 +208,18 @@ describe('Transport serializers', () => { expect(batchSegments).to.be.ok; expect(isArray(batchSegments)).to.be.true; const request = output[i]; - expect(serializer.generateRequest(baseUrl, batchSegments[0])) - .to.jsonEqual(request.generateRequest); - expect(serializer.generateRequest(baseUrl, batchSegments[0], true)) - .to.jsonEqual(request.generateRequestWithPayload); - expect(serializer.generateBatchRequest(baseUrl, batchSegments)) - .to.jsonEqual(request.generateRequest); - expect(serializer.generateBatchRequest(baseUrl, batchSegments, true)) - .to.jsonEqual(request.generateBatchRequestWithPayload); + expect( + serializer.generateRequest(baseUrl, batchSegments[0]) + ).to.jsonEqual(request.generateRequest); + expect( + serializer.generateRequest(baseUrl, batchSegments[0], true) + ).to.jsonEqual(request.generateRequestWithPayload); + expect( + serializer.generateBatchRequest(baseUrl, batchSegments) + ).to.jsonEqual(request.generateRequest); + expect( + serializer.generateBatchRequest(baseUrl, batchSegments, true) + ).to.jsonEqual(request.generateBatchRequestWithPayload); } }); }); diff --git a/extensions/amp-analytics/0.1/test/test-transport.js b/extensions/amp-analytics/0.1/test/test-transport.js index f11696f9c178b..6608a7ca6bfeb 100644 --- a/extensions/amp-analytics/0.1/test/test-transport.js +++ b/extensions/amp-analytics/0.1/test/test-transport.js @@ -24,422 +24,503 @@ import {getMode} from '../../../../src/mode'; import {installTimerService} from '../../../../src/service/timer-impl'; import {loadPromise} from '../../../../src/event-helper'; -describes.realWin('amp-analytics.transport', { - amp: false, - allowExternalResources: true, -}, env => { - - let sandbox; - let win; - let doc; - let openXhrStub; - let sendXhrStub; - let sendBeaconStub; - let imagePixelVerifier; - - beforeEach(() => { - sandbox = env.sandbox; - win = env.win; - doc = win.document; - openXhrStub = sandbox.stub(); - sendXhrStub = sandbox.stub(); - sendBeaconStub = sandbox.stub(); - }); - - it('prefers beacon over xhrpost and image', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, +describes.realWin( + 'amp-analytics.transport', + { + amp: false, + allowExternalResources: true, + }, + env => { + let sandbox; + let win; + let doc; + let openXhrStub; + let sendXhrStub; + let sendBeaconStub; + let imagePixelVerifier; + + beforeEach(() => { + sandbox = env.sandbox; + win = env.win; + doc = win.document; + openXhrStub = sandbox.stub(); + sendXhrStub = sandbox.stub(); + sendBeaconStub = sandbox.stub(); }); - expectBeacon('https://example.com/test', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('prefers xhrpost over image', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: false, xhrpost: true, image: true, + + it('prefers beacon over xhrpost and image', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, + }); + expectBeacon('https://example.com/test', ''); + expectNoXhr(); + expectNoImagePixel(); }); - expectNoBeacon(); - expectXhr('https://example.com/test', ''); - expectNoImagePixel(); - }); - - it('reluctantly uses image if nothing else is enabled', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - image: true, + + it('prefers xhrpost over image', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: false, + xhrpost: true, + image: true, + }); + expectNoBeacon(); + expectXhr('https://example.com/test', ''); + expectNoImagePixel(); }); - expectNoBeacon(); - expectImagePixel('https://example.com/test'); - expectNoXhr(); - }); - - it('falls back to image setting suppressWarnings to true', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: false, xhrpost: false, image: {suppressWarnings: true}, + + it('reluctantly uses image if nothing else is enabled', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + image: true, + }); + expectNoBeacon(); + expectImagePixel('https://example.com/test'); + expectNoXhr(); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test'); - }); - - it('falls back to image setting referrerPolicy', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, referrerPolicy: 'no-referrer', + + it('falls back to image setting suppressWarnings to true', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: false, + xhrpost: false, + image: {suppressWarnings: true}, + }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test'); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test', 'no-referrer'); - }); - - it('falls back to xhrpost when enabled and beacon is not available', () => { - setupStubs(false, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, + + it('falls back to image setting referrerPolicy', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, + referrerPolicy: 'no-referrer', + }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test', 'no-referrer'); }); - expectNoBeacon(); - expectXhr('https://example.com/test', ''); - expectNoImagePixel(); - }); - - it('falls back to image when beacon not found and xhr disabled', () => { - setupStubs(false, true); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: false, image: true, + + it('falls back to xhrpost when enabled and beacon is not available', () => { + setupStubs(false, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, + }); + expectNoBeacon(); + expectXhr('https://example.com/test', ''); + expectNoImagePixel(); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test'); - }); - - it('falls back to image when beacon and xhr are not available', () => { - setupStubs(false, false); - sendRequest(win, 'https://example.com/test', { - beacon: true, xhrpost: true, image: true, + + it('falls back to image when beacon not found and xhr disabled', () => { + setupStubs(false, true); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: false, + image: true, + }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test'); }); - expectNoBeacon(); - expectNoXhr(); - expectImagePixel('https://example.com/test'); - }); - - it('does not send a request when no transport methods are enabled', () => { - setupStubs(true, true); - sendRequest(win, 'https://example.com/test', {}); - expectNoBeacon(); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('does not send a request when URL is empty', () => { - setupStubs(true, true); - sendRequest(win, '', {beacon: true, xhrpost: true, image: true}); - expectNoBeacon(); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], false); - expectBeacon('https://e.com/test?a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test?a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request useBody', () => { - setupStubs(true, true); - new Transport(win, {beacon: true, useBody: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], false); - expectBeacon('https://e.com/test', '{"a":1,"b":"hello"}'); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send single segment request useBody in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true, useBody: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test', '[{"a":1,"b":"hello"}]'); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send multi-segment request w/o batch (only 1st sent)', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }, { - extraUrlParams: { - a: 2, - b: 'world', - }, - }], false); - expectBeacon('https://e.com/test?a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send multi-segment request in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }, { - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test?a=1&b=hello&a=1&b=hello', ''); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('send multi-segment request useBody in batch', () => { - setupStubs(true, true); - new Transport(win, {beacon: true, useBody: true}).sendRequest('https://e.com/test', [{ - extraUrlParams: { - a: 1, - b: 'hello', - }, - }, { - extraUrlParams: { - a: 1, - b: 'hello', - }, - }], true); - expectBeacon('https://e.com/test', '[{"a":1,"b":"hello"},{"a":1,"b":"hello"}]'); - expectNoXhr(); - expectNoImagePixel(); - }); - - it('asserts that urls are https', () => { - allowConsoleError(() => { expect(() => { - sendRequest(win, 'http://example.com/test', {image: true}); - }).to.throw(/https/); }); - }); - - it('should NOT allow __amp_source_origin', () => { - allowConsoleError(() => { expect(() => { - sendRequest(win, 'https://twitter.com?__amp_source_origin=1', {image: true}); - }).to.throw(/Source origin is not allowed in/); }); - }); - - describe('sendRequestUsingIframe', () => { - const url = 'http://iframe.localhost:9876/test/fixtures/served/iframe.html'; - - function sendRequestUsingIframe(win, url) { - new Transport(win).sendRequestUsingIframe(url, {}); - } - it('should create and delete an iframe', () => { - const clock = lolex.install({target: win}); - installTimerService(win); - sendRequestUsingIframe(win, url); - const iframe = doc.querySelector('iframe[src="' + url + '"]'); - expect(iframe).to.be.ok; - expect(iframe.getAttribute('sandbox')).to.equal( - 'allow-scripts allow-same-origin'); - return loadPromise(iframe).then(() => { - clock.tick(4999); - expect(doc.querySelector('iframe[src="' + url + '"]')).to.be.ok; - clock.tick(1); - expect(doc.querySelector('iframe[src="' + url + '"]')).to.not.be.ok; + it('falls back to image when beacon and xhr are not available', () => { + setupStubs(false, false); + sendRequest(win, 'https://example.com/test', { + beacon: true, + xhrpost: true, + image: true, }); + expectNoBeacon(); + expectNoXhr(); + expectImagePixel('https://example.com/test'); }); - it('iframe asserts that urls are https', () => { - allowConsoleError(() => { expect(() => { - sendRequestUsingIframe(win, 'http://example.com/test'); - }).to.throw(/https/); }); + it('does not send a request when no transport methods are enabled', () => { + setupStubs(true, true); + sendRequest(win, 'https://example.com/test', {}); + expectNoBeacon(); + expectNoXhr(); + expectNoImagePixel(); }); - it('forbids same origin', () => { - const fakeWin = { - location: { - href: 'https://example.com/abc', - }, - }; - allowConsoleError(() => { - expect(() => { - sendRequestUsingIframe(fakeWin, 'https://example.com/123'); - }).to.throw(/Origin of iframe request/); - }); + it('does not send a request when URL is empty', () => { + setupStubs(true, true); + sendRequest(win, '', {beacon: true, xhrpost: true, image: true}); + expectNoBeacon(); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send single segment request', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + false + ); + expectBeacon('https://e.com/test?a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); }); - }); - describe('iframe transport', () => { + it('send single segment request in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon('https://e.com/test?a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); + }); - it('does not initialize transport iframe if not used', () => { - const transport = new Transport(win, { - image: true, - xhrpost: true, - beacon: false, - }); + it('send single segment request useBody', () => { + setupStubs(true, true); + new Transport(win, {beacon: true, useBody: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + false + ); + expectBeacon('https://e.com/test', '{"a":1,"b":"hello"}'); + expectNoXhr(); + expectNoImagePixel(); + }); - const ampAnalyticsEl = null; + it('send single segment request useBody in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true, useBody: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon('https://e.com/test', '[{"a":1,"b":"hello"}]'); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send multi-segment request w/o batch (only 1st sent)', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + { + extraUrlParams: { + a: 2, + b: 'world', + }, + }, + ], + false + ); + expectBeacon('https://e.com/test?a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send multi-segment request in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon('https://e.com/test?a=1&b=hello&a=1&b=hello', ''); + expectNoXhr(); + expectNoImagePixel(); + }); + + it('send multi-segment request useBody in batch', () => { + setupStubs(true, true); + new Transport(win, {beacon: true, useBody: true}).sendRequest( + 'https://e.com/test', + [ + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + { + extraUrlParams: { + a: 1, + b: 'hello', + }, + }, + ], + true + ); + expectBeacon( + 'https://e.com/test', + '[{"a":1,"b":"hello"},{"a":1,"b":"hello"}]' + ); + expectNoXhr(); + expectNoImagePixel(); + }); - const preconnectSpy = sandbox.spy(); - transport.maybeInitIframeTransport(win, ampAnalyticsEl, { - preload: preconnectSpy, + it('asserts that urls are https', () => { + allowConsoleError(() => { + expect(() => { + sendRequest(win, 'http://example.com/test', {image: true}); + }).to.throw(/https/); }); - expect(transport.iframeTransport_).to.be.null; - expect(preconnectSpy).to.not.be.called; }); - it('initialize iframe transport when used', () => { - const transport = new Transport(win, { - iframe: '//test', + it('should NOT allow __amp_source_origin', () => { + allowConsoleError(() => { + expect(() => { + sendRequest(win, 'https://twitter.com?__amp_source_origin=1', { + image: true, + }); + }).to.throw(/Source origin is not allowed in/); }); + }); - const ad = doc.createElement('amp-ad'); - ad.getResourceId = () => '123'; - doc.body.appendChild(ad); - const frame = doc.createElement('iframe'); - ad.appendChild(frame); - frame.contentWindow.document.write( - ''); - frame.contentWindow.__AMP_TOP = win; - const ampAnalyticsEl = - frame.contentWindow.document.querySelector('amp-analytics'); - - const preconnectSpy = sandbox.spy(); - transport.maybeInitIframeTransport(win, ampAnalyticsEl, { - preload: preconnectSpy, + describe('sendRequestUsingIframe', () => { + const url = + 'http://iframe.localhost:9876/test/fixtures/served/iframe.html'; + + function sendRequestUsingIframe(win, url) { + new Transport(win).sendRequestUsingIframe(url, {}); + } + + it('should create and delete an iframe', () => { + const clock = lolex.install({target: win}); + installTimerService(win); + sendRequestUsingIframe(win, url); + const iframe = doc.querySelector('iframe[src="' + url + '"]'); + expect(iframe).to.be.ok; + expect(iframe.getAttribute('sandbox')).to.equal( + 'allow-scripts allow-same-origin' + ); + return loadPromise(iframe).then(() => { + clock.tick(4999); + expect(doc.querySelector('iframe[src="' + url + '"]')).to.be.ok; + clock.tick(1); + expect(doc.querySelector('iframe[src="' + url + '"]')).to.not.be.ok; + }); }); - expect(transport.iframeTransport_).to.be.ok; - expect(preconnectSpy).to.be.called; - transport.deleteIframeTransport(); - expect(transport.iframeTransport_).to.be.null; - }); + it('iframe asserts that urls are https', () => { + allowConsoleError(() => { + expect(() => { + sendRequestUsingIframe(win, 'http://example.com/test'); + }).to.throw(/https/); + }); + }); - it('initialize iframe transport when used with inabox', () => { - win.AMP_MODE = win.AMP_MODE || {}; - win.AMP_MODE.runtime = 'inabox'; - expect(getMode(win).runtime).to.equal('inabox'); + it('forbids same origin', () => { + const fakeWin = { + location: { + href: 'https://example.com/abc', + }, + }; + allowConsoleError(() => { + expect(() => { + sendRequestUsingIframe(fakeWin, 'https://example.com/123'); + }).to.throw(/Origin of iframe request/); + }); + }); + }); - const transport = new Transport(win, { - iframe: '//test', + describe('iframe transport', () => { + it('does not initialize transport iframe if not used', () => { + const transport = new Transport(win, { + image: true, + xhrpost: true, + beacon: false, + }); + + const ampAnalyticsEl = null; + + const preconnectSpy = sandbox.spy(); + transport.maybeInitIframeTransport(win, ampAnalyticsEl, { + preload: preconnectSpy, + }); + expect(transport.iframeTransport_).to.be.null; + expect(preconnectSpy).to.not.be.called; }); - const frame = doc.createElement('iframe'); - doc.body.appendChild(frame); - frame.contentWindow.document.write( - ''); - frame.contentWindow.__AMP_TOP = win; - const ampAnalyticsEl = - frame.contentWindow.document.querySelector('amp-analytics'); - - const preconnectSpy = sandbox.spy(); - transport.maybeInitIframeTransport(win, ampAnalyticsEl, { - preload: preconnectSpy, + it('initialize iframe transport when used', () => { + const transport = new Transport(win, { + iframe: '//test', + }); + + const ad = doc.createElement('amp-ad'); + ad.getResourceId = () => '123'; + doc.body.appendChild(ad); + const frame = doc.createElement('iframe'); + ad.appendChild(frame); + frame.contentWindow.document.write( + '' + ); + frame.contentWindow.__AMP_TOP = win; + const ampAnalyticsEl = frame.contentWindow.document.querySelector( + 'amp-analytics' + ); + + const preconnectSpy = sandbox.spy(); + transport.maybeInitIframeTransport(win, ampAnalyticsEl, { + preload: preconnectSpy, + }); + expect(transport.iframeTransport_).to.be.ok; + expect(preconnectSpy).to.be.called; + + transport.deleteIframeTransport(); + expect(transport.iframeTransport_).to.be.null; }); - expect(transport.iframeTransport_).to.be.ok; - expect(preconnectSpy).to.be.called; - transport.deleteIframeTransport(); - expect(transport.iframeTransport_).to.be.null; - }); + it('initialize iframe transport when used with inabox', () => { + win.AMP_MODE = win.AMP_MODE || {}; + win.AMP_MODE.runtime = 'inabox'; + expect(getMode(win).runtime).to.equal('inabox'); + + const transport = new Transport(win, { + iframe: '//test', + }); + + const frame = doc.createElement('iframe'); + doc.body.appendChild(frame); + frame.contentWindow.document.write( + '' + ); + frame.contentWindow.__AMP_TOP = win; + const ampAnalyticsEl = frame.contentWindow.document.querySelector( + 'amp-analytics' + ); + + const preconnectSpy = sandbox.spy(); + transport.maybeInitIframeTransport(win, ampAnalyticsEl, { + preload: preconnectSpy, + }); + expect(transport.iframeTransport_).to.be.ok; + expect(preconnectSpy).to.be.called; + + transport.deleteIframeTransport(); + expect(transport.iframeTransport_).to.be.null; + }); - it('send via iframe transport', () => { - setupStubs(true, true); - const transport = new Transport(win, { - beacon: true, xhrpost: true, image: true, - iframe: '//test', + it('send via iframe transport', () => { + setupStubs(true, true); + const transport = new Transport(win, { + beacon: true, + xhrpost: true, + image: true, + iframe: '//test', + }); + const iframeTransportSendRequestSpy = sandbox.spy(); + transport.iframeTransport_ = { + sendRequest: iframeTransportSendRequestSpy, + }; + transport.sendRequest('test test', [{}], false); + expectNoBeacon(); + expectNoXhr(); + expectNoImagePixel(); + expect(iframeTransportSendRequestSpy).to.be.calledWith('test test'); }); - const iframeTransportSendRequestSpy = sandbox.spy(); - transport.iframeTransport_ = { - sendRequest: iframeTransportSendRequestSpy, - }; - transport.sendRequest('test test', [{}], false); - expectNoBeacon(); - expectNoXhr(); - expectNoImagePixel(); - expect(iframeTransportSendRequestSpy).to.be.calledWith('test test'); }); - }); - - function setupStubs(beacon, xhr) { - const wi = mockWindowInterface(sandbox); - wi.getSendBeacon.returns(beacon ? sendBeaconStub : undefined); - - const FakeXMLHttpRequest = () => { - return { - withCredentials: false, - open: openXhrStub, - send: sendXhrStub, - setRequestHeader: () => {}, + + function setupStubs(beacon, xhr) { + const wi = mockWindowInterface(sandbox); + wi.getSendBeacon.returns(beacon ? sendBeaconStub : undefined); + + const FakeXMLHttpRequest = () => { + return { + withCredentials: false, + open: openXhrStub, + send: sendXhrStub, + setRequestHeader: () => {}, + }; }; - }; - wi.getXMLHttpRequest.returns(xhr ? FakeXMLHttpRequest : undefined); - sendBeaconStub.returns(beacon); + wi.getXMLHttpRequest.returns(xhr ? FakeXMLHttpRequest : undefined); + sendBeaconStub.returns(beacon); - imagePixelVerifier = new ImagePixelVerifier(wi); - } + imagePixelVerifier = new ImagePixelVerifier(wi); + } - function sendRequest(win, request, options) { - new Transport(win, options).sendRequest(request, [{}], false); - } + function sendRequest(win, request, options) { + new Transport(win, options).sendRequest(request, [{}], false); + } - function expectBeacon(url, payload) { - expect(sendBeaconStub).to.be.calledWith(url, payload); - } + function expectBeacon(url, payload) { + expect(sendBeaconStub).to.be.calledWith(url, payload); + } - function expectNoBeacon() { - expect(sendBeaconStub).to.not.be.called; - } + function expectNoBeacon() { + expect(sendBeaconStub).to.not.be.called; + } - function expectXhr(url, payload) { - expect(openXhrStub).to.be.calledWith('POST', url, true); - expect(sendXhrStub).to.be.calledWith(payload); - } + function expectXhr(url, payload) { + expect(openXhrStub).to.be.calledWith('POST', url, true); + expect(sendXhrStub).to.be.calledWith(payload); + } - function expectNoXhr() { - expect(openXhrStub).to.not.be.called; - expect(sendXhrStub).to.not.be.called; - } + function expectNoXhr() { + expect(openXhrStub).to.not.be.called; + expect(sendXhrStub).to.not.be.called; + } - function expectImagePixel(url, referrerPolicy) { - imagePixelVerifier.verifyRequest(url, referrerPolicy); - } + function expectImagePixel(url, referrerPolicy) { + imagePixelVerifier.verifyRequest(url, referrerPolicy); + } - function expectNoImagePixel() { - expect(imagePixelVerifier.hasRequestSent()).to.be.false; + function expectNoImagePixel() { + expect(imagePixelVerifier.hasRequestSent()).to.be.false; + } } -}); +); diff --git a/extensions/amp-analytics/0.1/test/test-variables.js b/extensions/amp-analytics/0.1/test/test-variables.js index da132ad462916..37cd8711f53f3 100644 --- a/extensions/amp-analytics/0.1/test/test-variables.js +++ b/extensions/amp-analytics/0.1/test/test-variables.js @@ -1,4 +1,3 @@ - /** * Copyright 2015 The AMP HTML Authors. All Rights Reserved. * @@ -41,20 +40,19 @@ describe('amp-analytics.VariableService', function() { describe('encodeVars', () => { it('correctly encodes scalars and arrays', () => { expect(encodeVars('abc %&')).to.equal('abc%20%25%26'); - expect(encodeVars('SOME_MACRO(abc,123)')) - .to.equal('SOME_MACRO(abc,123)'); + expect(encodeVars('SOME_MACRO(abc,123)')).to.equal('SOME_MACRO(abc,123)'); const array = ['abc %&', 'a b']; expect(encodeVars(array)).to.equal('abc%20%25%26,a%20b'); // Test non-inplace semantics by testing again. expect(encodeVars(array)).to.equal('abc%20%25%26,a%20b'); - expect(encodeVars(['12.3', 'SOME_MACRO(abc,123)', 'ab/c'])) - .to.equal('12.3,SOME_MACRO(abc,123),ab%2Fc'); + expect(encodeVars(['12.3', 'SOME_MACRO(abc,123)', 'ab/c'])).to.equal( + '12.3,SOME_MACRO(abc,123),ab%2Fc' + ); }); }); describe('expand', () => { - const vars = { 'a': '${b}', 'b': '${c}', @@ -63,7 +61,9 @@ describe('amp-analytics.VariableService', function() { function check(template, expected, vars) { const actual = variables.expandTemplateSync( - template, new ExpansionOptions(vars)); + template, + new ExpansionOptions(vars) + ); expect(actual).to.equal(expected); } @@ -73,7 +73,9 @@ describe('amp-analytics.VariableService', function() { it('expands nested vars (no encode)', () => { const actual = variables.expandTemplateSync( - '${a}', new ExpansionOptions(vars, undefined, true)); + '${a}', + new ExpansionOptions(vars, undefined, true) + ); expect(actual).to.equal('https://www.google.com/a?b=1&c=2'); }); @@ -111,36 +113,47 @@ describe('amp-analytics.VariableService', function() { }); check('${foo}&${bar(3,4)}', 'FOO(1,2)&BAR(3,4)', { - 'foo': 'FOO(1,2)', 'bar': 'BAR', + 'foo': 'FOO(1,2)', + 'bar': 'BAR', }); // TODO: fix this, should be 'AAA(1,2)%26BBB(3,4)%26CCC(5,6)%26DDD(7,8)' check('${all}', 'AAA(1%2C2)%26BBB(3%2C4)%26CCC(5%2C6)%26DDD(7,8)', { - 'a': 'AAA', 'b': 'BBB', 'c': 'CCC(5,6)', 'd': 'DDD(7,8)', + 'a': 'AAA', + 'b': 'BBB', + 'c': 'CCC(5,6)', + 'd': 'DDD(7,8)', 'all': '${a(1,2)}&${b(3,4)}&${c}&${d}', }); }); it('respect freeze variables', () => { - const vars = new ExpansionOptions({'fooParam': 'QUERY_PARAM', - 'freeze': 'error'}); + const vars = new ExpansionOptions({ + 'fooParam': 'QUERY_PARAM', + 'freeze': 'error', + }); vars.freezeVar('freeze'); const actual = variables.expandTemplateSync( - '${fooParam(foo,bar)}${nonfreeze}${freeze}', vars); + '${fooParam(foo,bar)}${nonfreeze}${freeze}', + vars + ); expect(actual).to.equal('QUERY_PARAM(foo,bar)${freeze}'); }); it('expands array vars', () => { - check('${array}', - 'xy%26x,MACRO(abc,def),MACRO(abc%2Cdef)%26123,%24%7Bfoo%7D', { - 'foo': 'bar', - 'array': [ - 'xy&x', // special chars should be encoded - 'MACRO(abc,def)', // do not encode macro - 'MACRO(abc,def)&123', // this is not a macro - '${foo}', // vars in array is not expanded - ], - }); + check( + '${array}', + 'xy%26x,MACRO(abc,def),MACRO(abc%2Cdef)%26123,%24%7Bfoo%7D', + { + 'foo': 'bar', + 'array': [ + 'xy&x', // special chars should be encoded + 'MACRO(abc,def)', // do not encode macro + 'MACRO(abc,def)&123', // this is not a macro + '${foo}', // vars in array is not expanded + ], + } + ); }); it('handles empty var name', () => { @@ -149,18 +162,27 @@ describe('amp-analytics.VariableService', function() { describe('should handle recursive vars', () => { const recursiveVars = { - '1': '1${2}', '2': '2${3}', '3': '3${4}', '4': '4${1}', + '1': '1${2}', + '2': '2${3}', + '3': '3${4}', + '4': '4${1}', }; it('default to 2 recursions', () => { - expectAsyncConsoleError(/Maximum depth reached while expanding variables/); + expectAsyncConsoleError( + /Maximum depth reached while expanding variables/ + ); check('${1}', '123%24%7B4%7D', recursiveVars); }); it('customize recursions to 5', () => { - expectAsyncConsoleError(/Maximum depth reached while expanding variables/); + expectAsyncConsoleError( + /Maximum depth reached while expanding variables/ + ); const actual = variables.expandTemplateSync( - '${1}', new ExpansionOptions(recursiveVars, 5)); + '${1}', + new ExpansionOptions(recursiveVars, 5) + ); expect(actual).to.equal('123412%24%7B3%7D'); }); }); @@ -191,11 +213,14 @@ describe('amp-analytics.VariableService', function() { it('default works without first arg', () => check('$DEFAULT(,two)', 'two')); - it('default works without first arg length', - () => check('$DEFAULT($TRIM(), two)', 'two')); + it('default works without first arg length', () => + check('$DEFAULT($TRIM(), two)', 'two')); - it('hash works', () => check('$HASH(test)', - 'doQSMg97CqWBL85CjcRwazyuUOAqZMqhangiSb_o78S37xzLEmJV0ZYEff7fF6Cp')); + it('hash works', () => + check( + '$HASH(test)', + 'doQSMg97CqWBL85CjcRwazyuUOAqZMqhangiSb_o78S37xzLEmJV0ZYEff7fF6Cp' + )); it('substr works', () => check('$SUBSTR(Hello world!, 1, 4)', 'ello')); @@ -219,14 +244,18 @@ describe('amp-analytics.VariableService', function() { it('if works', () => check('$IF(hey, truthy, falsey)', 'truthy')); it('chaining works', () => { - return check('$SUBSTR(Hello world!, 6)', 'world!').then(() => - check('$TOUPPERCASE($SUBSTR(Hello world!, 6))', 'WORLD!')).then(() => - check('$BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6)))', 'V09STEQh')) - .then(() => - check('$HASH($BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6))))', - 'OPTTt2IGW8-R31MrIF_cRUwLTZ9jLDOXEuhNz_Q' + - 'S7Uc5ZmODduHWdplzrZ7Jsnqx') - ); + return check('$SUBSTR(Hello world!, 6)', 'world!') + .then(() => check('$TOUPPERCASE($SUBSTR(Hello world!, 6))', 'WORLD!')) + .then(() => + check('$BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6)))', 'V09STEQh') + ) + .then(() => + check( + '$HASH($BASE64($TOUPPERCASE($SUBSTR(Hello world!, 6))))', + 'OPTTt2IGW8-R31MrIF_cRUwLTZ9jLDOXEuhNz_Q' + + 'S7Uc5ZmODduHWdplzrZ7Jsnqx' + ) + ); }); it('replaces common use case', () => { @@ -246,8 +275,10 @@ describe('amp-analytics.VariableService', function() { }); it('replaces respecting space as arg', () => { - return check('$REPLACE(this-is-a-test, `-`, ` `)', - 'this%20is%20a%20test'); + return check( + '$REPLACE(this-is-a-test, `-`, ` `)', + 'this%20is%20a%20test' + ); }); it('replaces respecting backticks', () => { @@ -263,13 +294,14 @@ describe('amp-analytics.VariableService', function() { const linkerReaderStub = sandbox.stub(linkerReader, 'get'); linkerReaderStub.withArgs('gl', 'cid').returns('a1b2c3'); linkerReaderStub.withArgs('gl', 'gclid').returns(123); - return check('LINKER_PARAM(gl, cid)&LINKER_PARAM(gl, gclid)', - 'a1b2c3&123'); + return check( + 'LINKER_PARAM(gl, cid)&LINKER_PARAM(gl, gclid)', + 'a1b2c3&123' + ); }); }); describe('getNameArgs:', () => { - function check(input, name, argList) { it('can parse ' + name, () => { expect(getNameArgsForTesting(input)).to.deep.equal({name, argList}); @@ -283,7 +315,6 @@ describe('amp-analytics.VariableService', function() { check('client id\nand something', 'client id\nand something', ''); check('client id\nclientId()', 'client id\nclientId()', ''); - check('clientId()', 'clientId', '()'); check('clientId(abc)', 'clientId', '(abc)'); check('clientId(abc,def)', 'clientId', '(abc,def)'); diff --git a/extensions/amp-analytics/0.1/test/test-vendors.js b/extensions/amp-analytics/0.1/test/test-vendors.js index 3c3c1d7e47361..98995823966f8 100644 --- a/extensions/amp-analytics/0.1/test/test-vendors.js +++ b/extensions/amp-analytics/0.1/test/test-vendors.js @@ -31,163 +31,187 @@ const VENDOR_REQUESTS = require('./vendor-requests.json'); const AnalyticsConfig = Object.assign({}, ANALYTICS_CONFIG); describe('iframe transport', () => { - it('Should not contain iframe transport if not whitelisted', () => { for (const vendor in AnalyticsConfig) { const vendorEntry = AnalyticsConfig[vendor]; - if (hasOwn(vendorEntry, 'transport') && - hasOwn(vendorEntry.transport, 'iframe')) { - expect(vendorEntry['transport']['iframe']) - .to.equal(IFRAME_TRANSPORTS[vendor]); + if ( + hasOwn(vendorEntry, 'transport') && + hasOwn(vendorEntry.transport, 'iframe') + ) { + expect(vendorEntry['transport']['iframe']).to.equal( + IFRAME_TRANSPORTS[vendor] + ); } } }); }); -describes.realWin('amp-analytics', { - amp: { - extensions: ['amp-analytics'], +describes.realWin( + 'amp-analytics', + { + amp: { + extensions: ['amp-analytics'], + }, }, -}, function(env) { - let win, doc; - let requestVerifier; - - beforeEach(() => { - win = env.win; - doc = win.document; - const wi = mockWindowInterface(env.sandbox); - wi.getLocation.returns(win.location); - requestVerifier = new ImagePixelVerifier(wi); - }); + function(env) { + let win, doc; + let requestVerifier; + + beforeEach(() => { + win = env.win; + doc = win.document; + const wi = mockWindowInterface(env.sandbox); + wi.getLocation.returns(win.location); + requestVerifier = new ImagePixelVerifier(wi); + }); + + function getAnalyticsTag(config, attrs) { + config['transport'] = { + xhrpost: false, + beacon: false, + }; + config = JSON.stringify(config); + const el = doc.createElement('amp-analytics'); + const script = doc.createElement('script'); + script.textContent = config; + script.setAttribute('type', 'application/json'); + el.appendChild(script); + for (const k in attrs) { + el.setAttribute(k, attrs[k]); + } + doc.body.appendChild(el); - function getAnalyticsTag(config, attrs) { - config['transport'] = { - xhrpost: false, - beacon: false, - }; - config = JSON.stringify(config); - const el = doc.createElement('amp-analytics'); - const script = doc.createElement('script'); - script.textContent = config; - script.setAttribute('type', 'application/json'); - el.appendChild(script); - for (const k in attrs) { - el.setAttribute(k, attrs[k]); + el.connectedCallback(); + const analytics = new AmpAnalytics(el); + analytics.createdCallback(); + analytics.buildCallback(); + return analytics; } - doc.body.appendChild(el); - - el.connectedCallback(); - const analytics = new AmpAnalytics(el); - analytics.createdCallback(); - analytics.buildCallback(); - return analytics; - } - - /** - * Clears the properties in the config that should only be used in vendor - * configs. This is needed because we pass in all the vendor requests as - * inline config and iframePings/optout are not allowed to be used without - * AMP team's approval. - * - * @param {!JsonObject} config The inline config to update. - * @return {!JsonObject} - */ - function clearVendorOnlyConfig(config) { - for (const t in config.triggers) { - if (config.triggers[t].iframePing) { - config.triggers[t].iframePing = undefined; + /** + * Clears the properties in the config that should only be used in vendor + * configs. This is needed because we pass in all the vendor requests as + * inline config and iframePings/optout are not allowed to be used without + * AMP team's approval. + * + * @param {!JsonObject} config The inline config to update. + * @return {!JsonObject} + */ + function clearVendorOnlyConfig(config) { + for (const t in config.triggers) { + if (config.triggers[t].iframePing) { + config.triggers[t].iframePing = undefined; + } } + if (config.optout) { + config.optout = undefined; + } + return config; } - if (config.optout) { - config.optout = undefined; - } - return config; - } - describe('vendor request tests', () => { - for (const vendor in AnalyticsConfig) { - if (vendor === 'default') { - continue; - } - const config = AnalyticsConfig[vendor]; - if (!config.requests) { - delete AnalyticsConfig[vendor]; - continue; - } - describe('analytics vendor: ' + vendor, function() { - beforeEach(() => { - // Remove all the triggers to prevent unwanted requests, for instance - // one from a "visible" trigger. Those unwanted requests are a source - // of test flakiness. Especially they will alternate value of var - // $requestCount. - config.triggers = {}; - }); + describe('vendor request tests', () => { + for (const vendor in AnalyticsConfig) { + if (vendor === 'default') { + continue; + } + const config = AnalyticsConfig[vendor]; + if (!config.requests) { + delete AnalyticsConfig[vendor]; + continue; + } + describe('analytics vendor: ' + vendor, function() { + beforeEach(() => { + // Remove all the triggers to prevent unwanted requests, for instance + // one from a "visible" trigger. Those unwanted requests are a source + // of test flakiness. Especially they will alternate value of var + // $requestCount. + config.triggers = {}; + }); - for (const name in config.requests) { - it('should produce request: ' + name + - '. If this test fails update vendor-requests.json', function* () { - const urlReplacements = - Services.urlReplacementsForDoc(doc.documentElement); - const analytics = getAnalyticsTag(clearVendorOnlyConfig(config)); - sandbox.stub(urlReplacements.getVariableSource(), 'get').callsFake( - function(name) { - expect(this.replacements_).to.have.property(name); - const defaultValue = `_${name.toLowerCase()}_`; - return { - sync: () => defaultValue, - }; - }); - - sandbox.stub(ExpansionOptions.prototype, 'getVar').callsFake( - function(name) { - let val = this.vars[name]; - if (val == null || val == '') { - val = '!' + name; + for (const name in config.requests) { + it( + 'should produce request: ' + + name + + '. If this test fails update vendor-requests.json', + function*() { + const urlReplacements = Services.urlReplacementsForDoc( + doc.documentElement + ); + const analytics = getAnalyticsTag( + clearVendorOnlyConfig(config) + ); + sandbox + .stub(urlReplacements.getVariableSource(), 'get') + .callsFake(function(name) { + expect(this.replacements_).to.have.property(name); + const defaultValue = `_${name.toLowerCase()}_`; + return { + sync: () => defaultValue, + }; + }); + + sandbox + .stub(ExpansionOptions.prototype, 'getVar') + .callsFake(function(name) { + let val = this.vars[name]; + if (val == null || val == '') { + val = '!' + name; + } + return val; + }); + analytics.createdCallback(); + analytics.buildCallback(); + yield analytics.layoutCallback(); + + // Wait for event queue to clear. + yield macroTask(); + + analytics.handleEvent_( + { + request: name, + }, + { + vars: Object.create(null), } - return val; - }); - analytics.createdCallback(); - analytics.buildCallback(); - yield analytics.layoutCallback(); - - // Wait for event queue to clear. - yield macroTask(); - - analytics.handleEvent_({ - request: name, - }, { - vars: Object.create(null), - }); - yield macroTask(); - expect(requestVerifier.hasRequestSent()).to.be.true; - let url = requestVerifier.getLastRequestUrl(); - - const vendorData = VENDOR_REQUESTS[vendor]; - if (!vendorData) { - throw new Error('Add vendor ' + vendor + - ' to vendor-requests.json'); - } - const val = vendorData[name]; - if (val == '') { - url = ''; - } - if (val == null) { - throw new Error('Define ' + vendor + '.' + name + - ' in vendor-requests.json. Expected value: ' + url); - } - - // Write this out for easy copy pasting. - writeOutput(vendor, name, url); - - expect(url).to.equal(val); - }); - } - }); - } - }); -}); + ); + yield macroTask(); + expect(requestVerifier.hasRequestSent()).to.be.true; + let url = requestVerifier.getLastRequestUrl(); + + const vendorData = VENDOR_REQUESTS[vendor]; + if (!vendorData) { + throw new Error( + 'Add vendor ' + vendor + ' to vendor-requests.json' + ); + } + const val = vendorData[name]; + if (val == '') { + url = ''; + } + if (val == null) { + throw new Error( + 'Define ' + + vendor + + '.' + + name + + ' in vendor-requests.json. Expected value: ' + + url + ); + } + + // Write this out for easy copy pasting. + writeOutput(vendor, name, url); + + expect(url).to.equal(val); + } + ); + } + }); + } + }); + } +); const actualResults = {}; diff --git a/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js b/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js index 55105f4d4a8df..d8526c700a838 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-manager-for-mapp.js @@ -46,7 +46,6 @@ class MockVisibilityInterface { } } - describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { let win; let ampdoc; @@ -65,8 +64,7 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { viewer = win.services.viewer.obj; sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); visibilityInterface = new MockVisibilityInterface(); - root = - new VisibilityManagerForMApp(ampdoc, visibilityInterface); + root = new VisibilityManagerForMApp(ampdoc, visibilityInterface); win.IntersectionObserver = null; @@ -78,8 +76,7 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { it('should initialize correctly', () => { viewer.setVisibilityState_(VisibilityState.HIDDEN); visibilityInterface = new MockVisibilityInterface(0.5); - root = - new VisibilityManagerForMApp(ampdoc, visibilityInterface); + root = new VisibilityManagerForMApp(ampdoc, visibilityInterface); expect(root.parent).to.be.null; expect(root.ampdoc).to.equal(ampdoc); expect(root.getStartTime()).to.equal(viewer.getFirstVisibleTime()); @@ -111,8 +108,7 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { it('should switch root model to no-visibility on dispose', () => { visibilityInterface = new MockVisibilityInterface(1); - root = - new VisibilityManagerForMApp(ampdoc, visibilityInterface); + root = new VisibilityManagerForMApp(ampdoc, visibilityInterface); expect(root.getRootVisibility()).to.equal(1); root.dispose(); expect(root.getRootVisibility()).to.equal(0); @@ -121,15 +117,21 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { it('should not support listen on element', () => { const target = win.document.createElement('div'); //root.listenElement(target, {}, null, null, () => {}); - allowConsoleError(() => { expect(() => { - root.listenElement(target, {}, null, null, () => {}); - }).to.throw(/element level visibility not supported/); }); - allowConsoleError(() => { expect(() => { - root.observe(); - }).to.throw(/element level visibility not supported/); }); - allowConsoleError(() => { expect(() => { - root.getElementVisibility(); - }).to.throw(/element level visibility not supported/); }); + allowConsoleError(() => { + expect(() => { + root.listenElement(target, {}, null, null, () => {}); + }).to.throw(/element level visibility not supported/); + }); + allowConsoleError(() => { + expect(() => { + root.observe(); + }).to.throw(/element level visibility not supported/); + }); + allowConsoleError(() => { + expect(() => { + root.getElementVisibility(); + }).to.throw(/element level visibility not supported/); + }); }); it('should protect from invalid intersection values', () => { @@ -179,7 +181,9 @@ describes.fakeWin('VisibilityManagerForMapp', {amp: true}, env => { // Back to visible range visibilityInterface.fireVisibilityChangeForTesting( - 0.3, layoutRectLtwh(1, 2, 100, 201)); + 0.3, + layoutRectLtwh(1, 2, 100, 201) + ); clock.tick(3); return eventPromise.then(state => { expect(disposed).to.be.calledOnce; diff --git a/extensions/amp-analytics/0.1/test/test-visibility-manager.js b/extensions/amp-analytics/0.1/test/test-visibility-manager.js index 33476c107dc38..5422288dddd2e 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-manager.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-manager.js @@ -27,7 +27,6 @@ import {VisibilityState} from '../../../../src/visibility-state'; import {layoutRectLtwh, rectIntersection} from '../../../../src/layout-rect'; class IntersectionObserverStub { - constructor(callback, options) { this.callback = callback; this.options = options; @@ -63,7 +62,6 @@ class IntersectionObserverStub { } } - describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { let win; let ampdoc; @@ -82,8 +80,7 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { viewer = win.services.viewer.obj; sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); viewport = win.services.viewport.obj; - startVisibilityHandlerCount = - viewer.visibilityObservable_.getHandlerCount(); + startVisibilityHandlerCount = viewer.visibilityObservable_.getHandlerCount(); root = new VisibilityManagerForDoc(ampdoc); @@ -166,8 +163,9 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { }); it('should switch visibility based on viewer for main doc', () => { - expect(viewer.visibilityObservable_.getHandlerCount()) - .equal(startVisibilityHandlerCount + 1); + expect(viewer.visibilityObservable_.getHandlerCount()).equal( + startVisibilityHandlerCount + 1 + ); expect(root.getRootVisibility()).to.equal(1); // Go prerender. @@ -198,11 +196,13 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { // Unrelated event. const otherTarget = win.document.createElement('div'); - inOb.callback([{ - target: otherTarget, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target: otherTarget, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(root.getRootVisibility()).to.equal(0); // Move to the viewport. @@ -310,7 +310,12 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { root.listenRoot(spec, null, null, modelsCalled); root.listenElement(otherTarget, spec, null, null, modelsCalled); root.listenElement( - otherTarget, {totalTimeMin: 20}, null, null, modelsCalled); + otherTarget, + {totalTimeMin: 20}, + null, + null, + modelsCalled + ); expect(root.models_).to.have.length(3); root.models_.forEach(model => { model.unsubscribe(modelsDisposed); @@ -332,8 +337,9 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(otherUnsubscribes.callCount).to.equal(2); // Viewer and viewport have been unsubscribed. - expect(viewer.visibilityObservable_.getHandlerCount()) - .equal(startVisibilityHandlerCount); + expect(viewer.visibilityObservable_.getHandlerCount()).equal( + startVisibilityHandlerCount + ); // Intersection observer disconnected. expect(inOb.disconnected).to.be.true; @@ -350,16 +356,20 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { // Check observer is correctly set. const inOb = root.getIntersectionObserver_(); expect(inOb).to.be.instanceOf(IntersectionObserverPolyfill); - expect(viewport.scrollObservable_.getHandlerCount()) - .to.equal(startScrollCount + 1); - expect(viewport.changeObservable_.getHandlerCount()) - .to.equal(startChangeCount + 1); + expect(viewport.scrollObservable_.getHandlerCount()).to.equal( + startScrollCount + 1 + ); + expect(viewport.changeObservable_.getHandlerCount()).to.equal( + startChangeCount + 1 + ); root.dispose(); - expect(viewport.scrollObservable_.getHandlerCount()) - .to.equal(startScrollCount); - expect(viewport.changeObservable_.getHandlerCount()) - .to.equal(startChangeCount); + expect(viewport.scrollObservable_.getHandlerCount()).to.equal( + startScrollCount + ); + expect(viewport.changeObservable_.getHandlerCount()).to.equal( + startChangeCount + ); }); it('should support polyfill on non-amp root element', () => { @@ -389,8 +399,12 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { } return null; }); - expect(rootElement.getLayoutBox()) - .to.contain({left: 0, top: 50, width: 100, height: 100}); + expect(rootElement.getLayoutBox()).to.contain({ + left: 0, + top: 50, + width: 100, + height: 100, + }); viewport.scrollObservable_.fire({type: 'scroll'}); expect(model.getVisibility_()).to.equal(0.5); @@ -404,8 +418,9 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { const disposed = sandbox.spy(); const spec = {totalTimeMin: 10}; root.listenRoot(spec, null, null, eventResolver); - sandbox.stub(root, 'getRootLayoutBox').callsFake( - () => layoutRectLtwh(11, 21, 101, 201)); + sandbox + .stub(root, 'getRootLayoutBox') + .callsFake(() => layoutRectLtwh(11, 21, 101, 201)); expect(root.models_).to.have.length(1); const model = root.models_[0]; @@ -481,9 +496,14 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { const testPromise = new Promise(resolve => { testPromiseResolver = resolve; }); - root.listenRoot({}, null, () => { - return testPromise; - }, eventResolver); + root.listenRoot( + {}, + null, + () => { + return testPromise; + }, + eventResolver + ); expect(root.models_).to.have.length(1); const model = root.models_[0]; model.unsubscribe(disposed); @@ -533,11 +553,13 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(model.getVisibility_()).to.equal(0); // In viewport. - inOb.callback([{ - target, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0.3); // Go invisible on root. @@ -575,26 +597,51 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(model.getVisibility_()).to.equal(0); // Valid value. - inOb.callback([{target, intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0.3); // Invalid negative value. - inOb.callback([{target, intersectionRatio: -0.01, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: -0.01, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0); - inOb.callback([{target, intersectionRatio: -1000, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: -1000, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(0); // Invalid overflow value. - inOb.callback([{target, intersectionRatio: 1.01, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: 1.01, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(1); - inOb.callback([{target, intersectionRatio: 1000, - intersectionRect: layoutRectLtwh(0, 0, 1, 1)}]); + inOb.callback([ + { + target, + intersectionRatio: 1000, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model.getVisibility_()).to.equal(1); }); @@ -618,11 +665,13 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { expect(inOb.elements).to.contain(target); // In viewport. - inOb.callback([{ - target, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(model1.getVisibility_()).to.equal(0.3); expect(trackedElement.intersectionRatio).to.equal(0.3); @@ -646,29 +695,32 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { sandbox.stub(model1, 'reset_'); // Fire the first event. clock.tick(11); - return eventPromise.then(state => { - // First event fired. The first model should be cleaned up, but not - // the other. - expect(state.totalVisibleTime).to.equal(10); - expect(disposed1).to.be.calledOnce; - expect(root.models_).to.have.length(1); - expect(root.trackedElements_[target.__AMP_VIS_ID]) - .to.equal(trackedElement); - expect(trackedElement.listeners).to.have.length(1); - expect(inOb.elements).to.contain(target); - - // Fire the second event. - clock.tick(10); - return eventPromise2; - }).then(state => { - // Second event fired. Everything should be released now. - expect(state.totalVisibleTime).to.equal(20); - expect(disposed2).to.be.calledOnce; - expect(root.models_).to.have.length(0); - expect(root.trackedElements_[target.__AMP_VIS_ID]).to.not.exist; - expect(trackedElement.listeners).to.have.length(0); - expect(inOb.elements).to.not.contain(target); - }); + return eventPromise + .then(state => { + // First event fired. The first model should be cleaned up, but not + // the other. + expect(state.totalVisibleTime).to.equal(10); + expect(disposed1).to.be.calledOnce; + expect(root.models_).to.have.length(1); + expect(root.trackedElements_[target.__AMP_VIS_ID]).to.equal( + trackedElement + ); + expect(trackedElement.listeners).to.have.length(1); + expect(inOb.elements).to.contain(target); + + // Fire the second event. + clock.tick(10); + return eventPromise2; + }) + .then(state => { + // Second event fired. Everything should be released now. + expect(state.totalVisibleTime).to.equal(20); + expect(disposed2).to.be.calledOnce; + expect(root.models_).to.have.length(0); + expect(root.trackedElements_[target.__AMP_VIS_ID]).to.not.exist; + expect(trackedElement.listeners).to.have.length(0); + expect(inOb.elements).to.not.contain(target); + }); }); it('should listen on a resource', () => { @@ -680,17 +732,20 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { }, }; const resources = win.services.resources.obj; - sandbox.stub(resources, 'getResourceForElementOptional').callsFake( - () => resource); + sandbox + .stub(resources, 'getResourceForElementOptional') + .callsFake(() => resource); const spec = {totalTimeMin: 10}; root.listenElement(target, spec, null, null, eventResolver); const inOb = root.getIntersectionObserver_(); - inOb.callback([{ - target, - intersectionRatio: 0.3, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); + inOb.callback([ + { + target, + intersectionRatio: 0.3, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); expect(root.models_).to.have.length(1); const model = root.models_[0]; @@ -708,227 +763,239 @@ describes.fakeWin('VisibilityManagerForDoc', {amp: true}, env => { }); }); +describes.realWin( + 'EmbedAnalyticsRoot', + { + amp: {ampdoc: 'fie'}, + }, + env => { + let parentWin; + let win; + let ampdoc; + let embed; + let clock; + let viewer; + let viewport; + let parentRoot; + let root; + let inob; + + beforeEach(() => { + parentWin = env.parentWin; + win = env.win; + ampdoc = env.ampdoc; + embed = env.embed; + embed.host = ampdoc.win.document.createElement('amp-host'); + clock = sandbox.useFakeTimers(); + clock.tick(1); -describes.realWin('EmbedAnalyticsRoot', { - amp: {ampdoc: 'fie'}, -}, env => { - let parentWin; - let win; - let ampdoc; - let embed; - let clock; - let viewer; - let viewport; - let parentRoot; - let root; - let inob; - - beforeEach(() => { - parentWin = env.parentWin; - win = env.win; - ampdoc = env.ampdoc; - embed = env.embed; - embed.host = ampdoc.win.document.createElement('amp-host'); - clock = sandbox.useFakeTimers(); - clock.tick(1); - - viewport = parentWin.services.viewport.obj; - viewer = parentWin.services.viewer.obj; - sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); - - parentRoot = new VisibilityManagerForDoc(ampdoc); - parentWin.IntersectionObserver = IntersectionObserverStub; - parentWin.IntersectionObserverEntry = function() {}; - parentWin.IntersectionObserverEntry.prototype.intersectionRatio = 1; - - root = new VisibilityManagerForEmbed(parentRoot, embed); - inob = parentRoot.getIntersectionObserver_(); - }); - - it('should dispose with parent', () => { - const unsubscribeSpy = sandbox.spy(); - root.unsubscribe(unsubscribeSpy); - - expect(parentRoot.children_).to.have.length(1); - expect(parentRoot.children_[0]).to.equal(root); - - parentRoot.dispose(); - expect(parentRoot.children_).to.have.length(0); - expect(unsubscribeSpy).to.be.calledOnce; - }); - - it('should remove from parent when disposed', () => { - const unsubscribeSpy = sandbox.spy(); - root.unsubscribe(unsubscribeSpy); + viewport = parentWin.services.viewport.obj; + viewer = parentWin.services.viewer.obj; + sandbox.stub(viewer, 'getFirstVisibleTime').callsFake(() => 1); - expect(parentRoot.children_).to.have.length(1); - expect(parentRoot.children_[0]).to.equal(root); + parentRoot = new VisibilityManagerForDoc(ampdoc); + parentWin.IntersectionObserver = IntersectionObserverStub; + parentWin.IntersectionObserverEntry = function() {}; + parentWin.IntersectionObserverEntry.prototype.intersectionRatio = 1; - root.dispose(); - expect(parentRoot.children_).to.have.length(0); - expect(unsubscribeSpy).to.be.calledOnce; - }); + root = new VisibilityManagerForEmbed(parentRoot, embed); + inob = parentRoot.getIntersectionObserver_(); + }); - it('should initialize correctly backgrounded', () => { - viewer.setVisibilityState_(VisibilityState.HIDDEN); - root = new VisibilityManagerForEmbed(parentRoot, embed); + it('should dispose with parent', () => { + const unsubscribeSpy = sandbox.spy(); + root.unsubscribe(unsubscribeSpy); - expect(root.parent).to.equal(parentRoot); - expect(root.ampdoc).to.equal(ampdoc); - expect(root.getStartTime()).to.equal(embed.getStartTime()); - expect(root.isBackgrounded()).to.be.true; - expect(root.isBackgroundedAtStart()).to.be.true; + expect(parentRoot.children_).to.have.length(1); + expect(parentRoot.children_[0]).to.equal(root); - // Root model starts invisible. - expect(root.getRootVisibility()).to.equal(0); - }); + parentRoot.dispose(); + expect(parentRoot.children_).to.have.length(0); + expect(unsubscribeSpy).to.be.calledOnce; + }); - it('should initialize correctly foregrounded', () => { - expect(root.parent).to.equal(parentRoot); - expect(root.ampdoc).to.equal(ampdoc); - expect(root.getStartTime()).to.equal(embed.getStartTime()); - expect(root.isBackgrounded()).to.be.false; - expect(root.isBackgroundedAtStart()).to.be.false; + it('should remove from parent when disposed', () => { + const unsubscribeSpy = sandbox.spy(); + root.unsubscribe(unsubscribeSpy); - // Root model starts invisible. - root.setRootVisibility(1); - expect(root.getRootVisibility()).to.equal(1); - }); + expect(parentRoot.children_).to.have.length(1); + expect(parentRoot.children_[0]).to.equal(root); - it('should resolve root layout box', () => { - sandbox.stub(viewport, 'getLayoutRect').callsFake(element => { - if (element == embed.iframe) { - return layoutRectLtwh(11, 21, 101, 201); - } - return null; - }); - expect(root.getRootLayoutBox()).to.contain({ - left: 11, - top: 21, - width: 101, - height: 201, + root.dispose(); + expect(parentRoot.children_).to.have.length(0); + expect(unsubscribeSpy).to.be.calledOnce; }); - }); - - it('should ask parent to observe host element', () => { - const id = embed.host.__AMP_VIS_ID; - expect(parentRoot.trackedElements_[id]).to.be.ok; - root.dispose(); - expect(parentRoot.trackedElements_[id]).to.be.undefined; - }); + it('should initialize correctly backgrounded', () => { + viewer.setVisibilityState_(VisibilityState.HIDDEN); + root = new VisibilityManagerForEmbed(parentRoot, embed); - it('should delegate observation to parent', () => { - const inOb = { - observe: sandbox.spy(), - unobserve: sandbox.spy(), - }; - parentRoot.intersectionObserver_ = inOb; + expect(root.parent).to.equal(parentRoot); + expect(root.ampdoc).to.equal(ampdoc); + expect(root.getStartTime()).to.equal(embed.getStartTime()); + expect(root.isBackgrounded()).to.be.true; + expect(root.isBackgroundedAtStart()).to.be.true; - const listener = sandbox.spy(); - const target = win.document.createElement('div'); + // Root model starts invisible. + expect(root.getRootVisibility()).to.equal(0); + }); - // Observe. - const unlisten = root.observe(target, listener); - expect(inOb.observe).to.be.calledOnce; - expect(inOb.observe).to.be.calledWith(target); - const id = target.__AMP_VIS_ID; - expect(parentRoot.trackedElements_[id]).to.be.ok; + it('should initialize correctly foregrounded', () => { + expect(root.parent).to.equal(parentRoot); + expect(root.ampdoc).to.equal(ampdoc); + expect(root.getStartTime()).to.equal(embed.getStartTime()); + expect(root.isBackgrounded()).to.be.false; + expect(root.isBackgroundedAtStart()).to.be.false; - // Unobserve. - unlisten(); - expect(inOb.unobserve).to.be.calledOnce; - expect(inOb.unobserve).to.be.calledWith(target); - expect(parentRoot.trackedElements_[id]).to.be.undefined; - }); - - it('should depend on parent for visibility', () => { - const callbackSpy = sandbox.spy(); - const otherTarget = win.document.createElement('div'); - root.listenRoot({}, null, null, callbackSpy); - expect(root.models_).to.have.length(1); - const rootModel = root.models_[0]; + // Root model starts invisible. + root.setRootVisibility(1); + expect(root.getRootVisibility()).to.equal(1); + }); - root.listenElement(otherTarget, {}, null, null, callbackSpy); - expect(root.models_).to.have.length(2); - const elementModel = root.models_[1]; + it('should resolve root layout box', () => { + sandbox.stub(viewport, 'getLayoutRect').callsFake(element => { + if (element == embed.iframe) { + return layoutRectLtwh(11, 21, 101, 201); + } + return null; + }); + expect(root.getRootLayoutBox()).to.contain({ + left: 11, + top: 21, + width: 101, + height: 201, + }); + }); - // Set up. - expect(inob.elements).to.contain(embed.host); - expect(inob.elements).to.contain(otherTarget); + it('should ask parent to observe host element', () => { + const id = embed.host.__AMP_VIS_ID; + expect(parentRoot.trackedElements_[id]).to.be.ok; - // Start state. - expect(parentRoot.getRootVisibility()).to.equal(1); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); - - // Make root visible. - inob.callback([{ - target: embed.host, - intersectionRatio: 0.5, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0.5); - expect(rootModel.getVisibility_()).to.equal(0.5); - expect(elementModel.getVisibility_()).to.equal(0); - - // Make element visible. - inob.callback([{ - target: otherTarget, - intersectionRatio: 0.45, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0.5); - expect(rootModel.getVisibility_()).to.equal(0.5); - expect(elementModel.getVisibility_()).to.equal(0.45); - - // Hide parent. - viewer.setVisibilityState_(VisibilityState.HIDDEN); - expect(parentRoot.getRootVisibility()).to.equal(0); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); + root.dispose(); + expect(parentRoot.trackedElements_[id]).to.be.undefined; + }); - // Show parent. - viewer.setVisibilityState_(VisibilityState.VISIBLE); - expect(parentRoot.getRootVisibility()).to.equal(1); - expect(root.getRootVisibility()).to.equal(0.5); - expect(rootModel.getVisibility_()).to.equal(0.5); - expect(elementModel.getVisibility_()).to.equal(0.45); - - // Hide root. - inob.callback([{ - target: embed.host, - intersectionRatio: 0, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); - - // Update element. - inob.callback([{ - target: otherTarget, - intersectionRatio: 0.55, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0); - expect(rootModel.getVisibility_()).to.equal(0); - expect(elementModel.getVisibility_()).to.equal(0); - - // Show root. - inob.callback([{ - target: embed.host, - intersectionRatio: 0.7, - intersectionRect: layoutRectLtwh(0, 0, 1, 1), - }]); - expect(root.getRootVisibility()).to.equal(0.7); - expect(rootModel.getVisibility_()).to.equal(0.7); - expect(elementModel.getVisibility_()).to.equal(0.55); - }); -}); + it('should delegate observation to parent', () => { + const inOb = { + observe: sandbox.spy(), + unobserve: sandbox.spy(), + }; + parentRoot.intersectionObserver_ = inOb; + + const listener = sandbox.spy(); + const target = win.document.createElement('div'); + + // Observe. + const unlisten = root.observe(target, listener); + expect(inOb.observe).to.be.calledOnce; + expect(inOb.observe).to.be.calledWith(target); + const id = target.__AMP_VIS_ID; + expect(parentRoot.trackedElements_[id]).to.be.ok; + + // Unobserve. + unlisten(); + expect(inOb.unobserve).to.be.calledOnce; + expect(inOb.unobserve).to.be.calledWith(target); + expect(parentRoot.trackedElements_[id]).to.be.undefined; + }); + it('should depend on parent for visibility', () => { + const callbackSpy = sandbox.spy(); + const otherTarget = win.document.createElement('div'); + root.listenRoot({}, null, null, callbackSpy); + expect(root.models_).to.have.length(1); + const rootModel = root.models_[0]; + + root.listenElement(otherTarget, {}, null, null, callbackSpy); + expect(root.models_).to.have.length(2); + const elementModel = root.models_[1]; + + // Set up. + expect(inob.elements).to.contain(embed.host); + expect(inob.elements).to.contain(otherTarget); + + // Start state. + expect(parentRoot.getRootVisibility()).to.equal(1); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Make root visible. + inob.callback([ + { + target: embed.host, + intersectionRatio: 0.5, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0.5); + expect(rootModel.getVisibility_()).to.equal(0.5); + expect(elementModel.getVisibility_()).to.equal(0); + + // Make element visible. + inob.callback([ + { + target: otherTarget, + intersectionRatio: 0.45, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0.5); + expect(rootModel.getVisibility_()).to.equal(0.5); + expect(elementModel.getVisibility_()).to.equal(0.45); + + // Hide parent. + viewer.setVisibilityState_(VisibilityState.HIDDEN); + expect(parentRoot.getRootVisibility()).to.equal(0); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Show parent. + viewer.setVisibilityState_(VisibilityState.VISIBLE); + expect(parentRoot.getRootVisibility()).to.equal(1); + expect(root.getRootVisibility()).to.equal(0.5); + expect(rootModel.getVisibility_()).to.equal(0.5); + expect(elementModel.getVisibility_()).to.equal(0.45); + + // Hide root. + inob.callback([ + { + target: embed.host, + intersectionRatio: 0, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Update element. + inob.callback([ + { + target: otherTarget, + intersectionRatio: 0.55, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0); + expect(rootModel.getVisibility_()).to.equal(0); + expect(elementModel.getVisibility_()).to.equal(0); + + // Show root. + inob.callback([ + { + target: embed.host, + intersectionRatio: 0.7, + intersectionRect: layoutRectLtwh(0, 0, 1, 1), + }, + ]); + expect(root.getRootVisibility()).to.equal(0.7); + expect(rootModel.getVisibility_()).to.equal(0.7); + expect(elementModel.getVisibility_()).to.equal(0.55); + }); + } +); describes.realWin('VisibilityManager integrated', {amp: true}, env => { let win, doc; @@ -1013,15 +1080,18 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { const resource = resources.getResourceForElement(ampElement); scrollTop = 10; - sandbox.stub(resource, 'getLayoutBox').callsFake( - () => layoutRectLtwh(0, scrollTop, 100, 100)); + sandbox + .stub(resource, 'getLayoutBox') + .callsFake(() => layoutRectLtwh(0, scrollTop, 100, 100)); }); }); function fireIntersect(intersectPercent) { scrollTop = 100 - intersectPercent; const entry = makeIntersectionEntry( - [0, scrollTop, 100, 100], [0, 0, 100, 100]); + [0, scrollTop, 100, 100], + [0, 0, 100, 100] + ); inObCallback([entry]); } @@ -1029,8 +1099,9 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { boundingClientRect = layoutRectLtwh.apply(null, boundingClientRect); rootBounds = layoutRectLtwh.apply(null, rootBounds); const intersect = rectIntersection(boundingClientRect, rootBounds); - const ratio = (intersect.width * intersect.height) / - (boundingClientRect.width * boundingClientRect.height); + const ratio = + (intersect.width * intersect.height) / + (boundingClientRect.width * boundingClientRect.height); return { intersectionRect: intersect, boundingClientRect, @@ -1048,48 +1119,63 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, {}, readyPromise, () => { - return readyReportPromise; - }, eventResolver); + visibility.listenElement( + ampElement, + {}, + readyPromise, + () => { + return readyReportPromise; + }, + eventResolver + ); - return Promise.resolve().then(() => { - clock.tick(100); - fireIntersect(25); // visible - readyResolver(); - }).then(() => { - clock.tick(5); - readyReportResolver(); - return eventPromise; - }).then(state => { - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 100, - lastSeenTime: 105, - lastVisibleTime: 105, - loadTimeVisibility: 25, - maxVisiblePercentage: 25, - minVisiblePercentage: 25, - opacity: 0.5, - totalVisibleTime: 5, - maxContinuousVisibleTime: 5, - intersectionRatio: 0.25, - intersectionRect: '{"left":0,"top":75,"width":100,"height":25,' + - '"bottom":100,"right":100,"x":0,"y":75}', + return Promise.resolve() + .then(() => { + clock.tick(100); + fireIntersect(25); // visible + readyResolver(); + }) + .then(() => { + clock.tick(5); + readyReportResolver(); + return eventPromise; + }) + .then(state => { + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 75, + firstSeenTime: 100, + lastSeenTime: 105, + lastVisibleTime: 105, + loadTimeVisibility: 25, + maxVisiblePercentage: 25, + minVisiblePercentage: 25, + opacity: 0.5, + totalVisibleTime: 5, + maxContinuousVisibleTime: 5, + intersectionRatio: 0.25, + intersectionRect: + '{"left":0,"top":75,"width":100,"height":25,' + + '"bottom":100,"right":100,"x":0,"y":75}', + }); }); - }); }); - it('should wait for readyPromise with readyReportPromise', async() => { + it('should wait for readyPromise with readyReportPromise', async () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, {}, readyPromise, () => - readyReportPromise, eventResolver); + visibility.listenElement( + ampElement, + {}, + readyPromise, + () => readyReportPromise, + eventResolver + ); const model = visibility.models_[0]; await Promise.resolve(); @@ -1131,12 +1217,17 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { }); }); - it('should wait for readyReportPromise with reportWhen', async() => { + it('should wait for readyReportPromise with reportWhen', async () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, {reportWhen: 'documentExit'}, - readyPromise, () => readyReportPromise, eventResolver); + visibility.listenElement( + ampElement, + {reportWhen: 'documentExit'}, + readyPromise, + () => readyReportPromise, + eventResolver + ); const model = visibility.models_[0]; await Promise.resolve(); @@ -1175,83 +1266,143 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { }); }); - it('should wait for readyReportPromise with reportWhen and never meets ' + - 'visiblePercentageMin', async() => { - viewer.setVisibilityState_(VisibilityState.VISIBLE); - visibility = new VisibilityManagerForDoc(ampdoc); + it( + 'should wait for readyReportPromise with reportWhen and never meets ' + + 'visiblePercentageMin', + async () => { + viewer.setVisibilityState_(VisibilityState.VISIBLE); + visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, { - reportWhen: 'documentExit', - visiblePercentageMin: 50, - }, - readyPromise, () => readyReportPromise, eventResolver); - const model = visibility.models_[0]; + visibility.listenElement( + ampElement, + { + reportWhen: 'documentExit', + visiblePercentageMin: 50, + }, + readyPromise, + () => readyReportPromise, + eventResolver + ); + const model = visibility.models_[0]; - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; - clock.tick(20); - fireIntersect(25); // Doesn't meet visiblePercentageMin. - clock.tick(30); + clock.tick(20); + fireIntersect(25); // Doesn't meet visiblePercentageMin. + clock.tick(30); - readyResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; + readyResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; - clock.tick(40); + clock.tick(40); - readyReportResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.true; + readyReportResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.true; - const state = await eventPromise; - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 50, - lastSeenTime: 90, - lastVisibleTime: 0, // Didn't meet visibility. - loadTimeVisibility: 25, - // FIXME: max/minVisiblePercentage should equal loadTimeVisibility. - // See https://github.com/ampproject/amphtml/issues/19567 - maxVisiblePercentage: 0, - minVisiblePercentage: 0, - totalVisibleTime: 0, - maxContinuousVisibleTime: 0, - }); - }); + const state = await eventPromise; + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 75, + firstSeenTime: 50, + lastSeenTime: 90, + lastVisibleTime: 0, // Didn't meet visibility. + loadTimeVisibility: 25, + // FIXME: max/minVisiblePercentage should equal loadTimeVisibility. + // See https://github.com/ampproject/amphtml/issues/19567 + maxVisiblePercentage: 0, + minVisiblePercentage: 0, + totalVisibleTime: 0, + maxContinuousVisibleTime: 0, + }); + } + ); + + it( + 'should accumulate timings and wait for readyReportPromise with ' + + 'reportWhen and high minTotalVisibleTime', + async () => { + viewer.setVisibilityState_(VisibilityState.VISIBLE); + visibility = new VisibilityManagerForDoc(ampdoc); + + visibility.listenElement( + ampElement, + { + reportWhen: 'documentExit', + minTotalVisibleTime: 100000, // Never met + }, + readyPromise, + () => readyReportPromise, + eventResolver + ); + const model = visibility.models_[0]; + + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; + + readyResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; + + clock.tick(20); + fireIntersect(25); // visible + clock.tick(20); + fireIntersect(0); // hidden + clock.tick(20); + fireIntersect(35); // visible again + clock.tick(30); + fireIntersect(0); // hidden + clock.tick(20); + + readyReportResolver(); + await Promise.resolve(); + expect(isModelResolved(model)).to.be.true; + + const state = await eventPromise; + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 100, + firstSeenTime: 20, + lastSeenTime: 60, + lastVisibleTime: 90, + loadTimeVisibility: 25, + maxVisiblePercentage: 35, + minVisiblePercentage: 25, + totalVisibleTime: 50, + maxContinuousVisibleTime: 30, + }); + } + ); - it('should accumulate timings and wait for readyReportPromise with ' + - 'reportWhen and high minTotalVisibleTime', async() => { + it('should wait for readyReportPromise when missing readyPromise', async () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement(ampElement, { - reportWhen: 'documentExit', - minTotalVisibleTime: 100000, // Never met - }, readyPromise, () => readyReportPromise, eventResolver); + visibility.listenElement( + ampElement, + {}, + null, + () => readyReportPromise, + eventResolver + ); const model = visibility.models_[0]; - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; - - readyResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; - clock.tick(20); fireIntersect(25); // visible - clock.tick(20); - fireIntersect(0); // hidden - clock.tick(20); - fireIntersect(35); // visible again clock.tick(30); - fireIntersect(0); // hidden - clock.tick(20); + + await Promise.resolve(); + expect(isModelResolved(model)).to.be.false; readyReportResolver(); await Promise.resolve(); @@ -1264,75 +1415,44 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { elementHeight: 100, elementWidth: 100, elementX: 0, - elementY: 100, + elementY: 75, firstSeenTime: 20, - lastSeenTime: 60, - lastVisibleTime: 90, + lastSeenTime: 50, + lastVisibleTime: 50, loadTimeVisibility: 25, - maxVisiblePercentage: 35, + maxVisiblePercentage: 25, minVisiblePercentage: 25, - totalVisibleTime: 50, + totalVisibleTime: 30, maxContinuousVisibleTime: 30, }); }); - it('should wait for readyReportPromise when missing readyPromise', - async() => { - viewer.setVisibilityState_(VisibilityState.VISIBLE); - visibility = new VisibilityManagerForDoc(ampdoc); - - visibility.listenElement(ampElement, {}, null, () => - readyReportPromise, eventResolver); - const model = visibility.models_[0]; - - clock.tick(20); - fireIntersect(25); // visible - clock.tick(30); - - await Promise.resolve(); - expect(isModelResolved(model)).to.be.false; - - readyReportResolver(); - await Promise.resolve(); - expect(isModelResolved(model)).to.be.true; - - const state = await eventPromise; - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 20, - lastSeenTime: 50, - lastVisibleTime: 50, - loadTimeVisibility: 25, - maxVisiblePercentage: 25, - minVisiblePercentage: 25, - totalVisibleTime: 30, - maxContinuousVisibleTime: 30, - }); - }); - it('should execute "visible" trigger with percent range', () => { viewer.setVisibilityState_(VisibilityState.VISIBLE); visibility = new VisibilityManagerForDoc(ampdoc); const spy = sandbox.spy(); - visibility.listenElement(ampElement, { - 'visiblePercentageThresholds': [[0, 30], [50, 100]], - }, Promise.resolve(), null, spy); + visibility.listenElement( + ampElement, + { + 'visiblePercentageThresholds': [[0, 30], [50, 100]], + }, + Promise.resolve(), + null, + spy + ); - return Promise.resolve().then(() => { - fireIntersect(25); // visible - }).then(() => { - expect(spy).to.be.calledOnce; - fireIntersect(55); - return Promise.resolve().then(() => { - expect(spy).to.be.calledTwice; + return Promise.resolve() + .then(() => { + fireIntersect(25); // visible + }) + .then(() => { + expect(spy).to.be.calledOnce; + fireIntersect(55); + return Promise.resolve().then(() => { + expect(spy).to.be.calledTwice; + }); }); - }); }); it('should trigger "visible" with no duration condition', () => { @@ -1340,74 +1460,80 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { visibility = new VisibilityManagerForDoc(ampdoc); visibility.listenElement( - ampElement, - {visiblePercentageMin: 20}, - readyPromise, - null, - eventResolver); + ampElement, + {visiblePercentageMin: 20}, + readyPromise, + null, + eventResolver + ); // add multiple triggers on the same element visibility.listenElement( - ampElement, - {visiblePercentageMin: 30}, - readyPromise, - null, - eventResolver2); + ampElement, + {visiblePercentageMin: 30}, + readyPromise, + null, + eventResolver2 + ); // "observe" should not have been called since resource not loaded yet. expect(observeSpy).to.be.called; readyResolver(); - return Promise.resolve().then(() => { - expect(observeSpy).to.be.calledWith(ampElement); + return Promise.resolve() + .then(() => { + expect(observeSpy).to.be.calledWith(ampElement); - clock.tick(135); - fireIntersect(5); // below visiblePercentageMin, no trigger - - clock.tick(100); - fireIntersect(25); // above spec 1 min visible, trigger callback 1 - return eventPromise.then(state => { - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 75, - firstSeenTime: 135, - lastSeenTime: 235, - lastVisibleTime: 235, - loadTimeVisibility: 5, - maxVisiblePercentage: 25, - minVisiblePercentage: 25, - totalVisibleTime: 0, // duration metrics are always 0 - maxContinuousVisibleTime: 0, // as it triggers immediately - }); - expect(unobserveSpy).to.not.be.called; + clock.tick(135); + fireIntersect(5); // below visiblePercentageMin, no trigger clock.tick(100); - fireIntersect(35); // above spec 2 min visible, trigger callback 2 - return eventPromise2; - }).then(state => { - expect(state).to.contains({ - backgrounded: 0, - backgroundedAtStart: 0, - elementHeight: 100, - elementWidth: 100, - elementX: 0, - elementY: 65, - firstSeenTime: 135, - lastSeenTime: 335, - lastVisibleTime: 335, - loadTimeVisibility: 5, - maxVisiblePercentage: 35, - minVisiblePercentage: 35, - totalVisibleTime: 0, // duration metrics is always 0 - maxContinuousVisibleTime: 0, // as it triggers immediately - }); + fireIntersect(25); // above spec 1 min visible, trigger callback 1 + return eventPromise + .then(state => { + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 75, + firstSeenTime: 135, + lastSeenTime: 235, + lastVisibleTime: 235, + loadTimeVisibility: 5, + maxVisiblePercentage: 25, + minVisiblePercentage: 25, + totalVisibleTime: 0, // duration metrics are always 0 + maxContinuousVisibleTime: 0, // as it triggers immediately + }); + expect(unobserveSpy).to.not.be.called; + + clock.tick(100); + fireIntersect(35); // above spec 2 min visible, trigger callback 2 + return eventPromise2; + }) + .then(state => { + expect(state).to.contains({ + backgrounded: 0, + backgroundedAtStart: 0, + elementHeight: 100, + elementWidth: 100, + elementX: 0, + elementY: 65, + firstSeenTime: 135, + lastSeenTime: 335, + lastVisibleTime: 335, + loadTimeVisibility: 5, + maxVisiblePercentage: 35, + minVisiblePercentage: 35, + totalVisibleTime: 0, // duration metrics is always 0 + maxContinuousVisibleTime: 0, // as it triggers immediately + }); + }); + }) + .then(() => { + expect(unobserveSpy).to.be.called; // unobserve when all callback fired }); - }).then(() => { - expect(unobserveSpy).to.be.called; // unobserve when all callback fired - }); }); it('should trigger "visible" with duration condition', () => { @@ -1415,11 +1541,12 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { visibility = new VisibilityManagerForDoc(ampdoc); visibility.listenElement( - ampElement, - {continuousTimeMin: 1000}, - readyPromise, - null, - eventResolver); + ampElement, + {continuousTimeMin: 1000}, + readyPromise, + null, + eventResolver + ); const model = visibility.models_[0]; readyResolver(); @@ -1480,12 +1607,7 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { viewer.setVisibilityState_(VisibilityState.HIDDEN); visibility = new VisibilityManagerForDoc(ampdoc); - visibility.listenElement( - ampElement, - {}, - readyPromise, - null, - eventResolver); + visibility.listenElement(ampElement, {}, readyPromise, null, eventResolver); viewer.setVisibilityState_(VisibilityState.VISIBLE); readyResolver(); @@ -1504,7 +1626,6 @@ describes.realWin('VisibilityManager integrated', {amp: true}, env => { }); }); - describes.fakeWin('scroll depth', {amp: true}, env => { let ampdoc; let root; diff --git a/extensions/amp-analytics/0.1/test/test-visibility-model.js b/extensions/amp-analytics/0.1/test/test-visibility-model.js index 1f32a69930c32..c5c85dea41ddb 100644 --- a/extensions/amp-analytics/0.1/test/test-visibility-model.js +++ b/extensions/amp-analytics/0.1/test/test-visibility-model.js @@ -19,12 +19,11 @@ import {VisibilityModel} from '../visibility-model'; const NO_SPEC = {}; const NO_CALC = () => 0; - describes.sandboxed('VisibilityModel', {}, () => { let startTime; let clock; - const tick = async(timeout = 0) => { + const tick = async (timeout = 0) => { // Wait for the micro-task queue to clear since we need to wait for // internal promises to finish before running assertions; await Promise.resolve(); @@ -37,9 +36,7 @@ describes.sandboxed('VisibilityModel', {}, () => { clock.tick(startTime + 1); }); - describe('config', () => { - function config(spec) { return new VisibilityModel(spec, NO_CALC).spec_; } @@ -53,110 +50,106 @@ describes.sandboxed('VisibilityModel', {}, () => { it('should parse visiblePercentageMin', () => { expect(config({}).visiblePercentageMin).to.equal(0); - expect(config({visiblePercentageMin: ''}).visiblePercentageMin) - .to.equal(0); - expect(config({visiblePercentageMin: 0}).visiblePercentageMin) - .to.equal(0); - expect(config({visiblePercentageMin: '0'}).visiblePercentageMin) - .to.equal(0); - expect(config({visiblePercentageMin: 50}).visiblePercentageMin) - .to.equal(0.5); - expect(config({visiblePercentageMin: '50'}).visiblePercentageMin) - .to.equal(0.5); - expect(config({visiblePercentageMin: 100}).visiblePercentageMin) - .to.equal(1); - expect(config({visiblePercentageMin: '100'}).visiblePercentageMin) - .to.equal(1); + expect(config({visiblePercentageMin: ''}).visiblePercentageMin).to.equal( + 0 + ); + expect(config({visiblePercentageMin: 0}).visiblePercentageMin).to.equal( + 0 + ); + expect(config({visiblePercentageMin: '0'}).visiblePercentageMin).to.equal( + 0 + ); + expect(config({visiblePercentageMin: 50}).visiblePercentageMin).to.equal( + 0.5 + ); + expect( + config({visiblePercentageMin: '50'}).visiblePercentageMin + ).to.equal(0.5); + expect(config({visiblePercentageMin: 100}).visiblePercentageMin).to.equal( + 1 + ); + expect( + config({visiblePercentageMin: '100'}).visiblePercentageMin + ).to.equal(1); }); it('should parse visiblePercentageMax', () => { expect(config({}).visiblePercentageMax).to.equal(1); - expect(config({visiblePercentageMax: ''}).visiblePercentageMax) - .to.equal(1); - expect(config({visiblePercentageMax: 0}).visiblePercentageMax) - .to.equal(0); - expect(config({visiblePercentageMax: '0'}).visiblePercentageMax) - .to.equal(0); - expect(config({visiblePercentageMax: 50}).visiblePercentageMax) - .to.equal(0.5); - expect(config({visiblePercentageMax: '50'}).visiblePercentageMax) - .to.equal(0.5); - expect(config({visiblePercentageMax: 100}).visiblePercentageMax) - .to.equal(1); - expect(config({visiblePercentageMax: '100'}).visiblePercentageMax) - .to.equal(1); + expect(config({visiblePercentageMax: ''}).visiblePercentageMax).to.equal( + 1 + ); + expect(config({visiblePercentageMax: 0}).visiblePercentageMax).to.equal( + 0 + ); + expect(config({visiblePercentageMax: '0'}).visiblePercentageMax).to.equal( + 0 + ); + expect(config({visiblePercentageMax: 50}).visiblePercentageMax).to.equal( + 0.5 + ); + expect( + config({visiblePercentageMax: '50'}).visiblePercentageMax + ).to.equal(0.5); + expect(config({visiblePercentageMax: 100}).visiblePercentageMax).to.equal( + 1 + ); + expect( + config({visiblePercentageMax: '100'}).visiblePercentageMax + ).to.equal(1); }); it('should parse totalTimeMin', () => { expect(config({}).totalTimeMin).to.equal(0); - expect(config({totalTimeMin: ''}).totalTimeMin) - .to.equal(0); - expect(config({totalTimeMin: 0}).totalTimeMin) - .to.equal(0); - expect(config({totalTimeMin: '0'}).totalTimeMin) - .to.equal(0); - expect(config({totalTimeMin: 50}).totalTimeMin) - .to.equal(50); - expect(config({totalTimeMin: '50'}).totalTimeMin) - .to.equal(50); - expect(config({totalTimeMin: 100}).totalTimeMin) - .to.equal(100); - expect(config({totalTimeMin: '100'}).totalTimeMin) - .to.equal(100); + expect(config({totalTimeMin: ''}).totalTimeMin).to.equal(0); + expect(config({totalTimeMin: 0}).totalTimeMin).to.equal(0); + expect(config({totalTimeMin: '0'}).totalTimeMin).to.equal(0); + expect(config({totalTimeMin: 50}).totalTimeMin).to.equal(50); + expect(config({totalTimeMin: '50'}).totalTimeMin).to.equal(50); + expect(config({totalTimeMin: 100}).totalTimeMin).to.equal(100); + expect(config({totalTimeMin: '100'}).totalTimeMin).to.equal(100); }); it('should parse totalTimeMax', () => { expect(config({}).totalTimeMax).to.equal(Infinity); - expect(config({totalTimeMax: ''}).totalTimeMax) - .to.equal(Infinity); - expect(config({totalTimeMax: 0}).totalTimeMax) - .to.equal(Infinity); - expect(config({totalTimeMax: '0'}).totalTimeMax) - .to.equal(Infinity); - expect(config({totalTimeMax: 50}).totalTimeMax) - .to.equal(50); - expect(config({totalTimeMax: '50'}).totalTimeMax) - .to.equal(50); - expect(config({totalTimeMax: 100}).totalTimeMax) - .to.equal(100); - expect(config({totalTimeMax: '100'}).totalTimeMax) - .to.equal(100); + expect(config({totalTimeMax: ''}).totalTimeMax).to.equal(Infinity); + expect(config({totalTimeMax: 0}).totalTimeMax).to.equal(Infinity); + expect(config({totalTimeMax: '0'}).totalTimeMax).to.equal(Infinity); + expect(config({totalTimeMax: 50}).totalTimeMax).to.equal(50); + expect(config({totalTimeMax: '50'}).totalTimeMax).to.equal(50); + expect(config({totalTimeMax: 100}).totalTimeMax).to.equal(100); + expect(config({totalTimeMax: '100'}).totalTimeMax).to.equal(100); }); it('should parse continuousTimeMin', () => { expect(config({}).continuousTimeMin).to.equal(0); - expect(config({continuousTimeMin: ''}).continuousTimeMin) - .to.equal(0); - expect(config({continuousTimeMin: 0}).continuousTimeMin) - .to.equal(0); - expect(config({continuousTimeMin: '0'}).continuousTimeMin) - .to.equal(0); - expect(config({continuousTimeMin: 50}).continuousTimeMin) - .to.equal(50); - expect(config({continuousTimeMin: '50'}).continuousTimeMin) - .to.equal(50); - expect(config({continuousTimeMin: 100}).continuousTimeMin) - .to.equal(100); - expect(config({continuousTimeMin: '100'}).continuousTimeMin) - .to.equal(100); + expect(config({continuousTimeMin: ''}).continuousTimeMin).to.equal(0); + expect(config({continuousTimeMin: 0}).continuousTimeMin).to.equal(0); + expect(config({continuousTimeMin: '0'}).continuousTimeMin).to.equal(0); + expect(config({continuousTimeMin: 50}).continuousTimeMin).to.equal(50); + expect(config({continuousTimeMin: '50'}).continuousTimeMin).to.equal(50); + expect(config({continuousTimeMin: 100}).continuousTimeMin).to.equal(100); + expect(config({continuousTimeMin: '100'}).continuousTimeMin).to.equal( + 100 + ); }); it('should parse continuousTimeMax', () => { expect(config({}).continuousTimeMax).to.equal(Infinity); - expect(config({continuousTimeMax: ''}).continuousTimeMax) - .to.equal(Infinity); - expect(config({continuousTimeMax: 0}).continuousTimeMax) - .to.equal(Infinity); - expect(config({continuousTimeMax: '0'}).continuousTimeMax) - .to.equal(Infinity); - expect(config({continuousTimeMax: 50}).continuousTimeMax) - .to.equal(50); - expect(config({continuousTimeMax: '50'}).continuousTimeMax) - .to.equal(50); - expect(config({continuousTimeMax: 100}).continuousTimeMax) - .to.equal(100); - expect(config({continuousTimeMax: '100'}).continuousTimeMax) - .to.equal(100); + expect(config({continuousTimeMax: ''}).continuousTimeMax).to.equal( + Infinity + ); + expect(config({continuousTimeMax: 0}).continuousTimeMax).to.equal( + Infinity + ); + expect(config({continuousTimeMax: '0'}).continuousTimeMax).to.equal( + Infinity + ); + expect(config({continuousTimeMax: 50}).continuousTimeMax).to.equal(50); + expect(config({continuousTimeMax: '50'}).continuousTimeMax).to.equal(50); + expect(config({continuousTimeMax: 100}).continuousTimeMax).to.equal(100); + expect(config({continuousTimeMax: '100'}).continuousTimeMax).to.equal( + 100 + ); }); it('should parse repeat', () => { @@ -350,12 +343,15 @@ describes.sandboxed('VisibilityModel', {}, () => { let visibilityValueForTesting = null; beforeEach(() => { - vh = new VisibilityModel({ - minVisiblePercentage: 25, - totalTimeMin: 10, - continuousTimeMin: 10, - continuousTimeMax: 1000, - }, NO_CALC); + vh = new VisibilityModel( + { + minVisiblePercentage: 25, + totalTimeMin: 10, + continuousTimeMin: 10, + continuousTimeMax: 1000, + }, + NO_CALC + ); updateStub = sandbox.stub(vh, 'update').callsFake(() => { if (visibilityValueForTesting) { vh.update_(visibilityValueForTesting); @@ -531,41 +527,48 @@ describes.sandboxed('VisibilityModel', {}, () => { const shouldTriggerEventTestSpecs = [ {reportWhen: 'documentExit'}, {reportWhen: 'documentHidden'}, - {reportWhen: 'documentExit', totalTimeMin: 100000, - visiblePercentageMin: 50}, + { + reportWhen: 'documentExit', + totalTimeMin: 100000, + visiblePercentageMin: 50, + }, ]; for (const i in shouldTriggerEventTestSpecs) { - it('should trigger event with reportWhen,' + - `test case #${i}`, async() => { - const vh = new VisibilityModel( - shouldTriggerEventTestSpecs[i], () => 0); - - vh.onTriggerEvent(eventSpy); - // TODO(warrengm): Inverting the two following lines will break this - // test. Consider updating the API to make it safer. - vh.setReportReady(() => reportPromise); - vh.setReady(true); - - vh.update(); - await tick(); - expect(eventSpy).to.not.be.called; - - promiseResolver(); - await tick(); - expect(eventSpy).to.be.calledOnce; - - // Subsequent calls should not trigger the event again. - vh.update(); - await tick(); - expect(eventSpy).to.be.calledOnce; - }); + it( + 'should trigger event with reportWhen,' + `test case #${i}`, + async () => { + const vh = new VisibilityModel( + shouldTriggerEventTestSpecs[i], + () => 0 + ); + + vh.onTriggerEvent(eventSpy); + // TODO(warrengm): Inverting the two following lines will break this + // test. Consider updating the API to make it safer. + vh.setReportReady(() => reportPromise); + vh.setReady(true); + + vh.update(); + await tick(); + expect(eventSpy).to.not.be.called; + + promiseResolver(); + await tick(); + expect(eventSpy).to.be.calledOnce; + + // Subsequent calls should not trigger the event again. + vh.update(); + await tick(); + expect(eventSpy).to.be.calledOnce; + } + ); } }); }); it('conditions met, wait to reset', () => { - const resolveSpy = vh.eventResolver_ = sandbox.spy(); + const resolveSpy = (vh.eventResolver_ = sandbox.spy()); vh.update_(1); vh.continuousTime_ = 10; vh.totalVisibleTime_ = 10; @@ -575,8 +578,8 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('conditions not met, reset', () => { - const updateCounterSpy = vh.updateCounters_ = sandbox.spy(); - const resetSpy = vh.reset_ = sandbox.spy(); + const updateCounterSpy = (vh.updateCounters_ = sandbox.spy()); + const resetSpy = (vh.reset_ = sandbox.spy()); vh.waitToReset_ = true; vh.update_(1); expect(resetSpy).to.not.be.called; @@ -586,9 +589,7 @@ describes.sandboxed('VisibilityModel', {}, () => { }); }); - describe('tracking math', () => { - it('should register "seen" values', () => { const vh = new VisibilityModel(NO_SPEC, NO_CALC); @@ -658,10 +659,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should match custom visibility position', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 10, - visiblePercentageMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + visiblePercentageMin: 10, + visiblePercentageMax: 90, + }, + NO_CALC + ); vh.updateCounters_(0); expect(vh.matchesVisibility_).to.be.false; @@ -808,10 +812,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should yield based on position only', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 10, - visiblePercentageMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + visiblePercentageMin: 10, + visiblePercentageMax: 90, + }, + NO_CALC + ); clock.tick(100); expect(vh.updateCounters_(0)).to.be.false; expect(vh.updateCounters_(0.1)).to.be.false; @@ -821,10 +828,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should yield based on total time only', () => { - const vh = new VisibilityModel({ - totalTimeMin: 10, - totalTimeMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + totalTimeMin: 10, + totalTimeMax: 90, + }, + NO_CALC + ); expect(vh.updateCounters_(0.1)).to.be.false; clock.tick(5); expect(vh.updateCounters_(0)).to.be.false; @@ -837,10 +847,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should yield based on continuous time only', () => { - const vh = new VisibilityModel({ - continuousTimeMin: 10, - continuousTimeMax: 90, - }, NO_CALC); + const vh = new VisibilityModel( + { + continuousTimeMin: 10, + continuousTimeMax: 90, + }, + NO_CALC + ); expect(vh.updateCounters_(0.1)).to.be.false; clock.tick(5); expect(vh.updateCounters_(0)).to.be.false; @@ -865,11 +878,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should trigger for visibility percent only', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - visiblePercentageMax: 80, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + visiblePercentageMax: 80, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.63; vh.update(); @@ -882,11 +898,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should only update load-time visibility once', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - visiblePercentageMax: 80, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + visiblePercentageMax: 80, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.49; vh.update(); @@ -901,10 +920,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with totalTimeMin condition', () => { - const vh = new VisibilityModel({ - totalTimeMin: 1000, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + totalTimeMin: 1000, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.63; vh.update(); @@ -933,10 +955,13 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with continuousTimeMin condition', () => { - const vh = new VisibilityModel({ - continuousTimeMin: 1000, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + continuousTimeMin: 1000, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.63; vh.update(); @@ -974,11 +999,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with totalTimeMin and visiblePercentageMin', () => { - const vh = new VisibilityModel({ - totalTimeMin: 1000, - visiblePercentageMin: 10, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + totalTimeMin: 1000, + visiblePercentageMin: 10, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.05; vh.update(); @@ -1009,11 +1037,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with continuousTimeMin=1k and totalTimeMin=2k', () => { - const vh = new VisibilityModel({ - totalTimeMin: 2000, - continuousTimeMin: 1000, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + totalTimeMin: 2000, + continuousTimeMin: 1000, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.05; vh.update(); @@ -1041,11 +1072,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire with continuousTimeMin=1k and visPercentageMin=50', () => { - const vh = new VisibilityModel({ - continuousTimeMin: 1000, - visiblePercentageMin: 49, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + continuousTimeMin: 1000, + visiblePercentageMin: 49, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); clock.tick(999); visibility = 0; @@ -1074,11 +1108,14 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire for visiblePercentageMin=visiblePercentageMax=100', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 100, - visiblePercentageMax: 100, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 100, + visiblePercentageMax: 100, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); visibility = 0.99; clock.tick(200); vh.update(); @@ -1090,12 +1127,15 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should fire for visiblePercentageMin=visiblePercentageMax=0', () => { - const vh = new VisibilityModel({ - visiblePercentageMin: 0, - visiblePercentageMax: 0, - repeat: true, - }, calcVisibility); - const eventSpy = vh.eventResolver_ = sandbox.spy(); + const vh = new VisibilityModel( + { + visiblePercentageMin: 0, + visiblePercentageMax: 0, + repeat: true, + }, + calcVisibility + ); + const eventSpy = (vh.eventResolver_ = sandbox.spy()); sandbox.stub(vh, 'reset_').callsFake(() => { vh.eventPromise_ = new Promise(unused => { vh.eventResolver_ = eventSpy; @@ -1145,11 +1185,14 @@ describes.sandboxed('VisibilityModel', {}, () => { calcVisibility = () => visibility; }); - it('should wait for repeat interval', function* () { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - repeat: true, - }, calcVisibility); + it('should wait for repeat interval', function*() { + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + repeat: true, + }, + calcVisibility + ); const spy = sandbox.spy(); vh.onTriggerEvent(() => { spy(); @@ -1167,11 +1210,14 @@ describes.sandboxed('VisibilityModel', {}, () => { expect(spy).to.be.calledOnce; }); - it('should wait for not match to fire again w/o interval', function* () { - const vh = new VisibilityModel({ - visiblePercentageMin: 49, - repeat: true, - }, calcVisibility); + it('should wait for not match to fire again w/o interval', function*() { + const vh = new VisibilityModel( + { + visiblePercentageMin: 49, + repeat: true, + }, + calcVisibility + ); const spy = sandbox.spy(); vh.onTriggerEvent(() => { spy(); @@ -1207,9 +1253,12 @@ describes.sandboxed('VisibilityModel', {}, () => { }); it('should correctly update initialScrollDepth', () => { - const vh = new VisibilityModel({ - repeat: true, - }, calcVisibility); + const vh = new VisibilityModel( + { + repeat: true, + }, + calcVisibility + ); vh.maybeSetInitialScrollDepth(200); vh.maybeSetInitialScrollDepth(100); vh.maybeSetInitialScrollDepth(400); diff --git a/extensions/amp-analytics/0.1/transport-serializer.js b/extensions/amp-analytics/0.1/transport-serializer.js index 3b1d30b0c6287..08abbfd4554ae 100644 --- a/extensions/amp-analytics/0.1/transport-serializer.js +++ b/extensions/amp-analytics/0.1/transport-serializer.js @@ -43,7 +43,6 @@ export let RequestDef; * @interface */ export class TransportSerializerDef { - /** * @param {string} unusedBaseUrl * @param {!BatchSegmentDef} unusedSegment @@ -67,7 +66,6 @@ export class TransportSerializerDef { * @implements {TransportSerializerDef} */ class DefaultTransportSerializer { - /** @override */ generateRequest(baseUrl, segment, withPayload = false) { if (withPayload) { @@ -87,7 +85,8 @@ class DefaultTransportSerializer { return { url: baseUrl.replace(EXTRA_URL_PARAM_VAR, ''), payload: JSON.stringify( - segments.map(segment => segment['extraUrlParams'])), + segments.map(segment => segment['extraUrlParams']) + ), }; } return { @@ -115,9 +114,9 @@ export const TransportSerializers = { */ export function defaultSerializer(baseUrl, batchSegments) { const extraUrlParamsStr = batchSegments - .map(item => serializeQueryString(item['extraUrlParams'])) - .filter(queryString => !!queryString) - .join('&'); + .map(item => serializeQueryString(item['extraUrlParams'])) + .filter(queryString => !!queryString) + .join('&'); let requestUrl; if (baseUrl.indexOf(EXTRA_URL_PARAM_VAR) >= 0) { requestUrl = baseUrl.replace(EXTRA_URL_PARAM_VAR, extraUrlParamsStr); diff --git a/extensions/amp-analytics/0.1/transport.js b/extensions/amp-analytics/0.1/transport.js index 2125df2cf1235..57dcd3e39f733 100644 --- a/extensions/amp-analytics/0.1/transport.js +++ b/extensions/amp-analytics/0.1/transport.js @@ -45,12 +45,11 @@ const TAG_ = 'amp-analytics/transport'; * Transport defines the ways how the analytics pings are going to be sent. */ export class Transport { - /** * @param {!Window} win * @param {!JsonObject} options */ - constructor(win, options = /** @type {!JsonObject} */({})) { + constructor(win, options = /** @type {!JsonObject} */ ({})) { /** @private {!Window} */ this.win_ = win; @@ -58,7 +57,9 @@ export class Transport { this.options_ = options; /** @private {string|undefined} */ - this.referrerPolicy_ = /** @type {string|undefined} */ (this.options_['referrerPolicy']); + this.referrerPolicy_ = /** @type {string|undefined} */ (this.options_[ + 'referrerPolicy' + ]); // no-referrer is only supported in image transport if (this.referrerPolicy_ === 'no-referrer') { @@ -111,21 +112,28 @@ export class Transport { return; } - if (this.options_['beacon'] && Transport.sendRequestUsingBeacon( - this.win_, getRequest(this.useBody_))) { + if ( + this.options_['beacon'] && + Transport.sendRequestUsingBeacon(this.win_, getRequest(this.useBody_)) + ) { return; } - if (this.options_['xhrpost'] && Transport.sendRequestUsingXhr( - this.win_, getRequest(this.useBody_))) { + if ( + this.options_['xhrpost'] && + Transport.sendRequestUsingXhr(this.win_, getRequest(this.useBody_)) + ) { return; } const image = this.options_['image']; if (image) { - const suppressWarnings = (typeof image == 'object' && - image['suppressWarnings']); + const suppressWarnings = + typeof image == 'object' && image['suppressWarnings']; Transport.sendRequestUsingImage( - this.win_, getRequest(false), suppressWarnings, - /** @type {string|undefined} */ (this.referrerPolicy_)); + this.win_, + getRequest(false), + suppressWarnings, + /** @type {string|undefined} */ (this.referrerPolicy_) + ); return; } user().warn(TAG_, 'Failed to send request', url, this.options_); @@ -152,13 +160,20 @@ export class Transport { const type = element.getAttribute('type'); // In inabox there is no amp-ad element. - const ampAdResourceId = this.isInabox_ ? '1' : user().assertString( - getAmpAdResourceId(element, getTopWindow(win)), - 'No friendly amp-ad ancestor element was found ' + - 'for amp-analytics tag with iframe transport.'); + const ampAdResourceId = this.isInabox_ + ? '1' + : user().assertString( + getAmpAdResourceId(element, getTopWindow(win)), + 'No friendly amp-ad ancestor element was found ' + + 'for amp-analytics tag with iframe transport.' + ); this.iframeTransport_ = new IframeTransport( - win, type, this.options_, ampAdResourceId); + win, + type, + this.options_, + ampAdResourceId + ); } /** @@ -190,11 +205,12 @@ export class Transport { assertHttpsUrl(request, 'amp-analytics request'); userAssert( - parseUrlDeprecated(request).origin != + parseUrlDeprecated(request).origin != parseUrlDeprecated(this.win_.location.href).origin, - 'Origin of iframe request must not be equal to the document origin.' + + 'Origin of iframe request must not be equal to the document origin.' + ' See https://github.com/ampproject/' + - ' amphtml/blob/master/spec/amp-iframe-origin-policy.md for details.'); + ' amphtml/blob/master/spec/amp-iframe-origin-policy.md for details.' + ); /** @const {!Element} */ const iframe = this.win_.document.createElement('iframe'); @@ -215,7 +231,9 @@ export class Transport { * @return {!TransportSerializerDef} */ getSerializer_() { - return /** @type {!TransportSerializerDef} */(TransportSerializers['default']); + return /** @type {!TransportSerializerDef} */ (TransportSerializers[ + 'default' + ]); } /** @@ -226,14 +244,19 @@ export class Transport { */ static sendRequestUsingImage(win, request, suppressWarnings, referrerPolicy) { const image = createPixel(win, request.url, referrerPolicy); - loadPromise(image).then(() => { - dev().fine(TAG_, 'Sent image request', request.url); - }).catch(() => { - if (!suppressWarnings) { - user().warn(TAG_, 'Response unparseable or failed to send image ' + - 'request', request.url); - } - }); + loadPromise(image) + .then(() => { + dev().fine(TAG_, 'Sent image request', request.url); + }) + .catch(() => { + if (!suppressWarnings) { + user().warn( + TAG_, + 'Response unparseable or failed to send image ' + 'request', + request.url + ); + } + }); } /** diff --git a/extensions/amp-analytics/0.1/variables.js b/extensions/amp-analytics/0.1/variables.js index 46676252a9357..c53d24b69da0a 100644 --- a/extensions/amp-analytics/0.1/variables.js +++ b/extensions/amp-analytics/0.1/variables.js @@ -84,8 +84,6 @@ export class ExpansionOptions { } } - - /** * @param {string} str * @param {string} s @@ -95,12 +93,16 @@ export class ExpansionOptions { function substrMacro(str, s, opt_l) { const start = Number(s); let {length} = str; - userAssert(isFiniteNumber(start), - 'Start index ' + start + 'in substr macro should be a number'); + userAssert( + isFiniteNumber(start), + 'Start index ' + start + 'in substr macro should be a number' + ); if (opt_l) { length = Number(opt_l); - userAssert(isFiniteNumber(length), - 'Length ' + length + ' in substr macro should be a number'); + userAssert( + isFiniteNumber(length), + 'Length ' + length + ' in substr macro should be a number' + ); } return str.substr(start, length); @@ -135,7 +137,6 @@ function replaceMacro(string, matchPattern, opt_newSubStr) { return string.replace(regex, opt_newSubStr); } - /** * Provides support for processing of advanced variable syntax like nested * expansions macros etc. @@ -145,7 +146,6 @@ export class VariableService { * @param {!Window} window */ constructor(window) { - /** @private {!Window} */ this.win_ = window; @@ -163,13 +163,15 @@ export class VariableService { this.register_('$NOT', value => String(!value)); this.register_('$BASE64', value => base64UrlEncodeFromString(value)); this.register_('$HASH', this.hashMacro_.bind(this)); - this.register_('$IF', - (value, thenValue, elseValue) => value ? thenValue : elseValue); + this.register_('$IF', (value, thenValue, elseValue) => + value ? thenValue : elseValue + ); this.register_('$REPLACE', replaceMacro); // TODO(ccordry): Make sure this stays a window level service when this // VariableService is migrated to document level. this.register_('LINKER_PARAM', (name, id) => - this.linkerReader_.get(name, id)); + this.linkerReader_.get(name, id) + ); } /** @@ -184,8 +186,7 @@ export class VariableService { * @param {*} macro */ register_(name, macro) { - devAssert(!this.macros_[name], 'Macro "' + name - + '" already registered.'); + devAssert(!this.macros_[name], 'Macro "' + name + '" already registered.'); this.macros_[name] = macro; } @@ -207,8 +208,11 @@ export class VariableService { expandTemplateSync(template, options) { return template.replace(/\${([^}]*)}/g, (match, key) => { if (options.iterations < 0) { - user().error(TAG, 'Maximum depth reached while expanding variables. ' + - 'Please ensure that the variables are not recursive.'); + user().error( + TAG, + 'Maximum depth reached while expanding variables. ' + + 'Please ensure that the variables are not recursive.' + ); return match; } @@ -227,13 +231,18 @@ export class VariableService { let value = options.getVar(name); if (typeof value == 'string') { - value = this.expandTemplateSync(value, - new ExpansionOptions(options.vars, options.iterations - 1, - true /* noEncode */)); + value = this.expandTemplateSync( + value, + new ExpansionOptions( + options.vars, + options.iterations - 1, + true /* noEncode */ + ) + ); } if (!options.noEncode) { - value = encodeVars(/** @type {string|?Array} */(value)); + value = encodeVars(/** @type {string|?Array} */ (value)); } if (value) { value += argList; @@ -242,7 +251,6 @@ export class VariableService { }); } - /** * @param {string} value * @return {!Promise} diff --git a/extensions/amp-analytics/0.1/vendors.js b/extensions/amp-analytics/0.1/vendors.js index 246122717b1c7..8d847ceeae0ae 100644 --- a/extensions/amp-analytics/0.1/vendors.js +++ b/extensions/amp-analytics/0.1/vendors.js @@ -58,9 +58,7 @@ import {MOBIFY_CONFIG} from './vendors/mobify'; import {MPARTICLE_CONFIG} from './vendors/mparticle'; import {NEWRELIC_CONFIG} from './vendors/newrelic'; import {NIELSEN_CONFIG} from './vendors/nielsen'; -import { - NIELSEN_MARKETING_CLOUD_CONFIG, -} from './vendors/nielsen-marketing-cloud'; +import {NIELSEN_MARKETING_CLOUD_CONFIG} from './vendors/nielsen-marketing-cloud'; import {OEWADIRECT_CONFIG} from './vendors/oewadirect'; import {OEWA_CONFIG} from './vendors/oewa'; import {PARSELY_CONFIG} from './vendors/parsely'; @@ -72,9 +70,7 @@ import {PRESSBOARD_CONFIG} from './vendors/pressboard'; import {QUANTCAST_CONFIG} from './vendors/quantcast'; import {RETARGETLY_CONFIG} from './vendors/retargetly'; import {ADOBEANALYTICS_CONFIG} from './vendors/adobeanalytics'; -import { - ADOBEANALYTICS_NATIVECONFIG_CONFIG, -} from './vendors/adobeanalytics_nativeConfig'; +import {ADOBEANALYTICS_NATIVECONFIG_CONFIG} from './vendors/adobeanalytics_nativeConfig'; import {INFONLINE_CONFIG} from './vendors/infonline'; import {SIMPLEREACH_CONFIG} from './vendors/simplereach'; import {SEGMENT_CONFIG} from './vendors/segment'; @@ -91,9 +87,7 @@ import {LINKPULSE_CONFIG} from './vendors/linkpulse'; import {RAKAM_CONFIG} from './vendors/rakam'; import {IBEATANALYTICS_CONFIG} from './vendors/ibeatanalytics'; import {TOPMAILRU_CONFIG} from './vendors/topmailru'; -import { - ORACLEINFINITYANALYTICS_CONFIG, -} from './vendors/oracleInfinityAnalytics'; +import {ORACLEINFINITYANALYTICS_CONFIG} from './vendors/oracleInfinityAnalytics'; import {MOAT_CONFIG} from './vendors/moat'; import {BG_CONFIG} from './vendors/bg'; import {UPSCORE_CONFIG} from './vendors/upscore'; @@ -104,7 +98,6 @@ import {NAVEGG_CONFIG} from './vendors/navegg'; * @const {!JsonObject} */ export const ANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ - // Default parent configuration applied to all amp-analytics tags. 'default': { 'transport': {'beacon': true, 'xhrpost': true, 'image': true}, @@ -252,15 +245,17 @@ if (getMode().test || getMode().localDev) { ANALYTICS_CONFIG['_fake_'] = _FAKE_; } -ANALYTICS_CONFIG['infonline']['triggers']['pageview']['iframe' + -/* TEMPORARY EXCEPTION */ 'Ping'] = true; +ANALYTICS_CONFIG['infonline']['triggers']['pageview'][ + 'iframe' + /* TEMPORARY EXCEPTION */ 'Ping' +] = true; -ANALYTICS_CONFIG['adobeanalytics_nativeConfig'] - ['triggers']['pageLoad']['iframe' + - /* TEMPORARY EXCEPTION */ 'Ping'] = true; +ANALYTICS_CONFIG['adobeanalytics_nativeConfig']['triggers']['pageLoad'][ + 'iframe' + /* TEMPORARY EXCEPTION */ 'Ping' +] = true; -ANALYTICS_CONFIG['oewa']['triggers']['pageview']['iframe' + -/* TEMPORARY EXCEPTION */ 'Ping'] = true; +ANALYTICS_CONFIG['oewa']['triggers']['pageview'][ + 'iframe' + /* TEMPORARY EXCEPTION */ 'Ping' +] = true; mergeIframeTransportConfig(ANALYTICS_CONFIG, IFRAME_TRANSPORTS); @@ -274,8 +269,11 @@ function mergeIframeTransportConfig(config, iframeTransportConfig) { for (const vendor in iframeTransportConfig) { if (hasOwn(iframeTransportConfig, vendor)) { const url = iframeTransportConfig[vendor]; - config[vendor]['transport'] = - Object.assign({}, config[vendor]['transport'], {'iframe': url}); + config[vendor]['transport'] = Object.assign( + {}, + config[vendor]['transport'], + {'iframe': url} + ); } } } diff --git a/extensions/amp-analytics/0.1/vendors/acquialift.js b/extensions/amp-analytics/0.1/vendors/acquialift.js index 0bd20aa09b26f..c86939bcbc294 100644 --- a/extensions/amp-analytics/0.1/vendors/acquialift.js +++ b/extensions/amp-analytics/0.1/vendors/acquialift.js @@ -22,18 +22,18 @@ export const ACQUIALIFT_CONFIG = /** @type {!JsonObject} */ ({ }, 'transport': {'beacon': true, 'xhrpost': true, 'image': false}, 'requests': { - 'base': 'https://${decisionApiUrl}/capture?account_id=${accountId}&site_id=${siteId}', - 'basicCapture': '${base}' + + 'base': + 'https://${decisionApiUrl}/capture?account_id=${accountId}&site_id=${siteId}', + 'basicCapture': + '${base}' + '&ident=${clientId(tc_ptid)}' + '&identsrc=amp' + '&es=Amp' + '&url=${canonicalUrl}' + '&rurl=${documentReferrer}' + '&cttl=${title}', - 'pageview': '${basicCapture}' + - '&en=Content View', - 'click': '${basicCapture}' + - '&en=Click-Through', + 'pageview': '${basicCapture}' + '&en=Content View', + 'click': '${basicCapture}' + '&en=Click-Through', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/adobeanalytics.js b/extensions/amp-analytics/0.1/vendors/adobeanalytics.js index 34589c5a1532e..23ac497e103d8 100644 --- a/extensions/amp-analytics/0.1/vendors/adobeanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/adobeanalytics.js @@ -27,18 +27,20 @@ export const ADOBEANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'requestPath': '/b/ss/${reportSuites}/0/amp-1.0/s${random}', // vid starts with z to work around #2198 - 'basePrefix': 'vid=z${clientId(adobe_amp_id)}' + - '&ndh=0' + - '&ce=${documentCharset}' + - '&pageName=${pageName}' + - '&g=${ampdocUrl}' + - '&r=${documentReferrer}' + - '&bh=${availableScreenHeight}' + - '&bw=${availableScreenWidth}' + - '&c=${screenColorDepth}' + - '&j=amp' + - '&s=${screenWidth}x${screenHeight}', + 'basePrefix': + 'vid=z${clientId(adobe_amp_id)}' + + '&ndh=0' + + '&ce=${documentCharset}' + + '&pageName=${pageName}' + + '&g=${ampdocUrl}' + + '&r=${documentReferrer}' + + '&bh=${availableScreenHeight}' + + '&bw=${availableScreenWidth}' + + '&c=${screenColorDepth}' + + '&j=amp' + + '&s=${screenWidth}x${screenHeight}', 'pageview': 'https://${host}${requestPath}?${basePrefix}', - 'click': 'https://${host}${requestPath}?${basePrefix}&pe=lnk_${linkType}&pev1=${linkUrl}&pev2=${linkName}', + 'click': + 'https://${host}${requestPath}?${basePrefix}&pe=lnk_${linkType}&pev1=${linkUrl}&pev2=${linkName}', }, }); diff --git a/extensions/amp-analytics/0.1/vendors/afsanalytics.js b/extensions/amp-analytics/0.1/vendors/afsanalytics.js index 6cf4487ecd54c..36fc402fb5e0a 100644 --- a/extensions/amp-analytics/0.1/vendors/afsanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/afsanalytics.js @@ -25,7 +25,8 @@ export const AFSANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': '//${server}.afsanalytics.com', 'base': '${host}/cgi_bin/', - 'pageview': '${base}connect.cgi?usr=${websiteid}Pauto' + + 'pageview': + '${base}connect.cgi?usr=${websiteid}Pauto' + '&js=1' + '&=1' + '&title=${title}' + @@ -34,7 +35,8 @@ export const AFSANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ '&resolution=${screenWidth}x${screenHeight}' + '&color=${screenColorDepth}' + '&Tips=${random}', - 'click': '${base}click.cgi?usr=${websiteid}' + + 'click': + '${base}click.cgi?usr=${websiteid}' + '&event=${event}' + '&exit=${clicklabel}', }, diff --git a/extensions/amp-analytics/0.1/vendors/alexametrics.js b/extensions/amp-analytics/0.1/vendors/alexametrics.js index 928bb545d5953..bbd0ac8bb4b42 100644 --- a/extensions/amp-analytics/0.1/vendors/alexametrics.js +++ b/extensions/amp-analytics/0.1/vendors/alexametrics.js @@ -16,8 +16,10 @@ export const ALEXAMETRICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'base': 'https://${ampAtrkHost}/atrk.gif?account=${atrk_acct}&domain=${domain}', - 'pageview': '${base}&jsv=amp-${ampVersion}' + + 'base': + 'https://${ampAtrkHost}/atrk.gif?account=${atrk_acct}&domain=${domain}', + 'pageview': + '${base}&jsv=amp-${ampVersion}' + '&frame_height=${viewportHeight}&frame_width=${viewportWidth}' + '&title=${title}&time=${timestamp}&time_zone_offset=${timezone}' + '&screen_params=${screenWidth}x${screenHeight}x${screenColorDepth}' + diff --git a/extensions/amp-analytics/0.1/vendors/atinternet.js b/extensions/amp-analytics/0.1/vendors/atinternet.js index 83639c49c27fa..a1c7e9b67b285 100644 --- a/extensions/amp-analytics/0.1/vendors/atinternet.js +++ b/extensions/amp-analytics/0.1/vendors/atinternet.js @@ -21,12 +21,12 @@ export const ATINTERNET_CONFIG = /** @type {!JsonObject} */ ({ 'domain': '.xiti.com', }, 'requests': { - 'base': 'https://${log}${domain}/${pixelPath}?s=${site}&ts=${timestamp}&r=${screenWidth}x${screenHeight}x${screenColorDepth}&re=${availableScreenWidth}x${availableScreenHeight}', + 'base': + 'https://${log}${domain}/${pixelPath}?s=${site}&ts=${timestamp}&r=${screenWidth}x${screenHeight}x${screenColorDepth}&re=${availableScreenWidth}x${availableScreenHeight}', 'suffix': '&medium=amp&${extraUrlParams}&ref=${documentReferrer}', - 'pageview': '${base}&' + - 'p=${title}&' + - 's2=${level2}${suffix}', - 'click': '${base}&' + + 'pageview': '${base}&' + 'p=${title}&' + 's2=${level2}${suffix}', + 'click': + '${base}&' + 'pclick=${title}&' + 's2click=${level2}&' + 'p=${label}&' + diff --git a/extensions/amp-analytics/0.1/vendors/baiduanalytics.js b/extensions/amp-analytics/0.1/vendors/baiduanalytics.js index cdac22f0ae26c..8f7e7a3401642 100644 --- a/extensions/amp-analytics/0.1/vendors/baiduanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/baiduanalytics.js @@ -17,11 +17,11 @@ export const BAIDUANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://hm.baidu.com', - 'base': '${host}/hm.gif?' + - 'si=${token}&nv=0&st=4&v=pixel-1.0&rnd=${timestamp}', + 'base': + '${host}/hm.gif?' + 'si=${token}&nv=0&st=4&v=pixel-1.0&rnd=${timestamp}', 'pageview': '${base}&et=0', - 'event': '${base}&ep=${category}*${action}*' + - '${label}*${value}&et=4&api=8_0', + 'event': + '${base}&ep=${category}*${action}*' + '${label}*${value}&et=4&api=8_0', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/bg.js b/extensions/amp-analytics/0.1/vendors/bg.js index 055bf6d107298..2c88980a93da1 100644 --- a/extensions/amp-analytics/0.1/vendors/bg.js +++ b/extensions/amp-analytics/0.1/vendors/bg.js @@ -14,5 +14,4 @@ * limitations under the License. */ -export const BG_CONFIG = /** @type {!JsonObject} */ ({ -}); +export const BG_CONFIG = /** @type {!JsonObject} */ ({}); diff --git a/extensions/amp-analytics/0.1/vendors/burt.js b/extensions/amp-analytics/0.1/vendors/burt.js index 9fda1e7a55894..5355ea5a87ab3 100644 --- a/extensions/amp-analytics/0.1/vendors/burt.js +++ b/extensions/amp-analytics/0.1/vendors/burt.js @@ -22,17 +22,19 @@ export const BURT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': '//${trackingKey}.c.richmetrics.com/', - 'base': '${host}imglog?' + + 'base': + '${host}imglog?' + 'e=${trackingKey}&' + 'pi=${trackingKey}' + - '|${pageViewId}' + - '|${canonicalPath}' + - '|${clientId(burt-amp-user-id)}&' + + '|${pageViewId}' + + '|${canonicalPath}' + + '|${clientId(burt-amp-user-id)}&' + 'ui=${clientId(burt-amp-user-id)}&' + 'v=amp&' + 'ts=${timestamp}&' + 'sn=${requestCount}&', - 'pageview': '${base}' + + 'pageview': + '${base}' + 'type=page&' + 'ca=${category}&' + 'sc=${subCategory}&' + @@ -44,8 +46,7 @@ export const BURT_CONFIG = /** @type {!JsonObject} */ ({ 'sd=${screenWidth}x${screenHeight}&' + 'wd=${availableScreenWidth}x${availableScreenHeight}&' + 'ws=${scrollLeft}x${scrollTop}', - 'pageping': '${base}' + - 'type=pageping', + 'pageping': '${base}' + 'type=pageping', }, 'triggers': { 'pageview': { diff --git a/extensions/amp-analytics/0.1/vendors/byside.js b/extensions/amp-analytics/0.1/vendors/byside.js index 7c2c38e830cf2..7a382b2d89b4c 100644 --- a/extensions/amp-analytics/0.1/vendors/byside.js +++ b/extensions/amp-analytics/0.1/vendors/byside.js @@ -26,8 +26,9 @@ export const BYSIDE_CONFIG = /** @type {!JsonObject} */ ({ 'host': '//${webcareZone}.byside.com/', 'base': '${host}BWA${webcareId}/amp/', 'pageview': '${base}pixel.php', - 'event': '${base}signal.php?event_id=${eventId}' + - '&event_label=${eventLabel}&fields=${fields}', + 'event': + '${base}signal.php?event_id=${eventId}' + + '&event_label=${eventLabel}&fields=${fields}', }, 'extraUrlParams': { 'webcare_id': '${webcareId}', diff --git a/extensions/amp-analytics/0.1/vendors/chartbeat.js b/extensions/amp-analytics/0.1/vendors/chartbeat.js index 9e8d5169f41d6..b1e2e2dfd78e7 100644 --- a/extensions/amp-analytics/0.1/vendors/chartbeat.js +++ b/extensions/amp-analytics/0.1/vendors/chartbeat.js @@ -17,7 +17,8 @@ export const CHARTBEAT_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://ping.chartbeat.net', - 'basePrefix': '/ping?h=${domain}&' + + 'basePrefix': + '/ping?h=${domain}&' + 'p=${canonicalPath}&' + 'u=${clientId(_cb)}&' + 'd=${canonicalHost}&' + diff --git a/extensions/amp-analytics/0.1/vendors/clicky.js b/extensions/amp-analytics/0.1/vendors/clicky.js index 8e582e42c166b..d3b3b1e0fb988 100644 --- a/extensions/amp-analytics/0.1/vendors/clicky.js +++ b/extensions/amp-analytics/0.1/vendors/clicky.js @@ -19,11 +19,10 @@ export const CLICKY_CONFIG = /** @type {!JsonObject} */ ({ 'site_id': '', }, 'requests': { - 'base': 'https://in.getclicky.com/in.php?' + - 'site_id=${site_id}', - 'baseSuffix': '&mime=${contentType}&' + - 'x=${random}', - 'pageview': '${base}&' + + 'base': 'https://in.getclicky.com/in.php?' + 'site_id=${site_id}', + 'baseSuffix': '&mime=${contentType}&' + 'x=${random}', + 'pageview': + '${base}&' + 'res=${screenWidth}x${screenHeight}&' + 'lang=${browserLanguage}&' + 'secure=1&' + @@ -31,9 +30,7 @@ export const CLICKY_CONFIG = /** @type {!JsonObject} */ ({ 'href=${canonicalPath}&' + 'title=${title}' + '${baseSuffix}', - 'interval': '${base}&' + - 'type=ping' + - '${baseSuffix}', + 'interval': '${base}&' + 'type=ping' + '${baseSuffix}', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/colanalytics.js b/extensions/amp-analytics/0.1/vendors/colanalytics.js index dda474f3380f9..a3b065eceb4bd 100644 --- a/extensions/amp-analytics/0.1/vendors/colanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/colanalytics.js @@ -18,7 +18,8 @@ export const COLANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://ase.clmbtech.com', 'base': '${host}/message', - 'pageview': '${base}?cid=${id}' + + 'pageview': + '${base}?cid=${id}' + '&val_101=${id}' + '&val_101=${canonicalPath}' + '&ch=${canonicalHost}' + diff --git a/extensions/amp-analytics/0.1/vendors/comscore.js b/extensions/amp-analytics/0.1/vendors/comscore.js index 67aa0d06b6a82..eb7a94b77bd76 100644 --- a/extensions/amp-analytics/0.1/vendors/comscore.js +++ b/extensions/amp-analytics/0.1/vendors/comscore.js @@ -21,7 +21,8 @@ export const COMSCORE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://sb.scorecardresearch.com', 'base': '${host}/b?', - 'pageview': '${base}c1=2' + + 'pageview': + '${base}c1=2' + '&c2=${c2}' + '&cs_pv=${pageViewId}' + '&c12=${clientId(comScore)}' + diff --git a/extensions/amp-analytics/0.1/vendors/cxense.js b/extensions/amp-analytics/0.1/vendors/cxense.js index f17e12881d1f4..b82c3452d1aab 100644 --- a/extensions/amp-analytics/0.1/vendors/cxense.js +++ b/extensions/amp-analytics/0.1/vendors/cxense.js @@ -18,11 +18,12 @@ export const CXENSE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://scomcluster.cxense.com', 'base': '${host}/Repo/rep.gif', - 'pageview': '${base}?ver=1&typ=pgv&sid=${siteId}&ckp=${clientId(cX_P)}&' + - 'loc=${sourceUrl}&rnd=${random}&ref=${documentReferrer}&' + - 'ltm=${timestamp}&wsz=${screenWidth}x${screenHeight}&' + - 'bln=${browserLanguage}&chs=${documentCharset}&' + - 'col=${screenColorDepth}&tzo=${timezone}&cp_cx_channel=amp', + 'pageview': + '${base}?ver=1&typ=pgv&sid=${siteId}&ckp=${clientId(cX_P)}&' + + 'loc=${sourceUrl}&rnd=${random}&ref=${documentReferrer}&' + + 'ltm=${timestamp}&wsz=${screenWidth}x${screenHeight}&' + + 'bln=${browserLanguage}&chs=${documentCharset}&' + + 'col=${screenColorDepth}&tzo=${timezone}&cp_cx_channel=amp', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/dynatrace.js b/extensions/amp-analytics/0.1/vendors/dynatrace.js index f66d0d8368550..af3d414caf470 100644 --- a/extensions/amp-analytics/0.1/vendors/dynatrace.js +++ b/extensions/amp-analytics/0.1/vendors/dynatrace.js @@ -16,8 +16,10 @@ export const DYNATRACE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'endpoint': '${protocol}://${tenant}${separator}${environment}:${port}/ampbf/${tenantpath}', - 'pageview': '${endpoint}?type=js&' + + 'endpoint': + '${protocol}://${tenant}${separator}${environment}:${port}/ampbf/${tenantpath}', + 'pageview': + '${endpoint}?type=js&' + 'flavor=amp&' + 'v=1&' + 'a=1%7C1%7C_load_%7C_load_%7C-%7C${navTiming(navigationStart)}%7C' + diff --git a/extensions/amp-analytics/0.1/vendors/epica.js b/extensions/amp-analytics/0.1/vendors/epica.js index c682f207ff8de..68c60e24d726c 100644 --- a/extensions/amp-analytics/0.1/vendors/epica.js +++ b/extensions/amp-analytics/0.1/vendors/epica.js @@ -25,7 +25,8 @@ export const EPICA_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://cat.poder.io/api/v1/pixel', - 'base': '?writeKey=${writeKey}' + + 'base': + '?writeKey=${writeKey}' + '&context.library.name=amp' + '&anonymousId=${anonymousId}' + '&context.locale=${browserLanguage}' + diff --git a/extensions/amp-analytics/0.1/vendors/euleriananalytics.js b/extensions/amp-analytics/0.1/vendors/euleriananalytics.js index 07fa6cefc2cbc..e893475640069 100644 --- a/extensions/amp-analytics/0.1/vendors/euleriananalytics.js +++ b/extensions/amp-analytics/0.1/vendors/euleriananalytics.js @@ -21,21 +21,24 @@ export const EULERIANANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://${analyticsHost}', - 'basePrefix': '-/${random}?' + + 'basePrefix': + '-/${random}?' + 'euid-amp=${clientId(etuix)}&' + 'url=${documentLocation}&', - 'pageview': '${base}/col2/${basePrefix}' + + 'pageview': + '${base}/col2/${basePrefix}' + 'rf=${externalReferrer}&' + 'urlp=${pagePath}&' + 'ss=${screenWidth}x${screenHeight}&' + 'sd=${screenColorDepth}', - 'action': '${base}/action/${basePrefix}' + + 'action': + '${base}/action/${basePrefix}' + 'eact=${actionCode}&' + 'actr=${actionRef}', - 'user': '${base}/uparam/${basePrefix}' + - 'euk${userParamKey}=${userParamVal}', - 'contextflag': '${base}/cflag2/${basePrefix}' + - 'ecf0k=${cflagKey}&ecf0v=${cflagVal}', + 'user': + '${base}/uparam/${basePrefix}' + 'euk${userParamKey}=${userParamVal}', + 'contextflag': + '${base}/cflag2/${basePrefix}' + 'ecf0k=${cflagKey}&ecf0v=${cflagVal}', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/facebookpixel.js b/extensions/amp-analytics/0.1/vendors/facebookpixel.js index 1b3103b931f52..074da89fda0ad 100644 --- a/extensions/amp-analytics/0.1/vendors/facebookpixel.js +++ b/extensions/amp-analytics/0.1/vendors/facebookpixel.js @@ -21,73 +21,82 @@ export const FACEBOOKPIXEL_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://www.facebook.com', 'base': '${host}/tr?noscript=1', - 'pageview': '${base}&ev=PageView&' + - 'id=${pixelId}', - 'event': '${base}&ev=${eventName}&' + - 'id=${pixelId}' + - '&cd[content_name]=${content_name}', - 'eventViewContent': '${base}&ev=ViewContent&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_type]=${content_type}' + - '&cd[content_ids]=${content_ids}', - 'eventSearch': '${base}&ev=Search&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_category]=${content_category}' + - '&cd[content_ids]=${content_ids}' + - '&cd[search_string]=${search_string}', - 'eventAddToCart': '${base}&ev=AddToCart&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_type]=${content_type}' + - '&cd[content_ids]=${content_ids}', - 'eventAddToWishlist': '${base}&ev=AddToWishlist&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_category]=${content_category}' + - '&cd[content_ids]=${content_ids}', - 'eventInitiateCheckout': '${base}&ev=InitiateCheckout&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_category]=${content_category}' + - '&cd[num_items]=${num_items}' + - '&cd[content_ids]=${content_ids}', - 'eventAddPaymentInfo': '${base}&ev=AddPaymentInfo&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_category]=${content_category}' + - '&cd[content_ids]=${content_ids}', - 'eventPurchase': '${base}&ev=Purchase&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_type]=${content_type}' + - '&cd[content_ids]=${content_ids}' + - '&cd[num_items]=${num_items}', - 'eventLead': '${base}&ev=Lead&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[content_category]=${content_category}', - 'eventCompleteRegistration': '${base}&ev=CompleteRegistration&' + - 'id=${pixelId}' + - '&cd[value]=${value}' + - '&cd[currency]=${currency}' + - '&cd[content_name]=${content_name}' + - '&cd[status]=${status}', + 'pageview': '${base}&ev=PageView&' + 'id=${pixelId}', + 'event': + '${base}&ev=${eventName}&' + + 'id=${pixelId}' + + '&cd[content_name]=${content_name}', + 'eventViewContent': + '${base}&ev=ViewContent&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_type]=${content_type}' + + '&cd[content_ids]=${content_ids}', + 'eventSearch': + '${base}&ev=Search&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_category]=${content_category}' + + '&cd[content_ids]=${content_ids}' + + '&cd[search_string]=${search_string}', + 'eventAddToCart': + '${base}&ev=AddToCart&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_type]=${content_type}' + + '&cd[content_ids]=${content_ids}', + 'eventAddToWishlist': + '${base}&ev=AddToWishlist&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_category]=${content_category}' + + '&cd[content_ids]=${content_ids}', + 'eventInitiateCheckout': + '${base}&ev=InitiateCheckout&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_category]=${content_category}' + + '&cd[num_items]=${num_items}' + + '&cd[content_ids]=${content_ids}', + 'eventAddPaymentInfo': + '${base}&ev=AddPaymentInfo&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_category]=${content_category}' + + '&cd[content_ids]=${content_ids}', + 'eventPurchase': + '${base}&ev=Purchase&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_type]=${content_type}' + + '&cd[content_ids]=${content_ids}' + + '&cd[num_items]=${num_items}', + 'eventLead': + '${base}&ev=Lead&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[content_category]=${content_category}', + 'eventCompleteRegistration': + '${base}&ev=CompleteRegistration&' + + 'id=${pixelId}' + + '&cd[value]=${value}' + + '&cd[currency]=${currency}' + + '&cd[content_name]=${content_name}' + + '&cd[status]=${status}', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/gemius.js b/extensions/amp-analytics/0.1/vendors/gemius.js index a13a85365d02b..da6a0e84df24d 100644 --- a/extensions/amp-analytics/0.1/vendors/gemius.js +++ b/extensions/amp-analytics/0.1/vendors/gemius.js @@ -19,7 +19,8 @@ export const GEMIUS_CONFIG = /** @type {!JsonObject} */ ({ 'dnt': '0', }, 'requests': { - 'base': 'https://${prefix}.hit.gemius.pl/_${timestamp}/redot.gif?l=91&id=${identifier}&screen=${screenWidth}x${screenHeight}&window=${viewportWidth}x${viewportHeight}&fr=1&href=${sourceUrl}&ref=${documentReferrer}&extra=gemamp%3D1%7Campid%3D${clientId(gemius)}%7C${extraparams}&nc=${dnt}', + 'base': + 'https://${prefix}.hit.gemius.pl/_${timestamp}/redot.gif?l=91&id=${identifier}&screen=${screenWidth}x${screenHeight}&window=${viewportWidth}x${viewportHeight}&fr=1&href=${sourceUrl}&ref=${documentReferrer}&extra=gemamp%3D1%7Campid%3D${clientId(gemius)}%7C${extraparams}&nc=${dnt}', 'pageview': '${base}&et=view&hsrc=1', 'event': '${base}&et=action&hsrc=3', }, diff --git a/extensions/amp-analytics/0.1/vendors/googleadwords.js b/extensions/amp-analytics/0.1/vendors/googleadwords.js index ee193e88093d6..c6b076f3c23ff 100644 --- a/extensions/amp-analytics/0.1/vendors/googleadwords.js +++ b/extensions/amp-analytics/0.1/vendors/googleadwords.js @@ -19,27 +19,29 @@ export const GOOGLEADWORDS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'conversion_prefix': 'https://www.googleadservices.com/pagead/conversion/', 'remarketing_prefix': - 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', - 'common_params': '${googleConversionId}/?' + - 'cv=amp2&' + // Increment when making changes. - 'label=${googleConversionLabel}&' + - 'random=${random}&' + - 'url=${sourceUrl}&' + - 'ref=${documentReferrer}&' + - 'fst=${pageViewId}&' + - 'num=${counter(googleadwords)}&' + - 'fmt=3&' + - 'async=1&' + - 'u_h=${screenHeight}&u_w=${screenWidth}&' + - 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + - 'u_cd=${screenColorDepth}&' + - 'u_tz=${timezone}&' + - 'tiba=${title}&' + - 'guid=ON&script=0', - 'conversion_params': 'value=${googleConversionValue}&' + - 'currency_code=${googleConversionCurrency}&' + - 'bg=${googleConversionColor}&' + - 'hl=${googleConversionLanguage}', + 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', + 'common_params': + '${googleConversionId}/?' + + 'cv=amp2&' + // Increment when making changes. + 'label=${googleConversionLabel}&' + + 'random=${random}&' + + 'url=${sourceUrl}&' + + 'ref=${documentReferrer}&' + + 'fst=${pageViewId}&' + + 'num=${counter(googleadwords)}&' + + 'fmt=3&' + + 'async=1&' + + 'u_h=${screenHeight}&u_w=${screenWidth}&' + + 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + + 'u_cd=${screenColorDepth}&' + + 'u_tz=${timezone}&' + + 'tiba=${title}&' + + 'guid=ON&script=0', + 'conversion_params': + 'value=${googleConversionValue}&' + + 'currency_code=${googleConversionCurrency}&' + + 'bg=${googleConversionColor}&' + + 'hl=${googleConversionLanguage}', 'conversion': '${conversion_prefix}${common_params}&${conversion_params}', 'remarketing': '${remarketing_prefix}${common_params}', }, diff --git a/extensions/amp-analytics/0.1/vendors/googleanalytics.js b/extensions/amp-analytics/0.1/vendors/googleanalytics.js index 078d9697d94b5..e839a42b81c61 100644 --- a/extensions/amp-analytics/0.1/vendors/googleanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/googleanalytics.js @@ -25,59 +25,64 @@ export const GOOGLEANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://www.google-analytics.com', - 'basePrefix': 'v=1&' + - '_v=a1&' + - 'ds=${dataSource}&' + - '${anonymizeIP}&' + - '_s=${requestCount}&' + - 'dt=${title}&' + - 'sr=${screenWidth}x${screenHeight}&' + - '_utmht=${timestamp}&' + - 'cid=${clientId}&' + - 'tid=${account}&' + - 'dl=${documentLocation}&' + - 'dr=${externalReferrer}&' + - 'sd=${screenColorDepth}&' + - 'ul=${browserLanguage}&' + - 'de=${documentCharset}', - 'baseSuffix': '&a=${pageViewId}&' + - 'z=${random}', - 'pageview': '${host}/r/collect?${basePrefix}&' + - 't=pageview&' + - 'jid=${random}&' + - '_r=1' + - '${baseSuffix}', - 'event': '${host}/collect?${basePrefix}&' + - 't=event&' + - 'jid=&' + - 'ec=${eventCategory}&' + - 'ea=${eventAction}&' + - 'el=${eventLabel}&' + - 'ev=${eventValue}' + - '${baseSuffix}', - 'social': '${host}/collect?${basePrefix}&' + - 't=social&' + - 'jid=&' + - 'sa=${socialAction}&' + - 'sn=${socialNetwork}&' + - 'st=${socialTarget}' + - '${baseSuffix}', - 'timing': '${host}/collect?${basePrefix}&' + - 't=${timingRequestType}&' + - 'jid=&' + - 'plt=${pageLoadTime}&' + - 'dns=${domainLookupTime}&' + - 'tcp=${tcpConnectTime}&' + - 'rrt=${redirectTime}&' + - 'srt=${serverResponseTime}&' + - 'pdt=${pageDownloadTime}&' + - 'clt=${contentLoadTime}&' + - 'dit=${domInteractiveTime}' + - '${baseSuffix}', - 'error': '${host}/collect?${basePrefix}&' + - 't=exception&' + - 'exd=${errorParam}' + - '${baseSuffix}', + 'basePrefix': + 'v=1&' + + '_v=a1&' + + 'ds=${dataSource}&' + + '${anonymizeIP}&' + + '_s=${requestCount}&' + + 'dt=${title}&' + + 'sr=${screenWidth}x${screenHeight}&' + + '_utmht=${timestamp}&' + + 'cid=${clientId}&' + + 'tid=${account}&' + + 'dl=${documentLocation}&' + + 'dr=${externalReferrer}&' + + 'sd=${screenColorDepth}&' + + 'ul=${browserLanguage}&' + + 'de=${documentCharset}', + 'baseSuffix': '&a=${pageViewId}&' + 'z=${random}', + 'pageview': + '${host}/r/collect?${basePrefix}&' + + 't=pageview&' + + 'jid=${random}&' + + '_r=1' + + '${baseSuffix}', + 'event': + '${host}/collect?${basePrefix}&' + + 't=event&' + + 'jid=&' + + 'ec=${eventCategory}&' + + 'ea=${eventAction}&' + + 'el=${eventLabel}&' + + 'ev=${eventValue}' + + '${baseSuffix}', + 'social': + '${host}/collect?${basePrefix}&' + + 't=social&' + + 'jid=&' + + 'sa=${socialAction}&' + + 'sn=${socialNetwork}&' + + 'st=${socialTarget}' + + '${baseSuffix}', + 'timing': + '${host}/collect?${basePrefix}&' + + 't=${timingRequestType}&' + + 'jid=&' + + 'plt=${pageLoadTime}&' + + 'dns=${domainLookupTime}&' + + 'tcp=${tcpConnectTime}&' + + 'rrt=${redirectTime}&' + + 'srt=${serverResponseTime}&' + + 'pdt=${pageDownloadTime}&' + + 'clt=${contentLoadTime}&' + + 'dit=${domInteractiveTime}' + + '${baseSuffix}', + 'error': + '${host}/collect?${basePrefix}&' + + 't=exception&' + + 'exd=${errorParam}' + + '${baseSuffix}', }, 'triggers': { 'performanceTiming': { diff --git a/extensions/amp-analytics/0.1/vendors/gtag.js b/extensions/amp-analytics/0.1/vendors/gtag.js index b5f9a65321f27..68084ea1974e5 100644 --- a/extensions/amp-analytics/0.1/vendors/gtag.js +++ b/extensions/amp-analytics/0.1/vendors/gtag.js @@ -40,83 +40,79 @@ export const GTAG_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'uaHost': 'https://www.google-analytics.com', 'uaBasePrefix': - 'v=1&' + - '_v=a1&' + - 'ds=${dataSource}&' + - '${anonymizeIP}&' + - '_s=${requestCount}&' + - 'dt=${title}&' + - 'sr=${screenWidth}x${screenHeight}&' + - 'cid=${clientId}&' + - 'tid=${trackingId}&' + - 'dl=${sourceUrl}&' + - 'dr=${externalReferrer}&' + - 'sd=${screenColorDepth}&' + - 'ul=${browserLanguage}&' + - 'de=${documentCharset}', - 'uaBaseSuffix': - '&a=${pageViewId}&' + - 'z=${random}', + 'v=1&' + + '_v=a1&' + + 'ds=${dataSource}&' + + '${anonymizeIP}&' + + '_s=${requestCount}&' + + 'dt=${title}&' + + 'sr=${screenWidth}x${screenHeight}&' + + 'cid=${clientId}&' + + 'tid=${trackingId}&' + + 'dl=${sourceUrl}&' + + 'dr=${externalReferrer}&' + + 'sd=${screenColorDepth}&' + + 'ul=${browserLanguage}&' + + 'de=${documentCharset}', + 'uaBaseSuffix': '&a=${pageViewId}&' + 'z=${random}', 'uaPageviewCommon': - '&t=pageview&' + - 'jid=${random}&' + - 'gjid=${random}&' + - '_r=1', + '&t=pageview&' + 'jid=${random}&' + 'gjid=${random}&' + '_r=1', 'uaPageview': - '${uaHost}/r/collect?${uaBasePrefix}' + - '${uaPageviewCommon}' + - '${uaBaseSuffix}', + '${uaHost}/r/collect?${uaBasePrefix}' + + '${uaPageviewCommon}' + + '${uaBaseSuffix}', 'uaPageviewNpa': - '${uaHost}/collect?${uaBasePrefix}' + - '${uaPageviewCommon}' + - '${uaBaseSuffix}', + '${uaHost}/collect?${uaBasePrefix}' + + '${uaPageviewCommon}' + + '${uaBaseSuffix}', 'uaEvent': - '${uaHost}/collect?${uaBasePrefix}&' + - 't=event&' + - 'jid=' + - '${uaBaseSuffix}', + '${uaHost}/collect?${uaBasePrefix}&' + + 't=event&' + + 'jid=' + + '${uaBaseSuffix}', 'uaTiming': - '${uaHost}/collect?${uaBasePrefix}&' + - 'jid=&' + - 'plt=${pageLoadTime}&' + - 'dns=${domainLookupTime}&' + - 'tcp=${tcpConnectTime}&' + - 'rrt=${redirectTime}&' + - 'srt=${serverResponseTime}&' + - 'pdt=${pageDownloadTime}&' + - 'clt=${contentLoadTime}&' + - 'dit=${domInteractiveTime}' + - '${uaBaseSuffix}', + '${uaHost}/collect?${uaBasePrefix}&' + + 'jid=&' + + 'plt=${pageLoadTime}&' + + 'dns=${domainLookupTime}&' + + 'tcp=${tcpConnectTime}&' + + 'rrt=${redirectTime}&' + + 'srt=${serverResponseTime}&' + + 'pdt=${pageDownloadTime}&' + + 'clt=${contentLoadTime}&' + + 'dit=${domInteractiveTime}' + + '${uaBaseSuffix}', 'uaError': - '${uaHost}/collect?${uaBasePrefix}&' + - 't=exception&' + - 'exd=${errorParam}' + - '${uaBaseSuffix}', - 'awConversionPrefix': - 'https://www.googleadservices.com/pagead/conversion/', + '${uaHost}/collect?${uaBasePrefix}&' + + 't=exception&' + + 'exd=${errorParam}' + + '${uaBaseSuffix}', + 'awConversionPrefix': 'https://www.googleadservices.com/pagead/conversion/', 'awRemarketingPrefix': - 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', + 'https://googleads.g.doubleclick.net/pagead/viewthroughconversion/', 'awCommonParams': - '${conversionId}/?' + - 'cv=amp3&' + // Increment when making changes. - 'label=${conversionLabel}&' + - 'random=${random}&' + - 'url=${sourceUrl}&' + - 'ref=${documentReferrer}&' + - 'fst=${pageViewId}&' + - 'num=${counter(googleadwords)}&' + - 'fmt=3&' + - 'async=1&' + - 'u_h=${screenHeight}&u_w=${screenWidth}&' + - 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + - 'u_cd=${screenColorDepth}&' + - 'u_tz=${timezone}&' + - 'tiba=${title}&' + - 'guid=ON&script=0', + '${conversionId}/?' + + 'cv=amp3&' + // Increment when making changes. + 'label=${conversionLabel}&' + + 'random=${random}&' + + 'url=${sourceUrl}&' + + 'ref=${documentReferrer}&' + + 'fst=${pageViewId}&' + + 'num=${counter(googleadwords)}&' + + 'fmt=3&' + + 'async=1&' + + 'u_h=${screenHeight}&u_w=${screenWidth}&' + + 'u_ah=${availableScreenHeight}&u_aw=${availableScreenWidth}&' + + 'u_cd=${screenColorDepth}&' + + 'u_tz=${timezone}&' + + 'tiba=${title}&' + + 'guid=ON&script=0', 'awConversion': '${awConversionPrefix}${awCommonParams}', 'awRemarketing': '${awRemarketingPrefix}${awCommonParams}', - 'flBase': 'https://ad.doubleclick.net/activity;src=${flSrc};type=${flType};cat=${flCat}', - 'flDynamicBase': 'https://${flSrc}.fls.doubleclick.net/activityi;src=${flSrc};type=${flType};cat=${flCat}', + 'flBase': + 'https://ad.doubleclick.net/activity;src=${flSrc};type=${flType};cat=${flCat}', + 'flDynamicBase': + 'https://${flSrc}.fls.doubleclick.net/activityi;src=${flSrc};type=${flType};cat=${flCat}', 'dnsBase': 'https://ad.doubleclick.net/ddm/clk/', }, 'transport': { diff --git a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js index 778e8973f352f..a760206169e64 100644 --- a/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/ibeatanalytics.js @@ -18,29 +18,30 @@ export const IBEATANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://ibeat.indiatimes.com', 'base': 'https://ibeat.indiatimes.com/iBeat/pageTrendlogAmp.html', - 'pageview': '${base}?' + - '&h=${h}' + - '&d=${h}' + - '&url=${url}' + - '&k=${key}' + - '&ts=${time}' + - '&ch=${channel}' + - '&sid=${uid}' + - '&at=${agentType}' + - '&ref=${documentReferrer}' + - '&aid=${aid}' + - '&loc=1' + - '&ct=1' + - '&cat=${cat}' + - '&scat=${scat}' + - '&ac=1' + - '&tg=${tags}' + - '&ctids=${catIds}' + - '&pts=${pagePublishTime}' + - '&auth=${author}' + - '&pos=${position}' + - '&iBeatField=${ibeatFields}' + - '&cid=${clientId(MSCSAuthDetails)}', + 'pageview': + '${base}?' + + '&h=${h}' + + '&d=${h}' + + '&url=${url}' + + '&k=${key}' + + '&ts=${time}' + + '&ch=${channel}' + + '&sid=${uid}' + + '&at=${agentType}' + + '&ref=${documentReferrer}' + + '&aid=${aid}' + + '&loc=1' + + '&ct=1' + + '&cat=${cat}' + + '&scat=${scat}' + + '&ac=1' + + '&tg=${tags}' + + '&ctids=${catIds}' + + '&pts=${pagePublishTime}' + + '&auth=${author}' + + '&pos=${position}' + + '&iBeatField=${ibeatFields}' + + '&cid=${clientId(MSCSAuthDetails)}', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/infonline.js b/extensions/amp-analytics/0.1/vendors/infonline.js index 12aed4022f587..3819a7890393e 100644 --- a/extensions/amp-analytics/0.1/vendors/infonline.js +++ b/extensions/amp-analytics/0.1/vendors/infonline.js @@ -21,7 +21,8 @@ export const INFONLINE_CONFIG = /** @type {!JsonObject} */ ({ }, 'transport': {'beacon': false, 'xhrpost': false, 'image': true}, 'requests': { - 'pageview': '${url}?st=${st}' + + 'pageview': + '${url}?st=${st}' + '&sv=${sv}' + '&ap=${ap}' + '&co=${co}' + diff --git a/extensions/amp-analytics/0.1/vendors/iplabel.js b/extensions/amp-analytics/0.1/vendors/iplabel.js index fb718d348e5a3..496a4d59f6e68 100644 --- a/extensions/amp-analytics/0.1/vendors/iplabel.js +++ b/extensions/amp-analytics/0.1/vendors/iplabel.js @@ -18,31 +18,32 @@ export const IPLABEL_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'collectorUrl': 'm.col.ip-label.net', 'endpoint': 'https://${collectorUrl}/coll/', - 'onload': '${endpoint}?' + - 'T=${trackerId}&' + - 'm=' + - '2502|${navTiming(navigationStart)}|' + - '2508|${navTiming(domainLookupStart)}|' + - '2509|${navTiming(domainLookupEnd)}|' + - '2510|${navTiming(connectStart)}|' + - '2512|${navTiming(connectEnd)}|' + - '2514|${navTiming(responseStart)}|' + - '2515|${navTiming(responseEnd)}|' + - '2517|${navTiming(domInteractive)}|' + - '2520|${navTiming(loadEventStart)}' + - '&ts=${timestamp}' + - '&ua=${userAgent}' + - '&d=${ipldim}' + - '&i=${clientip}' + - '&d[1]=${customdim}' + - '&d[2]=${business}' + - '&d[3]=${abtesting}' + - '&d[4]=${infrastructure}' + - '&d[5]=${customer}' + - '&u=${urlgroup}' + - '&w=${availableScreenWidth}&h=${availableScreenHeight}' + - '&r=${documentReferrer}' + - '&l=${browserLanguage}', + 'onload': + '${endpoint}?' + + 'T=${trackerId}&' + + 'm=' + + '2502|${navTiming(navigationStart)}|' + + '2508|${navTiming(domainLookupStart)}|' + + '2509|${navTiming(domainLookupEnd)}|' + + '2510|${navTiming(connectStart)}|' + + '2512|${navTiming(connectEnd)}|' + + '2514|${navTiming(responseStart)}|' + + '2515|${navTiming(responseEnd)}|' + + '2517|${navTiming(domInteractive)}|' + + '2520|${navTiming(loadEventStart)}' + + '&ts=${timestamp}' + + '&ua=${userAgent}' + + '&d=${ipldim}' + + '&i=${clientip}' + + '&d[1]=${customdim}' + + '&d[2]=${business}' + + '&d[3]=${abtesting}' + + '&d[4]=${infrastructure}' + + '&d[5]=${customer}' + + '&u=${urlgroup}' + + '&w=${availableScreenWidth}&h=${availableScreenHeight}' + + '&r=${documentReferrer}' + + '&l=${browserLanguage}', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/krux.js b/extensions/amp-analytics/0.1/vendors/krux.js index 08a412375d507..b5f4733a34d62 100644 --- a/extensions/amp-analytics/0.1/vendors/krux.js +++ b/extensions/amp-analytics/0.1/vendors/krux.js @@ -17,7 +17,8 @@ export const KRUX_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'beaconHost': 'https://beacon.krxd.net', - 'timing': 't_navigation_type=0&' + + 'timing': + 't_navigation_type=0&' + 't_dns=${domainLookupTime}&' + 't_tcp=${tcpConnectTime}&' + 't_http_request=${serverResponseTime}&' + @@ -25,7 +26,8 @@ export const KRUX_CONFIG = /** @type {!JsonObject} */ ({ 't_content_ready=${contentLoadTime}&' + 't_window_load=${pageLoadTime}&' + 't_redirect=${redirectTime}', - 'common': 'source=amp&' + + 'common': + 'source=amp&' + 'confid=${confid}&' + '_kpid=${pubid}&' + '_kcp_s=${site}&' + diff --git a/extensions/amp-analytics/0.1/vendors/linkpulse.js b/extensions/amp-analytics/0.1/vendors/linkpulse.js index 7ff88d0407b56..63071f87c5083 100644 --- a/extensions/amp-analytics/0.1/vendors/linkpulse.js +++ b/extensions/amp-analytics/0.1/vendors/linkpulse.js @@ -27,35 +27,38 @@ export const LINKPULSE_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://${host}', - 'pageview': '${base}/p?i=${id}' + - '&r=${documentReferrer}' + - '&p=${pageUrl}' + - '&s=${section}' + - '&t=${type}' + - '&c=${channel}' + - '&mt=${title}' + - '&_t=amp' + - '&_r=${random}', - 'pageload': '${base}/pl?i=${id}' + - '&ct=${domInteractiveTime}' + - '&rt=${pageDownloadTime}' + - '&pt=${pageLoadTime}' + - '&p=${pageUrl}' + - '&c=${channel}' + - '&t=${type}' + - '&s=${section}' + - '&_t=amp' + - '&_r=${random}', - 'ping': '${base}/u?i=${id}' + - '&u=${clientId(_lp4_u)}' + - '&p=${pageUrl}' + - '&uActive=true' + - '&isPing=yes' + - '&c=${channel}' + - '&t=${type}' + - '&s=${section}' + - '&_t=amp' + - '&_r=${random}', + 'pageview': + '${base}/p?i=${id}' + + '&r=${documentReferrer}' + + '&p=${pageUrl}' + + '&s=${section}' + + '&t=${type}' + + '&c=${channel}' + + '&mt=${title}' + + '&_t=amp' + + '&_r=${random}', + 'pageload': + '${base}/pl?i=${id}' + + '&ct=${domInteractiveTime}' + + '&rt=${pageDownloadTime}' + + '&pt=${pageLoadTime}' + + '&p=${pageUrl}' + + '&c=${channel}' + + '&t=${type}' + + '&s=${section}' + + '&_t=amp' + + '&_r=${random}', + 'ping': + '${base}/u?i=${id}' + + '&u=${clientId(_lp4_u)}' + + '&p=${pageUrl}' + + '&uActive=true' + + '&isPing=yes' + + '&c=${channel}' + + '&t=${type}' + + '&s=${section}' + + '&_t=amp' + + '&_r=${random}', }, 'triggers': { 'pageview': { diff --git a/extensions/amp-analytics/0.1/vendors/marinsoftware.js b/extensions/amp-analytics/0.1/vendors/marinsoftware.js index 1e6cdbfab22f0..ca6cd21b88a1d 100644 --- a/extensions/amp-analytics/0.1/vendors/marinsoftware.js +++ b/extensions/amp-analytics/0.1/vendors/marinsoftware.js @@ -17,26 +17,26 @@ export const MARINSOFTWARE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'base': 'https://tracker.marinsm.com/tp', - 'baseParams': 'cid=${trackerId}' + + 'baseParams': + 'cid=${trackerId}' + '&Version=${ampVersion}' + '&ds=AMP' + '&ref=${externalReferrer}' + '&page=${sourceUrl}' + '&uuid=${clientId(marin_amp_id)}' + '&rnd=${random}', - 'pageView': '${base}?' + - '${baseParams}' + - '&act=1', - 'conversion': '${base}?' + + 'pageView': '${base}?' + '${baseParams}' + '&act=1', + 'conversion': + '${base}?' + '${baseParams}' + '&act=2' + '&trans=UTM:I' + - '|${orderId}' + - '|${marinConversionType}' + - '|${productName}' + - '|${category}' + - '|${price}' + - '|${quantity}', + '|${orderId}' + + '|${marinConversionType}' + + '|${productName}' + + '|${category}' + + '|${price}' + + '|${quantity}', }, 'transport': { 'beacon': true, diff --git a/extensions/amp-analytics/0.1/vendors/mediametrie.js b/extensions/amp-analytics/0.1/vendors/mediametrie.js index 89b4ec89ff2d3..c1ab050e2d689 100644 --- a/extensions/amp-analytics/0.1/vendors/mediametrie.js +++ b/extensions/amp-analytics/0.1/vendors/mediametrie.js @@ -17,7 +17,8 @@ export const MEDIAMETRIE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://prof.estat.com/m/web', - 'pageview': '${host}/${serial}?' + + 'pageview': + '${host}/${serial}?' + 'c=${level1}' + '&dom=${ampdocUrl}' + '&enc=${documentCharset}' + diff --git a/extensions/amp-analytics/0.1/vendors/mediarithmics.js b/extensions/amp-analytics/0.1/vendors/mediarithmics.js index 2eba2e209a9a8..5c61b8e36fa3b 100644 --- a/extensions/amp-analytics/0.1/vendors/mediarithmics.js +++ b/extensions/amp-analytics/0.1/vendors/mediarithmics.js @@ -23,7 +23,8 @@ export const MEDIARITHMICS_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://${domain}', - 'pageview': '${host}/v1/visits/pixel?' + + 'pageview': + '${host}/v1/visits/pixel?' + '$site_token=${site_token}' + '&$url=${url}' + '&$ev=${event_name}' + diff --git a/extensions/amp-analytics/0.1/vendors/mediator.js b/extensions/amp-analytics/0.1/vendors/mediator.js index 66f8a4e44688b..511dfa6ded658 100644 --- a/extensions/amp-analytics/0.1/vendors/mediator.js +++ b/extensions/amp-analytics/0.1/vendors/mediator.js @@ -18,8 +18,8 @@ export const MEDIATOR_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': '//collector.mediator.media/script/${mediator_id}/amp/', 'renderstart': '${host}init/?url=${canonicalUrl}', - 'prefix': '${host}register/?url=${canonicalUrl}' + - '&ref=${documentReferrer}&', + 'prefix': + '${host}register/?url=${canonicalUrl}' + '&ref=${documentReferrer}&', 'suffix': 'vh=${viewportHeight}&sh=${scrollHeight}&st=${scrollTop}', 'pageview': '${prefix}e=v', 'timer': '${prefix}e=t&${suffix}', @@ -43,36 +43,28 @@ export const MEDIATOR_CONFIG = /** @type {!JsonObject} */ ({ 'scrollPing0': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 5, - ], + 'verticalBoundaries': [5], }, 'request': 's0', }, 'scrollPing1': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 35, - ], + 'verticalBoundaries': [35], }, 'request': 's1', }, 'scrollPing2': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 65, - ], + 'verticalBoundaries': [65], }, 'request': 's2', }, 'scrollPing3': { 'on': 'scroll', 'scrollSpec': { - 'verticalBoundaries': [ - 95, - ], + 'verticalBoundaries': [95], }, 'request': 's3', }, diff --git a/extensions/amp-analytics/0.1/vendors/metrika.js b/extensions/amp-analytics/0.1/vendors/metrika.js index 9f86735439f6c..f538ae8d7210a 100644 --- a/extensions/amp-analytics/0.1/vendors/metrika.js +++ b/extensions/amp-analytics/0.1/vendors/metrika.js @@ -18,16 +18,18 @@ export const METRIKA_CONFIG = /** @type {!JsonObject} */ ({ 'transport': {'beacon': true, 'xhrpost': true, 'image': false}, 'requests': { 'pageview': '${_watch}?browser-info=${_brInfo}&${_siteInfo}&${_suffix}', - 'notBounce': '${_watch}?browser-info=ar%3A1%3Anb%3A1%3A${_brInfo}' + - '&${_suffix}', + 'notBounce': + '${_watch}?browser-info=ar%3A1%3Anb%3A1%3A${_brInfo}' + '&${_suffix}', 'externalLink': '${_watch}?browser-info=ln%3A1%3A${_brInfo}&${_suffix}', - 'reachGoal': '${_watch}?browser-info=ar%3A1%3A${_brInfo}&${_siteInfo}' + + 'reachGoal': + '${_watch}?browser-info=ar%3A1%3A${_brInfo}&${_siteInfo}' + '&${_goalSuffix}', '_domain': 'https://mc.yandex.ru', '_watch': '${_domain}/watch/${counterId}', '_suffix': 'page-url=${sourceUrl}&page-ref=${documentReferrer}', - '_goalSuffix': 'page-url=goal%3A%2F%2F${sourceHost}%2F${goalId}' + - '&page-ref=${sourceUrl}', + '_goalSuffix': + 'page-url=goal%3A%2F%2F${sourceHost}%2F${goalId}' + + '&page-ref=${sourceUrl}', '_techInfo': [ 'amp%3A1%3Az%3A${timezone}%3Ai%3A${timestamp}%3Arn%3A${random}', 'la%3A${browserLanguage}%3Aen%3A${documentCharset}', diff --git a/extensions/amp-analytics/0.1/vendors/moat.js b/extensions/amp-analytics/0.1/vendors/moat.js index 72ffbf34bc1c2..b2b3e0bc91cf6 100644 --- a/extensions/amp-analytics/0.1/vendors/moat.js +++ b/extensions/amp-analytics/0.1/vendors/moat.js @@ -19,95 +19,105 @@ export const MOAT_CONFIG = /** @type {!JsonObject} */ ({ 'element': ':root', }, 'requests': { - 'load': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'load', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'acctType': '${acctType}', - 'adType': '${adType}', - 'qs': '${qs}', - 'element': { - 'src': '${htmlAttr(img,src,width)}', - 'viewer': '${viewer}', - }, - 'document': { - 'AMPDocumentHostname': '${ampdocHostname}', - 'AMPDocumentURL': '${ampdocUrl}', - 'canonicalHost': '${canonicalHost}', - 'canonicalHostname': '${canonicalHostname}', - 'canonicalPath': '${canonicalPath}', - 'canonicalURL': '${canonicalUrl}', - 'documentCharset': '${documentCharset}', - 'documentReferrer': '${documentReferrer}', - 'externalReferrer': '${externalReferrer}', - 'sourceURL': '${sourceUrl}', - 'sourceHost': '${sourceHost}', - 'sourceHostname': '${sourceHostname}', - 'sourcePath': '${sourcePath}', - 'title': '${title}', - 'viewer': '${viewer}', - }, - 'device': { - 'availableScreenHeight': '${availableScreenHeight}', - 'availableScreenWidth': '${availableScreenWidth}', - 'browserLanguage': '${browserLanguage}', - 'screenColorDepth': '${screenColorDepth}', - 'screenHeight': '${screenHeight}', - 'screenWidth': '${screenWidth}', - 'scrollHeight': '${scrollHeight}', - 'scrollWidth': '${scrollWidth}', - 'scrollLeft': '${scrollLeft}', - 'scrollTop': '${scrollTop}', - 'timezone': '${timezone}', - 'userAgent': '${userAgent}', + 'load': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'load', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'acctType': '${acctType}', + 'adType': '${adType}', + 'qs': '${qs}', + 'element': { + 'src': '${htmlAttr(img,src,width)}', + 'viewer': '${viewer}', + }, + 'document': { + 'AMPDocumentHostname': '${ampdocHostname}', + 'AMPDocumentURL': '${ampdocUrl}', + 'canonicalHost': '${canonicalHost}', + 'canonicalHostname': '${canonicalHostname}', + 'canonicalPath': '${canonicalPath}', + 'canonicalURL': '${canonicalUrl}', + 'documentCharset': '${documentCharset}', + 'documentReferrer': '${documentReferrer}', + 'externalReferrer': '${externalReferrer}', + 'sourceURL': '${sourceUrl}', + 'sourceHost': '${sourceHost}', + 'sourceHostname': '${sourceHostname}', + 'sourcePath': '${sourcePath}', + 'title': '${title}', + 'viewer': '${viewer}', + }, + 'device': { + 'availableScreenHeight': '${availableScreenHeight}', + 'availableScreenWidth': '${availableScreenWidth}', + 'browserLanguage': '${browserLanguage}', + 'screenColorDepth': '${screenColorDepth}', + 'screenHeight': '${screenHeight}', + 'screenWidth': '${screenWidth}', + 'scrollHeight': '${scrollHeight}', + 'scrollWidth': '${scrollWidth}', + 'scrollLeft': '${scrollLeft}', + 'scrollTop': '${scrollTop}', + 'timezone': '${timezone}', + 'userAgent': '${userAgent}', + 'viewportHeight': '${viewportHeight}', + 'viewportWidth': '${viewportWidth}', + }, + 'requestCount': '${requestCount}', + 'timeStamp': '${timestamp}', + }) + ), + 'unload': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'unload', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'requestCount': '${requestCount}', + 'timeStamp': '${timestamp}', + }) + ), + 'click': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'click', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'requestCount': '${requestCount}', + 'timeStamp': '${timestamp}', + }) + ), + 'viewability': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'viewability', + 'pcode': '${pcode}', + 'l0t': '${l0t}', + 'backgroundState': '${backgroundState}', + 'intersectionRect': '${intersectionRect}', + 'intersectionRatio': '${intersectionRatio}', + 'maxVisiblePercentage': '${maxVisiblePercentage}', + 'minVisiblePercentage': '${minVisiblePercentage}', + 'x': '${elementX}', + 'y': '${elementY}', + 'height': '${elementHeight}', + 'width': '${elementWidth}', 'viewportHeight': '${viewportHeight}', 'viewportWidth': '${viewportWidth}', - }, - 'requestCount': '${requestCount}', - 'timeStamp': '${timestamp}', - })), - 'unload': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'unload', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'requestCount': '${requestCount}', - 'timeStamp': '${timestamp}', - })), - 'click': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'click', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'requestCount': '${requestCount}', - 'timeStamp': '${timestamp}', - })), - 'viewability': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'viewability', - 'pcode': '${pcode}', - 'l0t': '${l0t}', - 'backgroundState': '${backgroundState}', - 'intersectionRect': '${intersectionRect}', - 'intersectionRatio': '${intersectionRatio}', - 'maxVisiblePercentage': '${maxVisiblePercentage}', - 'minVisiblePercentage': '${minVisiblePercentage}', - 'x': '${elementX}', - 'y': '${elementY}', - 'height': '${elementHeight}', - 'width': '${elementWidth}', - 'viewportHeight': '${viewportHeight}', - 'viewportWidth': '${viewportWidth}', - 'opacity': '${opacity}', - 'timeStamp': '${timestamp}', - 'requestCount': '${requestCount}', - })), - 'iframe': JSON.stringify(/** @type {!JsonObject} */ ({ - 'type': 'iframe', - 'pcode': '${pcode}', - 'height': '${elementHeight}', - 'width': '${elementWidth}', - 'x': '${elementX}', - 'y': '${elementY}', - 'requestCount': '${requestCount}', - })), + 'opacity': '${opacity}', + 'timeStamp': '${timestamp}', + 'requestCount': '${requestCount}', + }) + ), + 'iframe': JSON.stringify( + /** @type {!JsonObject} */ ({ + 'type': 'iframe', + 'pcode': '${pcode}', + 'height': '${elementHeight}', + 'width': '${elementWidth}', + 'x': '${elementX}', + 'y': '${elementY}', + 'requestCount': '${requestCount}', + }) + ), }, 'triggers': { 'load': { @@ -131,10 +141,28 @@ export const MOAT_CONFIG = /** @type {!JsonObject} */ ({ 'visibilitySpec': { 'repeat': true, 'visiblePercentageThresholds': [ - [0,0],[0,5],[5,10],[10,15],[15,20],[20,25], - [25,30],[30,35],[35,40],[40,45],[45,50], - [50,55],[55,60],[60,65],[65,70],[70,75], - [75,80],[80,85],[85,90],[90,95],[95,100],[100,100], + [0, 0], + [0, 5], + [5, 10], + [10, 15], + [15, 20], + [20, 25], + [25, 30], + [30, 35], + [35, 40], + [40, 45], + [45, 50], + [50, 55], + [55, 60], + [60, 65], + [65, 70], + [70, 75], + [75, 80], + [80, 85], + [85, 90], + [90, 95], + [95, 100], + [100, 100], ], }, }, @@ -144,7 +172,7 @@ export const MOAT_CONFIG = /** @type {!JsonObject} */ ({ 'request': 'iframe', 'visibilitySpec': { 'repeat': true, - 'visiblePercentageThresholds': [[0,0]], + 'visiblePercentageThresholds': [[0, 0]], }, }, }, diff --git a/extensions/amp-analytics/0.1/vendors/mobify.js b/extensions/amp-analytics/0.1/vendors/mobify.js index b417546f6fa87..717bc272d08d4 100644 --- a/extensions/amp-analytics/0.1/vendors/mobify.js +++ b/extensions/amp-analytics/0.1/vendors/mobify.js @@ -31,18 +31,22 @@ export const MOBIFY_CONFIG = /** @type {!JsonObject} */ ({ '%22referrer%22%3a%22${documentReferrer}%22', '%22templateName%22%3a%22${templateName}%22', ].join('%2c'), - '_basePrefix': '${_host}/s.gif?' + + '_basePrefix': + '${_host}/s.gif?' + 'slug=${projectSlug}&' + 'timestamp_local=${timestamp}&' + 'channel=web&' + 'dimensions=%7b${_dimensions}%7d', - 'ampstart': '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + + 'ampstart': + '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + '%22action%22%3a%22ampStart%22%2c%22value%22' + '%3a${navTiming(navigationStart,domLoading)}%7d', 'pageview': '${_basePrefix}&data=%7b%22action%22%3a%22pageview%22%7d', - 'pageload': '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + + 'pageload': + '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + '%22action%22%3a%22load%22%2c%22value%22%3a${pageLoadTime}%7d', - 'pagedcl': '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + + 'pagedcl': + '${_basePrefix}&data=%7b%22category%22%3a%22timing%22%2c' + '%22action%22%3a%22DOMContentLoaded%22%2c%22value%22' + '%3a${contentLoadTime}%7d', }, diff --git a/extensions/amp-analytics/0.1/vendors/mparticle.js b/extensions/amp-analytics/0.1/vendors/mparticle.js index 6ffe57363a53b..5ad33f93ba51c 100644 --- a/extensions/amp-analytics/0.1/vendors/mparticle.js +++ b/extensions/amp-analytics/0.1/vendors/mparticle.js @@ -23,31 +23,34 @@ export const MPARTICLE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://pixels.mparticle.com', 'endpointPath': '/v1/${apiKey}/Pixel', - 'baseParams': 'et=${eventType}&' + - 'amp_id=${amp_clientId}&' + - 'attrs_k=${eventAttributes_Keys}&' + - 'attrs_v=${eventAttributes_Values}&' + - 'ua_k=${userAttributes_Keys}&' + - 'ua_v=${userAttributes_Values}&' + - 'ui_t=${userIdentities_Types}&' + - 'ui_v=${userIdentities_Values}&' + - 'flags_k=${customFlags_Keys}&' + - 'flags_v=${customFlags_Values}&' + - 'ct=${timestamp}&' + - 'dbg=${debug}&' + - 'lc=${location}&' + - 'av=${appVersion}', - 'pageview': '${host}${endpointPath}?' + - 'dt=ScreenView&' + - 'n=${pageName}&' + - 'hn=${ampdocUrl}&' + - 'ttl=${title}&' + - 'path=${canonicalPath}&' + - '${baseParams}', - 'event': '${host}${endpointPath}?' + - 'dt=AppEvent&' + - 'n=${eventName}&' + - '${baseParams}', + 'baseParams': + 'et=${eventType}&' + + 'amp_id=${amp_clientId}&' + + 'attrs_k=${eventAttributes_Keys}&' + + 'attrs_v=${eventAttributes_Values}&' + + 'ua_k=${userAttributes_Keys}&' + + 'ua_v=${userAttributes_Values}&' + + 'ui_t=${userIdentities_Types}&' + + 'ui_v=${userIdentities_Values}&' + + 'flags_k=${customFlags_Keys}&' + + 'flags_v=${customFlags_Values}&' + + 'ct=${timestamp}&' + + 'dbg=${debug}&' + + 'lc=${location}&' + + 'av=${appVersion}', + 'pageview': + '${host}${endpointPath}?' + + 'dt=ScreenView&' + + 'n=${pageName}&' + + 'hn=${ampdocUrl}&' + + 'ttl=${title}&' + + 'path=${canonicalPath}&' + + '${baseParams}', + 'event': + '${host}${endpointPath}?' + + 'dt=AppEvent&' + + 'n=${eventName}&' + + '${baseParams}', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/mpulse.js b/extensions/amp-analytics/0.1/vendors/mpulse.js index c327613b20721..8dfe7bcae27a0 100644 --- a/extensions/amp-analytics/0.1/vendors/mpulse.js +++ b/extensions/amp-analytics/0.1/vendors/mpulse.js @@ -16,7 +16,8 @@ export const MPULSE_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'onvisible': 'https://${beacon_url}?' + + 'onvisible': + 'https://${beacon_url}?' + 'h.d=${h.d}' + '&h.key=${h.key}' + '&h.t=${h.t}' + diff --git a/extensions/amp-analytics/0.1/vendors/navegg.js b/extensions/amp-analytics/0.1/vendors/navegg.js index 9cdea202e32fd..f2fa45e835011 100644 --- a/extensions/amp-analytics/0.1/vendors/navegg.js +++ b/extensions/amp-analytics/0.1/vendors/navegg.js @@ -18,13 +18,13 @@ export const NAVEGG_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'pageview': 'https://amp.navdmp.com/amp?' + - 'aid=${clientId(navegg_id)}&' + - 'url=${canonicalUrl}&' + - 'ref=${documentReferrer}&' + - 'tit=${title}&' + - 'lan=${browserLanguage}' + - '&acc=${account}&' + - 'v=7', + 'aid=${clientId(navegg_id)}&' + + 'url=${canonicalUrl}&' + + 'ref=${documentReferrer}&' + + 'tit=${title}&' + + 'lan=${browserLanguage}' + + '&acc=${account}&' + + 'v=7', }, 'triggers': { 'trackpageview': { diff --git a/extensions/amp-analytics/0.1/vendors/newrelic.js b/extensions/amp-analytics/0.1/vendors/newrelic.js index 2d7a7839d3bb1..ef879f0f06661 100644 --- a/extensions/amp-analytics/0.1/vendors/newrelic.js +++ b/extensions/amp-analytics/0.1/vendors/newrelic.js @@ -16,12 +16,13 @@ export const NEWRELIC_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'pageview': 'https://${beacon}/amp?appId=${appId}' + + 'pageview': + 'https://${beacon}/amp?appId=${appId}' + '&licenseKey=${licenseKey}' + '&Url=${ampdocUrl}' + '&canonicalUrl=${canonicalUrl}' + '&timeToDomContentLoadedEventEnd=' + - '${navTiming(domContentLoadedEventEnd)}' + + '${navTiming(domContentLoadedEventEnd)}' + '&timeToDomInteractive=${navTiming(domInteractive)}' + '&timeToDomComplete=${navTiming(domComplete)}' + '&timeToDomLoading=${navTiming(domLoading)}' + diff --git a/extensions/amp-analytics/0.1/vendors/nielsen.js b/extensions/amp-analytics/0.1/vendors/nielsen.js index 6596f6d09a266..ed886a95386e1 100644 --- a/extensions/amp-analytics/0.1/vendors/nielsen.js +++ b/extensions/amp-analytics/0.1/vendors/nielsen.js @@ -20,8 +20,10 @@ export const NIELSEN_CONFIG = /** @type {!JsonObject} */ ({ 'prefix': '', }, 'requests': { - 'session': 'https://${prefix}uaid-linkage.imrworldwide.com/cgi-bin/gn?prd=session&c13=asid,P${apid}&sessionId=${sessionId}_${pageViewId}&pingtype=4&enc=false&c61=createtm,${timestamp}&rnd=${random}', - 'cloudapi': 'https://${prefix}cloudapi.imrworldwide.com/nmapi/v2/${apid}/${sessionId}_${pageViewId}/a?b=%7B%22devInfo%22%3A%7B%22devId%22%3A%22${sessionId}_${pageViewId}%22%2C%22apn%22%3A%22${apn}%22%2C%22apv%22%3A%22${apv}%22%2C%22apid%22%3A%22${apid}%22%7D%2C%22metadata%22%3A%7B%22static%22%3A%7B%22type%22%3A%22static%22%2C%22section%22%3A%22${section}%22%2C%22assetid%22%3A%22${pageViewId}%22%2C%22segA%22%3A%22${segA}%22%2C%22segB%22%3A%22${segB}%22%2C%22segC%22%3A%22${segC}%22%2C%22adModel%22%3A%220%22%2C%22dataSrc%22%3A%22cms%22%7D%2C%22content%22%3A%7B%7D%2C%22ad%22%3A%7B%7D%7D%2C%22event%22%3A%22playhead%22%2C%22position%22%3A%22${timestamp}%22%2C%22data%22%3A%7B%22hidden%22%3A%22${backgroundState}%22%2C%22blur%22%3A%22${backgroundState}%22%2C%22position%22%3A%22${timestamp}%22%7D%2C%22type%22%3A%22static%22%2C%22utc%22%3A%22${timestamp}%22%2C%22index%22%3A%22${requestCount}%22%7D', + 'session': + 'https://${prefix}uaid-linkage.imrworldwide.com/cgi-bin/gn?prd=session&c13=asid,P${apid}&sessionId=${sessionId}_${pageViewId}&pingtype=4&enc=false&c61=createtm,${timestamp}&rnd=${random}', + 'cloudapi': + 'https://${prefix}cloudapi.imrworldwide.com/nmapi/v2/${apid}/${sessionId}_${pageViewId}/a?b=%7B%22devInfo%22%3A%7B%22devId%22%3A%22${sessionId}_${pageViewId}%22%2C%22apn%22%3A%22${apn}%22%2C%22apv%22%3A%22${apv}%22%2C%22apid%22%3A%22${apid}%22%7D%2C%22metadata%22%3A%7B%22static%22%3A%7B%22type%22%3A%22static%22%2C%22section%22%3A%22${section}%22%2C%22assetid%22%3A%22${pageViewId}%22%2C%22segA%22%3A%22${segA}%22%2C%22segB%22%3A%22${segB}%22%2C%22segC%22%3A%22${segC}%22%2C%22adModel%22%3A%220%22%2C%22dataSrc%22%3A%22cms%22%7D%2C%22content%22%3A%7B%7D%2C%22ad%22%3A%7B%7D%7D%2C%22event%22%3A%22playhead%22%2C%22position%22%3A%22${timestamp}%22%2C%22data%22%3A%7B%22hidden%22%3A%22${backgroundState}%22%2C%22blur%22%3A%22${backgroundState}%22%2C%22position%22%3A%22${timestamp}%22%7D%2C%22type%22%3A%22static%22%2C%22utc%22%3A%22${timestamp}%22%2C%22index%22%3A%22${requestCount}%22%7D', }, 'triggers': { 'visible': { diff --git a/extensions/amp-analytics/0.1/vendors/oewa.js b/extensions/amp-analytics/0.1/vendors/oewa.js index ddb5233c701a5..d4e0b9c032d60 100644 --- a/extensions/amp-analytics/0.1/vendors/oewa.js +++ b/extensions/amp-analytics/0.1/vendors/oewa.js @@ -17,7 +17,8 @@ export const OEWA_CONFIG = /** @type {!JsonObject} */ ({ 'transport': {'beacon': false, 'xhrpost': false, 'image': true}, 'requests': { - 'pageview': '${url}?s=${s}' + + 'pageview': + '${url}?s=${s}' + '&=1' + '&cp=${cp}' + '&host=${canonicalHost}' + diff --git a/extensions/amp-analytics/0.1/vendors/oewadirect.js b/extensions/amp-analytics/0.1/vendors/oewadirect.js index 1f03e1d139f51..fa8e8596175bc 100644 --- a/extensions/amp-analytics/0.1/vendors/oewadirect.js +++ b/extensions/amp-analytics/0.1/vendors/oewadirect.js @@ -17,7 +17,8 @@ export const OEWADIRECT_CONFIG = /** @type {!JsonObject} */ ({ 'transport': {'beacon': false, 'xhrpost': false, 'image': true}, 'requests': { - 'pageview': 'https://${s}.oewabox.at/j0=,,,r=${canonicalUrl};+,amp=1+cp=${cp}+ssl=1+hn=${canonicalHost};;;?lt=${pageViewId}&x=${screenWidth}x${screenHeight}x24&c=CLIENT_ID(oewa)', + 'pageview': + 'https://${s}.oewabox.at/j0=,,,r=${canonicalUrl};+,amp=1+cp=${cp}+ssl=1+hn=${canonicalHost};;;?lt=${pageViewId}&x=${screenWidth}x${screenHeight}x24&c=CLIENT_ID(oewa)', }, 'triggers': { 'pageview': { diff --git a/extensions/amp-analytics/0.1/vendors/parsely.js b/extensions/amp-analytics/0.1/vendors/parsely.js index b4c0967f93a0c..8ac942be4fbee 100644 --- a/extensions/amp-analytics/0.1/vendors/parsely.js +++ b/extensions/amp-analytics/0.1/vendors/parsely.js @@ -17,21 +17,24 @@ export const PARSELY_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://srv.pixel.parsely.com', - 'basePrefix': '${host}/plogger/?' + + 'basePrefix': + '${host}/plogger/?' + 'rand=${timestamp}&' + 'idsite=${apikey}&' + 'url=${ampdocUrl}&' + 'urlref=${documentReferrer}&' + 'screen=${screenWidth}x${screenHeight}%7C' + - '${availableScreenWidth}x${availableScreenHeight}%7C' + - '${screenColorDepth}&' + + '${availableScreenWidth}x${availableScreenHeight}%7C' + + '${screenColorDepth}&' + 'title=${title}&' + 'date=${timestamp}&' + 'ampid=${clientId(_parsely_visitor)}', - 'pageview': '${basePrefix}&action=pageview&metadata=' + - '{\"canonical_url\":\"${canonicalUrl}\"}', - 'heartbeat': '${basePrefix}&action=heartbeat' + - '&tt=${totalEngagedTime}&inc=${incrementalEngagedTime(parsely-js)}', + 'pageview': + '${basePrefix}&action=pageview&metadata=' + + '{"canonical_url":"${canonicalUrl}"}', + 'heartbeat': + '${basePrefix}&action=heartbeat' + + '&tt=${totalEngagedTime}&inc=${incrementalEngagedTime(parsely-js)}', }, 'triggers': { 'defaultPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/permutive.js b/extensions/amp-analytics/0.1/vendors/permutive.js index 482a0a03243b0..edcb900d1b746 100644 --- a/extensions/amp-analytics/0.1/vendors/permutive.js +++ b/extensions/amp-analytics/0.1/vendors/permutive.js @@ -19,20 +19,18 @@ export const PERMUTIVE_CONFIG = /** @type {!JsonObject} */ ({ 'identity': '${clientId(_ga)}', }, 'requests': { - 'track': 'https://${namespace}.amp.permutive.com/track' + + 'track': + 'https://${namespace}.amp.permutive.com/track' + '?k=${key}' + '&i=${identity}' + '&it=amp', - 'pageview': '${track}' + + 'pageview': + '${track}' + '&e=Pageview' + '&_ep_isp_info=%24ip_isp_info' + '&_ep_geo_info=%24ip_geo_info', - 'engagement': '${track}' + - '&e=PageviewEngagement' + - '&_ep_engaged_time=5', - 'completion': '${track}' + - '&e=PageviewEngagement' + - '&_ep_completion=0.25', + 'engagement': '${track}' + '&e=PageviewEngagement' + '&_ep_engaged_time=5', + 'completion': '${track}' + '&e=PageviewEngagement' + '&_ep_completion=0.25', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/piStats.js b/extensions/amp-analytics/0.1/vendors/piStats.js index 381a5d63f6702..c33d892639221 100644 --- a/extensions/amp-analytics/0.1/vendors/piStats.js +++ b/extensions/amp-analytics/0.1/vendors/piStats.js @@ -17,19 +17,20 @@ export const PISTATS_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://events.pi-stats.com', - 'basePrefix': '${host}/eventsamp/?' + - 'e=PageLoad&' + - 'pid=${property}&' + - 'url=${ampdocUrl}&' + - 'cnt=${cntId}&' + - 'lang=${language}&' + - 'ref=${documentReferrer}&' + - 'id=${clientId(piStatsDEVICEID)}&' + - 'ua=${userAgent}&' + - 'ctype=web&' + - 'blang=${browserLanguage}&' + - 'v=2.0&' + - 'dist=Javascript', + 'basePrefix': + '${host}/eventsamp/?' + + 'e=PageLoad&' + + 'pid=${property}&' + + 'url=${ampdocUrl}&' + + 'cnt=${cntId}&' + + 'lang=${language}&' + + 'ref=${documentReferrer}&' + + 'id=${clientId(piStatsDEVICEID)}&' + + 'ua=${userAgent}&' + + 'ctype=web&' + + 'blang=${browserLanguage}&' + + 'v=2.0&' + + 'dist=Javascript', 'pageview': '${basePrefix}&eventtype=pageview', }, 'triggers': { diff --git a/extensions/amp-analytics/0.1/vendors/piano.js b/extensions/amp-analytics/0.1/vendors/piano.js index 291a605b72ca8..105e310df5f93 100644 --- a/extensions/amp-analytics/0.1/vendors/piano.js +++ b/extensions/amp-analytics/0.1/vendors/piano.js @@ -18,9 +18,11 @@ export const PIANO_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://api-v3.tinypass.com', 'basePrefix': '/api/v3', - 'baseSuffix': '&pageview_id=${pageViewId}&rand=${random}&' + + 'baseSuffix': + '&pageview_id=${pageViewId}&rand=${random}&' + 'amp_client_id=${clientId}&aid=${aid}', - 'pageview': '${host}${basePrefix}/page/track?url=${canonicalUrl}&' + + 'pageview': + '${host}${basePrefix}/page/track?url=${canonicalUrl}&' + 'referer=${documentReferrer}&content_created=${contentCreated}&' + 'content_author=${contentAuthor}&content_section=${contentSection}&' + 'timezone_offset=${timezone}&tags=${tags}&_url=${ampdocUrl}&' + diff --git a/extensions/amp-analytics/0.1/vendors/pinpoll.js b/extensions/amp-analytics/0.1/vendors/pinpoll.js index 030e4b958f70f..0e548ef6dbf06 100644 --- a/extensions/amp-analytics/0.1/vendors/pinpoll.js +++ b/extensions/amp-analytics/0.1/vendors/pinpoll.js @@ -16,7 +16,8 @@ export const PINPOLL_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { - 'pageview': '${protocol}://${host}/${version}?' + + 'pageview': + '${protocol}://${host}/${version}?' + 'url=${sourceUrl}&' + 'sourceHost=${sourceHost}&' + 'sourceHostname=${sourceHostname}&' + diff --git a/extensions/amp-analytics/0.1/vendors/pressboard.js b/extensions/amp-analytics/0.1/vendors/pressboard.js index be0cb359bd97a..1a67b1477c0f3 100644 --- a/extensions/amp-analytics/0.1/vendors/pressboard.js +++ b/extensions/amp-analytics/0.1/vendors/pressboard.js @@ -29,7 +29,8 @@ export const PRESSBOARD_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://adserver.pressboard.ca', - 'common_params': '&=1&url=${canonicalUrl}' + + 'common_params': + '&=1&url=${canonicalUrl}' + '&referrer=${documentReferrer}' + '&ts=${timestamp}' + '&ua=${userAgent}' + @@ -38,7 +39,8 @@ export const PRESSBOARD_CONFIG = /** @type {!JsonObject} */ ({ '&mid=${mediaId}&cid=${campaignId}&sid=${storyRequestId}' + '&geoid=${geoNameId}&cn=${country}&rg=${region}&ct=${city}' + '&dbi=${dbInstance}&tz=${timeZoneOffset}', - 'conversion_params': '&hbt=${requestCount}' + + 'conversion_params': + '&hbt=${requestCount}' + '&pvid=${pageViewId}' + '&asurl=${sourceUrl}' + '&ash=${scrollHeight}' + @@ -47,7 +49,8 @@ export const PRESSBOARD_CONFIG = /** @type {!JsonObject} */ ({ '&avh=${viewportHeight}' + '&ast=${scrollTop}' + '&atet=${totalEngagedTime}', - 'conversion': '${host}' + + 'conversion': + '${host}' + '/track/attention-amp?' + '${common_params}' + '${conversion_params}', diff --git a/extensions/amp-analytics/0.1/vendors/quantcast.js b/extensions/amp-analytics/0.1/vendors/quantcast.js index 5c26ca8746031..178ca4f66fc5e 100644 --- a/extensions/amp-analytics/0.1/vendors/quantcast.js +++ b/extensions/amp-analytics/0.1/vendors/quantcast.js @@ -20,7 +20,8 @@ export const QUANTCAST_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://pixel.quantserve.com/pixel', - 'pageview': '${host};r=${random};a=${pcode};labels=${labels};' + + 'pageview': + '${host};r=${random};a=${pcode};labels=${labels};' + 'fpan=;fpa=${clientId(__qca)};ns=0;ce=1;cm=;je=0;' + 'sr=${screenWidth}x${screenHeight}x${screenColorDepth};' + 'enc=n;et=${timestamp};ref=${documentReferrer};url=${canonicalUrl}', diff --git a/extensions/amp-analytics/0.1/vendors/rakam.js b/extensions/amp-analytics/0.1/vendors/rakam.js index de2da54214104..3738987c2a19c 100644 --- a/extensions/amp-analytics/0.1/vendors/rakam.js +++ b/extensions/amp-analytics/0.1/vendors/rakam.js @@ -19,7 +19,8 @@ export const RAKAM_CONFIG = /** @type {!JsonObject} */ ({ 'deviceId': 'CLIENT_ID(rakam_device_id)', }, 'requests': { - 'base': '?api.api_key=${writeKey}' + + 'base': + '?api.api_key=${writeKey}' + '&prop._platform=amp' + '&prop._device_id=${deviceId}' + '&prop.locale=${browserLanguage}' + @@ -31,7 +32,9 @@ export const RAKAM_CONFIG = /** @type {!JsonObject} */ ({ '&prop.timezone=${timezone}' + '&prop._time=${timestamp}' + '&prop.resolution=${screenWidth} × ${screenHeight}', - 'pageview': 'https://${apiEndpoint}/event/pixel${base}&collection=${pageViewName}', - 'custom': 'https://${apiEndpoint}/event/pixel${base}&collection=${collection}', + 'pageview': + 'https://${apiEndpoint}/event/pixel${base}&collection=${pageViewName}', + 'custom': + 'https://${apiEndpoint}/event/pixel${base}&collection=${collection}', }, }); diff --git a/extensions/amp-analytics/0.1/vendors/reppublika.js b/extensions/amp-analytics/0.1/vendors/reppublika.js index 7a631f0b46865..8fb797407cc21 100644 --- a/extensions/amp-analytics/0.1/vendors/reppublika.js +++ b/extensions/amp-analytics/0.1/vendors/reppublika.js @@ -18,7 +18,8 @@ export const REPPUBLIKA_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://t5.mindtake.com', 'basePrefix': '/tag/cid/', - 'baseSuffix': 'Service=${service}&Category=${category}&' + + 'baseSuffix': + 'Service=${service}&Category=${category}&' + 'Url=${sourceUrl}&Device=${device}&uid=${random}', 'pageview': '${host}${basePrefix}${code}/track.gif?${baseSuffix}', }, diff --git a/extensions/amp-analytics/0.1/vendors/retargetly.js b/extensions/amp-analytics/0.1/vendors/retargetly.js index 399aa93b24d43..581a160df7e08 100644 --- a/extensions/amp-analytics/0.1/vendors/retargetly.js +++ b/extensions/amp-analytics/0.1/vendors/retargetly.js @@ -17,9 +17,10 @@ export const RETARGETLY_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'host': 'https://api.retargetly.com', - 'page': '${host}/api?id=${accountId}&src=${sourceId}&url=${sourceUrl}' + - '&n=${title}&ref=${documentReferrer}&ua=${userAgent}' + - '&random=${random}&bl=${browserLanguage}&source=amp', + 'page': + '${host}/api?id=${accountId}&src=${sourceId}&url=${sourceUrl}' + + '&n=${title}&ref=${documentReferrer}&ua=${userAgent}' + + '&random=${random}&bl=${browserLanguage}&source=amp', }, 'transport': { 'beacon': false, @@ -33,5 +34,3 @@ export const RETARGETLY_CONFIG = /** @type {!JsonObject} */ ({ }, }, }); - - diff --git a/extensions/amp-analytics/0.1/vendors/segment.js b/extensions/amp-analytics/0.1/vendors/segment.js index 49667f4eccce2..c6e3f4bbf1eb3 100644 --- a/extensions/amp-analytics/0.1/vendors/segment.js +++ b/extensions/amp-analytics/0.1/vendors/segment.js @@ -25,7 +25,8 @@ export const SEGMENT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://api.segment.io/v1/pixel', - 'base': '?writeKey=${writeKey}' + + 'base': + '?writeKey=${writeKey}' + '&context.library.name=amp' + '&anonymousId=${anonymousId}' + '&context.locale=${browserLanguage}' + diff --git a/extensions/amp-analytics/0.1/vendors/shinystat.js b/extensions/amp-analytics/0.1/vendors/shinystat.js index 32161d6313b5e..90df67cfceaed 100644 --- a/extensions/amp-analytics/0.1/vendors/shinystat.js +++ b/extensions/amp-analytics/0.1/vendors/shinystat.js @@ -22,19 +22,19 @@ export const SHINYSTAT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://amp.shinystat.com/cgi-bin/shinyamp.cgi', - 'commpar': 'AMP=1&RM=${random}' + - '&USER=${account}' + - '&PAG=${page}' + - '&HR=${sourceUrl}' + - '&REFER=${documentReferrer}' + - '&RES=${screenWidth}X${screenHeight}' + - '&COLOR=${screenColorDepth}' + - '&CID=${clientId(AMP_CID)}' + - '&PAGID=${pageViewId}' + - '&TITL=${title}' + - '&RQC=${requestCount}', - 'pagepar': '&VIE=${viewer}' + - '&PLT=${pageLoadTime}', + 'commpar': + 'AMP=1&RM=${random}' + + '&USER=${account}' + + '&PAG=${page}' + + '&HR=${sourceUrl}' + + '&REFER=${documentReferrer}' + + '&RES=${screenWidth}X${screenHeight}' + + '&COLOR=${screenColorDepth}' + + '&CID=${clientId(AMP_CID)}' + + '&PAGID=${pageViewId}' + + '&TITL=${title}' + + '&RQC=${requestCount}', + 'pagepar': '&VIE=${viewer}' + '&PLT=${pageLoadTime}', 'eventpar': '&SSXL=1', 'linkpar': '&LINK=${outboundLink}', 'pageview': '${base}?${commpar}${pagepar}', diff --git a/extensions/amp-analytics/0.1/vendors/simplereach.js b/extensions/amp-analytics/0.1/vendors/simplereach.js index d33de25b8a303..4ef074339cd62 100644 --- a/extensions/amp-analytics/0.1/vendors/simplereach.js +++ b/extensions/amp-analytics/0.1/vendors/simplereach.js @@ -24,7 +24,8 @@ export const SIMPLEREACH_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://edge.simplereach.com', - 'baseParams': 'amp=true' + + 'baseParams': + 'amp=true' + '&pid=${pid}' + '&title=${title}' + '&url=${canonicalUrl}' + @@ -39,9 +40,7 @@ export const SIMPLEREACH_CONFIG = /** @type {!JsonObject} */ ({ '&article_id=${article_id}' + '&ignore_metadata=${ignore_metadata}', 'visible': '${host}/n?${baseParams}', - 'timer': '${host}/t?${baseParams}' + - '&t=5000' + - '&e=5000', + 'timer': '${host}/t?${baseParams}' + '&t=5000' + '&e=5000', }, 'triggers': { 'visible': { diff --git a/extensions/amp-analytics/0.1/vendors/snowplow.js b/extensions/amp-analytics/0.1/vendors/snowplow.js index 8f04a2ea7c07e..4bc0a669f81a3 100644 --- a/extensions/amp-analytics/0.1/vendors/snowplow.js +++ b/extensions/amp-analytics/0.1/vendors/snowplow.js @@ -20,18 +20,20 @@ export const SNOWPLOW_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'aaVersion': 'amp-0.2', - 'basePrefix': 'https://${collectorHost}/i?url=${canonicalUrl}&page=${title}&' + - 'res=${screenWidth}x${screenHeight}&stm=${timestamp}&' + - 'tz=${timezone}&aid=${appId}&p=web&tv=${aaVersion}&' + - 'cd=${screenColorDepth}&cs=${documentCharset}&' + - 'duid=${duid}&' + - 'lang=${browserLanguage}&refr=${documentReferrer}&stm=${timezone}&' + - 'vp=${viewportWidth}x${viewportHeight}', + 'basePrefix': + 'https://${collectorHost}/i?url=${canonicalUrl}&page=${title}&' + + 'res=${screenWidth}x${screenHeight}&stm=${timestamp}&' + + 'tz=${timezone}&aid=${appId}&p=web&tv=${aaVersion}&' + + 'cd=${screenColorDepth}&cs=${documentCharset}&' + + 'duid=${duid}&' + + 'lang=${browserLanguage}&refr=${documentReferrer}&stm=${timezone}&' + + 'vp=${viewportWidth}x${viewportHeight}', 'pageView': '${basePrefix}&e=pv', - 'structEvent': '${basePrefix}&e=se&' + - 'se_ca=${structEventCategory}&se_ac=${structEventAction}&' + - 'se_la=${structEventLabel}&se_pr=${structEventProperty}&' + - 'se_va=${structEventValue}', + 'structEvent': + '${basePrefix}&e=se&' + + 'se_ca=${structEventCategory}&se_ac=${structEventAction}&' + + 'se_la=${structEventLabel}&se_pr=${structEventProperty}&' + + 'se_va=${structEventValue}', }, 'transport': { 'beacon': false, diff --git a/extensions/amp-analytics/0.1/vendors/teaanalytics.js b/extensions/amp-analytics/0.1/vendors/teaanalytics.js index fb4956b5d1191..18e40a0712870 100644 --- a/extensions/amp-analytics/0.1/vendors/teaanalytics.js +++ b/extensions/amp-analytics/0.1/vendors/teaanalytics.js @@ -22,27 +22,26 @@ export const TEAANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ requests: { domain: 'https://${channel}/v1/amp', commonParams: - 'user.user_unique_id=${userUniqueId}' + - '&header.app_id=${app_id}' + - '&header.language=${browserLanguage}' + - '&header.screen_height=${screenHeight}' + - '&header.screen_width=${screenWidth}' + - '&header.resolution=${screenHeight}x${screenWidth}' + - '&header.tz_offset=${timezone}' + - '&header.tz_name=${timezoneCode}' + - '&header.referrer=${documentReferrer}' + - '&header.custom.user_agent=${userAgent}' + - '&event.local_time_ms=${timestamp}' + - '&event.params._staging_flag=${debug}' + - '&verbose=${debug}', - base: '${domain}?' + - '${commonParams}' + - '&rnd=${random}', - pageview: '${base}' + - '&event=predefine_pageview' + - '&event.params.url=${sourceUrl}' + - '&event.params.url_path=${sourcePath}' + - '&event.params.title=${title}', + 'user.user_unique_id=${userUniqueId}' + + '&header.app_id=${app_id}' + + '&header.language=${browserLanguage}' + + '&header.screen_height=${screenHeight}' + + '&header.screen_width=${screenWidth}' + + '&header.resolution=${screenHeight}x${screenWidth}' + + '&header.tz_offset=${timezone}' + + '&header.tz_name=${timezoneCode}' + + '&header.referrer=${documentReferrer}' + + '&header.custom.user_agent=${userAgent}' + + '&event.local_time_ms=${timestamp}' + + '&event.params._staging_flag=${debug}' + + '&verbose=${debug}', + base: '${domain}?' + '${commonParams}' + '&rnd=${random}', + pageview: + '${base}' + + '&event=predefine_pageview' + + '&event.params.url=${sourceUrl}' + + '&event.params.url_path=${sourcePath}' + + '&event.params.title=${title}', event: '${base}', }, }); diff --git a/extensions/amp-analytics/0.1/vendors/tealiumcollect.js b/extensions/amp-analytics/0.1/vendors/tealiumcollect.js index 2fbe70c217c5a..17b83ee8f7465 100644 --- a/extensions/amp-analytics/0.1/vendors/tealiumcollect.js +++ b/extensions/amp-analytics/0.1/vendors/tealiumcollect.js @@ -28,30 +28,34 @@ export const TEALIUMCOLLECT_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://collect.tealiumiq.com', - 'base': '${host}/event?${tealium}&' + - '${dom1}&${dom2}&${datetime}&' + - 'tealium_event=${tealium_event}&' + - 'amp_version=${ampVersion}&' + - 'amp_request_count=${requestCount}', - 'tealium': 'tealium_account=${account}&' + - 'tealium_profile=${profile}&' + - 'tealium_datasource=${datasource}&' + - 'tealium_visitor_id=${visitor_id}', - 'dom1': 'url=${sourceUrl}&doc_url=${ampdocUrl}&' + - 'domain=${sourceHost}&pathname=${sourcePath}&' + - 'amp_hostname=${ampdocHostname}&' + - 'canonical_hostname=${canonicalHostname}', - 'dom2': 'title=${title}&' + - 'viewport_width=${availableScreenWidth}&' + - 'viewport_height=${availableScreenHeight}', - 'datetime': 'timestamp=${timestamp}&' + - 'tz=${timezone}&lang=${browserLanguage}', - 'pageview': '${base}&referrer=${documentReferrer}&' + - 'screen_size=${screenWidth}x${screenHeight}&' + - 'content_load_ms=${contentLoadTime}&' + - 'page_view_id=${pageViewId}', - 'event': '${base}&' + - 'scroll_y=${scrollTop}&scroll_x=${scrollLeft}', + 'base': + '${host}/event?${tealium}&' + + '${dom1}&${dom2}&${datetime}&' + + 'tealium_event=${tealium_event}&' + + 'amp_version=${ampVersion}&' + + 'amp_request_count=${requestCount}', + 'tealium': + 'tealium_account=${account}&' + + 'tealium_profile=${profile}&' + + 'tealium_datasource=${datasource}&' + + 'tealium_visitor_id=${visitor_id}', + 'dom1': + 'url=${sourceUrl}&doc_url=${ampdocUrl}&' + + 'domain=${sourceHost}&pathname=${sourcePath}&' + + 'amp_hostname=${ampdocHostname}&' + + 'canonical_hostname=${canonicalHostname}', + 'dom2': + 'title=${title}&' + + 'viewport_width=${availableScreenWidth}&' + + 'viewport_height=${availableScreenHeight}', + 'datetime': + 'timestamp=${timestamp}&' + 'tz=${timezone}&lang=${browserLanguage}', + 'pageview': + '${base}&referrer=${documentReferrer}&' + + 'screen_size=${screenWidth}x${screenHeight}&' + + 'content_load_ms=${contentLoadTime}&' + + 'page_view_id=${pageViewId}', + 'event': '${base}&' + 'scroll_y=${scrollTop}&scroll_x=${scrollLeft}', }, 'triggers': { 'defaultPageview': { @@ -63,4 +67,3 @@ export const TEALIUMCOLLECT_CONFIG = /** @type {!JsonObject} */ ({ }, }, }); - diff --git a/extensions/amp-analytics/0.1/vendors/top100.js b/extensions/amp-analytics/0.1/vendors/top100.js index ad01f0d029476..a106c1d8c8f5a 100644 --- a/extensions/amp-analytics/0.1/vendors/top100.js +++ b/extensions/amp-analytics/0.1/vendors/top100.js @@ -23,23 +23,24 @@ export const TOP100_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'host': 'https://kraken.rambler.ru', - 'base': '${host}/cnt/?pid=${pid}' + - '&rid=${rid}' + - '&v=${version}' + - '&rn=${random}' + - '&ruid=${ruid}' + - '&ct=amp', - 'pageview': '${base}&et=pv' + - '${_pageData}' + - '${_screenData}', - '_screenData': '&sr=${screenWidth}x${screenHeight}' + - '&cd=${screenColorDepth}-bit' + - '&bs=${scrollWidth}x${scrollHeight}', - '_pageData': '&pt=${title}' + - '&rf=${documentReferrer}' + - '&en=${documentCharset}' + - '&la=${browserLanguage}' + - '&tz=${timezone}', + 'base': + '${host}/cnt/?pid=${pid}' + + '&rid=${rid}' + + '&v=${version}' + + '&rn=${random}' + + '&ruid=${ruid}' + + '&ct=amp', + 'pageview': '${base}&et=pv' + '${_pageData}' + '${_screenData}', + '_screenData': + '&sr=${screenWidth}x${screenHeight}' + + '&cd=${screenColorDepth}-bit' + + '&bs=${scrollWidth}x${scrollHeight}', + '_pageData': + '&pt=${title}' + + '&rf=${documentReferrer}' + + '&en=${documentCharset}' + + '&la=${browserLanguage}' + + '&tz=${timezone}', }, 'triggers': { 'trackPageview': { diff --git a/extensions/amp-analytics/0.1/vendors/topmailru.js b/extensions/amp-analytics/0.1/vendors/topmailru.js index 995062c0d4476..26da8bc328508 100644 --- a/extensions/amp-analytics/0.1/vendors/topmailru.js +++ b/extensions/amp-analytics/0.1/vendors/topmailru.js @@ -26,17 +26,20 @@ export const TOPMAILRU_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'pageView': '${_domain}/counter?${_basicMessage};title=${title}', - 'reachGoal': '${_domain}/tracker?${_basicMessage};title=${title}' + - ';e=RG%3A${value}%2F${goal}', - 'sendEvent': '${_domain}/tracker?${_basicMessage}' + - ';e=CE%3A${value}%2F${category}%3B${action}%3B${label}', + 'reachGoal': + '${_domain}/tracker?${_basicMessage};title=${title}' + + ';e=RG%3A${value}%2F${goal}', + 'sendEvent': + '${_domain}/tracker?${_basicMessage}' + + ';e=CE%3A${value}%2F${category}%3B${action}%3B${label}', '_domain': 'https://top-fwz1.mail.ru', - '_basicMessage': 'js=13;id=${id};u=${url};r=${referrer}' + - ';s=${screenWidth}*${screenHeight}' + - ';vp=${viewportWidth}*${viewportHeight}' + - ';st=${start};gender=${gender};age=${age}' + - ';pid=${pid};userid=${userid};device=${device}' + - ';params=${params};_=${random}', + '_basicMessage': + 'js=13;id=${id};u=${url};r=${referrer}' + + ';s=${screenWidth}*${screenHeight}' + + ';vp=${viewportWidth}*${viewportHeight}' + + ';st=${start};gender=${gender};age=${age}' + + ';pid=${pid};userid=${userid};device=${device}' + + ';params=${params};_=${random}', }, 'triggers': { 'pageView': { diff --git a/extensions/amp-analytics/0.1/vendors/treasuredata.js b/extensions/amp-analytics/0.1/vendors/treasuredata.js index 4754b3eee9c66..b78d0def0cec8 100644 --- a/extensions/amp-analytics/0.1/vendors/treasuredata.js +++ b/extensions/amp-analytics/0.1/vendors/treasuredata.js @@ -23,7 +23,8 @@ export const TREASUREDATA_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'base': 'https://${host}/postback/v3/event/${database}', - 'baseParams': 'td_write_key=${writeKey}' + + 'baseParams': + 'td_write_key=${writeKey}' + '&td_global_id=td_global_id' + '&td_client_id=CLIENT_ID(_td)' + '&td_charset=DOCUMENT_CHARSET' + diff --git a/extensions/amp-analytics/0.1/vendors/umenganalytics.js b/extensions/amp-analytics/0.1/vendors/umenganalytics.js index b75b084707b32..c12e352a96dc8 100644 --- a/extensions/amp-analytics/0.1/vendors/umenganalytics.js +++ b/extensions/amp-analytics/0.1/vendors/umenganalytics.js @@ -22,16 +22,17 @@ export const UMENGANALYTICS_CONFIG = /** @type {!JsonObject} */ ({ 'eventProps': '', }, 'requests': { - 'base': 'https://b.cnzz.com/utrack?' + - '&_siteid=${siteid}' + - '&_distinct_id=${clientId(umeng_amp_id)}' + - '&_t=${timestamp}' + - '&_s=google' + - '&_b=web' + - '&_r=${externalReferrer}' + - '&_h=${screenHeight}' + - '&_w=${screenWidth}' + - '&_ivt=${initial_view_time}', + 'base': + 'https://b.cnzz.com/utrack?' + + '&_siteid=${siteid}' + + '&_distinct_id=${clientId(umeng_amp_id)}' + + '&_t=${timestamp}' + + '&_s=google' + + '&_b=web' + + '&_r=${externalReferrer}' + + '&_h=${screenHeight}' + + '&_w=${screenWidth}' + + '&_ivt=${initial_view_time}', 'pageview': '${base}&_ename=$w_page_view&_eprops=${eventProps}', 'event': '${base}&_ename=${eventName}&_eprops=${eventProps}', }, diff --git a/extensions/amp-analytics/0.1/vendors/upscore.js b/extensions/amp-analytics/0.1/vendors/upscore.js index e98bac80e80e1..6b2fb1b1b82e5 100644 --- a/extensions/amp-analytics/0.1/vendors/upscore.js +++ b/extensions/amp-analytics/0.1/vendors/upscore.js @@ -14,27 +14,29 @@ * limitations under the License. */ -export const UPSCORE_CONFIG = /**@type {!JsonObject} */({ +export const UPSCORE_CONFIG = /**@type {!JsonObject} */ ({ 'requests': { 'host': 'https://hit-pool.upscore.com/amp?', - 'basePrefix': 'u_id=${clientId(upscore)}&' + - 'hit_id=${pageViewId}&' + - 'scTop=${scrollTop}&' + - 'scHeight=${scrollHeight}&' + - 'vHeight=${viewportHeight}&' + - 'domain=${domain}&' + - 'load=${domInteractiveTime}&' + - 'timespent=${totalEngagedTime}', - 'initialHit': 'author=${author}&' + - 'creator=${creator}&' + - 'o_id=${object_id}&' + - 'o_type=${object_type}&' + - 'pubdate=${pubdate}&' + - 'ref=${documentReferrer}&' + - 'section=${section}&' + - 'url=${ampdocUrl}&' + - 'agent=${userAgent}&' + - 'location=${ampGeo(ISOCountry)}', + 'basePrefix': + 'u_id=${clientId(upscore)}&' + + 'hit_id=${pageViewId}&' + + 'scTop=${scrollTop}&' + + 'scHeight=${scrollHeight}&' + + 'vHeight=${viewportHeight}&' + + 'domain=${domain}&' + + 'load=${domInteractiveTime}&' + + 'timespent=${totalEngagedTime}', + 'initialHit': + 'author=${author}&' + + 'creator=${creator}&' + + 'o_id=${object_id}&' + + 'o_type=${object_type}&' + + 'pubdate=${pubdate}&' + + 'ref=${documentReferrer}&' + + 'section=${section}&' + + 'url=${ampdocUrl}&' + + 'agent=${userAgent}&' + + 'location=${ampGeo(ISOCountry)}', 'finalbeat': '${host}${basePrefix}&type=final', 'heartbeat': '${host}${basePrefix}&type=pulse', 'pageview': '${host}${basePrefix}&${initialHit}&type=init', diff --git a/extensions/amp-analytics/0.1/vendors/webtrekk.js b/extensions/amp-analytics/0.1/vendors/webtrekk.js index 97c0415d79acd..f705ee86bdf57 100644 --- a/extensions/amp-analytics/0.1/vendors/webtrekk.js +++ b/extensions/amp-analytics/0.1/vendors/webtrekk.js @@ -17,30 +17,36 @@ export const WEBTREKK_CONFIG = /** @type {!JsonObject} */ ({ 'requests': { 'trackURL': 'https://${trackDomain}/${trackId}/wt', - 'parameterPrefix': '?p=432,${contentId},1,' + + 'parameterPrefix': + '?p=432,${contentId},1,' + '${screenWidth}x${screenHeight},${screenColorDepth},1,' + '${timestamp},${documentReferrer},${viewportWidth}x' + '${viewportHeight},0&tz=${timezone}' + '&eid=${clientId(amp-wt3-eid)}&la=${browserLanguage}', 'parameterSuffix': '&pu=${sourceUrl}', - 'pageParameter': '&cp1=${pageParameter1}' + + 'pageParameter': + '&cp1=${pageParameter1}' + '&cp2=${pageParameter2}&cp3=${pageParameter3}' + '&cp4=${pageParameter4}&cp5=${pageParameter5}' + '&cp6=${pageParameter6}&cp7=${pageParameter7}' + '&cp8=${pageParameter8}&cp9=${pageParameter9}' + '&cp10=${pageParameter10}', - 'pageCategories': '&cg1=${pageCategory1}' + + 'pageCategories': + '&cg1=${pageCategory1}' + '&cg2=${pageCategory2}&cg3=${pageCategory3}' + '&cg4=${pageCategory4}&cg5=${pageCategory5}' + '&cg6=${pageCategory6}&cg7=${pageCategory7}' + '&cg8=${pageCategory8}&cg9=${pageCategory9}' + '&cg10=${pageCategory10}', - 'pageview': '${trackURL}${parameterPrefix}${pageParameter}' + + 'pageview': + '${trackURL}${parameterPrefix}${pageParameter}' + '${pageCategories}${parameterSuffix}', - 'actionParameter': '&ck1=${actionParameter1}' + + 'actionParameter': + '&ck1=${actionParameter1}' + '&ck2=${actionParameter2}&ck3=${actionParameter3}' + '&ck4=${actionParameter4}&ck5=${actionParameter5}', - 'event': '${trackURL}${parameterPrefix}&ct=${actionName}' + + 'event': + '${trackURL}${parameterPrefix}&ct=${actionName}' + '${actionParameter}${parameterSuffix}', }, 'transport': { diff --git a/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js b/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js index e8d94d41e9c39..86807a2ea92bf 100644 --- a/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js +++ b/extensions/amp-analytics/0.1/vendors/webtrekk_v2.js @@ -23,22 +23,27 @@ export const WEBTREKK_V2_CONFIG = /** @type {!JsonObject} */ ({ }, 'requests': { 'trackURL': 'https://${trackDomain}/${trackId}/wt', - 'basePrefix': '?p=440,${contentId},1,' + + 'basePrefix': + '?p=440,${contentId},1,' + '${screenWidth}x${screenHeight},${screenColorDepth},1,', - 'baseSuffix': ',${documentReferrer},' + + 'baseSuffix': + ',${documentReferrer},' + '${viewportWidth}x${viewportHeight},0' + '&tz=${timezone}&eid=${everId}&la=${browserLanguage}', 'parameterPrefix': '${basePrefix}${timestamp}${baseSuffix}', 'parameterSuffix': '&pu=${sourceUrl}&eor=1', - 'pageview': '${trackURL}${parameterPrefix}&${extraUrlParams}' + + 'pageview': + '${trackURL}${parameterPrefix}&${extraUrlParams}' + '&cp570=${pageLoadTime}${parameterSuffix}', - 'event': '${trackURL}${parameterPrefix}&ct=${actionName}' + + 'event': + '${trackURL}${parameterPrefix}&ct=${actionName}' + '&${extraUrlParams}${parameterSuffix}', - 'scroll': '${trackURL}${parameterPrefix}&ct=${actionName}' + + 'scroll': + '${trackURL}${parameterPrefix}&ct=${actionName}' + '&ck540=${verticalScrollBoundary}${parameterSuffix}', - 'mediaPrefix': '${trackURL}${basePrefix}${baseSuffix}' + - '&mi=${mediaName}', - 'mediaSuffix': '&mt1=${currentTime}&mt2=${duration}' + + 'mediaPrefix': '${trackURL}${basePrefix}${baseSuffix}' + '&mi=${mediaName}', + 'mediaSuffix': + '&mt1=${currentTime}&mt2=${duration}' + '&${extraUrlParams}${parameterSuffix}&x=${playedTotal}', 'mediaPlay': '${mediaPrefix}&mk=play${mediaSuffix}', 'mediaPause': '${mediaPrefix}&mk=pause${mediaSuffix}', diff --git a/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js b/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js index 76dd2301f5d7f..b741ab589c071 100644 --- a/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js +++ b/extensions/amp-analytics/0.1/visibility-manager-for-mapp.js @@ -52,7 +52,8 @@ export class VisibilityManagerForMApp extends VisibilityManager { // Initate the listener this.visibilityInterface_.onVisibilityChange( - this.onVisibilityChangeHandler_.bind(this)); + this.onVisibilityChangeHandler_.bind(this) + ); } /** @override */ @@ -81,23 +82,27 @@ export class VisibilityManagerForMApp extends VisibilityManager { return this.backgroundedAtStart_; } - /** @override */ getRootMinOpacity() { // Copied the implementation from VisibilityManagerForDoc, // doesn't count iframe opacity const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); + root.documentElement || root.body || root + ); return getMinOpacity(rootElement); } /** @override */ listenElement() { // #listenElement not supported in mApp - devAssert(false, '%s: element level visibility not supported, ' + + devAssert( + false, + '%s: element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp', TAG); + 'VisibilityManager for mApp', + TAG + ); return () => {}; } @@ -107,8 +112,9 @@ export class VisibilityManagerForMApp extends VisibilityManager { getRootLayoutBox() { // By the time `#getRootLayoutBox` is called, it is guaranteed that // onVisibilityChangeHandler has been called at least once - return /** @type {!../../../src/layout-rect.LayoutRectDef} */ ( - devAssert(this.intersectionRect_)); + return /** @type {!../../../src/layout-rect.LayoutRectDef} */ (devAssert( + this.intersectionRect_ + )); } /** @@ -132,9 +138,13 @@ export class VisibilityManagerForMApp extends VisibilityManager { * @override */ observe() { - devAssert(false, '%s: element level visibility not supported, ' + + devAssert( + false, + '%s: element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp', TAG); + 'VisibilityManager for mApp', + TAG + ); return () => {}; } @@ -142,9 +152,13 @@ export class VisibilityManagerForMApp extends VisibilityManager { * @override */ getElementVisibility() { - devAssert(false, '%s: element level visibility not supported, ' + + devAssert( + false, + '%s: element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp', TAG); + 'VisibilityManager for mApp', + TAG + ); return 0; } @@ -153,9 +167,12 @@ export class VisibilityManagerForMApp extends VisibilityManager { * @return {?JsonObject} */ getElementIntersectionRect() { - dev().error(TAG, 'element level visibility not supported, ' + + dev().error( + TAG, + 'element level visibility not supported, ' + 'getElementIntersectionRect should not be called in ' + - 'VisibilityManager for mApp'); + 'VisibilityManager for mApp' + ); return dict({}); } } diff --git a/extensions/amp-analytics/0.1/visibility-manager.js b/extensions/amp-analytics/0.1/visibility-manager.js index 7d64ec7fa6ae8..4e3e4ba739f89 100644 --- a/extensions/amp-analytics/0.1/visibility-manager.js +++ b/extensions/amp-analytics/0.1/visibility-manager.js @@ -51,7 +51,6 @@ function getElementId(element) { return id; } - /** * A base class for `VisibilityManagerForDoc` and `VisibilityManagerForEmbed`. * The instance of this class corresponds 1:1 to `AnalyticsRoot`. It represents @@ -178,11 +177,11 @@ export class VisibilityManager { isBackgroundedAtStart() {} /** - * Returns the root's, root's parent's and root's children's - * lowest opacity value - * @return {number} - * @abstract - */ + * Returns the root's, root's parent's and root's children's + * lowest opacity value + * @return {number} + * @abstract + */ getRootMinOpacity() {} /** @@ -252,8 +251,13 @@ export class VisibilityManager { */ listenRoot(spec, readyPromise, createReportPromiseFunc, callback) { const calcVisibility = this.getRootVisibility.bind(this); - return this.createModelAndListen_(calcVisibility, spec, readyPromise, - createReportPromiseFunc, callback); + return this.createModelAndListen_( + calcVisibility, + spec, + readyPromise, + createReportPromiseFunc, + callback + ); } /** @@ -268,10 +272,21 @@ export class VisibilityManager { * @return {!UnlistenDef} */ listenElement( - element, spec, readyPromise, createReportPromiseFunc, callback) { + element, + spec, + readyPromise, + createReportPromiseFunc, + callback + ) { const calcVisibility = this.getElementVisibility.bind(this, element); - return this.createModelAndListen_(calcVisibility, spec, readyPromise, - createReportPromiseFunc, callback, element); + return this.createModelAndListen_( + calcVisibility, + spec, + readyPromise, + createReportPromiseFunc, + callback, + element + ); } /** @@ -284,11 +299,19 @@ export class VisibilityManager { * @param {!Element=} opt_element * @return {!UnlistenDef} */ - createModelAndListen_(calcVisibility, spec, - readyPromise, createReportPromiseFunc, callback, opt_element) { - if (spec['visiblePercentageThresholds'] && - spec['visiblePercentageMin'] == undefined && - spec['visiblePercentageMax'] == undefined) { + createModelAndListen_( + calcVisibility, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ) { + if ( + spec['visiblePercentageThresholds'] && + spec['visiblePercentageMin'] == undefined && + spec['visiblePercentageMax'] == undefined + ) { const unlisteners = []; const ranges = spec['visiblePercentageThresholds']; if (!ranges || !isArray(ranges)) { @@ -298,14 +321,18 @@ export class VisibilityManager { for (let i = 0; i < ranges.length; i++) { const percents = ranges[i]; if (!isArray(percents) || percents.length != 2) { - user().error(TAG, - 'visiblePercentageThresholds entry length is not 2'); + user().error( + TAG, + 'visiblePercentageThresholds entry length is not 2' + ); continue; } if (!isFiniteNumber(percents[0]) || !isFiniteNumber(percents[1])) { // not valid number - user().error(TAG, - 'visiblePercentageThresholds entry is not valid number'); + user().error( + TAG, + 'visiblePercentageThresholds entry is not valid number' + ); continue; } const min = Number(percents[0]); @@ -315,26 +342,46 @@ export class VisibilityManager { // special cases: if min and max are both 0, or both 100, then both // are inclusive. Otherwise it would not be possible to trigger an // event on exactly 0% or 100%. - if (min < 0 || max > 100 || min > max || - (min == max && min != 100 && max != 0)) { - user().error(TAG, - 'visiblePercentageThresholds entry invalid min/max value'); + if ( + min < 0 || + max > 100 || + min > max || + (min == max && min != 100 && max != 0) + ) { + user().error( + TAG, + 'visiblePercentageThresholds entry invalid min/max value' + ); continue; } const newSpec = spec; newSpec['visiblePercentageMin'] = min; newSpec['visiblePercentageMax'] = max; const model = new VisibilityModel(newSpec, calcVisibility); - unlisteners.push(this.listen_(model, spec, readyPromise, - createReportPromiseFunc, callback, opt_element)); + unlisteners.push( + this.listen_( + model, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ) + ); } return () => { unlisteners.forEach(unlistener => unlistener()); }; } const model = new VisibilityModel(spec, calcVisibility); - return this.listen_(model, spec, readyPromise, - createReportPromiseFunc, callback, opt_element); + return this.listen_( + model, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ); } /** @@ -347,8 +394,14 @@ export class VisibilityManager { * @return {!UnlistenDef} * @private */ - listen_(model, spec, - readyPromise, createReportPromiseFunc, callback, opt_element) { + listen_( + model, + spec, + readyPromise, + createReportPromiseFunc, + callback, + opt_element + ) { if (createReportPromiseFunc) { model.setReportReady(createReportPromiseFunc); } @@ -382,19 +435,21 @@ export class VisibilityManager { let layoutBox; if (opt_element) { state['opacity'] = getMinOpacity(opt_element); - const resource = - this.resources_.getResourceForElementOptional(opt_element); - layoutBox = - resource ? - resource.getLayoutBox() : - viewport.getLayoutRect(opt_element); + const resource = this.resources_.getResourceForElementOptional( + opt_element + ); + layoutBox = resource + ? resource.getLayoutBox() + : viewport.getLayoutRect(opt_element); const intersectionRatio = this.getElementVisibility(opt_element); const intersectionRect = this.getElementIntersectionRect(opt_element); - Object.assign(state, dict({ - 'intersectionRatio': intersectionRatio, - 'intersectionRect': JSON.stringify(intersectionRect), - })); - + Object.assign( + state, + dict({ + 'intersectionRatio': intersectionRatio, + 'intersectionRect': JSON.stringify(intersectionRect), + }) + ); } else { state['opacity'] = this.getRootMinOpacity(); state['intersectionRatio'] = this.getRootVisibility(); @@ -403,16 +458,25 @@ export class VisibilityManager { model.maybeDispose(); if (layoutBox) { - Object.assign(state, dict({ - 'elementX': layoutBox.left, - 'elementY': layoutBox.top, - 'elementWidth': layoutBox.width, - 'elementHeight': layoutBox.height, - })); + Object.assign( + state, + dict({ + 'elementX': layoutBox.left, + 'elementY': layoutBox.top, + 'elementWidth': layoutBox.width, + 'elementHeight': layoutBox.height, + }) + ); state['initialScrollDepth'] = layoutPositionRelativeToScrolledViewport( - layoutBox, viewport, model.getInitialScrollDepth()); + layoutBox, + viewport, + model.getInitialScrollDepth() + ); state['maxScrollDepth'] = layoutPositionRelativeToScrolledViewport( - layoutBox, viewport, this.getMaxScrollDepth()); + layoutBox, + viewport, + this.getMaxScrollDepth() + ); } callback(state); }); @@ -466,7 +530,6 @@ export class VisibilityManager { getElementIntersectionRect(unusedElement) {} } - /** * The implementation of `VisibilityManager` for an AMP document. Two * distinct modes are supported: the main AMP doc and a in-a-box doc. @@ -507,20 +570,23 @@ export class VisibilityManagerForDoc extends VisibilityManager { // In-a-box: visibility depends on the InOb. const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); - this.unsubscribe(this.observe( - rootElement, - this.setRootVisibility.bind(this))); + root.documentElement || root.body || root + ); + this.unsubscribe( + this.observe(rootElement, this.setRootVisibility.bind(this)) + ); } else { // Main document: visibility is based on the viewer. this.setRootVisibility(this.viewer_.isVisible() ? 1 : 0); - this.unsubscribe(this.viewer_.onVisibilityChanged(() => { - const isVisible = this.viewer_.isVisible(); - if (!isVisible) { - this.backgrounded_ = true; - } - this.setRootVisibility(isVisible ? 1 : 0); - })); + this.unsubscribe( + this.viewer_.onVisibilityChanged(() => { + const isVisible = this.viewer_.isVisible(); + if (!isVisible) { + this.backgrounded_ = true; + } + this.setRootVisibility(isVisible ? 1 : 0); + }) + ); } } @@ -552,7 +618,8 @@ export class VisibilityManagerForDoc extends VisibilityManager { getRootMinOpacity() { const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); + root.documentElement || root.body || root + ); return getMinOpacity(rootElement); } @@ -561,7 +628,8 @@ export class VisibilityManagerForDoc extends VisibilityManager { // This code is the same for "in-a-box" and standalone doc. const root = this.ampdoc.getRootNode(); const rootElement = dev().assertElement( - root.documentElement || root.body || root); + root.documentElement || root.body || root + ); return this.viewport_.getLayoutRect(rootElement); } @@ -648,14 +716,16 @@ export class VisibilityManagerForDoc extends VisibilityManager { const {win} = this.ampdoc; if (nativeIntersectionObserverSupported(win)) { return new win.IntersectionObserver( - this.onIntersectionChanges_.bind(this), - {threshold: DEFAULT_THRESHOLD}); + this.onIntersectionChanges_.bind(this), + {threshold: DEFAULT_THRESHOLD} + ); } // Polyfill. const intersectionObserverPolyfill = new IntersectionObserverPolyfill( - this.onIntersectionChanges_.bind(this), - {threshold: DEFAULT_THRESHOLD}); + this.onIntersectionChanges_.bind(this), + {threshold: DEFAULT_THRESHOLD} + ); const ticker = () => { intersectionObserverPolyfill.tick(this.viewport_.getRect()); }; @@ -695,14 +765,17 @@ export class VisibilityManagerForDoc extends VisibilityManager { let intersection = change.intersectionRect; // IntersectionRect type now changed from ClientRect to DOMRectReadOnly. // TODO(@zhouyx): Fix all InOb related type. - intersection = layoutRectLtwh(Number(intersection.left), - Number(intersection.top), - Number(intersection.width), - Number(intersection.height)); + intersection = layoutRectLtwh( + Number(intersection.left), + Number(intersection.top), + Number(intersection.width), + Number(intersection.height) + ); this.onIntersectionChange_( - change.target, - change.intersectionRatio, - intersection); + change.target, + change.intersectionRatio, + intersection + ); }); } @@ -726,7 +799,6 @@ export class VisibilityManagerForDoc extends VisibilityManager { } } - /** * The implementation of `VisibilityManager` for a FIE embed. This visibility * root delegates most of tracking functions to its parent, the ampdoc root. @@ -745,9 +817,12 @@ export class VisibilityManagerForEmbed extends VisibilityManager { /** @const @private {boolean} */ this.backgroundedAtStart_ = this.parent.isBackgrounded(); - this.unsubscribe(this.parent.observe( + this.unsubscribe( + this.parent.observe( dev().assertElement(embed.host), - this.setRootVisibility.bind(this))); + this.setRootVisibility.bind(this) + ) + ); } /** @override */ @@ -812,5 +887,4 @@ export class VisibilityManagerForEmbed extends VisibilityManager { } return this.parent.getElementIntersectionRect(element); } - } diff --git a/extensions/amp-analytics/0.1/visibility-model.js b/extensions/amp-analytics/0.1/visibility-model.js index 8b870c5d9eedf..98781ee02aad7 100644 --- a/extensions/amp-analytics/0.1/visibility-model.js +++ b/extensions/amp-analytics/0.1/visibility-model.js @@ -161,8 +161,10 @@ export class VisibilityModel { * @private */ reset_() { - devAssert(!this.eventResolver_, - 'Attempt to refresh visible event before previous one resolve'); + devAssert( + !this.eventResolver_, + 'Attempt to refresh visible event before previous one resolve' + ); const deferred = new Deferred(); this.eventPromise_ = deferred.promise; this.eventResolver_ = deferred.resolve; @@ -320,7 +322,7 @@ export class VisibilityModel { // When ignoreVisibilityForReport_ is true, we update counters but fire the // event when the report ready promise is resolved. const conditionsMet = - this.updateCounters_(visibility) || this.ignoreVisibilityForReport_; + this.updateCounters_(visibility) || this.ignoreVisibilityForReport_; if (conditionsMet) { if (this.scheduledUpdateTimeoutId_) { clearTimeout(this.scheduledUpdateTimeoutId_); @@ -366,8 +368,11 @@ export class VisibilityModel { * @return {boolean} */ isVisibilityMatch_(visibility) { - devAssert(visibility >= 0 && visibility <= 1, - 'invalid visibility value: %s', visibility); + devAssert( + visibility >= 0 && visibility <= 1, + 'invalid visibility value: %s', + visibility + ); // Special case: If visiblePercentageMin is 100%, then it doesn't make // sense to do the usual (min, max] since that would never be true. if (this.spec_['visiblePercentageMin'] == 1) { @@ -378,8 +383,10 @@ export class VisibilityModel { if (this.spec_['visiblePercentageMax'] == 0) { return visibility == 0; } - return visibility > this.spec_['visiblePercentageMin'] && - visibility <= this.spec_['visiblePercentageMax']; + return ( + visibility > this.spec_['visiblePercentageMin'] && + visibility <= this.spec_['visiblePercentageMax'] + ); } /** @@ -388,8 +395,11 @@ export class VisibilityModel { * @private */ updateCounters_(visibility) { - devAssert(visibility >= 0 && visibility <= 1, - 'invalid visibility value: %s', visibility); + devAssert( + visibility >= 0 && visibility <= 1, + 'invalid visibility value: %s', + visibility + ); const now = Date.now(); if (visibility > 0) { @@ -397,14 +407,15 @@ export class VisibilityModel { this.lastSeenTime_ = now; // Consider it as load time visibility if this happens within 300ms of // page load. - if (!this.loadTimeVisibility_ && (now - this.createdTime_) < 300) { + if (!this.loadTimeVisibility_ && now - this.createdTime_ < 300) { this.loadTimeVisibility_ = visibility; } } const prevMatchesVisibility = this.matchesVisibility_; - const timeSinceLastUpdate = - this.lastVisibleUpdateTime_ ? now - this.lastVisibleUpdateTime_ : 0; + const timeSinceLastUpdate = this.lastVisibleUpdateTime_ + ? now - this.lastVisibleUpdateTime_ + : 0; this.matchesVisibility_ = this.isVisibilityMatch_(visibility); if (this.matchesVisibility_) { this.everMatchedVisibility_ = true; @@ -412,8 +423,10 @@ export class VisibilityModel { // Keep counting. this.totalVisibleTime_ += timeSinceLastUpdate; this.continuousTime_ += timeSinceLastUpdate; - this.maxContinuousVisibleTime_ = - Math.max(this.maxContinuousVisibleTime_, this.continuousTime_); + this.maxContinuousVisibleTime_ = Math.max( + this.maxContinuousVisibleTime_, + this.continuousTime_ + ); } else { // The resource came into view: start counting. devAssert(!this.lastVisibleUpdateTime_); @@ -421,19 +434,22 @@ export class VisibilityModel { } this.lastVisibleUpdateTime_ = now; this.minVisiblePercentage_ = - this.minVisiblePercentage_ > 0 ? - Math.min(this.minVisiblePercentage_, visibility) : - visibility; - this.maxVisiblePercentage_ = - Math.max(this.maxVisiblePercentage_, visibility); + this.minVisiblePercentage_ > 0 + ? Math.min(this.minVisiblePercentage_, visibility) + : visibility; + this.maxVisiblePercentage_ = Math.max( + this.maxVisiblePercentage_, + visibility + ); this.lastVisibleTime_ = now; } else if (prevMatchesVisibility) { // The resource went out of view. Do final calculations and reset state. devAssert(this.lastVisibleUpdateTime_ > 0); this.maxContinuousVisibleTime_ = Math.max( - this.maxContinuousVisibleTime_, - this.continuousTime_ + timeSinceLastUpdate); + this.maxContinuousVisibleTime_, + this.continuousTime_ + timeSinceLastUpdate + ); // Reset for next visibility event. this.lastVisibleUpdateTime_ = 0; @@ -442,11 +458,13 @@ export class VisibilityModel { this.lastVisibleTime_ = now; } - return this.everMatchedVisibility_ && - (this.totalVisibleTime_ >= this.spec_['totalTimeMin']) && - (this.totalVisibleTime_ <= this.spec_['totalTimeMax']) && - (this.maxContinuousVisibleTime_ >= this.spec_['continuousTimeMin']) && - (this.maxContinuousVisibleTime_ <= this.spec_['continuousTimeMax']); + return ( + this.everMatchedVisibility_ && + this.totalVisibleTime_ >= this.spec_['totalTimeMin'] && + this.totalVisibleTime_ <= this.spec_['totalTimeMax'] && + this.maxContinuousVisibleTime_ >= this.spec_['continuousTimeMin'] && + this.maxContinuousVisibleTime_ <= this.spec_['continuousTimeMax'] + ); } /** @@ -478,18 +496,22 @@ export class VisibilityModel { */ computeTimeToWait_() { const waitForContinuousTime = Math.max( - this.spec_['continuousTimeMin'] - this.continuousTime_, 0); + this.spec_['continuousTimeMin'] - this.continuousTime_, + 0 + ); const waitForTotalTime = Math.max( - this.spec_['totalTimeMin'] - this.totalVisibleTime_, 0); + this.spec_['totalTimeMin'] - this.totalVisibleTime_, + 0 + ); const maxWaitTime = Math.max(waitForContinuousTime, waitForTotalTime); return Math.min( - maxWaitTime, - waitForContinuousTime || Infinity, - waitForTotalTime || Infinity); + maxWaitTime, + waitForContinuousTime || Infinity, + waitForTotalTime || Infinity + ); } } - /** * Calculates the specified time based on the given `baseTime`. * @param {time} time diff --git a/extensions/amp-anim/0.1/amp-anim.js b/extensions/amp-anim/0.1/amp-anim.js index c189593d48207..4cb901442db83 100644 --- a/extensions/amp-anim/0.1/amp-anim.js +++ b/extensions/amp-anim/0.1/amp-anim.js @@ -21,15 +21,19 @@ import {isLayoutSizeDefined} from '../../../src/layout'; import {propagateObjectFitStyles} from '../../../src/style'; const TAG = 'amp-anim'; -const BUILD_ATTRIBUTES = ['alt', 'aria-label', 'aria-describedby', - 'aria-labelledby']; +const BUILD_ATTRIBUTES = [ + 'alt', + 'aria-label', + 'aria-describedby', + 'aria-labelledby', +]; const LAYOUT_ATTRIBUTES = ['src', 'srcset']; /** @visibleForTesting */ -export const SRC_PLACEHOLDER = 'data:image/gif;base64,' + -'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; +export const SRC_PLACEHOLDER = + 'data:image/gif;base64,' + + 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; export class AmpAnim extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -59,15 +63,16 @@ export class AmpAnim extends AMP.BaseElement { if (this.element.getAttribute('role') == 'img') { this.element.removeAttribute('role'); this.user().error( - 'AMP-ANIM', 'Setting role=img on amp-anim elements ' + + 'AMP-ANIM', + 'Setting role=img on amp-anim elements ' + 'breaks screen readers. Please just set alt or ARIA attributes, ' + 'they will be correctly propagated for the underlying ' + - 'element.'); + 'element.' + ); } // The image is initially hidden if a placeholder is available. - st.toggle(dev().assertElement(this.img_), - !this.getPlaceholder()); + st.toggle(dev().assertElement(this.img_), !this.getPlaceholder()); this.element.appendChild(this.img_); } diff --git a/extensions/amp-anim/0.1/test/test-amp-anim.js b/extensions/amp-anim/0.1/test/test-amp-anim.js index 82e961c1f5472..823a9bb825cbe 100644 --- a/extensions/amp-anim/0.1/test/test-amp-anim.js +++ b/extensions/amp-anim/0.1/test/test-amp-anim.js @@ -19,114 +19,117 @@ import {AmpAnim, SRC_PLACEHOLDER} from '../amp-anim'; const EXAMPLE_SRCSET = `https://media.giphy.com/media/yFQ0ywscgobJK/giphy.gif 1282w, https://media.giphy.com/media/vFKqnCdLPNOKc/giphy.gif 1923w`; -describes.realWin('amp-anim', { - amp: { - ampdoc: 'single', - extensions: ['amp-anim'], +describes.realWin( + 'amp-anim', + { + amp: { + ampdoc: 'single', + extensions: ['amp-anim'], + }, }, -}, env => { - - it('should propagate ARIA attributes', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('srcset', EXAMPLE_SRCSET); - el.setAttribute('width', 100); - el.setAttribute('height', 100); - el.setAttribute('aria-label', 'Hello'); - el.setAttribute('aria-labelledby', 'id2'); - el.setAttribute('aria-describedby', 'id3'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.getAttribute('aria-label')).to.equal('Hello'); - expect(img.getAttribute('aria-labelledby')).to.equal('id2'); - expect(img.getAttribute('aria-describedby')).to.equal('id3'); - expect(img.getAttribute('decoding')).to.equal('async'); - }); - - it('should propagate src and srcset', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('srcset', EXAMPLE_SRCSET); - el.setAttribute('width', 100); - el.setAttribute('height', 100); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.getAttribute('src')).to.equal('test.jpg'); - expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); - }); - - it('should set src to placeholder on unlayout and reset on layout', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('srcset', EXAMPLE_SRCSET); - el.setAttribute('width', 100); - el.setAttribute('height', 100); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.getAttribute('src')).to.equal('test.jpg'); - expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); - - impl.unlayoutCallback(); - expect(img.getAttribute('src')).to.equal(SRC_PLACEHOLDER); - - impl.layoutCallback(); - expect(img.getAttribute('src')).to.equal('test.jpg'); - }); - - it('should propagate the object-fit attribute', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-fit', 'cover'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectFit).to.equal('cover'); - }); - - it('should not propagate the object-fit attribute if invalid', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-fit', 'foo 80%'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectFit).to.be.empty; - }); - - it('should propagate the object-position attribute', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-position', '20% 80%'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectPosition).to.equal('20% 80%'); - }); - - it('should not propagate the object-position attribute if invalid', () => { - const el = env.win.document.createElement('amp-anim'); - el.setAttribute('src', 'test.jpg'); - el.setAttribute('object-position', 'url:("example.com")'); - - const impl = new AmpAnim(el); - impl.buildCallback(); - impl.layoutCallback(); - const img = el.querySelector('img'); - expect(img.style.objectPosition).to.be.empty; - }); -}); + env => { + it('should propagate ARIA attributes', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('srcset', EXAMPLE_SRCSET); + el.setAttribute('width', 100); + el.setAttribute('height', 100); + el.setAttribute('aria-label', 'Hello'); + el.setAttribute('aria-labelledby', 'id2'); + el.setAttribute('aria-describedby', 'id3'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.getAttribute('aria-label')).to.equal('Hello'); + expect(img.getAttribute('aria-labelledby')).to.equal('id2'); + expect(img.getAttribute('aria-describedby')).to.equal('id3'); + expect(img.getAttribute('decoding')).to.equal('async'); + }); + + it('should propagate src and srcset', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('srcset', EXAMPLE_SRCSET); + el.setAttribute('width', 100); + el.setAttribute('height', 100); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.getAttribute('src')).to.equal('test.jpg'); + expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); + }); + + it('should set src to placeholder on unlayout and reset on layout', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('srcset', EXAMPLE_SRCSET); + el.setAttribute('width', 100); + el.setAttribute('height', 100); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.getAttribute('src')).to.equal('test.jpg'); + expect(img.getAttribute('srcset')).to.equal(EXAMPLE_SRCSET); + + impl.unlayoutCallback(); + expect(img.getAttribute('src')).to.equal(SRC_PLACEHOLDER); + + impl.layoutCallback(); + expect(img.getAttribute('src')).to.equal('test.jpg'); + }); + + it('should propagate the object-fit attribute', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-fit', 'cover'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectFit).to.equal('cover'); + }); + + it('should not propagate the object-fit attribute if invalid', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-fit', 'foo 80%'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectFit).to.be.empty; + }); + + it('should propagate the object-position attribute', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-position', '20% 80%'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectPosition).to.equal('20% 80%'); + }); + + it('should not propagate the object-position attribute if invalid', () => { + const el = env.win.document.createElement('amp-anim'); + el.setAttribute('src', 'test.jpg'); + el.setAttribute('object-position', 'url:("example.com")'); + + const impl = new AmpAnim(el); + impl.buildCallback(); + impl.layoutCallback(); + const img = el.querySelector('img'); + expect(img.style.objectPosition).to.be.empty; + }); + } +); diff --git a/extensions/amp-animation/0.1/amp-animation.js b/extensions/amp-animation/0.1/amp-animation.js index 62580045f5ab1..60a3803a7bd97 100644 --- a/extensions/amp-animation/0.1/amp-animation.js +++ b/extensions/amp-animation/0.1/amp-animation.js @@ -23,8 +23,7 @@ import {WebAnimationService} from './web-animation-service'; import {childElementByTag} from '../../../src/dom'; import {clamp} from '../../../src/utils/math'; import {getDetail, listen} from '../../../src/event-helper'; -import {getFriendlyIframeEmbedOptional} - from '../../../src/friendly-iframe-embed'; +import {getFriendlyIframeEmbedOptional} from '../../../src/friendly-iframe-embed'; import {getParentWindowFrameElement} from '../../../src/service'; import {installWebAnimationsIfNecessary} from './web-animations-polyfill'; import {isFiniteNumber} from '../../../src/types'; @@ -34,9 +33,7 @@ import {user, userAssert} from '../../../src/log'; const TAG = 'amp-animation'; - export class AmpAnimation extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -77,24 +74,28 @@ export class AmpAnimation extends AMP.BaseElement { const trigger = this.element.getAttribute('trigger'); if (trigger) { this.triggerOnVisibility_ = userAssert( - trigger == 'visibility', - 'Only allowed value for "trigger" is "visibility": %s', - this.element); + trigger == 'visibility', + 'Only allowed value for "trigger" is "visibility": %s', + this.element + ); } // TODO(dvoytenko): Remove once we support direct parent visibility. if (trigger == 'visibility') { userAssert( - this.element.parentNode == this.element.ownerDocument.body || + this.element.parentNode == this.element.ownerDocument.body || this.element.parentNode == ampdoc.getBody(), - '%s is only allowed as a direct child of element when trigger' - + ' is visibility. This restriction will be removed soon.', TAG); + '%s is only allowed as a direct child of element when trigger' + + ' is visibility. This restriction will be removed soon.', + TAG + ); } // Parse config. const scriptElement = userAssert( - childElementByTag(this.element, 'script'), - '"`; - tag.textContent = JSON.stringify(content); - return tag; - }; + describe('getAllLdJsonTypes', () => { + const createLdJsonTag = content => { + const tag = html` + + `; + tag.textContent = JSON.stringify(content); + return tag; + }; + + it('returns empty', () => { + expect(DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc)).to.be + .empty; + }); - it('returns empty', () => { - expect(DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc)).to.be.empty; + it('returns all found @types', () => { + const expectedA = 'foo'; + const expectedB = 'bar'; + const expectedC = 'baz'; + + mockRootNodeContent([ + html` + + `, + createLdJsonTag({'@type': expectedA}), + createLdJsonTag({'tacos': 'sí por favor'}), + createLdJsonTag({'@type': expectedB}), + createLdJsonTag({'@type': expectedC}), + createLdJsonTag(''), + ]); + + expect( + DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc) + ).to.deep.equal([expectedA, expectedB, expectedC]); + }); }); + }); - it('returns all found @types', () => { - const expectedA = 'foo'; - const expectedB = 'bar'; - const expectedC = 'baz'; - - mockRootNodeContent([ - html``, - createLdJsonTag({'@type': expectedA}), - createLdJsonTag({'tacos': 'sí por favor'}), - createLdJsonTag({'@type': expectedB}), - createLdJsonTag({'@type': expectedC}), - createLdJsonTag(''), - ]); - - expect(DocMetaAnnotations.getAllLdJsonTypes(env.ampdoc)) - .to.deep.equal([ - expectedA, - expectedB, - expectedC, - ]); + describe('by LD+JSON @type', () => { + it('rejects doc with invalid LD+JSON @type', () => { + mockIsProxyOrigin(true); + mockLdJsonSchemaTypes('hamberder'); + expectIsEnabled(false); }); - }); - - }); + ldJsonSchemaTypes.forEach(type => { + const typeSubObj = `{..."@type": "${type}"}`; - describe('by LD+JSON @type', () => { + it(`accepts docs with ${typeSubObj} schema and proxy origin`, () => { + mockLdJsonSchemaTypes(type); + mockIsProxyOrigin(true); + expectIsEnabled(true); + }); - it('rejects doc with invalid LD+JSON @type', () => { - mockIsProxyOrigin(true); - mockLdJsonSchemaTypes('hamberder'); - expectIsEnabled(false); - }); + it(`rejects docs with ${typeSubObj} schema, lightbox explicit`, () => { + const doc = env.win.document; - ldJsonSchemaTypes.forEach(type => { - const typeSubObj = `{..."@type": "${type}"}`; + const extensionScript = createElementWithAttributes(doc, 'script', { + 'custom-element': REQUIRED_EXTENSION, + }); - it(`accepts docs with ${typeSubObj} schema and proxy origin`, () => { - mockLdJsonSchemaTypes(type); - mockIsProxyOrigin(true); - expectIsEnabled(true); - }); + const lightboxable = createElementWithAttributes(doc, 'amp-img', { + [LIGHTBOXABLE_ATTR]: '', + }); - it(`rejects docs with ${typeSubObj} schema, lightbox explicit`, () => { - const doc = env.win.document; + doc.head.appendChild(extensionScript); + doc.body.appendChild(lightboxable); - const extensionScript = createElementWithAttributes(doc, 'script', { - 'custom-element': REQUIRED_EXTENSION, + mockLdJsonSchemaTypes(type); + mockIsProxyOrigin(true); + expectIsEnabled(false); }); - const lightboxable = createElementWithAttributes(doc, 'amp-img', { - [LIGHTBOXABLE_ATTR]: '', + it(`rejects docs with ${typeSubObj} schema, non-proxy origin`, () => { + mockLdJsonSchemaTypes(type); + mockIsProxyOrigin(false); + expectIsEnabled(false); }); - - doc.head.appendChild(extensionScript); - doc.body.appendChild(lightboxable); - - mockLdJsonSchemaTypes(type); - mockIsProxyOrigin(true); - expectIsEnabled(false); }); + }); - it(`rejects docs with ${typeSubObj} schema, non-proxy origin`, () => { - mockLdJsonSchemaTypes(type); - mockIsProxyOrigin(false); + describe('by og:type', () => { + it('rejects doc with invalid ', () => { + mockIsProxyOrigin(true); + mockOgType('cinnamonroll'); expectIsEnabled(false); }); - }); - }); + ogTypes.forEach(type => { + const ogTypeMeta = ``; - describe('by og:type', () => { + it(`accepts docs with ${ogTypeMeta} and proxy origin`, () => { + mockOgType(type); + mockIsProxyOrigin(true); + expectIsEnabled(true); + }); - it('rejects doc with invalid ', () => { - mockIsProxyOrigin(true); - mockOgType('cinnamonroll'); - expectIsEnabled(false); - }); + it(`rejects docs with ${ogTypeMeta}, but lightbox explicit`, () => { + const doc = env.win.document; - ogTypes.forEach(type => { - const ogTypeMeta = ``; + const extensionScript = createElementWithAttributes(doc, 'script', { + 'custom-element': REQUIRED_EXTENSION, + }); - it(`accepts docs with ${ogTypeMeta} and proxy origin`, () => { - mockOgType(type); - mockIsProxyOrigin(true); - expectIsEnabled(true); - }); + const lightboxable = createElementWithAttributes(doc, 'amp-img', { + [LIGHTBOXABLE_ATTR]: '', + }); - it(`rejects docs with ${ogTypeMeta}, but lightbox explicit`, () => { - const doc = env.win.document; + doc.head.appendChild(extensionScript); + doc.body.appendChild(lightboxable); - const extensionScript = createElementWithAttributes(doc, 'script', { - 'custom-element': REQUIRED_EXTENSION, + mockOgType(type); + mockIsProxyOrigin(true); + expectIsEnabled(false); }); - const lightboxable = createElementWithAttributes(doc, 'amp-img', { - [LIGHTBOXABLE_ATTR]: '', + it(`rejects docs with ${ogTypeMeta} for non-proxy origin`, () => { + mockOgType(type); + mockIsProxyOrigin(false); + expectIsEnabled(false); }); - - doc.head.appendChild(extensionScript); - doc.body.appendChild(lightboxable); - - mockOgType(type); - mockIsProxyOrigin(true); - expectIsEnabled(false); }); - - it(`rejects docs with ${ogTypeMeta} for non-proxy origin`, () => { - mockOgType(type); - mockIsProxyOrigin(false); - expectIsEnabled(false); - }); - }); }); - }); - - describe('apply', () => { + describe('apply', () => { + it('sets attribute', async () => { + const element = html` + + `; - it('sets attribute', async() => { - const element = html``; + await apply(env.ampdoc, element); - await apply(env.ampdoc, element); - - expect(element).to.have.attribute(LIGHTBOXABLE_ATTR); - }); + expect(element).to.have.attribute(LIGHTBOXABLE_ATTR); + }); - it('sets unique group for each element', async() => { - const candidates = [1, 2, 3].map(() => html``); + it('sets unique group for each element', async () => { + const candidates = [1, 2, 3].map( + () => + html` + + ` + ); - await Promise.all(candidates.map(c => apply(env.ampdoc, c))); + await Promise.all(candidates.map(c => apply(env.ampdoc, c))); - squaredCompare(candidates, (a, b) => { - expect(a.getAttribute(LIGHTBOXABLE_ATTR)) - .not.to.equal(b.getAttribute(LIGHTBOXABLE_ATTR)); + squaredCompare(candidates, (a, b) => { + expect(a.getAttribute(LIGHTBOXABLE_ATTR)).not.to.equal( + b.getAttribute(LIGHTBOXABLE_ATTR) + ); + }); }); - }); - it('dispatches event', async() => { - const element = html``; + it('dispatches event', async () => { + const element = html` + + `; - element.dispatchCustomEvent = env.sandbox.spy(); + element.dispatchCustomEvent = env.sandbox.spy(); - await apply(env.ampdoc, element); + await apply(env.ampdoc, element); - expect(element.dispatchCustomEvent.withArgs(AutoLightboxEvents.NEWLY_SET)) - .to.have.been.calledOnce; + expect( + element.dispatchCustomEvent.withArgs(AutoLightboxEvents.NEWLY_SET) + ).to.have.been.calledOnce; + }); }); - - }); - -}); + } +); diff --git a/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js b/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js index f468a4f2a2621..b0f4744c12216 100644 --- a/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js +++ b/extensions/amp-auto-lightbox/0.1/test/test-carousel-criteria.js @@ -18,119 +18,177 @@ import {CarouselCriteria} from '../carousel-criteria'; import {htmlFor} from '../../../../src/static-template'; import {toggleExperiment} from '../../../../src/experiments'; - const TAG = 'amp-auto-lightbox'; - -describes.realWin(TAG, { - amp: { - amp: true, - ampdoc: 'single', - experiments: ['amp-auto-lightbox-carousel'], +describes.realWin( + TAG, + { + amp: { + amp: true, + ampdoc: 'single', + experiments: ['amp-auto-lightbox-carousel'], + }, }, -}, env => { + env => { + let html; + + function buildCarousel(slides) { + const element = html` + + `; + slides.forEach(slide => { + slide.classList.add('amp-carousel-slide'); + element.appendChild(slide); + }); + env.win.document.body.appendChild(element); + return element; + } + + beforeEach(() => { + html = htmlFor(env.win.document.body); + toggleExperiment(env.win, 'amp-auto-lightbox-carousel', true); + }); + + it('rejects carousels without ', () => { + const root = buildCarousel([ + html` +
    Slide 1
    + `, + html` +
    Slide 2
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); - let html; + it('rejects carousels with but non-image slides', () => { + const root = buildCarousel([ + html` + + `, + html` + + `, + html` +
    Slide
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); - function buildCarousel(slides) { - const element = html``; - slides.forEach(slide => { - slide.classList.add('amp-carousel-slide'); - element.appendChild(slide); + it('accepts carousels with only ', () => { + const root = buildCarousel([ + html` + + `, + html` + + `, + html` + + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; }); - env.win.document.body.appendChild(element); - return element; - } - beforeEach(() => { - html = htmlFor(env.win.document.body); - toggleExperiment(env.win, 'amp-auto-lightbox-carousel', true); - }); - - it('rejects carousels without ', () => { - const root = buildCarousel([ - html`
    Slide 1
    `, - html`
    Slide 2
    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - - it('rejects carousels with but non-image slides', () => { - const root = buildCarousel([ - html``, - html``, - html`
    Slide
    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - - it('accepts carousels with only ', () => { - const root = buildCarousel([ - html``, - html``, - html``, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; - }); - - it('accepts carousels with only (nested)', () => { - const root = buildCarousel([ - html`
    `, - html`
    `, - html`
    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; - }); - - it('accepts carousels with in every slide (mixed)', () => { - const root = buildCarousel([ - html`
    Hello world!
    `, - html``, - html`
    Hola
    `, - html`

    My Image

    `, - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; - }); - - it('rejects deep trees with only ', () => { - const deep = html`
    - -
    `; - - const root = buildCarousel([ - deep, - deep.cloneNode(/* deep */ true), - deep.cloneNode(/* deep */ true), - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - - it('rejects wide trees with only ', () => { - const wide = html`
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    `; - - const root = buildCarousel([ - wide, - wide.cloneNode(/* deep */ true), - wide.cloneNode(/* deep */ true), - ]); - - expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; - }); - -}); + it('accepts carousels with only (nested)', () => { + const root = buildCarousel([ + html` +
    + `, + html` +
    + `, + html` +
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; + }); + + it('accepts carousels with in every slide (mixed)', () => { + const root = buildCarousel([ + html` +
    Hello world!
    + `, + html` + + `, + html` +
    + +
    Hola
    +
    + `, + html` +
    +

    My Image

    + +
    + `, + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.true; + }); + + it('rejects deep trees with only ', () => { + const deep = html` +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + `; + + const root = buildCarousel([ + deep, + deep.cloneNode(/* deep */ true), + deep.cloneNode(/* deep */ true), + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); + + it('rejects wide trees with only ', () => { + const wide = html` +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + `; + + const root = buildCarousel([ + wide, + wide.cloneNode(/* deep */ true), + wide.cloneNode(/* deep */ true), + ]); + + expect(CarouselCriteria.meetsAll(root)).to.eventually.be.false; + }); + } +); diff --git a/extensions/amp-autocomplete/0.1/amp-autocomplete.js b/extensions/amp-autocomplete/0.1/amp-autocomplete.js index 2df1affd98b3f..dbd7b361f8722 100644 --- a/extensions/amp-autocomplete/0.1/amp-autocomplete.js +++ b/extensions/amp-autocomplete/0.1/amp-autocomplete.js @@ -19,10 +19,11 @@ import {CSS} from '../../../build/amp-autocomplete-0.1.css'; import {Keys} from '../../../src/utils/key-codes'; import {Layout} from '../../../src/layout'; import {Services} from '../../../src/services'; -import {UrlReplacementPolicy, - batchFetchJsonFor} from '../../../src/batched-json'; -import {childElementsByTag, - removeChildren} from '../../../src/dom'; +import { + UrlReplacementPolicy, + batchFetchJsonFor, +} from '../../../src/batched-json'; +import {childElementsByTag, removeChildren} from '../../../src/dom'; import {createCustomEvent} from '../../../src/event-helper'; import {dev, user, userAssert} from '../../../src/log'; import {getValueForExpr, tryParseJson} from '../../../src/json'; @@ -49,7 +50,6 @@ export const FilterType = { }; export class AmpAutocomplete extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -129,47 +129,65 @@ export class AmpAutocomplete extends AMP.BaseElement { /** @override */ buildCallback() { - userAssert(isExperimentOn(this.win, 'amp-autocomplete'), - `Experiment ${EXPERIMENT} is not turned on.`); + userAssert( + isExperimentOn(this.win, 'amp-autocomplete'), + `Experiment ${EXPERIMENT} is not turned on.` + ); this.action_ = Services.actionServiceForDoc(this.element); - const jsonScript = - this.element.querySelector('script[type="application/json"]'); + const jsonScript = this.element.querySelector( + 'script[type="application/json"]' + ); if (jsonScript) { this.sourceData_ = this.getInlineData_(jsonScript); } else if (!this.element.hasAttribute('src')) { - user().warn(TAG, 'Expected a '; - element.build(); + element.build(); - expect(ampState.fetchAndUpdate_).to.not.have.been.called; - expect(ampState.fetch_).to.not.have.been.called; - expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); - }); + expect(ampState.fetchAndUpdate_).to.not.have.been.called; + expect(ampState.fetch_).to.not.have.been.called; + expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); + }); - it('should parse child and fetch `src` if both provided', () => { - element.innerHTML = ''; - element.setAttribute('src', 'https://foo.com/bar?baz=1'); - element.build(); + it('should parse child and fetch `src` if both provided', () => { + element.innerHTML = + ''; + element.setAttribute('src', 'https://foo.com/bar?baz=1'); + element.build(); - // IMPORTANT: No CORS fetch should happen until viewer is visible. - expect(ampState.fetch_).to.not.have.been.called; + // IMPORTANT: No CORS fetch should happen until viewer is visible. + expect(ampState.fetch_).to.not.have.been.called; - whenFirstVisiblePromiseResolve(); - return whenFirstVisiblePromise.then(() => { - expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); - expect(ampState.fetchAndUpdate_).calledWithExactly(/* isInit */ true); - return getViewerAuthTokenIfAvailableStub(); - }).then(() => { - return ampState.fetch_(); - }).then(() => { - expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + whenFirstVisiblePromiseResolve(); + return whenFirstVisiblePromise + .then(() => { + expect(ampState.updateState_).calledWithMatch({foo: 'bar'}); + expect(ampState.fetchAndUpdate_).calledWithExactly(/* isInit */ true); + return getViewerAuthTokenIfAvailableStub(); + }) + .then(() => { + return ampState.fetch_(); + }) + .then(() => { + expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + }); }); - }); - it('should fetch json if `src` is mutated', () => { - sandbox.stub(viewer, 'hasBeenVisible').returns(false); + it('should fetch json if `src` is mutated', () => { + sandbox.stub(viewer, 'hasBeenVisible').returns(false); - element.setAttribute('src', 'https://foo.com/bar?baz=1'); - element.build(); + element.setAttribute('src', 'https://foo.com/bar?baz=1'); + element.build(); - // IMPORTANT: No CORS fetch should happen until viewer is visible. - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; + // IMPORTANT: No CORS fetch should happen until viewer is visible. + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; - allowConsoleError(() => { - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); - }); + allowConsoleError(() => { + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + }); - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; - viewer.hasBeenVisible.returns(true); - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + viewer.hasBeenVisible.returns(true); + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); - expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; - expect(ampState.fetch_).to.not.have.been.called; + expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; + expect(ampState.fetch_).to.not.have.been.called; - whenFirstVisiblePromiseResolve(); - return whenFirstVisiblePromise + whenFirstVisiblePromiseResolve(); + return whenFirstVisiblePromise .then(() => getViewerAuthTokenIfAvailableStub()) .then(() => ampState.fetch_()) .then(() => { expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); }); - }); - - it('should fetch with auth token if `crossorigin` attribute exists' - + ' with `amp-viewer-auth-token-via-post`', () => { - sandbox.stub(viewer, 'hasBeenVisible').returns(false); - getViewerAuthTokenIfAvailableStub.returns(Promise.resolve('idToken')); - - element.setAttribute('src', 'https://foo.com/bar?baz=1'); - element.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); - element.build(); - - // IMPORTANT: No CORS fetch should happen until viewer is visible. - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; - - allowConsoleError(() => { - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); }); - expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; - expect(ampState.fetch_).to.not.have.been.called; + it( + 'should fetch with auth token if `crossorigin` attribute exists' + + ' with `amp-viewer-auth-token-via-post`', + () => { + sandbox.stub(viewer, 'hasBeenVisible').returns(false); + getViewerAuthTokenIfAvailableStub.returns(Promise.resolve('idToken')); - viewer.hasBeenVisible.returns(true); - element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + element.setAttribute('src', 'https://foo.com/bar?baz=1'); + element.setAttribute('crossorigin', 'amp-viewer-auth-token-via-post'); + element.build(); - expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; - expect(ampState.fetch_).to.not.have.been.called; + // IMPORTANT: No CORS fetch should happen until viewer is visible. + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; - whenFirstVisiblePromiseResolve(); - return whenFirstVisiblePromise - .then(() => ampState.prepareAndSendFetch_({win}, element)) - .then(() => { - expect(fetchStub).to.have.been.called; - expect(fetchStub.firstCall.args.slice(-1).pop()) - .to.be.equal('idToken'); - expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + allowConsoleError(() => { + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); }); - }); -}); + + expect(ampState.fetchAndUpdate_).to.have.been.calledOnce; + expect(ampState.fetch_).to.not.have.been.called; + + viewer.hasBeenVisible.returns(true); + element.mutatedAttributesCallback({src: 'https://foo.com/bar?baz=1'}); + + expect(ampState.fetchAndUpdate_).to.have.been.calledTwice; + expect(ampState.fetch_).to.not.have.been.called; + + whenFirstVisiblePromiseResolve(); + return whenFirstVisiblePromise + .then(() => ampState.prepareAndSendFetch_({win}, element)) + .then(() => { + expect(fetchStub).to.have.been.called; + expect(fetchStub.firstCall.args.slice(-1).pop()).to.be.equal( + 'idToken' + ); + expect(ampState.updateState_).calledWithMatch({baz: 'qux'}); + }); + } + ); + } +); diff --git a/extensions/amp-bind/0.1/test/test-bind-evaluator.js b/extensions/amp-bind/0.1/test/test-bind-evaluator.js index dd910fe7ab1ea..990ef1c38a2f9 100644 --- a/extensions/amp-bind/0.1/test/test-bind-evaluator.js +++ b/extensions/amp-bind/0.1/test/test-bind-evaluator.js @@ -42,33 +42,41 @@ describe('BindEvaluator', () => { it('should allow callers to add bindings multiple times', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + ]); expect(numberOfBindings()).to.equal(1); - evaluator.addBindings([{ - tagName: 'SPAN', - property: 'text', - expressionString: 'oneplusone + 3', - }]); + evaluator.addBindings([ + { + tagName: 'SPAN', + property: 'text', + expressionString: 'oneplusone + 3', + }, + ]); expect(numberOfBindings()).to.equal(2); }); it('should allow callers to remove bindings', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + ]); expect(numberOfBindings()).to.equal(1); - evaluator.addBindings([{ - tagName: 'SPAN', - property: 'text', - expressionString: 'oneplusone + 3', - }]); + evaluator.addBindings([ + { + tagName: 'SPAN', + property: 'text', + expressionString: 'oneplusone + 3', + }, + ]); expect(numberOfBindings()).to.equal(2); evaluator.removeBindingsWithExpressionStrings(['oneplusone + 2']); expect(numberOfBindings()).to.equal(1); @@ -77,15 +85,18 @@ describe('BindEvaluator', () => { }); it('should only evaluate duplicate expressions once', () => { - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: '1+1', - }, { - tagName: 'DIV', - property: 'text', - expressionString: '1+1', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: '1+1', + }, + { + tagName: 'DIV', + property: 'text', + expressionString: '1+1', + }, + ]); const stub = sandbox.stub(BindExpression.prototype, 'evaluate'); stub.returns('stubbed'); evaluator.evaluateBindings({}); @@ -94,15 +105,18 @@ describe('BindEvaluator', () => { it('should clean up removed expressions from its cache', () => { expect(numberOfCachedExpressions()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }, { - tagName: 'A', - property: 'href', - expressionString: 'url', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + { + tagName: 'A', + property: 'href', + expressionString: 'url', + }, + ]); expect(numberOfCachedExpressions()).to.equal(2); evaluator.removeBindingsWithExpressionStrings(['url']); expect(numberOfCachedExpressions()).to.equal(1); @@ -110,11 +124,13 @@ describe('BindEvaluator', () => { it('should evaluate expressions given a scope with needed bindings', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'oneplusone + 2', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'oneplusone + 2', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({oneplusone: 2}); expect(results['oneplusone + 2']).to.equal(4); @@ -123,11 +139,13 @@ describe('BindEvaluator', () => { it('should treat out-of-scope vars as null', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'outOfScope', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'outOfScope', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({}); expect(results['outOfScope']).to.be.null; @@ -136,22 +154,27 @@ describe('BindEvaluator', () => { it('should validate a common expression on each respective binding', () => { const string = /* eslint no-script-url: 0 */ '"javascript:alert(1)"'; - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: string, - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: string, + }, + ]); let {results, errors} = evaluator.evaluateBindings({}); - expect(results[string]) - .to.equal(/* eslint no-script-url: 0 */ 'javascript:alert(1)'); + expect(results[string]).to.equal( + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ); expect(errors[string]).to.be.undefined; // An expression used in a single invalid binding should be removed. - evaluator.addBindings([{ - tagName: 'A', - property: 'href', - expressionString: string, - }]); + evaluator.addBindings([ + { + tagName: 'A', + property: 'href', + expressionString: string, + }, + ]); ({results, errors} = evaluator.evaluateBindings({})); expect(results[string]).to.be.undefined; expect(errors[string].message).to.match(/not a valid result/); @@ -159,16 +182,20 @@ describe('BindEvaluator', () => { it('should evaluate expressions with macros', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addMacros([{ - id: 'add', - argumentNames: ['a', 'b'], - expressionString: 'a + b', - }]); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'add(oneplusone, 2)', - }]); + evaluator.addMacros([ + { + id: 'add', + argumentNames: ['a', 'b'], + expressionString: 'a + b', + }, + ]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'add(oneplusone, 2)', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({oneplusone: 2}); expect(results['add(oneplusone, 2)']).to.equal(4); @@ -177,20 +204,25 @@ describe('BindEvaluator', () => { it('should evaluate expressions with nested macros', () => { expect(numberOfBindings()).to.equal(0); - evaluator.addMacros([{ - id: 'add', - argumentNames: ['a', 'b'], - expressionString: 'a + b', - }, { - id: 'addThree', - argumentNames: ['a', 'b', 'c'], - expressionString: 'add(add(a, b), c)', - }]); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'addThree(oneplusone, 2, 2)', - }]); + evaluator.addMacros([ + { + id: 'add', + argumentNames: ['a', 'b'], + expressionString: 'a + b', + }, + { + id: 'addThree', + argumentNames: ['a', 'b', 'c'], + expressionString: 'add(add(a, b), c)', + }, + ]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'addThree(oneplusone, 2, 2)', + }, + ]); expect(numberOfBindings()).to.equal(1); const {results, errors} = evaluator.evaluateBindings({oneplusone: 2}); expect(results['addThree(oneplusone, 2, 2)']).to.equal(6); @@ -198,43 +230,52 @@ describe('BindEvaluator', () => { }); it('should not allow recursive macros', () => { - evaluator.addMacros([{ - id: 'recurse', - expressionString: 'recurse()', - }]); + evaluator.addMacros([ + { + id: 'recurse', + expressionString: 'recurse()', + }, + ]); - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'recurse()', - }]); + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'recurse()', + }, + ]); const {results, errors} = evaluator.evaluateBindings({}); expect(results['recurse()']).to.be.undefined; expect(errors['recurse()'].message).to.match( - /recurse is not a supported function/); + /recurse is not a supported function/ + ); }); it('should not allow cyclic references in macros', () => { - evaluator.addMacros([{ - id: 'foo', - argumentNames: ['x'], - expressionString: 'bar(x)', - }, { - id: 'bar', - argumentNames: ['x'], - expressionString: 'foo(x)', - }]); - - evaluator.addBindings([{ - tagName: 'P', - property: 'text', - expressionString: 'bar()', - }]); + evaluator.addMacros([ + { + id: 'foo', + argumentNames: ['x'], + expressionString: 'bar(x)', + }, + { + id: 'bar', + argumentNames: ['x'], + expressionString: 'foo(x)', + }, + ]); + + evaluator.addBindings([ + { + tagName: 'P', + property: 'text', + expressionString: 'bar()', + }, + ]); const {results, errors} = evaluator.evaluateBindings({}); expect(results['bar()']).to.be.undefined; - expect(errors['bar()'].message).to.match( - /bar is not a supported function/); + expect(errors['bar()'].message).to.match(/bar is not a supported function/); }); }); diff --git a/extensions/amp-bind/0.1/test/test-bind-expression.js b/extensions/amp-bind/0.1/test/test-bind-expression.js index c9ee23ee26e9b..0b46eabcba6f3 100644 --- a/extensions/amp-bind/0.1/test/test-bind-expression.js +++ b/extensions/amp-bind/0.1/test/test-bind-expression.js @@ -74,27 +74,69 @@ describe('BindExpression', () => { }); it('disallow: operators with side effects', () => { - expect(() => { evaluate('foo = 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo += 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo -= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo *= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo /= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo %= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo **= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo <<= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >>= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >>>= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo &= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo ^= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo |= 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo++', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo--', {foo: 0}); }).to.throw(); - expect(() => { evaluate('~foo', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo << 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >> 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('foo >>> 1', {foo: 0}); }).to.throw(); - expect(() => { evaluate('new Object()', {foo: 0}); }).to.throw(); - expect(() => { evaluate('delete foo', {foo: 0}); }).to.throw(); + expect(() => { + evaluate('foo = 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo += 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo -= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo *= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo /= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo %= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo **= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo <<= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >>= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >>>= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo &= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo ^= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo |= 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo++', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo--', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('~foo', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo << 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >> 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('foo >>> 1', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('new Object()', {foo: 0}); + }).to.throw(); + expect(() => { + evaluate('delete foo', {foo: 0}); + }).to.throw(); }); /** @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators */ @@ -109,10 +151,18 @@ describe('BindExpression', () => { expect(evaluate('new')).to.be.null; expect(evaluate('super')).to.be.null; - expect(() => { evaluate('function*'); }).to.throw(); - expect(() => { evaluate('/ab+c/i'); }).to.throw(); - expect(() => { evaluate('yield*'); }).to.throw(); - expect(() => { evaluate('async function*'); }).to.throw(); + expect(() => { + evaluate('function*'); + }).to.throw(); + expect(() => { + evaluate('/ab+c/i'); + }).to.throw(); + expect(() => { + evaluate('yield*'); + }).to.throw(); + expect(() => { + evaluate('async function*'); + }).to.throw(); }); }); @@ -162,8 +212,7 @@ describe('BindExpression', () => { expect(evaluate('"abc".indexOf("b")')).to.equal(1); expect(evaluate('"aaa".lastIndexOf("a")')).to.equal(2); expect(evaluate('"abc".slice(0, 2)')).to.equal('ab'); - expect(evaluate('"a-b-c".split("-")')) - .to.deep.equal(['a', 'b', 'c']); + expect(evaluate('"a-b-c".split("-")')).to.deep.equal(['a', 'b', 'c']); expect(evaluate('"abc".substr(1)')).to.equal('bc'); expect(evaluate('"abc".substring(0, 2)')).to.equal('ab'); expect(evaluate('"ABC".toLowerCase()')).to.equal('abc'); @@ -215,10 +264,12 @@ describe('BindExpression', () => { expect(evaluate('foo', {foo: 'bar'})).to.equal('bar'); expect(evaluate('foo', {foo: 1})).to.equal(1); expect(evaluate('foo', {foo: [1, 2, 3]})).to.deep.equal([1, 2, 3]); - expect(evaluate('foo', {foo: {'bar': 'qux'}})) - .to.deep.equal({bar: 'qux'}); - expect(evaluate('{"foo": bar}', {bar: 'qux'})) - .to.deep.equal({foo: 'qux'}); + expect(evaluate('foo', {foo: {'bar': 'qux'}})).to.deep.equal({ + bar: 'qux', + }); + expect(evaluate('{"foo": bar}', {bar: 'qux'})).to.deep.equal({ + foo: 'qux', + }); expect(evaluate('[foo]', {foo: 'bar'})).to.deep.equal(['bar']); expect(evaluate('foo[1]', {foo: ['b', 'c']})).to.equal('c'); expect(evaluate('foo.length', {foo: [1, 2, 3]})).to.equal(3); @@ -230,17 +281,15 @@ describe('BindExpression', () => { it('literals', () => { expect(evaluate('[]')).to.deep.equal([]); expect(evaluate('["a", "b"].length')).to.equal(2); - expect(evaluate('[1, "a", [], {}]')) - .to.deep.equal([1, 'a', [], {}]); + expect(evaluate('[1, "a", [], {}]')).to.deep.equal([1, 'a', [], {}]); expect(evaluate('["a", "b"][1]')).to.equal('b'); expect(evaluate('["a", foo][1]', {foo: 'b'})).to.equal('b'); }); it('trailing commas in literals', () => { - expect(evaluate('[1,2,3,]')).to.deep.equal([1,2,3]); + expect(evaluate('[1,2,3,]')).to.deep.equal([1, 2, 3]); expect(evaluate('["a", "b",].length')).to.equal(2); - expect(evaluate('[1, "a", [], {},]')) - .to.deep.equal([1, 'a', [], {}]); + expect(evaluate('[1, "a", [], {},]')).to.deep.equal([1, 'a', [], {}]); expect(evaluate('["a", "b",][1]')).to.equal('b'); expect(evaluate('["a", foo,][1]', {foo: 'b'})).to.equal('b'); }); @@ -255,29 +304,44 @@ describe('BindExpression', () => { }); it('prototype functions', () => { - expect(evaluate('["a", "b"].concat(["c", "d"])')) - .to.deep.equal(['a', 'b', 'c', 'd']); + expect(evaluate('["a", "b"].concat(["c", "d"])')).to.deep.equal([ + 'a', + 'b', + 'c', + 'd', + ]); expect(evaluate('["a", "a"].indexOf("a")')).to.equal(0); expect(evaluate('["a", "b", "c"].join("-")')).to.equal('a-b-c'); expect(evaluate('["a", "a"].lastIndexOf("a")')).to.equal(1); - expect(evaluate('["a", "b", "c"].slice(1, 2)')) - .to.deep.equal(['b']); + expect(evaluate('["a", "b", "c"].slice(1, 2)')).to.deep.equal(['b']); expect(evaluate('[1, 2, 3, 4, 5].includes(3)')).to.be.true; }); it('custom Array#sort()', () => { expect(evaluate('[11, 1, 2].sort()')).to.deep.equal([1, 11, 2]); - expect(evaluate('[11, 1, 2].sort((x, y) => x - y)')) - .to.deep.equal([1, 2, 11]); + expect(evaluate('[11, 1, 2].sort((x, y) => x - y)')).to.deep.equal([ + 1, + 2, + 11, + ]); const a = [11, 1, 2]; expect(evaluate('a.sort()', {a})).to.deep.equal([1, 11, 2]); - expect(evaluate('a.sort((x, y) => x - y)', {a})) - .to.deep.equal([1, 2, 11]); + expect(evaluate('a.sort((x, y) => x - y)', {a})).to.deep.equal([ + 1, + 2, + 11, + ]); // Sort should be out-of-place i.e. does not sort the caller. - expect(evaluate('a.sort().concat(a)', {a})) - .to.deep.equal([1, 11, 2, 11, 1, 2]); + expect(evaluate('a.sort().concat(a)', {a})).to.deep.equal([ + 1, + 11, + 2, + 11, + 1, + 2, + ]); }); it('custom Array#splice()', () => { @@ -293,8 +357,12 @@ describe('BindExpression', () => { expect(evaluate('a.splice(1, 1, 47)', {a})).to.deep.equal([1, 47, 3]); // Splice should be out-of-place i.e. does not splice the caller. - expect(evaluate('a.splice(1).concat(a)', {a})) - .to.deep.equal([1, 1, 2, 3]); + expect(evaluate('a.splice(1).concat(a)', {a})).to.deep.equal([ + 1, + 1, + 2, + 3, + ]); }); it('non-whitelisted functions', () => { @@ -384,10 +452,12 @@ describe('BindExpression', () => { }); it('encodeURI and encodeURIComponent', () => { - expect(evaluate('encodeURI("http://google.com/s p a c e.html")')) - .to.equal('http://google.com/s%20p%20a%20c%20e.html'); - expect(evaluate('encodeURIComponent("http://google.com/foo?foo=bar")')) - .to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Ffoo%3Dbar'); + expect( + evaluate('encodeURI("http://google.com/s p a c e.html")') + ).to.equal('http://google.com/s%20p%20a%20c%20e.html'); + expect( + evaluate('encodeURIComponent("http://google.com/foo?foo=bar")') + ).to.equal('http%3A%2F%2Fgoogle.com%2Ffoo%3Ffoo%3Dbar'); }); it('splice()', () => { @@ -417,26 +487,45 @@ describe('BindExpression', () => { }); it('disallow: function declarations', () => { - expect(() => { evaluate('(function() {})'); }).to.throw(); - expect(() => { evaluate('function foo() {}'); }).to.throw(); - expect(() => { evaluate('new Function()'); }).to.throw(); - expect(() => { evaluate('Function()'); }).to.throw(); - expect(() => { evaluate('() => {}'); }).to.throw(); - expect(() => { evaluate('class Foo {}'); }).to.throw(); + expect(() => { + evaluate('(function() {})'); + }).to.throw(); + expect(() => { + evaluate('function foo() {}'); + }).to.throw(); + expect(() => { + evaluate('new Function()'); + }).to.throw(); + expect(() => { + evaluate('Function()'); + }).to.throw(); + expect(() => { + evaluate('() => {}'); + }).to.throw(); + expect(() => { + evaluate('class Foo {}'); + }).to.throw(); }); it('disallow: invocation of custom functions in scope', () => { const scope = { foo: { - bar: () => { 'bar'; }, + bar: () => { + 'bar'; + }, + }, + baz: () => { + 'baz'; }, - baz: () => { 'baz'; }, qux: window.Function, }; // baz() throws a parse error because functions must have a caller. - expect(() => { evaluate('baz()', scope); }).to.throw(); - expect(() => { evaluate('foo.bar()', scope); }) - .to.throw(Error, unsupportedFunctionError); + expect(() => { + evaluate('baz()', scope); + }).to.throw(); + expect(() => { + evaluate('foo.bar()', scope); + }).to.throw(Error, unsupportedFunctionError); expect(() => { evaluate('foo.qux("a", "return a")', scope); }).to.throw(unsupportedFunctionError); @@ -478,8 +567,11 @@ describe('BindExpression', () => { // Only allow objects in arguments for some functions. expect(evaluate('keys({x: 2})')).to.deep.equal(['x']); expect(evaluate('values({x: 2})')).to.deep.equal([2]); - expect(evaluate('splice([1, 3], 1, 0, {x: 2})')) - .to.deep.equal([1, {x: 2}, 3]); + expect(evaluate('splice([1, 3], 1, 0, {x: 2})')).to.deep.equal([ + 1, + {x: 2}, + 3, + ]); }); }); @@ -530,13 +622,21 @@ describe('BindExpression', () => { }); it('disallow: loops', () => { - expect(() => { evaluate('if (foo) "bar"', {foo: 0}); }).to.throw(); + expect(() => { + evaluate('if (foo) "bar"', {foo: 0}); + }).to.throw(); expect(() => { evaluate('switch (foo) { case 0: "bar" }', {foo: 0}); }).to.throw(); - expect(() => { evaluate('for (;;) {}'); }).to.throw(); - expect(() => { evaluate('while (true) {}'); }).to.throw(); - expect(() => { evaluate('do {} while (true)'); }).to.throw(); + expect(() => { + evaluate('for (;;) {}'); + }).to.throw(); + expect(() => { + evaluate('while (true) {}'); + }).to.throw(); + expect(() => { + evaluate('do {} while (true)'); + }).to.throw(); expect(() => { evaluate('for (var i in foo) {}', {foo: [1, 2, 3]}); }).to.throw(); @@ -554,16 +654,36 @@ describe('BindExpression', () => { expect(evaluate('NaN')).to.be.null; expect(evaluate('undefined')).to.be.null; - expect(() => { evaluate('eval()'); }).to.throw(); - expect(() => { evaluate('uneval()'); }).to.throw(); - expect(() => { evaluate('isFinite()'); }).to.throw(); - expect(() => { evaluate('isNaN()'); }).to.throw(); - expect(() => { evaluate('parseFloat()'); }).to.throw(); - expect(() => { evaluate('parseInt()'); }).to.throw(); - expect(() => { evaluate('decodeURI()'); }).to.throw(); - expect(() => { evaluate('decodeURIComponent()'); }).to.throw(); - expect(() => { evaluate('escape()'); }).to.throw(); - expect(() => { evaluate('unescape()'); }).to.throw(); + expect(() => { + evaluate('eval()'); + }).to.throw(); + expect(() => { + evaluate('uneval()'); + }).to.throw(); + expect(() => { + evaluate('isFinite()'); + }).to.throw(); + expect(() => { + evaluate('isNaN()'); + }).to.throw(); + expect(() => { + evaluate('parseFloat()'); + }).to.throw(); + expect(() => { + evaluate('parseInt()'); + }).to.throw(); + expect(() => { + evaluate('decodeURI()'); + }).to.throw(); + expect(() => { + evaluate('decodeURIComponent()'); + }).to.throw(); + expect(() => { + evaluate('escape()'); + }).to.throw(); + expect(() => { + evaluate('unescape()'); + }).to.throw(); expect(evaluate('Object')).to.be.null; expect(evaluate('Function')).to.be.null; @@ -623,8 +743,8 @@ describe('BindExpression', () => { }); it('disallow: exceeding maximum AST size', () => { - expect(new BindExpression('1 + 1', {}, /* maxAstSize */ 3)) - .to.not.be.null; + expect(new BindExpression('1 + 1', {}, /* maxAstSize */ 3)).to.not.be + .null; // The expression '1 + 1' should have an AST size of 3 -- one for each // literal, and a PLUS expression wrapping them. @@ -641,16 +761,16 @@ describe('BindExpression', () => { expect(add.getExpressionSize()).to.equal(3); // The expression add(1, 1) should have an AST size of 3. - expect(new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 3)) - .to.not.be.null; + expect(new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 3)).to.not + .be.null; expect(() => { new BindExpression('add(1, 1)', {add}, /* maxAstSize */ 2); }).to.throw(expressionSizeExceededError); // The expression add(1, 1 + 1) should have an AST size of 5. - expect(new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 5)) - .to.not.be.null; + expect(new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 5)).to + .not.be.null; expect(() => { new BindExpression('add(1, 1 + 1)', {add}, /* maxAstSize */ 4); @@ -692,27 +812,51 @@ describe('BindExpression', () => { }); it('disallow: usage other than as function parameter', () => { - expect(() => { evaluate('() => 123'); }).to.throw(); - expect(() => { evaluate('x => 123'); }).to.throw(); - expect(() => { evaluate('(x, y) => 123'); }).to.throw(); + expect(() => { + evaluate('() => 123'); + }).to.throw(); + expect(() => { + evaluate('x => 123'); + }).to.throw(); + expect(() => { + evaluate('(x, y) => 123'); + }).to.throw(); - expect(() => { evaluate('(() => 123).constructor()'); }).to.throw(); - expect(() => { evaluate('(x => 123).constructor()'); }).to.throw(); - expect(() => { evaluate('((x, y) => 123).constructor()'); }).to.throw(); + expect(() => { + evaluate('(() => 123).constructor()'); + }).to.throw(); + expect(() => { + evaluate('(x => 123).constructor()'); + }).to.throw(); + expect(() => { + evaluate('((x, y) => 123).constructor()'); + }).to.throw(); - expect(() => { evaluate('(() => 123).name'); }).to.throw(); - expect(() => { evaluate('(x => 123).name'); }).to.throw(); - expect(() => { evaluate('((x, y) => 123).name'); }).to.throw(); + expect(() => { + evaluate('(() => 123).name'); + }).to.throw(); + expect(() => { + evaluate('(x => 123).name'); + }).to.throw(); + expect(() => { + evaluate('((x, y) => 123).name'); + }).to.throw(); }); it('disallow: `arguments` or `this`', () => { const a = [1, 2, 3]; - expect(evaluate('a.map(() => arguments)', {a})) - .to.deep.equal([null, null, null]); + expect(evaluate('a.map(() => arguments)', {a})).to.deep.equal([ + null, + null, + null, + ]); expect(evaluate('a.reduce(() => arguments)', {a})).to.deep.equal(null); - expect(evaluate('a.map(() => this)', {a})) - .to.deep.equal([null, null, null]); + expect(evaluate('a.map(() => this)', {a})).to.deep.equal([ + null, + null, + null, + ]); expect(evaluate('a.reduce(() => this)', {a})).to.deep.equal(null); }); }); diff --git a/extensions/amp-bind/0.1/test/test-bind-validator.js b/extensions/amp-bind/0.1/test/test-bind-validator.js index 496d4a9b5abc3..1686c3dbf5e09 100644 --- a/extensions/amp-bind/0.1/test/test-bind-validator.js +++ b/extensions/amp-bind/0.1/test/test-bind-validator.js @@ -126,35 +126,64 @@ describe('BindValidator (allowUrlProperties=true)', () => { it('should NOT allow invalid "class" attribute values', () => { expect(val.isResultValid('DIV', 'class', 'foo')).to.be.true; - expect(val.isResultValid( - 'DIV', 'class', 'i-amphtml-foo')).to.be.false; - expect(val.isResultValid( - 'DIV', 'class', 'foo i-amphtml-bar')).to.be.false; + expect(val.isResultValid('DIV', 'class', 'i-amphtml-foo')).to.be.false; + expect(val.isResultValid('DIV', 'class', 'foo i-amphtml-bar')).to.be + .false; }); it('should NOT sanitize "text" attribute values', () => { expect(val.isResultValid('P', 'text', 'Hello World')).to.be.true; expect(val.isResultValid('P', 'text', '')).to.be.true; expect(val.isResultValid('P', 'text', null)).to.be.true; - expect(val.isResultValid( - 'P', 'text', '')).to.be.true; + expect(val.isResultValid('P', 'text', '')).to.be + .true; }); it('should block dangerous attribute URLs in standard elements', () => { - expect(val.isResultValid('A', 'href', - /* eslint no-script-url: 0 */ 'javascript:alert(1)')).to.be.false; - expect(val.isResultValid('A', 'href', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; - - expect(val.isResultValid('SOURCE', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)')).to.be.false; - expect(val.isResultValid('SOURCE', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; - - expect(val.isResultValid('TRACK', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)')).to.be.false; - expect(val.isResultValid('TRACK', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; + expect( + val.isResultValid( + 'A', + 'href', + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ) + ).to.be.false; + expect( + val.isResultValid( + 'A', + 'href', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; + + expect( + val.isResultValid( + 'SOURCE', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ) + ).to.be.false; + expect( + val.isResultValid( + 'SOURCE', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; + + expect( + val.isResultValid( + 'TRACK', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)' + ) + ).to.be.false; + expect( + val.isResultValid( + 'TRACK', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; }); it('should NOT allow unsupported "type" values', () => { @@ -184,25 +213,39 @@ describe('BindValidator (allowUrlProperties=true)', () => { it('should support ', () => { expect(val.canBind('AMP-IMG', 'src')).to.be.true; - expect(val.isResultValid( - 'AMP-IMG', 'src', 'http://foo.com/bar.jpg')).to.be.true; - expect(val.isResultValid('AMP-IMG', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; - expect(val.isResultValid( - 'AMP-IMG', 'src', '?__amp_source_origin=foo')).to.be.false; - - expect(val.isResultValid( + expect(val.isResultValid('AMP-IMG', 'src', 'http://foo.com/bar.jpg')).to + .be.true; + expect( + val.isResultValid( + 'AMP-IMG', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; + expect(val.isResultValid('AMP-IMG', 'src', '?__amp_source_origin=foo')).to + .be.false; + + expect( + val.isResultValid( 'AMP-IMG', 'srcset', - 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x')).to.be.true; - expect(val.isResultValid( + 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x' + ) + ).to.be.true; + expect( + val.isResultValid( 'AMP-IMG', 'srcset', - /* eslint no-script-url: 0 */ 'javascript:alert(1);')).to.be.false; - expect(val.isResultValid( + /* eslint no-script-url: 0 */ 'javascript:alert(1);' + ) + ).to.be.false; + expect( + val.isResultValid( 'AMP-IMG', 'src', - 'http://a.com/b.jpg 1x, ?__amp_source_origin=foo 2x')).to.be.false; + 'http://a.com/b.jpg 1x, ?__amp_source_origin=foo 2x' + ) + ).to.be.false; }); it('should support ', () => { @@ -223,12 +266,12 @@ describe('BindValidator (allowUrlProperties=true)', () => { it('should support ', () => { expect(val.canBind('AMP-STATE', 'src')).to.be.true; - expect(val.isResultValid( - 'AMP-STATE', 'src', 'https://foo.com/bar.json')).to.be.true; - expect(val.isResultValid( - 'AMP-STATE', 'src', 'http://foo.com/bar.json')).to.be.false; - expect(val.isResultValid( - 'AMP-STATE', 'src', 'data://foo.com/bar.json')).to.be.false; + expect(val.isResultValid('AMP-STATE', 'src', 'https://foo.com/bar.json')) + .to.be.true; + expect(val.isResultValid('AMP-STATE', 'src', 'http://foo.com/bar.json')) + .to.be.false; + expect(val.isResultValid('AMP-STATE', 'src', 'data://foo.com/bar.json')) + .to.be.false; }); it('should support ', () => { @@ -236,18 +279,28 @@ describe('BindValidator (allowUrlProperties=true)', () => { expect(val.canBind('AMP-VIDEO', 'poster')).to.be.true; expect(val.canBind('AMP-VIDEO', 'src')).to.be.true; - expect(val.isResultValid( - 'AMP-VIDEO', 'src', 'https://foo.com/bar.mp4')).to.be.true; - expect(val.isResultValid( - 'AMP-VIDEO', 'src', 'http://foo.com/bar.mp4')).to.be.false; - expect(val.isResultValid('AMP-VIDEO', 'src', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; + expect(val.isResultValid('AMP-VIDEO', 'src', 'https://foo.com/bar.mp4')) + .to.be.true; + expect(val.isResultValid('AMP-VIDEO', 'src', 'http://foo.com/bar.mp4')).to + .be.false; + expect( + val.isResultValid( + 'AMP-VIDEO', + 'src', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; }); it('should support (svg) image', () => { expect(val.canBind('IMAGE', 'xlink:href')).to.be.true; - expect(val.isResultValid('IMAGE', 'xlink:href', - /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;')).to.be.false; + expect( + val.isResultValid( + 'IMAGE', + 'xlink:href', + /* eslint no-script-url: 0 */ 'javascript:alert(1)\n;' + ) + ).to.be.false; }); }); }); @@ -269,13 +322,16 @@ describe('BindValidator (allowUrlProperties=false)', () => { it('should not validate results of URL properties', () => { expect(val.isResultValid('A', 'href', 'https://google.com')).to.be.false; - expect(val.isResultValid('AMP-IMG', 'src', 'https://foo.com/bar.jpg')) - .to.be.false; - expect(val.isResultValid( + expect(val.isResultValid('AMP-IMG', 'src', 'https://foo.com/bar.jpg')).to.be + .false; + expect( + val.isResultValid( 'AMP-IMG', 'srcset', - 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x')).to.be.false; + 'http://a.com/b.jpg 1x, http://c.com/d.jpg 2x' + ) + ).to.be.false; expect(val.isResultValid('IMAGE', 'xlink:href', 'https://foo.com/bar.jpg')) - .to.be.false; + .to.be.false; }); }); diff --git a/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js b/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js index 9d4128f35fe2c..a938713dacd4c 100644 --- a/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js +++ b/extensions/amp-bodymovin-animation/0.1/amp-bodymovin-animation.js @@ -33,7 +33,6 @@ import {userAssert} from '../../../src/log'; const TAG = 'amp-bodymovin-animation'; export class AmpBodymovinAnimation extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -73,9 +72,10 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { * @override */ preconnectCallback(opt_onLayout) { - const scriptToLoad = this.renderer_ === 'svg' ? - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' : - 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; + const scriptToLoad = + this.renderer_ === 'svg' + ? 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' + : 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; preloadBootstrap(this.win, this.preconnect); this.preconnect.url(scriptToLoad, opt_onLayout); } @@ -85,23 +85,47 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { this.loop_ = this.element.getAttribute('loop') || 'true'; this.autoplay_ = !this.element.hasAttribute('noautoplay'); this.renderer_ = this.element.getAttribute('renderer') || 'svg'; - userAssert(this.element.hasAttribute('src'), - 'The src attribute must be specified for '); + userAssert( + this.element.hasAttribute('src'), + 'The src attribute must be specified for ' + ); assertHttpsUrl(this.element.getAttribute('src'), this.element); const deferred = new Deferred(); this.playerReadyPromise_ = deferred.promise; this.playerReadyResolver_ = deferred.resolve; // Register relevant actions - this.registerAction('play', () => { this.play_(); }, ActionTrust.LOW); - this.registerAction('pause', () => { this.pause_(); }, ActionTrust.LOW); - this.registerAction('stop', () => { this.stop_(); }, ActionTrust.LOW); - this.registerAction('seekTo', invocation => { - const {args} = invocation; - if (args) { - this.seekTo_(args); - } - }, ActionTrust.LOW); + this.registerAction( + 'play', + () => { + this.play_(); + }, + ActionTrust.LOW + ); + this.registerAction( + 'pause', + () => { + this.pause_(); + }, + ActionTrust.LOW + ); + this.registerAction( + 'stop', + () => { + this.stop_(); + }, + ActionTrust.LOW + ); + this.registerAction( + 'seekTo', + invocation => { + const {args} = invocation; + if (args) { + this.seekTo_(args); + } + }, + ActionTrust.LOW + ); } /** @override */ @@ -115,19 +139,25 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { animationData: data, }; const iframe = getIframe( - this.win, this.element, 'bodymovinanimation', opt_context); - return Services.vsyncFor(this.win).mutatePromise(() => { - this.applyFillContent(iframe); - this.unlistenMessage_ = listen( + this.win, + this.element, + 'bodymovinanimation', + opt_context + ); + return Services.vsyncFor(this.win) + .mutatePromise(() => { + this.applyFillContent(iframe); + this.unlistenMessage_ = listen( this.win, 'message', this.handleBodymovinMessages_.bind(this) - ); - this.element.appendChild(iframe); - this.iframe_ = iframe; - }).then(() => { - return this.playerReadyPromise_; - }); + ); + this.element.appendChild(iframe); + this.iframe_ = iframe; + }) + .then(() => { + return this.playerReadyPromise_; + }); }); } @@ -154,8 +184,13 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { if (this.iframe_ && event.source != this.iframe_.contentWindow) { return; } - if (!getData(event) || !(isObject(getData(event)) - || startsWith(/** @type {string} */ (getData(event)), '{'))) { + if ( + !getData(event) || + !( + isObject(getData(event)) || + startsWith(/** @type {string} */ (getData(event)), '{') + ) + ) { return; // Doesn't look like JSON. } @@ -181,12 +216,14 @@ export class AmpBodymovinAnimation extends AMP.BaseElement { sendCommand_(action, opt_valueType, opt_value) { this.playerReadyPromise_.then(() => { if (this.iframe_ && this.iframe_.contentWindow) { - const message = JSON.stringify(dict({ - 'action': action, - 'valueType': opt_valueType || '', - 'value': opt_value || '', - })); - this.iframe_.contentWindow. /*OK*/postMessage(message, '*'); + const message = JSON.stringify( + dict({ + 'action': action, + 'valueType': opt_valueType || '', + 'value': opt_value || '', + }) + ); + this.iframe_.contentWindow./*OK*/ postMessage(message, '*'); } }); } diff --git a/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js b/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js index 04af04b91a673..30637a67bfdff 100644 --- a/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js +++ b/extensions/amp-bodymovin-animation/0.1/test/integration/test-amp-bodymovin-animation.js @@ -14,30 +14,41 @@ * limitations under the License. */ -describe.configure().ifChrome().run('amp-bodymovin-animation', function() { - const extensions = ['amp-bodymovin-animation']; - const bodymovinBody = ` +describe + .configure() + .ifChrome() + .run('amp-bodymovin-animation', function() { + const extensions = ['amp-bodymovin-animation']; + const bodymovinBody = `
    Stop
    `; - describes.integration('amp-bodymovin-animation iframe renders', { - body: bodymovinBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. - }); + describes.integration( + 'amp-bodymovin-animation iframe renders', + { + body: bodymovinBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); - describes.integration('amp-bodymovin-animation actions work', { - body: bodymovinBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. - }); + describes.integration( + 'amp-bodymovin-animation actions work', + { + body: bodymovinBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); - const bodymovinNoAutoplayBody = ` + const bodymovinNoAutoplayBody = ` Play
    Pause
    `; - describes.integration('amp-bodymovin-animation actions work', { - body: bodymovinNoAutoplayBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. - }); + describes.integration( + 'amp-bodymovin-animation actions work', + { + body: bodymovinNoAutoplayBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); - const bodymovinSeekToBody = ` + const bodymovinSeekToBody = `
    Seek to 1/2
    `; - describes.integration('amp-bodymovin-animation actions work', { - body: bodymovinSeekToBody, - extensions, - }, unusedEnv => { - // TODO(nainar): Add test. + describes.integration( + 'amp-bodymovin-animation actions work', + { + body: bodymovinSeekToBody, + extensions, + }, + unusedEnv => { + // TODO(nainar): Add test. + } + ); }); -}); diff --git a/extensions/amp-brid-player/0.1/amp-brid-player.js b/extensions/amp-brid-player/0.1/amp-brid-player.js index 691ef1c4e8fab..bf761cd1a7312 100644 --- a/extensions/amp-brid-player/0.1/amp-brid-player.js +++ b/extensions/amp-brid-player/0.1/amp-brid-player.js @@ -33,9 +33,7 @@ import { } from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; import {htmlFor} from '../../../src/static-template'; -import { - installVideoManagerForDoc, -} from '../../../src/service/video-manager-impl'; +import {installVideoManagerForDoc} from '../../../src/service/video-manager-impl'; import {isLayoutSizeDefined} from '../../../src/layout'; const TAG = 'amp-brid-player'; @@ -44,7 +42,6 @@ const TAG = 'amp-brid-player'; * @implements {../../../src/video-interface.VideoInterface} */ class AmpBridPlayer extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -115,11 +112,18 @@ class AmpBridPlayer extends AMP.BaseElement { } //Create iframe - const src = 'https://services.brid.tv/services/iframe/' + - encodeURIComponent(feedType) + - '/' + encodeURIComponent(this.feedID_) + - '/' + encodeURIComponent(this.partnerID_) + - '/' + encodeURIComponent(this.playerID_) + '/0/' + itemsNum + '/?amp=1'; + const src = + 'https://services.brid.tv/services/iframe/' + + encodeURIComponent(feedType) + + '/' + + encodeURIComponent(this.feedID_) + + '/' + + encodeURIComponent(this.partnerID_) + + '/' + + encodeURIComponent(this.playerID_) + + '/0/' + + itemsNum + + '/?amp=1'; this.videoIframeSrc_ = assertAbsoluteHttpOrHttpsUrl(src); @@ -131,21 +135,25 @@ class AmpBridPlayer extends AMP.BaseElement { const {element} = this; this.partnerID_ = userAssert( - element.getAttribute('data-partner'), - 'The data-partner attribute is required for %s', - element); + element.getAttribute('data-partner'), + 'The data-partner attribute is required for %s', + element + ); - this.playerID_ = userAssert(element.getAttribute('data-player'), - 'The data-player attribute is required for %s', - element); + this.playerID_ = userAssert( + element.getAttribute('data-player'), + 'The data-player attribute is required for %s', + element + ); this.feedID_ = userAssert( - (element.getAttribute('data-video') || - element.getAttribute('data-playlist') || - element.getAttribute('data-outstream')), - 'Either the data-video or the data-playlist or the data-outstream ' + + element.getAttribute('data-video') || + element.getAttribute('data-playlist') || + element.getAttribute('data-outstream'), + 'Either the data-video or the data-playlist or the data-outstream ' + 'attributes must be specified for %s', - element); + element + ); const deferred = new Deferred(); this.playerReadyPromise_ = deferred.promise; @@ -162,13 +170,12 @@ class AmpBridPlayer extends AMP.BaseElement { this.iframe_ = /** @type {HTMLIFrameElement} */ (iframe); this.unlistenMessage_ = listen( - this.win, - 'message', - this.handleBridMessage_.bind(this) + this.win, + 'message', + this.handleBridMessage_.bind(this) ); - return this.loadPromise(iframe) - .then(() => this.playerReadyPromise_); + return this.loadPromise(iframe).then(() => this.playerReadyPromise_); } /** @override */ @@ -196,8 +203,10 @@ class AmpBridPlayer extends AMP.BaseElement { createPlaceholderCallback() { const {element} = this; - if (!element.hasAttribute('data-video') && - !element.hasAttribute('data-playlist')) { + if ( + !element.hasAttribute('data-video') && + !element.hasAttribute('data-playlist') + ) { return; } @@ -213,13 +222,15 @@ class AmpBridPlayer extends AMP.BaseElement { this.propagateAttributes(['aria-label'], placeholder); this.applyFillContent(placeholder); - placeholder.setAttribute('src', - `https://cdn.brid.tv/live/partners/${encodeURIComponent(partnerID)}` + - `/snapshot/${encodeURIComponent(feedID)}.jpg`); + placeholder.setAttribute( + 'src', + `https://cdn.brid.tv/live/partners/${encodeURIComponent(partnerID)}` + + `/snapshot/${encodeURIComponent(feedID)}.jpg` + ); - const altText = placeholder.hasAttribute('aria-label') ? - 'Loading video - ' + placeholder.getAttribute('aria-label') : - 'Loading video'; + const altText = placeholder.hasAttribute('aria-label') + ? 'Loading video - ' + placeholder.getAttribute('aria-label') + : 'Loading video'; placeholder.setAttribute('alt', altText); @@ -233,12 +244,11 @@ class AmpBridPlayer extends AMP.BaseElement { * @private * */ sendCommand_(command, opt_arg) { - this.playerReadyPromise_.then(() => { if (this.iframe_ && this.iframe_.contentWindow) { const args = opt_arg === undefined ? '' : '|' + opt_arg; const message = 'Brid|' + command + args; - this.iframe_.contentWindow./*OK*/postMessage(message, '*'); + this.iframe_.contentWindow./*OK*/ postMessage(message, '*'); } }); } @@ -388,7 +398,6 @@ class AmpBridPlayer extends AMP.BaseElement { } } - AMP.extension(TAG, '0.1', AMP => { AMP.registerElement(TAG, AmpBridPlayer); }); diff --git a/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js b/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js index 30f76f52a8e11..37631b0bec76b 100644 --- a/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js +++ b/extensions/amp-brid-player/0.1/test/test-amp-brid-player.js @@ -19,100 +19,118 @@ import {Services} from '../../../../src/services'; import {VideoEvents} from '../../../../src/video-interface'; import {listenOncePromise} from '../../../../src/event-helper'; - -describes.realWin('amp-brid-player', { - amp: { - extensions: ['amp-brid-player'], +describes.realWin( + 'amp-brid-player', + { + amp: { + extensions: ['amp-brid-player'], + }, }, -}, env => { - let win, doc; - let timer; - - beforeEach(() => { - win = env.win; - doc = win.document; - timer = Services.timerFor(win); - }); - - function getBridPlayer(attributes, opt_responsive) { - const bc = doc.createElement('amp-brid-player'); + env => { + let win, doc; + let timer; + + beforeEach(() => { + win = env.win; + doc = win.document; + timer = Services.timerFor(win); + }); - for (const key in attributes) { - bc.setAttribute(key, attributes[key]); - } - bc.setAttribute('width', '640'); - bc.setAttribute('height', '360'); - if (opt_responsive) { - bc.setAttribute('layout', 'responsive'); + function getBridPlayer(attributes, opt_responsive) { + const bc = doc.createElement('amp-brid-player'); + + for (const key in attributes) { + bc.setAttribute(key, attributes[key]); + } + bc.setAttribute('width', '640'); + bc.setAttribute('height', '360'); + if (opt_responsive) { + bc.setAttribute('layout', 'responsive'); + } + + // see yt test implementation + timer.promise(50).then(() => { + const bridTimerIframe = bc.querySelector('iframe'); + + bc.implementation_.handleBridMessage_({ + origin: 'https://services.brid.tv', + source: bridTimerIframe.contentWindow, + data: 'Brid|0|trigger|ready', + }); + }); + doc.body.appendChild(bc); + return bc + .build() + .then(() => { + bc.layoutCallback(); + }) + .then(() => bc); } - // see yt test implementation - timer.promise(50).then(() => { - const bridTimerIframe = bc.querySelector('iframe'); - - bc.implementation_.handleBridMessage_({ - origin: 'https://services.brid.tv', - source: bridTimerIframe.contentWindow, - data: 'Brid|0|trigger|ready', + it('renders', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '4144', + 'data-video': '13663', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.tagName).to.equal('IFRAME'); + expect(iframe.src).to.equal( + 'https://services.brid.tv/services/iframe/video/13663/264/4144/0/1/?amp=1' + ); }); }); - doc.body.appendChild(bc); - return bc.build().then(() => { bc.layoutCallback(); }).then(() => bc); - } - - it('renders', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '4144', - 'data-video': '13663', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.tagName).to.equal('IFRAME'); - expect(iframe.src).to.equal( - 'https://services.brid.tv/services/iframe/video/13663/264/4144/0/1/?amp=1'); - }); - }); - it('renders responsively', () => { - return getBridPlayer({ - 'data-partner': '1177', - 'data-player': '979', - 'data-video': '5204', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.className).to.match(/i-amphtml-fill-content/); + it('renders responsively', () => { + return getBridPlayer( + { + 'data-partner': '1177', + 'data-player': '979', + 'data-video': '5204', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.className).to.match(/i-amphtml-fill-content/); + }); }); - }); - it('requires data-partner', () => { - return allowConsoleError(() => { return getBridPlayer({ - 'data-player': '4144', - 'data-video': '13663', - }).should.eventually.be.rejectedWith( - /The data-partner attribute is required for/); + it('requires data-partner', () => { + return allowConsoleError(() => { + return getBridPlayer({ + 'data-player': '4144', + 'data-video': '13663', + }).should.eventually.be.rejectedWith( + /The data-partner attribute is required for/ + ); + }); }); - }); - it('requires data-player', () => { - return allowConsoleError(() => { return getBridPlayer({ - 'data-partner': '264', - 'data-video': '13663', - }).should.eventually.be.rejectedWith( - /The data-player attribute is required for/); + it('requires data-player', () => { + return allowConsoleError(() => { + return getBridPlayer({ + 'data-partner': '264', + 'data-video': '13663', + }).should.eventually.be.rejectedWith( + /The data-player attribute is required for/ + ); + }); }); - }); - it('should forward events from brid-player to the amp element', () => { - return getBridPlayer({ - 'data-partner': '1177', - 'data-player': '979', - 'data-video': '5204', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - - return Promise.resolve() + it('should forward events from brid-player to the amp element', () => { + return getBridPlayer( + { + 'data-partner': '1177', + 'data-player': '979', + 'data-video': '5204', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + + return Promise.resolve() .then(() => { const p = listenOncePromise(bc, VideoEvents.PLAYING); sendFakeMessage(bc, iframe, 'trigger|play'); @@ -133,62 +151,66 @@ describes.realWin('amp-brid-player', { sendFakeMessage(bc, iframe, 'volume|1'); return p; }); + }); }); - }); + function sendFakeMessage(bc, iframe, command) { + bc.implementation_.handleBridMessage_({ + origin: 'https://services.brid.tv', + source: iframe.contentWindow, + data: 'Brid|0|' + command, + }); + } - function sendFakeMessage(bc, iframe, command) { - bc.implementation_.handleBridMessage_({ - origin: 'https://services.brid.tv', - source: iframe.contentWindow, - data: 'Brid|0|' + command, - }); - } - - describe('createPlaceholderCallback', () => { - it('should create a placeholder image', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '979', - 'data-video': '13663', - }).then(brid => { - const img = brid.querySelector('amp-img'); - expect(img).to.not.be.null; - expect(img.getAttribute('src')).to.equal( - 'https://cdn.brid.tv/live/partners/264/snapshot/13663.jpg'); - expect(img.getAttribute('layout')).to.equal('fill'); - expect(img.hasAttribute('placeholder')).to.be.true; - expect(img.getAttribute('alt')).to.equal('Loading video'); - expect(img.getAttribute('referrerpolicy')).to.equal('origin'); + describe('createPlaceholderCallback', () => { + it('should create a placeholder image', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '979', + 'data-video': '13663', + }).then(brid => { + const img = brid.querySelector('amp-img'); + expect(img).to.not.be.null; + expect(img.getAttribute('src')).to.equal( + 'https://cdn.brid.tv/live/partners/264/snapshot/13663.jpg' + ); + expect(img.getAttribute('layout')).to.equal('fill'); + expect(img.hasAttribute('placeholder')).to.be.true; + expect(img.getAttribute('alt')).to.equal('Loading video'); + expect(img.getAttribute('referrerpolicy')).to.equal('origin'); + }); }); - }); - it('should propagate aria label for placeholder image', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '979', - 'data-video': '13663', - 'aria-label': 'great video', - }).then(brid => { - const img = brid.querySelector('amp-img'); - expect(img).to.not.be.null; - expect(img.getAttribute('alt')).to.equal('Loading video - great video'); + it('should propagate aria label for placeholder image', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '979', + 'data-video': '13663', + 'aria-label': 'great video', + }).then(brid => { + const img = brid.querySelector('amp-img'); + expect(img).to.not.be.null; + expect(img.getAttribute('alt')).to.equal( + 'Loading video - great video' + ); + }); }); - }); - it('should create a fallback for default snapshot', () => { - return getBridPlayer({ - 'data-partner': '264', - 'data-player': '979', - 'data-video': '13663', - }).then(brid => { - const img = brid.querySelector('amp-img'); - const fallbackImg = img.querySelector('amp-img'); - expect(fallbackImg).to.not.be.null; - expect(fallbackImg.getAttribute('src')).to.equal( - 'https://cdn.brid.tv/live/default/defaultSnapshot.png'); - expect(fallbackImg.getAttribute('layout')).to.equal('fill'); - expect(fallbackImg.hasAttribute('fallback')).to.be.true; - expect(fallbackImg.getAttribute('referrerpolicy')).to.equal('origin'); + it('should create a fallback for default snapshot', () => { + return getBridPlayer({ + 'data-partner': '264', + 'data-player': '979', + 'data-video': '13663', + }).then(brid => { + const img = brid.querySelector('amp-img'); + const fallbackImg = img.querySelector('amp-img'); + expect(fallbackImg).to.not.be.null; + expect(fallbackImg.getAttribute('src')).to.equal( + 'https://cdn.brid.tv/live/default/defaultSnapshot.png' + ); + expect(fallbackImg.getAttribute('layout')).to.equal('fill'); + expect(fallbackImg.hasAttribute('fallback')).to.be.true; + expect(fallbackImg.getAttribute('referrerpolicy')).to.equal('origin'); + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-brightcove/0.1/amp-brightcove.js b/extensions/amp-brightcove/0.1/amp-brightcove.js index e8785a592b885..5f58c60a8b05b 100644 --- a/extensions/amp-brightcove/0.1/amp-brightcove.js +++ b/extensions/amp-brightcove/0.1/amp-brightcove.js @@ -35,19 +35,14 @@ import { removeElement, } from '../../../src/dom'; import {getData, listen} from '../../../src/event-helper'; -import { - installVideoManagerForDoc, -} from '../../../src/service/video-manager-impl'; +import {installVideoManagerForDoc} from '../../../src/service/video-manager-impl'; import {isLayoutSizeDefined} from '../../../src/layout'; - /** @private @const {string} */ const TAG = 'amp-brightcove'; - /** @implements {../../../src/video-interface.VideoInterface} */ class AmpBrightcove extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -118,12 +113,17 @@ class AmpBrightcove extends AMP.BaseElement { this.playerReadyResolver_ = deferred.resolve; // Warn if the player does not have video interface support - this.readyTimeout_ = /** @type {number} */ ( - Services.timerFor(window).delay(() => { - user().warn(TAG, - 'Did not receive ready callback from player %s.' + - ' Ensure it has the videojs-amp-support plugin.', this.playerId_); - }, 3000)); + this.readyTimeout_ = /** @type {number} */ (Services.timerFor(window).delay( + () => { + user().warn( + TAG, + 'Did not receive ready callback from player %s.' + + ' Ensure it has the videojs-amp-support plugin.', + this.playerId_ + ); + }, + 3000 + )); this.playerReadyResolver_(this.iframe_); } @@ -134,13 +134,11 @@ class AmpBrightcove extends AMP.BaseElement { this.iframe_ = iframe; - this.unlistenMessage_ = listen( - this.win, - 'message', - e => this.handlePlayerMessage_(e)); + this.unlistenMessage_ = listen(this.win, 'message', e => + this.handlePlayerMessage_(e) + ); - return this.loadPromise(iframe) - .then(() => this.playerReadyPromise_); + return this.loadPromise(iframe).then(() => this.playerReadyPromise_); } /** @@ -154,10 +152,15 @@ class AmpBrightcove extends AMP.BaseElement { // We still need to check this.iframe_ as the component may have // been unlaid out by now. if (this.iframe_ && this.iframe_.contentWindow) { - this.iframe_.contentWindow. /*OK*/ postMessage(JSON.stringify(dict({ - 'command': command, - 'args': arg, - })), 'https://players.brightcove.net'); + this.iframe_.contentWindow./*OK*/ postMessage( + JSON.stringify( + dict({ + 'command': command, + 'args': arg, + }) + ), + 'https://players.brightcove.net' + ); } }); } @@ -209,21 +212,22 @@ class AmpBrightcove extends AMP.BaseElement { this.duration_ = data['dur']; } - if (redispatch(element, eventType, { - 'ready': VideoEvents.LOAD, - 'playing': VideoEvents.PLAYING, - 'pause': VideoEvents.PAUSE, - 'ended': VideoEvents.ENDED, - 'ads-ad-started': VideoEvents.AD_START, - 'ads-ad-ended': VideoEvents.AD_END, - })) { + if ( + redispatch(element, eventType, { + 'ready': VideoEvents.LOAD, + 'playing': VideoEvents.PLAYING, + 'pause': VideoEvents.PAUSE, + 'ended': VideoEvents.ENDED, + 'ads-ad-started': VideoEvents.AD_START, + 'ads-ad-ended': VideoEvents.AD_END, + }) + ) { return; } if (eventType === 'volumechange') { const muted = data['muted']; - if (muted == null || - this.muted_ == muted) { + if (muted == null || this.muted_ == muted) { return; } this.muted_ = muted; @@ -239,18 +243,20 @@ class AmpBrightcove extends AMP.BaseElement { onReady_(data) { this.hasAmpSupport_ = true; - Services.timerFor(this.win) - .cancel(this.readyTimeout_); + Services.timerFor(this.win).cancel(this.readyTimeout_); const {element} = this; installVideoManagerForDoc(element); Services.videoManagerForDoc(element).register(this); - dev().info(TAG, - 'Player %s ready. ' + + dev().info( + TAG, + 'Player %s ready. ' + 'Brightcove Player version: %s AMP Support version: %s', - this.playerId_, data['bcVersion'], data['ampSupportVersion'] + this.playerId_, + data['bcVersion'], + data['ampSupportVersion'] ); } @@ -261,34 +267,34 @@ class AmpBrightcove extends AMP.BaseElement { getIframeSrc_() { const {element: el} = this; const account = userAssert( - el.getAttribute('data-account'), - 'The data-account attribute is required for %s', - el); - const embed = (el.getAttribute('data-embed') || 'default'); + el.getAttribute('data-account'), + 'The data-account attribute is required for %s', + el + ); + const embed = el.getAttribute('data-embed') || 'default'; - this.playerId_ = (el.getAttribute('data-player') || + this.playerId_ = + el.getAttribute('data-player') || el.getAttribute('data-player-id') || - 'default'); + 'default'; const src = - `https://players.brightcove.net/${encodeURIComponent(account)}` + - `/${encodeURIComponent(this.playerId_)}` + - `_${encodeURIComponent(embed)}/index.html` + - // These are encodeURIComponent'd in encodeId_(). - (el.getAttribute('data-playlist-id') ? - '?playlistId=' + this.encodeId_(el.getAttribute('data-playlist-id')) : - (el.getAttribute('data-video-id') ? - '?videoId=' + this.encodeId_(el.getAttribute('data-video-id')) : - '' - ) - ); + `https://players.brightcove.net/${encodeURIComponent(account)}` + + `/${encodeURIComponent(this.playerId_)}` + + `_${encodeURIComponent(embed)}/index.html` + + // These are encodeURIComponent'd in encodeId_(). + (el.getAttribute('data-playlist-id') + ? '?playlistId=' + this.encodeId_(el.getAttribute('data-playlist-id')) + : el.getAttribute('data-video-id') + ? '?videoId=' + this.encodeId_(el.getAttribute('data-video-id')) + : ''); const customReferrer = el.getAttribute('data-referrer'); if (customReferrer) { el.setAttribute( - 'data-param-referrer', - this.urlReplacements_.expandUrlSync(customReferrer) + 'data-param-referrer', + this.urlReplacements_.expandUrlSync(customReferrer) ); } @@ -305,9 +311,13 @@ class AmpBrightcove extends AMP.BaseElement { const embed = mutations['data-embed']; const playlistId = mutations['data-playlist-id']; const videoId = mutations['data-video-id']; - if (account !== undefined || playerId !== undefined || - playlistId !== undefined || embed !== undefined || - videoId !== undefined) { + if ( + account !== undefined || + playerId !== undefined || + playlistId !== undefined || + embed !== undefined || + videoId !== undefined + ) { if (this.iframe_) { this.iframe_.src = this.getIframeSrc_(); } @@ -315,10 +325,10 @@ class AmpBrightcove extends AMP.BaseElement { } /** - * @param {string} id - * @return {string} - * @private - */ + * @param {string} id + * @return {string} + * @private + */ encodeId_(id) { /* id is either a Brightcove-assigned id, or a customer-generated reference id. reference ids are prefixed 'ref:' and the colon @@ -331,8 +341,12 @@ class AmpBrightcove extends AMP.BaseElement { /** @override */ pauseCallback() { - if (this.iframe_ && this.iframe_.contentWindow && - this.hasAmpSupport_ && this.playing_) { + if ( + this.iframe_ && + this.iframe_.contentWindow && + this.hasAmpSupport_ && + this.playing_ + ) { this.pause(); } } @@ -463,7 +477,6 @@ class AmpBrightcove extends AMP.BaseElement { } } - AMP.extension(TAG, '0.1', AMP => { AMP.registerElement(TAG, AmpBrightcove); }); diff --git a/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js b/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js index 803261c691605..20a9890ddcc27 100644 --- a/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js +++ b/extensions/amp-brightcove/0.1/test/test-amp-brightcove.js @@ -19,174 +19,190 @@ import {VideoEvents} from '../../../../src/video-interface'; import {listenOncePromise} from '../../../../src/event-helper'; import {parseUrlDeprecated} from '../../../../src/url'; - -describes.realWin('amp-brightcove', { - amp: { - extensions: ['amp-brightcove'], +describes.realWin( + 'amp-brightcove', + { + amp: { + extensions: ['amp-brightcove'], + }, }, -}, env => { - let win, doc; - - beforeEach(() => { - win = env.win; - doc = win.document; - }); - - function getBrightcove(attributes, opt_responsive) { - const bc = doc.createElement('amp-brightcove'); - for (const key in attributes) { - bc.setAttribute(key, attributes[key]); + env => { + let win, doc; + + beforeEach(() => { + win = env.win; + doc = win.document; + }); + + function getBrightcove(attributes, opt_responsive) { + const bc = doc.createElement('amp-brightcove'); + for (const key in attributes) { + bc.setAttribute(key, attributes[key]); + } + bc.setAttribute('width', '111'); + bc.setAttribute('height', '222'); + if (opt_responsive) { + bc.setAttribute('layout', 'responsive'); + } + doc.body.appendChild(bc); + return bc + .build() + .then(() => bc.layoutCallback()) + .then(() => bc); } - bc.setAttribute('width', '111'); - bc.setAttribute('height', '222'); - if (opt_responsive) { - bc.setAttribute('layout', 'responsive'); + + function fakePostMessage(bc, info) { + bc.implementation_.handlePlayerMessage_({ + origin: 'https://players.brightcove.net', + source: bc.querySelector('iframe').contentWindow, + data: JSON.stringify(info), + }); } - doc.body.appendChild(bc); - return bc.build().then(() => bc.layoutCallback()).then(() => bc); - } - function fakePostMessage(bc, info) { - bc.implementation_.handlePlayerMessage_({ - origin: 'https://players.brightcove.net', - source: bc.querySelector('iframe').contentWindow, - data: JSON.stringify(info), + it('renders', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.tagName).to.equal('IFRAME'); + expect(iframe.src).to.equal( + 'https://players.brightcove.net/1290862519001/default_default' + + '/index.html?videoId=ref:amp-test-video&playsinline=true' + ); + }); }); - } - it('renders', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.tagName).to.equal('IFRAME'); - expect(iframe.src).to.equal( - 'https://players.brightcove.net/1290862519001/default_default' + - '/index.html?videoId=ref:amp-test-video&playsinline=true'); + it('renders responsively', () => { + return getBrightcove( + { + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.className).to.match(/i-amphtml-fill-content/); + }); }); - }); - - it('renders responsively', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.className).to.match(/i-amphtml-fill-content/); + + it('requires data-account', () => { + expectAsyncConsoleError(/The data-account attribute is required for/, 1); + return getBrightcove({}).should.eventually.be.rejectedWith( + /The data-account attribute is required for/ + ); }); - }); - - it('requires data-account', () => { - expectAsyncConsoleError(/The data-account attribute is required for/, 1); - return getBrightcove({}).should.eventually.be.rejectedWith( - /The data-account attribute is required for/); - }); - - it('removes iframe after unlayoutCallback', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }, true).then(bc => { - const iframe = bc.querySelector('iframe'); - expect(iframe).to.not.be.null; - const obj = bc.implementation_; - obj.unlayoutCallback(); - expect(bc.querySelector('iframe')).to.be.null; - expect(obj.iframe_).to.be.null; + + it('removes iframe after unlayoutCallback', () => { + return getBrightcove( + { + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }, + true + ).then(bc => { + const iframe = bc.querySelector('iframe'); + expect(iframe).to.not.be.null; + const obj = bc.implementation_; + obj.unlayoutCallback(); + expect(bc.querySelector('iframe')).to.be.null; + expect(obj.iframe_).to.be.null; + }); }); - }); - - it('should pass data-param-* attributes to the iframe src', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - 'data-param-my-param': 'hello world', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - const params = parseUrlDeprecated(iframe.src).search.split('&'); - expect(params).to.contain('myParam=hello%20world'); + + it('should pass data-param-* attributes to the iframe src', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + 'data-param-my-param': 'hello world', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + const params = parseUrlDeprecated(iframe.src).search.split('&'); + expect(params).to.contain('myParam=hello%20world'); + }); }); - }); - it('should propagate mutated attributes', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should propagate mutated attributes', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).to.equal( + expect(iframe.src).to.equal( 'https://players.brightcove.net/1290862519001/default_default' + - '/index.html?videoId=ref:amp-test-video&playsinline=true'); + '/index.html?videoId=ref:amp-test-video&playsinline=true' + ); - bc.setAttribute('data-account', '12345'); - bc.setAttribute('data-video-id', 'abcdef'); - bc.mutatedAttributesCallback({ - 'data-account': '12345', - 'data-video-id': 'abcdef', - }); + bc.setAttribute('data-account', '12345'); + bc.setAttribute('data-video-id', 'abcdef'); + bc.mutatedAttributesCallback({ + 'data-account': '12345', + 'data-video-id': 'abcdef', + }); - expect(iframe.src).to.equal('https://players.brightcove.net/' + - '12345/default_default/index.html?videoId=abcdef&playsinline=true'); + expect(iframe.src).to.equal( + 'https://players.brightcove.net/' + + '12345/default_default/index.html?videoId=abcdef&playsinline=true' + ); + }); }); - }); - - it('should give precedence to playlist id', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - 'data-playlist-id': 'ref:test-playlist', - }).then(bc => { - const iframe = bc.querySelector('iframe'); - - expect(iframe.src).to.contain('playlistId=ref:test-playlist'); - expect(iframe.src).not.to.contain('videoId'); + + it('should give precedence to playlist id', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + 'data-playlist-id': 'ref:test-playlist', + }).then(bc => { + const iframe = bc.querySelector('iframe'); + + expect(iframe.src).to.contain('playlistId=ref:test-playlist'); + expect(iframe.src).not.to.contain('videoId'); + }); }); - }); - it('should allow both playlist and video id to be unset', () => { - return getBrightcove({ - 'data-account': '1290862519001', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should allow both playlist and video id to be unset', () => { + return getBrightcove({ + 'data-account': '1290862519001', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).not.to.contain('&playlistId'); - expect(iframe.src).not.to.contain('&videoId'); + expect(iframe.src).not.to.contain('&playlistId'); + expect(iframe.src).not.to.contain('&videoId'); + }); }); - }); - it('should pass referrer', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-referrer': 'COUNTER', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should pass referrer', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-referrer': 'COUNTER', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).to.contain('referrer=1'); + expect(iframe.src).to.contain('referrer=1'); + }); }); - }); - it('should force playsinline', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - 'data-param-playsinline': 'false', - }).then(bc => { - const iframe = bc.querySelector('iframe'); + it('should force playsinline', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + 'data-param-playsinline': 'false', + }).then(bc => { + const iframe = bc.querySelector('iframe'); - expect(iframe.src).to.contain('playsinline=true'); + expect(iframe.src).to.contain('playsinline=true'); + }); }); - }); - - it('should forward events', () => { - return getBrightcove({ - 'data-account': '1290862519001', - 'data-video-id': 'ref:amp-test-video', - }).then(bc => { - return Promise.resolve() + + it('should forward events', () => { + return getBrightcove({ + 'data-account': '1290862519001', + 'data-video-id': 'ref:amp-test-video', + }).then(bc => { + return Promise.resolve() .then(() => { const p = listenOncePromise(bc, VideoEvents.LOAD); fakePostMessage(bc, {event: 'ready', muted: false, playing: false}); @@ -194,32 +210,47 @@ describes.realWin('amp-brightcove', { }) .then(() => { const p = listenOncePromise(bc, VideoEvents.AD_START); - fakePostMessage(bc, - {event: 'ads-ad-started', muted: false, playing: false}); + fakePostMessage(bc, { + event: 'ads-ad-started', + muted: false, + playing: false, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.AD_END); - fakePostMessage(bc, - {event: 'ads-ad-ended', muted: false, playing: false}); + fakePostMessage(bc, { + event: 'ads-ad-ended', + muted: false, + playing: false, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.PLAYING); - fakePostMessage(bc, - {event: 'playing', muted: false, playing: true}); + fakePostMessage(bc, { + event: 'playing', + muted: false, + playing: true, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.MUTED); - fakePostMessage(bc, - {event: 'volumechange', muted: true, playing: true}); + fakePostMessage(bc, { + event: 'volumechange', + muted: true, + playing: true, + }); return p; }) .then(() => { const p = listenOncePromise(bc, VideoEvents.UNMUTED); - fakePostMessage(bc, - {event: 'volumechange', muted: false, playing: true}); + fakePostMessage(bc, { + event: 'volumechange', + muted: false, + playing: true, + }); return p; }) .then(() => { @@ -232,6 +263,7 @@ describes.realWin('amp-brightcove', { fakePostMessage(bc, {event: 'ended', muted: false, playing: false}); return p; }); + }); }); - }); -}); + } +); diff --git a/extensions/amp-byside-content/0.1/amp-byside-content.js b/extensions/amp-byside-content/0.1/amp-byside-content.js index 3f566d6fae6d6..561117df387bc 100644 --- a/extensions/amp-byside-content/0.1/amp-byside-content.js +++ b/extensions/amp-byside-content/0.1/amp-byside-content.js @@ -65,7 +65,6 @@ const DEFAULT_LANG_ = 'pt'; let iframeCount_ = 0; export class AmpBysideContent extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -111,9 +110,11 @@ export class AmpBysideContent extends AMP.BaseElement { /** @const {function()} */ this.boundUpdateSize_ = debounce( - this.win, data => { - this.updateSize_(/** @type {Object} */ (data)); - }, 100 + this.win, + data => { + this.updateSize_(/** @type {Object} */ (data)); + }, + 100 ); } @@ -135,28 +136,28 @@ export class AmpBysideContent extends AMP.BaseElement { /** @override */ buildCallback() { this.webcareId_ = userAssert( - this.element.getAttribute('data-webcare-id'), - 'The data-webcare-id attribute is required for <%s> %s', - TAG_, - this.element + this.element.getAttribute('data-webcare-id'), + 'The data-webcare-id attribute is required for <%s> %s', + TAG_, + this.element ); this.label_ = userAssert( - this.element.getAttribute('data-label'), - 'The data-label attribute is required for <%s> %s', - TAG_, - this.element + this.element.getAttribute('data-label'), + 'The data-label attribute is required for <%s> %s', + TAG_, + this.element ); - this.webcareZone_ = (this.element.getAttribute('data-webcare-zone') || - DEFAULT_WEBCARE_ZONE_); - this.channel_ = (this.element.getAttribute('data-channel') || ''); - this.lang_ = (this.element.getAttribute('data-lang') || DEFAULT_LANG_); - this.fid_ = (this.element.getAttribute('data-fid') || ''); + this.webcareZone_ = + this.element.getAttribute('data-webcare-zone') || DEFAULT_WEBCARE_ZONE_; + this.channel_ = this.element.getAttribute('data-channel') || ''; + this.lang_ = this.element.getAttribute('data-lang') || DEFAULT_LANG_; + this.fid_ = this.element.getAttribute('data-fid') || ''; this.origin_ = this.composeOrigin_(); - this.baseUrl_ = this.origin_ + '/BWA' + - encodeURIComponent(this.webcareId_) + '/amp/'; + this.baseUrl_ = + this.origin_ + '/BWA' + encodeURIComponent(this.webcareId_) + '/amp/'; } /** @override */ @@ -183,8 +184,8 @@ export class AmpBysideContent extends AMP.BaseElement { iframe.setAttribute('allowtransparency', 'true'); iframe.setAttribute('allowfullscreen', 'true'); iframe.setAttribute( - 'sandbox', - 'allow-scripts allow-same-origin allow-popups' + 'sandbox', + 'allow-scripts allow-same-origin allow-popups' ); setStyles(iframe, { @@ -194,30 +195,33 @@ export class AmpBysideContent extends AMP.BaseElement { this.element.appendChild(this.getOverflowElement_()); this.applyFillContent(iframe); - return this.composeSrcUrl_().then(src => { - this.iframeSrc_ = assertHttpsUrl(src, this.element, this.getName_()); - iframe.src = this.iframeSrc_; + return this.composeSrcUrl_() + .then(src => { + this.iframeSrc_ = assertHttpsUrl(src, this.element, this.getName_()); + iframe.src = this.iframeSrc_; - const unlisten = listenFor(iframe, 'embed-size', this.boundUpdateSize_); - this.unlisteners_.push(unlisten); + const unlisten = listenFor(iframe, 'embed-size', this.boundUpdateSize_); + this.unlisteners_.push(unlisten); - this.element.appendChild(iframe); + this.element.appendChild(iframe); - return (this.iframePromise_ = this.loadPromise(iframe)); - }).then(() => { - this.getVsync().mutate(() => { - setStyles(iframe, { - 'opacity': 1, + return (this.iframePromise_ = this.loadPromise(iframe)); + }) + .then(() => { + this.getVsync().mutate(() => { + setStyles(iframe, { + 'opacity': 1, + }); }); }); - }); } /** @private */ composeOrigin_() { - const subDomain = this.webcareZone_ === MAIN_WEBCARE_ZONE_ ? - MAIN_WEBCARE_ZONE_SUBDOMAIN_ : - this.webcareZone_; + const subDomain = + this.webcareZone_ === MAIN_WEBCARE_ZONE_ + ? MAIN_WEBCARE_ZONE_SUBDOMAIN_ + : this.webcareZone_; return 'https://' + encodeURIComponent(subDomain) + '.' + BYSIDE_DOMAIN_; } @@ -231,7 +235,7 @@ export class AmpBysideContent extends AMP.BaseElement { 'bwch': this.channel_ || '', 'lang': this.lang_ || '', 'fid': this.fid_ || '', - 'bwit': (this.fid_ ? 'I' : 'A'), + 'bwit': this.fid_ ? 'I' : 'A', 'tuid': 'CLIENT_ID(byside_webcare_tuid)', 'suid': '', 'puid': 'PAGE_VIEW_IDpTIMESTAMP', @@ -272,16 +276,28 @@ export class AmpBysideContent extends AMP.BaseElement { */ getOverflowElement_() { const doc = /** @type {!Document} */ (this.element.ownerDocument); - const overflow = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-overflow', - 'overflow': '', - })); - const overflowContent = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-overflow-content', - })); - const arrow = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-arrow-down', - })); + const overflow = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-overflow', + 'overflow': '', + }) + ); + const overflowContent = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-overflow-content', + }) + ); + const arrow = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-arrow-down', + }) + ); overflowContent.appendChild(arrow); overflow.appendChild(overflowContent); @@ -291,12 +307,20 @@ export class AmpBysideContent extends AMP.BaseElement { /** @return {!Element} @private */ createBySideLoader_() { const doc = /** @type {!Document} */ (this.element.ownerDocument); - const loadingContainer = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-loading-container', - })); - const loadingAnimation = createElementWithAttributes(doc, 'div', dict({ - 'class': 'i-amphtml-byside-content-loading-animation', - })); + const loadingContainer = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-loading-container', + }) + ); + const loadingAnimation = createElementWithAttributes( + doc, + 'div', + dict({ + 'class': 'i-amphtml-byside-content-loading-animation', + }) + ); loadingContainer.appendChild(loadingAnimation); return loadingContainer; @@ -316,33 +340,41 @@ export class AmpBysideContent extends AMP.BaseElement { const height = parseInt(data['height'], 10); if (!isNaN(height)) { newHeight = Math.max( - height + (this.element./*OK*/offsetHeight - - this.iframe_./*OK*/offsetHeight), - height); + height + + (this.element./*OK*/ offsetHeight - + this.iframe_./*OK*/ offsetHeight), + height + ); } const width = parseInt(data['width'], 10); if (!isNaN(width)) { newWidth = Math.max( - width + (this.element./*OK*/offsetWidth - - this.iframe_./*OK*/offsetWidth), - width); + width + + (this.element./*OK*/ offsetWidth - this.iframe_./*OK*/ offsetWidth), + width + ); } if (newHeight !== undefined || newWidth !== undefined) { - this.attemptChangeSize(newHeight, newWidth).then(() => { - if (newHeight !== undefined) { - this.element.setAttribute('height', newHeight); - } - if (newWidth !== undefined) { - this.element.setAttribute('width', newWidth); - } - }, () => {}); + this.attemptChangeSize(newHeight, newWidth).then( + () => { + if (newHeight !== undefined) { + this.element.setAttribute('height', newHeight); + } + if (newWidth !== undefined) { + this.element.setAttribute('width', newWidth); + } + }, + () => {} + ); } else { - user().warn(TAG_, - 'Ignoring embed-size request because ' - + 'no width or height value is provided', - this.element); + user().warn( + TAG_, + 'Ignoring embed-size request because ' + + 'no width or height value is provided', + this.element + ); } }); } diff --git a/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js b/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js index d71b1b0083222..3ebe84906e8f8 100644 --- a/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js +++ b/extensions/amp-byside-content/0.1/test/test-amp-byside-content.js @@ -17,173 +17,184 @@ import '../amp-byside-content'; import {mockServiceForDoc} from '../../../../testing/test-helper'; -describes.realWin('amp-byside-content', { - amp: { - extensions: ['amp-byside-content'], +describes.realWin( + 'amp-byside-content', + { + amp: { + extensions: ['amp-byside-content'], + }, + ampAdCss: true, }, - ampAdCss: true, -}, env => { - let win, doc, urlMock; - - beforeEach(() => { - win = env.win; - doc = win.document; - urlMock = mockServiceForDoc(sandbox, env.ampdoc, 'url-replace', [ - 'expandUrlAsync', - ]); - }); - - function getElement(attributes, opt_responsive, opt_beforeLayoutCallback) { - const elem = doc.createElement('amp-byside-content'); - - for (const key in attributes) { - elem.setAttribute(key, attributes[key]); - } + env => { + let win, doc, urlMock; + + beforeEach(() => { + win = env.win; + doc = win.document; + urlMock = mockServiceForDoc(sandbox, env.ampdoc, 'url-replace', [ + 'expandUrlAsync', + ]); + }); - elem.setAttribute('width', '640'); - elem.setAttribute('height', '360'); - if (opt_responsive) { - elem.setAttribute('layout', 'responsive'); - } + function getElement(attributes, opt_responsive, opt_beforeLayoutCallback) { + const elem = doc.createElement('amp-byside-content'); - doc.body.appendChild(elem); - return elem.build().then(() => { - urlMock.expandUrlAsync - .returns(Promise.resolve(elem.implementation_.baseUrl_)) - .withArgs(sinon.match.any); - if (opt_beforeLayoutCallback) { - opt_beforeLayoutCallback(elem); + for (const key in attributes) { + elem.setAttribute(key, attributes[key]); } - return elem.layoutCallback(); - }).then(() => elem); - } + elem.setAttribute('width', '640'); + elem.setAttribute('height', '360'); + if (opt_responsive) { + elem.setAttribute('layout', 'responsive'); + } - function testIframe(elem) { - const iframe = elem.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.getAttribute('frameborder')).to.equal('0'); - expect(iframe.className).to.match(/i-amphtml-fill-content/); - expect(iframe.fakeSrc).to.satisfy(src => { - return src.startsWith(elem.implementation_.baseUrl_); - }); - } + doc.body.appendChild(elem); + return elem + .build() + .then(() => { + urlMock.expandUrlAsync + .returns(Promise.resolve(elem.implementation_.baseUrl_)) + .withArgs(sinon.match.any); + if (opt_beforeLayoutCallback) { + opt_beforeLayoutCallback(elem); + } + + return elem.layoutCallback(); + }) + .then(() => elem); + } - it('renders', () => { - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'amp-simple', - }).then(elem => { - testIframe(elem); + function testIframe(elem) { + const iframe = elem.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.getAttribute('frameborder')).to.equal('0'); + expect(iframe.className).to.match(/i-amphtml-fill-content/); + expect(iframe.fakeSrc).to.satisfy(src => { + return src.startsWith(elem.implementation_.baseUrl_); + }); + } + + it('renders', () => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'amp-simple', + }).then(elem => { + testIframe(elem); + }); }); - }); - it('requires data-label', () => { - return allowConsoleError(() => { return getElement({ - 'data-webcare-id': 'D6604AE5D0', - }).should.eventually.be.rejectedWith( - /The data-label attribute is required for/); + it('requires data-label', () => { + return allowConsoleError(() => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + }).should.eventually.be.rejectedWith( + /The data-label attribute is required for/ + ); + }); }); - }); - it('requires data-webcare-id', () => { - return allowConsoleError(() => { return getElement({ - 'data-label': 'placeholder-label', - }).should.eventually.be.rejectedWith( - /The data-webcare-id attribute is required for/); + it('requires data-webcare-id', () => { + return allowConsoleError(() => { + return getElement({ + 'data-label': 'placeholder-label', + }).should.eventually.be.rejectedWith( + /The data-webcare-id attribute is required for/ + ); + }); }); - }); - - it('generates correct default origin', () => { - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }).then(elem => { - expect(elem.implementation_.origin_).to.equal( + + it('generates correct default origin', () => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }).then(elem => { + expect(elem.implementation_.origin_).to.equal( 'https://webcare.byside.com' - ); + ); + }); }); - }); - it('generates correct provided webcare zone', () => { - const webcareZone = 'sa1'; + it('generates correct provided webcare zone', () => { + const webcareZone = 'sa1'; - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - 'data-webcare-zone': webcareZone, - }).then(elem => { - expect(elem.implementation_.origin_).to.equal( + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + 'data-webcare-zone': webcareZone, + }).then(elem => { + expect(elem.implementation_.origin_).to.equal( 'https://' + webcareZone + '.byside.com' - ); + ); + }); }); - }); - - it('should create a loading animation', () => { - return getElement({ - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }).then(elem => { - const loader = elem.querySelector( + + it('should create a loading animation', () => { + return getElement({ + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }).then(elem => { + const loader = elem.querySelector( '.i-amphtml-byside-content-loading-animation' - ); - expect(loader).to.not.be.null; + ); + expect(loader).to.not.be.null; + }); }); - }); - it('builds a placeholder loading animation without inserting iframe', () => { - const attributes = { - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }; + it('builds a placeholder loading animation without inserting iframe', () => { + const attributes = { + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }; - return getElement(attributes, true, elem => { - const placeholder = elem.querySelector('[placeholder]'); - const iframe = elem.querySelector('iframe'); - expect(iframe).to.be.null; - expect(placeholder).to.not.have.display('none'); - }).then(elem => { - const placeholder = elem.querySelector('[placeholder]'); - elem.getVsync = () => { - return { - mutate: fn => fn(), + return getElement(attributes, true, elem => { + const placeholder = elem.querySelector('[placeholder]'); + const iframe = elem.querySelector('iframe'); + expect(iframe).to.be.null; + expect(placeholder).to.not.have.display('none'); + }).then(elem => { + const placeholder = elem.querySelector('[placeholder]'); + elem.getVsync = () => { + return { + mutate: fn => fn(), + }; }; - }; - // test iframe - testIframe(elem); + // test iframe + testIframe(elem); - // test placeholder too - elem.implementation_.iframePromise_.then(() => { - expect(placeholder).to.have.display('none'); + // test placeholder too + elem.implementation_.iframePromise_.then(() => { + expect(placeholder).to.have.display('none'); + }); }); }); - }); - it('passes down sandbox attribute to iframe', () => { - const sandbox = 'allow-scripts allow-same-origin allow-popups'; - const attributes = { - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }; + it('passes down sandbox attribute to iframe', () => { + const sandbox = 'allow-scripts allow-same-origin allow-popups'; + const attributes = { + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }; - return getElement(attributes, false).then(elem => { - const iframe = elem.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.getAttribute('sandbox')).to.equal(sandbox); + return getElement(attributes, false).then(elem => { + const iframe = elem.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.getAttribute('sandbox')).to.equal(sandbox); + }); }); - }); - it('sets scrollable atribute in iframe', () => { - const attributes = { - 'data-webcare-id': 'D6604AE5D0', - 'data-label': 'placeholder-label', - }; + it('sets scrollable atribute in iframe', () => { + const attributes = { + 'data-webcare-id': 'D6604AE5D0', + 'data-label': 'placeholder-label', + }; - return getElement(attributes, false).then(elem => { - const iframe = elem.querySelector('iframe'); - expect(iframe).to.not.be.null; - expect(iframe.getAttribute('scrolling')).to.equal('no'); + return getElement(attributes, false).then(elem => { + const iframe = elem.querySelector('iframe'); + expect(iframe).to.not.be.null; + expect(iframe.getAttribute('scrolling')).to.equal('no'); + }); }); - }); -}); + } +); diff --git a/extensions/amp-call-tracking/0.1/amp-call-tracking.js b/extensions/amp-call-tracking/0.1/amp-call-tracking.js index 1a8e34ca9e0b1..3a3dc08e0e7e1 100644 --- a/extensions/amp-call-tracking/0.1/amp-call-tracking.js +++ b/extensions/amp-call-tracking/0.1/amp-call-tracking.js @@ -19,14 +19,12 @@ import {Services} from '../../../src/services'; import {assertHttpsUrl} from '../../../src/url'; import {user, userAssert} from '../../../src/log'; - /** * Bookkeeps all unique URL requests so that no URL is called twice. * @type {!Object} */ let cachedResponsePromises_ = {}; - /** * Fetches vendor response. * @param {!Window} win @@ -36,25 +34,22 @@ let cachedResponsePromises_ = {}; function fetch_(win, url) { if (!(url in cachedResponsePromises_)) { cachedResponsePromises_[url] = Services.xhrFor(win) - .fetchJson(url, {credentials: 'include'}) - .then(res => res.json()); + .fetchJson(url, {credentials: 'include'}) + .then(res => res.json()); } return cachedResponsePromises_[url]; } - /** @visibleForTesting */ export function clearResponseCacheForTesting() { cachedResponsePromises_ = {}; } - /** * Implementation of `amp-call-tracking` component. See * {@link ../amp-call-tracking.md} for the spec. */ export class AmpCallTracking extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -74,7 +69,9 @@ export class AmpCallTracking extends AMP.BaseElement { /** @override */ buildCallback() { this.configUrl_ = assertHttpsUrl( - this.element.getAttribute('config'), this.element); + this.element.getAttribute('config'), + this.element + ); this.hyperlink_ = this.element.firstElementChild; } @@ -82,21 +79,22 @@ export class AmpCallTracking extends AMP.BaseElement { /** @override */ layoutCallback() { return Services.urlReplacementsForDoc(this.element) - .expandUrlAsync(user().assertString(this.configUrl_)) - .then(url => fetch_(this.win, url)) - .then(data => { - userAssert('phoneNumber' in data, - 'Response must contain a non-empty phoneNumber field %s', - this.element); - - this.hyperlink_.setAttribute('href', `tel:${data['phoneNumber']}`); - this.hyperlink_.textContent = data['formattedPhoneNumber'] - || data['phoneNumber']; - }); + .expandUrlAsync(user().assertString(this.configUrl_)) + .then(url => fetch_(this.win, url)) + .then(data => { + userAssert( + 'phoneNumber' in data, + 'Response must contain a non-empty phoneNumber field %s', + this.element + ); + + this.hyperlink_.setAttribute('href', `tel:${data['phoneNumber']}`); + this.hyperlink_.textContent = + data['formattedPhoneNumber'] || data['phoneNumber']; + }); } } - AMP.extension('amp-call-tracking', '0.1', AMP => { AMP.registerElement('amp-call-tracking', AmpCallTracking); }); diff --git a/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js b/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js index 4ea093fd8946e..30f626dae49d1 100644 --- a/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js +++ b/extensions/amp-call-tracking/0.1/test/test-amp-call-tracking.js @@ -18,113 +18,126 @@ import '../amp-call-tracking'; import {Services} from '../../../../src/services'; import {clearResponseCacheForTesting} from '../amp-call-tracking'; - -describes.realWin('amp-call-tracking', { - amp: { - extensions: ['amp-call-tracking'], +describes.realWin( + 'amp-call-tracking', + { + amp: { + extensions: ['amp-call-tracking'], + }, }, -}, env => { - let win, doc; - let xhrMock; - - beforeEach(() => { - win = env.win; - doc = win.document; - xhrMock = sandbox.mock(Services.xhrFor(win)); - }); + env => { + let win, doc; + let xhrMock; + + beforeEach(() => { + win = env.win; + doc = win.document; + xhrMock = sandbox.mock(Services.xhrFor(win)); + }); - afterEach(() => { - clearResponseCacheForTesting(); - xhrMock.verify(); - }); + afterEach(() => { + clearResponseCacheForTesting(); + xhrMock.verify(); + }); - function getCallTrackingEl(config = {}) { - const hyperlink = doc.createElement('a'); - const callTrackingEl = doc.createElement('amp-call-tracking'); + function getCallTrackingEl(config = {}) { + const hyperlink = doc.createElement('a'); + const callTrackingEl = doc.createElement('amp-call-tracking'); - callTrackingEl.setAttribute('config', config.url); + callTrackingEl.setAttribute('config', config.url); - hyperlink.setAttribute('href', `tel:${config.defaultNumber}`); - hyperlink.textContent = config.defaultContent || config.defaultNumber; + hyperlink.setAttribute('href', `tel:${config.defaultNumber}`); + hyperlink.textContent = config.defaultContent || config.defaultNumber; - callTrackingEl.appendChild(hyperlink); + callTrackingEl.appendChild(hyperlink); - doc.body.appendChild(callTrackingEl); - return callTrackingEl.build().then(() => { - return callTrackingEl.layoutCallback(); - }).then(() => callTrackingEl); - } + doc.body.appendChild(callTrackingEl); + return callTrackingEl + .build() + .then(() => { + return callTrackingEl.layoutCallback(); + }) + .then(() => callTrackingEl); + } - function mockXhrResponse(url, response) { - xhrMock + function mockXhrResponse(url, response) { + xhrMock .expects('fetchJson') .withArgs(url, sandbox.match(init => init.credentials == 'include')) - .returns(Promise.resolve({ - json() { - return Promise.resolve(response); - }, - })); - } - - function expectHyperlinkToBe(callTrackingEl, href, textContent) { - const hyperlink = callTrackingEl.getRealChildren()[0]; - - expect(hyperlink.getAttribute('href')).to.equal(href); - expect(hyperlink.textContent).to.equal(textContent); - } - - it('should render with required response fields', () => { - const url = 'https://example.com/test.json'; - - const defaultNumber = '123456'; - const defaultContent = '+1 (23) 456'; - - const phoneNumber = '981234'; - - mockXhrResponse(url, {phoneNumber}); - - return getCallTrackingEl({ - url, - defaultNumber, - defaultContent, - }).then(callTrackingEl => { - expectHyperlinkToBe(callTrackingEl, `tel:${phoneNumber}`, phoneNumber); + .returns( + Promise.resolve({ + json() { + return Promise.resolve(response); + }, + }) + ); + } + + function expectHyperlinkToBe(callTrackingEl, href, textContent) { + const hyperlink = callTrackingEl.getRealChildren()[0]; + + expect(hyperlink.getAttribute('href')).to.equal(href); + expect(hyperlink.textContent).to.equal(textContent); + } + + it('should render with required response fields', () => { + const url = 'https://example.com/test.json'; + + const defaultNumber = '123456'; + const defaultContent = '+1 (23) 456'; + + const phoneNumber = '981234'; + + mockXhrResponse(url, {phoneNumber}); + + return getCallTrackingEl({ + url, + defaultNumber, + defaultContent, + }).then(callTrackingEl => { + expectHyperlinkToBe(callTrackingEl, `tel:${phoneNumber}`, phoneNumber); + }); }); - }); - it('should use all response fields to compose hyperlink', () => { - const url = 'https://example.com/test.json'; + it('should use all response fields to compose hyperlink', () => { + const url = 'https://example.com/test.json'; - const defaultNumber = '123456'; - const defaultContent = '+1 (23) 456'; + const defaultNumber = '123456'; + const defaultContent = '+1 (23) 456'; - const phoneNumber = '187654321'; - const formattedPhoneNumber = '+1 (87) 654-321'; + const phoneNumber = '187654321'; + const formattedPhoneNumber = '+1 (87) 654-321'; - mockXhrResponse(url, {phoneNumber, formattedPhoneNumber}); + mockXhrResponse(url, {phoneNumber, formattedPhoneNumber}); - return getCallTrackingEl({ - url, - defaultNumber, - defaultContent, - }).then(callTrackingEl => { - expectHyperlinkToBe( - callTrackingEl, `tel:${phoneNumber}`, formattedPhoneNumber); + return getCallTrackingEl({ + url, + defaultNumber, + defaultContent, + }).then(callTrackingEl => { + expectHyperlinkToBe( + callTrackingEl, + `tel:${phoneNumber}`, + formattedPhoneNumber + ); + }); }); - }); - it('should fail when response does not contain a phoneNumber field', () => { - const url = 'https://example.com/test.json'; + it('should fail when response does not contain a phoneNumber field', () => { + const url = 'https://example.com/test.json'; - const defaultNumber = '123456'; - const defaultContent = '+1 (23) 456'; + const defaultNumber = '123456'; + const defaultContent = '+1 (23) 456'; - mockXhrResponse(url, {}); + mockXhrResponse(url, {}); - return expect(getCallTrackingEl({ - url, - defaultNumber, - defaultContent, - })).rejectedWith(/Response must contain a non-empty phoneNumber field/); - }); -}); + return expect( + getCallTrackingEl({ + url, + defaultNumber, + defaultContent, + }) + ).rejectedWith(/Response must contain a non-empty phoneNumber field/); + }); + } +); diff --git a/extensions/amp-carousel/0.1/base-carousel.js b/extensions/amp-carousel/0.1/base-carousel.js index 4154adf87c1a0..a31ae2a449219 100644 --- a/extensions/amp-carousel/0.1/base-carousel.js +++ b/extensions/amp-carousel/0.1/base-carousel.js @@ -20,7 +20,6 @@ import {Services} from '../../../src/services'; * @abstract */ export class BaseCarousel extends AMP.BaseElement { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -38,8 +37,8 @@ export class BaseCarousel extends AMP.BaseElement { /** @override */ buildCallback() { const input = Services.inputFor(this.win); - this.showControls_ = input.isMouseDetected() || - this.element.hasAttribute('controls'); + this.showControls_ = + input.isMouseDetected() || this.element.hasAttribute('controls'); if (this.showControls_) { this.element.classList.add('i-amphtml-carousel-has-controls'); @@ -174,9 +173,13 @@ export class BaseCarousel extends AMP.BaseElement { this.mutateElement(() => { this.element.classList.remove(className); this.prevButton_.classList.toggle( - 'i-amphtml-screen-reader', !this.showControls_); + 'i-amphtml-screen-reader', + !this.showControls_ + ); this.nextButton_.classList.toggle( - 'i-amphtml-screen-reader', !this.showControls_); + 'i-amphtml-screen-reader', + !this.showControls_ + ); }); }, 4000); }); @@ -199,8 +202,10 @@ export class BaseCarousel extends AMP.BaseElement { * @protected */ getNextButtonTitle() { - return this.element.getAttribute('data-next-button-aria-label') - || 'Next item in carousel'; + return ( + this.element.getAttribute('data-next-button-aria-label') || + 'Next item in carousel' + ); } /** @@ -208,8 +213,10 @@ export class BaseCarousel extends AMP.BaseElement { * @protected */ getPrevButtonTitle() { - return this.element.getAttribute('data-prev-button-aria-label') - || 'Previous item in carousel'; + return ( + this.element.getAttribute('data-prev-button-aria-label') || + 'Previous item in carousel' + ); } /** @override */ diff --git a/extensions/amp-carousel/0.1/base-slides.js b/extensions/amp-carousel/0.1/base-slides.js index ce87e6193a71f..6bc7fb8ec32b1 100644 --- a/extensions/amp-carousel/0.1/base-slides.js +++ b/extensions/amp-carousel/0.1/base-slides.js @@ -21,7 +21,6 @@ import {isFiniteNumber} from '../../../src/types'; import {userAssert} from '../../../src/log'; export class BaseSlides extends BaseCarousel { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -74,14 +73,18 @@ export class BaseSlides extends BaseCarousel { this.setupAutoplay_(); } - this.registerAction('toggleAutoplay', invocation => { - const {args} = invocation; - if (args && args['toggleOn'] !== undefined) { - this.toggleAutoplay_(args['toggleOn']); - } else { - this.toggleAutoplay_(!this.hasAutoplay_); - } - }, ActionTrust.LOW); + this.registerAction( + 'toggleAutoplay', + invocation => { + const {args} = invocation; + if (args && args['toggleOn'] !== undefined) { + this.toggleAutoplay_(args['toggleOn']); + } else { + this.toggleAutoplay_(!this.hasAutoplay_); + } + }, + ActionTrust.LOW + ); } /** @@ -129,18 +132,18 @@ export class BaseSlides extends BaseCarousel { updateViewportState(unusedInViewport) {} /** - * Checks if a carousel is eligible to loop, regardless of the loop attribute. - * @return {boolean} - * @protected - */ + * Checks if a carousel is eligible to loop, regardless of the loop attribute. + * @return {boolean} + * @protected + */ isLoopingEligible() { return false; } /** - * Sets up the `autoplay` configuration. - * @private - */ + * Sets up the `autoplay` configuration. + * @private + */ setupAutoplay_() { const delayValue = Number(this.element.getAttribute('delay')); // If it isn't a number and is not greater than 0 then don't assign @@ -162,19 +165,20 @@ export class BaseSlides extends BaseCarousel { } /** - * Starts the autoplay delay if allowed. - * @private - */ + * Starts the autoplay delay if allowed. + * @private + */ autoplay_() { if (!this.shouldAutoplay_ || this.autoplayLoops_ == 0) { return; } this.clearAutoplay(); - this.autoplayTimeoutId_ = /** @type {number} */ ( - Services.timerFor(this.win).delay( - this.go.bind( - this, /* dir */ 1, /* animate */ true, /* autoplay */ true), - this.autoplayDelay_)); + this.autoplayTimeoutId_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay( + this.go.bind(this, /* dir */ 1, /* animate */ true, /* autoplay */ true), + this.autoplayDelay_ + )); } /** @@ -204,9 +208,9 @@ export class BaseSlides extends BaseCarousel { } /** - * Clear the autoplay timer. - * @protected - */ + * Clear the autoplay timer. + * @protected + */ clearAutoplay() { if (this.autoplayTimeoutId_ !== null) { Services.timerFor(this.win).cancel(this.autoplayTimeoutId_); @@ -215,9 +219,9 @@ export class BaseSlides extends BaseCarousel { } /** - * Remove autoplay. - * @protected - */ + * Remove autoplay. + * @protected + */ removeAutoplay() { this.clearAutoplay(); if (this.loopAdded_) { diff --git a/extensions/amp-carousel/0.1/scrollable-carousel.js b/extensions/amp-carousel/0.1/scrollable-carousel.js index 545e5d37c35e2..266d996af234a 100644 --- a/extensions/amp-carousel/0.1/scrollable-carousel.js +++ b/extensions/amp-carousel/0.1/scrollable-carousel.js @@ -27,7 +27,6 @@ import {numeric} from '../../../src/transition'; const TAG = 'amp-scrollable-carousel'; export class AmpScrollableCarousel extends BaseCarousel { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -64,8 +63,9 @@ export class AmpScrollableCarousel extends BaseCarousel { this.container_.classList.add('i-amphtml-scrollable-carousel-container'); this.element.appendChild(this.container_); - this.useLayers_ = isExperimentOn(this.win, 'layers') && isExperimentOn( - this.win, 'layers-prioritization'); + this.useLayers_ = + isExperimentOn(this.win, 'layers') && + isExperimentOn(this.win, 'layers-prioritization'); this.cells_.forEach(cell => { if (!this.useLayers_) { @@ -78,16 +78,19 @@ export class AmpScrollableCarousel extends BaseCarousel { this.cancelTouchEvents_(); - this.container_.addEventListener( - 'scroll', this.scrollHandler_.bind(this)); + this.container_.addEventListener('scroll', this.scrollHandler_.bind(this)); - this.registerAction('goToSlide', invocation => { - const {args} = invocation; - if (args) { - const index = parseInt(args['index'], 10); - this.goToSlide_(index); - } - }, ActionTrust.LOW); + this.registerAction( + 'goToSlide', + invocation => { + const {args} = invocation; + if (args) { + const index = parseInt(args['index'], 10); + this.goToSlide_(index); + } + }, + ActionTrust.LOW + ); if (this.useLayers_) { this.declareLayer(this.container_); @@ -122,15 +125,20 @@ export class AmpScrollableCarousel extends BaseCarousel { if (!animate) { this.commitSwitch_(newPos); - this.container_./*OK*/scrollLeft = newPos; + this.container_./*OK*/ scrollLeft = newPos; } else { /** @const {!TransitionDef} */ const interpolate = numeric(oldPos, newPos); const duration = 200; const curve = 'ease-in-out'; - Animation.animate(this.element, pos => { - this.container_./*OK*/scrollLeft = interpolate(pos); - }, duration, curve).thenAlways(() => { + Animation.animate( + this.element, + pos => { + this.container_./*OK*/ scrollLeft = interpolate(pos); + }, + duration, + curve + ).thenAlways(() => { this.commitSwitch_(newPos); }); } @@ -164,9 +172,14 @@ export class AmpScrollableCarousel extends BaseCarousel { const interpolate = numeric(oldPos, newPos); const duration = 200; const curve = 'ease-in-out'; - Animation.animate(this.element, pos => { - this.container_./*OK*/scrollLeft = interpolate(pos); - }, duration, curve).thenAlways(() => { + Animation.animate( + this.element, + pos => { + this.container_./*OK*/ scrollLeft = interpolate(pos); + }, + duration, + curve + ).thenAlways(() => { this.commitSwitch_(newPos); }); }; @@ -179,9 +192,9 @@ export class AmpScrollableCarousel extends BaseCarousel { * @param {number} index */ getPosForSlideIndex_(index) { - const containerWidth = this.element./*OK*/offsetWidth; - const targetPosition = this.cells_[index]./*OK*/offsetLeft; - const targetWidth = this.cells_[index]./*OK*/offsetWidth; + const containerWidth = this.element./*OK*/ offsetWidth; + const targetPosition = this.cells_[index]./*OK*/ offsetLeft; + const targetWidth = this.cells_[index]./*OK*/ offsetWidth; return targetPosition - (containerWidth - targetWidth) / 2; } @@ -190,7 +203,7 @@ export class AmpScrollableCarousel extends BaseCarousel { * @private */ scrollHandler_() { - const currentScrollLeft = this.container_./*OK*/scrollLeft; + const currentScrollLeft = this.container_./*OK*/ scrollLeft; this.pos_ = currentScrollLeft; if (this.scrollTimerId_ === null) { @@ -203,20 +216,29 @@ export class AmpScrollableCarousel extends BaseCarousel { * @private */ waitForScroll_(startingScrollLeft) { - this.scrollTimerId_ = /** @type {number} */ ( - Services.timerFor(this.win).delay(() => { - // TODO(yuxichen): test out the threshold for identifying fast scrolling - if (Math.abs(startingScrollLeft - this.pos_) < 30) { - dev().fine(TAG, 'slow scrolling: %s - %s', - startingScrollLeft, this.pos_); - this.scrollTimerId_ = null; - this.commitSwitch_(this.pos_); - } else { - dev().fine(TAG, 'fast scrolling: %s - %s', - startingScrollLeft, this.pos_); - this.waitForScroll_(this.pos_); - } - }, 100)); + this.scrollTimerId_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay(() => { + // TODO(yuxichen): test out the threshold for identifying fast scrolling + if (Math.abs(startingScrollLeft - this.pos_) < 30) { + dev().fine( + TAG, + 'slow scrolling: %s - %s', + startingScrollLeft, + this.pos_ + ); + this.scrollTimerId_ = null; + this.commitSwitch_(this.pos_); + } else { + dev().fine( + TAG, + 'fast scrolling: %s - %s', + startingScrollLeft, + this.pos_ + ); + this.waitForScroll_(this.pos_); + } + }, 100)); } /** @@ -244,14 +266,13 @@ export class AmpScrollableCarousel extends BaseCarousel { */ nextPos_(pos, dir) { // TODO(jridgewell): this could be using cached values from Layers. - const containerWidth = this.element./*OK*/offsetWidth; - const fullWidth = this.container_./*OK*/scrollWidth; + const containerWidth = this.element./*OK*/ offsetWidth; + const fullWidth = this.container_./*OK*/ scrollWidth; const newPos = pos + dir * containerWidth; if (newPos < 0) { return 0; } - if (fullWidth >= containerWidth && - newPos > fullWidth - containerWidth) { + if (fullWidth >= containerWidth && newPos > fullWidth - containerWidth) { return fullWidth - containerWidth; } return newPos; @@ -266,8 +287,10 @@ export class AmpScrollableCarousel extends BaseCarousel { const containerWidth = this.getLayoutWidth(); for (let i = 0; i < this.cells_.length; i++) { const cell = this.cells_[i]; - if (cell./*OK*/offsetLeft + cell./*OK*/offsetWidth >= pos && - cell./*OK*/offsetLeft <= pos + containerWidth) { + if ( + cell./*OK*/ offsetLeft + cell./*OK*/ offsetWidth >= pos && + cell./*OK*/ offsetLeft <= pos + containerWidth + ) { callback(cell); } } @@ -327,7 +350,7 @@ export class AmpScrollableCarousel extends BaseCarousel { hasNext() { // TODO(jridgewell): this could be using cached values from Layers. const containerWidth = this.getLayoutWidth(); - const scrollWidth = this.container_./*OK*/scrollWidth; + const scrollWidth = this.container_./*OK*/ scrollWidth; const maxPos = Math.max(scrollWidth - containerWidth, 0); return this.pos_ != maxPos; } diff --git a/extensions/amp-carousel/0.1/slidescroll.js b/extensions/amp-carousel/0.1/slidescroll.js index 682941da970e7..6b44a35f3b204 100644 --- a/extensions/amp-carousel/0.1/slidescroll.js +++ b/extensions/amp-carousel/0.1/slidescroll.js @@ -52,7 +52,6 @@ const CUSTOM_SNAP_TIMEOUT = 100; const TAG = 'AMP-CAROUSEL'; export class AmpSlideScroll extends BaseSlides { - /** @param {!AmpElement} element */ constructor(element) { super(element); @@ -132,9 +131,13 @@ export class AmpSlideScroll extends BaseSlides { // - Non iOS devices with the flag turned off. /** @private {boolean} */ this.shouldDisableCssSnap_ = startsWith( - Services.platformFor(this.win).getIosVersionString(), - '10.3') ? true : this.isIos_ ? false : !isExperimentOn( - this.win, 'amp-carousel-chrome-scroll-snap'); + Services.platformFor(this.win).getIosVersionString(), + '10.3' + ) + ? true + : this.isIos_ + ? false + : !isExperimentOn(this.win, 'amp-carousel-chrome-scroll-snap'); } /** @override */ @@ -147,8 +150,8 @@ export class AmpSlideScroll extends BaseSlides { this.vsync_ = this.getVsync(); this.action_ = Services.actionServiceForDoc(this.element); - this.hasNativeSnapPoints_ = ( - getStyle(this.element, 'scrollSnapType') != undefined); + this.hasNativeSnapPoints_ = + getStyle(this.element, 'scrollSnapType') != undefined; if (this.shouldDisableCssSnap_) { this.hasNativeSnapPoints_ = false; @@ -185,7 +188,8 @@ export class AmpSlideScroll extends BaseSlides { this.slides_.forEach((slide, index) => { this.dataSlideIdArr_.push( - slide.getAttribute('data-slide-id') || index.toString()); + slide.getAttribute('data-slide-id') || index.toString() + ); this.setAsOwner(slide); slide.classList.add('amp-carousel-slide'); @@ -201,20 +205,30 @@ export class AmpSlideScroll extends BaseSlides { this.cancelTouchEvents_(); this.slidesContainer_.addEventListener( - 'scroll', this.scrollHandler_.bind(this)); + 'scroll', + this.scrollHandler_.bind(this) + ); this.slidesContainer_.addEventListener( - 'touchmove', this.touchMoveHandler_.bind(this)); + 'touchmove', + this.touchMoveHandler_.bind(this) + ); this.slidesContainer_.addEventListener( - 'touchend', this.touchEndHandler_.bind(this)); - - this.registerAction('goToSlide', invocation => { - const {args} = invocation; - if (args) { - this.goToSlide(args['index']); - } - }, ActionTrust.LOW); + 'touchend', + this.touchEndHandler_.bind(this) + ); + + this.registerAction( + 'goToSlide', + invocation => { + const {args} = invocation; + if (args) { + this.goToSlide(args['index']); + } + }, + ActionTrust.LOW + ); } /** @override */ @@ -254,19 +268,21 @@ export class AmpSlideScroll extends BaseSlides { if (this.scrollTimeout_) { Services.timerFor(this.win).cancel(this.scrollTimeout_); } - const timeout = this.shouldDisableCssSnap_ ? IOS_TOUCH_TIMEOUT + const timeout = this.shouldDisableCssSnap_ + ? IOS_TOUCH_TIMEOUT : NATIVE_TOUCH_TIMEOUT; // Timer that detects scroll end and/or end of snap scroll. - this.touchEndTimeout_ = /** @type {number} */ ( - Services.timerFor(this.win).delay(() => { - const currentScrollLeft = this.slidesContainer_./*OK*/scrollLeft; + this.touchEndTimeout_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay(() => { + const currentScrollLeft = this.slidesContainer_./*OK*/ scrollLeft; - if (this.snappingInProgress_) { - return; - } - this.updateOnScroll_(currentScrollLeft); - this.touchEndTimeout_ = null; - }, timeout)); + if (this.snappingInProgress_) { + return; + } + this.updateOnScroll_(currentScrollLeft); + this.touchEndTimeout_ = null; + }, timeout)); } this.hasTouchMoved_ = false; } @@ -281,7 +297,9 @@ export class AmpSlideScroll extends BaseSlides { // TODO(sparhami) #19259 Tracks a more generic way to do this. Remove once // we have something better. const isScaled = closestAncestorElementBySelector( - this.element, '[i-amphtml-scale-animation]'); + this.element, + '[i-amphtml-scale-animation]' + ); if (isScaled) { return Promise.resolve(); } @@ -290,7 +308,9 @@ export class AmpSlideScroll extends BaseSlides { this.showSlide_(this.initialSlideIndex_); } else { const index = user().assertNumber( - this.slideIndex_, 'E#19457 this.slideIndex_'); + this.slideIndex_, + 'E#19457 this.slideIndex_' + ); const scrollLeft = this.getScrollLeftForIndex_(index); // When display is toggled on a partcular media or element resizes, // it will need to be re-laid-out. This is only needed when the slide @@ -299,7 +319,7 @@ export class AmpSlideScroll extends BaseSlides { this.scheduleLayout(this.slides_[index]); // Reset scrollLeft on orientationChange or anything that changes the // size of the carousel. - this.slidesContainer_./*OK*/scrollLeft = scrollLeft; + this.slidesContainer_./*OK*/ scrollLeft = scrollLeft; this.previousScrollLeft_ = scrollLeft; } return Promise.resolve(); @@ -315,8 +335,11 @@ export class AmpSlideScroll extends BaseSlides { updateViewportState(inViewport) { if (this.slideIndex_ !== null) { this.updateInViewport( - this.slides_[user().assertNumber(this.slideIndex_, - 'E#19457 this.slideIndex_')], inViewport); + this.slides_[ + user().assertNumber(this.slideIndex_, 'E#19457 this.slideIndex_') + ], + inViewport + ); } } @@ -335,17 +358,15 @@ export class AmpSlideScroll extends BaseSlides { if (this.slideIndex_ !== null) { const hasNext = this.hasNext(); const hasPrev = this.hasPrev(); - if ((dir == 1 && hasNext) || - (dir == -1 && hasPrev)) { - let newIndex = (dev().assertNumber(this.slideIndex_)) + dir; + if ((dir == 1 && hasNext) || (dir == -1 && hasPrev)) { + let newIndex = dev().assertNumber(this.slideIndex_) + dir; if (newIndex == -1) { newIndex = this.noOfSlides_ - 1; } else if (newIndex >= this.noOfSlides_) { newIndex = 0; } if (animate) { - const currentScrollLeft = - (dir == 1 && !hasPrev) ? 0 : this.slideWidth_; + const currentScrollLeft = dir == 1 && !hasPrev ? 0 : this.slideWidth_; this.customSnap_(currentScrollLeft, dir); } else { this.showSlideAndTriggerAction_(newIndex); @@ -364,27 +385,31 @@ export class AmpSlideScroll extends BaseSlides { Services.timerFor(this.win).cancel(this.scrollTimeout_); } - const currentScrollLeft = this.slidesContainer_./*OK*/scrollLeft; + const currentScrollLeft = this.slidesContainer_./*OK*/ scrollLeft; if (!this.isIos_) { this.handleCustomElasticScroll_(currentScrollLeft); } if (!this.touchEndTimeout_) { - const timeout = this.hasNativeSnapPoints_ ? NATIVE_SNAP_TIMEOUT : ( - this.isIos_ ? IOS_CUSTOM_SNAP_TIMEOUT : CUSTOM_SNAP_TIMEOUT); + const timeout = this.hasNativeSnapPoints_ + ? NATIVE_SNAP_TIMEOUT + : this.isIos_ + ? IOS_CUSTOM_SNAP_TIMEOUT + : CUSTOM_SNAP_TIMEOUT; // Timer that detects scroll end and/or end of snap scroll. - this.scrollTimeout_ = /** @type {number} */ ( - Services.timerFor(this.win).delay(() => { - if (this.snappingInProgress_) { - return; - } - if (this.hasNativeSnapPoints_) { - this.updateOnScroll_(currentScrollLeft); - } else { - this.customSnap_(currentScrollLeft); - } - }, timeout)); + this.scrollTimeout_ = /** @type {number} */ (Services.timerFor( + this.win + ).delay(() => { + if (this.snappingInProgress_) { + return; + } + if (this.hasNativeSnapPoints_) { + this.updateOnScroll_(currentScrollLeft); + } else { + this.customSnap_(currentScrollLeft); + } + }, timeout)); } this.previousScrollLeft_ = currentScrollLeft; } @@ -394,15 +419,19 @@ export class AmpSlideScroll extends BaseSlides { * @param {number} currentScrollLeft scrollLeft value of the slides container. */ handleCustomElasticScroll_(currentScrollLeft) { - const scrollWidth = this.slidesContainer_./*OK*/scrollWidth; - if (this.elasticScrollState_ == -1 && - currentScrollLeft >= this.previousScrollLeft_) { + const scrollWidth = this.slidesContainer_./*OK*/ scrollWidth; + if ( + this.elasticScrollState_ == -1 && + currentScrollLeft >= this.previousScrollLeft_ + ) { // Elastic Scroll is reversing direction take control. this.customSnap_(currentScrollLeft).then(() => { this.elasticScrollState_ = 0; }); - } else if (this.elasticScrollState_ == 1 && - currentScrollLeft <= this.previousScrollLeft_) { + } else if ( + this.elasticScrollState_ == 1 && + currentScrollLeft <= this.previousScrollLeft_ + ) { // Elastic Scroll is reversing direction take control. this.customSnap_(currentScrollLeft).then(() => { this.elasticScrollState_ = 0; @@ -410,7 +439,7 @@ export class AmpSlideScroll extends BaseSlides { } else if (currentScrollLeft < 0) { // Direction = -1. this.elasticScrollState_ = -1; - } else if ((currentScrollLeft + this.slideWidth_) > scrollWidth) { + } else if (currentScrollLeft + this.slideWidth_ > scrollWidth) { // Direction = +1. this.elasticScrollState_ = 1; } else { @@ -437,8 +466,7 @@ export class AmpSlideScroll extends BaseSlides { diff = opt_forceDir; } - if (diff == 1 || - (diff != -1 && diff == -1 * (this.noOfSlides_ - 1))) { + if (diff == 1 || (diff != -1 && diff == -1 * (this.noOfSlides_ - 1))) { // Move fwd. toScrollLeft = hasPrev ? this.slideWidth_ * 2 : this.slideWidth_; } else if (diff == -1 || diff == this.noOfSlides_ - 1) { @@ -480,11 +508,19 @@ export class AmpSlideScroll extends BaseSlides { let newIndex = this.slideIndex_ + updateValue; if (this.shouldLoop) { - newIndex = (newIndex < 0) ? this.noOfSlides_ - 1 : - (newIndex >= this.noOfSlides_) ? 0 : newIndex; + newIndex = + newIndex < 0 + ? this.noOfSlides_ - 1 + : newIndex >= this.noOfSlides_ + ? 0 + : newIndex; } else { - newIndex = (newIndex < 0) ? 0 : - (newIndex >= this.noOfSlides_) ? this.noOfSlides_ - 1 : newIndex; + newIndex = + newIndex < 0 + ? 0 + : newIndex >= this.noOfSlides_ + ? this.noOfSlides_ - 1 + : newIndex; } return newIndex; } @@ -496,8 +532,9 @@ export class AmpSlideScroll extends BaseSlides { * @private */ getButtonSuffixFormat_() { - return this.element.getAttribute('data-button-count-format') || - '(%s of %s)'; + return ( + this.element.getAttribute('data-button-count-format') || '(%s of %s)' + ); } /** @@ -508,8 +545,12 @@ export class AmpSlideScroll extends BaseSlides { getButtonTitleSuffix_(buttonIndex) { const index = String(buttonIndex + 1); const count = String(this.noOfSlides_); - return ' ' + this.getButtonSuffixFormat_().replace('%s', index) - .replace('%s', count); + return ( + ' ' + + this.getButtonSuffixFormat_() + .replace('%s', index) + .replace('%s', count) + ); } /** @@ -588,8 +629,11 @@ export class AmpSlideScroll extends BaseSlides { * @private */ getPrevIndex_(currentIndex) { - return (currentIndex - 1 >= 0) ? currentIndex - 1 : - (this.shouldLoop) ? this.noOfSlides_ - 1 : null; + return currentIndex - 1 >= 0 + ? currentIndex - 1 + : this.shouldLoop + ? this.noOfSlides_ - 1 + : null; } /** @@ -599,8 +643,11 @@ export class AmpSlideScroll extends BaseSlides { * @private */ getNextIndex_(currentIndex) { - return (currentIndex + 1 < this.noOfSlides_) ? currentIndex + 1 : - (this.shouldLoop) ? 0 : null; + return currentIndex + 1 < this.noOfSlides_ + ? currentIndex + 1 + : this.shouldLoop + ? 0 + : null; } /** @@ -614,9 +661,11 @@ export class AmpSlideScroll extends BaseSlides { showSlide_(newIndex) { const {noOfSlides_} = this; newIndex = dev().assertNumber(newIndex); - if (newIndex < 0 || - newIndex >= noOfSlides_ || - this.slideIndex_ == newIndex) { + if ( + newIndex < 0 || + newIndex >= noOfSlides_ || + this.slideIndex_ == newIndex + ) { return false; } const prevIndex = this.getPrevIndex_(newIndex); @@ -631,15 +680,22 @@ export class AmpSlideScroll extends BaseSlides { showIndexArr.push(nextIndex); } if (this.slideIndex_ !== null) { - this.updateInViewport(this.slides_[ - user().assertNumber(this.slideIndex_, 'E#19457 this.slideIndex_')], - false); + this.updateInViewport( + this.slides_[ + user().assertNumber(this.slideIndex_, 'E#19457 this.slideIndex_') + ], + false + ); } const newSlideInView = this.slides_[newIndex]; if (newSlideInView === undefined) { - dev().error(TAG, 'Attempting to access a non-existant slide %s / %s', - newIndex, noOfSlides_); + dev().error( + TAG, + 'Attempting to access a non-existant slide %s / %s', + newIndex, + noOfSlides_ + ); return false; } this.updateInViewport(newSlideInView, true); @@ -657,8 +713,9 @@ export class AmpSlideScroll extends BaseSlides { this.slides_[showIndex].setAttribute('aria-hidden', 'true'); } }); - this.slidesContainer_./*OK*/scrollLeft = - this.getScrollLeftForIndex_(newIndex); + this.slidesContainer_./*OK*/ scrollLeft = this.getScrollLeftForIndex_( + newIndex + ); this.triggerAnalyticsEvent_(newIndex); this.slideIndex_ = newIndex; // If we have a specified number of autoplay loops and @@ -687,9 +744,11 @@ export class AmpSlideScroll extends BaseSlides { if (slideChanged) { const name = 'slideChange'; - const event = - createCustomEvent(this.win, `slidescroll.${name}`, - dict({'index': newIndex})); + const event = createCustomEvent( + this.win, + `slidescroll.${name}`, + dict({'index': newIndex}) + ); this.action_.trigger(this.element, name, event, ActionTrust.HIGH); this.element.dispatchCustomEvent(name, {index: newIndex}); @@ -731,8 +790,9 @@ export class AmpSlideScroll extends BaseSlides { if (this.shouldLoop) { setStyle(this.slideWrappers_[i], 'order', ''); } - dev().assertElement(this.slideWrappers_[i]).classList - .remove(SHOWN_CSS_CLASS); + dev() + .assertElement(this.slideWrappers_[i]) + .classList.remove(SHOWN_CSS_CLASS); this.slides_[i].removeAttribute('aria-hidden'); } // Pause if not the current slide @@ -758,9 +818,14 @@ export class AmpSlideScroll extends BaseSlides { const curve = bezierCurve(0.8, 0, 0.6, 1); // ease-in const duration = 80; const slidesContainer = dev().assertElement(this.slidesContainer_); - return Animation.animate(slidesContainer, pos => { - this.slidesContainer_./*OK*/scrollLeft = interpolate(pos); - }, duration, curve).thenAlways(); + return Animation.animate( + slidesContainer, + pos => { + this.slidesContainer_./*OK*/ scrollLeft = interpolate(pos); + }, + duration, + curve + ).thenAlways(); } /** @@ -794,8 +859,9 @@ export class AmpSlideScroll extends BaseSlides { } } const fromSlide = - this.slideIndex_ === null ? - 'null' : this.dataSlideIdArr_[dev().assertNumber(this.slideIndex_)]; + this.slideIndex_ === null + ? 'null' + : this.dataSlideIdArr_[dev().assertNumber(this.slideIndex_)]; const vars = dict({ 'fromSlide': fromSlide, diff --git a/extensions/amp-carousel/0.1/test/test-base-slide.js b/extensions/amp-carousel/0.1/test/test-base-slide.js index 433c1cab9fdff..f9f303e00fd9a 100644 --- a/extensions/amp-carousel/0.1/test/test-base-slide.js +++ b/extensions/amp-carousel/0.1/test/test-base-slide.js @@ -48,7 +48,6 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { let autoplaySpy; let clearAutoplaySpy; - beforeEach(() => { win = env.win; doc = win.document; @@ -60,16 +59,16 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { setupAutoplaySpy = sandbox.spy(BaseSlides.prototype, 'setupAutoplay_'); buildButtonsSpy = sandbox.spy(BaseSlides.prototype, 'buildButtons'); setupGesturesSpy = sandbox.spy(BaseSlides.prototype, 'setupGestures'); - setControlsStateSpy = - sandbox.spy(BaseSlides.prototype, 'setControlsState'); + setControlsStateSpy = sandbox.spy(BaseSlides.prototype, 'setControlsState'); hintControlsSpy = sandbox.spy(BaseSlides.prototype, 'hintControls'); autoplaySpy = sandbox.spy(BaseSlides.prototype, 'autoplay_'); clearAutoplaySpy = sandbox.spy(BaseSlides.prototype, 'clearAutoplay'); - onViewportCallbackSpy = - sandbox.spy(BaseSlides.prototype, 'onViewportCallback'); + onViewportCallbackSpy = sandbox.spy( + BaseSlides.prototype, + 'onViewportCallback' + ); }); - function setElement(options) { const element = doc.createElement('div'); if (options.loop) { @@ -89,9 +88,7 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { return element; } - class TestCarousel extends BaseSlides { - /** @override */ buildSlides() { buildSlidesSpy(); @@ -119,9 +116,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { } it('should do the right buildCallback processing', () => { - const carouselLoopOnly = new TestCarousel(setElement({ - loop: true, - })); + const carouselLoopOnly = new TestCarousel( + setElement({ + loop: true, + }) + ); carouselLoopOnly.buildCallback(); @@ -132,9 +131,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { expect(setupGesturesSpy).to.be.calledOnce; expect(setControlsStateSpy).to.be.calledOnce; - const carouselAutoplayOnly = new TestCarousel(setElement({ - autoplay: true, - })); + const carouselAutoplayOnly = new TestCarousel( + setElement({ + autoplay: true, + }) + ); carouselAutoplayOnly.buildCallback(); @@ -145,10 +146,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { expect(setupGesturesSpy).to.have.callCount(2); expect(setControlsStateSpy).to.have.callCount(2); - const carouselAutoplayWithLoop = new TestCarousel(setElement({ - loop: true, - autoplay: true, - })); + const carouselAutoplayWithLoop = new TestCarousel( + setElement({ + loop: true, + autoplay: true, + }) + ); carouselAutoplayWithLoop.buildCallback(); @@ -161,10 +164,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should handle viewportCallback when in viewport', () => { - const carousel = new TestCarousel(setElement({ - loop: true, - autoplay: true, - })); + const carousel = new TestCarousel( + setElement({ + loop: true, + autoplay: true, + }) + ); carousel.viewportCallback(true); expect(onViewportCallbackSpy).to.have.been.calledWith(true); @@ -174,10 +179,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should handle viewportCallback when not in viewport', () => { - const carousel = new TestCarousel(setElement({ - loop: true, - autoplay: true, - })); + const carousel = new TestCarousel( + setElement({ + loop: true, + autoplay: true, + }) + ); carousel.viewportCallback(false); expect(onViewportCallbackSpy).to.have.been.calledWith(false); @@ -187,9 +194,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should setup autoplay with no delay set', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + }) + ); carousel.autoplayDelay_ = 5000; expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.setupAutoplay_(); @@ -200,10 +209,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should setup autoplay with specified number of loops', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - autoplayLoops: 5, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + autoplayLoops: 5, + }) + ); expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.buildCallback(); expect(carousel.element.hasAttribute('loop')).to.be.true; @@ -212,12 +223,13 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { expect(carousel.shouldLoop).to.be.true; }); - it('should setup autoplay with delay set', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 3000, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 3000, + }) + ); carousel.autoplayDelay_ = 5000; expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.setupAutoplay_(); @@ -228,10 +240,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should setup autoplay with delay set lower', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.autoplayDelay_ = 5000; expect(carousel.element.hasAttribute('loop')).to.be.false; carousel.setupAutoplay_(); @@ -242,10 +256,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should start timer on autoplay', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); @@ -254,9 +270,11 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should not start timer on when there is no autoplay', () => { - const carousel = new TestCarousel(setElement({ - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); @@ -265,10 +283,12 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('should clear timeout', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); @@ -280,62 +300,83 @@ describes.fakeWin('BaseSlides', {amp: true}, env => { }); it('toggle autoPlay status using speficied value & autoplay=true', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); expect(carousel.shouldAutoplay_).to.be.true; const args = {'toggleOn': false}; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.false; args['toggleOn'] = true; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.true; }); it('toggle autoPlay status using speficied value & autoplay=false', () => { - const carousel = new TestCarousel(setElement({ - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + delay: 300, + }) + ); carousel.buildCallback(); expect(carousel.shouldAutoplay_).to.be.false; const args = {'toggleOn': true}; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.true; args['toggleOn'] = false; - carousel.executeAction( - {method: 'toggleAutoplay', args, satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + args, + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.false; }); it('toggle autoPlay status without speficied value & autoplay=true', () => { - const carousel = new TestCarousel(setElement({ - autoplay: true, - delay: 300, - })); + const carousel = new TestCarousel( + setElement({ + autoplay: true, + delay: 300, + }) + ); carousel.buildCallback(); carousel.autoplay_(); expect(carousel.shouldAutoplay_).to.be.true; - carousel.executeAction( - {method: 'toggleAutoplay', satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.false; - carousel.executeAction( - {method: 'toggleAutoplay', satisfiesTrust: () => true}); + carousel.executeAction({ + method: 'toggleAutoplay', + satisfiesTrust: () => true, + }); expect(carousel.shouldAutoplay_).to.be.true; }); - }); diff --git a/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js b/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js index 8a43fa6b2ee3f..dc923e8ef8a1a 100644 --- a/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js +++ b/extensions/amp-carousel/0.1/test/test-scrollable-carousel.js @@ -16,289 +16,387 @@ import '../amp-carousel'; - -describes.realWin('test-scrollable-carousel', { - amp: { - extensions: ['amp-carousel'], +describes.realWin( + 'test-scrollable-carousel', + { + amp: { + extensions: ['amp-carousel'], + }, }, -}, env => { - let win, doc; - - beforeEach(() => { - win = env.win; - doc = win.document; - env.iframe.width = '300'; - env.iframe.height = '200'; - }); - - function getAmpScrollableCarousel() { - const imgUrl = 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + + env => { + let win, doc; + + beforeEach(() => { + win = env.win; + doc = win.document; + env.iframe.width = '300'; + env.iframe.height = '200'; + }); + + function getAmpScrollableCarousel() { + const imgUrl = + 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + 'Rf78IofLb9QjS5_0mqsY1zEFc=w300-h200-no'; - const carouselElement = doc.createElement('amp-carousel'); - carouselElement.setAttribute('width', '300'); - carouselElement.setAttribute('height', '100'); - - const slideCount = 7; - for (let i = 0; i < slideCount; i++) { - const img = document.createElement('amp-img'); - img.setAttribute('src', imgUrl); - img.setAttribute('width', '120'); - img.setAttribute('height', '100'); - img.style.width = '120px'; - img.style.height = '100px'; - img.id = 'img-' + i; - carouselElement.appendChild(img); + const carouselElement = doc.createElement('amp-carousel'); + carouselElement.setAttribute('width', '300'); + carouselElement.setAttribute('height', '100'); + + const slideCount = 7; + for (let i = 0; i < slideCount; i++) { + const img = document.createElement('amp-img'); + img.setAttribute('src', imgUrl); + img.setAttribute('width', '120'); + img.setAttribute('height', '100'); + img.style.width = '120px'; + img.style.height = '100px'; + img.id = 'img-' + i; + carouselElement.appendChild(img); + } + + doc.body.appendChild(carouselElement); + return carouselElement + .build() + .then(() => { + carouselElement.updateLayoutBox({ + top: 0, + left: 0, + width: 300, + height: 100, + }); + return carouselElement.layoutCallback(); + }) + .then(() => carouselElement); } - doc.body.appendChild(carouselElement); - return carouselElement.build().then(() => { - carouselElement.updateLayoutBox( - {top: 0, left: 0, width: 300, height: 100}); - return carouselElement.layoutCallback(); - }).then(() => carouselElement); + it( + 'should initialize correctly: create container, build initial slides ' + + 'and show control buttons', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // create container + expect( + carousel.getElementsByClassName( + 'i-amphtml-scrollable-carousel-container' + ).length + ).to.equal(1); + const container = carousel.getElementsByClassName( + 'i-amphtml-scrollable-carousel-container' + )[0]; + const containerStyle = win.getComputedStyle(container, null); + + expect(containerStyle.getPropertyValue('overflow-x')).to.equal( + 'auto' + ); + expect(containerStyle.getPropertyValue('overflow-y')).to.equal( + 'hidden' + ); + expect(containerStyle.getPropertyValue('white-space')).to.equal( + 'nowrap' + ); + + // build child slides + const carouselSlideEls = container.getElementsByClassName( + 'amp-carousel-slide' + ); + expect(carouselSlideEls.length).to.equal(7); + expect(carouselSlideEls[0]).to.have.display('inline-block'); + + // show control buttons correctly + expect(impl.hasPrev()).to.be.false; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on next button and the ' + + 'space to the right is MORE than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the next button + impl.goCallback(1, /*animate*/ false); + + // scroll to the correct position + expect(impl.container_./*OK*/ scrollLeft).to.equal(300); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[1], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[0]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[1]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.callCount(3); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[4]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[5]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[6]); + + // set control buttons correctly + expect(impl.hasPrev()).to.be.true; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on next button and the ' + + 'space to the right is LESS than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // click on the next button the first time + impl.goCallback(1, /*animate*/ false); + + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the next button the second time + impl.goCallback(1, /*animate*/ false); + + // scroll to the correct position + // note the correct scrollLeft is not 600 (300 * 2) but 588 (888 - 300) + expect(impl.container_./*OK*/ scrollLeft).to.equal(588); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[5], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[6], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[2]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[5]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[6]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.not.been.called; + + // set control buttons correctly + expect(impl.hasPrev()).to.be.true; + expect(impl.hasNext()).to.be.false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .true; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on previous button and the ' + + 'space to the left is MORE than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // click on the next button twice to reach the right end + // scrollLeft after second click is 588 + impl.goCallback(1, /*animate*/ false); + impl.goCallback(1, /*animate*/ false); + + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the previous button + impl.goCallback(-1, /*animate*/ false); + + // scroll to the correct position + expect(impl.container_./*OK*/ scrollLeft).to.equal(288); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[5], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[6], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[5]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[6]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.callCount(3); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[0]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[1]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[2]); + + // set control buttons correctly + expect(impl.hasPrev()).to.be.true; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); + + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip( + 'should behave correctly when clicking on previous button and the ' + + 'space to the left is LESS than containerWidth', + () => { + return getAmpScrollableCarousel().then(carousel => { + const impl = carousel.implementation_; + + // click on the next button twice to reach the right end and click on + // the previous button once, scrollLeft after third click is 288 + impl.goCallback(1, /*animate*/ false); + impl.goCallback(1, /*animate*/ false); + impl.goCallback(-1, /*animate*/ false); + + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + + // click on the previous button + impl.goCallback(-1, /*animate*/ false); + + // scroll to the correct position + expect(impl.container_./*OK*/ scrollLeft).to.equal(0); + + // load new slides in viewport + expect(updateInViewportSpy).to.have.callCount(5); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[0], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[1], + true + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[2], + true + ); + + // unload and pause old slides in viewport + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[3], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.cells_[4], + false + ); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[4]); + + // schedule layout for new slides + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[1]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); + + // preload slides in viewport + expect(schedulePreloadSpy).to.have.not.been.called; + + // set control buttons correctly + expect(impl.hasPrev()).to.be.false; + expect(impl.hasNext()).to.be.true; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + }); + } + ); } - - it('should initialize correctly: create container, build initial slides ' + - 'and show control buttons', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // create container - expect(carousel.getElementsByClassName( - 'i-amphtml-scrollable-carousel-container').length).to.equal(1); - const container = carousel.getElementsByClassName( - 'i-amphtml-scrollable-carousel-container')[0]; - const containerStyle = win.getComputedStyle(container, null); - - expect(containerStyle.getPropertyValue('overflow-x')).to.equal('auto'); - expect(containerStyle.getPropertyValue('overflow-y')).to.equal('hidden'); - expect(containerStyle.getPropertyValue('white-space')).to.equal('nowrap'); - - // build child slides - const carouselSlideEls = - container.getElementsByClassName('amp-carousel-slide'); - expect(carouselSlideEls.length).to.equal(7); - expect(carouselSlideEls[0]).to.have.display('inline-block'); - - // show control buttons correctly - expect(impl.hasPrev()).to.be.false; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on next button and the ' + - 'space to the right is MORE than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the next button - impl.goCallback(1, /*animate*/ false); - - // scroll to the correct position - expect(impl.container_./*OK*/scrollLeft).to.equal(300); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[2], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[3], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[4], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[0], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[1], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[0]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[1]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[4]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[5]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[6]); - - // set control buttons correctly - expect(impl.hasPrev()).to.be.true; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on next button and the ' + - 'space to the right is LESS than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // click on the next button the first time - impl.goCallback(1, /*animate*/ false); - - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the next button the second time - impl.goCallback(1, /*animate*/ false); - - // scroll to the correct position - // note the correct scrollLeft is not 600 (300 * 2) but 588 (888 - 300) - expect(impl.container_./*OK*/scrollLeft).to.equal(588); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[4], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[5], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[6], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[2], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[3], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[2]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[5]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[6]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.not.been.called; - - // set control buttons correctly - expect(impl.hasPrev()).to.be.true; - expect(impl.hasNext()).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.true; - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on previous button and the ' + - 'space to the left is MORE than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // click on the next button twice to reach the right end - // scrollLeft after second click is 588 - impl.goCallback(1, /*animate*/ false); - impl.goCallback(1, /*animate*/ false); - - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the previous button - impl.goCallback(-1, /*animate*/ false); - - // scroll to the correct position - expect(impl.container_./*OK*/scrollLeft).to.equal(288); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[2], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[3], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[4], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[5], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[6], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[5]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[6]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[3]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[4]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[0]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[1]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.cells_[2]); - - // set control buttons correctly - expect(impl.hasPrev()).to.be.true; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should behave correctly when clicking on previous button and the ' + - 'space to the left is LESS than containerWidth', () => { - return getAmpScrollableCarousel().then(carousel => { - const impl = carousel.implementation_; - - // click on the next button twice to reach the right end and click on - // the previous button once, scrollLeft after third click is 288 - impl.goCallback(1, /*animate*/ false); - impl.goCallback(1, /*animate*/ false); - impl.goCallback(-1, /*animate*/ false); - - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - - // click on the previous button - impl.goCallback(-1, /*animate*/ false); - - // scroll to the correct position - expect(impl.container_./*OK*/scrollLeft).to.equal(0); - - // load new slides in viewport - expect(updateInViewportSpy).to.have.callCount(5); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[0], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[1], true); - expect(updateInViewportSpy).to.have.been.calledWith(impl.cells_[2], true); - - // unload and pause old slides in viewport - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[3], false); - expect(updateInViewportSpy).to.have.been - .calledWith(impl.cells_[4], false); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[3]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.cells_[4]); - - // schedule layout for new slides - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[0]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[1]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.cells_[2]); - - // preload slides in viewport - expect(schedulePreloadSpy).to.have.not.been.called; - - // set control buttons correctly - expect(impl.hasPrev()).to.be.false; - expect(impl.hasNext()).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); -}); +); diff --git a/extensions/amp-carousel/0.1/test/test-slidescroll.js b/extensions/amp-carousel/0.1/test/test-slidescroll.js index 3cedf1aedd568..dc9902802bfb2 100644 --- a/extensions/amp-carousel/0.1/test/test-slidescroll.js +++ b/extensions/amp-carousel/0.1/test/test-slidescroll.js @@ -16,846 +16,352 @@ import '../amp-carousel'; - -describes.realWin('SlideScroll', { - amp: { - extensions: ['amp-carousel'], +describes.realWin( + 'SlideScroll', + { + amp: { + extensions: ['amp-carousel'], + }, }, -}, env => { - const SHOW_CLASS = 'i-amphtml-slide-item-show'; - let win, doc; - - beforeEach(() => { - win = env.win; - doc = win.document; - env.iframe.width = '1000'; - env.iframe.height = '1000'; - }); - - function getAmpSlideScroll( - opt_hasLooping, opt_slideCount = 5, opt_attachToDom = true, - opt_hasAutoplay = false, opt_autoplayLoops) { - const imgUrl = 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + + env => { + const SHOW_CLASS = 'i-amphtml-slide-item-show'; + let win, doc; + + beforeEach(() => { + win = env.win; + doc = win.document; + env.iframe.width = '1000'; + env.iframe.height = '1000'; + }); + + function getAmpSlideScroll( + opt_hasLooping, + opt_slideCount = 5, + opt_attachToDom = true, + opt_hasAutoplay = false, + opt_autoplayLoops + ) { + const imgUrl = + 'https://lh3.googleusercontent.com/5rcQ32ml8E5ONp9f9-' + 'Rf78IofLb9QjS5_0mqsY1zEFc=w300-h200-no'; - const ampSlideScroll = doc.createElement('amp-carousel'); - ampSlideScroll.setAttribute('type', 'slides'); - ampSlideScroll.setAttribute('width', '400'); - ampSlideScroll.setAttribute('height', '300'); - ampSlideScroll.style.position = 'relative'; - ampSlideScroll.setAttribute('controls', ''); - if (opt_hasLooping) { - ampSlideScroll.setAttribute('loop', ''); - } - if (opt_hasAutoplay) { - if (!opt_autoplayLoops) { - ampSlideScroll.setAttribute('autoplay', ''); - } else { - ampSlideScroll.setAttribute('autoplay', opt_autoplayLoops); + const ampSlideScroll = doc.createElement('amp-carousel'); + ampSlideScroll.setAttribute('type', 'slides'); + ampSlideScroll.setAttribute('width', '400'); + ampSlideScroll.setAttribute('height', '300'); + ampSlideScroll.style.position = 'relative'; + ampSlideScroll.setAttribute('controls', ''); + if (opt_hasLooping) { + ampSlideScroll.setAttribute('loop', ''); + } + if (opt_hasAutoplay) { + if (!opt_autoplayLoops) { + ampSlideScroll.setAttribute('autoplay', ''); + } else { + ampSlideScroll.setAttribute('autoplay', opt_autoplayLoops); + } } - } - for (let i = 0; i < opt_slideCount; i++) { - const img = doc.createElement('amp-img'); - img.setAttribute('src', imgUrl); - img.setAttribute('width', '400'); - img.setAttribute('height', '300'); - // See https://github.com/ampproject/amphtml/issues/3989 - img.style.display = 'inline'; - if (i == 0) { - img.setAttribute('data-slide-id', 'slide-id'); + for (let i = 0; i < opt_slideCount; i++) { + const img = doc.createElement('amp-img'); + img.setAttribute('src', imgUrl); + img.setAttribute('width', '400'); + img.setAttribute('height', '300'); + // See https://github.com/ampproject/amphtml/issues/3989 + img.style.display = 'inline'; + if (i == 0) { + img.setAttribute('data-slide-id', 'slide-id'); + } + ampSlideScroll.appendChild(img); } - ampSlideScroll.appendChild(img); - } - if (opt_attachToDom) { - doc.body.appendChild(ampSlideScroll); - return ampSlideScroll.build().then(() => { - ampSlideScroll.updateLayoutBox( - {top: 0, left: 0, width: 400, height: 300}); - return ampSlideScroll.layoutCallback(); - }).then(() => ampSlideScroll); + if (opt_attachToDom) { + doc.body.appendChild(ampSlideScroll); + return ampSlideScroll + .build() + .then(() => { + ampSlideScroll.updateLayoutBox({ + top: 0, + left: 0, + width: 400, + height: 300, + }); + return ampSlideScroll.layoutCallback(); + }) + .then(() => ampSlideScroll); + } + return Promise.resolve(ampSlideScroll); } - return Promise.resolve(ampSlideScroll); - } - it('should create container and wrappers and show initial slides', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - expect( + it('should create container and wrappers and show initial slides', () => { + return getAmpSlideScroll().then(ampSlideScroll => { + expect( ampSlideScroll.getElementsByClassName('i-amphtml-slides-container') - .length).to.equal(1); - expect( + .length + ).to.equal(1); + expect( ampSlideScroll.querySelectorAll( - '.i-amphtml-slides-container > .i-amphtml-slide-item').length) - .to.equal(5); - expect( - ampSlideScroll.getElementsByClassName('amp-carousel-slide').length) - .to.equal(5); - expect(ampSlideScroll.querySelector('.i-amphtml-slides-container') - .getAttribute('aria-live')).to.equal('polite'); - const impl = ampSlideScroll.implementation_; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - }); - }); - - it('should go to the correct slide on button click', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - - impl.goCallback(1); - expect(showSlideSpy).to.have.been.calledWith(1); - expect(showSlideSpy).to.be.calledOnce; - - impl.goCallback(-1); - expect(showSlideSpy).to.have.been.calledWith(0); - expect(showSlideSpy).to.have.callCount(2); - - impl.goCallback(0); - expect(showSlideSpy).to.have.callCount(2); - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should show the correct slide', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - const hideRestOfTheSlidesSpy = sandbox.spy(impl, 'hideRestOfTheSlides_'); - const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); - const analyticsEventSpy = sandbox.spy(impl, 'analyticsEvent_'); - - expect(impl.showSlide_(-1)).to.be.false; - expect(updateInViewportSpy).to.not.have.been.called; - expect(scheduleLayoutSpy).to.not.have.been.called; - expect(schedulePreloadSpy).to.not.have.been.called; - expect(hideRestOfTheSlidesSpy).to.not.have.been.called; - expect(setControlsStateSpy).to.not.have.been.called; - expect(analyticsEventSpy).to.not.have.been.called; - - expect(impl.showSlide_(5)).to.be.false; - expect(updateInViewportSpy).to.not.have.been.called; - expect(scheduleLayoutSpy).to.not.have.been.called; - expect(schedulePreloadSpy).to.not.have.been.called; - expect(hideRestOfTheSlidesSpy).to.not.have.been.called; - expect(setControlsStateSpy).to.not.have.been.called; - expect(analyticsEventSpy).to.not.have.been.called; - - expect(impl.showSlide_(impl.slideIndex_)).to.be.false; - expect(updateInViewportSpy).to.not.have.been.called; - expect(scheduleLayoutSpy).to.not.have.been.called; - expect(schedulePreloadSpy).to.not.have.been.called; - expect(hideRestOfTheSlidesSpy).to.not.have.been.called; - expect(setControlsStateSpy).to.not.have.been.called; - expect(analyticsEventSpy).to.not.have.been.called; - - expect(impl.showSlide_(1)).to.be.true; - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], true); - expect(updateInViewportSpy).to.have.callCount(2); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[2]); - expect(scheduleLayoutSpy).to.be.calledOnce; - expect(schedulePreloadSpy).to.have.callCount(2); - expect(impl.slideIndex_).to.equal(1); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); - expect(hideRestOfTheSlidesSpy).to.be.calledOnce; - expect(setControlsStateSpy).to.be.calledOnce; - expect(analyticsEventSpy).to.have.callCount(2); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-next', {'fromSlide': 'slide-id', 'toSlide': '1'}); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-change', {'fromSlide': 'slide-id', 'toSlide': '1'}); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[2].getAttribute('aria-hidden')).to.equal('true'); - - expect(impl.showSlide_(0)).to.be.true; - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], true); - expect(updateInViewportSpy).to.have.callCount(4); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); - expect(scheduleLayoutSpy).to.have.callCount(2); - expect(schedulePreloadSpy).to.have.callCount(3); - expect(impl.slideIndex_).to.equal(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(0); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); - expect(hideRestOfTheSlidesSpy).to.have.callCount(2); - expect(setControlsStateSpy).to.have.callCount(2); - expect(analyticsEventSpy).to.have.callCount(4); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-prev', {'fromSlide': '1', 'toSlide': 'slide-id'}); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-change', {'fromSlide': '1', 'toSlide': 'slide-id'}); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - - expect(impl.showSlide_(4)).to.be.true; - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[4], true); - expect(updateInViewportSpy).to.have.callCount(6); - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[3]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[4]); - expect(scheduleLayoutSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.callCount(4); - expect(impl.slideIndex_).to.equal(4); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); - expect(hideRestOfTheSlidesSpy).to.have.callCount(3); - expect(setControlsStateSpy).to.have.callCount(3); - expect(analyticsEventSpy).to.have.callCount(6); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-prev', {'fromSlide': 'slide-id', 'toSlide': '4'}); - expect(analyticsEventSpy).to.have.been.calledWith( - 'amp-carousel-change', {'fromSlide': 'slide-id', 'toSlide': '4'}); - expect(impl.slides_[3].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal(null); - }); - }); - - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should hide the unwanted slides', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const hideRestOfTheSlidesSpy = sandbox.spy(impl, 'hideRestOfTheSlides_'); - - impl.showSlide_(1); - - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.callCount(2); - - impl.showSlide_(0); - - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0,1]); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.callCount(4); - - impl.showSlide_(4); - - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); - - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); - expect(schedulePauseSpy).to.have.callCount(7); - }); - }); - - it('should show/hide the correct controls', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - - impl.showSlide_(1); - expect(impl.hasNext()).to.be.true; - expect(impl.hasPrev()).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - - impl.showSlide_(0); - expect(impl.hasNext()).to.be.true; - expect(impl.hasPrev()).to.be.false; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; - - impl.showSlide_(4); - expect(impl.hasNext()).to.be.false; - expect(impl.hasPrev()).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.true; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; - }); - }); - - it('should set the correct scrollLeft when there is only one slide', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - - impl.noOfSlides_ = 1; - impl.showSlide_(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(0); - }); - }); - - it('should update to the right slide on scroll', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - - impl.vsync_ = { - mutatePromise: cb => { - cb(); - return { - then: cb2 => { - cb2(); - }, - }; - }, - mutate: cb => { - cb(); - }, - }; - - // Move to slide 1 (from slide 0). - impl.showSlide_(1); - expect(showSlideSpy).to.be.calledWith(1); - expect(impl.snappingInProgress_).to.be.false; - - //Move to slide 0 - via scrolling back. - impl.updateOnScroll_(1); - expect(showSlideSpy).to.be.calledWith(0); - expect(impl.slideIndex_).to.equal(0); - - // Try scrolling Fwd and move to slide 1. - impl.updateOnScroll_(401); - expect(showSlideSpy).to.be.calledWith(1); - expect(impl.slideIndex_).to.equal(1); - - - impl.updateOnScroll_(700); - expect(showSlideSpy).to.be.calledWith(2); - expect(impl.slideIndex_).to.equal(2); - - impl.showSlide_(4); - impl.updateOnScroll_(700); - expect(showSlideSpy).to.be.calledWith(4); - expect(impl.slideIndex_).to.equal(4); - }); - }); - - it('should get the correct next slide index for a scrollLeft' , () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - - // Already at slide 0; - expect(impl.getNextSlideIndex_(0)).to.equal(0); - expect(impl.getNextSlideIndex_(100)).to.equal(0); - expect(impl.getNextSlideIndex_(200)).to.equal(1); - expect(impl.getNextSlideIndex_(400)).to.equal(1); - - impl.showSlide_(3); - - expect(impl.getNextSlideIndex_(0)).to.equal(2); - expect(impl.getNextSlideIndex_(100)).to.equal(2); - expect(impl.getNextSlideIndex_(200)).to.equal(3); - expect(impl.getNextSlideIndex_(400)).to.equal(3); - expect(impl.getNextSlideIndex_(500)).to.equal(3); - expect(impl.getNextSlideIndex_(600)).to.equal(4); - expect(impl.getNextSlideIndex_(800)).to.equal(4); - - impl.showSlide_(4); - expect(impl.getNextSlideIndex_(0)).to.equal(3); - expect(impl.getNextSlideIndex_(100)).to.equal(3); - expect(impl.getNextSlideIndex_(200)).to.equal(4); - expect(impl.getNextSlideIndex_(400)).to.equal(4); - }); - }); - - it('should custom snap to the correct slide', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); - - impl.customSnap_(0); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(100); - expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); - impl.customSnap_(200); - expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); - impl.customSnap_(400); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - - impl.showSlide_(3); - - impl.customSnap_(0); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(100); - expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); - impl.customSnap_(200); - expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); - impl.customSnap_(400); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - impl.customSnap_(500); - expect(animateScrollLeftSpy).to.have.been.calledWith(500, 400); - impl.customSnap_(600); - expect(animateScrollLeftSpy).to.have.been.calledWith(600, 800); - impl.customSnap_(800); - expect(animateScrollLeftSpy).to.have.been.calledWith(800, 800); - - impl.showSlide_(4); - - impl.customSnap_(0); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(100); - expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); - impl.customSnap_(200); - expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); - impl.customSnap_(400); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - - impl.showSlide_(0); - - impl.customSnap_(0, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); - impl.customSnap_(0, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - - impl.showSlide_(3); - - impl.customSnap_(400, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); - impl.customSnap_(400, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - - impl.showSlide_(4); - - impl.customSnap_(400, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); - impl.customSnap_(400, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); - }); - }); - - it('should custom snap to the correct slide - special case', () => { - return getAmpSlideScroll(null, 2).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); - - impl.customSnap_(0, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - - impl.showSlide_(1); - - impl.customSnap_(400, -1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); - }); - }); - - it('should handle custom elastic scroll', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const customSnapSpy = sandbox.stub(impl, 'customSnap_').callsFake(() => { - return { - then: cb => { - cb(); - }, - }; + '.i-amphtml-slides-container > .i-amphtml-slide-item' + ).length + ).to.equal(5); + expect( + ampSlideScroll.getElementsByClassName('amp-carousel-slide').length + ).to.equal(5); + expect( + ampSlideScroll + .querySelector('.i-amphtml-slides-container') + .getAttribute('aria-live') + ).to.equal('polite'); + const impl = ampSlideScroll.implementation_; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); }); - - impl.handleCustomElasticScroll_(-10); - expect(impl.elasticScrollState_).to.equal(-1); - impl.previousScrollLeft_ = -10; - impl.handleCustomElasticScroll_(-5); - expect(customSnapSpy).to.have.been.calledWith(-5); - - impl.previousScrollLeft_ = null; - - impl.handleCustomElasticScroll_(410); - expect(impl.elasticScrollState_).to.equal(1); - impl.previousScrollLeft_ = 410; - impl.handleCustomElasticScroll_(405); - expect(customSnapSpy).to.have.been.calledWith(405); }); - }); - - it('should handle layout measures (orientation changes)', async() => { - const ampSlideScroll = await getAmpSlideScroll(); - const impl = ampSlideScroll.implementation_; - const getLayoutWidthStub = sandbox.stub(impl, 'getLayoutWidth'); - - getLayoutWidthStub.returns(200); - impl.onLayoutMeasure(); - expect(getLayoutWidthStub).to.have.been.calledOnce; - expect(impl.slideWidth_).to.equal(200); - - // Show the first slide, make sure the scroll position is correct. - impl.showSlide_(1); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(200); - - // Now do a layout measure letting the component know it changed size. - getLayoutWidthStub.returns(400); - impl.onLayoutMeasure(); - expect(getLayoutWidthStub).to.have.callCount(2); - expect(impl.slideWidth_).to.equal(400); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(200); - - // Make sure the scroll position is correct after layoutCallback. - await impl.layoutCallback(); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(400); - }); - - it('should relayout the current slide on layoutCallback', () => { - return getAmpSlideScroll().then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const scheduleLayoutSpy_ = sandbox.spy(impl, 'scheduleLayout'); - impl.slideIndex_ = null; - impl.layoutCallback(); - expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[0]); - impl.showSlide_(1); - impl.layoutCallback(); - expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[1]); - }); - }); + it('should go to the correct slide on button click', () => { + return getAmpSlideScroll().then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - describe('Looping', () => { - beforeEach(() => { - sandbox = sinon.sandbox; - }); + impl.goCallback(1); + expect(showSlideSpy).to.have.been.calledWith(1); + expect(showSlideSpy).to.be.calledOnce; - afterEach(() => { - sandbox.restore(); - }); + impl.goCallback(-1); + expect(showSlideSpy).to.have.been.calledWith(0); + expect(showSlideSpy).to.have.callCount(2); - it('should create container and wrappers and show initial slides', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; + impl.goCallback(0); + expect(showSlideSpy).to.have.callCount(2); }); }); // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should show the correct slides when looping', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it.skip('should show the correct slide', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - const hideRestOfTheSlidesSpy = - sandbox.spy(impl, 'hideRestOfTheSlides_'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); - - expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - - impl.showSlide_(1); - + const analyticsEventSpy = sandbox.spy(impl, 'analyticsEvent_'); + + expect(impl.showSlide_(-1)).to.be.false; + expect(updateInViewportSpy).to.not.have.been.called; + expect(scheduleLayoutSpy).to.not.have.been.called; + expect(schedulePreloadSpy).to.not.have.been.called; + expect(hideRestOfTheSlidesSpy).to.not.have.been.called; + expect(setControlsStateSpy).to.not.have.been.called; + expect(analyticsEventSpy).to.not.have.been.called; + + expect(impl.showSlide_(5)).to.be.false; + expect(updateInViewportSpy).to.not.have.been.called; + expect(scheduleLayoutSpy).to.not.have.been.called; + expect(schedulePreloadSpy).to.not.have.been.called; + expect(hideRestOfTheSlidesSpy).to.not.have.been.called; + expect(setControlsStateSpy).to.not.have.been.called; + expect(analyticsEventSpy).to.not.have.been.called; + + expect(impl.showSlide_(impl.slideIndex_)).to.be.false; + expect(updateInViewportSpy).to.not.have.been.called; + expect(scheduleLayoutSpy).to.not.have.been.called; + expect(schedulePreloadSpy).to.not.have.been.called; + expect(hideRestOfTheSlidesSpy).to.not.have.been.called; + expect(setControlsStateSpy).to.not.have.been.called; + expect(analyticsEventSpy).to.not.have.been.called; + + expect(impl.showSlide_(1)).to.be.true; expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); + impl.slides_[0], + false + ); expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], true); + impl.slides_[1], + true + ); expect(updateInViewportSpy).to.have.callCount(2); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[2]); expect(scheduleLayoutSpy).to.be.calledOnce; expect(schedulePreloadSpy).to.have.callCount(2); expect(impl.slideIndex_).to.equal(1); - expect(impl.slidesContainer_./*OK*/scrollLeft) - .to.equal(impl.slideWidth_); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); expect(hideRestOfTheSlidesSpy).to.be.calledOnce; expect(setControlsStateSpy).to.be.calledOnce; + expect(analyticsEventSpy).to.have.callCount(2); + expect(analyticsEventSpy).to.have.been.calledWith('amp-carousel-next', { + 'fromSlide': 'slide-id', + 'toSlide': '1', + }); + expect(analyticsEventSpy).to.have.been.calledWith( + 'amp-carousel-change', + {'fromSlide': 'slide-id', 'toSlide': '1'} + ); expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); expect(impl.slides_[2].getAttribute('aria-hidden')).to.equal('true'); - impl.showSlide_(0); - + expect(impl.showSlide_(0)).to.be.true; expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], false); + impl.slides_[1], + false + ); expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], true); + impl.slides_[0], + true + ); expect(updateInViewportSpy).to.have.callCount(4); - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[4]); expect(scheduleLayoutSpy).to.have.callCount(2); - expect(schedulePreloadSpy).to.have.callCount(4); + expect(schedulePreloadSpy).to.have.callCount(3); expect(impl.slideIndex_).to.equal(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(400); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(0); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); expect(hideRestOfTheSlidesSpy).to.have.callCount(2); expect(setControlsStateSpy).to.have.callCount(2); - expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); + expect(analyticsEventSpy).to.have.callCount(4); + expect(analyticsEventSpy).to.have.been.calledWith('amp-carousel-prev', { + 'fromSlide': '1', + 'toSlide': 'slide-id', + }); + expect(analyticsEventSpy).to.have.been.calledWith( + 'amp-carousel-change', + {'fromSlide': '1', 'toSlide': 'slide-id'} + ); expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - impl.showSlide_(4); - + expect(impl.showSlide_(4)).to.be.true; expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); + impl.slides_[0], + false + ); expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[4], true); + impl.slides_[4], + true + ); expect(updateInViewportSpy).to.have.callCount(6); - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[3]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[4]); expect(scheduleLayoutSpy).to.have.callCount(3); - expect(schedulePreloadSpy).to.have.callCount(6); + expect(schedulePreloadSpy).to.have.callCount(4); expect(impl.slideIndex_).to.equal(4); - expect(impl.slidesContainer_./*OK*/scrollLeft) - .to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); expect(hideRestOfTheSlidesSpy).to.have.callCount(3); expect(setControlsStateSpy).to.have.callCount(3); + expect(analyticsEventSpy).to.have.callCount(6); + expect(analyticsEventSpy).to.have.been.calledWith('amp-carousel-prev', { + 'fromSlide': 'slide-id', + 'toSlide': '4', + }); + expect(analyticsEventSpy).to.have.been.calledWith( + 'amp-carousel-change', + {'fromSlide': 'slide-id', 'toSlide': '4'} + ); expect(impl.slides_[3].getAttribute('aria-hidden')).to.equal('true'); expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); - - }); - }); - - it('show correct slides when looping with `autoplay` for 2 slides', () => { - return getAmpSlideScroll(true, 2).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); - const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); - const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); - const hideRestOfTheSlidesSpy = - sandbox.spy(impl, 'hideRestOfTheSlides_'); - const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); - - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - - impl.showSlide_(1); - - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], true); - expect(updateInViewportSpy).to.have.callCount(2); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); - expect(scheduleLayoutSpy).to.be.calledOnce; - expect(schedulePreloadSpy).to.have.callCount(1); - expect(impl.slideIndex_).to.equal(1); - expect(impl.slidesContainer_./*OK*/scrollLeft) - .to.equal(impl.slideWidth_); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); - expect(hideRestOfTheSlidesSpy).to.be.calledOnce; - expect(setControlsStateSpy).to.be.calledOnce; - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); - - impl.showSlide_(0); - - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[1], false); - expect(updateInViewportSpy).to.have.been.calledWith( - impl.slides_[0], true); - expect(updateInViewportSpy).to.have.callCount(4); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); - expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); - expect(scheduleLayoutSpy).to.have.callCount(2); - expect(schedulePreloadSpy).to.have.callCount(2); - expect(impl.slideIndex_).to.equal(0); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); - expect(hideRestOfTheSlidesSpy).to.have.callCount(2); - expect(setControlsStateSpy).to.have.callCount(2); - expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); - expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); - }); - }); - - it('do not set `autoplay` status if `autoplay=0` specified', () => { - return getAmpSlideScroll(false, 3, true, true, 0).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const setupAutoplaySpy = sandbox.spy(impl, 'setupAutoplay_'); - expect(setupAutoplaySpy).to.not.have.been.called; - }); - }); - - it('removes `autoplay` status after provided loops are made', () => { - return getAmpSlideScroll(false, 3, true, true, 2).then(ampSlideScroll => { - const impl = ampSlideScroll.implementation_; - const removeAutoplaySpy = sandbox.spy(impl, 'removeAutoplay'); - impl.showSlide_(1); - impl.showSlide_(2); - expect(impl.loopsMade_).to.equal(1); - impl.showSlide_(0); - impl.showSlide_(1); - impl.showSlide_(2); - expect(impl.loopsMade_).to.equal(2); - expect(removeAutoplaySpy).to.have.been.called; - expect(ampSlideScroll.hasAttribute('loop')).to.be.false; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal(null); }); }); // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should hide unwanted slides when looping', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it.skip('should hide the unwanted slides', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); - const hideRestOfTheSlidesSpy = - sandbox.spy(impl, 'hideRestOfTheSlides_'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); impl.showSlide_(1); expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.false; - - expect(impl.slideWrappers_[0].style.order).to.equal('1'); - expect(impl.slideWrappers_[1].style.order).to.equal('2'); - expect(impl.slideWrappers_[2].style.order).to.equal('3'); - expect(impl.slideWrappers_[3].style.order).to.equal(''); - expect(impl.slideWrappers_[4].style.order).to.equal(''); - - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .false; expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.callCount(3); + expect(schedulePauseSpy).to.have.callCount(2); impl.showSlide_(0); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); - - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].style.order).to.equal('2'); - expect(impl.slideWrappers_[1].style.order).to.equal('3'); - expect(impl.slideWrappers_[2].style.order).to.equal(''); - expect(impl.slideWrappers_[3].style.order).to.equal(''); - expect(impl.slideWrappers_[4].style.order).to.equal('1'); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .false; expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.callCount(6); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); + expect(schedulePauseSpy).to.have.callCount(4); impl.showSlide_(4); - expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); - - expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)) - .to.be.false; - expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)) - .to.be.true; - expect(impl.slideWrappers_[0].style.order).to.equal('3'); - expect(impl.slideWrappers_[1].style.order).to.equal(''); - expect(impl.slideWrappers_[2].style.order).to.equal(''); - expect(impl.slideWrappers_[3].style.order).to.equal('1'); - expect(impl.slideWrappers_[4].style.order).to.equal('2'); - expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4]); + + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); - expect(schedulePauseSpy).to.have.callCount(9); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); + expect(schedulePauseSpy).to.have.callCount(7); }); }); - it('should show/hide the correct controls when looping', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should show/hide the correct controls', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; impl.showSlide_(1); @@ -866,34 +372,42 @@ describes.realWin('SlideScroll', { impl.showSlide_(0); expect(impl.hasNext()).to.be.true; - expect(impl.hasPrev()).to.be.true; + expect(impl.hasPrev()).to.be.false; expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; - expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.true; impl.showSlide_(4); - expect(impl.hasNext()).to.be.true; + expect(impl.hasNext()).to.be.false; expect(impl.hasPrev()).to.be.true; - expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.false; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be.true; expect(impl.prevButton_.classList.contains('amp-disabled')).to.be.false; }); }); it('should set the correct scrollLeft when there is only one slide', () => { - return getAmpSlideScroll(true, 1).then(ampSlideScroll => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; impl.noOfSlides_ = 1; impl.showSlide_(0); - expect(impl.slidesContainer_./*OK*/scrollLeft).to.equal(0); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(0); }); }); it('should update to the right slide on scroll', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const showSlideSpy = sandbox.spy(impl, 'showSlide_'); impl.vsync_ = { + mutatePromise: cb => { + cb(); + return { + then: cb2 => { + cb2(); + }, + }; + }, mutate: cb => { cb(); }, @@ -909,39 +423,31 @@ describes.realWin('SlideScroll', { expect(showSlideSpy).to.be.calledWith(0); expect(impl.slideIndex_).to.equal(0); - // Try scrolling Fwd and move a little fwd to stay in the same slide. + // Try scrolling Fwd and move to slide 1. impl.updateOnScroll_(401); - expect(showSlideSpy).to.be.calledWith(0); - expect(impl.slideIndex_).to.equal(0); - - impl.updateOnScroll_(700); expect(showSlideSpy).to.be.calledWith(1); expect(impl.slideIndex_).to.equal(1); - impl.showSlide_(4); impl.updateOnScroll_(700); - expect(showSlideSpy).to.be.calledWith(0); - expect(impl.slideIndex_).to.equal(0); + expect(showSlideSpy).to.be.calledWith(2); + expect(impl.slideIndex_).to.equal(2); - impl.updateOnScroll_(1); + impl.showSlide_(4); + impl.updateOnScroll_(700); expect(showSlideSpy).to.be.calledWith(4); expect(impl.slideIndex_).to.equal(4); }); }); - it('should get the correct next slide index for a scrollLeft' , () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should get the correct next slide index for a scrollLeft', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; // Already at slide 0; - - expect(impl.getNextSlideIndex_(0)).to.equal(4); - expect(impl.getNextSlideIndex_(100)).to.equal(4); - expect(impl.getNextSlideIndex_(200)).to.equal(0); - expect(impl.getNextSlideIndex_(400)).to.equal(0); - expect(impl.getNextSlideIndex_(500)).to.equal(0); - expect(impl.getNextSlideIndex_(600)).to.equal(1); - expect(impl.getNextSlideIndex_(800)).to.equal(1); + expect(impl.getNextSlideIndex_(0)).to.equal(0); + expect(impl.getNextSlideIndex_(100)).to.equal(0); + expect(impl.getNextSlideIndex_(200)).to.equal(1); + expect(impl.getNextSlideIndex_(400)).to.equal(1); impl.showSlide_(3); @@ -958,17 +464,25 @@ describes.realWin('SlideScroll', { expect(impl.getNextSlideIndex_(100)).to.equal(3); expect(impl.getNextSlideIndex_(200)).to.equal(4); expect(impl.getNextSlideIndex_(400)).to.equal(4); - expect(impl.getNextSlideIndex_(500)).to.equal(4); - expect(impl.getNextSlideIndex_(600)).to.equal(0); - expect(impl.getNextSlideIndex_(800)).to.equal(0); }); }); it('should custom snap to the correct slide', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); + impl.customSnap_(0); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(100); + expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); + impl.customSnap_(200); + expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); + impl.customSnap_(400); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); + + impl.showSlide_(3); + impl.customSnap_(0); expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); impl.customSnap_(100); @@ -984,217 +498,806 @@ describes.realWin('SlideScroll', { impl.customSnap_(800); expect(animateScrollLeftSpy).to.have.been.calledWith(800, 800); + impl.showSlide_(4); + + impl.customSnap_(0); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(100); + expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); + impl.customSnap_(200); + expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); + impl.customSnap_(400); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); + + impl.showSlide_(0); + + impl.customSnap_(0, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(0, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); + + impl.showSlide_(3); + impl.customSnap_(400, -1); expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); impl.customSnap_(400, 1); - expect(animateScrollLeftSpy).to.have.been.calledWith(400, 800); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); + + impl.showSlide_(4); + + impl.customSnap_(400, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); + impl.customSnap_(400, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); }); }); - it('should go to the correct slide on button click', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should custom snap to the correct slide - special case', () => { + return getAmpSlideScroll(null, 2).then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); - impl.goCallback(-1); - expect(showSlideSpy).to.have.been.calledWith(4); - expect(showSlideSpy).to.be.calledOnce; + impl.customSnap_(0, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 400); - impl.goCallback(1); - expect(showSlideSpy).to.have.been.calledWith(0); - expect(showSlideSpy).to.have.callCount(2); + impl.showSlide_(1); - impl.goCallback(1); - expect(showSlideSpy).to.have.been.calledWith(1); - expect(showSlideSpy).to.have.callCount(3); + impl.customSnap_(400, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); }); }); - // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. - it.skip('should update slide when `slide` attribute is mutated', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { - expectAsyncConsoleError(/Invalid \[slide\] value:/, 1); - + it('should handle custom elastic scroll', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const customSnapSpy = sandbox + .stub(impl, 'customSnap_') + .callsFake(() => { + return { + then: cb => { + cb(); + }, + }; + }); + + impl.handleCustomElasticScroll_(-10); + expect(impl.elasticScrollState_).to.equal(-1); + impl.previousScrollLeft_ = -10; + impl.handleCustomElasticScroll_(-5); + expect(customSnapSpy).to.have.been.calledWith(-5); + + impl.previousScrollLeft_ = null; + + impl.handleCustomElasticScroll_(410); + expect(impl.elasticScrollState_).to.equal(1); + impl.previousScrollLeft_ = 410; + impl.handleCustomElasticScroll_(405); + expect(customSnapSpy).to.have.been.calledWith(405); + }); + }); - impl.mutatedAttributesCallback({slide: 2}); - expect(showSlideSpy).to.have.been.calledWith(2); + it('should handle layout measures (orientation changes)', async () => { + const ampSlideScroll = await getAmpSlideScroll(); + const impl = ampSlideScroll.implementation_; + const getLayoutWidthStub = sandbox.stub(impl, 'getLayoutWidth'); - impl.mutatedAttributesCallback({slide: 0}); - expect(showSlideSpy).to.have.been.calledWith(0); + getLayoutWidthStub.returns(200); + impl.onLayoutMeasure(); + expect(getLayoutWidthStub).to.have.been.calledOnce; + expect(impl.slideWidth_).to.equal(200); - // Don't call showSlide_() if slide is not finite. - showSlideSpy.resetHistory(); - impl.mutatedAttributesCallback({slide: Number.POSITIVE_INFINITY}); - expect(showSlideSpy.called).to.be.false; - }); + // Show the first slide, make sure the scroll position is correct. + impl.showSlide_(1); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(200); + + // Now do a layout measure letting the component know it changed size. + getLayoutWidthStub.returns(400); + impl.onLayoutMeasure(); + expect(getLayoutWidthStub).to.have.callCount(2); + expect(impl.slideWidth_).to.equal(400); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(200); + + // Make sure the scroll position is correct after layoutCallback. + await impl.layoutCallback(); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(400); }); - it('should trigger `slideChange` action when user changes slides', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { + it('should relayout the current slide on layoutCallback', () => { + return getAmpSlideScroll().then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; - const triggerSpy = sandbox.spy(impl.action_, 'trigger'); - - impl.goCallback(-1, /* animate */ false); - expect(triggerSpy).to.have.been.calledWith( - ampSlideScroll, - 'slideChange', - /* CustomEvent */ sinon.match.has('detail', {index: 4})); + const scheduleLayoutSpy_ = sandbox.spy(impl, 'scheduleLayout'); + impl.slideIndex_ = null; + impl.layoutCallback(); + expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[0]); - impl.goCallback(1, /* animate */ false); - expect(triggerSpy).to.have.been.calledWith( - ampSlideScroll, - 'slideChange', - /* CustomEvent */ sinon.match.has('detail', {index: 0})); + impl.showSlide_(1); + impl.layoutCallback(); + expect(scheduleLayoutSpy_).to.have.been.calledWith(impl.slides_[1]); }); }); - it('should goToSlide on action', () => { - return getAmpSlideScroll(true).then(ampSlideScroll => { - expectAsyncConsoleError(/Invalid \[slide\] value:/, 4); + describe('Looping', () => { + beforeEach(() => { + sandbox = sinon.sandbox; + }); - const impl = ampSlideScroll.implementation_; - const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - const satisfiesTrust = () => true; + afterEach(() => { + sandbox.restore(); + }); - let args = {'index': '123'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + it('should create container and wrappers and show initial slides', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + }); + }); - args = {'index': '5'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip('should show the correct slides when looping', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); + const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); + + expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(1); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + true + ); + expect(updateInViewportSpy).to.have.callCount(2); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[2]); + expect(scheduleLayoutSpy).to.be.calledOnce; + expect(schedulePreloadSpy).to.have.callCount(2); + expect(impl.slideIndex_).to.equal(1); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); + expect(hideRestOfTheSlidesSpy).to.be.calledOnce; + expect(setControlsStateSpy).to.be.calledOnce; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[2].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(0); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + true + ); + expect(updateInViewportSpy).to.have.callCount(4); + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[4]); + expect(scheduleLayoutSpy).to.have.callCount(2); + expect(schedulePreloadSpy).to.have.callCount(4); + expect(impl.slideIndex_).to.equal(0); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(400); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); + expect(hideRestOfTheSlidesSpy).to.have.callCount(2); + expect(setControlsStateSpy).to.have.callCount(2); + expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(4); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[4], + true + ); + expect(updateInViewportSpy).to.have.callCount(6); + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[3]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[4]); + expect(scheduleLayoutSpy).to.have.callCount(3); + expect(schedulePreloadSpy).to.have.callCount(6); + expect(impl.slideIndex_).to.equal(4); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); + expect(hideRestOfTheSlidesSpy).to.have.callCount(3); + expect(setControlsStateSpy).to.have.callCount(3); + expect(impl.slides_[3].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[4].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); + }); + }); - args = {'index': 'ssds11'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + it('show correct slides when looping with `autoplay` for 2 slides', () => { + return getAmpSlideScroll(true, 2).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const updateInViewportSpy = sandbox.spy(impl, 'updateInViewport'); + const scheduleLayoutSpy = sandbox.spy(impl, 'scheduleLayout'); + const schedulePreloadSpy = sandbox.spy(impl, 'schedulePreload'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); + const setControlsStateSpy = sandbox.spy(impl, 'setControlsState'); + + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + + impl.showSlide_(1); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + true + ); + expect(updateInViewportSpy).to.have.callCount(2); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[0]); + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[1]); + expect(scheduleLayoutSpy).to.be.calledOnce; + expect(schedulePreloadSpy).to.have.callCount(1); + expect(impl.slideIndex_).to.equal(1); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal( + impl.slideWidth_ + ); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); + expect(hideRestOfTheSlidesSpy).to.be.calledOnce; + expect(setControlsStateSpy).to.be.calledOnce; + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('true'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('false'); + + impl.showSlide_(0); + + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[1], + false + ); + expect(updateInViewportSpy).to.have.been.calledWith( + impl.slides_[0], + true + ); + expect(updateInViewportSpy).to.have.callCount(4); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(scheduleLayoutSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePreloadSpy).to.have.been.calledWith(impl.slides_[1]); + expect(scheduleLayoutSpy).to.have.callCount(2); + expect(schedulePreloadSpy).to.have.callCount(2); + expect(impl.slideIndex_).to.equal(0); + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1]); + expect(hideRestOfTheSlidesSpy).to.have.callCount(2); + expect(setControlsStateSpy).to.have.callCount(2); + expect(impl.slides_[0].getAttribute('aria-hidden')).to.equal('false'); + expect(impl.slides_[1].getAttribute('aria-hidden')).to.equal('true'); + }); + }); - args = {'index': '-1'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + it('do not set `autoplay` status if `autoplay=0` specified', () => { + return getAmpSlideScroll(false, 3, true, true, 0).then( + ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const setupAutoplaySpy = sandbox.spy(impl, 'setupAutoplay_'); + expect(setupAutoplaySpy).to.not.have.been.called; + } + ); + }); - args = {'index': '0'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.have.been.calledWith(0); + it('removes `autoplay` status after provided loops are made', () => { + return getAmpSlideScroll(false, 3, true, true, 2).then( + ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const removeAutoplaySpy = sandbox.spy(impl, 'removeAutoplay'); + impl.showSlide_(1); + impl.showSlide_(2); + expect(impl.loopsMade_).to.equal(1); + impl.showSlide_(0); + impl.showSlide_(1); + impl.showSlide_(2); + expect(impl.loopsMade_).to.equal(2); + expect(removeAutoplaySpy).to.have.been.called; + expect(ampSlideScroll.hasAttribute('loop')).to.be.false; + } + ); + }); - args = {'index': '4'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.have.been.calledWith(4); + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip('should hide unwanted slides when looping', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const schedulePauseSpy = sandbox.spy(impl, 'schedulePause'); + const hideRestOfTheSlidesSpy = sandbox.spy( + impl, + 'hideRestOfTheSlides_' + ); + + impl.showSlide_(1); + + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([0, 1, 2]); + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .false; + + expect(impl.slideWrappers_[0].style.order).to.equal('1'); + expect(impl.slideWrappers_[1].style.order).to.equal('2'); + expect(impl.slideWrappers_[2].style.order).to.equal('3'); + expect(impl.slideWrappers_[3].style.order).to.equal(''); + expect(impl.slideWrappers_[4].style.order).to.equal(''); + + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); + expect(schedulePauseSpy).to.have.callCount(3); + + impl.showSlide_(0); + + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([4, 0, 1]); + + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].style.order).to.equal('2'); + expect(impl.slideWrappers_[1].style.order).to.equal('3'); + expect(impl.slideWrappers_[2].style.order).to.equal(''); + expect(impl.slideWrappers_[3].style.order).to.equal(''); + expect(impl.slideWrappers_[4].style.order).to.equal('1'); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[2]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[4]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePauseSpy).to.have.callCount(6); + + impl.showSlide_(4); + + expect(hideRestOfTheSlidesSpy).to.have.been.calledWith([3, 4, 0]); + + expect(impl.slideWrappers_[0].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[1].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[2].classList.contains(SHOW_CLASS)).to.be + .false; + expect(impl.slideWrappers_[3].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[4].classList.contains(SHOW_CLASS)).to.be + .true; + expect(impl.slideWrappers_[0].style.order).to.equal('3'); + expect(impl.slideWrappers_[1].style.order).to.equal(''); + expect(impl.slideWrappers_[2].style.order).to.equal(''); + expect(impl.slideWrappers_[3].style.order).to.equal('1'); + expect(impl.slideWrappers_[4].style.order).to.equal('2'); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[3]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[0]); + expect(schedulePauseSpy).to.have.been.calledWith(impl.slides_[1]); + expect(schedulePauseSpy).to.have.callCount(9); + }); }); - }); - it('should NOT call showSlide_ before layout', () => { - const promise = getAmpSlideScroll(true, 5, /* opt_attachToDom */ false); - return promise.then(ampSlideScroll => { + it('should show/hide the correct controls when looping', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; - // Layout happens asynchronously after attaching to DOM, so we can - // test pre-layoutCallback logic now. - doc.body.appendChild(ampSlideScroll); - return ampSlideScroll.build().then(() => { + impl.showSlide_(1); + expect(impl.hasNext()).to.be.true; + expect(impl.hasPrev()).to.be.true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + + impl.showSlide_(0); + expect(impl.hasNext()).to.be.true; + expect(impl.hasPrev()).to.be.true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + + impl.showSlide_(4); + expect(impl.hasNext()).to.be.true; + expect(impl.hasPrev()).to.be.true; + expect(impl.nextButton_.classList.contains('amp-disabled')).to.be + .false; + expect(impl.prevButton_.classList.contains('amp-disabled')).to.be + .false; + }); + }); + + it('should set the correct scrollLeft when there is only one slide', () => { + return getAmpSlideScroll(true, 1).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + + impl.noOfSlides_ = 1; + impl.showSlide_(0); + expect(impl.slidesContainer_./*OK*/ scrollLeft).to.equal(0); + }); + }); + + it('should update to the right slide on scroll', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { const impl = ampSlideScroll.implementation_; const showSlideSpy = sandbox.spy(impl, 'showSlide_'); - const satisfiesTrust = () => true; - const args = {'index': '3'}; - impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); - expect(showSlideSpy).to.not.have.been.called; + impl.vsync_ = { + mutate: cb => { + cb(); + }, + }; - impl.mutatedAttributesCallback({slide: 2}); - expect(showSlideSpy).to.not.have.been.called; + // Move to slide 1 (from slide 0). + impl.showSlide_(1); + expect(showSlideSpy).to.be.calledWith(1); + expect(impl.snappingInProgress_).to.be.false; + + //Move to slide 0 - via scrolling back. + impl.updateOnScroll_(1); + expect(showSlideSpy).to.be.calledWith(0); + expect(impl.slideIndex_).to.equal(0); + + // Try scrolling Fwd and move a little fwd to stay in the same slide. + impl.updateOnScroll_(401); + expect(showSlideSpy).to.be.calledWith(0); + expect(impl.slideIndex_).to.equal(0); + + impl.updateOnScroll_(700); + expect(showSlideSpy).to.be.calledWith(1); + expect(impl.slideIndex_).to.equal(1); + + impl.showSlide_(4); + impl.updateOnScroll_(700); + expect(showSlideSpy).to.be.calledWith(0); + expect(impl.slideIndex_).to.equal(0); + + impl.updateOnScroll_(1); + expect(showSlideSpy).to.be.calledWith(4); + expect(impl.slideIndex_).to.equal(4); + }); + }); - impl.onLayoutMeasure(); - ampSlideScroll.layoutCallback(); + it('should get the correct next slide index for a scrollLeft', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; - // Should show the last slide index requested before layout. - expect(showSlideSpy).to.have.been.calledWith(2); + // Already at slide 0; + + expect(impl.getNextSlideIndex_(0)).to.equal(4); + expect(impl.getNextSlideIndex_(100)).to.equal(4); + expect(impl.getNextSlideIndex_(200)).to.equal(0); + expect(impl.getNextSlideIndex_(400)).to.equal(0); + expect(impl.getNextSlideIndex_(500)).to.equal(0); + expect(impl.getNextSlideIndex_(600)).to.equal(1); + expect(impl.getNextSlideIndex_(800)).to.equal(1); + + impl.showSlide_(3); + + expect(impl.getNextSlideIndex_(0)).to.equal(2); + expect(impl.getNextSlideIndex_(100)).to.equal(2); + expect(impl.getNextSlideIndex_(200)).to.equal(3); + expect(impl.getNextSlideIndex_(400)).to.equal(3); + expect(impl.getNextSlideIndex_(500)).to.equal(3); + expect(impl.getNextSlideIndex_(600)).to.equal(4); + expect(impl.getNextSlideIndex_(800)).to.equal(4); + + impl.showSlide_(4); + expect(impl.getNextSlideIndex_(0)).to.equal(3); + expect(impl.getNextSlideIndex_(100)).to.equal(3); + expect(impl.getNextSlideIndex_(200)).to.equal(4); + expect(impl.getNextSlideIndex_(400)).to.equal(4); + expect(impl.getNextSlideIndex_(500)).to.equal(4); + expect(impl.getNextSlideIndex_(600)).to.equal(0); + expect(impl.getNextSlideIndex_(800)).to.equal(0); + }); + }); + + it('should custom snap to the correct slide', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const animateScrollLeftSpy = sandbox.spy(impl, 'animateScrollLeft_'); + + impl.customSnap_(0); + expect(animateScrollLeftSpy).to.have.been.calledWith(0, 0); + impl.customSnap_(100); + expect(animateScrollLeftSpy).to.have.been.calledWith(100, 0); + impl.customSnap_(200); + expect(animateScrollLeftSpy).to.have.been.calledWith(200, 400); + impl.customSnap_(400); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 400); + impl.customSnap_(500); + expect(animateScrollLeftSpy).to.have.been.calledWith(500, 400); + impl.customSnap_(600); + expect(animateScrollLeftSpy).to.have.been.calledWith(600, 800); + impl.customSnap_(800); + expect(animateScrollLeftSpy).to.have.been.calledWith(800, 800); + + impl.customSnap_(400, -1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 0); + impl.customSnap_(400, 1); + expect(animateScrollLeftSpy).to.have.been.calledWith(400, 800); + }); + }); + + it('should go to the correct slide on button click', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + + impl.goCallback(-1); + expect(showSlideSpy).to.have.been.calledWith(4); expect(showSlideSpy).to.be.calledOnce; + + impl.goCallback(1); + expect(showSlideSpy).to.have.been.calledWith(0); + expect(showSlideSpy).to.have.callCount(2); + + impl.goCallback(1); + expect(showSlideSpy).to.have.been.calledWith(1); + expect(showSlideSpy).to.have.callCount(3); }); }); - }); - it('should NOT call showSlide_ before re-layout', () => { - return getAmpSlideScroll(false, 5, false).then(ampSlideScroll => { + // TODO(#17197): This test triggers sinonjs/sinon issues 1709 and 1321. + it.skip('should update slide when `slide` attribute is mutated', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + expectAsyncConsoleError(/Invalid \[slide\] value:/, 1); + + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + + impl.mutatedAttributesCallback({slide: 2}); + expect(showSlideSpy).to.have.been.calledWith(2); + + impl.mutatedAttributesCallback({slide: 0}); + expect(showSlideSpy).to.have.been.calledWith(0); + + // Don't call showSlide_() if slide is not finite. + showSlideSpy.resetHistory(); + impl.mutatedAttributesCallback({slide: Number.POSITIVE_INFINITY}); + expect(showSlideSpy.called).to.be.false; + }); + }); + + it('should trigger `slideChange` action when user changes slides', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + const impl = ampSlideScroll.implementation_; + const triggerSpy = sandbox.spy(impl.action_, 'trigger'); + + impl.goCallback(-1, /* animate */ false); + expect(triggerSpy).to.have.been.calledWith( + ampSlideScroll, + 'slideChange', + /* CustomEvent */ sinon.match.has('detail', {index: 4}) + ); + + impl.goCallback(1, /* animate */ false); + expect(triggerSpy).to.have.been.calledWith( + ampSlideScroll, + 'slideChange', + /* CustomEvent */ sinon.match.has('detail', {index: 0}) + ); + }); + }); + + it('should goToSlide on action', () => { + return getAmpSlideScroll(true).then(ampSlideScroll => { + expectAsyncConsoleError(/Invalid \[slide\] value:/, 4); - doc.body.appendChild(ampSlideScroll); - return ampSlideScroll.build().then(() => { const impl = ampSlideScroll.implementation_; const showSlideSpy = sandbox.spy(impl, 'showSlide_'); const satisfiesTrust = () => true; - // Test that showSlide_ due to goToSlide(index=1) is not called before - // layout. - let args = {'index': '1'}; + let args = {'index': '123'}; impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); expect(showSlideSpy).to.not.have.been.called; - // Test that showSlide_ is called after layout. - impl.onLayoutMeasure(); - ampSlideScroll.layoutCallback(); - - expect(showSlideSpy).to.have.been.calledWith(1); - expect(showSlideSpy).to.be.calledOnce; + args = {'index': '5'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; - // Unlayout - showSlideSpy.resetHistory(); - impl.unlayoutCallback(); + args = {'index': 'ssds11'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; - // Test that showSlide_ due to goToSlide(index=4) is not called before - // layout. - args = {'index': '4'}; + args = {'index': '-1'}; impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); expect(showSlideSpy).to.not.have.been.called; - // Test that showSlide_ is called after layout. - impl.onLayoutMeasure(); - ampSlideScroll.layoutCallback(); + args = {'index': '0'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.have.been.calledWith(0); + args = {'index': '4'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); expect(showSlideSpy).to.have.been.calledWith(4); - expect(showSlideSpy).to.be.calledOnce; + }); + }); + + it('should NOT call showSlide_ before layout', () => { + const promise = getAmpSlideScroll(true, 5, /* opt_attachToDom */ false); + return promise.then(ampSlideScroll => { + // Layout happens asynchronously after attaching to DOM, so we can + // test pre-layoutCallback logic now. + doc.body.appendChild(ampSlideScroll); + return ampSlideScroll.build().then(() => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const satisfiesTrust = () => true; + + const args = {'index': '3'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; + + impl.mutatedAttributesCallback({slide: 2}); + expect(showSlideSpy).to.not.have.been.called; + + impl.onLayoutMeasure(); + ampSlideScroll.layoutCallback(); + + // Should show the last slide index requested before layout. + expect(showSlideSpy).to.have.been.calledWith(2); + expect(showSlideSpy).to.be.calledOnce; + }); + }); + }); + + it('should NOT call showSlide_ before re-layout', () => { + return getAmpSlideScroll(false, 5, false).then(ampSlideScroll => { + doc.body.appendChild(ampSlideScroll); + return ampSlideScroll.build().then(() => { + const impl = ampSlideScroll.implementation_; + const showSlideSpy = sandbox.spy(impl, 'showSlide_'); + const satisfiesTrust = () => true; + + // Test that showSlide_ due to goToSlide(index=1) is not called before + // layout. + let args = {'index': '1'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; + + // Test that showSlide_ is called after layout. + impl.onLayoutMeasure(); + ampSlideScroll.layoutCallback(); + + expect(showSlideSpy).to.have.been.calledWith(1); + expect(showSlideSpy).to.be.calledOnce; + + // Unlayout + showSlideSpy.resetHistory(); + impl.unlayoutCallback(); + + // Test that showSlide_ due to goToSlide(index=4) is not called before + // layout. + args = {'index': '4'}; + impl.executeAction({method: 'goToSlide', args, satisfiesTrust}); + expect(showSlideSpy).to.not.have.been.called; + + // Test that showSlide_ is called after layout. + impl.onLayoutMeasure(); + ampSlideScroll.layoutCallback(); + + expect(showSlideSpy).to.have.been.calledWith(4); + expect(showSlideSpy).to.be.calledOnce; + }); }); }); }); - }); - describe('button titles', () => { - function getNextTitle(el) { - return el.querySelector('.amp-carousel-button-next') + describe('button titles', () => { + function getNextTitle(el) { + return el + .querySelector('.amp-carousel-button-next') .getAttribute('title'); - } + } - function getPrevTitle(el) { - return el.querySelector('.amp-carousel-button-prev') + function getPrevTitle(el) { + return el + .querySelector('.amp-carousel-button-prev') .getAttribute('title'); - } + } - describe('when not looping', () => { - it('should have the correct values on the first index', function* () { - const el = yield getAmpSlideScroll(false, 3); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (1 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); - }); + describe('when not looping', () => { + it('should have the correct values on the first index', function*() { + const el = yield getAmpSlideScroll(false, 3); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (1 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); + }); - it('should have the correct values on the last index', function* () { - const el = yield getAmpSlideScroll(false, 3); - el.implementation_.showSlide_(2); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (2 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (3 of 3)'); + it('should have the correct values on the last index', function*() { + const el = yield getAmpSlideScroll(false, 3); + el.implementation_.showSlide_(2); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (2 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (3 of 3)'); + }); }); - }); - describe('when looping', () => { - it('should have the correct values on the first index', function* () { - const el = yield getAmpSlideScroll(true, 3); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (3 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); - }); + describe('when looping', () => { + it('should have the correct values on the first index', function*() { + const el = yield getAmpSlideScroll(true, 3); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (3 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (2 of 3)'); + }); - it('should have the correct values on the last index', function* () { - const el = yield getAmpSlideScroll(true, 3); - el.implementation_.showSlide_(2); - expect(getPrevTitle(el)).to.equal('Previous item in carousel (2 of 3)'); - expect(getNextTitle(el)).to.equal('Next item in carousel (1 of 3)'); + it('should have the correct values on the last index', function*() { + const el = yield getAmpSlideScroll(true, 3); + el.implementation_.showSlide_(2); + expect(getPrevTitle(el)).to.equal( + 'Previous item in carousel (2 of 3)' + ); + expect(getNextTitle(el)).to.equal('Next item in carousel (1 of 3)'); + }); }); }); - }); -}); + } +); diff --git a/extensions/amp-consent/0.1/amp-consent.js b/extensions/amp-consent/0.1/amp-consent.js index 1ff39ec38d320..1a0da79f07d72 100644 --- a/extensions/amp-consent/0.1/amp-consent.js +++ b/extensions/amp-consent/0.1/amp-consent.js @@ -54,7 +54,6 @@ export const ACTION_TYPE = { DISMISS: 'dismiss', }; - export class AmpConsent extends AMP.BaseElement { /** @param {!AmpElement} element */ constructor(element) { @@ -107,8 +106,10 @@ export class AmpConsent extends AMP.BaseElement { /** @override */ buildCallback() { - userAssert(this.element.getAttribute('id'), - 'amp-consent should have an id'); + userAssert( + this.element.getAttribute('id'), + 'amp-consent should have an id' + ); const config = new ConsentConfig(this.element); @@ -118,8 +119,11 @@ export class AmpConsent extends AMP.BaseElement { this.consentId_ = this.consentConfig_['consentInstanceId']; if (this.consentConfig_['postPromptUI']) { - this.postPromptUI_ = - new ConsentUI(this, dict({}), this.consentConfig_['postPromptUI']); + this.postPromptUI_ = new ConsentUI( + this, + dict({}), + this.consentConfig_['postPromptUI'] + ); } /** @@ -143,7 +147,9 @@ export class AmpConsent extends AMP.BaseElement { const policyConfig = this.consentConfig_['policy'] || dict({}); this.policyConfig_ = expandPolicyConfig( - policyConfig, /** @type {string} */ (this.consentId_)); + policyConfig, + /** @type {string} */ (this.consentId_) + ); const children = this.getRealChildren(); for (let i = 0; i < children.length; i++) { @@ -153,44 +159,47 @@ export class AmpConsent extends AMP.BaseElement { this.setAsOwner(child); } - const consentPolicyManagerPromise = - getServicePromiseForDoc(this.getAmpDoc(), CONSENT_POLICY_MANAGER) - .then(manager => { - this.consentPolicyManager_ = /** @type {!ConsentPolicyManager} */ ( - manager); - this.consentPolicyManager_.setLegacyConsentInstanceId( - /** @type {string} */ (this.consentId_)); - const policyKeys = - Object.keys(/** @type {!Object} */ (this.policyConfig_)); - for (let i = 0; i < policyKeys.length; i++) { - this.consentPolicyManager_.registerConsentPolicyInstance( - policyKeys[i], this.policyConfig_[policyKeys[i]]); - } - }); - - const consentStateManagerPromise = - getServicePromiseForDoc(this.getAmpDoc(), CONSENT_STATE_MANAGER) - .then(manager => { - manager.registerConsentInstance( - this.consentId_, this.consentConfig_); - this.consentStateManager_ = /** @type {!ConsentStateManager} */ ( - manager); - }); - - const notificationUiManagerPromise = - getServicePromiseForDoc(this.getAmpDoc(), NOTIFICATION_UI_MANAGER) - .then(manager => { - this.notificationUiManager_ = /** @type {!NotificationUiManager} */ ( - manager); - }); + const consentPolicyManagerPromise = getServicePromiseForDoc( + this.getAmpDoc(), + CONSENT_POLICY_MANAGER + ).then(manager => { + this.consentPolicyManager_ = /** @type {!ConsentPolicyManager} */ (manager); + this.consentPolicyManager_.setLegacyConsentInstanceId( + /** @type {string} */ (this.consentId_) + ); + const policyKeys = Object.keys( + /** @type {!Object} */ (this.policyConfig_) + ); + for (let i = 0; i < policyKeys.length; i++) { + this.consentPolicyManager_.registerConsentPolicyInstance( + policyKeys[i], + this.policyConfig_[policyKeys[i]] + ); + } + }); + + const consentStateManagerPromise = getServicePromiseForDoc( + this.getAmpDoc(), + CONSENT_STATE_MANAGER + ).then(manager => { + manager.registerConsentInstance(this.consentId_, this.consentConfig_); + this.consentStateManager_ = /** @type {!ConsentStateManager} */ (manager); + }); + + const notificationUiManagerPromise = getServicePromiseForDoc( + this.getAmpDoc(), + NOTIFICATION_UI_MANAGER + ).then(manager => { + this.notificationUiManager_ = /** @type {!NotificationUiManager} */ (manager); + }); Promise.all([ consentStateManagerPromise, notificationUiManagerPromise, - consentPolicyManagerPromise]) - .then(() => { - this.init_(); - }); + consentPolicyManagerPromise, + ]).then(() => { + this.init_(); + }); } /** @@ -236,18 +245,27 @@ export class AmpConsent extends AMP.BaseElement { user().error(TAG, 'consent-response message missing required info'); return; } - if (isExperimentOn(this.win, 'amp-consent-v2') && - data['info'] !== undefined) { + if ( + isExperimentOn(this.win, 'amp-consent-v2') && + data['info'] !== undefined + ) { if (typeof data['info'] != 'string') { - user().error(TAG, 'consent-response info only supports string, ' + - '%s, treated as undefined', data['info']); + user().error( + TAG, + 'consent-response info only supports string, ' + + '%s, treated as undefined', + data['info'] + ); data['info'] = undefined; } if (data['action'] === ACTION_TYPE.DISMISS) { if (data['info']) { - this.user().error(TAG, - 'Consent string value %s not applicable on user dismiss, ' + - 'stored value will be kept and used', consentString); + this.user().error( + TAG, + 'Consent string value %s not applicable on user dismiss, ' + + 'stored value will be kept and used', + consentString + ); } data['info'] = undefined; } @@ -299,8 +317,7 @@ export class AmpConsent extends AMP.BaseElement { */ show_() { if (this.isPromptUIOn_) { - dev().error(TAG, - 'Attempt to show an already displayed prompt UI'); + dev().error(TAG, 'Attempt to show an already displayed prompt UI'); } this.vsync_.mutate(() => { @@ -356,16 +373,19 @@ export class AmpConsent extends AMP.BaseElement { if (action == ACTION_TYPE.ACCEPT) { //accept this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.ACCEPTED, - consentString); + CONSENT_ITEM_STATE.ACCEPTED, + consentString + ); } else if (action == ACTION_TYPE.REJECT) { // reject this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.REJECTED, - consentString); + CONSENT_ITEM_STATE.REJECTED, + consentString + ); } else if (action == ACTION_TYPE.DISMISS) { this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.DISMISSED); + CONSENT_ITEM_STATE.DISMISSED + ); } // Hide current dialog @@ -378,16 +398,19 @@ export class AmpConsent extends AMP.BaseElement { init_() { this.passSharedData_(); - this.getConsentRequiredPromise_().then(isConsentRequired => { - return this.initPromptUI_(isConsentRequired); - }).then(isPostPromptUIRequired => { - if (isPostPromptUIRequired) { - this.handlePostPromptUI_(); - } - this.consentPolicyManager_.enableTimeout(); - }).catch(unusedError => { - // TODO: Handle errors - }); + this.getConsentRequiredPromise_() + .then(isConsentRequired => { + return this.initPromptUI_(isConsentRequired); + }) + .then(isPostPromptUIRequired => { + if (isPostPromptUIRequired) { + this.handlePostPromptUI_(); + } + this.consentPolicyManager_.enableTimeout(); + }) + .catch(unusedError => { + // TODO: Handle errors + }); this.enableInteractions_(); } @@ -398,27 +421,34 @@ export class AmpConsent extends AMP.BaseElement { * @return {!Promise} */ getConsentRequiredPromise_() { - userAssert(this.consentConfig_['checkConsentHref'] || + userAssert( + this.consentConfig_['checkConsentHref'] || this.consentConfig_['promptIfUnknownForGeoGroup'], - 'neither checkConsentHref nor ' + - 'promptIfUnknownForGeoGroup is defined'); + 'neither checkConsentHref nor ' + 'promptIfUnknownForGeoGroup is defined' + ); let consentRequiredPromise = null; if (this.consentConfig_['promptIfUnknownForGeoGroup']) { const geoGroup = this.consentConfig_['promptIfUnknownForGeoGroup']; consentRequiredPromise = this.isConsentRequiredGeo_(geoGroup); } else { - consentRequiredPromise = - this.getConsentRemote_().then(remoteConfigResponse => { - if (!remoteConfigResponse || - !hasOwn(remoteConfigResponse, 'promptIfUnknown')) { - this.user().error(TAG, 'Expecting promptIfUnknown from ' + + consentRequiredPromise = this.getConsentRemote_().then( + remoteConfigResponse => { + if ( + !remoteConfigResponse || + !hasOwn(remoteConfigResponse, 'promptIfUnknown') + ) { + this.user().error( + TAG, + 'Expecting promptIfUnknown from ' + 'checkConsentHref when promptIfUnknownForGeoGroup is not ' + - 'specified'); - // Set to false if not defined - return false; - } - return !!remoteConfigResponse['promptIfUnknown']; - }); + 'specified' + ); + // Set to false if not defined + return false; + } + return !!remoteConfigResponse['promptIfUnknown']; + } + ); } return consentRequiredPromise.then(required => { return !!required; @@ -447,9 +477,8 @@ export class AmpConsent extends AMP.BaseElement { */ isConsentRequiredGeo_(geoGroup) { return Services.geoForDocOrNull(this.element).then(geo => { - userAssert(geo, - 'requires to use promptIfUnknownForGeoGroup'); - return (geo.isInCountryGroup(geoGroup) == GEO_IN_GROUP.IN); + userAssert(geo, 'requires to use promptIfUnknownForGeoGroup'); + return geo.isInCountryGroup(geoGroup) == GEO_IN_GROUP.IN; }); } @@ -478,8 +507,7 @@ export class AmpConsent extends AMP.BaseElement { body: request, requireAmpResponseSourceOrigin: false, }; - const href = - this.consentConfig_['checkConsentHref']; + const href = this.consentConfig_['checkConsentHref']; assertHttpsUrl(href, this.element); const ampdoc = this.getAmpDoc(); const sourceBase = getSourceUrl(ampdoc.getUrl()); @@ -487,8 +515,8 @@ export class AmpConsent extends AMP.BaseElement { const viewer = Services.viewerForDoc(ampdoc); this.remoteConfigPromise_ = viewer.whenFirstVisible().then(() => { return Services.xhrFor(this.win) - .fetchJson(resolvedHref, init) - .then(res => res.json()); + .fetchJson(resolvedHref, init) + .then(res => res.json()); }); } return this.remoteConfigPromise_; @@ -500,31 +528,35 @@ export class AmpConsent extends AMP.BaseElement { * @return {Promise} */ initPromptUI_(isConsentRequired) { - this.consentUI_ = new ConsentUI(this, - /** @type {!JsonObject} */ ( - devAssert(this.consentConfig_, 'consent config not found'))); + this.consentUI_ = new ConsentUI( + this, + /** @type {!JsonObject} */ (devAssert( + this.consentConfig_, + 'consent config not found' + )) + ); // Get current consent state - return this.consentStateManager_.getConsentInstanceInfo() - .then(info => { - if (hasStoredValue(info)) { - // Has user stored value, no need to prompt - return true; - } - if (!isConsentRequired) { - // no need to prompt if remote reponse say so - // Also no need to display postPromptUI - this.consentStateManager_.updateConsentInstanceState( - CONSENT_ITEM_STATE.NOT_REQUIRED); - return false; - } - // Prompt - this.scheduleDisplay_(); - return true; - // TODO(@zhouyx): - // Race condition on consent state change between schedule to - // display and display. Add one more check before display - }); + return this.consentStateManager_.getConsentInstanceInfo().then(info => { + if (hasStoredValue(info)) { + // Has user stored value, no need to prompt + return true; + } + if (!isConsentRequired) { + // no need to prompt if remote reponse say so + // Also no need to display postPromptUI + this.consentStateManager_.updateConsentInstanceState( + CONSENT_ITEM_STATE.NOT_REQUIRED + ); + return false; + } + // Prompt + this.scheduleDisplay_(); + return true; + // TODO(@zhouyx): + // Race condition on consent state change between schedule to + // display and display. Add one more check before display + }); } /** @@ -551,7 +583,6 @@ export class AmpConsent extends AMP.BaseElement { } } - AMP.extension('amp-consent', '0.1', AMP => { AMP.registerElement('amp-consent', AmpConsent, CSS); AMP.registerServiceForDoc(NOTIFICATION_UI_MANAGER, NotificationUiManager); diff --git a/extensions/amp-consent/0.1/cmps.js b/extensions/amp-consent/0.1/cmps.js index f4e03e4ec8f53..1460e2a1100c7 100644 --- a/extensions/amp-consent/0.1/cmps.js +++ b/extensions/amp-consent/0.1/cmps.js @@ -25,7 +25,7 @@ import {getMode} from '../../../src/mode'; * } */ -export const CMP_CONFIG = ({}); +export const CMP_CONFIG = {}; if (getMode().test || getMode().localDev) { CMP_CONFIG['_ping_'] = { diff --git a/extensions/amp-consent/0.1/consent-config.js b/extensions/amp-consent/0.1/consent-config.js index 6de1562210f7a..520854246cf19 100644 --- a/extensions/amp-consent/0.1/consent-config.js +++ b/extensions/amp-consent/0.1/consent-config.js @@ -32,7 +32,6 @@ const ALLOWED_DEPR_CONSENTINSTANCE_ATTRS = { }; export class ConsentConfig { - /** @param {!Element} element */ constructor(element) { /** @private {!Element} */ @@ -64,8 +63,11 @@ export class ConsentConfig { const consentsConfigDepr = config['consents']; if (!isExperimentOn(this.win_, 'amp-consent-v2')) { userAssert(consentsConfigDepr, '%s: consents config is required', TAG); - userAssert(Object.keys(consentsConfigDepr).length != 0, - '%s: can\'t find consent instance', TAG); + userAssert( + Object.keys(consentsConfigDepr).length != 0, + "%s: can't find consent instance", + TAG + ); } if (!config['consents']) { @@ -75,8 +77,11 @@ export class ConsentConfig { // Assert single consent instance const keys = Object.keys(consentsConfigDepr); - userAssert(keys.length <= 1, - '%s: only single consent instance is supported', TAG); + userAssert( + keys.length <= 1, + '%s: only single consent instance is supported', + TAG + ); if (keys.length > 0) { config['consentInstanceId'] = keys[0]; @@ -108,16 +113,25 @@ export class ConsentConfig { */ validateAndParseConfig_() { const inlineConfig = this.convertInlineConfigFormat_( - /** @type {!JsonObject} */ ( - userAssert(this.getInlineConfig_(), '%s: Inline config not found'))); + /** @type {!JsonObject} */ (userAssert( + this.getInlineConfig_(), + '%s: Inline config not found' + )) + ); const cmpConfig = this.getCMPConfig_(); - const config = /** @type {!JsonObject} */ - (deepMerge(cmpConfig || {}, inlineConfig || {}, 1)); + const config /** @type {!JsonObject} */ = deepMerge( + cmpConfig || {}, + inlineConfig || {}, + 1 + ); - userAssert(config['consentInstanceId'], - '%s: consentInstanceId to store consent info is required', TAG); + userAssert( + config['consentInstanceId'], + '%s: consentInstanceId to store consent info is required', + TAG + ); if (config['policy']) { // Only respect 'default' consent policy; @@ -125,8 +139,11 @@ export class ConsentConfig { // TODO (@zhouyx): Validate waitFor value for (let i = 0; i < keys.length; i++) { if (keys[i] != 'default') { - user().warn(TAG, 'policy %s is currently not supported ' + - 'and will be ignored', keys[i]); + user().warn( + TAG, + 'policy %s is currently not supported ' + 'and will be ignored', + keys[i] + ); delete config['policy'][keys[i]]; } } @@ -182,8 +199,11 @@ export class ConsentConfig { * @param {!JsonObject} config */ validateCMPConfig_(config) { - const assertValues = - ['consentInstanceId', 'checkConsentHref', 'promptUISrc']; + const assertValues = [ + 'consentInstanceId', + 'checkConsentHref', + 'promptUISrc', + ]; for (let i = 0; i < assertValues.length; i++) { const attribute = assertValues[i]; devAssert(config[attribute], 'CMP config must specify %s', attribute); diff --git a/extensions/amp-consent/0.1/consent-info.js b/extensions/amp-consent/0.1/consent-info.js index 0e05edd84607c..55908fc376d2a 100644 --- a/extensions/amp-consent/0.1/consent-info.js +++ b/extensions/amp-consent/0.1/consent-info.js @@ -18,7 +18,6 @@ import {dev} from '../../../src/log'; import {isEnumValue, isObject} from '../../../src/types'; import {map} from '../../../src/utils/object'; - /** * Key values for retriving/storing consent info object. * STATE: Set when user accept or reject consent. @@ -64,7 +63,10 @@ export let ConsentInfoDef; export function getStoredConsentInfo(value) { if (value === undefined) { return constructConsentInfo( - CONSENT_ITEM_STATE.UNKNOWN, undefined, undefined); + CONSENT_ITEM_STATE.UNKNOWN, + undefined, + undefined + ); } if (typeof value === 'boolean') { // legacy format @@ -75,9 +77,11 @@ export function getStoredConsentInfo(value) { } const consentState = convertValueToState(value[STORAGE_KEY.STATE]); - return constructConsentInfo(consentState, - value[STORAGE_KEY.STRING], - (value[STORAGE_KEY.IS_DIRTY] && value[STORAGE_KEY.IS_DIRTY] === 1)); + return constructConsentInfo( + consentState, + value[STORAGE_KEY.STRING], + value[STORAGE_KEY.IS_DIRTY] && value[STORAGE_KEY.IS_DIRTY] === 1 + ); } /** @@ -89,8 +93,10 @@ export function recalculateConsentStateValue(newState, previousState) { if (!isEnumValue(CONSENT_ITEM_STATE, newState)) { newState = CONSENT_ITEM_STATE.UNKNOWN; } - if (newState == CONSENT_ITEM_STATE.DISMISSED || - newState == CONSENT_ITEM_STATE.UNKNOWN) { + if ( + newState == CONSENT_ITEM_STATE.DISMISSED || + newState == CONSENT_ITEM_STATE.UNKNOWN + ) { return previousState || CONSENT_ITEM_STATE.UNKNOWN; } if (newState == CONSENT_ITEM_STATE.NOT_REQUIRED) { @@ -108,9 +114,11 @@ export function recalculateConsentStateValue(newState, previousState) { * @return {?boolean|Object} */ export function composeStoreValue(consentInfo, opt_forceNew) { - if (!opt_forceNew && - !consentInfo['consentString'] && - consentInfo['isDirty'] === undefined) { + if ( + !opt_forceNew && + !consentInfo['consentString'] && + consentInfo['isDirty'] === undefined + ) { // TODO: Remove after turn on amp-consent-v2 return calculateLegacyStateValue(consentInfo['consentState']); } @@ -167,10 +175,11 @@ export function isConsentInfoStoredValueSame(infoA, infoB) { return true; } if (infoA && infoB) { - const stateEqual = calculateLegacyStateValue(infoA['consentState']) === - calculateLegacyStateValue(infoB['consentState']); + const stateEqual = + calculateLegacyStateValue(infoA['consentState']) === + calculateLegacyStateValue(infoB['consentState']); const stringEqual = - ((infoA['consentString'] || '') === (infoB['consentString'] || '')); + (infoA['consentString'] || '') === (infoB['consentString'] || ''); const isDirtyEqual = !!infoA['isDirty'] === !!infoB['isDirty']; return stateEqual && stringEqual && isDirtyEqual; } @@ -194,8 +203,11 @@ function getLegacyStoredConsentInfo(value) { * @param {boolean=} opt_isDirty * @return {!ConsentInfoDef} */ -export function constructConsentInfo(consentState, - opt_consentString, opt_isDirty) { +export function constructConsentInfo( + consentState, + opt_consentString, + opt_isDirty +) { return { 'consentState': consentState, 'consentString': opt_consentString, @@ -225,8 +237,10 @@ export function hasStoredValue(info) { if (info['consentString']) { return true; } - return info['consentState'] === CONSENT_ITEM_STATE.ACCEPTED || - info['consentState'] === CONSENT_ITEM_STATE.REJECTED; + return ( + info['consentState'] === CONSENT_ITEM_STATE.ACCEPTED || + info['consentState'] === CONSENT_ITEM_STATE.REJECTED + ); } /** diff --git a/extensions/amp-consent/0.1/consent-policy-manager.js b/extensions/amp-consent/0.1/consent-policy-manager.js index f11345efc53a4..7d4be85a9d5ca 100644 --- a/extensions/amp-consent/0.1/consent-policy-manager.js +++ b/extensions/amp-consent/0.1/consent-policy-manager.js @@ -24,8 +24,6 @@ import {isFiniteNumber, isObject} from '../../../src/types'; import {map} from '../../../src/utils/object'; import {user, userAssert} from '../../../src/log'; - - const CONSENT_STATE_MANAGER = 'consentStateManager'; const TAG = 'consent-policy-manager'; @@ -36,7 +34,6 @@ const WHITELIST_POLICY = { '_auto_reject': true, }; - export class ConsentPolicyManager { /** * Creates an instance of ConsentPolicyManager. @@ -53,8 +50,10 @@ export class ConsentPolicyManager { this.instances_ = map(); /** @private {!Promise} */ - this.ConsentStateManagerPromise_ = - getServicePromiseForDoc(this.ampdoc_, CONSENT_STATE_MANAGER); + this.ConsentStateManagerPromise_ = getServicePromiseForDoc( + this.ampdoc_, + CONSENT_STATE_MANAGER + ); /** @private {!Deferred} */ this.consentPromptInitiated_ = new Deferred(); @@ -127,8 +126,10 @@ export class ConsentPolicyManager { const waitFor = Object.keys(config['waitFor'] || {}); if (waitFor.length !== 1 || waitFor[0] !== this.consentInstanceIdDepr_) { - user().error(TAG, - 'invalid waitFor value, consent policy will never resolve'); + user().error( + TAG, + 'invalid waitFor value, consent policy will never resolve' + ); return; } @@ -198,8 +199,8 @@ export class ConsentPolicyManager { if (state == CONSENT_ITEM_STATE.NOT_REQUIRED) { const shouldOverwrite = - this.consentState_ != CONSENT_ITEM_STATE.ACCEPTED && - this.consentState_ != CONSENT_ITEM_STATE.REJECTED; + this.consentState_ != CONSENT_ITEM_STATE.ACCEPTED && + this.consentState_ != CONSENT_ITEM_STATE.REJECTED; // Ignore the consent item state and overwrite state value. if (shouldOverwrite) { this.consentState_ = CONSENT_ITEM_STATE.NOT_REQUIRED; @@ -225,8 +226,11 @@ export class ConsentPolicyManager { whenPolicyResolved(policyId) { // If customized policy is not supported if (!WHITELIST_POLICY[policyId]) { - user().error(TAG, 'can not find policy %s, ' + - 'only predefined policies are supported', policyId); + user().error( + TAG, + 'can not find policy %s, ' + 'only predefined policies are supported', + policyId + ); return Promise.resolve(CONSENT_POLICY_STATE.UNKNOWN); } return this.whenPolicyInstanceRegistered_(policyId).then(() => { @@ -244,8 +248,11 @@ export class ConsentPolicyManager { whenPolicyUnblock(policyId) { // If customized policy is not supported if (!WHITELIST_POLICY[policyId]) { - user().error(TAG, 'can not find policy %s, ' + - 'only predefined policies are supported', policyId); + user().error( + TAG, + 'can not find policy %s, ' + 'only predefined policies are supported', + policyId + ); return Promise.resolve(false); } return this.whenPolicyInstanceRegistered_(policyId).then(() => { @@ -266,10 +273,10 @@ export class ConsentPolicyManager { */ getMergedSharedData(policyId) { return this.whenPolicyResolved(policyId) - .then(() => this.ConsentStateManagerPromise_) - .then(manager => { - return manager.getConsentInstanceSharedData(); - }); + .then(() => this.ConsentStateManagerPromise_) + .then(manager => { + return manager.getConsentInstanceSharedData(); + }); } /** @@ -296,8 +303,8 @@ export class ConsentPolicyManager { if (!this.policyInstancesDeferred_[policyId]) { this.policyInstancesDeferred_[policyId] = new Deferred(); } - return /** @type {!Promise} */ ( - this.policyInstancesDeferred_[policyId].promise); + return /** @type {!Promise} */ (this.policyInstancesDeferred_[policyId] + .promise); } } @@ -322,9 +329,10 @@ export class ConsentPolicyInstance { this.status_ = CONSENT_POLICY_STATE.UNKNOWN; /** @private {!Array} */ - this.unblockStateLists_ = config['unblockOn'] || - [CONSENT_POLICY_STATE.SUFFICIENT, - CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED]; + this.unblockStateLists_ = config['unblockOn'] || [ + CONSENT_POLICY_STATE.SUFFICIENT, + CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED, + ]; } /** @@ -345,20 +353,30 @@ export class ConsentPolicyInstance { * "fallbackAction": "reject" * } */ - if (timeoutConfig['fallbackAction'] && - timeoutConfig['fallbackAction'] == 'reject') { + if ( + timeoutConfig['fallbackAction'] && + timeoutConfig['fallbackAction'] == 'reject' + ) { fallbackState = CONSENT_ITEM_STATE.REJECTED; - } else if (timeoutConfig['fallbackAction'] && - timeoutConfig['fallbackAction'] != 'dismiss') { - user().error(TAG, 'unsupported fallbackAction %s', - timeoutConfig['fallbackAction']); + } else if ( + timeoutConfig['fallbackAction'] && + timeoutConfig['fallbackAction'] != 'dismiss' + ) { + user().error( + TAG, + 'unsupported fallbackAction %s', + timeoutConfig['fallbackAction'] + ); } timeoutSecond = timeoutConfig['seconds']; } else { timeoutSecond = timeoutConfig; } - userAssert(isFiniteNumber(timeoutSecond), - 'invalid timeout value %s', timeoutSecond); + userAssert( + isFiniteNumber(timeoutSecond), + 'invalid timeout value %s', + timeoutSecond + ); } if (timeoutSecond != null) { @@ -368,7 +386,6 @@ export class ConsentPolicyInstance { this.evaluate(fallbackState, true); }, timeoutSecond * 1000); } - } /** @@ -427,6 +444,6 @@ export class ConsentPolicyInstance { * @return {boolean} */ shouldUnblock() { - return (this.unblockStateLists_.indexOf(this.status_) > -1); + return this.unblockStateLists_.indexOf(this.status_) > -1; } } diff --git a/extensions/amp-consent/0.1/consent-state-manager.js b/extensions/amp-consent/0.1/consent-state-manager.js index e958e55e2c319..549acc8d8d259 100644 --- a/extensions/amp-consent/0.1/consent-state-manager.js +++ b/extensions/amp-consent/0.1/consent-state-manager.js @@ -30,7 +30,6 @@ import {assertHttpsUrl} from '../../../src/url'; import {dev, devAssert, user} from '../../../src/log'; import {isExperimentOn} from '../../../src/experiments'; - const TAG = 'CONSENT-STATE-MANAGER'; const CID_SCOPE = 'AMP-CONSENT'; @@ -66,9 +65,13 @@ export class ConsentStateManager { */ registerConsentInstance(instanceId, config) { if (this.instance_) { - dev().error(TAG, 'Cannot register consent instance %s, ' + + dev().error( + TAG, + 'Cannot register consent instance %s, ' + 'instance %s has already been registered.', - instanceId, this.instanceId_); + instanceId, + this.instanceId_ + ); return; } @@ -105,8 +108,7 @@ export class ConsentStateManager { * @return {Promise} */ getConsentInstanceInfo() { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); return this.instance_.get(); } @@ -115,11 +117,13 @@ export class ConsentStateManager { * @param {function(!ConsentInfoDef)} handler */ onConsentStateChange(handler) { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); - devAssert(!this.consentChangeHandler_, - '%s: Duplicate consent change handler, will be ignored', TAG); + devAssert( + !this.consentChangeHandler_, + '%s: Duplicate consent change handler, will be ignored', + TAG + ); this.consentChangeHandler_ = handler; @@ -129,7 +133,6 @@ export class ConsentStateManager { }); } - /** * Sets a promise which resolves to a shareData object that is to be returned * from the remote endpoint. @@ -137,8 +140,7 @@ export class ConsentStateManager { * @param {Promise} sharedDataPromise */ setConsentInstanceSharedData(sharedDataPromise) { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); this.instance_.sharedDataPromise = sharedDataPromise; } @@ -149,8 +151,7 @@ export class ConsentStateManager { * @return {?Promise} */ getConsentInstanceSharedData() { - devAssert(this.instance_, - '%s: cannot find the instance', TAG); + devAssert(this.instance_, '%s: cannot find the instance', TAG); return this.instance_.sharedDataPromise; } @@ -185,8 +186,10 @@ export class ConsentInstance { this.ampdoc_ = ampdoc; /** @private {boolean} */ - this.isAmpConsentV2ExperimentOn_ = - isExperimentOn(ampdoc.win, 'amp-consent-v2'); + this.isAmpConsentV2ExperimentOn_ = isExperimentOn( + ampdoc.win, + 'amp-consent-v2' + ); /** @private {string} */ this.id_ = id; @@ -217,16 +220,17 @@ export class ConsentInstance { */ update(state, consentString) { const localState = - this.localConsentInfo_ && this.localConsentInfo_['consentState']; + this.localConsentInfo_ && this.localConsentInfo_['consentState']; const localConsentStr = - this.localConsentInfo_ && this.localConsentInfo_['consentString']; - const calculatedState = - recalculateConsentStateValue(state, localState); + this.localConsentInfo_ && this.localConsentInfo_['consentString']; + const calculatedState = recalculateConsentStateValue(state, localState); if (state === CONSENT_ITEM_STATE.DISMISSED) { // If state is dismissed, use the old consent string. - this.localConsentInfo_ = - constructConsentInfo(calculatedState, localConsentStr); + this.localConsentInfo_ = constructConsentInfo( + calculatedState, + localConsentStr + ); return; } @@ -250,8 +254,7 @@ export class ConsentInstance { */ updateStoredValue_(consentInfo) { this.storagePromise_.then(storage => { - if (!isConsentInfoStoredValueSame( - consentInfo, this.localConsentInfo_)) { + if (!isConsentInfoStoredValueSame(consentInfo, this.localConsentInfo_)) { // If state has changed. do not store outdated value. return; } @@ -261,9 +264,11 @@ export class ConsentInstance { // Verify the length of consentString. // 150 * 2 (utf8Encode) * 4/3 (base64) = 400 bytes. // TODO: Need utf8Encode if necessary. - user().error(TAG, - 'Cannot store consentString which length exceeds 150 ' + - 'Previous stored consentInfo will be cleared'); + user().error( + TAG, + 'Cannot store consentString which length exceeds 150 ' + + 'Previous stored consentInfo will be cleared' + ); // If new consentInfo value cannot be stored, need to remove previous // value storage.remove(this.storageKey_); @@ -272,7 +277,9 @@ export class ConsentInstance { } const value = composeStoreValue( - consentInfo, this.isAmpConsentV2ExperimentOn_); + consentInfo, + this.isAmpConsentV2ExperimentOn_ + ); if (value == null) { // Value can be false, do not use !value check // Nothing to store to localStorage @@ -293,21 +300,24 @@ export class ConsentInstance { return Promise.resolve(this.localConsentInfo_); } - return this.storagePromise_.then(storage => { - return storage.get(this.storageKey_); - }).then(storedValue => { - if (this.localConsentInfo_) { - // If local value has been updated, return most updated value; + return this.storagePromise_ + .then(storage => { + return storage.get(this.storageKey_); + }) + .then(storedValue => { + if (this.localConsentInfo_) { + // If local value has been updated, return most updated value; + return this.localConsentInfo_; + } + + const consentInfo = getStoredConsentInfo(storedValue); + this.localConsentInfo_ = consentInfo; return this.localConsentInfo_; - } - - const consentInfo = getStoredConsentInfo(storedValue); - this.localConsentInfo_ = consentInfo; - return this.localConsentInfo_; - }).catch(e => { - dev().error(TAG, 'Failed to read storage', e); - return constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN); - }); + }) + .catch(e => { + dev().error(TAG, 'Failed to read storage', e); + return constructConsentInfo(CONSENT_ITEM_STATE.UNKNOWN); + }); } /** @@ -319,11 +329,12 @@ export class ConsentInstance { if (!this.onUpdateHref_) { return; } - const consentState = - calculateLegacyStateValue(consentInfo['consentState']); + const consentState = calculateLegacyStateValue(consentInfo['consentState']); const cidPromise = Services.cidForDoc(this.ampdoc_).then(cid => { - return cid.get({scope: CID_SCOPE, createCookieIfNotPresent: true}, - Promise.resolve()); + return cid.get( + {scope: CID_SCOPE, createCookieIfNotPresent: true}, + Promise.resolve() + ); }); cidPromise.then(userId => { const request = /** @type {!JsonObject} */ ({ @@ -343,10 +354,14 @@ export class ConsentInstance { body: request, ampCors: false, }; - Services.viewerForDoc(this.ampdoc_).whenFirstVisible().then(() => { - Services.xhrFor(this.ampdoc_.win).fetchJson( - /** @type {string} */ (this.onUpdateHref_), init); - }); + Services.viewerForDoc(this.ampdoc_) + .whenFirstVisible() + .then(() => { + Services.xhrFor(this.ampdoc_.win).fetchJson( + /** @type {string} */ (this.onUpdateHref_), + init + ); + }); }); } } diff --git a/extensions/amp-consent/0.1/consent-ui.js b/extensions/amp-consent/0.1/consent-ui.js index c1f50098cd6fd..45d6f4c0b178d 100644 --- a/extensions/amp-consent/0.1/consent-ui.js +++ b/extensions/amp-consent/0.1/consent-ui.js @@ -16,9 +16,7 @@ import {Deferred} from '../../../src/utils/promise'; import {Services} from '../../../src/services'; -import { - assertHttpsUrl, -} from '../../../src/url'; +import {assertHttpsUrl} from '../../../src/url'; import {dev, user} from '../../../src/log'; import {dict} from '../../../src/utils/object'; import { @@ -52,14 +50,12 @@ export const consentUiClasses = { }; export class ConsentUI { - /** * @param {!AMP.BaseElement} baseInstance * @param {!JsonObject} config * @param {string=} opt_postPromptUI */ constructor(baseInstance, config, opt_postPromptUI) { - /** @private {!AMP.BaseElement} */ this.baseInstance_ = baseInstance; @@ -82,7 +78,8 @@ export class ConsentUI { this.ui_ = null; /** @private {boolean} */ - this.overlayEnabled_ = isExperimentOn(baseInstance.win, 'amp-consent-v2') && + this.overlayEnabled_ = + isExperimentOn(baseInstance.win, 'amp-consent-v2') && config['uiConfig'] && config['uiConfig']['overlay'] === true; @@ -137,11 +134,13 @@ export class ConsentUI { */ init_(config, opt_postPromptUI) { if (opt_postPromptUI) { - const postPromptUI = - this.ampdoc_.getElementById(opt_postPromptUI); + const postPromptUI = this.ampdoc_.getElementById(opt_postPromptUI); if (!postPromptUI) { - user().error(TAG, 'postPromptUI element with ' + - 'id=%s not found', opt_postPromptUI); + user().error( + TAG, + 'postPromptUI element with ' + 'id=%s not found', + opt_postPromptUI + ); } this.ui_ = dev().assertElement(postPromptUI); this.isPostPrompt_ = true; @@ -153,15 +152,17 @@ export class ConsentUI { // Always respect promptUI first const promptElement = this.ampdoc_.getElementById(promptUI); if (!promptElement || !this.parent_.contains(promptElement)) { - user().error(TAG, 'child element of with ' + - 'promptUI id %s not found', promptUI); + user().error( + TAG, + 'child element of with ' + 'promptUI id %s not found', + promptUI + ); } this.ui_ = dev().assertElement(promptElement); } else if (promptUISrc && isExperimentOn(this.win_, 'amp-consent-v2')) { // Create an iframe element with the provided src this.isCreatedIframe_ = true; - this.ui_ = - this.createPromptIframeFromSrc_(promptUISrc); + this.ui_ = this.createPromptIframeFromSrc_(promptUISrc); this.placeholder_ = this.createPlaceholder_(); this.clientConfig_ = config['clientConfig'] || null; } @@ -188,7 +189,6 @@ export class ConsentUI { // being hidden. CMP iframe is responsible to call consent-iframe-ready // API before consent-response API. this.baseInstance_.mutateElement(() => { - if (!this.isPostPrompt_) { this.elementWithFocusBeforeShowing_ = this.document_.activeElement; } @@ -198,7 +198,7 @@ export class ConsentUI { this.showIframe_(); if (!this.isPostPrompt_) { - this.ui_./*OK*/focus(); + this.ui_./*OK*/ focus(); } }); }); @@ -211,7 +211,6 @@ export class ConsentUI { toggle(this.ui_, true); if (!this.isPostPrompt_) { - this.elementWithFocusBeforeShowing_ = this.document_.activeElement; this.maybeShowOverlay_(); @@ -221,16 +220,14 @@ export class ConsentUI { // for example this.baseInstance_.scheduleLayout(this.ui_); - this.ui_./*OK*/focus(); + this.ui_./*OK*/ focus(); } }; // If the UI is an AMP Element, wait until it's built before showing it, // to avoid race conditions where the UI would be hidden by the runtime // at build time. (see #18841). - isAmpElement(this.ui_) ? - this.ui_.whenBuilt().then(() => show()) : - show(); + isAmpElement(this.ui_) ? this.ui_.whenBuilt().then(() => show()) : show(); } this.isVisible_ = true; @@ -240,7 +237,6 @@ export class ConsentUI { * Hide the UI */ hide() { - if (!this.ui_) { // Nothing to hide from; return; @@ -275,12 +271,12 @@ export class ConsentUI { this.isVisible_ = false; if (this.elementWithFocusBeforeShowing_) { - this.elementWithFocusBeforeShowing_./*OK*/focus(); + this.elementWithFocusBeforeShowing_./*OK*/ focus(); this.elementWithFocusBeforeShowing_ = null; } else if (this.win_.document.body.children.length > 0) { // TODO (torch2424): Find if the first child can not be // focusable due to styling. - this.win_.document.body.children[0]./*OK*/focus(); + this.win_.document.body.children[0]./*OK*/ focus(); } }); } @@ -290,31 +286,31 @@ export class ConsentUI { * @param {!JsonObject} data */ handleReady_(data) { - this.initialHeight_ = DEFAULT_INITIAL_HEIGHT; this.enableBorder_ = DEFAULT_ENABLE_BORDER; // Set our initial height if (data['initialHeight']) { - if (typeof data['initialHeight'] === 'string' && - data['initialHeight'].indexOf('vh') >= 0) { - + if ( + typeof data['initialHeight'] === 'string' && + data['initialHeight'].indexOf('vh') >= 0 + ) { const dataHeight = parseInt(data['initialHeight'], 10); if (dataHeight >= 10 && dataHeight <= 60) { this.initialHeight_ = `${dataHeight}vh`; } else { user().error( - TAG, - `Inavlid initial height: ${data['initialHeight']}.` + - 'Minimum: 10vh. Maximum: 60vh.' + TAG, + `Inavlid initial height: ${data['initialHeight']}.` + + 'Minimum: 10vh. Maximum: 60vh.' ); } } else { user().error( - TAG, - `Inavlid initial height: ${data['initialHeight']}.` + - 'Must be a string in "vh" units.' + TAG, + `Inavlid initial height: ${data['initialHeight']}.` + + 'Must be a string in "vh" units.' ); } } @@ -409,8 +405,10 @@ export class ConsentUI { * @return {!Promise} */ getClientInfoPromise_() { - const consentStatePromise = - getServicePromiseForDoc(this.ampdoc_, CONSENT_STATE_MANAGER); + const consentStatePromise = getServicePromiseForDoc( + this.ampdoc_, + CONSENT_STATE_MANAGER + ); return consentStatePromise.then(consentStateManager => { return consentStateManager.getConsentInstanceInfo().then(consentInfo => { return dict({ @@ -431,8 +429,11 @@ export class ConsentUI { this.iframeReady_ = new Deferred(); const {classList} = this.parent_; if (!elementByTag(this.parent_, 'placeholder')) { - insertAfterOrAtStart(this.parent_, - dev().assertElement(this.placeholder_), null); + insertAfterOrAtStart( + this.parent_, + dev().assertElement(this.placeholder_), + null + ); } classList.add(consentUiClasses.loading); toggle(dev().assertElement(this.ui_), false); @@ -551,7 +552,7 @@ export class ConsentUI { this.parent_.ownerDocument.body.appendChild(mask); this.maskElement_ = mask; } - toggle(this.maskElement_, /* display */true); + toggle(this.maskElement_, /* display */ true); this.disableScroll_(); } @@ -566,7 +567,7 @@ export class ConsentUI { } if (this.maskElement_) { - toggle(this.maskElement_, /* display */false); + toggle(this.maskElement_, /* display */ false); } this.enableScroll_(); } @@ -625,11 +626,10 @@ export class ConsentUI { } if (data['action'] === 'ready') { - this.handleReady_(/** @type {!JsonObject} */(data)); + this.handleReady_(/** @type {!JsonObject} */ (data)); } if (data['action'] === 'enter-fullscreen') { - // TODO (@torch2424) Send response back if enter fullscreen was succesful if (!this.isIframeVisible_) { return; diff --git a/extensions/amp-consent/0.1/test/test-amp-consent.js b/extensions/amp-consent/0.1/test/test-amp-consent.js index 0f39cf40904db..4706e4a7cc63d 100644 --- a/extensions/amp-consent/0.1/test/test-amp-consent.js +++ b/extensions/amp-consent/0.1/test/test-amp-consent.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - ACTION_TYPE, - AmpConsent, -} from '../amp-consent'; +import {ACTION_TYPE, AmpConsent} from '../amp-consent'; import {CONSENT_ITEM_STATE} from '../consent-info'; import {GEO_IN_GROUP} from '../../../amp-geo/0.1/amp-geo-in-group'; import {dict} from '../../../../src/utils/object'; @@ -28,485 +25,506 @@ import { } from '../../../../src/service'; import {toggleExperiment} from '../../../../src/experiments'; -describes.realWin('amp-consent', { - amp: { - extensions: ['amp-consent'], - ampdoc: 'single', +describes.realWin( + 'amp-consent', + { + amp: { + extensions: ['amp-consent'], + ampdoc: 'single', + }, }, -}, env => { - let win; - let doc; - let ampdoc; - let jsonMockResponses; - let storageValue; - let requestBody; - let ISOCountryGroups; - let xhrServiceMock; - - beforeEach(() => { - doc = env.win.document; - ampdoc = env.ampdoc; - win = env.win; - toggleExperiment(win, 'amp-consent-v2', true); - - - storageValue = {}; - jsonMockResponses = { - 'https://response1/': '{"promptIfUnknown": true}', - 'https://response2/': '{}', - 'https://response3/': '{"promptIfUnknown": false}', - }; - - xhrServiceMock = {fetchJson: (url, init) => { - requestBody = init.body; - expect(init.credentials).to.equal('include'); - expect(init.method).to.equal('POST'); - return Promise.resolve({ - json() { - return Promise.resolve(JSON.parse(jsonMockResponses[url])); + env => { + let win; + let doc; + let ampdoc; + let jsonMockResponses; + let storageValue; + let requestBody; + let ISOCountryGroups; + let xhrServiceMock; + + beforeEach(() => { + doc = env.win.document; + ampdoc = env.ampdoc; + win = env.win; + toggleExperiment(win, 'amp-consent-v2', true); + + storageValue = {}; + jsonMockResponses = { + 'https://response1/': '{"promptIfUnknown": true}', + 'https://response2/': '{}', + 'https://response3/': '{"promptIfUnknown": false}', + }; + + xhrServiceMock = { + fetchJson: (url, init) => { + requestBody = init.body; + expect(init.credentials).to.equal('include'); + expect(init.method).to.equal('POST'); + return Promise.resolve({ + json() { + return Promise.resolve(JSON.parse(jsonMockResponses[url])); + }, + }); }, + }; + + resetServiceForTesting(win, 'xhr'); + registerServiceBuilder(win, 'xhr', function() { + return xhrServiceMock; }); - }}; - resetServiceForTesting(win, 'xhr'); - registerServiceBuilder(win, 'xhr', function() { - return xhrServiceMock; - }); + resetServiceForTesting(win, 'geo'); + registerServiceBuilder(win, 'geo', function() { + return Promise.resolve({ + isInCountryGroup: group => + ISOCountryGroups.indexOf(group) >= 0 + ? GEO_IN_GROUP.IN + : GEO_IN_GROUP.NOT_IN, + }); + }); - resetServiceForTesting(win, 'geo'); - registerServiceBuilder(win, 'geo', function() { - return Promise.resolve({ - isInCountryGroup: group => - ISOCountryGroups.indexOf(group) >= 0 ? - GEO_IN_GROUP.IN : GEO_IN_GROUP.NOT_IN, + resetServiceForTesting(win, 'storage'); + registerServiceBuilder(win, 'storage', function() { + return Promise.resolve({ + get: name => { + return Promise.resolve(storageValue[name]); + }, + set: (name, value) => { + storageValue[name] = value; + return Promise.resolve(); + }, + }); }); }); - resetServiceForTesting(win, 'storage'); - registerServiceBuilder(win, 'storage', function() { - return Promise.resolve({ - get: name => { - return Promise.resolve(storageValue[name]); - }, - set: (name, value) => { - storageValue[name] = value; - return Promise.resolve(); - }, + describe('amp-consent', () => { + describe('consent config', () => { + let consentElement; + + it('get consent/policy/postPromptUI config', () => { + consentElement = createConsentElement( + doc, + dict({ + 'consents': { + 'test': { + 'checkConsentHref': '/override', + }, + }, + 'clientConfig': { + 'test': 'ABC', + }, + 'postPromptUI': 'test', + }) + ); + const postPromptUI = document.createElement('div'); + postPromptUI.setAttribute('id', 'test'); + consentElement.appendChild(postPromptUI); + doc.body.appendChild(consentElement); + const ampConsent = new AmpConsent(consentElement); + ampConsent.buildCallback(); + + expect(ampConsent.postPromptUI_).to.not.be.null; + expect(ampConsent.consentId_).to.equal('test'); + expect(ampConsent.consentConfig_).to.deep.equal( + dict({ + 'consentInstanceId': 'test', + 'checkConsentHref': '/override', + 'postPromptUI': 'test', + 'clientConfig': { + 'test': 'ABC', + }, + }) + ); + + expect(Object.keys(ampConsent.policyConfig_)).to.have.length(4); + expect(ampConsent.policyConfig_['default']).to.be.ok; + expect(ampConsent.policyConfig_['_till_responded']).to.be.ok; + expect(ampConsent.policyConfig_['_till_accepted']).to.be.ok; + expect(ampConsent.policyConfig_['_auto_reject']).to.be.ok; + }); + + it('relative checkConsentHref is resolved', function*() { + const fetchSpy = sandbox.spy(xhrServiceMock, 'fetchJson'); + consentElement = createConsentElement( + doc, + dict({ + 'consents': { + 'XYZ': { + 'checkConsentHref': '/r/1', + }, + }, + }) + ); + const ampConsent = new AmpConsent(consentElement); + doc.body.appendChild(consentElement); + const getUrlStub = sandbox.stub(ampdoc, 'getUrl'); + // return a cache Url to test origin source being used to resolve. + getUrlStub.callsFake(() => { + return 'https://cdn.ampproject.org/v/www.origin.com/foo/?f=0#h'; + }); + ampConsent.buildCallback(); + yield macroTask(); + expect(fetchSpy).to.be.calledOnce; + expect(win.testLocation.origin).not.to.be.empty; + expect(fetchSpy).to.be.calledWith('http://www.origin.com/r/1'); + }); }); }); - }); - describe('amp-consent', () => { - describe('consent config', () => { + describe('server communication', () => { + let defaultConfig; + let ampConsent; let consentElement; - - it('get consent/policy/postPromptUI config', () => { - consentElement = createConsentElement(doc, dict({ + beforeEach(() => { + defaultConfig = dict({ 'consents': { - 'test': { - 'checkConsentHref': '/override', + 'ABC': { + 'checkConsentHref': 'https://response1', }, }, - 'clientConfig': { - 'test': 'ABC', - }, - 'postPromptUI': 'test', - })); - const postPromptUI = document.createElement('div'); - postPromptUI.setAttribute('id', 'test'); - consentElement.appendChild(postPromptUI); + }); + consentElement = createConsentElement(doc, defaultConfig); doc.body.appendChild(consentElement); - const ampConsent = new AmpConsent(consentElement); - ampConsent.buildCallback(); + ampConsent = new AmpConsent(consentElement); + }); - expect(ampConsent.postPromptUI_).to.not.be.null; - expect(ampConsent.consentId_).to.equal('test'); - expect(ampConsent.consentConfig_).to.deep.equal(dict({ - 'consentInstanceId': 'test', - 'checkConsentHref': '/override', - 'postPromptUI': 'test', - 'clientConfig': { - 'test': 'ABC', - }, - })); + it('send post request to server', function*() { + ampConsent.buildCallback(); + yield macroTask(); + expect(requestBody).to.deep.equal({ + 'consentInstanceId': 'ABC', + }); + }); - expect(Object.keys(ampConsent.policyConfig_)).to.have.length(4); - expect(ampConsent.policyConfig_['default']).to.be.ok; - expect(ampConsent.policyConfig_['_till_responded']).to.be.ok; - expect(ampConsent.policyConfig_['_till_accepted']).to.be.ok; - expect(ampConsent.policyConfig_['_auto_reject']).to.be.ok; + it('read promptIfUnknown from server response', function*() { + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.true; + }); }); + }); - it('relative checkConsentHref is resolved', function* () { - const fetchSpy = sandbox.spy(xhrServiceMock, 'fetchJson'); - consentElement = createConsentElement(doc, dict({ + describe('amp-geo integration', () => { + let defaultConfig; + let ampConsent; + let consentElement; + beforeEach(() => { + defaultConfig = dict({ 'consents': { - 'XYZ': { - 'checkConsentHref': '/r/1', + 'ABC': { + 'promptIfUnknownForGeoGroup': 'testGroup', }, }, - })); - const ampConsent = new AmpConsent(consentElement); - doc.body.appendChild(consentElement); - const getUrlStub = sandbox.stub(ampdoc, 'getUrl'); - // return a cache Url to test origin source being used to resolve. - getUrlStub.callsFake(() => { - return 'https://cdn.ampproject.org/v/www.origin.com/foo/?f=0#h'; }); - ampConsent.buildCallback(); - yield macroTask(); - expect(fetchSpy).to.be.calledOnce; - expect(win.testLocation.origin).not.to.be.empty; - expect(fetchSpy).to.be.calledWith('http://www.origin.com/r/1'); + consentElement = createConsentElement(doc, defaultConfig); }); - }); - }); - describe('server communication', () => { - let defaultConfig; - let ampConsent; - let consentElement; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - }, - }, + it('in geo group', function*() { + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + ISOCountryGroups = ['unknown', 'testGroup']; + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.true; + }); }); - consentElement = createConsentElement(doc, defaultConfig); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - }); - it('send post request to server', function* () { - ampConsent.buildCallback(); - yield macroTask(); - expect(requestBody).to.deep.equal({ - 'consentInstanceId': 'ABC', + it('not in geo group', function*() { + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + ISOCountryGroups = ['unknown']; + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.false; + }); }); - }); - it('read promptIfUnknown from server response', function* () { - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.true; + it('geo override promptIfUnknown', function*() { + ISOCountryGroups = ['unknown']; + consentElement = createConsentElement( + doc, + dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response1', + 'promptIfUnknownForGeoGroup': 'testGroup', + }, + }, + }) + ); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + ampConsent.buildCallback(); + return ampConsent.getConsentRequiredPromise_().then(isRequired => { + expect(isRequired).to.be.false; + }); }); }); - }); - describe('amp-geo integration', () => { - let defaultConfig; - let ampConsent; - let consentElement; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'promptIfUnknownForGeoGroup': 'testGroup', + describe('external consent action', () => { + let defaultConfig; + let ampConsent; + let actionSpy; + let event; + let ampIframe; + let iframe; + let consentElement; + beforeEach(() => { + defaultConfig = dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response1', + }, }, - }, + }); + consentElement = createConsentElement(doc, defaultConfig); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + actionSpy = sandbox.stub(ampConsent, 'handleAction_'); + ampConsent.enableInteractions_(); + ampIframe = document.createElement('amp-iframe'); + iframe = doc.createElement('iframe'); + ampIframe.appendChild(iframe); + ampConsent.element.appendChild(ampIframe); + ampConsent.isPromptUIOn_ = true; + event = new Event('message'); }); - consentElement = createConsentElement(doc, defaultConfig); - }); - it('in geo group', function* () { - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - ISOCountryGroups = ['unknown', 'testGroup']; - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.true; + it('listen to external consent response msg', () => { + event.data = { + 'type': 'consent-response', + 'action': 'accept', + 'info': 'accept-string', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, 'accept-string'); }); - }); - it('not in geo group', function* () { - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - ISOCountryGroups = ['unknown']; - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.false; + it('ignore info when prompt UI is not displayed', () => { + ampConsent.isPromptUIOn_ = false; + event.data = { + 'type': 'consent-response', + 'action': 'accept', + 'info': 'accept-string', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.not.be.called; }); - }); - it('geo override promptIfUnknown', function* () { - ISOCountryGroups = ['unknown']; - consentElement = createConsentElement(doc, dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - 'promptIfUnknownForGeoGroup': 'testGroup', - }, - }, - })); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - ampConsent.buildCallback(); - return ampConsent.getConsentRequiredPromise_().then(isRequired => { - expect(isRequired).to.be.false; - }); - }); - }); - - describe('external consent action', () => { - let defaultConfig; - let ampConsent; - let actionSpy; - let event; - let ampIframe; - let iframe; - let consentElement; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - }, - }, + it('ignore info w/o amp-consent-v2 flag', () => { + // TODO(@zhouyx): Remove with amp-consent-v2 flag + toggleExperiment(win, 'amp-consent-v2', false); + event.data = { + 'type': 'consent-response', + 'action': 'accept', + 'info': 'accept-string', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, undefined); }); - consentElement = createConsentElement(doc, defaultConfig); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - actionSpy = sandbox.stub(ampConsent, 'handleAction_'); - ampConsent.enableInteractions_(); - ampIframe = document.createElement('amp-iframe'); - iframe = doc.createElement('iframe'); - ampIframe.appendChild(iframe); - ampConsent.element.appendChild(ampIframe); - ampConsent.isPromptUIOn_ = true; - event = new Event('message'); - }); - - it('listen to external consent response msg', () => { - event.data = { - 'type': 'consent-response', - 'action': 'accept', - 'info': 'accept-string', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, - 'accept-string'); - }); - - it('ignore info when prompt UI is not displayed', () => { - ampConsent.isPromptUIOn_ = false; - event.data = { - 'type': 'consent-response', - 'action': 'accept', - 'info': 'accept-string', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.not.be.called; - }); - - it('ignore info w/o amp-consent-v2 flag', () => { - // TODO(@zhouyx): Remove with amp-consent-v2 flag - toggleExperiment(win, 'amp-consent-v2', false); - event.data = { - 'type': 'consent-response', - 'action': 'accept', - 'info': 'accept-string', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.be.calledWith(ACTION_TYPE.ACCEPT, - undefined); - }); - it('ignore msg from incorrect source', () => { - event.data = { - 'type': 'consent-response', - 'action': 'accept', - }; - event.source = null; - win.dispatchEvent(event); - expect(actionSpy).to.not.be.called; - }); + it('ignore msg from incorrect source', () => { + event.data = { + 'type': 'consent-response', + 'action': 'accept', + }; + event.source = null; + win.dispatchEvent(event); + expect(actionSpy).to.not.be.called; + }); - it('ignore info with action dismiss', () => { - expectAsyncConsoleError('[amp-consent] ' + - 'Consent string value %s not applicable on user dismiss, ' + - 'stored value will be kept and used '); - event.data = { - 'type': 'consent-response', - 'action': 'dismiss', - 'info': 'test', - }; - event.source = iframe.contentWindow; - win.dispatchEvent(event); - expect(actionSpy).to.be.calledWith(ACTION_TYPE.DISMISS); + it('ignore info with action dismiss', () => { + expectAsyncConsoleError( + '[amp-consent] ' + + 'Consent string value %s not applicable on user dismiss, ' + + 'stored value will be kept and used ' + ); + event.data = { + 'type': 'consent-response', + 'action': 'dismiss', + 'info': 'test', + }; + event.source = iframe.contentWindow; + win.dispatchEvent(event); + expect(actionSpy).to.be.calledWith(ACTION_TYPE.DISMISS); + }); }); - }); - describe('UI', () => { - let uiElement; - let defaultConfig; - let ampConsent; - let updateConsentInstanceStateSpy; - let consentElement; - let postPromptUI; + describe('UI', () => { + let uiElement; + let defaultConfig; + let ampConsent; + let updateConsentInstanceStateSpy; + let consentElement; + let postPromptUI; - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response1', - 'promptUI': '123', + beforeEach(() => { + defaultConfig = dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response1', + 'promptUI': '123', + }, }, - }, - 'postPromptUI': 'test', - }); - consentElement = createConsentElement(doc, defaultConfig); - uiElement = document.createElement('div'); - uiElement.setAttribute('id', '123'); - consentElement.appendChild(uiElement); - postPromptUI = document.createElement('div'); - postPromptUI.setAttribute('id', 'test'); - consentElement.appendChild(postPromptUI); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - sandbox.stub(ampConsent.vsync_, 'mutate').callsFake(fn => { - fn(); - }); - sandbox.stub(ampConsent, 'mutateElement').callsFake(fn => { - fn(); + 'postPromptUI': 'test', + }); + consentElement = createConsentElement(doc, defaultConfig); + uiElement = document.createElement('div'); + uiElement.setAttribute('id', '123'); + consentElement.appendChild(uiElement); + postPromptUI = document.createElement('div'); + postPromptUI.setAttribute('id', 'test'); + consentElement.appendChild(postPromptUI); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + sandbox.stub(ampConsent.vsync_, 'mutate').callsFake(fn => { + fn(); + }); + sandbox.stub(ampConsent, 'mutateElement').callsFake(fn => { + fn(); + }); }); - }); - - it('update current displaying status', function* () { - ampConsent.buildCallback(); - yield macroTask(); - updateConsentInstanceStateSpy = - sandbox.spy(ampConsent.consentStateManager_, - 'updateConsentInstanceState'); - yield macroTask(); - expect(ampConsent.isPromptUIOn_).to.be.true; - yield macroTask(); - ampConsent.handleAction_(ACTION_TYPE.ACCEPT); - expect(updateConsentInstanceStateSpy).to.be.calledWith( - CONSENT_ITEM_STATE.ACCEPTED); - yield macroTask(); - expect(ampConsent.isPromptUIOn_).to.be.false; - }); - - it('ignore action when no consent prompt is displaying', function* () { - ampConsent.buildCallback(); - yield macroTask(); - updateConsentInstanceStateSpy = - sandbox.spy(ampConsent.consentStateManager_, - 'updateConsentInstanceState'); - ampConsent.handleAction_(ACTION_TYPE.DISMISS); - yield macroTask(); - expect(updateConsentInstanceStateSpy).to.be.calledOnce; - updateConsentInstanceStateSpy.resetHistory(); - expect(ampConsent.isPromptUIOn_).to.be.false; - ampConsent.handleAction_(ACTION_TYPE.DISMISS); - yield macroTask(); - expect(updateConsentInstanceStateSpy).to.not.be.called; - }); - describe('schedule display', () => { - it('should check for pending consent UI', function* () { + it('update current displaying status', function*() { ampConsent.buildCallback(); yield macroTask(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); - ampConsent.scheduleDisplay_(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); - ampConsent.hide_(); + updateConsentInstanceStateSpy = sandbox.spy( + ampConsent.consentStateManager_, + 'updateConsentInstanceState' + ); yield macroTask(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(0); - ampConsent.scheduleDisplay_(); - ampConsent.scheduleDisplay_(); - ampConsent.scheduleDisplay_(); - expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); - }); - }); - - describe('postPromptUI', () => { - let postPromptUI; - - beforeEach(() => { - postPromptUI = doc.getElementById('test'); + expect(ampConsent.isPromptUIOn_).to.be.true; + yield macroTask(); + ampConsent.handleAction_(ACTION_TYPE.ACCEPT); + expect(updateConsentInstanceStateSpy).to.be.calledWith( + CONSENT_ITEM_STATE.ACCEPTED + ); + yield macroTask(); + expect(ampConsent.isPromptUIOn_).to.be.false; }); - it('handle postPromptUI', function* () { - storageValue = { - 'amp-consent:ABC': true, - }; - - // Build the amp consent, and check that everything is - // initialized correctly + it('ignore action when no consent prompt is displaying', function*() { ampConsent.buildCallback(); - ampConsent.element.classList.remove('i-amphtml-notbuilt'); - expect(ampConsent.postPromptUI_).to.not.be.null; - expect(ampConsent.element).to.have.display('none'); - expect(postPromptUI).to.have.display('none'); - - // Wait for all modifications to the element to be applied. - // Then make more assertions. yield macroTask(); - expect(ampConsent.element).to.not.have.display('none'); - expect(ampConsent.element.classList.contains('amp-active')).to.be.true; - expect(ampConsent.element.classList.contains('amp-hidden')).to.be.false; - expect(postPromptUI).to.not.have.display('none'); - - // Schedule the display of the element - ampConsent.scheduleDisplay_(); - - // Wait for the element to be displayed, - // And the postPrompt to be hidden. + updateConsentInstanceStateSpy = sandbox.spy( + ampConsent.consentStateManager_, + 'updateConsentInstanceState' + ); + ampConsent.handleAction_(ACTION_TYPE.DISMISS); + yield macroTask(); + expect(updateConsentInstanceStateSpy).to.be.calledOnce; + updateConsentInstanceStateSpy.resetHistory(); + expect(ampConsent.isPromptUIOn_).to.be.false; + ampConsent.handleAction_(ACTION_TYPE.DISMISS); yield macroTask(); - expect(postPromptUI).to.have.display('none'); + expect(updateConsentInstanceStateSpy).to.not.be.called; }); - describe('hide/show postPromptUI', () => { - beforeEach(() => { - defaultConfig = dict({ - 'consents': { - 'ABC': { - 'checkConsentHref': 'https://response3', - }, - }, - // There's already an amp-consent from a parent beforeEach with a - // test postPromptUI - 'postPromptUI': 'test2', - }); - consentElement = createConsentElement(doc, defaultConfig); - postPromptUI = doc.createElement('div'); - postPromptUI.setAttribute('id', 'test2'); - consentElement.appendChild(postPromptUI); - doc.body.appendChild(consentElement); - ampConsent = new AmpConsent(consentElement); - }); - - it('hide postPromptUI', function* () { + describe('schedule display', () => { + it('should check for pending consent UI', function*() { ampConsent.buildCallback(); - ampConsent.element.classList.remove('i-amphtml-notbuilt'); yield macroTask(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); + ampConsent.scheduleDisplay_(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); + ampConsent.hide_(); + yield macroTask(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(0); + ampConsent.scheduleDisplay_(); + ampConsent.scheduleDisplay_(); + ampConsent.scheduleDisplay_(); + expect(ampConsent.notificationUiManager_.queueSize_).to.equal(1); + }); + }); - expect(postPromptUI).to.not.be.null; - expect(postPromptUI).to.have.display('none'); + describe('postPromptUI', () => { + let postPromptUI; + + beforeEach(() => { + postPromptUI = doc.getElementById('test'); }); - it('show postPromptUI', function* () { + it('handle postPromptUI', function*() { storageValue = { 'amp-consent:ABC': true, }; + + // Build the amp consent, and check that everything is + // initialized correctly ampConsent.buildCallback(); ampConsent.element.classList.remove('i-amphtml-notbuilt'); - yield macroTask(); + expect(ampConsent.postPromptUI_).to.not.be.null; + expect(ampConsent.element).to.have.display('none'); + expect(postPromptUI).to.have.display('none'); - expect(postPromptUI).to.not.be.null; + // Wait for all modifications to the element to be applied. + // Then make more assertions. + yield macroTask(); + expect(ampConsent.element).to.not.have.display('none'); + expect(ampConsent.element.classList.contains('amp-active')).to.be + .true; + expect(ampConsent.element.classList.contains('amp-hidden')).to.be + .false; expect(postPromptUI).to.not.have.display('none'); + + // Schedule the display of the element + ampConsent.scheduleDisplay_(); + + // Wait for the element to be displayed, + // And the postPrompt to be hidden. + yield macroTask(); + expect(postPromptUI).to.have.display('none'); + }); + + describe('hide/show postPromptUI', () => { + beforeEach(() => { + defaultConfig = dict({ + 'consents': { + 'ABC': { + 'checkConsentHref': 'https://response3', + }, + }, + // There's already an amp-consent from a parent beforeEach with a + // test postPromptUI + 'postPromptUI': 'test2', + }); + consentElement = createConsentElement(doc, defaultConfig); + postPromptUI = doc.createElement('div'); + postPromptUI.setAttribute('id', 'test2'); + consentElement.appendChild(postPromptUI); + doc.body.appendChild(consentElement); + ampConsent = new AmpConsent(consentElement); + }); + + it('hide postPromptUI', function*() { + ampConsent.buildCallback(); + ampConsent.element.classList.remove('i-amphtml-notbuilt'); + yield macroTask(); + + expect(postPromptUI).to.not.be.null; + expect(postPromptUI).to.have.display('none'); + }); + + it('show postPromptUI', function*() { + storageValue = { + 'amp-consent:ABC': true, + }; + ampConsent.buildCallback(); + ampConsent.element.classList.remove('i-amphtml-notbuilt'); + yield macroTask(); + + expect(postPromptUI).to.not.be.null; + expect(postPromptUI).to.not.have.display('none'); + }); }); }); }); - }); -}); - + } +); /** * Create an element from config for testing diff --git a/extensions/amp-consent/0.1/test/test-consent-config.js b/extensions/amp-consent/0.1/test/test-consent-config.js index ac6513e46087e..78b549e9933a1 100644 --- a/extensions/amp-consent/0.1/test/test-consent-config.js +++ b/extensions/amp-consent/0.1/test/test-consent-config.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; import {ConsentConfig, expandPolicyConfig} from '../consent-config'; import {dict} from '../../../../src/utils/object'; @@ -47,111 +46,129 @@ describes.realWin('ConsentConfig', {amp: 1}, env => { it('read inline config', () => { appendConfigScriptElement(doc, element, defaultConfig); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': 'ABC', - 'checkConsentHref': 'https://response1', - })); + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': 'ABC', + 'checkConsentHref': 'https://response1', + }) + ); }); it('read cmp config', () => { appendConfigScriptElement(doc, element, dict({})); element.setAttribute('type', '_ping_'); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': '_ping_', - 'checkConsentHref': '/get-consent-v1', - 'promptUISrc': - '/test/manual/diy-consent.html', - })); + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': '_ping_', + 'checkConsentHref': '/get-consent-v1', + 'promptUISrc': '/test/manual/diy-consent.html', + }) + ); }); it('support deprecated config format', () => { - appendConfigScriptElement(doc, element, dict({ - 'consents': { - 'ABC': { - 'promptIfUnknownForGeoGroup': 'eea', - 'checkConsentHref': '/href', - 'clientConfig': { - 'test': 'error', + appendConfigScriptElement( + doc, + element, + dict({ + 'consents': { + 'ABC': { + 'promptIfUnknownForGeoGroup': 'eea', + 'checkConsentHref': '/href', + 'clientConfig': { + 'test': 'error', + }, }, }, - }, - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'postPromptUI': 'test', - })); + 'clientConfig': { + 'test': 'ABC', + }, + 'uiConfig': { + 'overlay': true, + }, + 'postPromptUI': 'test', + }) + ); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': 'ABC', - 'promptIfUnknownForGeoGroup': 'eea', - 'checkConsentHref': '/href', - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'postPromptUI': 'test', - })); + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': 'ABC', + 'promptIfUnknownForGeoGroup': 'eea', + 'checkConsentHref': '/href', + 'clientConfig': { + 'test': 'ABC', + }, + 'uiConfig': { + 'overlay': true, + }, + 'postPromptUI': 'test', + }) + ); }); it('merge inline config w/ cmp config', () => { - appendConfigScriptElement(doc, element, dict({ - 'consentInstanceId': '_ping_', - 'promptIfUnknownForGeoGroup': 'eea', - 'checkConsentHref': '/override', - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'policy': { - 'default': { - 'waitFor': {}, + appendConfigScriptElement( + doc, + element, + dict({ + 'consentInstanceId': '_ping_', + 'promptIfUnknownForGeoGroup': 'eea', + 'checkConsentHref': '/override', + 'clientConfig': { + 'test': 'ABC', }, - }, - 'postPromptUI': 'test', - })); + 'uiConfig': { + 'overlay': true, + }, + 'policy': { + 'default': { + 'waitFor': {}, + }, + }, + 'postPromptUI': 'test', + }) + ); element.setAttribute('type', '_ping_'); const consentConfig = new ConsentConfig(element); - expect(consentConfig.getConsentConfig()).to.deep.equal(dict({ - 'consentInstanceId': '_ping_', - 'checkConsentHref': '/override', - 'promptUISrc': - '/test/manual/diy-consent.html', - 'promptIfUnknownForGeoGroup': 'eea', - 'postPromptUI': 'test', - 'clientConfig': { - 'test': 'ABC', - }, - 'uiConfig': { - 'overlay': true, - }, - 'policy': { - 'default': { - 'waitFor': {}, + expect(consentConfig.getConsentConfig()).to.deep.equal( + dict({ + 'consentInstanceId': '_ping_', + 'checkConsentHref': '/override', + 'promptUISrc': '/test/manual/diy-consent.html', + 'promptIfUnknownForGeoGroup': 'eea', + 'postPromptUI': 'test', + 'clientConfig': { + 'test': 'ABC', }, - }, - })); + 'uiConfig': { + 'overlay': true, + }, + 'policy': { + 'default': { + 'waitFor': {}, + }, + }, + }) + ); }); it('assert valid config', () => { - const scriptTypeError = 'amp-consent/consent-config: ' - ); - - fs.writeFileSync('dist/v0/' + fileName + '.html', - fileContents); - } + .pipe(gulp.dest(tempBuildDir)) + ) + .then(function() { + return compileJs('./' + tempBuildDir, builtName, './' + distDir, { + watch, + includePolyfills: true, + minify: options.minify || argv.minify, + minifiedName, + preventRemoveAndMakeDir: options.preventRemoveAndMakeDir, + extraGlobs: [tempBuildDir + '*.js'], }); + }) + .then(function() { + if (fs.existsSync(distDir + '/' + minifiedName)) { + // Build Helper Frame HTML + let fileContents = fs.readFileSync( + basePath + fileName + '.html', + 'utf8' + ); + fileContents = fileContents.replace( + '', + '' + ); + + fs.writeFileSync('dist/v0/' + fileName + '.html', fileContents); + } + }); } /** @@ -1659,12 +1873,8 @@ function buildLoginDoneVersion(version, options) { const html = fs.readFileSync(htmlPath, 'utf8'); const minJs = `https://${hostname}/v0/amp-login-done-${version}.js`; const minHtml = html - .replace( - `../../../dist/v0/amp-login-done-${version}.max.js`, - minJs) - .replace( - `../../../dist/v0/amp-login-done-${version}.js`, - minJs); + .replace(`../../../dist/v0/amp-login-done-${version}.max.js`, minJs) + .replace(`../../../dist/v0/amp-login-done-${version}.js`, minJs); if (minHtml.indexOf(minJs) == -1) { throw new Error('Failed to correctly set JS in login-done.html'); } @@ -1672,31 +1882,32 @@ function buildLoginDoneVersion(version, options) { mkdirSync('dist'); mkdirSync('dist/v0'); - fs.writeFileSync('dist/v0/amp-login-done-' + version + '.html', - minHtml); + fs.writeFileSync('dist/v0/amp-login-done-' + version + '.html', minHtml); // Build JS. const js = fs.readFileSync(jsPath, 'utf8'); const builtName = 'amp-login-done-' + version + '.max.js'; const minifiedName = 'amp-login-done-' + version + '.js'; const latestName = 'amp-login-done-latest.js'; - return toPromise(gulp.src(path + '/*.js') + return toPromise( + gulp + .src(path + '/*.js') .pipe($$.file(builtName, js)) - .pipe(gulp.dest(buildDir))) - .then(function() { - return compileJs('./' + buildDir, builtName, './dist/v0/', { - watch: false, - includePolyfills: true, - minify: options.minify || argv.minify, - minifiedName, - preventRemoveAndMakeDir: options.preventRemoveAndMakeDir, - latestName, - extraGlobs: [ - buildDir + 'amp-login-done-0.1.max.js', - buildDir + 'amp-login-done-dialog.js', - ], - }); - }); + .pipe(gulp.dest(buildDir)) + ).then(function() { + return compileJs('./' + buildDir, builtName, './dist/v0/', { + watch: false, + includePolyfills: true, + minify: options.minify || argv.minify, + minifiedName, + preventRemoveAndMakeDir: options.preventRemoveAndMakeDir, + latestName, + extraGlobs: [ + buildDir + 'amp-login-done-0.1.max.js', + buildDir + 'amp-login-done-dialog.js', + ], + }); + }); } /** @@ -1804,44 +2015,60 @@ gulp.task('build', 'Builds the AMP library', maybeUpdatePackages, build, { noextensions: ' Builds with no extensions.', }, }); -gulp.task('check-all', 'Run through all presubmit checks', - ['lint', 'dep-check', 'check-types', 'presubmit']); +gulp.task('check-all', 'Run through all presubmit checks', [ + 'lint', + 'dep-check', + 'check-types', + 'presubmit', +]); gulp.task('check-types', 'Check JS types', maybeUpdatePackages, checkTypes); gulp.task('css', 'Recompile css to build directory', maybeUpdatePackages, css); -gulp.task('default', 'Runs "watch" and then "serve"', - maybeUpdatePackages.concat(['watch']), serve, { - options: { - extensions: ' Watches and builds only the listed extensions.', - extensions_from: ' Watches and builds only the extensions from the ' + - 'listed AMP(s).', - noextensions: ' Watches and builds with no extensions.', - }, - }); +gulp.task( + 'default', + 'Runs "watch" and then "serve"', + maybeUpdatePackages.concat(['watch']), + serve, + { + options: { + extensions: ' Watches and builds only the listed extensions.', + extensions_from: + ' Watches and builds only the extensions from the ' + 'listed AMP(s).', + noextensions: ' Watches and builds with no extensions.', + }, + } +); gulp.task('dist', 'Build production binaries', maybeUpdatePackages, dist, { options: { - pseudo_names: ' Compiles with readable names. ' + - 'Great for profiling and debugging production code.', + pseudo_names: + ' Compiles with readable names. ' + + 'Great for profiling and debugging production code.', fortesting: ' Compiles production binaries for local testing', config: ' Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', - single_pass: 'Compile AMP\'s primary JS bundles in a single invocation', + single_pass: "Compile AMP's primary JS bundles in a single invocation", extensions: ' Builds only the listed extensions.', extensions_from: ' Builds only the extensions from the listed AMP(s).', noextensions: ' Builds with no extensions.', - single_pass_dest: ' The directory closure compiler will write out to ' + - 'with --single_pass mode. The default directory is `dist`', + single_pass_dest: + ' The directory closure compiler will write out to ' + + 'with --single_pass mode. The default directory is `dist`', full_sourcemaps: ' Includes source code content in sourcemaps', }, }); -gulp.task('watch', 'Watches for changes in files, re-builds when detected', - maybeUpdatePackages, watch, { - options: { - with_inabox: ' Also watch and build the amp-inabox.js binary.', - with_shadow: ' Also watch and build the amp-shadow.js binary.', - extensions: ' Watches and builds only the listed extensions.', - extensions_from: ' Watches and builds only the extensions from the ' + - 'listed AMP(s).', - noextensions: ' Watches and builds with no extensions.', - }, - }); +gulp.task( + 'watch', + 'Watches for changes in files, re-builds when detected', + maybeUpdatePackages, + watch, + { + options: { + with_inabox: ' Also watch and build the amp-inabox.js binary.', + with_shadow: ' Also watch and build the amp-shadow.js binary.', + extensions: ' Watches and builds only the listed extensions.', + extensions_from: + ' Watches and builds only the extensions from the ' + 'listed AMP(s).', + noextensions: ' Watches and builds with no extensions.', + }, + } +); gulp.task('build-experiments', 'Builds experiments.html/js', buildExperiments); gulp.task('build-login-done', 'Builds login-done.html/js', buildLoginDone); diff --git a/src/3p-frame-messaging.js b/src/3p-frame-messaging.js index ffebe980cf11f..bb46beb1ff114 100644 --- a/src/3p-frame-messaging.js +++ b/src/3p-frame-messaging.js @@ -19,7 +19,6 @@ import {dict} from './utils/object'; import {internalListenImplementation} from './event-helper-listen'; import {parseJson} from './json'; - /** @const */ const AMP_MESSAGE_PREFIX = 'amp-'; export const CONSTANTS = { @@ -74,10 +73,13 @@ export const MessageType = { */ export function listen(element, eventType, listener, opt_evtListenerOpts) { return internalListenImplementation( - element, eventType, listener, opt_evtListenerOpts); + element, + eventType, + listener, + opt_evtListenerOpts + ); } - /** * Serialize an AMP post message. Output looks like: * 'amp-011481323099490{"type":"position","sentinel":"12345","foo":"bar"}' @@ -87,8 +89,12 @@ export function listen(element, eventType, listener, opt_evtListenerOpts) { * @param {?string=} rtvVersion * @return {string} */ -export function serializeMessage(type, sentinel, data = dict(), - rtvVersion = null) { +export function serializeMessage( + type, + sentinel, + data = dict(), + rtvVersion = null +) { // TODO: consider wrap the data in a "data" field. { type, sentinal, data } const message = data; message['type'] = type; @@ -96,7 +102,6 @@ export function serializeMessage(type, sentinel, data = dict(), return AMP_MESSAGE_PREFIX + (rtvVersion || '') + JSON.stringify(message); } - /** * Deserialize an AMP post message. * Returns null if it's not valid AMP message format. @@ -118,16 +123,17 @@ export function deserializeMessage(message) { } } - /** * Returns true if message looks like it is an AMP postMessage * @param {*} message * @return {boolean} */ export function isAmpMessage(message) { - return (typeof message == 'string' && - message.indexOf(AMP_MESSAGE_PREFIX) == 0 && - message.indexOf('{') != -1); + return ( + typeof message == 'string' && + message.indexOf(AMP_MESSAGE_PREFIX) == 0 && + message.indexOf('{') != -1 + ); } /** @typedef {{creativeId: string, message: string}} */ diff --git a/src/3p-frame.js b/src/3p-frame.js index a94fcc7926361..587443ac255d7 100644 --- a/src/3p-frame.js +++ b/src/3p-frame.js @@ -54,8 +54,7 @@ function getFrameAttributes(parentWindow, element, opt_type, opt_context) { let attributes = dict(); // Do these first, as the other attributes have precedence. addDataAndJsonAttributes_(element, attributes); - attributes = getContextMetadata(parentWindow, element, sentinel, - attributes); + attributes = getContextMetadata(parentWindow, element, sentinel, attributes); attributes['type'] = type; Object.assign(attributes['_context'], opt_context); return attributes; @@ -75,38 +74,49 @@ function getFrameAttributes(parentWindow, element, opt_type, opt_context) { * @return {!HTMLIFrameElement} The iframe. */ export function getIframe( - parentWindow, parentElement, opt_type, opt_context, - {disallowCustom, allowFullscreen} = {}) { + parentWindow, + parentElement, + opt_type, + opt_context, + {disallowCustom, allowFullscreen} = {} +) { // Check that the parentElement is already in DOM. This code uses a new and // fast `isConnected` API and thus only used when it's available. devAssert( - parentElement['isConnected'] === undefined || + parentElement['isConnected'] === undefined || parentElement['isConnected'] === true, - 'Parent element must be in DOM'); - const attributes = - getFrameAttributes(parentWindow, parentElement, opt_type, opt_context); - const iframe = /** @type {!HTMLIFrameElement} */ ( - parentWindow.document.createElement('iframe')); + 'Parent element must be in DOM' + ); + const attributes = getFrameAttributes( + parentWindow, + parentElement, + opt_type, + opt_context + ); + const iframe = /** @type {!HTMLIFrameElement} */ (parentWindow.document.createElement( + 'iframe' + )); if (!count[attributes['type']]) { count[attributes['type']] = 0; } count[attributes['type']] += 1; - const baseUrl = getBootstrapBaseUrl( - parentWindow, undefined, disallowCustom); + const baseUrl = getBootstrapBaseUrl(parentWindow, undefined, disallowCustom); const host = parseUrlDeprecated(baseUrl).hostname; // This name attribute may be overwritten if this frame is chosen to // be the master frame. That is ok, as we will read the name off // for our uses before that would occur. // @see https://github.com/ampproject/amphtml/blob/master/3p/integration.js - const name = JSON.stringify(dict({ - 'host': host, - 'type': attributes['type'], - // https://github.com/ampproject/amphtml/pull/2955 - 'count': count[attributes['type']], - 'attributes': attributes, - })); + const name = JSON.stringify( + dict({ + 'host': host, + 'type': attributes['type'], + // https://github.com/ampproject/amphtml/pull/2955 + 'count': count[attributes['type']], + 'attributes': attributes, + }) + ); iframe.src = baseUrl; iframe.ampLocation = parseUrlDeprecated(baseUrl); @@ -135,15 +145,19 @@ export function getIframe( // Block synchronous XHR in ad. These are very rare, but super bad for UX // as they block the UI thread for the arbitrary amount of time until the // request completes. - iframe.setAttribute('allow', 'sync-xhr \'none\';'); + iframe.setAttribute('allow', "sync-xhr 'none';"); } const excludeFromSandbox = ['facebook']; - if (isExperimentOn(parentWindow, 'sandbox-ads') - && !excludeFromSandbox.includes(opt_type)) { + if ( + isExperimentOn(parentWindow, 'sandbox-ads') && + !excludeFromSandbox.includes(opt_type) + ) { applySandbox(iframe); } - iframe.setAttribute('data-amp-3p-sentinel', - attributes['_context']['sentinel']); + iframe.setAttribute( + 'data-amp-3p-sentinel', + attributes['_context']['sentinel'] + ); return iframe; } @@ -170,8 +184,9 @@ export function addDataAndJsonAttributes_(element, attributes) { const obj = tryParseJson(json); if (obj === undefined) { throw user().createError( - 'Error parsing JSON in json attribute in element %s', - element); + 'Error parsing JSON in json attribute in element %s', + element + ); } for (const key in obj) { attributes[key] = obj[key]; @@ -206,7 +221,10 @@ export function preloadBootstrap(win, preconnect, opt_disallowCustom) { * @visibleForTesting */ export function getBootstrapBaseUrl( - parentWindow, opt_strictForUnitTest, opt_disallowCustom) { + parentWindow, + opt_strictForUnitTest, + opt_disallowCustom +) { const customBootstrapBaseUrl = opt_disallowCustom ? null : getCustomBootstrapBaseUrl(parentWindow, opt_strictForUnitTest); @@ -240,10 +258,13 @@ export function getDefaultBootstrapBaseUrl(parentWindow, opt_srcFileBasename) { } // Ensure same sub-domain is used despite potentially different file. parentWindow.defaultBootstrapSubDomain = - parentWindow.defaultBootstrapSubDomain || getSubDomain(parentWindow); - return 'https://' + parentWindow.defaultBootstrapSubDomain + - `.${urls.thirdPartyFrameHost}/${version()}/` + - `${srcFileBasename}.html`; + parentWindow.defaultBootstrapSubDomain || getSubDomain(parentWindow); + return ( + 'https://' + + parentWindow.defaultBootstrapSubDomain + + `.${urls.thirdPartyFrameHost}/${version()}/` + + `${srcFileBasename}.html` + ); } /** @@ -253,11 +274,15 @@ export function getDefaultBootstrapBaseUrl(parentWindow, opt_srcFileBasename) { * @return {string} */ export function getDevelopmentBootstrapBaseUrl(parentWindow, srcFileBasename) { - return overrideBootstrapBaseUrl || getAdsLocalhost(parentWindow) - + '/dist.3p/' - + (getMode().minified ? `${version()}/${srcFileBasename}` - : `current/${srcFileBasename}.max`) - + '.html'; + return ( + overrideBootstrapBaseUrl || + getAdsLocalhost(parentWindow) + + '/dist.3p/' + + (getMode().minified + ? `${version()}/${srcFileBasename}` + : `current/${srcFileBasename}.max`) + + '.html' + ); } /** @@ -311,25 +336,33 @@ export function getRandom(win) { * @return {?string} */ function getCustomBootstrapBaseUrl(parentWindow, opt_strictForUnitTest) { - const meta = parentWindow.document - .querySelector('meta[name="amp-3p-iframe-src"]'); + const meta = parentWindow.document.querySelector( + 'meta[name="amp-3p-iframe-src"]' + ); if (!meta) { return null; } const url = assertHttpsUrl(meta.getAttribute('content'), meta); - userAssert(url.indexOf('?') == -1, - '3p iframe url must not include query string %s in element %s.', - url, meta); + userAssert( + url.indexOf('?') == -1, + '3p iframe url must not include query string %s in element %s.', + url, + meta + ); // This is not a security primitive, we just don't want this to happen in // practice. People could still redirect to the same origin, but they cannot // redirect to the proxy origin which is the important one. const parsed = parseUrlDeprecated(url); - userAssert((parsed.hostname == 'localhost' && !opt_strictForUnitTest) || + userAssert( + (parsed.hostname == 'localhost' && !opt_strictForUnitTest) || parsed.origin != parseUrlDeprecated(parentWindow.location.href).origin, - '3p iframe url must not be on the same origin as the current document ' + + '3p iframe url must not be on the same origin as the current document ' + '%s (%s) in element %s. See https://github.com/ampproject/amphtml' + - '/blob/master/spec/amp-iframe-origin-policy.md for details.', url, - parsed.origin, meta); + '/blob/master/spec/amp-iframe-origin-policy.md for details.', + url, + parsed.origin, + meta + ); return `${url}?${version()}`; } @@ -376,7 +409,7 @@ export function applySandbox(iframe) { for (let i = 0; i < requiredFlags.length; i++) { const flag = requiredFlags[i]; if (!iframe.sandbox.supports(flag)) { - dev().info(TAG, 'Iframe doesn\'t support %s', flag); + dev().info(TAG, "Iframe doesn't support %s", flag); return; } } diff --git a/src/ad-cid.js b/src/ad-cid.js index d506363c5eb1c..7fa11f270092b 100644 --- a/src/ad-cid.js +++ b/src/ad-cid.js @@ -29,8 +29,11 @@ export function getAdCid(adElement) { if (!config || !config.clientIdScope) { return Promise.resolve(); } - return getOrCreateAdCid(adElement.getAmpDoc(), config.clientIdScope, - config.clientIdCookieName); + return getOrCreateAdCid( + adElement.getAmpDoc(), + config.clientIdScope, + config.clientIdCookieName + ); } /** @@ -41,29 +44,39 @@ export function getAdCid(adElement) { * @return {!Promise} A promise for a CID or undefined. */ export function getOrCreateAdCid( - ampDoc, clientIdScope, opt_clientIdCookieName, opt_timeout) { - const timeout = isNaN(opt_timeout) || opt_timeout == null ? - 1000 : opt_timeout; + ampDoc, + clientIdScope, + opt_clientIdCookieName, + opt_timeout +) { + const timeout = + isNaN(opt_timeout) || opt_timeout == null ? 1000 : opt_timeout; const cidPromise = Services.cidForDoc(ampDoc).then(cidService => { if (!cidService) { return; } - return cidService.get({ - scope: dev().assertString(clientIdScope), - createCookieIfNotPresent: true, - cookieName: opt_clientIdCookieName, - }, Promise.resolve(undefined)).catch(error => { - // Not getting a CID is not fatal. - dev().error('AD-CID', error); - return undefined; - }); + return cidService + .get( + { + scope: dev().assertString(clientIdScope), + createCookieIfNotPresent: true, + cookieName: opt_clientIdCookieName, + }, + Promise.resolve(undefined) + ) + .catch(error => { + // Not getting a CID is not fatal. + dev().error('AD-CID', error); + return undefined; + }); }); // The CID should never be crucial for an ad. If it does not come within // 1 second, assume it will never arrive. return Services.timerFor(ampDoc.win) - .timeoutPromise(timeout, cidPromise, 'cid timeout').catch(error => { - // Timeout is not fatal. - dev().warn('AD-CID', error); - return undefined; - }); + .timeoutPromise(timeout, cidPromise, 'cid timeout') + .catch(error => { + // Timeout is not fatal. + dev().warn('AD-CID', error); + return undefined; + }); } diff --git a/src/ad-helper.js b/src/ad-helper.js index 030a4b3279489..b6b62143286e9 100644 --- a/src/ad-helper.js +++ b/src/ad-helper.js @@ -85,7 +85,7 @@ export function getAdContainer(element) { let el = element.parentElement; while (el && el.tagName != 'BODY') { if (CONTAINERS[el.tagName]) { - return element[AD_CONTAINER_PROP] = el.tagName; + return (element[AD_CONTAINER_PROP] = el.tagName); } el = el.parentElement; } @@ -108,12 +108,10 @@ export function getAmpAdResourceId(node, topWin) { if (frameParent.nodeName == 'AMP-AD') { return String(frameParent.getResourceId()); } - } catch (e) { - } + } catch (e) {} // Whether we entered the catch above (e.g. due to attempt to access // across xdomain boundary), or failed to enter the if further above, the // node is not within a friendly amp-ad tag. So, there is no amp-ad // resource ID. How to handle that is up to the caller, but see TODO above. return null; } - diff --git a/src/amp-shadow.js b/src/amp-shadow.js index d50e5dba10447..b4ae4aefa77d9 100644 --- a/src/amp-shadow.js +++ b/src/amp-shadow.js @@ -62,21 +62,27 @@ if (isExperimentOn(self, 'ampdoc-shell')) { installPerformanceService(self); const ampdocService = Services.ampdocServiceFor(self); const ampdocShell = ampdocService.installShellShadowDoc(); - installStylesForDoc(ampdocShell, cssText, () => { - installAmpdocServices(ampdocShell); + installStylesForDoc( + ampdocShell, + cssText, + () => { + installAmpdocServices(ampdocShell); - // Builtins. - installBuiltins(self); + // Builtins. + installBuiltins(self); - // Final configuration and stubbing. - adoptShadowMode(self); + // Final configuration and stubbing. + adoptShadowMode(self); - // Pre-stub already known elements. - stubElementsForDoc(ampdocShell); + // Pre-stub already known elements. + stubElementsForDoc(ampdocShell); - makeBodyVisible(self.document); - Services.resourcesForDoc(ampdocShell).ampInitComplete(); - }, /* opt_isRuntimeCss */ true, /* opt_ext */ 'amp-runtime'); + makeBodyVisible(self.document); + Services.resourcesForDoc(ampdocShell).ampInitComplete(); + }, + /* opt_isRuntimeCss */ true, + /* opt_ext */ 'amp-runtime' + ); } else { // PWA shell manages its own visibility and shadow ampdocs their own. bodyAlwaysVisible(self); @@ -92,7 +98,9 @@ if (isExperimentOn(self, 'ampdoc-shell')) { // tag to give some information that can be used in error reports. // (At least by sophisticated users). if (self.console) { - (console.info || console.log).call(console, - `Powered by AMP ⚡ HTML shadows – Version ${version()}`); + (console.info || console.log).call( + console, + `Powered by AMP ⚡ HTML shadows – Version ${version()}` + ); } self.document.documentElement.setAttribute('amp-version', version()); diff --git a/src/amp.js b/src/amp.js index 8f76df1aa9769..46c1f720ce0c5 100644 --- a/src/amp.js +++ b/src/amp.js @@ -91,57 +91,66 @@ if (shouldMainBootstrapRun) { installPerformanceService(self); /** @const {!./service/performance-impl.Performance} */ const perf = Services.performanceFor(self); - if (self.document.documentElement - .hasAttribute('i-amphtml-no-boilerplate')) { + if ( + self.document.documentElement.hasAttribute('i-amphtml-no-boilerplate') + ) { perf.addEnabledExperiment('no-boilerplate'); } installPlatformService(self); fontStylesheetTimeout(self); perf.tick('is'); - installStylesForDoc(ampdoc, cssText, () => { - startupChunk(self.document, function services() { - // Core services. - installRuntimeServices(self); - installAmpdocServices(ampdoc); - // We need the core services (viewer/resources) to start instrumenting - perf.coreServicesAvailable(); - maybeTrackImpression(self); - }); - startupChunk(self.document, function adoptWindow() { - adopt(self); - }); - startupChunk(self.document, function builtins() { - // Builtins. - installBuiltins(self); - }); - startupChunk(self.document, function stub() { - // Pre-stub already known elements. - stubElementsForDoc(ampdoc); - }); - startupChunk(self.document, function final() { - installPullToRefreshBlocker(self); - installAutoLightboxExtension(ampdoc); + installStylesForDoc( + ampdoc, + cssText, + () => { + startupChunk(self.document, function services() { + // Core services. + installRuntimeServices(self); + installAmpdocServices(ampdoc); + // We need the core services (viewer/resources) to start instrumenting + perf.coreServicesAvailable(); + maybeTrackImpression(self); + }); + startupChunk(self.document, function adoptWindow() { + adopt(self); + }); + startupChunk(self.document, function builtins() { + // Builtins. + installBuiltins(self); + }); + startupChunk(self.document, function stub() { + // Pre-stub already known elements. + stubElementsForDoc(ampdoc); + }); + startupChunk(self.document, function final() { + installPullToRefreshBlocker(self); + installAutoLightboxExtension(ampdoc); - maybeValidate(self); - makeBodyVisible(self.document); - }); - startupChunk(self.document, function finalTick() { - perf.tick('e_is'); - Services.resourcesForDoc(ampdoc).ampInitComplete(); - // TODO(erwinm): move invocation of the `flush` method when we have the - // new ticks in place to batch the ticks properly. - perf.flush(); - }); - }, /* opt_isRuntimeCss */ true, /* opt_ext */ 'amp-runtime'); + maybeValidate(self); + makeBodyVisible(self.document); + }); + startupChunk(self.document, function finalTick() { + perf.tick('e_is'); + Services.resourcesForDoc(ampdoc).ampInitComplete(); + // TODO(erwinm): move invocation of the `flush` method when we have the + // new ticks in place to batch the ticks properly. + perf.flush(); + }); + }, + /* opt_isRuntimeCss */ true, + /* opt_ext */ 'amp-runtime' + ); }); // Output a message to the console and add an attribute to the // tag to give some information that can be used in error reports. // (At least by sophisticated users). if (self.console) { - (console.info || console.log).call(console, - `Powered by AMP ⚡ HTML – Version ${version()}`, - self.location.href); + (console.info || console.log).call( + console, + `Powered by AMP ⚡ HTML – Version ${version()}`, + self.location.href + ); } self.document.documentElement.setAttribute('amp-version', version()); } diff --git a/src/animation.js b/src/animation.js index 219a4634b8f59..727e84fa31c18 100644 --- a/src/animation.js +++ b/src/animation.js @@ -31,7 +31,6 @@ const NOOP_CALLBACK = function() {}; * achieve the desired effect. */ export class Animation { - /** * Creates and starts animation with a single segment. Returns AnimationPlayer * object that can be used to monitor or control animation. @@ -45,9 +44,9 @@ export class Animation { */ static animate(contextNode, transition, duration, opt_curve) { return new Animation(contextNode) - .setCurve(opt_curve) - .add(0, transition, 1) - .start(duration); + .setCurve(opt_curve) + .add(0, transition, 1) + .start(duration); } /** @@ -116,13 +115,17 @@ export class Animation { * @return {!AnimationPlayer} */ start(duration) { - const player = new AnimationPlayer(this.vsync_, this.contextNode_, - this.segments_, this.curve_, duration); + const player = new AnimationPlayer( + this.vsync_, + this.contextNode_, + this.segments_, + this.curve_, + duration + ); return player; } } - /** * AnimationPlayer allows tracking and monitoring of the running animation. * Most importantly it exposes methods "then" and "thenAlways" that have the @@ -133,7 +136,6 @@ export class Animation { * implements {IThenable} */ class AnimationPlayer { - /** * @param {!./service/vsync-impl.Vsync} vsync * @param {!Node} contextNode @@ -142,7 +144,6 @@ class AnimationPlayer { * @param {./time.timeDef} duration */ constructor(vsync, contextNode, segments, defaultCurve, duration) { - /** @private @const {!./service/vsync-impl.Vsync} */ this.vsync_ = vsync; @@ -257,7 +258,7 @@ class AnimationPlayer { // Sort in the completion order. if (this.segments_.length > 1) { this.segments_.sort((s1, s2) => { - return (s1.delay + s1.duration) - (s2.delay + s2.duration); + return s1.delay + s1.duration - (s2.delay + s2.duration); }); } try { @@ -293,8 +294,10 @@ class AnimationPlayer { return; } const currentTime = Date.now(); - const normLinearTime = Math.min((currentTime - this.startTime_) / - this.duration_, 1); + const normLinearTime = Math.min( + (currentTime - this.startTime_) / this.duration_, + 1 + ); // Start segments due to be started for (let i = 0; i < this.segments_.length; i++) { @@ -334,8 +337,10 @@ class AnimationPlayer { let normLinearTime; let normTime; if (segment.duration > 0) { - normLinearTime = Math.min((totalLinearTime - segment.delay) / - segment.duration, 1); + normLinearTime = Math.min( + (totalLinearTime - segment.delay) / segment.duration, + 1 + ); normTime = normLinearTime; if (segment.curve && normTime != 1) { try { @@ -363,7 +368,6 @@ class AnimationPlayer { } } - /** * @typedef {{ * delay: ./time.normtimeDef, @@ -374,7 +378,6 @@ class AnimationPlayer { */ let SegmentDef; - /** * @typedef {{ * delay: ./time.normtimeDef, diff --git a/src/async-input.js b/src/async-input.js index dff8364a67e6f..68e5074c77617 100644 --- a/src/async-input.js +++ b/src/async-input.js @@ -30,7 +30,6 @@ * @interface */ export class AsyncInput { - /** * Called to get the asynchronous value of an * AsyncInput field. @@ -55,11 +54,10 @@ export const AsyncInputAttributes = { * Required attribute that must be asserted by every async-input * Element. This is used by AMP form to add the key * for the form submission request - */ + */ NAME: 'name', }; - /** * Classes * @@ -80,4 +78,3 @@ export const AsyncInputClasses = { */ 'ASYNC_INPUT': 'i-amphtml-async-input', }; - diff --git a/src/auto-lightbox.js b/src/auto-lightbox.js index 673b33c4b4c22..38552b2b3e0e3 100644 --- a/src/auto-lightbox.js +++ b/src/auto-lightbox.js @@ -19,7 +19,6 @@ import {Services} from './services'; import {dev} from './log'; import {isExperimentOn} from './experiments'; - /** @const @enum {string} */ export const AutoLightboxEvents = { // Triggered when the lightbox attribute is newly set on an item in order to @@ -27,7 +26,6 @@ export const AutoLightboxEvents = { NEWLY_SET: 'amp-auto-lightbox:newly-set', }; - /** * @param {!./service/ampdoc-impl.AmpDoc} ampdoc */ @@ -36,13 +34,18 @@ export function installAutoLightboxExtension(ampdoc) { if (!isExperimentOn(win, 'amp-auto-lightbox')) { return; } - chunk(ampdoc, () => { - Services.extensionsFor(win) - .installExtensionForDoc(ampdoc, 'amp-auto-lightbox'); - }, ChunkPriority.LOW); + chunk( + ampdoc, + () => { + Services.extensionsFor(win).installExtensionForDoc( + ampdoc, + 'amp-auto-lightbox' + ); + }, + ChunkPriority.LOW + ); } - /** * @param {!Element} element * @return {boolean} @@ -55,16 +58,22 @@ export function isActionableByTap(element) { return true; } const action = Services.actionServiceForDoc(element); - const hasTapAction = action.hasResolvableAction(element, 'tap', - dev().assertElement(element.parentElement)); + const hasTapAction = action.hasResolvableAction( + element, + 'tap', + dev().assertElement(element.parentElement) + ); if (hasTapAction) { return true; } const actionables = element.querySelectorAll('[on]'); for (let i = 0; i < actionables.length; i++) { const actionable = actionables[i]; - const hasTapAction = action.hasResolvableAction(actionable, 'tap', - dev().assertElement(actionable.parentElement)); + const hasTapAction = action.hasResolvableAction( + actionable, + 'tap', + dev().assertElement(actionable.parentElement) + ); if (hasTapAction) { return true; } diff --git a/src/base-element.js b/src/base-element.js index 3ff0f45eec6ef..938000501a103 100644 --- a/src/base-element.js +++ b/src/base-element.js @@ -192,15 +192,15 @@ export class BaseElement { } /** - * This is the priority of loading elements (layoutCallback). Used only to - * determine layout timing and preloading priority. Does not affect build time, - * etc. - * - * The lower the number, the higher the priority. - * - * The default priority for base elements is LayoutPriority.CONTENT. - * @return {number} - */ + * This is the priority of loading elements (layoutCallback). Used only to + * determine layout timing and preloading priority. Does not affect build time, + * etc. + * + * The lower the number, the higher the priority. + * + * The default priority for base elements is LayoutPriority.CONTENT. + * @return {number} + */ getLayoutPriority() { return LayoutPriority.CONTENT; } @@ -217,8 +217,9 @@ export class BaseElement { * @restricted */ updateLayoutPriority(newLayoutPriority) { - this.element.getResources().updateLayoutPriority( - this.element, newLayoutPriority); + this.element + .getResources() + .updateLayoutPriority(this.element, newLayoutPriority); } /** @return {!Layout} */ @@ -289,7 +290,7 @@ export class BaseElement { let policyId = null; if (this.element.hasAttribute('data-block-on-consent')) { policyId = - this.element.getAttribute('data-block-on-consent') || 'default'; + this.element.getAttribute('data-block-on-consent') || 'default'; } return policyId; } @@ -498,24 +499,21 @@ export class BaseElement { * viewport. Intended to be implemented by actual components. * @param {boolean} unusedInViewport */ - viewportCallback(unusedInViewport) { - } + viewportCallback(unusedInViewport) {} /** * Requests the element to stop its activity when the document goes into * inactive state. The scope is up to the actual component. Among other * things the active playback of video or audio content must be stopped. */ - pauseCallback() { - } + pauseCallback() {} /** * Requests the element to resume its activity when the document returns from * an inactive state. The scope is up to the actual component. Among other * things the active playback of video or audio content may be resumed. */ - resumeCallback() { - } + resumeCallback() {} /** * Requests the element to unload any expensive resources when the element @@ -561,8 +559,7 @@ export class BaseElement { * user event. Intended to be implemented by actual components. * @param {!./service/action-impl.ActionInvocation} unusedInvocation */ - activate(unusedInvocation) { - } + activate(unusedInvocation) {} /** * Minimum event trust required for activate(). @@ -615,9 +612,15 @@ export class BaseElement { * @public */ registerDefaultAction( - handler, alias = DEFAULT_ACTION, minTrust = ActionTrust.HIGH) { - devAssert(!this.defaultActionAlias_, - 'Default action "%s" already registered.', this.defaultActionAlias_); + handler, + alias = DEFAULT_ACTION, + minTrust = ActionTrust.HIGH + ) { + devAssert( + !this.defaultActionAlias_, + 'Default action "%s" already registered.', + this.defaultActionAlias_ + ); this.registerAction(alias, handler, minTrust); this.defaultActionAlias_ = alias; } @@ -698,7 +701,8 @@ export class BaseElement { const unlisteners = (isArray(events) ? events : [events]).map(eventType => listen(element, eventType, event => { this.element.dispatchCustomEvent(eventType, getData(event) || {}); - })); + }) + ); return () => unlisteners.forEach(unlisten => unlisten()); } @@ -878,7 +882,7 @@ export class BaseElement { * @public */ scheduleUnlayout(elements) { - this.element.getResources()./*OK*/scheduleUnlayout(this.element, elements); + this.element.getResources()./*OK*/ scheduleUnlayout(this.element, elements); } /** @@ -890,8 +894,9 @@ export class BaseElement { * @public */ updateInViewport(elements, inLocalViewport) { - this.element.getResources().updateInViewport( - this.element, elements, inLocalViewport); + this.element + .getResources() + .updateInViewport(this.element, elements, inLocalViewport); } /** @@ -902,8 +907,9 @@ export class BaseElement { * @public */ changeHeight(newHeight) { - this.element.getResources()./*OK*/changeSize( - this.element, newHeight, /* newWidth */ undefined); + this.element + .getResources() + ./*OK*/ changeSize(this.element, newHeight, /* newWidth */ undefined); } /** @@ -923,7 +929,6 @@ export class BaseElement { return this.element.getResources().attemptCollapse(this.element); } - /** * Return a promise that requests the runtime to update * the height of this element to the specified value. @@ -939,28 +944,30 @@ export class BaseElement { * @public */ attemptChangeHeight(newHeight) { - return this.element.getResources().attemptChangeSize( - this.element, newHeight, /* newWidth */ undefined); - } - - /** - * Return a promise that requests the runtime to update - * the size of this element to the specified value. - * The runtime will schedule this request and attempt to process it - * as soon as possible. However, unlike in {@link changeSize}, the runtime - * may refuse to make a change in which case it will show the element's - * overflow element if provided, which is supposed to provide the reader with - * the necessary user action. (The overflow element is shown only if the - * requested height is greater than 0.) - * The promise is resolved if the height is successfully updated. - * @param {number|undefined} newHeight - * @param {number|undefined} newWidth - * @return {!Promise} - * @public - */ + return this.element + .getResources() + .attemptChangeSize(this.element, newHeight, /* newWidth */ undefined); + } + + /** + * Return a promise that requests the runtime to update + * the size of this element to the specified value. + * The runtime will schedule this request and attempt to process it + * as soon as possible. However, unlike in {@link changeSize}, the runtime + * may refuse to make a change in which case it will show the element's + * overflow element if provided, which is supposed to provide the reader with + * the necessary user action. (The overflow element is shown only if the + * requested height is greater than 0.) + * The promise is resolved if the height is successfully updated. + * @param {number|undefined} newHeight + * @param {number|undefined} newWidth + * @return {!Promise} + * @public + */ attemptChangeSize(newHeight, newWidth) { - return this.element.getResources().attemptChangeSize( - this.element, newHeight, newWidth); + return this.element + .getResources() + .attemptChangeSize(this.element, newHeight, newWidth); } /** @@ -1009,8 +1016,9 @@ export class BaseElement { * @return {!Promise} */ measureMutateElement(measurer, mutator, opt_element) { - return this.element.getResources().measureMutateElement( - opt_element || this.element, measurer, mutator); + return this.element + .getResources() + .measureMutateElement(opt_element || this.element, measurer, mutator); } /** @@ -1082,12 +1090,13 @@ export class BaseElement { * @param {!Element=} opt_element */ declareLayer(opt_element) { - devAssert(isExperimentOn(this.win, 'layers'), 'Layers must be enabled' + - ' to declare layer.'); + devAssert( + isExperimentOn(this.win, 'layers'), + 'Layers must be enabled' + ' to declare layer.' + ); if (opt_element) { devAssert(this.element.contains(opt_element)); } return this.element.getLayers().declareLayer(opt_element || this.element); } - } diff --git a/src/batched-json.js b/src/batched-json.js index e94d1400887d5..354cb8aba753a 100644 --- a/src/batched-json.js +++ b/src/batched-json.js @@ -50,30 +50,30 @@ export function batchFetchJsonFor( opt_expr = '.', opt_urlReplacement = UrlReplacementPolicy.NONE, opt_refresh = false, - opt_token = undefined) -{ + opt_token = undefined +) { assertHttpsUrl(element.getAttribute('src'), element); const xhr = Services.batchedXhrFor(ampdoc.win); return requestForBatchFetch(element, opt_urlReplacement, opt_refresh) - .then(data => { - if (opt_token !== undefined) { - data.fetchOpt['method'] = 'POST'; - data.fetchOpt['headers'] = { - 'Content-Type': 'application/x-www-form-urlencoded', - }; - data.fetchOpt['body'] = { - 'ampViewerAuthToken': opt_token, - }; - } - return xhr.fetchJson(data.xhrUrl, data.fetchOpt); - }) - .then(res => res.json()) - .then(data => { - if (data == null) { - throw new Error('Response is undefined.'); - } - return getValueForExpr(data, opt_expr || '.'); - }); + .then(data => { + if (opt_token !== undefined) { + data.fetchOpt['method'] = 'POST'; + data.fetchOpt['headers'] = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + data.fetchOpt['body'] = { + 'ampViewerAuthToken': opt_token, + }; + } + return xhr.fetchJson(data.xhrUrl, data.fetchOpt); + }) + .then(res => res.json()) + .then(data => { + if (data == null) { + throw new Error('Response is undefined.'); + } + return getValueForExpr(data, opt_expr || '.'); + }); } /** @@ -90,9 +90,10 @@ export function requestForBatchFetch(element, replacement, refresh) { // Replace vars in URL if desired. const urlReplacements = Services.urlReplacementsForDoc(element); - const promise = (replacement >= UrlReplacementPolicy.OPT_IN) - ? urlReplacements.expandUrlAsync(url) - : Promise.resolve(url); + const promise = + replacement >= UrlReplacementPolicy.OPT_IN + ? urlReplacements.expandUrlAsync(url) + : Promise.resolve(url); return promise.then(xhrUrl => { // Throw user error if this element is performing URL substitutions @@ -100,10 +101,12 @@ export function requestForBatchFetch(element, replacement, refresh) { if (replacement == UrlReplacementPolicy.OPT_IN) { const invalid = urlReplacements.collectUnwhitelistedVarsSync(element); if (invalid.length > 0) { - throw user().createError('URL variable substitutions in CORS ' + + throw user().createError( + 'URL variable substitutions in CORS ' + 'fetches from dynamic URLs (e.g. via amp-bind) require opt-in. ' + `Please add data-amp-replace="${invalid.join(' ')}" to the ` + - `<${element.tagName}> element. See https://bit.ly/amp-var-subs.`); + `<${element.tagName}> element. See https://bit.ly/amp-var-subs.` + ); } } const fetchOpt = {}; diff --git a/src/chunk.js b/src/chunk.js index fac45c6528cb5..56ce67d7a2ef4 100644 --- a/src/chunk.js +++ b/src/chunk.js @@ -17,10 +17,7 @@ import {Services} from './services'; import {dev} from './log'; import {getData} from './event-helper'; -import { - getServiceForDoc, - registerServiceBuilderForDoc, -} from './service'; +import {getServiceForDoc, registerServiceBuilderForDoc} from './service'; import {makeBodyVisibleRecovery} from './style-installer'; import PriorityQueue from './utils/priority-queue'; @@ -298,8 +295,7 @@ class StartupTask extends Task { return false; } // Viewers send a URL param if we are not visible. - return !(/visibilityState=(hidden|prerender)/.test( - this.win_.location.hash)); + return !/visibilityState=(hidden|prerender)/.test(this.win_.location.hash); } } @@ -429,20 +425,22 @@ class Chunks { // If requestIdleCallback exists, schedule a task with it, but // do not wait longer than two seconds. if (nextTask.useRequestIdleCallback_() && this.win_.requestIdleCallback) { - onIdle(this.win_, - // Wait until we have a budget of at least 15ms. - // 15ms is a magic number. Budgets are higher when the user - // is completely idle (around 40), but that occurs too - // rarely to be usable. 15ms budgets can happen during scrolling - // but only if the device is doing super, super well, and no - // real processing is done between frames. - 15 /* minimumTimeRemaining */, - 2000 /* timeout */, - this.boundExecute_); + onIdle( + this.win_, + // Wait until we have a budget of at least 15ms. + // 15ms is a magic number. Budgets are higher when the user + // is completely idle (around 40), but that occurs too + // rarely to be usable. 15ms budgets can happen during scrolling + // but only if the device is doing super, super well, and no + // real processing is done between frames. + 15 /* minimumTimeRemaining */, + 2000 /* timeout */, + this.boundExecute_ + ); return; } // The message doesn't actually matter. - this.win_.postMessage/*OK*/('amp-macro-task', '*'); + this.win_.postMessage(/*OK*/ 'amp-macro-task', '*'); } } @@ -468,8 +466,12 @@ export function onIdle(win, minimumTimeRemaining, timeout, fn) { dev().fine(TAG, 'Timed out', timeout, info.didTimeout); fn(info); } else { - dev().fine(TAG, 'Rescheduling with', remainingTimeout, - info.timeRemaining()); + dev().fine( + TAG, + 'Rescheduling with', + remainingTimeout, + info.timeRemaining() + ); win.requestIdleCallback(rIC, {timeout: remainingTimeout}); } } else { diff --git a/src/clipboard.js b/src/clipboard.js index 50db4a8d17e2d..ae42ac759805e 100644 --- a/src/clipboard.js +++ b/src/clipboard.js @@ -16,7 +16,6 @@ import {removeElement} from './dom'; import {setStyles} from './style'; - /** * @param {!Window} win * @param {string} text @@ -62,7 +61,6 @@ export function copyTextToClipboard(win, text) { return copySuccessful; } - /** * @param {!Document} doc * @return {boolean} diff --git a/src/common-signals.js b/src/common-signals.js index 730ccebb24b3c..94b0643d1d8de 100644 --- a/src/common-signals.js +++ b/src/common-signals.js @@ -14,13 +14,11 @@ * limitations under the License. */ - /** * Commonly used signals across different elements and documents. * @enum {string} */ export const CommonSignals = { - /** * The element has been built. */ diff --git a/src/config.js b/src/config.js index 1a9101785bf71..94c63d0550763 100644 --- a/src/config.js +++ b/src/config.js @@ -23,11 +23,15 @@ */ const env = self.AMP_CONFIG || {}; -const thirdPartyFrameRegex = typeof env['thirdPartyFrameRegex'] == 'string' ? - new RegExp(env['thirdPartyFrameRegex']) : env['thirdPartyFrameRegex']; +const thirdPartyFrameRegex = + typeof env['thirdPartyFrameRegex'] == 'string' + ? new RegExp(env['thirdPartyFrameRegex']) + : env['thirdPartyFrameRegex']; -const cdnProxyRegex = typeof env['cdnProxyRegex'] == 'string' ? - new RegExp(env['cdnProxyRegex']) : env['cdnProxyRegex']; +const cdnProxyRegex = + typeof env['cdnProxyRegex'] == 'string' + ? new RegExp(env['cdnProxyRegex']) + : env['cdnProxyRegex']; /** @type {!Object} */ export const urls = { @@ -38,11 +42,11 @@ export const urls = { /* Note that cdnProxyRegex is only ever checked against origins * (proto://host[:port]) so does not need to consider path */ - cdnProxyRegex: cdnProxyRegex || - /^https:\/\/([a-zA-Z0-9_-]+\.)?cdn\.ampproject\.org$/, + cdnProxyRegex: + cdnProxyRegex || /^https:\/\/([a-zA-Z0-9_-]+\.)?cdn\.ampproject\.org$/, localhostRegex: /^https?:\/\/localhost(:\d+)?$/, - errorReporting: env['errorReportingUrl'] || - 'https://amp-error-reporting.appspot.com/r', + errorReporting: + env['errorReportingUrl'] || 'https://amp-error-reporting.appspot.com/r', localDev: env['localDev'] || false, }; diff --git a/src/consent.js b/src/consent.js index 8118c59917191..9e8ec68b2e40e 100644 --- a/src/consent.js +++ b/src/consent.js @@ -28,14 +28,14 @@ import {user} from './log'; * @return {!Promise} */ export function getConsentPolicyState(element, policyId = 'default') { - return Services.consentPolicyServiceForDocOrNull(element) - .then(consentPolicy => { - if (!consentPolicy) { - return null; - } - return consentPolicy.whenPolicyResolved( - /** @type {string} */ (policyId)); - }); + return Services.consentPolicyServiceForDocOrNull(element).then( + consentPolicy => { + if (!consentPolicy) { + return null; + } + return consentPolicy.whenPolicyResolved(/** @type {string} */ (policyId)); + } + ); } /** @@ -46,14 +46,16 @@ export function getConsentPolicyState(element, policyId = 'default') { * @return {!Promise} */ export function getConsentPolicySharedData(element, policyId) { - return Services.consentPolicyServiceForDocOrNull(element) - .then(consentPolicy => { - if (!consentPolicy) { - return null; - } - return consentPolicy.getMergedSharedData( - /** @type {string} */ (policyId)); - }); + return Services.consentPolicyServiceForDocOrNull(element).then( + consentPolicy => { + if (!consentPolicy) { + return null; + } + return consentPolicy.getMergedSharedData( + /** @type {string} */ (policyId) + ); + } + ); } /** @@ -65,14 +67,16 @@ export function getConsentPolicySharedData(element, policyId) { */ export function getConsentPolicyInfo(element, policyId) { // Return the stored consent string. - return Services.consentPolicyServiceForDocOrNull(element) - .then(consentPolicy => { - if (!consentPolicy) { - return null; - } - return consentPolicy.getConsentStringInfo( - /** @type {string} */ (policyId)); - }); + return Services.consentPolicyServiceForDocOrNull(element).then( + consentPolicy => { + if (!consentPolicy) { + return null; + } + return consentPolicy.getConsentStringInfo( + /** @type {string} */ (policyId) + ); + } + ); } /** @@ -82,8 +86,9 @@ export function getConsentPolicyInfo(element, policyId) { */ export function shouldBlockOnConsentByMeta(element) { const ampdoc = element.getAmpDoc(); - let content = - Services.documentInfoForDoc(ampdoc).metaTags['amp-consent-blocking']; + let content = Services.documentInfoForDoc(ampdoc).metaTags[ + 'amp-consent-blocking' + ]; if (!content) { return false; @@ -92,13 +97,18 @@ export function shouldBlockOnConsentByMeta(element) { // validator enforce uniqueness of // content will not be an array. if (typeof content !== 'string') { - user().error('CONSENT', - 'Invalid amp-consent-blocking value, ignore meta tag'); + user().error( + 'CONSENT', + 'Invalid amp-consent-blocking value, ignore meta tag' + ); return false; } // Handles whitespace - content = content.toUpperCase().replace(/\s/g, '').split(','); + content = content + .toUpperCase() + .replace(/\s/g, '') + .split(','); if (content.includes(element.tagName)) { return true; diff --git a/src/cookies.js b/src/cookies.js index 33fdc62cff87f..634bbb0fc59a8 100644 --- a/src/cookies.js +++ b/src/cookies.js @@ -15,14 +15,9 @@ */ import {endsWith} from './string'; -import { - isProxyOrigin, - parseUrlDeprecated, - tryDecodeUriComponent, -} from './url'; +import {isProxyOrigin, parseUrlDeprecated, tryDecodeUriComponent} from './url'; import {urls} from './config'; - /** * Returns the value of the cookie. The cookie access is restricted and must * go through the privacy review. Before using this method please file a @@ -128,11 +123,14 @@ function trySetCookie(win, name, value, expirationTime, domain) { value = 'delete'; expirationTime = 0; } - const cookie = encodeURIComponent(name) + '=' + - encodeURIComponent(value) + - '; path=/' + - (domain ? '; domain=' + domain : '') + - '; expires=' + new Date(expirationTime).toUTCString(); + const cookie = + encodeURIComponent(name) + + '=' + + encodeURIComponent(value) + + '; path=/' + + (domain ? '; domain=' + domain : '') + + '; expires=' + + new Date(expirationTime).toUTCString(); try { win.document.cookie = cookie; } catch (ignore) { @@ -155,14 +153,18 @@ function checkOriginForSettingCookie(win, options, name) { return; } if (isProxyOrigin(win.location.href)) { - throw new Error('Should never attempt to set cookie on proxy origin: ' - + name); + throw new Error( + 'Should never attempt to set cookie on proxy origin: ' + name + ); } const current = parseUrlDeprecated(win.location.href).hostname.toLowerCase(); const proxy = parseUrlDeprecated(urls.cdn).hostname.toLowerCase(); if (current == proxy || endsWith(current, '.' + proxy)) { - throw new Error('Should never attempt to set cookie on proxy origin.' - + ' (in depth check): ' + name); + throw new Error( + 'Should never attempt to set cookie on proxy origin.' + + ' (in depth check): ' + + name + ); } } diff --git a/src/css.js b/src/css.js index 7a781da6a0c7b..8c6a334f2592e 100644 --- a/src/css.js +++ b/src/css.js @@ -27,7 +27,6 @@ export function assertIsName(name) { devAssert(/^[\w-]+$/.test(name)); } - /** * @type {boolean|undefined} */ @@ -67,7 +66,7 @@ function testScopeSelector(el) { testElement.appendChild(testChild); // NOTE(cvializ, #12383): Firefox's implementation is incomplete, // therefore we test actual functionality of`:scope` as well. - return testElement./*OK*/querySelector(':scope div') === testChild; + return testElement./*OK*/ querySelector(':scope div') === testChild; } catch (e) { return false; } @@ -121,4 +120,3 @@ export function escapeCssSelectorNth(ident) { devAssert(escaped.indexOf(')') === -1); return escaped; } - diff --git a/src/curve.js b/src/curve.js index 35efaa36c314d..95b86ecd6421f 100644 --- a/src/curve.js +++ b/src/curve.js @@ -18,7 +18,6 @@ // the type system during compile time. import './time'; - /** * A CurveDef is a function that returns a normtime value (0 to 1) for another * normtime value. @@ -26,7 +25,6 @@ import './time'; */ export let CurveDef; - /** * Returns a cubic bezier curve. * @param {number} x1 X coordinate of the first control point. @@ -40,13 +38,11 @@ export function bezierCurve(x1, y1, x2, y2) { return bezier.solveYValueFromXValue.bind(bezier); } - /** * Thanks to * https://closure-library.googlecode.com/git-history/docs/local_closure_goog_math_bezier.js.source.html */ class Bezier { - /** * @param {number} x0 X coordinate of the start point. * @param {number} y0 Y coordinate of the start point. @@ -239,7 +235,6 @@ class Bezier { } } - /** * A collection of common curves. * See https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function @@ -251,7 +246,9 @@ export const Curves = { * @param {number} n * @return {number} */ - LINEAR(n) {return n;}, + LINEAR(n) { + return n; + }, /** * ease @@ -274,7 +271,6 @@ export const Curves = { EASE_IN_OUT: bezierCurve(0.42, 0.0, 0.58, 1.0), }; - /** * @const {!Object} */ @@ -286,7 +282,6 @@ const NAME_MAP = { 'ease-in-out': Curves.EASE_IN_OUT, }; - /** * If the argument is a string, this methods matches an existing curve by name. * @param {?CurveDef|string|undefined} curve diff --git a/src/custom-element.js b/src/custom-element.js index 637ba567d1371..544ecbdeea91a 100644 --- a/src/custom-element.js +++ b/src/custom-element.js @@ -32,9 +32,7 @@ import {Signals} from './utils/signals'; import {blockedByConsentError, isBlockedByConsent, reportError} from './error'; import {createLoaderElement} from '../src/loader'; import {dev, devAssert, rethrowAsync, user} from './log'; -import { - getIntersectionChangeEntry, -} from '../src/intersection-observer-polyfill'; +import {getIntersectionChangeEntry} from '../src/intersection-observer-polyfill'; import {getMode} from './mode'; import {htmlFor} from './static-template'; import {isExperimentOn} from './experiments'; @@ -55,7 +53,6 @@ const TAG = 'CustomElement'; */ const MIN_WIDTH_FOR_LOADING = 100; - /** * The elements positioned ahead of this threshold may have their loading * indicator initialized faster. This is benefitial to avoid relayout during @@ -64,7 +61,6 @@ const MIN_WIDTH_FOR_LOADING = 100; */ const PREPARE_LOADING_THRESHOLD = 1000; - /** * @enum {number} */ @@ -75,7 +71,6 @@ const UpgradeState = { UPGRADE_IN_PROGRESS: 4, }; - /** * Caches whether the template tag is supported to avoid memory allocations. * @type {boolean|undefined} @@ -94,7 +89,6 @@ function isTemplateTagSupported() { return templateTagSupported; } - /** * Creates a named custom element class. * @@ -103,8 +97,9 @@ function isTemplateTagSupported() { * @return {!Function} The custom element class. */ export function createCustomElementClass(win, name) { - const baseCustomElement = /** @type {Function} */ ( - createBaseCustomElementClass(win)); + const baseCustomElement = /** @type {Function} */ (createBaseCustomElementClass( + win + )); /** * @extends {HTMLElement} * @suppress {checkTypes} @@ -129,7 +124,6 @@ export function createCustomElementClass(win, name) { return CustomAmpElement; } - /** * Creates a base custom element class. * @@ -257,8 +251,8 @@ function createBaseCustomElementClass(win) { this.overflowElement_ = undefined; // `opt_implementationClass` is only used for tests. - let Ctor = win.ampExtendedElements && - win.ampExtendedElements[this.elementName()]; + let Ctor = + win.ampExtendedElements && win.ampExtendedElements[this.elementName()]; if (getMode().test && this.implementationClassForTesting) { Ctor = this.implementationClassForTesting; } @@ -319,8 +313,7 @@ function createBaseCustomElementClass(win) { * @abstract * @return {string} */ - elementName() { - } + elementName() {} /** @return {!Signals} */ signals() { @@ -336,8 +329,7 @@ function createBaseCustomElementClass(win) { */ getAmpDoc() { devAssert(this.ampdoc_, 'no ampdoc yet, since element is not attached'); - return /** @typedef {!./service/ampdoc-impl.AmpDoc} */ ( - this.ampdoc_); + return /** @typedef {!./service/ampdoc-impl.AmpDoc} */ this.ampdoc_; } /** @@ -349,9 +341,11 @@ function createBaseCustomElementClass(win) { */ getResources() { devAssert( - this.resources_, 'no resources yet, since element is not attached'); - return /** @typedef {!./service/resources-impl.Resources} */ ( - this.resources_); + this.resources_, + 'no resources yet, since element is not attached' + ); + return /** @typedef {!./service/resources-impl.Resources} */ this + .resources_; } /** @@ -363,8 +357,7 @@ function createBaseCustomElementClass(win) { */ getLayers() { devAssert(this.layers_, 'no layers yet, since element is not attached'); - return /** @typedef {!./service/layers-impl.LayoutLayers} */ ( - this.layers_); + return /** @typedef {!./service/layers-impl.LayoutLayers} */ this.layers_; } /** @@ -436,14 +429,17 @@ function createBaseCustomElementClass(win) { /** @private */ assertLayout_() { - if (this.layout_ != Layout.NODISPLAY && - !this.implementation_.isLayoutSupported(this.layout_)) { + if ( + this.layout_ != Layout.NODISPLAY && + !this.implementation_.isLayoutSupported(this.layout_) + ) { let error = 'Layout not supported: ' + this.layout_; if (!this.getAttribute('layout')) { - error += '. The element did not specify a layout attribute. ' + - 'Check https://www.ampproject.org/docs/guides/' + - 'responsive/control_layout and the respective element ' + - 'documentation for details.'; + error += + '. The element did not specify a layout attribute. ' + + 'Check https://www.ampproject.org/docs/guides/' + + 'responsive/control_layout and the respective element ' + + 'documentation for details.'; } throw user().createError(error); } @@ -473,8 +469,7 @@ function createBaseCustomElementClass(win) { * @return {number} @this {!Element} */ getLayoutPriority() { - devAssert( - this.isUpgraded(), 'Cannot get priority of unupgraded element'); + devAssert(this.isUpgraded(), 'Cannot get priority of unupgraded element'); return this.implementation_.getLayoutPriority(); } @@ -484,10 +479,10 @@ function createBaseCustomElementClass(win) { */ getDefaultActionAlias() { devAssert( - this.isUpgraded(), - 'Cannot get default action alias of unupgraded element'); + this.isUpgraded(), + 'Cannot get default action alias of unupgraded element' + ); return this.implementation_.getDefaultActionAlias(); - } /** @@ -505,53 +500,62 @@ function createBaseCustomElementClass(win) { if (this.buildingPromise_) { return this.buildingPromise_; } - return this.buildingPromise_ = new Promise((resolve, reject) => { + return (this.buildingPromise_ = new Promise((resolve, reject) => { const policyId = this.getConsentPolicy_(); if (!policyId) { resolve(this.implementation_.buildCallback()); } else { - Services.consentPolicyServiceForDocOrNull(this).then(policy => { - if (!policy) { - return true; - } - return policy.whenPolicyUnblock(/** @type {string} */ (policyId)); - }).then(shouldUnblock => { - if (shouldUnblock) { - resolve(this.implementation_.buildCallback()); - } else { - reject(blockedByConsentError()); - } - }); - } - }).then(() => { - this.preconnect(/* onLayout */false); - this.built_ = true; - this.classList.remove('i-amphtml-notbuilt'); - this.classList.remove('amp-notbuilt'); - this.signals_.signal(CommonSignals.BUILT); - if (this.isInViewport_) { - this.updateInViewport_(true); - } - if (this.actionQueue_) { - // Only schedule when the queue is not empty, which should be - // the case 99% of the time. - Services.timerFor(toWin(this.ownerDocument.defaultView)) - .delay(this.dequeueActions_.bind(this), 1); + Services.consentPolicyServiceForDocOrNull(this) + .then(policy => { + if (!policy) { + return true; + } + return policy.whenPolicyUnblock(/** @type {string} */ (policyId)); + }) + .then(shouldUnblock => { + if (shouldUnblock) { + resolve(this.implementation_.buildCallback()); + } else { + reject(blockedByConsentError()); + } + }); } - if (!this.getPlaceholder()) { - const placeholder = this.createPlaceholder(); - if (placeholder) { - this.appendChild(placeholder); + }).then( + () => { + this.preconnect(/* onLayout */ false); + this.built_ = true; + this.classList.remove('i-amphtml-notbuilt'); + this.classList.remove('amp-notbuilt'); + this.signals_.signal(CommonSignals.BUILT); + if (this.isInViewport_) { + this.updateInViewport_(true); } + if (this.actionQueue_) { + // Only schedule when the queue is not empty, which should be + // the case 99% of the time. + Services.timerFor(toWin(this.ownerDocument.defaultView)).delay( + this.dequeueActions_.bind(this), + 1 + ); + } + if (!this.getPlaceholder()) { + const placeholder = this.createPlaceholder(); + if (placeholder) { + this.appendChild(placeholder); + } + } + }, + reason => { + this.signals_.rejectSignal( + CommonSignals.BUILT, + /** @type {!Error} */ (reason) + ); + if (!isBlockedByConsent(reason)) { + reportError(reason, this); + } + throw reason; } - }, reason => { - this.signals_.rejectSignal(CommonSignals.BUILT, - /** @type {!Error} */ (reason)); - if (!isBlockedByConsent(reason)) { - reportError(reason, this); - } - throw reason; - }); + )); } /** @@ -617,8 +621,10 @@ function createBaseCustomElementClass(win) { if (this.isInViewport_) { // Already in viewport - start showing loading. this.toggleLoading(true); - } else if (layoutBox.top < PREPARE_LOADING_THRESHOLD && - layoutBox.top >= 0) { + } else if ( + layoutBox.top < PREPARE_LOADING_THRESHOLD && + layoutBox.top >= 0 + ) { // Few top elements will also be pre-initialized with a loading // element. this.mutateOrInvoke_(() => this.prepareLoading_()); @@ -631,8 +637,10 @@ function createBaseCustomElementClass(win) { * @private */ getSizer_() { - if (this.sizerElement === undefined && - this.layout_ === Layout.RESPONSIVE) { + if ( + this.sizerElement === undefined && + this.layout_ === Layout.RESPONSIVE + ) { // Expect sizer to exist, just not yet discovered. this.sizerElement = this.querySelector('i-amphtml-sizer'); } @@ -661,8 +669,10 @@ function createBaseCustomElementClass(win) { } if (this.mediaQuery_) { const {defaultView} = this.ownerDocument; - this.classList.toggle('i-amphtml-hidden-by-media-query', - !defaultView.matchMedia(this.mediaQuery_).matches); + this.classList.toggle( + 'i-amphtml-hidden-by-media-query', + !defaultView.matchMedia(this.mediaQuery_).matches + ); } // Sizes. @@ -671,21 +681,30 @@ function createBaseCustomElementClass(win) { this.sizeList_ = sizesAttr ? parseSizeList(sizesAttr) : null; } if (this.sizeList_) { - setStyle(this, 'width', this.sizeList_.select( - toWin(this.ownerDocument.defaultView))); + setStyle( + this, + 'width', + this.sizeList_.select(toWin(this.ownerDocument.defaultView)) + ); } // Heights. - if (this.heightsList_ === undefined && - this.layout_ === Layout.RESPONSIVE) { + if ( + this.heightsList_ === undefined && + this.layout_ === Layout.RESPONSIVE + ) { const heightsAttr = this.getAttribute('heights'); - this.heightsList_ = heightsAttr ? - parseSizeList(heightsAttr, /* allowPercent */ true) : null; + this.heightsList_ = heightsAttr + ? parseSizeList(heightsAttr, /* allowPercent */ true) + : null; } if (this.heightsList_) { const sizer = this.getSizer_(); if (sizer) { - setStyle(sizer, 'paddingTop', - this.heightsList_.select(toWin(this.ownerDocument.defaultView))); + setStyle( + sizer, + 'paddingTop', + this.heightsList_.select(toWin(this.ownerDocument.defaultView)) + ); } } } @@ -755,8 +774,10 @@ function createBaseCustomElementClass(win) { */ connectedCallback() { if (!isTemplateTagSupported() && this.isInTemplate_ === undefined) { - this.isInTemplate_ = - !!dom.closestAncestorElementBySelector(this, 'template'); + this.isInTemplate_ = !!dom.closestAncestorElementBySelector( + this, + 'template' + ); } if (this.isInTemplate_) { return; @@ -781,10 +802,14 @@ function createBaseCustomElementClass(win) { this.ampdoc_ = ampdoc; // Load the pre-stubbed extension if needed. const extensionId = this.tagName.toLowerCase(); - if (isStub(this.implementation_) && - !ampdoc.declaresExtension(extensionId)) { + if ( + isStub(this.implementation_) && + !ampdoc.declaresExtension(extensionId) + ) { Services.extensionsFor(win).installExtensionForDoc( - ampdoc, extensionId); + ampdoc, + extensionId + ); } } if (!this.resources_) { @@ -875,16 +900,20 @@ function createBaseCustomElementClass(win) { this.completeUpgrade_(impl, startTime); } else if (typeof res.then == 'function') { // It's a promise: wait until it's done. - res.then(upgrade => { - this.completeUpgrade_(upgrade || impl, startTime); - }).catch(reason => { - this.upgradeState_ = UpgradeState.UPGRADE_FAILED; - rethrowAsync(reason); - }); + res + .then(upgrade => { + this.completeUpgrade_(upgrade || impl, startTime); + }) + .catch(reason => { + this.upgradeState_ = UpgradeState.UPGRADE_FAILED; + rethrowAsync(reason); + }); } else { // It's an actual instance: upgrade immediately. this.completeUpgrade_( - /** @type {!./base-element.BaseElement} */(res), startTime); + /** @type {!./base-element.BaseElement} */ (res), + startTime + ); } } @@ -1014,7 +1043,9 @@ function createBaseCustomElementClass(win) { * @final @this {!Element} */ getLayoutBox() { - return this.getResources().getResourceForElement(this).getLayoutBox(); + return this.getResources() + .getResourceForElement(this) + .getLayoutBox(); } /** @@ -1024,7 +1055,9 @@ function createBaseCustomElementClass(win) { * @final @this {!Element} */ getPageLayoutBox() { - return this.getResources().getResourceForElement(this).getPageLayoutBox(); + return this.getResources() + .getResourceForElement(this) + .getPageLayoutBox(); } /** @@ -1032,7 +1065,9 @@ function createBaseCustomElementClass(win) { * @final @this {!Element} */ getOwner() { - return this.getResources().getResourceForElement(this).getOwner(); + return this.getResources() + .getResourceForElement(this) + .getOwner(); } /** @@ -1043,7 +1078,9 @@ function createBaseCustomElementClass(win) { */ getIntersectionChangeEntry() { const box = this.implementation_.getIntersectionElementLayoutBox(); - const owner = this.getResources().getResourceForElement(this).getOwner(); + const owner = this.getResources() + .getResourceForElement(this) + .getOwner(); const viewportBox = this.implementation_.getViewport().getRect(); // TODO(jridgewell, #4826): We may need to make this recursive. const ownerBox = owner && owner.getLayoutBox(); @@ -1055,7 +1092,9 @@ function createBaseCustomElementClass(win) { * @return {number} */ getResourceId() { - return this.getResources().getResourceForElement(this).getId(); + return this.getResources() + .getResourceForElement(this) + .getId(); } /** @@ -1063,7 +1102,9 @@ function createBaseCustomElementClass(win) { * @return {!ResourceState} */ getResourceState_() { - return this.getResources().getResourceForElement(this).getState(); + return this.getResources() + .getResourceForElement(this) + .getState(); } /** @@ -1108,10 +1149,9 @@ function createBaseCustomElementClass(win) { */ layoutCallback() { assertNotTemplate(this); - devAssert(this.isBuilt(), - 'Must be built to receive viewport events'); + devAssert(this.isBuilt(), 'Must be built to receive viewport events'); this.dispatchCustomEventForTesting(AmpEvents.LOAD_START); - const isLoadEvent = (this.layoutCount_ == 0); // First layout is "load". + const isLoadEvent = this.layoutCount_ == 0; // First layout is "load". this.signals_.reset(CommonSignals.UNLOAD); if (isLoadEvent) { this.signals_.signal(CommonSignals.LOAD_START); @@ -1121,33 +1161,38 @@ function createBaseCustomElementClass(win) { } const promise = tryResolve(() => this.implementation_.layoutCallback()); - this.preconnect(/* onLayout */true); + this.preconnect(/* onLayout */ true); this.classList.add('i-amphtml-layout'); - return promise.then(() => { - if (isLoadEvent) { - this.signals_.signal(CommonSignals.LOAD_END); - } - this.readyState = 'complete'; - this.layoutCount_++; - this.toggleLoading(false, {cleanup: true}); - // Check if this is the first success layout that needs - // to call firstLayoutCompleted. - if (!this.isFirstLayoutCompleted_) { - this.implementation_.firstLayoutCompleted(); - this.isFirstLayoutCompleted_ = true; - this.dispatchCustomEventForTesting(AmpEvents.LOAD_END); - } - }, reason => { - // add layoutCount_ by 1 despite load fails or not - if (isLoadEvent) { - this.signals_.rejectSignal( - CommonSignals.LOAD_END, /** @type {!Error} */ (reason)); + return promise.then( + () => { + if (isLoadEvent) { + this.signals_.signal(CommonSignals.LOAD_END); + } + this.readyState = 'complete'; + this.layoutCount_++; + this.toggleLoading(false, {cleanup: true}); + // Check if this is the first success layout that needs + // to call firstLayoutCompleted. + if (!this.isFirstLayoutCompleted_) { + this.implementation_.firstLayoutCompleted(); + this.isFirstLayoutCompleted_ = true; + this.dispatchCustomEventForTesting(AmpEvents.LOAD_END); + } + }, + reason => { + // add layoutCount_ by 1 despite load fails or not + if (isLoadEvent) { + this.signals_.rejectSignal( + CommonSignals.LOAD_END, + /** @type {!Error} */ (reason) + ); + } + this.layoutCount_++; + this.toggleLoading(false, {cleanup: true}); + throw reason; } - this.layoutCount_++; - this.toggleLoading(false, {cleanup: true}); - throw reason; - }); + ); } /** @@ -1175,8 +1220,7 @@ function createBaseCustomElementClass(win) { } // TODO(dvoytenko, #9177): investigate/cleanup viewport signals for // elements in dead iframes. - if (!this.ownerDocument || - !this.ownerDocument.defaultView) { + if (!this.ownerDocument || !this.ownerDocument.defaultView) { return; } this.isInViewport_ = inViewport; @@ -1190,9 +1234,11 @@ function createBaseCustomElementClass(win) { // TODO(dvoytenko, #9177): cleanup `this.ownerDocument.defaultView` // once investigation is complete. It appears that we get a lot of // errors here once the iframe is destroyed due to timer. - if (this.isInViewport_ && - this.ownerDocument && - this.ownerDocument.defaultView) { + if ( + this.isInViewport_ && + this.ownerDocument && + this.ownerDocument.defaultView + ) { this.toggleLoading(true); } }, 100); @@ -1323,7 +1369,7 @@ function createBaseCustomElementClass(win) { * element is no longer present. */ collapse() { - this.implementation_./*OK*/collapse(); + this.implementation_./*OK*/ collapse(); } /** @@ -1339,7 +1385,7 @@ function createBaseCustomElementClass(win) { * element is now present. */ expand() { - this.implementation_./*OK*/expand(); + this.implementation_./*OK*/ expand(); } /** @@ -1411,8 +1457,12 @@ function createBaseCustomElementClass(win) { try { this.implementation_.executeAction(invocation, deferred); } catch (e) { - rethrowAsync('Action execution failed:', e, - invocation.node.tagName, invocation.method); + rethrowAsync( + 'Action execution failed:', + e, + invocation.node.tagName, + invocation.method + ); } } @@ -1458,8 +1508,10 @@ function createBaseCustomElementClass(win) { * @package @final @this {!Element} */ getRealChildren() { - return dom.childElements(this, element => - !isInternalOrServiceNode(element)); + return dom.childElements( + this, + element => !isInternalOrServiceNode(element) + ); } /** @@ -1469,11 +1521,13 @@ function createBaseCustomElementClass(win) { */ getPlaceholder() { return dom.lastChildElement(this, el => { - return el.hasAttribute('placeholder') && + return ( + el.hasAttribute('placeholder') && // Blacklist elements that has a native placeholder property // like input and textarea. These are not allowed to be AMP // placeholders. - !isInputPlaceholder(el); + !isInputPlaceholder(el) + ); }); } @@ -1487,7 +1541,9 @@ function createBaseCustomElementClass(win) { if (show) { const placeholder = this.getPlaceholder(); if (placeholder) { - dev().assertElement(placeholder).classList.remove('amp-hidden'); + dev() + .assertElement(placeholder) + .classList.remove('amp-hidden'); } } else { const placeholders = dom.childElementsByAttr(this, 'placeholder'); @@ -1521,9 +1577,12 @@ function createBaseCustomElementClass(win) { assertNotTemplate(this); const resourceState = this.getResourceState_(); // Do not show fallback before layout - if (show && (resourceState == ResourceState.NOT_BUILT || + if ( + show && + (resourceState == ResourceState.NOT_BUILT || resourceState == ResourceState.NOT_LAID_OUT || - resourceState == ResourceState.READY_FOR_LAYOUT)) { + resourceState == ResourceState.READY_FOR_LAYOUT) + ) { return; } // This implementation is notably less efficient then placeholder @@ -1572,10 +1631,14 @@ function createBaseCustomElementClass(win) { if (this.loadingDisabled_ === undefined) { this.loadingDisabled_ = this.hasAttribute('noloading'); } - if (this.loadingDisabled_ || !isLoadingAllowed(this) || + if ( + this.loadingDisabled_ || + !isLoadingAllowed(this) || this.layoutWidth_ < MIN_WIDTH_FOR_LOADING || this.layoutCount_ > 0 || - isInternalOrServiceNode(this) || !isLayoutSizeDefined(this.layout_)) { + isInternalOrServiceNode(this) || + !isLayoutSizeDefined(this.layout_) + ) { return false; } return true; @@ -1587,11 +1650,11 @@ function createBaseCustomElementClass(win) { */ isInA4A_() { return ( - // in FIE + // in FIE (this.ampdoc_ && this.ampdoc_.win != this.ownerDocument.defaultView) || - - // in inabox - getMode().runtime == 'inabox'); + // in inabox + getMode().runtime == 'inabox' + ); } /** @@ -1613,7 +1676,9 @@ function createBaseCustomElementClass(win) { amp-hidden">`; const element = createLoaderElement( - /** @type {!Document} */ (doc), this.elementName()); + /** @type {!Document} */ (doc), + this.elementName() + ); container.appendChild(element); this.appendChild(container); @@ -1632,9 +1697,11 @@ function createBaseCustomElementClass(win) { const cleanup = opt_options && opt_options.cleanup; const force = opt_options && opt_options.force; assertNotTemplate(this); - if (state && !this.implementation_.isLoadingReused() && - (this.layoutCount_ > 0 || - this.signals_.get(CommonSignals.RENDER_START))) { + if ( + state && + !this.implementation_.isLoadingReused() && + (this.layoutCount_ > 0 || this.signals_.get(CommonSignals.RENDER_START)) + ) { // Loading has already been canceled. Ignore. return; } @@ -1666,8 +1733,7 @@ function createBaseCustomElementClass(win) { this.loadingContainer_.classList.toggle('amp-hidden', !state); this.loadingElement_.classList.toggle('amp-active', state); - if (!state && cleanup && - !this.implementation_.isLoadingReused()) { + if (!state && cleanup && !this.implementation_.isLoadingReused()) { const loadingContainer = this.loadingContainer_; this.loadingContainer_ = null; this.loadingElement_ = null; @@ -1685,7 +1751,9 @@ function createBaseCustomElementClass(win) { getLayoutDelayMeter_() { if (!this.layoutDelayMeter_) { this.layoutDelayMeter_ = new LayoutDelayMeter( - toWin(this.ownerDocument.defaultView), this.getLayoutPriority()); + toWin(this.ownerDocument.defaultView), + this.getLayoutPriority() + ); } return this.layoutDelayMeter_; } @@ -1722,8 +1790,11 @@ function createBaseCustomElementClass(win) { this.getOverflowElement(); if (!this.overflowElement_) { if (overflown && this.warnOnMissingOverflow) { - user().warn(TAG, - 'Cannot resize element and overflow is not available', this); + user().warn( + TAG, + 'Cannot resize element and overflow is not available', + this + ); } } else { this.overflowElement_.classList.toggle('amp-visible', overflown); @@ -1731,10 +1802,13 @@ function createBaseCustomElementClass(win) { if (overflown) { this.overflowElement_.onclick = () => { const resources = this.getResources(); - resources./*OK*/changeSize(this, requestedHeight, requestedWidth); + resources./*OK*/ changeSize(this, requestedHeight, requestedWidth); resources.mutateElement(this, () => { this.overflowCallback( - /* overflown */ false, requestedHeight, requestedWidth); + /* overflown */ false, + requestedHeight, + requestedWidth + ); }); }; } else { @@ -1766,23 +1840,20 @@ function isInputPlaceholder(element) { return 'placeholder' in element; } - /** @param {!Element} element */ function assertNotTemplate(element) { devAssert(!element.isInTemplate_, 'Must never be called in template'); } - /** * Whether the implementation is a stub. * @param {?./base-element.BaseElement} impl * @return {boolean} */ function isStub(impl) { - return (impl instanceof ElementStub); + return impl instanceof ElementStub; } - /** * Returns "true" for internal AMP nodes or for placeholder elements. * @param {!Node} node @@ -1792,15 +1863,17 @@ function isInternalOrServiceNode(node) { if (isInternalElement(node)) { return true; } - if (node.tagName && (node.hasAttribute('placeholder') || + if ( + node.tagName && + (node.hasAttribute('placeholder') || node.hasAttribute('fallback') || - node.hasAttribute('overflow'))) { + node.hasAttribute('overflow')) + ) { return true; } return false; } - /** * Creates a new custom element class prototype. * @@ -1809,8 +1882,7 @@ function isInternalOrServiceNode(node) { * @param {function(new:./base-element.BaseElement, !Element)=} opt_implementationClass For testing only. * @return {!Object} Prototype of element. */ -export function createAmpElementForTesting( - win, name, opt_implementationClass) { +export function createAmpElementForTesting(win, name, opt_implementationClass) { const Element = createCustomElementClass(win, name); if (getMode().test && opt_implementationClass) { Element.prototype.implementationClassForTesting = opt_implementationClass; diff --git a/src/document-fetcher.js b/src/document-fetcher.js index c409d35853ecb..8fd0f7f76a2a0 100644 --- a/src/document-fetcher.js +++ b/src/document-fetcher.js @@ -39,21 +39,23 @@ export function fetchDocument(win, input, opt_init) { init = setupAMPCors(win, input, init); input = setupInput(win, input, init); const ampdocService = Services.ampdocServiceFor(win); - const ampdocSingle = - ampdocService.isSingleDoc() ? ampdocService.getAmpDoc() : null; + const ampdocSingle = ampdocService.isSingleDoc() + ? ampdocService.getAmpDoc() + : null; init.responseType = 'document'; - return getViewerInterceptResponse(win, ampdocSingle, input, init) - .then(interceptorResponse => { - if (interceptorResponse) { - return interceptorResponse.text().then(body => - new DOMParser().parseFromString(body, 'text/html') - ); - } - return xhrRequest(input, init).then(({xhr, response}) => { - verifyAmpCORSHeaders(win, response, init); - return xhr.responseXML; - }); + return getViewerInterceptResponse(win, ampdocSingle, input, init).then( + interceptorResponse => { + if (interceptorResponse) { + return interceptorResponse + .text() + .then(body => new DOMParser().parseFromString(body, 'text/html')); + } + return xhrRequest(input, init).then(({xhr, response}) => { + verifyAmpCORSHeaders(win, response, init); + return xhr.responseXML; }); + } + ); } /** @@ -67,7 +69,7 @@ function xhrRequest(input, init) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(init.method || 'GET', input, true); - xhr.withCredentials = (init.credentials == 'include'); + xhr.withCredentials = init.credentials == 'include'; xhr.responseType = 'document'; // Incoming headers are in fetch format, // so we need to convert them into xhr. @@ -81,8 +83,7 @@ function xhrRequest(input, init) { } if (xhr.status < 100 || xhr.status > 599) { xhr.onreadystatechange = null; - reject(user().createExpectedError( - `Unknown HTTP status ${xhr.status}`)); + reject(user().createExpectedError(`Unknown HTTP status ${xhr.status}`)); return; } // TODO(dvoytenko): This is currently simplified: we will wait for the @@ -94,9 +95,14 @@ function xhrRequest(input, init) { statusText: xhr.statusText, headers: parseHeaders(xhr.getAllResponseHeaders()), }; - const response = new Response('', /** @type {!ResponseInit} */ (options)); - const promise = assertSuccess(response) - .then(response => ({response, xhr})); + const response = new Response( + '', + /** @type {!ResponseInit} */ (options) + ); + const promise = assertSuccess(response).then(response => ({ + response, + xhr, + })); resolve(promise); } }; diff --git a/src/document-ready.js b/src/document-ready.js index 3e2f0da049671..c2ef972059f8c 100644 --- a/src/document-ready.js +++ b/src/document-ready.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** * Whether the document is ready. * @param {!Document} doc diff --git a/src/document-submit.js b/src/document-submit.js index 58ff3d065d41e..6a4a06453b75b 100644 --- a/src/document-submit.js +++ b/src/document-submit.js @@ -32,16 +32,15 @@ import {isExtensionScriptInNode} from './element-service'; export function installGlobalSubmitListenerForDoc(ampdoc) { // Register global submit event listener only if the amp-form // extension is used. Allowing the usage of native forms, otherwise. - return isExtensionScriptInNode(ampdoc, 'amp-form') - .then(ampFormInstalled => { - if (ampFormInstalled) { - ampdoc.getRootNode().addEventListener( - 'submit', onDocumentFormSubmit_, true); - } - }); + return isExtensionScriptInNode(ampdoc, 'amp-form').then(ampFormInstalled => { + if (ampFormInstalled) { + ampdoc + .getRootNode() + .addEventListener('submit', onDocumentFormSubmit_, true); + } + }); } - /** * Intercept any submit on the current document and prevent invalid submits from * going through. @@ -77,9 +76,12 @@ export function onDocumentFormSubmit_(e) { const inputs = form.elements; for (let i = 0; i < inputs.length; i++) { - userAssert(!inputs[i].name || - inputs[i].name != SOURCE_ORIGIN_PARAM, - 'Illegal input name, %s found: %s', SOURCE_ORIGIN_PARAM, inputs[i]); + userAssert( + !inputs[i].name || inputs[i].name != SOURCE_ORIGIN_PARAM, + 'Illegal input name, %s found: %s', + SOURCE_ORIGIN_PARAM, + inputs[i] + ); } const action = form.getAttribute('action'); @@ -88,42 +90,58 @@ export function onDocumentFormSubmit_(e) { if (actionXhr) { assertHttpsUrl(actionXhr, form, 'action-xhr'); - userAssert(!isProxyOrigin(actionXhr), - 'form action-xhr should not be on AMP CDN: %s', form); + userAssert( + !isProxyOrigin(actionXhr), + 'form action-xhr should not be on AMP CDN: %s', + form + ); checkCorsUrl(actionXhr); } if (action) { assertHttpsUrl(action, form, 'action'); - userAssert(!isProxyOrigin(action), - 'form action should not be on AMP CDN: %s', form); + userAssert( + !isProxyOrigin(action), + 'form action should not be on AMP CDN: %s', + form + ); checkCorsUrl(action); } if (method == 'GET') { - userAssert(actionXhr || action, - 'form action-xhr or action attribute is required for method=GET: %s', - form); + userAssert( + actionXhr || action, + 'form action-xhr or action attribute is required for method=GET: %s', + form + ); } else if (method == 'POST') { if (action) { const TAG = 'form'; - user().error(TAG, - 'action attribute is invalid for method=POST: %s', form); + user().error( + TAG, + 'action attribute is invalid for method=POST: %s', + form + ); } if (!actionXhr) { e.preventDefault(); - userAssert(false, - 'Only XHR based (via action-xhr attribute) submissions are support ' + + userAssert( + false, + 'Only XHR based (via action-xhr attribute) submissions are support ' + 'for POST requests. %s', - form); + form + ); } } const target = form.getAttribute('target'); if (target) { - userAssert(target == '_blank' || target == '_top', - 'form target=%s is invalid can only be _blank or _top: %s', - target, form); + userAssert( + target == '_blank' || target == '_top', + 'form target=%s is invalid can only be _blank or _top: %s', + target, + form + ); } else { form.setAttribute('target', '_top'); } @@ -142,7 +160,13 @@ export function onDocumentFormSubmit_(e) { const actions = Services.actionServiceForDoc(form); actions.execute( - form, 'submit', /*args*/ null, /*source*/ form, /*caller*/ form, e, - ActionTrust.HIGH); + form, + 'submit', + /*args*/ null, + /*source*/ form, + /*caller*/ form, + e, + ActionTrust.HIGH + ); } } diff --git a/src/dom.js b/src/dom.js index 7f92d96f9e9a4..83b7b99d436df 100644 --- a/src/dom.js +++ b/src/dom.js @@ -37,12 +37,10 @@ const HTML_ESCAPE_CHARS = { const HTML_ESCAPE_REGEX = /(&|<|>|"|'|`)/g; /** @const {string} */ -export const UPGRADE_TO_CUSTOMELEMENT_PROMISE = - '__AMP_UPG_PRM'; +export const UPGRADE_TO_CUSTOMELEMENT_PROMISE = '__AMP_UPG_PRM'; /** @const {string} */ -export const UPGRADE_TO_CUSTOMELEMENT_RESOLVER = - '__AMP_UPG_RES'; +export const UPGRADE_TO_CUSTOMELEMENT_RESOLVER = '__AMP_UPG_RES'; /** * Waits until the child element is constructed. Once the child is found, the @@ -120,7 +118,6 @@ export function waitForBody(doc, callback) { onDocumentReady(doc, () => waitForHead(doc, callback)); } - /** * Waits for document's body to be available. * @param {!Document} doc @@ -130,7 +127,6 @@ export function waitForBodyPromise(doc) { return new Promise(resolve => waitForBody(doc, resolve)); } - /** * Removes the element. * @param {!Element} element @@ -141,7 +137,6 @@ export function removeElement(element) { } } - /** * Removes all child nodes of the specified element. * @param {!Element} parent @@ -152,7 +147,6 @@ export function removeChildren(parent) { } } - /** * Copies all children nodes of element "from" to element "to". Child nodes * are deeply cloned. Notice, that this method should be used with care and @@ -262,7 +256,6 @@ export function closest(element, callback, opt_stopAt) { return null; } - /** * Finds the closest node that satisfies the callback from this node * up the DOM subtree. @@ -279,7 +272,6 @@ export function closestNode(node, callback) { return null; } - /** * Finds the closest ancestor element with the specified selector from this * element. @@ -305,8 +297,11 @@ export function closestAncestorElementBySelector(element, selector) { */ export function ancestorElements(child, predicate) { const ancestors = []; - for (let ancestor = child.parentElement; ancestor; - ancestor = ancestor.parentElement) { + for ( + let ancestor = child.parentElement; + ancestor; + ancestor = ancestor.parentElement + ) { if (predicate(ancestor)) { ancestors.push(ancestor); } @@ -314,7 +309,6 @@ export function ancestorElements(child, predicate) { return ancestors; } - /** * Finds all ancestor elements that has the specified tag name. * @param {!Element} child @@ -336,8 +330,11 @@ export function ancestorElementsByTag(child, tagName) { * @return {?Element} */ export function childElement(parent, callback) { - for (let child = parent.firstElementChild; child; - child = child.nextElementSibling) { + for ( + let child = parent.firstElementChild; + child; + child = child.nextElementSibling + ) { if (callback(child)) { return child; } @@ -345,7 +342,6 @@ export function childElement(parent, callback) { return null; } - /** * Finds all child elements that satisfy the callback. * @param {!Element} parent @@ -354,8 +350,11 @@ export function childElement(parent, callback) { */ export function childElements(parent, callback) { const children = []; - for (let child = parent.firstElementChild; child; - child = child.nextElementSibling) { + for ( + let child = parent.firstElementChild; + child; + child = child.nextElementSibling + ) { if (callback(child)) { children.push(child); } @@ -363,7 +362,6 @@ export function childElements(parent, callback) { return children; } - /** * Finds the last child element that satisfies the callback. * @param {!Element} parent @@ -371,8 +369,11 @@ export function childElements(parent, callback) { * @return {?Element} */ export function lastChildElement(parent, callback) { - for (let child = parent.lastElementChild; child; - child = child.previousElementSibling) { + for ( + let child = parent.lastElementChild; + child; + child = child.previousElementSibling + ) { if (callback(child)) { return child; } @@ -389,8 +390,7 @@ export function lastChildElement(parent, callback) { */ export function childNodes(parent, callback) { const nodes = []; - for (let child = parent.firstChild; child; - child = child.nextSibling) { + for (let child = parent.firstChild; child; child = child.nextSibling) { if (callback(child)) { nodes.push(child); } @@ -406,10 +406,9 @@ export function childNodes(parent, callback) { */ export function childElementByAttr(parent, attr) { assertIsName(attr); - return scopedQuerySelector/*OK*/(parent, `> [${attr}]`); + return scopedQuerySelector(/*OK*/ parent, `> [${attr}]`); } - /** * Finds the last child element that has the specified attribute. * @param {!Element} parent @@ -423,7 +422,6 @@ export function lastChildElementByAttr(parent, attr) { }); } - /** * Finds all child elements that has the specified attribute. * @param {!Element} parent @@ -432,10 +430,9 @@ export function lastChildElementByAttr(parent, attr) { */ export function childElementsByAttr(parent, attr) { assertIsName(attr); - return scopedQuerySelectorAll/*OK*/(parent, `> [${attr}]`); + return scopedQuerySelectorAll(/*OK*/ parent, `> [${attr}]`); } - /** * Finds the first child element that has the specified tag name. * @param {!Element} parent @@ -444,10 +441,9 @@ export function childElementsByAttr(parent, attr) { */ export function childElementByTag(parent, tagName) { assertIsName(tagName); - return scopedQuerySelector/*OK*/(parent, `> ${tagName}`); + return scopedQuerySelector(/*OK*/ parent, `> ${tagName}`); } - /** * Finds all child elements with the specified tag name. * @param {!Element} parent @@ -456,7 +452,7 @@ export function childElementByTag(parent, tagName) { */ export function childElementsByTag(parent, tagName) { assertIsName(tagName); - return scopedQuerySelectorAll/*OK*/(parent, `> ${tagName}`); + return scopedQuerySelectorAll(/*OK*/ parent, `> ${tagName}`); } /** @@ -466,11 +462,12 @@ export function childElementsByTag(parent, tagName) { * @return {boolean} True if the element matched the selector. False otherwise. */ export function matches(el, selector) { - const matcher = el.matches || - el.webkitMatchesSelector || - el.mozMatchesSelector || - el.msMatchesSelector || - el.oMatchesSelector; + const matcher = + el.matches || + el.webkitMatchesSelector || + el.mozMatchesSelector || + el.msMatchesSelector || + el.oMatchesSelector; if (matcher) { return matcher.call(el, selector); } @@ -485,7 +482,7 @@ export function matches(el, selector) { */ export function elementByTag(element, tagName) { assertIsName(tagName); - return element./*OK*/querySelector(tagName); + return element./*OK*/ querySelector(tagName); } /** @@ -502,7 +499,7 @@ function scopedQuerySelectionFallback(root, selector) { const unique = 'i-amphtml-scoped'; root.classList.add(unique); const scopedSelector = prependSelectorsWith(selector, `.${unique}`); - const elements = root./*OK*/querySelectorAll(scopedSelector); + const elements = root./*OK*/ querySelectorAll(scopedSelector); root.classList.remove(unique); return elements; } @@ -516,7 +513,7 @@ function scopedQuerySelectionFallback(root, selector) { */ export function scopedQuerySelector(root, selector) { if (isScopeSelectorSupported(root)) { - return root./*OK*/querySelector(prependSelectorsWith(selector, ':scope')); + return root./*OK*/ querySelector(prependSelectorsWith(selector, ':scope')); } // Only IE. @@ -533,15 +530,15 @@ export function scopedQuerySelector(root, selector) { */ export function scopedQuerySelectorAll(root, selector) { if (isScopeSelectorSupported(root)) { - return root./*OK*/querySelectorAll( - prependSelectorsWith(selector, ':scope')); + return root./*OK*/ querySelectorAll( + prependSelectorsWith(selector, ':scope') + ); } // Only IE. return scopedQuerySelectionFallback(root, selector); } - /** * Returns element data-param- attributes as url parameters key-value pairs. * e.g. data-param-some-attr=value -> {someAttr: value}. @@ -551,8 +548,11 @@ export function scopedQuerySelectorAll(root, selector) { * @param {!RegExp=} opt_paramPattern Regex pattern to match data attributes. * @return {!JsonObject} */ -export function getDataParamsFromAttributes(element, opt_computeParamNameFunc, - opt_paramPattern) { +export function getDataParamsFromAttributes( + element, + opt_computeParamNameFunc, + opt_paramPattern +) { const computeParamNameFunc = opt_computeParamNameFunc || (key => key); const {dataset} = element; const params = dict(); @@ -582,8 +582,10 @@ export function hasNextNodeInDocumentOrder(element, opt_stopNode) { if (currentElement.nextSibling) { return true; } - } while ((currentElement = currentElement.parentNode) && - currentElement != opt_stopNode); + } while ( + (currentElement = currentElement.parentNode) && + currentElement != opt_stopNode + ); return false; } @@ -657,9 +659,11 @@ export function openWindowDialog(win, url, target, opt_features) { * @return {boolean} */ export function isJsonScriptTag(element) { - return element.tagName == 'SCRIPT' && - element.hasAttribute('type') && - element.getAttribute('type').toUpperCase() == 'APPLICATION/JSON'; + return ( + element.tagName == 'SCRIPT' && + element.hasAttribute('type') && + element.getAttribute('type').toUpperCase() == 'APPLICATION/JSON' + ); } /** @@ -668,8 +672,10 @@ export function isJsonScriptTag(element) { * @return {boolean} */ export function isJsonLdScriptTag(element) { - return element.tagName == 'SCRIPT' && - element.getAttribute('type').toUpperCase() == 'APPLICATION/LD+JSON'; + return ( + element.tagName == 'SCRIPT' && + element.getAttribute('type').toUpperCase() == 'APPLICATION/LD+JSON' + ); } /** @@ -678,9 +684,10 @@ export function isJsonLdScriptTag(element) { * @return {boolean} */ export function isRTL(doc) { - const dir = doc.body.getAttribute('dir') - || doc.documentElement.getAttribute('dir') - || 'ltr'; + const dir = + doc.body.getAttribute('dir') || + doc.documentElement.getAttribute('dir') || + 'ltr'; return dir == 'rtl'; } @@ -711,7 +718,7 @@ function escapeHtmlChar(c) { */ export function tryFocus(element) { try { - element./*OK*/focus(); + element./*OK*/ focus(); } catch (e) { // IE <= 7 may throw exceptions when focusing on hidden items. } @@ -735,9 +742,11 @@ export function isAmpElement(element) { const tag = element.tagName; // Use prefix to recognize AMP element. This is necessary because stub // may not be attached yet. - return startsWith(tag, 'AMP-') && - // Some "amp-*" elements are not really AMP elements. :smh: - !(tag == 'AMP-STICKY-AD-TOP-PADDING' || tag == 'AMP-BODY'); + return ( + startsWith(tag, 'AMP-') && + // Some "amp-*" elements are not really AMP elements. :smh: + !(tag == 'AMP-STICKY-AD-TOP-PADDING' || tag == 'AMP-BODY') + ); } /** @@ -758,7 +767,6 @@ export function whenUpgradedToCustomElement(element) { const deferred = new Deferred(); element[UPGRADE_TO_CUSTOMELEMENT_PROMISE] = deferred.promise; element[UPGRADE_TO_CUSTOMELEMENT_RESOLVER] = deferred.resolve; - } return element[UPGRADE_TO_CUSTOMELEMENT_PROMISE]; @@ -770,12 +778,13 @@ export function whenUpgradedToCustomElement(element) { * @param {!Element} element */ export function fullscreenEnter(element) { - const requestFs = element.requestFullscreen - || element.requestFullScreen - || element.webkitRequestFullscreen - || element.webkitEnterFullscreen - || element.msRequestFullscreen - || element.mozRequestFullScreen; + const requestFs = + element.requestFullscreen || + element.requestFullScreen || + element.webkitRequestFullscreen || + element.webkitEnterFullscreen || + element.msRequestFullscreen || + element.mozRequestFullScreen; if (requestFs) { requestFs.call(element); } @@ -788,12 +797,12 @@ export function fullscreenEnter(element) { */ export function fullscreenExit(element) { const elementBoundExit = - element.cancelFullScreen - || element.exitFullscreen - || element.webkitExitFullscreen - || element.webkitCancelFullScreen - || element.mozCancelFullScreen - || element.msExitFullscreen; + element.cancelFullScreen || + element.exitFullscreen || + element.webkitExitFullscreen || + element.webkitCancelFullScreen || + element.mozCancelFullScreen || + element.msExitFullscreen; if (elementBoundExit) { elementBoundExit.call(element); return; @@ -803,18 +812,17 @@ export function fullscreenExit(element) { return; } const docBoundExit = - ownerDocument.cancelFullScreen - || ownerDocument.exitFullscreencancelFullScreen - || ownerDocument.webkitExitFullscreencancelFullScreen - || ownerDocument.webkitCancelFullScreencancelFullScreen - || ownerDocument.mozCancelFullScreencancelFullScreen - || ownerDocument.msExitFullscreen; + ownerDocument.cancelFullScreen || + ownerDocument.exitFullscreencancelFullScreen || + ownerDocument.webkitExitFullscreencancelFullScreen || + ownerDocument.webkitCancelFullScreencancelFullScreen || + ownerDocument.mozCancelFullScreencancelFullScreen || + ownerDocument.msExitFullscreen; if (docBoundExit) { docBoundExit.call(ownerDocument); } } - /** * Replacement for `Document.fullscreenElement`. * https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenElement @@ -831,10 +839,10 @@ export function isFullscreenElement(element) { return false; } const fullscreenElement = - ownerDocument.fullscreenElement - || ownerDocument.webkitFullscreenElement - || ownerDocument.mozFullScreenElement - || ownerDocument.webkitCurrentFullScreenElement; + ownerDocument.fullscreenElement || + ownerDocument.webkitFullscreenElement || + ownerDocument.mozFullScreenElement || + ownerDocument.webkitCurrentFullScreenElement; return fullscreenElement == element; } @@ -851,7 +859,7 @@ export function isEnabled(element) { } const PRECEDING_OR_CONTAINS = - Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS; + Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS; /** * A sorting comparator that sorts elements in DOM tree order. diff --git a/src/element-service.js b/src/element-service.js index b25c47eeefa01..687595837e9ce 100644 --- a/src/element-service.js +++ b/src/element-service.js @@ -44,7 +44,8 @@ import {userAssert} from './log'; */ export function getElementService(win, id, extension, opt_element) { return getElementServiceIfAvailable(win, id, extension, opt_element).then( - service => assertService(service, id, extension)); + service => assertService(service, id, extension) + ); } /** @@ -79,7 +80,6 @@ function isElementScheduled(win, elementName) { return !!win.ampExtendedElements[elementName]; } - /** * Returns a promise for a service for the given id and window. Also expects an * element that has the actual implementation. The promise resolves when the @@ -94,11 +94,13 @@ function isElementScheduled(win, elementName) { * not the extension. * @return {!Promise<*>} */ -export function getElementServiceForDoc(element, id, extension, - opt_element) { +export function getElementServiceForDoc(element, id, extension, opt_element) { return getElementServiceIfAvailableForDoc( - element, id, extension, opt_element) - .then(service => assertService(service, id, extension)); + element, + id, + extension, + opt_element + ).then(service => assertService(service, id, extension)); } /** @@ -113,26 +115,31 @@ export function getElementServiceForDoc(element, id, extension, * @return {!Promise} */ export function getElementServiceIfAvailableForDoc( - element, id, extension, opt_element + element, + id, + extension, + opt_element ) { const s = getServicePromiseOrNullForDoc(element, id); if (s) { return /** @type {!Promise} */ (s); } const ampdoc = getAmpdoc(element); - return ampdoc.whenBodyAvailable() - .then(() => waitForExtensionIfPresent( - ampdoc.win, extension, ampdoc.win.document.head)) - .then(() => { - // If this service is provided by an element, then we can't depend on - // the service (they may not use the element). - if (opt_element) { - return getServicePromiseOrNullForDoc(element, id); - } else if (isElementScheduled(ampdoc.win, extension)) { - return getServicePromiseForDoc(element, id); - } - return null; - }); + return ampdoc + .whenBodyAvailable() + .then(() => + waitForExtensionIfPresent(ampdoc.win, extension, ampdoc.win.document.head) + ) + .then(() => { + // If this service is provided by an element, then we can't depend on + // the service (they may not use the element). + if (opt_element) { + return getServicePromiseOrNullForDoc(element, id); + } else if (isElementScheduled(ampdoc.win, extension)) { + return getServicePromiseForDoc(element, id); + } + return null; + }); } /** @@ -147,7 +154,9 @@ export function getElementServiceIfAvailableForDoc( * @return {!Promise} */ export function getElementServiceIfAvailableForDocInEmbedScope( - element, id, extension + element, + id, + extension ) { const s = getExistingServiceForDocInEmbedScope(element, id); if (s) { @@ -173,11 +182,16 @@ export function getElementServiceIfAvailableForDocInEmbedScope( * @private */ function assertService(service, id, extension) { - return /** @type {!Object} */ (userAssert(service, - 'Service %s was requested to be provided through %s, ' + + return /** @type {!Object} */ (userAssert( + service, + 'Service %s was requested to be provided through %s, ' + 'but %s is not loaded in the current page. To fix this ' + 'problem load the JavaScript file for %s in this page.', - id, extension, extension, extension)); + id, + extension, + extension, + extension + )); } /** @@ -206,11 +220,9 @@ export function extensionScriptsInNode(head) { * @return {!Promise} */ export function isExtensionScriptInNode(ampdoc, extensionId) { - return ampdoc.whenBodyAvailable() - .then(() => { - return extensionScriptInNode( - ampdoc.getHeadNode(), extensionId); - }); + return ampdoc.whenBodyAvailable().then(() => { + return extensionScriptInNode(ampdoc.getHeadNode(), extensionId); + }); } /** @@ -248,8 +260,10 @@ function waitForExtensionIfPresent(win, extension, head) { } const extensions = getService(win, 'extensions'); - return /** @type {!Promise} */ ( - extensions.waitForExtension(win, extension)); + return /** @type {!Promise} */ (extensions.waitForExtension( + win, + extension + )); } /** @@ -263,16 +277,17 @@ function waitForExtensionIfPresent(win, extension, head) { * @private */ function getElementServicePromiseOrNull(win, id, extension, opt_element) { - return dom.waitForBodyPromise(win.document) - .then(() => waitForExtensionIfPresent(win, extension, win.document.head)) - .then(() => { - // If this service is provided by an element, then we can't depend on - // the service (they may not use the element). - if (opt_element) { - return getServicePromiseOrNull(win, id); - } else if (isElementScheduled(win, extension)) { - return getServicePromise(win, id); - } - return null; - }); + return dom + .waitForBodyPromise(win.document) + .then(() => waitForExtensionIfPresent(win, extension, win.document.head)) + .then(() => { + // If this service is provided by an element, then we can't depend on + // the service (they may not use the element). + if (opt_element) { + return getServicePromiseOrNull(win, id); + } else if (isElementScheduled(win, extension)) { + return getServicePromise(win, id); + } + return null; + }); } diff --git a/src/element-stub.js b/src/element-stub.js index 8aaa87ff21f94..47fe9516db41f 100644 --- a/src/element-stub.js +++ b/src/element-stub.js @@ -20,7 +20,6 @@ import {devAssert} from './log'; /** @type {!Array} */ export const stubbedElements = []; - export class ElementStub extends BaseElement { /** @param {!AmpElement} element */ constructor(element) { diff --git a/src/error.js b/src/error.js index 6b33998ae2d62..00e85e1a4250d 100644 --- a/src/error.js +++ b/src/error.js @@ -14,7 +14,6 @@ * limitations under the License. */ - import {AmpEvents} from './amp-events'; import {Services} from './services'; import { @@ -25,16 +24,10 @@ import { isUserErrorMessage, } from './log'; import {dict} from './utils/object'; -import { - experimentTogglesOrNull, - getBinaryType, - isCanary, -} from './experiments'; +import {experimentTogglesOrNull, getBinaryType, isCanary} from './experiments'; import {exponentialBackoff} from './exponential-backoff'; import {getMode} from './mode'; -import { - isLoadErrorMessage, -} from './event-helper'; +import {isLoadErrorMessage} from './event-helper'; import {isProxyOrigin} from './url'; import {makeBodyVisibleRecovery} from './style-installer'; import {startsWith} from './string'; @@ -51,7 +44,6 @@ const CANCELLED = 'CANCELLED'; */ const BLOCK_BY_CONSENT = 'BLOCK_BY_CONSENT'; - /** * The threshold for errors throttled because nothing can be done about * them, but we'd still like to report the rough number. @@ -66,7 +58,6 @@ const NON_ACTIONABLE_ERROR_THROTTLE_THRESHOLD = 0.001; */ const USER_ERROR_THROTTLE_THRESHOLD = 0.1; - /** * Collects error messages, so they can be included in subsequent reports. * That allows identifying errors that might be caused by previous errors. @@ -130,9 +121,13 @@ let detectedJsEngine; */ export function reportErrorForWin(win, error, opt_associatedElement) { reportError(error, opt_associatedElement); - if (error && !!win && isUserErrorMessage(error.message) - && !isUserErrorEmbed(error.message)) { - reportErrorToAnalytics(/** @type {!Error} */(error), win); + if ( + error && + !!win && + isUserErrorMessage(error.message) && + !isUserErrorEmbed(error.message) + ) { + reportErrorToAnalytics(/** @type {!Error} */ (error), win); } } @@ -153,7 +148,7 @@ export function reportError(error, opt_associatedElement) { let isValidError; if (error) { if (error.message !== undefined) { - error = duplicateErrorIfNecessary(/** @type {!Error} */(error)); + error = duplicateErrorIfNecessary(/** @type {!Error} */ (error)); isValidError = true; } else { const origError = error; @@ -167,7 +162,8 @@ export function reportError(error, opt_associatedElement) { if (!isValidError && getMode().localDev && !getMode().test) { setTimeout(function() { const rethrow = new Error( - '_reported_ Error reported incorrectly: ' + error); + '_reported_ Error reported incorrectly: ' + error + ); throw rethrow; }); } @@ -189,7 +185,7 @@ export function reportError(error, opt_associatedElement) { // Report to console. if (self.console) { - const output = (console.error || console.log); + const output = console.error || console.log; if (error.messageArray) { output.apply(console, error.messageArray); } else { @@ -208,8 +204,14 @@ export function reportError(error, opt_associatedElement) { // 'call' to make linter happy. And .call to make compiler happy // that expects some @this. - onError['call'](undefined, undefined, undefined, undefined, - undefined, error); + onError['call']( + undefined, + undefined, + undefined, + undefined, + undefined, + error + ); } catch (errorReportingError) { setTimeout(function() { throw errorReportingError; @@ -268,7 +270,6 @@ export function isBlockedByConsent(errorOrMessage) { return false; } - /** * Install handling of global unhandled exceptions. * @param {!Window} win @@ -276,9 +277,11 @@ export function isBlockedByConsent(errorOrMessage) { export function installErrorReporting(win) { win.onerror = /** @type {!Function} */ (onError); win.addEventListener('unhandledrejection', event => { - if (event.reason && + if ( + event.reason && (event.reason.message === CANCELLED || - event.reason.message === BLOCK_BY_CONSENT)) { + event.reason.message === BLOCK_BY_CONSENT) + ) { event.preventDefault(); return; } @@ -315,11 +318,18 @@ function onError(message, filename, line, col, error) { // due to buggy browser extensions may be helpful to notify authors. return; } - const data = getErrorReportData(message, filename, line, col, error, - hasNonAmpJs); + const data = getErrorReportData( + message, + filename, + line, + col, + error, + hasNonAmpJs + ); if (data) { reportingBackoff(() => - reportErrorToServerOrViewer(this, /** @type {!JsonObject} */ (data))); + reportErrorToServerOrViewer(this, /** @type {!JsonObject} */ (data)) + ); } } @@ -433,8 +443,14 @@ function buildErrorMessage_(message, error) { * @return {!JsonObject|undefined} The data to post * visibleForTesting */ -export function getErrorReportData(message, filename, line, col, error, - hasNonAmpJs) { +export function getErrorReportData( + message, + filename, + line, + col, + error, + hasNonAmpJs +) { message = buildErrorMessage_(message, error); // An "expected" error is still an error, i.e. some features are disabled // or not functioning fully because of it. However, it's an expected @@ -455,13 +471,15 @@ export function getErrorReportData(message, filename, line, col, error, // We throttle load errors and generic "Script error." errors // that have no information and thus cannot be acted upon. - if (isLoadErrorMessage(message) || + if ( + isLoadErrorMessage(message) || // See https://github.com/ampproject/amphtml/issues/7353 // for context. message == 'Script error.' || // Window has become detached, really anything can happen // at this point. - detachedWindow) { + detachedWindow + ) { expected = true; if (throttleBase > NON_ACTIONABLE_ERROR_THROTTLE_THRESHOLD) { @@ -680,6 +698,8 @@ export function reportErrorToAnalytics(error, win) { * @private */ function getRootElement_(win) { - const root = Services.ampdocServiceFor(win).getAmpDoc().getRootNode(); + const root = Services.ampdocServiceFor(win) + .getAmpDoc() + .getRootNode(); return dev().assertElement(root.documentElement || root.body || root); } diff --git a/src/event-helper-listen.js b/src/event-helper-listen.js index 0b570238b13da..288e3424a131e 100644 --- a/src/event-helper-listen.js +++ b/src/event-helper-listen.js @@ -15,10 +15,10 @@ */ /** - * Whether addEventListener supports options or only takes capture as a boolean - * @type {boolean|undefined} - * @visibleForTesting - */ + * Whether addEventListener supports options or only takes capture as a boolean + * @type {boolean|undefined} + * @visibleForTesting + */ let optsSupported; /** @@ -34,8 +34,12 @@ let optsSupported; * @param {Object=} opt_evtListenerOpts * @return {!UnlistenDef} */ -export function internalListenImplementation(element, eventType, listener, - opt_evtListenerOpts) { +export function internalListenImplementation( + element, + eventType, + listener, + opt_evtListenerOpts +) { let localElement = element; let localListener = listener; /** @@ -58,16 +62,16 @@ export function internalListenImplementation(element, eventType, listener, capture = opt_evtListenerOpts.capture; } localElement.addEventListener( - eventType, - wrapped, - optsSupported ? opt_evtListenerOpts : capture + eventType, + wrapped, + optsSupported ? opt_evtListenerOpts : capture ); return () => { if (localElement) { localElement.removeEventListener( - eventType, - wrapped, - optsSupported ? opt_evtListenerOpts : capture + eventType, + wrapped, + optsSupported ? opt_evtListenerOpts : capture ); } // Ensure these are GC'd @@ -106,8 +110,8 @@ export function detectEvtListenerOptsSupport() { } /** - * Resets the test for whether addEventListener supports options or not. - */ + * Resets the test for whether addEventListener supports options or not. + */ export function resetEvtListenerOptsSupportForTesting() { optsSupported = undefined; } diff --git a/src/event-helper.js b/src/event-helper.js index 41bdf97ac9908..94b9197b52b59 100644 --- a/src/event-helper.js +++ b/src/event-helper.js @@ -39,7 +39,11 @@ export function createCustomEvent(win, type, detail, opt_eventInit) { // Deprecated fallback for IE. const e = win.document.createEvent('CustomEvent'); e.initCustomEvent( - type, !!eventInit.bubbles, !!eventInit.cancelable, detail); + type, + !!eventInit.bubbles, + !!eventInit.cancelable, + detail + ); return e; } } @@ -54,7 +58,11 @@ export function createCustomEvent(win, type, detail, opt_eventInit) { */ export function listen(element, eventType, listener, opt_evtListenerOpts) { return internalListenImplementation( - element, eventType, listener, opt_evtListenerOpts); + element, + eventType, + listener, + opt_evtListenerOpts + ); } /** @@ -86,19 +94,23 @@ export function getDetail(event) { */ export function listenOnce(element, eventType, listener, opt_evtListenerOpts) { let localListener = listener; - const unlisten = internalListenImplementation(element, eventType, event => { - try { - localListener(event); - } finally { - // Ensure listener is GC'd - localListener = null; - unlisten(); - } - }, opt_evtListenerOpts); + const unlisten = internalListenImplementation( + element, + eventType, + event => { + try { + localListener(event); + } finally { + // Ensure listener is GC'd + localListener = null; + unlisten(); + } + }, + opt_evtListenerOpts + ); return unlisten; } - /** * Returns a promise that will resolve as soon as the specified event has * fired on the element. @@ -110,8 +122,12 @@ export function listenOnce(element, eventType, listener, opt_evtListenerOpts) { * access to the unlistener, so it may be called manually when necessary. * @return {!Promise} */ -export function listenOncePromise(element, eventType, opt_evtListenerOpts, - opt_cancel) { +export function listenOncePromise( + element, + eventType, + opt_evtListenerOpts, + opt_cancel +) { let unlisten; const eventPromise = new Promise(resolve => { unlisten = listenOnce(element, eventType, resolve, opt_evtListenerOpts); @@ -123,18 +139,19 @@ export function listenOncePromise(element, eventType, opt_evtListenerOpts, return eventPromise; } - /** * Whether the specified element/window has been loaded already. * @param {!Element|!Window} eleOrWindow * @return {boolean} */ export function isLoaded(eleOrWindow) { - return !!(eleOrWindow.complete || eleOrWindow.readyState == 'complete' - // If the passed in thing is a Window, infer loaded state from - // - || (eleOrWindow.document - && eleOrWindow.document.readyState == 'complete')); + return !!( + eleOrWindow.complete || + eleOrWindow.readyState == 'complete' || + // If the passed in thing is a Window, infer loaded state from + // + (eleOrWindow.document && eleOrWindow.document.readyState == 'complete') + ); } /** @@ -166,17 +183,20 @@ export function loadPromise(eleOrWindow) { } }); - return loadingPromise.then(() => { - if (unlistenError) { - unlistenError(); + return loadingPromise.then( + () => { + if (unlistenError) { + unlistenError(); + } + return eleOrWindow; + }, + () => { + if (unlistenLoad) { + unlistenLoad(); + } + failedToLoad(eleOrWindow); } - return eleOrWindow; - }, () => { - if (unlistenLoad) { - unlistenLoad(); - } - failedToLoad(eleOrWindow); - }); + ); } /** diff --git a/src/examiner/examiner.js b/src/examiner/examiner.js index b0c71d8080d7b..3191b72dac0f4 100644 --- a/src/examiner/examiner.js +++ b/src/examiner/examiner.js @@ -25,8 +25,10 @@ function detectLongTasks(win) { const observer = new win.PerformanceObserver(function(entryList) { const entries = entryList.getEntries(); for (let i = 0; i < entries.length; i++) { - if (entries[i].entryType != 'longtask' - || entries[i].name != 'cross-origin-descendant') { + if ( + entries[i].entryType != 'longtask' || + entries[i].name != 'cross-origin-descendant' + ) { continue; } const attr = entries[i].attribution[0]; @@ -42,10 +44,10 @@ function detectLongTasks(win) { culprit = ``; } } - console./*OK*/log( - `%c LONG TASK %c ${duration}ms from ${culprit}`, - 'background: red; color: white', - 'background: #fff; color: #000' + console./*OK*/ log( + `%c LONG TASK %c ${duration}ms from ${culprit}`, + 'background: red; color: white', + 'background: #fff; color: #000' ); } }); @@ -57,7 +59,9 @@ function detectLongTasks(win) { * @return {boolean} */ function isLongTaskApiSupported(win) { - return !!win.PerformanceObserver - && !!win.TaskAttributionTiming - && ('containerName' in win.TaskAttributionTiming.prototype); + return ( + !!win.PerformanceObserver && + !!win.TaskAttributionTiming && + 'containerName' in win.TaskAttributionTiming.prototype + ); } diff --git a/src/experiments.js b/src/experiments.js index 4c56c1637903d..81fb8aabfbbf6 100644 --- a/src/experiments.js +++ b/src/experiments.js @@ -60,8 +60,9 @@ export function isCanary(win) { * @return {string} */ export function getBinaryType(win) { - return win.AMP_CONFIG && win.AMP_CONFIG.type ? - win.AMP_CONFIG.type : 'unknown'; + return win.AMP_CONFIG && win.AMP_CONFIG.type + ? win.AMP_CONFIG.type + : 'unknown'; } /** @@ -87,9 +88,13 @@ export function isExperimentOn(win, experimentId) { * Default: false (save durably). * @return {boolean} New state for experimentId. */ -export function toggleExperiment(win, experimentId, opt_on, - opt_transientExperiment) { - const currentlyOn = isExperimentOn(win, /*OK*/experimentId); +export function toggleExperiment( + win, + experimentId, + opt_on, + opt_transientExperiment +) { + const currentlyOn = isExperimentOn(win, /*OK*/ experimentId); const on = !!(opt_on !== undefined ? opt_on : !currentlyOn); if (on != currentlyOn) { const toggles = experimentToggles(win); @@ -127,12 +132,15 @@ export function experimentToggles(win) { } } // Read document level override from meta tag. - if (win.AMP_CONFIG - && Array.isArray(win.AMP_CONFIG['allow-doc-opt-in']) - && win.AMP_CONFIG['allow-doc-opt-in'].length > 0) { + if ( + win.AMP_CONFIG && + Array.isArray(win.AMP_CONFIG['allow-doc-opt-in']) && + win.AMP_CONFIG['allow-doc-opt-in'].length > 0 + ) { const allowed = win.AMP_CONFIG['allow-doc-opt-in']; - const meta = - win.document.head.querySelector('meta[name="amp-experiments-opt-in"]'); + const meta = win.document.head.querySelector( + 'meta[name="amp-experiments-opt-in"]' + ); if (meta) { const optedInExperiments = meta.getAttribute('content').split(','); for (let i = 0; i < optedInExperiments.length; i++) { @@ -145,9 +153,11 @@ export function experimentToggles(win) { Object.assign(toggles, getExperimentTogglesFromCookie(win)); - if (win.AMP_CONFIG - && Array.isArray(win.AMP_CONFIG['allow-url-opt-in']) - && win.AMP_CONFIG['allow-url-opt-in'].length > 0) { + if ( + win.AMP_CONFIG && + Array.isArray(win.AMP_CONFIG['allow-url-opt-in']) && + win.AMP_CONFIG['allow-url-opt-in'].length > 0 + ) { const allowed = win.AMP_CONFIG['allow-url-opt-in']; const hash = win.location.originalHash || win.location.hash; const params = parseQueryString(hash); @@ -209,12 +219,17 @@ function saveExperimentTogglesToCookie(win, toggles) { experimentIds.push((toggles[experiment] === false ? '-' : '') + experiment); } - setCookie(win, COOKIE_NAME, experimentIds.join(','), - Date.now() + COOKIE_EXPIRATION_INTERVAL, { - // Set explicit domain, so the cookie gets send to sub domains. - domain: win.location.hostname, - allowOnProxyOrigin: true, - }); + setCookie( + win, + COOKIE_NAME, + experimentIds.join(','), + Date.now() + COOKIE_EXPIRATION_INTERVAL, + { + // Set explicit domain, so the cookie gets send to sub domains. + domain: win.location.hostname, + allowOnProxyOrigin: true, + } + ); } /** @@ -303,12 +318,14 @@ export function randomlySelectUnsetExperiments(win, experiments) { } if (hasOwn(win.experimentBranches, experimentName)) { selectedExperiments[experimentName] = - win.experimentBranches[experimentName]; + win.experimentBranches[experimentName]; continue; } - if (!experiments[experimentName].isTrafficEligible || - !experiments[experimentName].isTrafficEligible(win)) { + if ( + !experiments[experimentName].isTrafficEligible || + !experiments[experimentName].isTrafficEligible(win) + ) { win.experimentBranches[experimentName] = null; continue; } @@ -316,12 +333,14 @@ export function randomlySelectUnsetExperiments(win, experiments) { // If we're in the experiment, but we haven't already forced a specific // experiment branch (e.g., via a test setup), then randomize the branch // choice. - if (!win.experimentBranches[experimentName] && - isExperimentOn(win, /*OK*/experimentName)) { + if ( + !win.experimentBranches[experimentName] && + isExperimentOn(win, /*OK*/ experimentName) + ) { const {branches} = experiments[experimentName]; win.experimentBranches[experimentName] = selectRandomItem(branches); selectedExperiments[experimentName] = - win.experimentBranches[experimentName]; + win.experimentBranches[experimentName]; } } return selectedExperiments; diff --git a/src/exponential-backoff.js b/src/exponential-backoff.js index 4ac84e5dbed47..d9e8d4440d612 100644 --- a/src/exponential-backoff.js +++ b/src/exponential-backoff.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** * @param {number=} opt_base Exponential base. Defaults to 2. * @return {function(function()): number} Function that when invoked will @@ -54,9 +53,9 @@ export function exponentialBackoffClock(opt_base) { * @return {number} */ export function getJitter(wait, opt_perc) { - opt_perc = opt_perc || .3; + opt_perc = opt_perc || 0.3; let jitter = wait * opt_perc * Math.random(); - if (Math.random() > .5) { + if (Math.random() > 0.5) { jitter *= -1; } return jitter; diff --git a/src/extension-analytics.js b/src/extension-analytics.js index c2b29c23daeb3..e2bec7d61dd31 100644 --- a/src/extension-analytics.js +++ b/src/extension-analytics.js @@ -16,10 +16,7 @@ import {CommonSignals} from './common-signals'; import {Services} from './services'; -import { - createElementWithAttributes, - removeElement, -} from './dom'; +import {createElementWithAttributes, removeElement} from './dom'; import {devAssert} from './log'; import {dict} from './utils/object'; import {isArray, toWin} from './types'; @@ -35,19 +32,27 @@ import {triggerAnalyticsEvent} from './analytics'; * @return {!Element} created analytics element */ export function insertAnalyticsElement( - parentElement, config, loadAnalytics = false, disableImmediate = false) { + parentElement, + config, + loadAnalytics = false, + disableImmediate = false +) { const doc = /** @type {!Document} */ (parentElement.ownerDocument); const analyticsElem = createElementWithAttributes( - doc, - 'amp-analytics', dict({ - 'sandbox': 'true', - 'trigger': disableImmediate ? '' : 'immediate', - })); + doc, + 'amp-analytics', + dict({ + 'sandbox': 'true', + 'trigger': disableImmediate ? '' : 'immediate', + }) + ); const scriptElem = createElementWithAttributes( - doc, - 'script', dict({ - 'type': 'application/json', - })); + doc, + 'script', + dict({ + 'type': 'application/json', + }) + ); scriptElem.textContent = JSON.stringify(config); analyticsElem.appendChild(scriptElem); analyticsElem.CONFIG = config; @@ -55,10 +60,11 @@ export function insertAnalyticsElement( // Force load analytics extension if script not included in page. if (loadAnalytics) { // Get Extensions service and force load analytics extension. - const extensions = - Services.extensionsFor(toWin(parentElement.ownerDocument.defaultView)); + const extensions = Services.extensionsFor( + toWin(parentElement.ownerDocument.defaultView) + ); const ampdoc = Services.ampdoc(parentElement); - extensions./*OK*/installExtensionForDoc(ampdoc, 'amp-analytics'); + extensions./*OK*/ installExtensionForDoc(ampdoc, 'amp-analytics'); } else { Services.analyticsForDocOrNull(parentElement).then(analytics => { devAssert(analytics); @@ -92,15 +98,20 @@ class CustomEventReporter { for (const event in config['triggers']) { const eventType = config['triggers'][event]['on']; - devAssert(eventType, - 'CustomEventReporter config must specify trigger eventType'); + devAssert( + eventType, + 'CustomEventReporter config must specify trigger eventType' + ); const newEventType = this.getEventTypeInSandbox_(eventType); config['triggers'][event]['on'] = newEventType; } - this.parent_.signals().whenSignal(CommonSignals.LOAD_START).then(() => { - insertAnalyticsElement(this.parent_, config, true); - }); + this.parent_ + .signals() + .whenSignal(CommonSignals.LOAD_START) + .then(() => { + insertAnalyticsElement(this.parent_, config, true); + }); } /** @@ -108,10 +119,15 @@ class CustomEventReporter { * @param {!JsonObject=} opt_vars A map of vars and their values. */ trigger(eventType, opt_vars) { - devAssert(this.config_['triggers'][eventType], - 'Cannot trigger non initiated eventType'); - triggerAnalyticsEvent(this.parent_, - this.getEventTypeInSandbox_(eventType), opt_vars); + devAssert( + this.config_['triggers'][eventType], + 'Cannot trigger non initiated eventType' + ); + triggerAnalyticsEvent( + this.parent_, + this.getEventTypeInSandbox_(eventType), + opt_vars + ); } /** * @param {string} eventType @@ -122,7 +138,6 @@ class CustomEventReporter { } } - /** * A builder class that enable extension elements to easily build and get a * CustomEventReporter instance. Its constructor requires the parent AMP @@ -132,7 +147,6 @@ class CustomEventReporter { export class CustomEventReporterBuilder { /** @param {!AmpElement} parent */ constructor(parent) { - /** @private {!AmpElement} */ this.parent_ = parent; @@ -167,8 +181,10 @@ export class CustomEventReporterBuilder { */ track(eventType, request) { request = isArray(request) ? request : [request]; - devAssert(!this.config_['triggers'][eventType], - 'customEventReporterBuilder should not track same eventType twice'); + devAssert( + !this.config_['triggers'][eventType], + 'customEventReporterBuilder should not track same eventType twice' + ); const requestList = []; for (let i = 0; i < request.length; i++) { const requestName = `${eventType}-request-${i}`; @@ -190,13 +206,14 @@ export class CustomEventReporterBuilder { build() { devAssert(this.config_, 'CustomEventReporter already built'); const report = new CustomEventReporter( - this.parent_, /** @type {!JsonObject} */ (this.config_)); + this.parent_, + /** @type {!JsonObject} */ (this.config_) + ); this.config_ = null; return report; } } - /** * A helper method that should be used by all extension elements to add their * sandbox analytics tracking. This method takes care of insert and remove the @@ -208,26 +225,32 @@ export function useAnalyticsInSandbox(element, promise) { let analyticsElement = null; let configPromise = promise; // Listener to LOAD_START signal. Insert analytics element on LOAD_START - element.signals().whenSignal(CommonSignals.LOAD_START).then(() => { - if (analyticsElement || !configPromise) { - return; - } - configPromise.then(config => { - if (!configPromise) { - // If config promise resolve after unload, do nothing. + element + .signals() + .whenSignal(CommonSignals.LOAD_START) + .then(() => { + if (analyticsElement || !configPromise) { return; } - configPromise = null; - analyticsElement = insertAnalyticsElement(element, config, false); + configPromise.then(config => { + if (!configPromise) { + // If config promise resolve after unload, do nothing. + return; + } + configPromise = null; + analyticsElement = insertAnalyticsElement(element, config, false); + }); }); - }); // Listener to UNLOAD signal. Destroy remove element on UNLOAD - element.signals().whenSignal(CommonSignals.UNLOAD).then(() => { - configPromise = null; - if (analyticsElement) { - removeElement(analyticsElement); - analyticsElement = null; - } - }); + element + .signals() + .whenSignal(CommonSignals.UNLOAD) + .then(() => { + configPromise = null; + if (analyticsElement) { + removeElement(analyticsElement); + analyticsElement = null; + } + }); } diff --git a/src/finite-state-machine.js b/src/finite-state-machine.js index 02c5eec853978..c79ac03e66c73 100644 --- a/src/finite-state-machine.js +++ b/src/finite-state-machine.js @@ -20,7 +20,6 @@ import {devAssert} from './log'; * @template STATE */ export class FiniteStateMachine { - /** * Constructs a FSM using the bits defined in initialState as changeable * states. @@ -51,8 +50,8 @@ export class FiniteStateMachine { addTransition(oldState, newState, callback) { const transition = this.statesToTransition_(oldState, newState); devAssert( - !this.transitions_[transition], - 'cannot define a duplicate transition callback' + !this.transitions_[transition], + 'cannot define a duplicate transition callback' ); this.transitions_[transition] = callback; } diff --git a/src/focus-history.js b/src/focus-history.js index c9c79b4312078..d3ad57ae8b0b9 100644 --- a/src/focus-history.js +++ b/src/focus-history.js @@ -18,7 +18,6 @@ import {Observable} from './observable'; import {Services} from './services'; import {dev} from './log'; - /** * FocusHistory keeps track of recent focused elements. This history can be * purged using `purgeBefore` method. @@ -89,8 +88,10 @@ export class FocusHistory { */ pushFocus_(element) { const now = Date.now(); - if (this.history_.length == 0 || - this.history_[this.history_.length - 1].el != element) { + if ( + this.history_.length == 0 || + this.history_[this.history_.length - 1].el != element + ) { this.history_.push({el: element, time: now}); } else { this.history_[this.history_.length - 1].time = now; diff --git a/src/font-stylesheet-timeout.js b/src/font-stylesheet-timeout.js index 214cc86b64fae..0e0b812f592c0 100644 --- a/src/font-stylesheet-timeout.js +++ b/src/font-stylesheet-timeout.js @@ -63,9 +63,10 @@ function maybeTimeoutFonts(win) { // Find all stylesheets that aren't loaded from the AMP CDN (those are // critical if they are present). const styleLinkElements = win.document.querySelectorAll( - `link[rel~="stylesheet"]:not([href^="${ - escapeCssSelectorIdent(urls.cdn) - }"])`); + `link[rel~="stylesheet"]:not([href^="${escapeCssSelectorIdent( + urls.cdn + )}"])` + ); // Compare external sheets against elements of document.styleSheets. // They do not appear in this list until they have been loaded. const timedoutStyleSheets = []; diff --git a/src/form-data-wrapper.js b/src/form-data-wrapper.js index 459df80be3d15..499ce36c9e347 100644 --- a/src/form-data-wrapper.js +++ b/src/form-data-wrapper.js @@ -15,10 +15,7 @@ */ import {Services} from './services'; -import { - getFormAsObject, - getSubmitButtonUsed, -} from './form'; +import {getFormAsObject, getSubmitButtonUsed} from './form'; import {iterateCursor} from './dom'; import {map} from './utils/object'; @@ -106,9 +103,9 @@ export class PolyfillFormDataWrapper { let nextIndex = 0; return /** @type {!Iterator>} */ ({ next() { - return nextIndex < fieldEntries.length ? - {value: fieldEntries[nextIndex++], done: false} : - {value: undefined, done: true}; + return nextIndex < fieldEntries.length + ? {value: fieldEntries[nextIndex++], done: false} + : {value: undefined, done: true}; }, }); } diff --git a/src/form.js b/src/form.js index 1fed28731d60a..5a352529ed5d0 100644 --- a/src/form.js +++ b/src/form.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - ancestorElementsByTag, - iterateCursor, -} from './dom'; +import {ancestorElementsByTag, iterateCursor} from './dom'; /** @const {string} */ const FORM_PROP_ = '__AMP_FORM'; @@ -55,19 +52,14 @@ export function getFormAsObject(form) { for (let i = 0; i < elements.length; i++) { const input = elements[i]; - const { - checked, - name, - multiple, - options, - tagName, - type, - value, - } = input; - if (!name || isDisabled(input) || - !submittableTagsRegex.test(tagName) || - unsubmittableTypesRegex.test(type) || - (checkableType.test(type) && !checked)) { + const {checked, name, multiple, options, tagName, type, value} = input; + if ( + !name || + isDisabled(input) || + !submittableTagsRegex.test(tagName) || + unsubmittableTypesRegex.test(type) || + (checkableType.test(type) && !checked) + ) { continue; } @@ -149,10 +141,7 @@ export function getSubmitButtonUsed(form) { * @return {boolean} */ function isSubmitButton(element) { - const { - tagName, - type, - } = element; + const {tagName, type} = element; return tagName == 'BUTTON' || type == 'submit'; } diff --git a/src/friendly-iframe-embed.js b/src/friendly-iframe-embed.js index ec9b88e9bcac3..a8f332bf9d6f8 100644 --- a/src/friendly-iframe-embed.js +++ b/src/friendly-iframe-embed.js @@ -33,14 +33,16 @@ import { } from './style'; import {toWin} from './types'; - /** @const {string} */ const EMBED_PROP = '__AMP_EMBED__'; /** @const {!Array} */ -const EXCLUDE_INI_LOAD = - ['AMP-AD', 'AMP-ANALYTICS', 'AMP-PIXEL', 'AMP-AD-EXIT']; - +const EXCLUDE_INI_LOAD = [ + 'AMP-AD', + 'AMP-ANALYTICS', + 'AMP-PIXEL', + 'AMP-AD-EXIT', +]; /** * Parameters used to create the new "friendly iframe" embed. @@ -61,7 +63,6 @@ const EXCLUDE_INI_LOAD = */ export let FriendlyIframeSpec; - /** * @type {boolean|undefined} * @visibleForTesting @@ -87,7 +88,6 @@ function isSrcdocSupported() { return srcdocSupported; } - /** * Sets whether the embed is currently visible. The interpretation of visibility * is up to the embed parent. However, most of typical cases would rely on @@ -100,7 +100,6 @@ export function setFriendlyIframeEmbedVisible(embed, visible) { embed.setVisible_(visible); } - /** * Returns the embed created using `installFriendlyIframeEmbed` or `null`. * Caution: This will only return the FIE after the iframe has 'loaded'. If you @@ -113,7 +112,6 @@ export function getFriendlyIframeEmbedOptional(iframe) { return /** @type {?FriendlyIframeEmbed} */ (iframe[EMBED_PROP]); } - /** * Creates the requested "friendly iframe" embed. Returns the promise that * will be resolved as soon as the embed is available. The actual @@ -125,8 +123,12 @@ export function getFriendlyIframeEmbedOptional(iframe) { * @param {function(!Window)=} opt_preinstallCallback * @return {!Promise} */ -export function installFriendlyIframeEmbed(iframe, container, spec, - opt_preinstallCallback) { +export function installFriendlyIframeEmbed( + iframe, + container, + spec, + opt_preinstallCallback +) { /** @const {!Window} */ const win = getTopWindow(toWin(iframe.ownerDocument.defaultView)); /** @const {!./service/extensions-impl.Extensions} */ @@ -137,8 +139,9 @@ export function installFriendlyIframeEmbed(iframe, container, spec, // Pre-load extensions. if (spec.extensionIds) { - spec.extensionIds.forEach( - extensionId => extensions.preloadExtension(extensionId)); + spec.extensionIds.forEach(extensionId => + extensions.preloadExtension(extensionId) + ); } const html = mergeHtml(spec); @@ -149,10 +152,12 @@ export function installFriendlyIframeEmbed(iframe, container, spec, iframe.readyState = 'complete'; }; const registerViolationListener = () => { - iframe.contentWindow.addEventListener('securitypolicyviolation', - violationEvent => { - dev().warn('FIE', 'security policy violation', violationEvent); - }); + iframe.contentWindow.addEventListener( + 'securitypolicyviolation', + violationEvent => { + dev().warn('FIE', 'security policy violation', violationEvent); + } + ); }; let loadedPromise; if (isSrcdocSupported()) { @@ -194,12 +199,14 @@ export function installFriendlyIframeEmbed(iframe, container, spec, // For safety, make sure we definitely stop polling when child doc is // loaded. - loadedPromise.catch(error => { - rethrowAsync(error); - }).then(() => { - resolve(); - win.clearInterval(interval); - }); + loadedPromise + .catch(error => { + rethrowAsync(error); + }) + .then(() => { + resolve(); + win.clearInterval(interval); + }); }); } @@ -210,14 +217,16 @@ export function installFriendlyIframeEmbed(iframe, container, spec, const childWin = /** @type {!Window} */ (iframe.contentWindow); // Add extensions. extensions.installExtensionsInChildWindow( - childWin, spec.extensionIds || [], opt_preinstallCallback); + childWin, + spec.extensionIds || [], + opt_preinstallCallback + ); // Ready to be shown. embed.startRender_(); return embed; }); } - /** * Returns `true` when iframe is ready. * @param {!HTMLIFrameElement} iframe @@ -230,13 +239,14 @@ function isIframeReady(iframe) { // no other reliable signal for `readyState` in a child window and thus // the best way to check is to see the contents of the body. const childDoc = iframe.contentWindow && iframe.contentWindow.document; - return !!(childDoc && - isDocumentReady(childDoc) && - childDoc.body && - childDoc.body.firstChild); + return !!( + childDoc && + isDocumentReady(childDoc) && + childDoc.body && + childDoc.body.firstChild + ); } - /** * Merges base and fonts into html document. * @param {!FriendlyIframeSpec} spec @@ -274,13 +284,16 @@ function mergeHtml(spec) { if (spec.fonts) { spec.fonts.forEach(font => { result.push( - ``); + `` + ); }); } // Load CSP - result.push(''); + result.push( + '" + ); // Postambule. if (ip > 0) { @@ -292,7 +305,6 @@ function mergeHtml(spec) { return result.join(''); } - /** * Exposes `mergeHtml` for testing purposes. * @param {!FriendlyIframeSpec} spec @@ -302,7 +314,6 @@ export function mergeHtmlForTesting(spec) { return mergeHtml(spec); } - /** * A "friendly iframe" embed. This is the iframe that's fully accessible to * the AMP runtime. It's similar to Shadow DOM in many respects, but it also @@ -314,7 +325,6 @@ export function mergeHtmlForTesting(spec) { * resources. */ export class FriendlyIframeEmbed { - /** * @param {!HTMLIFrameElement} iframe * @param {!FriendlyIframeSpec} spec @@ -325,7 +335,7 @@ export class FriendlyIframeEmbed { this.iframe = iframe; /** @const {!Window} */ - this.win = /** @type{!Window} */(iframe.contentWindow); + this.win = /** @type{!Window} */ (iframe.contentWindow); /** @const {!FriendlyIframeSpec} */ this.spec = spec; @@ -435,9 +445,11 @@ export class FriendlyIframeEmbed { rect = this.host.getLayoutBox(); } else { rect = layoutRectLtwh( - 0, 0, - this.win./*OK*/innerWidth, - this.win./*OK*/innerHeight); + 0, + 0, + this.win./*OK*/ innerWidth, + this.win./*OK*/ innerHeight + ); } Promise.all([ this.whenReady(), @@ -484,9 +496,9 @@ export class FriendlyIframeEmbed { * @visibleForTesting */ getBodyElement() { - return /** @type {!HTMLBodyElement} */ ( - (this.iframe.contentDocument || this.iframe.contentWindow.document) - .body); + return /** @type {!HTMLBodyElement} */ (( + this.iframe.contentDocument || this.iframe.contentWindow.document + ).body); } /** @@ -505,8 +517,11 @@ export class FriendlyIframeEmbed { * @private */ measureMutate_(task) { - return this.getResources_().measureMutateElement(this.iframe, - task.measure || null, task.mutate); + return this.getResources_().measureMutateElement( + this.iframe, + task.measure || null, + task.mutate + ); } /** @@ -516,16 +531,18 @@ export class FriendlyIframeEmbed { const ampAdParent = dev().assertElement(this.iframe.parentNode); // Security assertion. Otherwise any 3p frame could request lighbox mode. - userAssert(ampAdParent.tagName.toLowerCase() == 'amp-ad', - 'Only is allowed to enter lightbox mode.'); + userAssert( + ampAdParent.tagName.toLowerCase() == 'amp-ad', + 'Only is allowed to enter lightbox mode.' + ); let bodyStyle; return this.measureMutate_({ measure: () => { - const rect = this.host ? - this.host.getLayoutBox() : - this.iframe./*OK*/getBoundingClientRect(); + const rect = this.host + ? this.host.getLayoutBox() + : this.iframe./*OK*/ getBoundingClientRect(); // Offset by scroll top as iframe will be position: fixed. const dy = -Services.viewportForDoc(this.iframe).getScrollTop(); @@ -610,16 +627,16 @@ export class FriendlyIframeEmbed { */ export function whenContentIniLoad(elementOrAmpDoc, hostWin, rect) { return Services.resourcesForDoc(elementOrAmpDoc) - .getResourcesInRect(hostWin, rect) - .then(resources => { - const promises = []; - resources.forEach(r => { - if (!EXCLUDE_INI_LOAD.includes(r.element.tagName)) { - promises.push(r.loadedOnce()); - } - }); - return Promise.all(promises); + .getResourcesInRect(hostWin, rect) + .then(resources => { + const promises = []; + resources.forEach(r => { + if (!EXCLUDE_INI_LOAD.includes(r.element.tagName)) { + promises.push(r.loadedOnce()); + } }); + return Promise.all(promises); + }); } /** @@ -627,6 +644,8 @@ export function whenContentIniLoad(elementOrAmpDoc, hostWin, rect) { * @return {boolean} */ export function isInFie(element) { - return element.classList.contains('i-amphtml-fie') || - !!closestAncestorElementBySelector(element, '.i-amphtml-fie'); + return ( + element.classList.contains('i-amphtml-fie') || + !!closestAncestorElementBySelector(element, '.i-amphtml-fie') + ); } diff --git a/src/full-overlay-frame-helper.js b/src/full-overlay-frame-helper.js index 42c1c68bfe44e..46bcb908f2524 100644 --- a/src/full-overlay-frame-helper.js +++ b/src/full-overlay-frame-helper.js @@ -15,7 +15,6 @@ */ import {px, resetStyles, setStyles, translate} from './style'; - /** * Centers a frame with a translate transition. * This function does direct DOM manipulation, so it needs to run under vsync @@ -26,16 +25,21 @@ import {px, resetStyles, setStyles, translate} from './style'; * @param {number} transitionTimeMs */ export function centerFrameUnderVsyncMutate( - iframe, iframeRect, viewportSize, transitionTimeMs) { - + iframe, + iframeRect, + viewportSize, + transitionTimeMs +) { // TODO(alanorozco): Place a sentinel sibling on inabox to account for // gap necessary for position: fixed. const translateX = px( - (viewportSize.width / 2) - (iframeRect.width / 2) - iframeRect.left); + viewportSize.width / 2 - iframeRect.width / 2 - iframeRect.left + ); const translateY = px( - (viewportSize.height / 2) - (iframeRect.height / 2) - iframeRect.top); + viewportSize.height / 2 - iframeRect.height / 2 - iframeRect.top + ); setStyles(iframe, { 'position': 'fixed', @@ -51,7 +55,6 @@ export function centerFrameUnderVsyncMutate( }); } - /** * Expands frame to fill the entire viewport. * This function does direct DOM manipulation, so it needs to run under vsync @@ -75,7 +78,6 @@ export function expandFrameUnderVsyncMutate(iframe) { }); } - /** * Resets frame that was previously expanded to fill the entire viewport. * This function does direct DOM manipulation, so it needs to run under vsync diff --git a/src/gesture-recognizers.js b/src/gesture-recognizers.js index c2b4c181b9b3a..f24a84d9afd07 100644 --- a/src/gesture-recognizers.js +++ b/src/gesture-recognizers.js @@ -28,7 +28,6 @@ const DOUBLETAP_DELAY = 200; */ export let TapDef; - /** * Recognizes "tap" gestures. * @extends {GestureRecognizer} @@ -91,17 +90,18 @@ export class TapRecognizer extends GestureRecognizer { /** @override */ acceptStart() { - this.signalEmit({ - clientX: this.lastX_, - clientY: this.lastY_, - target: this.target_, - }, null); + this.signalEmit( + { + clientX: this.lastX_, + clientY: this.lastY_, + target: this.target_, + }, + null + ); this.signalEnd(); } } - - /** * A "doubletap" gesture. * @typedef {{ @@ -111,7 +111,6 @@ export class TapRecognizer extends GestureRecognizer { */ export let DoubletapDef; - /** * Recognizes a "doubletap" gesture. This gesture will block a single "tap" * for about 200ms while it's expecting the second "tap". @@ -202,8 +201,6 @@ export class DoubletapRecognizer extends GestureRecognizer { } } - - /** * A "swipe-xy", "swipe-x" or "swipe-y" gesture. A number of these gestures * may be emitted for a single touch series. @@ -218,7 +215,6 @@ export class DoubletapRecognizer extends GestureRecognizer { */ export let SwipeDef; - /** * Recognizes swipe gestures. This gesture will yield about 10ms to other * gestures. @@ -298,7 +294,7 @@ class SwipeRecognizer extends GestureRecognizer { onTouchMove(e) { const {touches} = e; if (touches && touches.length >= 1) { - const {clientX: x, clientY: y} = touches[0] ; + const {clientX: x, clientY: y} = touches[0]; this.lastX_ = x; this.lastY_ = y; if (this.eventing_) { @@ -376,10 +372,16 @@ class SwipeRecognizer extends GestureRecognizer { // It's often that `touchend` arrives on the next frame. These should // be ignored to avoid a significant velocity downgrade. if ((!last && deltaTime > 4) || (last && deltaTime > 16)) { - const velocityX = calcVelocity(this.lastX_ - this.prevX_, deltaTime, - this.velocityX_); - const velocityY = calcVelocity(this.lastY_ - this.prevY_, deltaTime, - this.velocityY_); + const velocityX = calcVelocity( + this.lastX_ - this.prevX_, + deltaTime, + this.velocityX_ + ); + const velocityY = calcVelocity( + this.lastY_ - this.prevY_, + deltaTime, + this.velocityY_ + ); // On iOS, the touchend will always have the same x/y position as the // last touchmove, so we want to make sure we do not remove the velocity. @@ -395,19 +397,22 @@ class SwipeRecognizer extends GestureRecognizer { this.prevTime_ = this.lastTime_; } - this.signalEmit({ - first, - last, - time: this.lastTime_, - deltaX: this.lastX_ - this.startX_, - deltaY: this.lastY_ - this.startY_, - startX: this.startX_, - startY: this.startY_, - lastX: this.lastX_, - lastY: this.lastY_, - velocityX: this.velocityX_, - velocityY: this.velocityY_, - }, event); + this.signalEmit( + { + first, + last, + time: this.lastTime_, + deltaX: this.lastX_ - this.startX_, + deltaY: this.lastY_ - this.startY_, + startX: this.startX_, + startY: this.startY_, + lastX: this.lastX_, + lastY: this.lastY_, + velocityX: this.velocityX_, + velocityY: this.velocityY_, + }, + event + ); } /** @@ -423,7 +428,6 @@ class SwipeRecognizer extends GestureRecognizer { } } - /** * Recognizes "swipe-xy" gesture. Yields about 10ms to other gestures. */ @@ -436,7 +440,6 @@ export class SwipeXYRecognizer extends SwipeRecognizer { } } - /** * Recognizes "swipe-x" gesture. Yields about 10ms to other gestures. */ @@ -449,7 +452,6 @@ export class SwipeXRecognizer extends SwipeRecognizer { } } - /** * Recognizes "swipe-y" gesture. Yields about 10ms to other gestures. */ @@ -462,8 +464,6 @@ export class SwipeYRecognizer extends SwipeRecognizer { } } - - /** * A "tapzoom" gesture. It has a center, delta off the center center and * the velocity of moving away from the center. @@ -480,7 +480,6 @@ export class SwipeYRecognizer extends SwipeRecognizer { */ let TapzoomDef; - /** * Recognizes a "tapzoom" gesture. This gesture will block other gestures * for about 400ms after first "tap" while it's expecting swipe. @@ -611,25 +610,34 @@ export class TapzoomRecognizer extends GestureRecognizer { if (first) { this.velocityX_ = this.velocityY_ = 0; } else if (this.lastTime_ - this.prevTime_ > 2) { - this.velocityX_ = calcVelocity(this.lastX_ - this.prevX_, - this.lastTime_ - this.prevTime_, this.velocityX_); - this.velocityY_ = calcVelocity(this.lastY_ - this.prevY_, - this.lastTime_ - this.prevTime_, this.velocityY_); + this.velocityX_ = calcVelocity( + this.lastX_ - this.prevX_, + this.lastTime_ - this.prevTime_, + this.velocityX_ + ); + this.velocityY_ = calcVelocity( + this.lastY_ - this.prevY_, + this.lastTime_ - this.prevTime_, + this.velocityY_ + ); } this.prevX_ = this.lastX_; this.prevY_ = this.lastY_; this.prevTime_ = this.lastTime_; - this.signalEmit({ - first, - last, - centerClientX: this.startX_, - centerClientY: this.startY_, - deltaX: this.lastX_ - this.startX_, - deltaY: this.lastY_ - this.startY_, - velocityX: this.velocityX_, - velocityY: this.velocityY_, - }, event); + this.signalEmit( + { + first, + last, + centerClientX: this.startX_, + centerClientY: this.startY_, + deltaX: this.lastX_ - this.startX_, + deltaY: this.lastY_ - this.startY_, + velocityX: this.velocityX_, + velocityY: this.velocityY_, + }, + event + ); } /** @@ -645,8 +653,6 @@ export class TapzoomRecognizer extends GestureRecognizer { } } - - /** * A "pinch" gesture. It has a center, delta off the center center and * the velocity of moving away from the center. "dir" component of `1` @@ -868,10 +874,16 @@ export class PinchRecognizer extends GestureRecognizer { // It's often that `touchend` arrives on the next frame. These should // be ignored to avoid a significant velocity downgrade. if ((!last && deltaTime > 4) || (last && deltaTime > 16)) { - this.velocityX_ = calcVelocity(deltaX - this.prevDeltaX_, deltaTime, - this.velocityX_); - this.velocityY_ = calcVelocity(deltaY - this.prevDeltaY_, deltaTime, - this.velocityY_); + this.velocityX_ = calcVelocity( + deltaX - this.prevDeltaX_, + deltaTime, + this.velocityX_ + ); + this.velocityY_ = calcVelocity( + deltaY - this.prevDeltaY_, + deltaTime, + this.velocityY_ + ); this.velocityX_ = Math.abs(this.velocityX_) > 1e-4 ? this.velocityX_ : 0; this.velocityY_ = Math.abs(this.velocityY_) > 1e-4 ? this.velocityY_ : 0; this.prevDeltaX_ = deltaX; @@ -879,22 +891,33 @@ export class PinchRecognizer extends GestureRecognizer { this.prevTime_ = this.lastTime_; } - const startSq = this.sqDist_(this.startX1_, this.startX2_, - this.startY1_, this.startY2_); - const lastSq = this.sqDist_(this.lastX1_, this.lastX2_, - this.lastY1_, this.lastY2_); - this.signalEmit({ - first, - last, - time: this.lastTime_, - centerClientX: this.centerClientX_, - centerClientY: this.centerClientY_, - dir: Math.sign(lastSq - startSq), - deltaX: deltaX * 0.5, - deltaY: deltaY * 0.5, - velocityX: this.velocityX_ * 0.5, - velocityY: this.velocityY_ * 0.5, - }, event); + const startSq = this.sqDist_( + this.startX1_, + this.startX2_, + this.startY1_, + this.startY2_ + ); + const lastSq = this.sqDist_( + this.lastX1_, + this.lastX2_, + this.lastY1_, + this.lastY2_ + ); + this.signalEmit( + { + first, + last, + time: this.lastTime_, + centerClientX: this.centerClientX_, + centerClientY: this.centerClientY_, + dir: Math.sign(lastSq - startSq), + deltaX: deltaX * 0.5, + deltaY: deltaY * 0.5, + velocityX: this.velocityX_ * 0.5, + velocityY: this.velocityY_ * 0.5, + }, + event + ); } /** @@ -926,8 +949,9 @@ export class PinchRecognizer extends GestureRecognizer { * @private */ deltaX_() { - return Math.abs((this.lastX1_ - this.startX1_) - - (this.lastX2_ - this.startX2_)); + return Math.abs( + this.lastX1_ - this.startX1_ - (this.lastX2_ - this.startX2_) + ); } /** @@ -935,7 +959,8 @@ export class PinchRecognizer extends GestureRecognizer { * @private */ deltaY_() { - return Math.abs((this.lastY1_ - this.startY1_) - - (this.lastY2_ - this.startY2_)); + return Math.abs( + this.lastY1_ - this.startY1_ - (this.lastY2_ - this.startY2_) + ); } } diff --git a/src/gesture.js b/src/gesture.js index 49fa4d3e77dea..b3fb020f33603 100644 --- a/src/gesture.js +++ b/src/gesture.js @@ -20,10 +20,8 @@ import {devAssert} from './log'; import {findIndex} from './utils/array'; import {toWin} from './types'; - const PROP_ = '__AMP_Gestures'; - /** * A gesture object contains the type and data of the gesture such as * a tap or a double-tap or a swipe. See {@link GestureRecognizer} for @@ -52,7 +50,6 @@ export class Gesture { } } - /** * Gestures object manages all gestures on a particular element. It listens * to all pointer events and delegates them to individual gesture recognizers. @@ -61,7 +58,6 @@ export class Gesture { * between competing recognizers to decide which gesture should go forward. */ export class Gestures { - /** * Creates if not yet created and returns the shared Gestures instance for * the specified element. @@ -112,8 +108,10 @@ export class Gestures { this.wasEventing_ = false; /** @private {!Pass} */ - this.pass_ = new Pass(toWin(element.ownerDocument.defaultView), - this.doPass_.bind(this)); + this.pass_ = new Pass( + toWin(element.ownerDocument.defaultView), + this.doPass_.bind(this) + ); /** @private {!Observable} */ this.pointerDownObservable_ = new Observable(); @@ -405,12 +403,16 @@ export class Gestures { * @visibleForTesting */ signalEmit_(recognizer, data, event) { - devAssert(this.eventing_ == recognizer, - 'Recognizer is not currently allowed: %s', recognizer.getType()); + devAssert( + this.eventing_ == recognizer, + 'Recognizer is not currently allowed: %s', + recognizer.getType() + ); const overserver = this.overservers_[recognizer.getType()]; if (overserver) { - overserver.fire(new Gesture(recognizer.getType(), data, Date.now(), - event)); + overserver.fire( + new Gesture(recognizer.getType(), data, Date.now(), event) + ); } } @@ -424,8 +426,7 @@ export class Gestures { if (!cancelEvent) { const now = Date.now(); for (let i = 0; i < this.recognizers_.length; i++) { - if (this.ready_[i] || - (this.pending_[i] && this.pending_[i] >= now)) { + if (this.ready_[i] || (this.pending_[i] && this.pending_[i] >= now)) { cancelEvent = true; break; } @@ -540,7 +541,6 @@ export class Gestures { } } - /** * The gesture recognizer receives the pointer events from Gestures instance. * Based on these events, it can "recognize" the gesture it's responsible for, @@ -569,7 +569,6 @@ export class Gestures { * @template DATA */ export class GestureRecognizer { - /** * @param {string} type * @param {!Gestures} manager @@ -642,15 +641,13 @@ export class GestureRecognizer { * state. It will be in this state until it calls {@link signalEnd} or * the {@link acceptCancel} is called by the Gestures instance. */ - acceptStart() { - } + acceptStart() {} /** * The Gestures instance calls this method to reset the recognizer. At this * point the recognizer is in the initial waiting state. */ - acceptCancel() { - } + acceptCancel() {} /** * The Gestures instance calls this method for each "touchstart" event. If @@ -681,6 +678,5 @@ export class GestureRecognizer { * next touch series. * @param {!Event} unusedEvent */ - onTouchEnd(unusedEvent) { - } + onTouchEnd(unusedEvent) {} } diff --git a/src/get-bounding-client-rect.js b/src/get-bounding-client-rect.js index 5fe4dd48cba73..8e659b2f7d9ac 100644 --- a/src/get-bounding-client-rect.js +++ b/src/get-bounding-client-rect.js @@ -21,10 +21,7 @@ * @see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/106812/ */ -import { - LayoutRectDef, - layoutRectLtwh, -} from './layout-rect'; +import {LayoutRectDef, layoutRectLtwh} from './layout-rect'; import {isConnectedNode} from './dom'; const nativeClientRect = Element.prototype.getBoundingClientRect; @@ -50,7 +47,7 @@ function getBoundingClientRect() { function shouldInstall(win) { try { const div = win.document.createElement('div'); - const rect = div./*OK*/getBoundingClientRect(); + const rect = div./*OK*/ getBoundingClientRect(); return rect.top !== 0; } catch (e) { // IE 10 or less diff --git a/src/get-html.js b/src/get-html.js index 3b08af682d090..3f7a73c9ff54e 100644 --- a/src/get-html.js +++ b/src/get-html.js @@ -20,13 +20,35 @@ import {startsWith} from './string'; const excludedTags = ['script', 'style']; /** @type {!Array} */ -const allowedAmpTags = ['amp-accordion', 'amp-app-banner', 'amp-carousel', - 'amp-fit-text', 'amp-form', 'amp-selector', 'amp-sidebar']; +const allowedAmpTags = [ + 'amp-accordion', + 'amp-app-banner', + 'amp-carousel', + 'amp-fit-text', + 'amp-form', + 'amp-selector', + 'amp-sidebar', +]; /** @type {!Array} */ -const allowedAttributes = ['action', 'alt', 'class', 'disabled', 'height', - 'href', 'id', 'name', 'placeholder', 'readonly', 'src', 'tabindex', - 'title', 'type', 'value', 'width']; +const allowedAttributes = [ + 'action', + 'alt', + 'class', + 'disabled', + 'height', + 'href', + 'id', + 'name', + 'placeholder', + 'readonly', + 'src', + 'tabindex', + 'title', + 'type', + 'value', + 'width', +]; /** * Returns content of HTML node @@ -76,7 +98,6 @@ function appendToResult(node, attrs, result) { } } - /** * * @param {!Element} node @@ -92,7 +113,6 @@ function isApplicableNode(node) { } } - /** * * @param {!Element} node diff --git a/src/iframe-attributes.js b/src/iframe-attributes.js index 49720ece31a5b..b87175b0466c8 100644 --- a/src/iframe-attributes.js +++ b/src/iframe-attributes.js @@ -31,7 +31,11 @@ import {version} from './internal-version'; * @return {!JsonObject} */ export function getContextMetadata( - parentWindow, element, sentinel, attributes) { + parentWindow, + element, + sentinel, + attributes +) { const startTime = Date.now(); const width = element.getAttribute('width'); const height = element.getAttribute('height'); @@ -80,12 +84,14 @@ export function getContextMetadata( 'mode': getModeObject(), 'canary': isCanary(parentWindow), 'hidden': !viewer.isVisible(), - 'initialLayoutRect': layoutRect ? { - 'left': layoutRect.left, - 'top': layoutRect.top, - 'width': layoutRect.width, - 'height': layoutRect.height, - } : null, + 'initialLayoutRect': layoutRect + ? { + 'left': layoutRect.left, + 'top': layoutRect.top, + 'width': layoutRect.width, + 'height': layoutRect.height, + } + : null, 'initialIntersection': element.getIntersectionChangeEntry(), 'domFingerprint': DomFingerprint.generate(element), 'experimentToggles': experimentToggles(parentWindow), diff --git a/src/iframe-helper.js b/src/iframe-helper.js index 01cda232856f6..bea1abf180072 100644 --- a/src/iframe-helper.js +++ b/src/iframe-helper.js @@ -131,8 +131,10 @@ function getListenForEvents(parentWin, sentinel, origin, triggerWin) { const {contentWindow} = we.frame; if (!contentWindow) { setTimeout(dropListenSentinel, 0, listenSentinel); - } else if (triggerWin == contentWindow || - isDescendantWindow(contentWindow, triggerWin)) { + } else if ( + triggerWin == contentWindow || + isDescendantWindow(contentWindow, triggerWin) + ) { // 3p code path, we may accept messages from nested frames. windowEvents = we; break; @@ -202,10 +204,10 @@ function registerGlobalListenerIfNeeded(parentWin) { } const listenForEvents = getListenForEvents( - parentWin, - data['sentinel'], - event.origin, - event.source + parentWin, + data['sentinel'], + event.origin, + event.source ); if (!listenForEvents) { return; @@ -250,25 +252,28 @@ export function listenFor( callback, opt_is3P, opt_includingNestedWindows, - opt_allowOpaqueOrigin) { - + opt_allowOpaqueOrigin +) { devAssert(iframe.src, 'only iframes with src supported'); - devAssert(!iframe.parentNode, 'cannot register events on an attached ' + - 'iframe. It will cause hair-pulling bugs like #2942'); + devAssert( + !iframe.parentNode, + 'cannot register events on an attached ' + + 'iframe. It will cause hair-pulling bugs like #2942' + ); devAssert(callback); const parentWin = iframe.ownerDocument.defaultView; registerGlobalListenerIfNeeded(parentWin); const listenForEvents = getOrCreateListenForEvents( - parentWin, - iframe, - opt_is3P + parentWin, + iframe, + opt_is3P ); const iframeOrigin = parseUrlDeprecated(iframe.src).origin; - let events = listenForEvents[typeOfMessage] || - (listenForEvents[typeOfMessage] = []); + let events = + listenForEvents[typeOfMessage] || (listenForEvents[typeOfMessage] = []); let unlisten; let listener = function(data, source, origin) { @@ -304,7 +309,7 @@ export function listenFor( events.push(listener); - return unlisten = function() { + return (unlisten = function() { if (listener) { const index = events.indexOf(listener); if (index > -1) { @@ -316,7 +321,7 @@ export function listenFor( events = null; callback = null; } - }; + }); } /** @@ -335,12 +340,17 @@ export function listenForOncePromise(iframe, typeOfMessages, opt_is3P) { return new Promise(resolve => { for (let i = 0; i < typeOfMessages.length; i++) { const message = typeOfMessages[i]; - const unlisten = listenFor(iframe, message, (data, source, origin) => { - for (let i = 0; i < unlistenList.length; i++) { - unlistenList[i](); - } - resolve({data, source, origin}); - }, opt_is3P); + const unlisten = listenFor( + iframe, + message, + (data, source, origin) => { + for (let i = 0; i < unlistenList.length; i++) { + unlistenList[i](); + } + resolve({data, source, origin}); + }, + opt_is3P + ); unlistenList.push(unlisten); } }); @@ -355,9 +365,13 @@ export function listenForOncePromise(iframe, typeOfMessages, opt_is3P) { * @param {boolean=} opt_is3P set to true if the iframe is 3p. */ export function postMessage(iframe, type, object, targetOrigin, opt_is3P) { - postMessageToWindows(iframe, - [{win: iframe.contentWindow, origin: targetOrigin}], type, object, - opt_is3P); + postMessageToWindows( + iframe, + [{win: iframe.contentWindow, origin: targetOrigin}], + type, + object, + opt_is3P + ); } /** @@ -384,7 +398,7 @@ export function postMessageToWindows(iframe, targets, type, object, opt_is3P) { } for (let i = 0; i < targets.length; i++) { const target = targets[i]; - target.win./*OK*/postMessage(payload, target.origin); + target.win./*OK*/ postMessage(payload, target.origin); } } @@ -409,11 +423,15 @@ function getSentinel_(iframe, opt_is3P) { export function parseIfNeeded(data) { if (typeof data == 'string') { if (data.charAt(0) == '{') { - data = tryParseJson(data, e => { - dev().warn('IFRAME-HELPER', + data = + tryParseJson(data, e => { + dev().warn( + 'IFRAME-HELPER', 'Postmessage could not be parsed. ' + - 'Is it in a valid JSON format?', e); - }) || null; + 'Is it in a valid JSON format?', + e + ); + }) || null; } else if (isAmpMessage(data)) { data = deserializeMessage(data); } else { @@ -423,8 +441,6 @@ export function parseIfNeeded(data) { return /** @type {?JsonObject} */ (data); } - - /** * Manages a postMessage API for an iframe with a subscription message and * a way to broadcast messages to all subscribed windows, which @@ -447,16 +463,21 @@ export class SubscriptionApi { this.clientWindows_ = []; /** @private @const {!UnlistenDef} */ - this.unlisten_ = listenFor(this.iframe_, type, (data, source, origin) => { - // This message might be from any window within the iframe, we need - // to keep track of which windows want to be sent updates. - if (!this.clientWindows_.some(entry => entry.win == source)) { - this.clientWindows_.push({win: source, origin}); - } - requestCallback(data, source, origin); - }, this.is3p_, - // For 3P frames we also allow nested frames within them to subscribe.. - this.is3p_ /* opt_includingNestedWindows */); + this.unlisten_ = listenFor( + this.iframe_, + type, + (data, source, origin) => { + // This message might be from any window within the iframe, we need + // to keep track of which windows want to be sent updates. + if (!this.clientWindows_.some(entry => entry.win == source)) { + this.clientWindows_.push({win: source, origin}); + } + requestCallback(data, source, origin); + }, + this.is3p_, + // For 3P frames we also allow nested frames within them to subscribe.. + this.is3p_ /* opt_includingNestedWindows */ + ); } /** @@ -468,11 +489,12 @@ export class SubscriptionApi { // Remove clients that have been removed from the DOM. remove(this.clientWindows_, client => !client.win.parent); postMessageToWindows( - this.iframe_, - this.clientWindows_, - type, - data, - this.is3p_); + this.iframe_, + this.clientWindows_, + type, + data, + this.is3p_ + ); } /** @@ -500,12 +522,7 @@ export function looksLikeTrackingIframe(element) { // Most common ad sizes // Array of [width, height] pairs. -const adSizes = [ - [300, 250], - [320, 50], - [300, 50], - [320, 100], -]; +const adSizes = [[300, 250], [320, 50], [300, 50], [320, 100]]; /** * Guess whether this element might be an ad. @@ -564,7 +581,8 @@ export function canInspectWindow(win) { // win['test'] could be truthy but not true the compiler shouldn't be able // to optimize this check away. return !!win.location.href && (win['test'] || true); - } catch (unusedErr) { // eslint-disable-line no-unused-vars + } catch (unusedErr) { + // eslint-disable-line no-unused-vars return false; } } diff --git a/src/iframe-video.js b/src/iframe-video.js index 6f8c0a37d5822..a0191580ec665 100644 --- a/src/iframe-video.js +++ b/src/iframe-video.js @@ -21,7 +21,6 @@ import {isArray, isObject} from './types'; import {startsWith} from './string'; import {tryParseJson} from './json'; - /** @enum {string} */ export const SandboxOptions = { ALLOW_SCRIPTS: 'allow-scripts', @@ -32,7 +31,6 @@ export const SandboxOptions = { 'allow-top-navigation-by-user-activation', }; - /** * @param {!Event} event * @param {?Element} iframe @@ -49,7 +47,6 @@ export function originMatches(event, iframe, host) { return host.test(event.origin); } - /** * Re-dispatches an event received from postMessage as an event in the host * document. @@ -70,7 +67,6 @@ export function redispatch(element, event, events) { return true; } - /** * @param {!./base-element.BaseElement} video * @param {string} src @@ -80,8 +76,9 @@ export function redispatch(element, event, events) { */ export function createFrameFor(video, src, opt_name, opt_sandbox) { const {element} = video; - const frame = - htmlFor(element)``; + const frame = htmlFor( + element + )``; if (opt_name) { frame.setAttribute('name', opt_name); @@ -103,7 +100,6 @@ export function createFrameFor(video, src, opt_name, opt_sandbox) { return frame; } - /** * @param {?} anything * @return {boolean} @@ -112,11 +108,11 @@ export function isJsonOrObj(anything) { if (!anything) { return false; } - return isObject(anything) || - startsWith(/** @type {string} */ (anything), '{'); + return ( + isObject(anything) || startsWith(/** @type {string} */ (anything), '{') + ); } - /** * @param {?JsonObject|string|undefined} objOrStr * @return {?JsonObject|undefined} @@ -128,7 +124,6 @@ export function objOrParseJson(objOrStr) { return tryParseJson(objOrStr); } - /** * @param {boolean} isMuted * @return {string} @@ -137,7 +132,6 @@ export function mutedOrUnmutedEvent(isMuted) { return isMuted ? VideoEvents.MUTED : VideoEvents.UNMUTED; } - /** * TEMPORARY workaround for M72-M74 user-activation breakage. * If this method is still here in May 2019, please ping @aghassemi diff --git a/src/impression.js b/src/impression.js index 0f7dca48a78a3..3a6946f16f6cb 100644 --- a/src/impression.js +++ b/src/impression.js @@ -30,10 +30,7 @@ const TIMEOUT_VALUE = 8000; let trackImpressionPromise = null; -const DEFAULT_APPEND_URL_PARAM = [ - 'gclid', - 'gclsrc', -]; +const DEFAULT_APPEND_URL_PARAM = ['gclid', 'gclsrc']; /** * These domains are trusted with more sensitive viewer operations such as @@ -57,8 +54,7 @@ const TRUSTED_REFERRER_HOSTS = [ * @return {!Promise} */ export function getTrackImpressionPromise() { - return userAssert(trackImpressionPromise, - 'E#19457 trackImpressionPromise'); + return userAssert(trackImpressionPromise, 'E#19457 trackImpressionPromise'); } /** @@ -78,36 +74,44 @@ export function maybeTrackImpression(win) { const deferred = new Deferred(); const {promise, resolve: resolveImpression} = deferred; - trackImpressionPromise = Services.timerFor(win).timeoutPromise(TIMEOUT_VALUE, - promise, 'TrackImpressionPromise timeout').catch(error => { - dev().warn('IMPRESSION', error); - }); + trackImpressionPromise = Services.timerFor(win) + .timeoutPromise(TIMEOUT_VALUE, promise, 'TrackImpressionPromise timeout') + .catch(error => { + dev().warn('IMPRESSION', error); + }); const viewer = Services.viewerForDoc(win.document.documentElement); const isTrustedViewerPromise = viewer.isTrustedViewer(); - const isTrustedReferrerPromise = viewer.getReferrerUrl().then( - referrer => isTrustedReferrer(referrer)); - Promise.all([ - isTrustedViewerPromise, - isTrustedReferrerPromise, - ]).then(results => { - const isTrustedViewer = results[0]; - const isTrustedReferrer = results[1]; - // Enable the feature in the case of trusted viewer, - // or trusted referrer - // or with experiment turned on - if (!isTrustedViewer && !isTrustedReferrer && !isExperimentOn(win, 'alp')) { - resolveImpression(); - return; + const isTrustedReferrerPromise = viewer + .getReferrerUrl() + .then(referrer => isTrustedReferrer(referrer)); + Promise.all([isTrustedViewerPromise, isTrustedReferrerPromise]).then( + results => { + const isTrustedViewer = results[0]; + const isTrustedReferrer = results[1]; + // Enable the feature in the case of trusted viewer, + // or trusted referrer + // or with experiment turned on + if ( + !isTrustedViewer && + !isTrustedReferrer && + !isExperimentOn(win, 'alp') + ) { + resolveImpression(); + return; + } + + const replaceUrlPromise = handleReplaceUrl(win); + const clickUrlPromise = handleClickUrl(win); + + Promise.all([replaceUrlPromise, clickUrlPromise]).then( + () => { + resolveImpression(); + }, + () => {} + ); } - - const replaceUrlPromise = handleReplaceUrl(win); - const clickUrlPromise = handleClickUrl(win); - - Promise.all([replaceUrlPromise, clickUrlPromise]).then(() => { - resolveImpression(); - }, () => {}); - }); + ); } /** @@ -146,16 +150,20 @@ function handleReplaceUrl(win) { } // request async replaceUrl is viewer support getReplaceUrl. - return viewer.sendMessageAwaitResponse('getReplaceUrl', /* data */ undefined) - .then(response => { + return viewer + .sendMessageAwaitResponse('getReplaceUrl', /* data */ undefined) + .then( + response => { if (!response || typeof response != 'object') { dev().warn('IMPRESSION', 'get invalid replaceUrl response'); return; } viewer.replaceUrl(response['replaceUrl'] || null); - }, err => { + }, + err => { dev().warn('IMPRESSION', 'Error request replaceUrl from viewer', err); - }); + } + ); } /** @@ -186,9 +194,11 @@ function handleClickUrl(win) { } if (clickUrl.indexOf('https://') != 0) { - user().warn('IMPRESSION', - 'click fragment param should start with https://. Found ', - clickUrl); + user().warn( + 'IMPRESSION', + 'click fragment param should start with https://. Found ', + clickUrl + ); return Promise.resolve(); } @@ -200,13 +210,17 @@ function handleClickUrl(win) { } // TODO(@zhouyx) need test with a real response. - return viewer.whenFirstVisible().then(() => { - return invoke(win, dev().assertString(clickUrl)); - }).then(response => { - applyResponse(win, response); - }).catch(err => { - user().warn('IMPRESSION', 'Error on request clickUrl: ', err); - }); + return viewer + .whenFirstVisible() + .then(() => { + return invoke(win, dev().assertString(clickUrl)); + }) + .then(response => { + applyResponse(win, response); + }) + .catch(err => { + user().warn('IMPRESSION', 'Error on request clickUrl: ', err); + }); } /** @@ -219,17 +233,19 @@ function invoke(win, clickUrl) { if (getMode().localDev && !getMode().test) { clickUrl = 'http://localhost:8000/impression-proxy?url=' + clickUrl; } - return Services.xhrFor(win).fetchJson(clickUrl, { - credentials: 'include', - // All origins are allows to send these requests. - requireAmpResponseSourceOrigin: false, - }).then(res => { - // Treat 204 no content response specially - if (res.status == 204) { - return null; - } - return res.json(); - }); + return Services.xhrFor(win) + .fetchJson(clickUrl, { + credentials: 'include', + // All origins are allows to send these requests. + requireAmpResponseSourceOrigin: false, + }) + .then(res => { + // Treat 204 no content response specially + if (res.status == 204) { + return null; + } + return res.json(); + }); } /** @@ -280,8 +296,9 @@ function applyResponse(win, response) { */ export function shouldAppendExtraParams(ampdoc) { return ampdoc.whenReady().then(() => { - return !!ampdoc.getBody().querySelector( - 'amp-analytics[type=googleanalytics]'); + return !!ampdoc + .getBody() + .querySelector('amp-analytics[type=googleanalytics]'); }); } @@ -329,9 +346,10 @@ function getQueryParamUrl(params) { let url = ''; for (let i = 0; i < params.length; i++) { const param = params[i]; - url += (i == 0) ? - `${param}=QUERY_PARAM(${param})` : - `&${param}=QUERY_PARAM(${param})`; + url += + i == 0 + ? `${param}=QUERY_PARAM(${param})` + : `&${param}=QUERY_PARAM(${param})`; } return url; } diff --git a/src/inabox/amp-inabox-lite.js b/src/inabox/amp-inabox-lite.js index 149f7f00d84a0..7877f712cbe61 100644 --- a/src/inabox/amp-inabox-lite.js +++ b/src/inabox/amp-inabox-lite.js @@ -24,11 +24,7 @@ import { installGlobalNavigationHandlerForDoc, } from '../service/navigation'; import {Services} from '../services'; -import { - adopt, - installBuiltins, - installRuntimeServices, -} from '../runtime'; +import {adopt, installBuiltins, installRuntimeServices} from '../runtime'; import {cssText} from '../../build/css'; import {fontStylesheetTimeout} from '../font-stylesheet-timeout'; import {getMode} from '../mode'; @@ -57,9 +53,7 @@ import {installResourcesServiceForDoc} from '../service/resources-impl'; import {installStandardActionsForDoc} from '../service/standard-actions-impl'; import {installStorageServiceForDoc} from '../service/storage-impl'; import {installUrlForDoc} from '../service/url-impl'; -import { - installUrlReplacementsServiceForDoc, -} from '../service/url-replacements-impl'; +import {installUrlReplacementsServiceForDoc} from '../service/url-replacements-impl'; getMode(self).runtime = 'inabox'; @@ -87,37 +81,45 @@ const ampdoc = ampdocService.getAmpDoc(self.document); installPerformanceService(self); // TODO: to be removed self.document.documentElement.classList.add('i-amphtml-inabox'); -const fullCss = cssText - + 'html.i-amphtml-inabox{width:100%!important;height:100%!important}'; -installStylesForDoc(ampdoc, fullCss, () => { - // Core services. - installRuntimeServices(self); - fontStylesheetTimeout(self); - installIframeMessagingClient(self); // TODO: to be removed - installAmpdocServices(ampdoc); - // We need the core services (viewer/resources) to start instrumenting - registerIniLoadListener(ampdoc); +const fullCss = + cssText + 'html.i-amphtml-inabox{width:100%!important;height:100%!important}'; +installStylesForDoc( + ampdoc, + fullCss, + () => { + // Core services. + installRuntimeServices(self); + fontStylesheetTimeout(self); + installIframeMessagingClient(self); // TODO: to be removed + installAmpdocServices(ampdoc); + // We need the core services (viewer/resources) to start instrumenting + registerIniLoadListener(ampdoc); - // Builtins. - installBuiltins(self); - adopt(self); + // Builtins. + installBuiltins(self); + adopt(self); - // Pre-stub already known elements. - stubElementsForDoc(ampdoc); + // Pre-stub already known elements. + stubElementsForDoc(ampdoc); - Navigation.installAnchorClickInterceptor(ampdoc, self); - makeBodyVisible(self.document); // TODO: to be simplified + Navigation.installAnchorClickInterceptor(ampdoc, self); + makeBodyVisible(self.document); // TODO: to be simplified - Services.resourcesForDoc(ampdoc).ampInitComplete(); -}, /* opt_isRuntimeCss */ true, /* opt_ext */ 'amp-runtime'); + Services.resourcesForDoc(ampdoc).ampInitComplete(); + }, + /* opt_isRuntimeCss */ true, + /* opt_ext */ 'amp-runtime' +); // Output a message to the console and add an attribute to the // tag to give some information that can be used in error reports. // (At least by sophisticated users). if (self.console) { - (console.info || console.log).call(console, - `Powered by AMP ⚡ HTML – Version ${version()}`, - self.location.href); + (console.info || console.log).call( + console, + `Powered by AMP ⚡ HTML – Version ${version()}`, + self.location.href + ); } self.document.documentElement.setAttribute('amp-version', version()); diff --git a/src/inabox/amp-inabox.js b/src/inabox/amp-inabox.js index d6d7536fdf720..247814df18774 100644 --- a/src/inabox/amp-inabox.js +++ b/src/inabox/amp-inabox.js @@ -80,56 +80,65 @@ startupChunk(self.document, function initial() { perf.tick('is'); self.document.documentElement.classList.add('i-amphtml-inabox'); - const fullCss = cssText - + 'html.i-amphtml-inabox{width:100%!important;height:100%!important}'; - installStylesForDoc(ampdoc, fullCss, () => { - startupChunk(self.document, function services() { - // Core services. - installRuntimeServices(self); - fontStylesheetTimeout(self); - installIframeMessagingClient(self); - // Install inabox specific Viewport service before - // runtime tries to install the normal one. - installViewerServiceForDoc(ampdoc); - installInaboxViewportService(ampdoc); - installAmpdocServices(ampdoc); - // We need the core services (viewer/resources) to start instrumenting - perf.coreServicesAvailable(); - maybeTrackImpression(self); - registerIniLoadListener(ampdoc); - }); - startupChunk(self.document, function builtins() { - // Builtins. - installBuiltins(self); - }); - startupChunk(self.document, function adoptWindow() { - adopt(self); - }); - startupChunk(self.document, function stub() { - // Pre-stub already known elements. - stubElementsForDoc(ampdoc); - }); - startupChunk(self.document, function final() { - Navigation.installAnchorClickInterceptor(ampdoc, self); - maybeValidate(self); - makeBodyVisible(self.document); - }); - startupChunk(self.document, function finalTick() { - perf.tick('e_is'); - Services.resourcesForDoc(ampdoc).ampInitComplete(); - // TODO(erwinm): move invocation of the `flush` method when we have the - // new ticks in place to batch the ticks properly. - perf.flush(); - }); - }, /* opt_isRuntimeCss */ true, /* opt_ext */ 'amp-runtime'); + const fullCss = + cssText + + 'html.i-amphtml-inabox{width:100%!important;height:100%!important}'; + installStylesForDoc( + ampdoc, + fullCss, + () => { + startupChunk(self.document, function services() { + // Core services. + installRuntimeServices(self); + fontStylesheetTimeout(self); + installIframeMessagingClient(self); + // Install inabox specific Viewport service before + // runtime tries to install the normal one. + installViewerServiceForDoc(ampdoc); + installInaboxViewportService(ampdoc); + installAmpdocServices(ampdoc); + // We need the core services (viewer/resources) to start instrumenting + perf.coreServicesAvailable(); + maybeTrackImpression(self); + registerIniLoadListener(ampdoc); + }); + startupChunk(self.document, function builtins() { + // Builtins. + installBuiltins(self); + }); + startupChunk(self.document, function adoptWindow() { + adopt(self); + }); + startupChunk(self.document, function stub() { + // Pre-stub already known elements. + stubElementsForDoc(ampdoc); + }); + startupChunk(self.document, function final() { + Navigation.installAnchorClickInterceptor(ampdoc, self); + maybeValidate(self); + makeBodyVisible(self.document); + }); + startupChunk(self.document, function finalTick() { + perf.tick('e_is'); + Services.resourcesForDoc(ampdoc).ampInitComplete(); + // TODO(erwinm): move invocation of the `flush` method when we have the + // new ticks in place to batch the ticks properly. + perf.flush(); + }); + }, + /* opt_isRuntimeCss */ true, + /* opt_ext */ 'amp-runtime' + ); }); // Output a message to the console and add an attribute to the // tag to give some information that can be used in error reports. // (At least by sophisticated users). if (self.console) { - (console.info || console.log).call(console, - `Powered by AMP ⚡ HTML – Version ${version()}`, - self.location.href); + (console.info || console.log).call( + console, + `Powered by AMP ⚡ HTML – Version ${version()}`, + self.location.href + ); } self.document.documentElement.setAttribute('amp-version', version()); diff --git a/src/inabox/host-services.js b/src/inabox/host-services.js index 69e9a538f4049..e733f4825e79b 100644 --- a/src/inabox/host-services.js +++ b/src/inabox/host-services.js @@ -55,7 +55,6 @@ export let HostServiceError; * rejectXXXServiceForDoc() when there is a failure. */ export class HostServices { - /** * @param {!Element|!../service/ampdoc-impl.AmpDoc} elementOrAmpDoc * @return {boolean} @@ -70,8 +69,10 @@ export class HostServices { * @return {!Promise} */ static visibilityForDoc(elementOrAmpDoc) { - return /** @type {!Promise} */ ( - getServicePromiseForDoc(elementOrAmpDoc, ServiceNames.VISIBILITY)); + return /** @type {!Promise} */ (getServicePromiseForDoc( + elementOrAmpDoc, + ServiceNames.VISIBILITY + )); } /** @@ -79,8 +80,12 @@ export class HostServices { * @param {function(new:Object, !../service/ampdoc-impl.AmpDoc)} impl */ static installVisibilityServiceForDoc(elementOrAmpDoc, impl) { - registerServiceBuilderForDoc(elementOrAmpDoc, - ServiceNames.VISIBILITY, impl, /* opt_instantiate */ true); + registerServiceBuilderForDoc( + elementOrAmpDoc, + ServiceNames.VISIBILITY, + impl, + /* opt_instantiate */ true + ); } /** @@ -96,8 +101,10 @@ export class HostServices { * @return {!Promise} */ static fullscreenForDoc(elementOrAmpDoc) { - return /** @type {!Promise} */ ( - getServicePromiseForDoc(elementOrAmpDoc, ServiceNames.FULLSCREEN)); + return /** @type {!Promise} */ (getServicePromiseForDoc( + elementOrAmpDoc, + ServiceNames.FULLSCREEN + )); } /** @@ -105,8 +112,12 @@ export class HostServices { * @param {function(new:Object, !../service/ampdoc-impl.AmpDoc)} impl */ static installFullscreenServiceForDoc(elementOrAmpDoc, impl) { - registerServiceBuilderForDoc(elementOrAmpDoc, - ServiceNames.FULLSCREEN, impl, /* opt_instantiate */ true); + registerServiceBuilderForDoc( + elementOrAmpDoc, + ServiceNames.FULLSCREEN, + impl, + /* opt_instantiate */ true + ); } /** @@ -122,8 +133,10 @@ export class HostServices { * @return {!Promise} */ static exitForDoc(elementOrAmpDoc) { - return /** @type {!Promise} */ ( - getServicePromiseForDoc(elementOrAmpDoc, ServiceNames.EXIT)); + return /** @type {!Promise} */ (getServicePromiseForDoc( + elementOrAmpDoc, + ServiceNames.EXIT + )); } /** @@ -131,8 +144,12 @@ export class HostServices { * @param {function(new:Object, !../service/ampdoc-impl.AmpDoc)} impl */ static installExitServiceForDoc(elementOrAmpDoc, impl) { - registerServiceBuilderForDoc(elementOrAmpDoc, - ServiceNames.EXIT, impl, /* opt_instantiate */ true); + registerServiceBuilderForDoc( + elementOrAmpDoc, + ServiceNames.EXIT, + impl, + /* opt_instantiate */ true + ); } /** @@ -151,14 +168,12 @@ export class HostServices { * @interface */ export class VisibilityInterface { - /** * Register a callback for visibility change events. * * @param {function(!VisibilityDataDef)} unusedCallback */ - onVisibilityChange(unusedCallback) { - } + onVisibilityChange(unusedCallback) {} } /** @@ -172,7 +187,6 @@ export class VisibilityInterface { */ export let VisibilityDataDef; - /** * FullscreenInterface defines interface provided by host to enable/disable * fullscreen mode. @@ -180,7 +194,6 @@ export let VisibilityDataDef; * @interface */ export class FullscreenInterface { - /** * Request to expand the given element to fullscreen overlay. * @@ -188,8 +201,7 @@ export class FullscreenInterface { * @return {!Promise} promise resolves to a boolean * indicating if the request was fulfilled */ - enterFullscreenOverlay(unusedTargetElement) { - } + enterFullscreenOverlay(unusedTargetElement) {} /** * Request to exit from fullscreen overlay. @@ -198,8 +210,7 @@ export class FullscreenInterface { * @return {!Promise} promise resolves to a boolean * indicating if the request was fulfilled */ - exitFullscreenOverlay(unusedTargetElement) { - } + exitFullscreenOverlay(unusedTargetElement) {} } /** @@ -208,7 +219,6 @@ export class FullscreenInterface { * @interface */ export class ExitInterface { - /** * Request to navigate to URL. * @@ -216,6 +226,5 @@ export class ExitInterface { * @return {!Promise} promise resolves to a boolean * indicating if the request was fulfilled */ - openUrl(unusedUrl) { - } + openUrl(unusedUrl) {} } diff --git a/src/inabox/inabox-iframe-messaging-client.js b/src/inabox/inabox-iframe-messaging-client.js index 8c5b89fbd7ee9..4b363f941e3a9 100644 --- a/src/inabox/inabox-iframe-messaging-client.js +++ b/src/inabox/inabox-iframe-messaging-client.js @@ -23,18 +23,22 @@ import {tryParseJson} from '../json'; * @return {!../../3p/iframe-messaging-client.IframeMessagingClient} */ export function iframeMessagingClientFor(win) { - return /** @type {!../../3p/iframe-messaging-client.IframeMessagingClient} */( - getService(win, 'iframeMessagingClient')); + return /** @type {!../../3p/iframe-messaging-client.IframeMessagingClient} */ (getService( + win, + 'iframeMessagingClient' + )); } /** * @param {!Window} win */ export function installIframeMessagingClient(win) { - registerServiceBuilder(win, - 'iframeMessagingClient', - createIframeMessagingClient.bind(null, win), - /* opt_instantiate */ true); + registerServiceBuilder( + win, + 'iframeMessagingClient', + createIframeMessagingClient.bind(null, win), + /* opt_instantiate */ true + ); } /** diff --git a/src/inabox/inabox-viewport.js b/src/inabox/inabox-viewport.js index 652b237d2284f..1dc609c1f38da 100644 --- a/src/inabox/inabox-viewport.js +++ b/src/inabox/inabox-viewport.js @@ -24,10 +24,7 @@ import {canInspectWindow} from '../iframe-helper'; import {dev, devAssert} from '../log'; import {iframeMessagingClientFor} from './inabox-iframe-messaging-client'; import {isExperimentOn} from '../experiments'; -import { - layoutRectLtwh, - moveLayoutRect, -} from '../layout-rect'; +import {layoutRectLtwh, moveLayoutRect} from '../layout-rect'; import {px, resetStyles, setImportantStyles} from '../style'; import {registerServiceBuilderForDoc} from '../service'; import {throttle} from '../utils/rate-limit'; @@ -44,30 +41,32 @@ const MIN_EVENT_INTERVAL = 100; * @visibleForTesting */ export function prepareBodyForOverlay(win, bodyElement) { - return Services.vsyncFor(win).runPromise({ - measure: state => { - state.width = win./*OK*/innerWidth; - state.height = win./*OK*/innerHeight; - }, - mutate: state => { - // We need to override runtime-level !important rules - setImportantStyles(bodyElement, { - 'background': 'transparent', - 'left': '50%', - 'top': '50%', - 'right': 'auto', - 'bottom': 'auto', - 'position': 'absolute', - 'height': px(state.height), - 'width': px(state.width), - 'margin-top': px(-state.height / 2), - 'margin-left': px(-state.width / 2), - }); + return Services.vsyncFor(win).runPromise( + { + measure: state => { + state.width = win./*OK*/ innerWidth; + state.height = win./*OK*/ innerHeight; + }, + mutate: state => { + // We need to override runtime-level !important rules + setImportantStyles(bodyElement, { + 'background': 'transparent', + 'left': '50%', + 'top': '50%', + 'right': 'auto', + 'bottom': 'auto', + 'position': 'absolute', + 'height': px(state.height), + 'width': px(state.width), + 'margin-top': px(-state.height / 2), + 'margin-left': px(-state.width / 2), + }); + }, }, - }, {}); + {} + ); } - /** * @param {!Window} win * @param {!Element} bodyElement @@ -91,7 +90,6 @@ export function resetBodyForOverlay(win, bodyElement) { }); } - /** * Implementation of ViewportBindingDef that works inside an non-scrollable * iframe box by listening to host doc for position and resize updates. @@ -100,7 +98,6 @@ export function resetBodyForOverlay(win, bodyElement) { * @implements {ViewportBindingDef} */ export class ViewportBindingInabox { - /** * @param {!Window} win */ @@ -114,8 +111,8 @@ export class ViewportBindingInabox { /** @private @const {!Observable} */ this.resizeObservable_ = new Observable(); - const boxWidth = win./*OK*/innerWidth; - const boxHeight = win./*OK*/innerHeight; + const boxWidth = win./*OK*/ innerWidth; + const boxHeight = win./*OK*/ innerHeight; /** * The current viewport rect. @@ -144,9 +141,13 @@ export class ViewportBindingInabox { this.requestPositionPromise_ = null; /** @private {function()} */ - this.fireScrollThrottle_ = throttle(this.win, () => { - this.scrollObservable_.fire(); - }, MIN_EVENT_INTERVAL); + this.fireScrollThrottle_ = throttle( + this.win, + () => { + this.scrollObservable_.fire(); + }, + MIN_EVENT_INTERVAL + ); /** @private @const {boolean} */ this.useLayers_ = isExperimentOn(this.win, 'layers'); @@ -162,8 +163,10 @@ export class ViewportBindingInabox { /** @override */ connect() { - if (isExperimentOn(this.win, 'inabox-viewport-friendly') && - canInspectWindow(this.win.top)) { + if ( + isExperimentOn(this.win, 'inabox-viewport-friendly') && + canInspectWindow(this.win.top) + ) { this.listenForPositionSameDomain(); } else { this.listenForPosition_(); @@ -173,11 +176,13 @@ export class ViewportBindingInabox { /** @private */ listenForPosition_() { this.iframeClient_.makeRequest( - MessageType.SEND_POSITIONS, MessageType.POSITION, - data => { - dev().fine(TAG, 'Position changed: ', data); - this.updateLayoutRects_(data['viewportRect'], data['targetRect']); - }); + MessageType.SEND_POSITIONS, + MessageType.POSITION, + data => { + dev().fine(TAG, 'Position changed: ', data); + this.updateLayoutRects_(data['viewportRect'], data['targetRect']); + } + ); } /** @visibleForTesting */ @@ -189,17 +194,17 @@ export class ViewportBindingInabox { if (this.topWindowPositionObserver_) { return Promise.resolve(); } - return Services.resourcesPromiseForDoc(this.win.document.documentElement) - .then(() => { - this.topWindowPositionObserver_ = new PositionObserver(this.win.top); - this.unobserveFunction_ = this.topWindowPositionObserver_.observe( - /** @type {!HTMLIFrameElement} */(this.win.frameElement), - data => { - this.updateLayoutRects_( - data['viewportRect'], - data['targetRect']); - }); - }); + return Services.resourcesPromiseForDoc( + this.win.document.documentElement + ).then(() => { + this.topWindowPositionObserver_ = new PositionObserver(this.win.top); + this.unobserveFunction_ = this.topWindowPositionObserver_.observe( + /** @type {!HTMLIFrameElement} */ (this.win.frameElement), + data => { + this.updateLayoutRects_(data['viewportRect'], data['targetRect']); + } + ); + }); } /** @@ -221,17 +226,18 @@ export class ViewportBindingInabox { /** @override */ getLayoutRect(el) { - const b = el./*OK*/getBoundingClientRect(); + const b = el./*OK*/ getBoundingClientRect(); let {left, top} = b; if (this.useLayers_) { left -= this.viewportRect_.left; top -= this.viewportRect_.top; } return layoutRectLtwh( - Math.round(left + this.boxRect_.left), - Math.round(top + this.boxRect_.top), - Math.round(b.width), - Math.round(b.height)); + Math.round(left + this.boxRect_.left), + Math.round(top + this.boxRect_.top), + Math.round(b.width), + Math.round(b.height) + ); } /** @override */ @@ -286,8 +292,11 @@ export class ViewportBindingInabox { return; } - const boxRect = moveLayoutRect(positionRect, this.viewportRect_.left, - this.viewportRect_.top); + const boxRect = moveLayoutRect( + positionRect, + this.viewportRect_.left, + this.viewportRect_.top + ); if (isChanged(boxRect, this.boxRect_)) { dev().fine(TAG, 'Updating viewport box rect: ', boxRect); @@ -324,36 +333,41 @@ export class ViewportBindingInabox { /** @override */ getRootClientRectAsync() { - if (isExperimentOn(this.win, 'inabox-viewport-friendly') && - canInspectWindow(this.win.top)) { + if ( + isExperimentOn(this.win, 'inabox-viewport-friendly') && + canInspectWindow(this.win.top) + ) { // Set up the listener if we haven't already. return this.listenForPositionSameDomain().then(() => this.topWindowPositionObserver_.getTargetRect( - /** @type {!HTMLIFrameElement} */(this.win.frameElement))); + /** @type {!HTMLIFrameElement} */ (this.win.frameElement) + ) + ); } if (!this.requestPositionPromise_) { this.requestPositionPromise_ = new Promise(resolve => { this.iframeClient_.requestOnce( - MessageType.SEND_POSITIONS, MessageType.POSITION, - data => { - this.requestPositionPromise_ = null; - devAssert(data.targetRect, 'Host should send targetRect'); - resolve(data.targetRect); - } + MessageType.SEND_POSITIONS, + MessageType.POSITION, + data => { + this.requestPositionPromise_ = null; + devAssert(data.targetRect, 'Host should send targetRect'); + resolve(data.targetRect); + } ); }); } return this.requestPositionPromise_; } - /** * @return {!Promise} * @private */ tryToEnterOverlayMode_() { - return this.prepareBodyForOverlay_() - .then(() => this.requestFullOverlayFrame_()); + return this.prepareBodyForOverlay_().then(() => + this.requestFullOverlayFrame_() + ); } /** @@ -361,8 +375,9 @@ export class ViewportBindingInabox { * @private */ leaveOverlayMode_() { - return this.requestCancelFullOverlayFrame_() - .then(() => this.resetBodyForOverlay_()); + return this.requestCancelFullOverlayFrame_().then(() => + this.resetBodyForOverlay_() + ); } /** @@ -390,17 +405,18 @@ export class ViewportBindingInabox { requestFullOverlayFrame_() { return new Promise((resolve, reject) => { const unlisten = this.iframeClient_.makeRequest( - MessageType.FULL_OVERLAY_FRAME, - MessageType.FULL_OVERLAY_FRAME_RESPONSE, - response => { - unlisten(); - if (response['success']) { - this.updateBoxRect_(response['boxRect']); - resolve(); - } else { - reject('Request to open lightbox rejected by host document'); - } - }); + MessageType.FULL_OVERLAY_FRAME, + MessageType.FULL_OVERLAY_FRAME_RESPONSE, + response => { + unlisten(); + if (response['success']) { + this.updateBoxRect_(response['boxRect']); + resolve(); + } else { + reject('Request to open lightbox rejected by host document'); + } + } + ); }); } @@ -411,13 +427,14 @@ export class ViewportBindingInabox { requestCancelFullOverlayFrame_() { return new Promise(resolve => { const unlisten = this.iframeClient_.makeRequest( - MessageType.CANCEL_FULL_OVERLAY_FRAME, - MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, - response => { - unlisten(); - this.updateBoxRect_(response['boxRect']); - resolve(); - }); + MessageType.CANCEL_FULL_OVERLAY_FRAME, + MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, + response => { + unlisten(); + this.updateBoxRect_(response['boxRect']); + resolve(); + } + ); }); } @@ -433,19 +450,43 @@ export class ViewportBindingInabox { } } - /** @override */ updatePaddingTop() {/* no-op */} - /** @override */ hideViewerHeader() {/* no-op */} - /** @override */ showViewerHeader() {/* no-op */} - /** @override */ disableScroll() {/* no-op */} - /** @override */ resetScroll() {/* no-op */} - /** @override */ ensureReadyForElements() {/* no-op */} - /** @override */ setScrollTop() {/* no-op */} - /** @override */ getScrollWidth() {return 0;} - /** @override */ getScrollHeight() {return 0;} - /** @override */ getContentHeight() {return 0;} + /** @override */ updatePaddingTop() { + /* no-op */ + } + /** @override */ hideViewerHeader() { + /* no-op */ + } + /** @override */ showViewerHeader() { + /* no-op */ + } + /** @override */ disableScroll() { + /* no-op */ + } + /** @override */ resetScroll() { + /* no-op */ + } + /** @override */ ensureReadyForElements() { + /* no-op */ + } + /** @override */ setScrollTop() { + /* no-op */ + } + /** @override */ getScrollWidth() { + return 0; + } + /** @override */ getScrollHeight() { + return 0; + } + /** @override */ getContentHeight() { + return 0; + } /** @override */ contentHeightChanged() {} - /** @override */ getBorderTop() {return 0;} - /** @override */ requiresFixedLayerTransfer() {return false;} + /** @override */ getBorderTop() { + return 0; + } + /** @override */ requiresFixedLayerTransfer() { + return false; + } } /** @@ -454,12 +495,14 @@ export class ViewportBindingInabox { export function installInaboxViewportService(ampdoc) { const binding = new ViewportBindingInabox(ampdoc.win); const viewer = Services.viewerForDoc(ampdoc); - registerServiceBuilderForDoc(ampdoc, - 'viewport', - function() { - return new Viewport(ampdoc, binding, viewer); - }, - /* opt_instantiate */ true); + registerServiceBuilderForDoc( + ampdoc, + 'viewport', + function() { + return new Viewport(ampdoc, binding, viewer); + }, + /* opt_instantiate */ true + ); } /** diff --git a/src/inabox/utils.js b/src/inabox/utils.js index 5236c68fb0fc4..e5d64d47bda3e 100644 --- a/src/inabox/utils.js +++ b/src/inabox/utils.js @@ -27,16 +27,20 @@ import {whenContentIniLoad} from '../friendly-iframe-embed'; export function registerIniLoadListener(ampdoc) { const {win} = ampdoc; const root = ampdoc.getRootNode(); - whenContentIniLoad(ampdoc, win, - Services.viewportForDoc(ampdoc).getLayoutRect( - root.documentElement || root.body || root)) - .then(() => { - win.dispatchEvent(createCustomEvent( - win, 'amp-ini-load', /* detail */ null, {bubbles: true})); - if (win.parent) { - win.parent./*OK*/postMessage('amp-ini-load', '*'); - } - }); + whenContentIniLoad( + ampdoc, + win, + Services.viewportForDoc(ampdoc).getLayoutRect( + root.documentElement || root.body || root + ) + ).then(() => { + win.dispatchEvent( + createCustomEvent(win, 'amp-ini-load', /* detail */ null, {bubbles: true}) + ); + if (win.parent) { + win.parent./*OK*/ postMessage('amp-ini-load', '*'); + } + }); } /** @@ -45,9 +49,9 @@ export function registerIniLoadListener(ampdoc) { * @return {?string} */ export function getA4AId(win) { - - const a4aIdMetaTag = win.document.head - .querySelector('meta[name="amp4ads-id"]'); + const a4aIdMetaTag = win.document.head.querySelector( + 'meta[name="amp4ads-id"]' + ); if (a4aIdMetaTag) { return a4aIdMetaTag.getAttribute('content'); diff --git a/src/input.js b/src/input.js index 0c23b5c6040d9..9a480ebeb6f58 100644 --- a/src/input.js +++ b/src/input.js @@ -20,13 +20,11 @@ import {dev} from './log'; import {listenOnce, listenOncePromise} from './event-helper'; import {registerServiceBuilder} from './service'; - const TAG_ = 'Input'; const MAX_MOUSE_CONFIRM_ATTEMPS_ = 3; const CLICK_TIMEOUT_ = 300; - /** * Detects and maintains different types of input such as touch, mouse or * keyboard. @@ -55,10 +53,11 @@ export class Input { this.boundMouseConfirmed_ = null; /** @private {boolean} */ - this.hasTouch_ = ('ontouchstart' in win || - (win.navigator['maxTouchPoints'] !== undefined && - win.navigator['maxTouchPoints'] > 0) || - win['DocumentTouch'] !== undefined); + this.hasTouch_ = + 'ontouchstart' in win || + (win.navigator['maxTouchPoints'] !== undefined && + win.navigator['maxTouchPoints'] > 0) || + win['DocumentTouch'] !== undefined; dev().fine(TAG_, 'touch detected:', this.hasTouch_); /** @private {boolean} */ @@ -85,8 +84,9 @@ export class Input { // mouse events. if (this.hasTouch_) { this.hasMouse_ = !this.hasTouch_; - this.boundOnMouseMove_ = - /** @private {function(!Event)} */ (this.onMouseMove_.bind(this)); + this.boundOnMouseMove_ = /** @private {function(!Event)} */ this.onMouseMove_.bind( + this + ); listenOnce(win.document, 'mousemove', this.boundOnMouseMove_); } } @@ -169,11 +169,14 @@ export class Input { // Ignore inputs. const {target} = e; - if (target && (target.tagName == 'INPUT' || - target.tagName == 'TEXTAREA' || - target.tagName == 'SELECT' || - target.tagName == 'OPTION' || - target.hasAttribute('contenteditable'))) { + if ( + target && + (target.tagName == 'INPUT' || + target.tagName == 'TEXTAREA' || + target.tagName == 'SELECT' || + target.tagName == 'OPTION' || + target.hasAttribute('contenteditable')) + ) { return; } @@ -211,18 +214,22 @@ export class Input { // touch/mouse emulation. Otherwise, if timeout exceeded, this looks // like a legitimate mouse event. let unlisten; - const listenPromise = listenOncePromise(this.win.document, 'click', - /* capture */ undefined, unlistener => { - unlisten = unlistener; - }); + const listenPromise = listenOncePromise( + this.win.document, + 'click', + /* capture */ undefined, + unlistener => { + unlisten = unlistener; + } + ); return Services.timerFor(this.win) - .timeoutPromise(CLICK_TIMEOUT_, listenPromise) - .then(this.boundMouseCanceled_, () => { - if (unlisten) { - unlisten(); - } - this.boundMouseConfirmed_(); - }); + .timeoutPromise(CLICK_TIMEOUT_, listenPromise) + .then(this.boundMouseCanceled_, () => { + if (unlisten) { + unlisten(); + } + this.boundMouseConfirmed_(); + }); } /** @private */ @@ -237,8 +244,11 @@ export class Input { // Repeat, if attempts allow. this.mouseConfirmAttemptCount_++; if (this.mouseConfirmAttemptCount_ <= MAX_MOUSE_CONFIRM_ATTEMPS_) { - listenOnce(this.win.document, 'mousemove', - /** @type {function(!Event)} */ (this.boundOnMouseMove_)); + listenOnce( + this.win.document, + 'mousemove', + /** @type {function(!Event)} */ (this.boundOnMouseMove_) + ); } else { dev().fine(TAG_, 'mouse detection failed'); } diff --git a/src/intersection-observer-polyfill.js b/src/intersection-observer-polyfill.js index bf619dcecdc02..2b96137bffbf4 100644 --- a/src/intersection-observer-polyfill.js +++ b/src/intersection-observer-polyfill.js @@ -38,9 +38,29 @@ import {layoutRectLtwh, moveLayoutRect, rectIntersection} from './layout-rect'; */ export let DOMRect; -export const DEFAULT_THRESHOLD = - [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, - 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1]; +export const DEFAULT_THRESHOLD = [ + 0, + 0.05, + 0.1, + 0.15, + 0.2, + 0.25, + 0.3, + 0.35, + 0.4, + 0.45, + 0.5, + 0.55, + 0.6, + 0.65, + 0.7, + 0.75, + 0.8, + 0.85, + 0.9, + 0.95, + 1, +]; /** @typedef {{ * element: !Element, @@ -65,13 +85,12 @@ const INIT_TIME = Date.now(); * @param {!./layout-rect.LayoutRectDef} hostViewport hostViewport's rect * @return {!IntersectionObserverEntry} A change entry. */ -export function getIntersectionChangeEntry( - element, owner, hostViewport) { - const intersection = rectIntersection(element, owner, hostViewport) || - layoutRectLtwh(0, 0, 0, 0); +export function getIntersectionChangeEntry(element, owner, hostViewport) { + const intersection = + rectIntersection(element, owner, hostViewport) || + layoutRectLtwh(0, 0, 0, 0); const ratio = intersectionRatio(intersection, element); - return calculateChangeEntry( - element, hostViewport, intersection, ratio); + return calculateChangeEntry(element, hostViewport, intersection, ratio); } /** @@ -79,9 +98,11 @@ export function getIntersectionChangeEntry( * @return {boolean} */ export function nativeIntersectionObserverSupported(win) { - return 'IntersectionObserver' in win && - 'IntersectionObserverEntry' in win && - 'intersectionRatio' in win.IntersectionObserverEntry.prototype; + return ( + 'IntersectionObserver' in win && + 'IntersectionObserverEntry' in win && + 'intersectionRatio' in win.IntersectionObserverEntry.prototype + ); } /** @@ -115,17 +136,24 @@ export class IntersectionObserverApi { /** @private {?SubscriptionApi} */ this.subscriptionApi_ = new SubscriptionApi( - iframe, 'send-intersections', opt_is3p || false, () => { - this.startSendingIntersection_(); - }); - - this.intersectionObserver_ = new IntersectionObserverPolyfill(entries => { - // Remove target info from cross origin iframe. - for (let i = 0; i < entries.length; i++) { - delete entries[i]['target']; + iframe, + 'send-intersections', + opt_is3p || false, + () => { + this.startSendingIntersection_(); } - this.subscriptionApi_.send('intersection', dict({'changes': entries})); - }, {threshold: DEFAULT_THRESHOLD}); + ); + + this.intersectionObserver_ = new IntersectionObserverPolyfill( + entries => { + // Remove target info from cross origin iframe. + for (let i = 0; i < entries.length; i++) { + delete entries[i]['target']; + } + this.subscriptionApi_.send('intersection', dict({'changes': entries})); + }, + {threshold: DEFAULT_THRESHOLD} + ); this.intersectionObserver_.tick(this.viewport_.getRect()); /** @const {function()} */ @@ -181,7 +209,6 @@ export class IntersectionObserverApi { } } - /** * The IntersectionObserverPolyfill class lets any element receive its * intersection data with the viewport. It acts like native browser supported @@ -204,15 +231,16 @@ export class IntersectionObserverPolyfill { // The input threshold can be a number or an array of numbers. let threshold = opt_option && opt_option.threshold; if (threshold) { - threshold = isArray(threshold) ? - threshold : [threshold]; + threshold = isArray(threshold) ? threshold : [threshold]; } else { threshold = [0]; } for (let i = 0; i < threshold.length; i++) { - devAssert(isFiniteNumber(threshold[i]), 'Threshold should be a ' + - 'finite number or an array of finite numbers'); + devAssert( + isFiniteNumber(threshold[i]), + 'Threshold should be a ' + 'finite number or an array of finite numbers' + ); } /** @@ -220,9 +248,11 @@ export class IntersectionObserverPolyfill { * @private @const {!Array} */ this.threshold_ = threshold.sort(); - devAssert(this.threshold_[0] >= 0 && + devAssert( + this.threshold_[0] >= 0 && this.threshold_[this.threshold_.length - 1] <= 1, - 'Threshold should be in the range from "[0, 1]"'); + 'Threshold should be in the range from "[0, 1]"' + ); /** @private {?./layout-rect.LayoutRectDef} */ this.lastViewportRect_ = null; @@ -282,25 +312,27 @@ export class IntersectionObserverPolyfill { // Get the new observed element's first changeEntry based on last viewport if (this.lastViewportRect_) { const change = this.getValidIntersectionChangeEntry_( - newState, this.lastViewportRect_, this.lastIframeRect_); + newState, + this.lastViewportRect_, + this.lastIframeRect_ + ); if (change) { this.callback_([change]); } } - // Add a mutation observer to tick ourself // TODO (@torch2424): Allow this to observe elements, // from multiple documents. const ampdoc = Services.ampdoc(element); if (ampdoc.win.MutationObserver && !this.hiddenObserverUnlistener_) { this.mutationPass_ = new Pass( - ampdoc.win, - this.handleMutationObserverPass_.bind(this, element) + ampdoc.win, + this.handleMutationObserverPass_.bind(this, element) ); const hiddenObserver = Services.hiddenObserverForDoc(element); this.hiddenObserverUnlistener_ = hiddenObserver.add( - this.handleMutationObserverNotification_.bind(this) + this.handleMutationObserverNotification_.bind(this) ); } @@ -337,10 +369,16 @@ export class IntersectionObserverPolyfill { tick(hostViewport, opt_iframe) { if (opt_iframe) { // If element inside an iframe. Adjust origin to the iframe.left/top. - hostViewport = - moveLayoutRect(hostViewport, -opt_iframe.left, -opt_iframe.top); - opt_iframe = - moveLayoutRect(opt_iframe, -opt_iframe.left, -opt_iframe.top); + hostViewport = moveLayoutRect( + hostViewport, + -opt_iframe.left, + -opt_iframe.top + ); + opt_iframe = moveLayoutRect( + opt_iframe, + -opt_iframe.left, + -opt_iframe.top + ); } this.lastViewportRect_ = hostViewport; @@ -350,9 +388,9 @@ export class IntersectionObserverPolyfill { for (let i = 0; i < this.observeEntries_.length; i++) { const change = this.getValidIntersectionChangeEntry_( - this.observeEntries_[i], - hostViewport, - opt_iframe + this.observeEntries_[i], + hostViewport, + opt_iframe ); if (change) { changes.push(change); @@ -389,8 +427,8 @@ export class IntersectionObserverPolyfill { // calculate intersectionRect. that the element intersects with hostViewport // and intersects with owner element and container iframe if exists. const intersectionRect = - rectIntersection(elementRect, ownerRect, hostViewport, opt_iframe) || - layoutRectLtwh(0, 0, 0, 0); + rectIntersection(elementRect, ownerRect, hostViewport, opt_iframe) || + layoutRectLtwh(0, 0, 0, 0); // calculate ratio, call callback based on new ratio value. const ratio = intersectionRatio(intersectionRect, elementRect); const newThresholdSlot = getThresholdSlot(this.threshold_, ratio); @@ -402,8 +440,12 @@ export class IntersectionObserverPolyfill { // To get same behavior as native IntersectionObserver set hostViewport null // if inside an iframe - const changeEntry = calculateChangeEntry(elementRect, - (opt_iframe ? null : hostViewport), intersectionRect, ratio); + const changeEntry = calculateChangeEntry( + elementRect, + opt_iframe ? null : hostViewport, + intersectionRect, + ratio + ); changeEntry.target = element; return changeEntry; } @@ -503,8 +545,7 @@ export function getThresholdSlot(sortedThreshold, ratio) { * @param {number} ratio * @return {!IntersectionObserverEntry}} */ -function calculateChangeEntry( - element, hostViewport, intersection, ratio) { +function calculateChangeEntry(element, hostViewport, intersection, ratio) { // If element not in an iframe. // adjust all LayoutRect to hostViewport Origin. let boundingClientRect = element; @@ -519,20 +560,31 @@ function calculateChangeEntry( // If element not in an iframe. // adjust all LayoutRect to hostViewport Origin. rootBounds = /** @type {!./layout-rect.LayoutRectDef} */ (rootBounds); - intersection = moveLayoutRect(intersection, -hostViewport.left, - -hostViewport.top); + intersection = moveLayoutRect( + intersection, + -hostViewport.left, + -hostViewport.top + ); // The element is relative to (0, 0), while the viewport moves. So, we must // adjust. - boundingClientRect = moveLayoutRect(boundingClientRect, - -hostViewport.left, -hostViewport.top); + boundingClientRect = moveLayoutRect( + boundingClientRect, + -hostViewport.left, + -hostViewport.top + ); // Now, move the viewport to (0, 0) - rootBounds = moveLayoutRect(rootBounds, - -hostViewport.left, -hostViewport.top); + rootBounds = moveLayoutRect( + rootBounds, + -hostViewport.left, + -hostViewport.top + ); } return /** @type {!IntersectionObserverEntry} */ ({ - time: (typeof performance !== 'undefined' && performance.now) ? - performance.now() : Date.now() - INIT_TIME, + time: + typeof performance !== 'undefined' && performance.now + ? performance.now() + : Date.now() - INIT_TIME, rootBounds, boundingClientRect, intersectionRect: intersection, diff --git a/src/intersection-observer.js b/src/intersection-observer.js index ed6c930c566b4..465b260edd0ee 100644 --- a/src/intersection-observer.js +++ b/src/intersection-observer.js @@ -60,26 +60,36 @@ function intersectionRatio(smaller, larger) { * @private */ export function getIntersectionChangeEntry(element, owner, viewport) { - devAssert(element.width >= 0 && element.height >= 0, - 'Negative dimensions in element.'); + devAssert( + element.width >= 0 && element.height >= 0, + 'Negative dimensions in element.' + ); // Building an IntersectionObserverEntry. let intersectionRect = element; if (owner) { - intersectionRect = rectIntersection(owner, element) || - // No intersection. - layoutRectLtwh(0, 0, 0, 0); - } - intersectionRect = rectIntersection(viewport, intersectionRect) || + intersectionRect = + rectIntersection(owner, element) || // No intersection. layoutRectLtwh(0, 0, 0, 0); + } + intersectionRect = + rectIntersection(viewport, intersectionRect) || + // No intersection. + layoutRectLtwh(0, 0, 0, 0); // The element is relative to (0, 0), while the viewport moves. So, we must // adjust. - const boundingClientRect = moveLayoutRect(element, -viewport.left, - -viewport.top); - intersectionRect = moveLayoutRect(intersectionRect, -viewport.left, - -viewport.top); + const boundingClientRect = moveLayoutRect( + element, + -viewport.left, + -viewport.top + ); + intersectionRect = moveLayoutRect( + intersectionRect, + -viewport.left, + -viewport.top + ); // Now, move the viewport to (0, 0) const rootBounds = moveLayoutRect(viewport, -viewport.left, -viewport.top); @@ -144,10 +154,13 @@ export class IntersectionObserver { * @private {!SubscriptionApi} */ this.postMessageApi_ = new SubscriptionApi( - iframe, 'send-intersections', opt_is3p || false, - // Each time someone subscribes we make sure that they - // get an update. - () => this.startSendingIntersectionChanges_()); + iframe, + 'send-intersections', + opt_is3p || false, + // Each time someone subscribes we make sure that they + // get an update. + () => this.startSendingIntersectionChanges_() + ); /** @private {?Function} */ this.unlistenViewportChanges_ = null; @@ -230,9 +243,10 @@ export class IntersectionObserver { return; } const change = this.baseElement_.element.getIntersectionChangeEntry(); - if (this.pendingChanges_.length > 0 && - this.pendingChanges_[this.pendingChanges_.length - 1].time - == change.time) { + if ( + this.pendingChanges_.length > 0 && + this.pendingChanges_[this.pendingChanges_.length - 1].time == change.time + ) { return; } this.pendingChanges_.push(change); @@ -254,9 +268,12 @@ export class IntersectionObserver { return; } // Note that SubscribeApi multicasts the update to all interested windows. - this.postMessageApi_.send('intersection', dict({ - 'changes': this.pendingChanges_, - })); + this.postMessageApi_.send( + 'intersection', + dict({ + 'changes': this.pendingChanges_, + }) + ); this.pendingChanges_.length = 0; } diff --git a/src/json.js b/src/json.js index c9c0a60b7f19a..beda34676f10d 100644 --- a/src/json.js +++ b/src/json.js @@ -31,21 +31,18 @@ import {isObject} from './types'; */ let JSONScalarDef; - /** * JSON object. It's a map with string keys and JSON values. * @typedef {*} should be !Object */ let JSONObjectDef; - /** * JSON array. It's an array with JSON values. * @typedef {*} should be !Array */ let JSONArrayDef; - /** * JSON value. It's either a scalar, an object or an array. * @typedef {*} should be !JSONScalarDef|!JSONObjectDef|!JSONArrayDef @@ -89,10 +86,11 @@ export function getValueForExpr(obj, expr) { let value = obj; for (let i = 0; i < parts.length; i++) { const part = parts[i]; - if (part && - value && - value[part] !== undefined && - hasOwnProperty(value, part) + if ( + part && + value && + value[part] !== undefined && + hasOwnProperty(value, part) ) { value = value[part]; continue; @@ -111,7 +109,7 @@ export function getValueForExpr(obj, expr) { * @return {?JsonObject} May be extend to parse arrays. */ export function parseJson(json) { - return /** @type {?JsonObject} */(JSON.parse(/** @type {string} */ (json))); + return /** @type {?JsonObject} */ (JSON.parse(/** @type {string} */ (json))); } /** @@ -211,7 +209,6 @@ export function deepEquals(a, b, depth = 5) { return true; } - /** * @param {*} obj * @param {string} key @@ -222,5 +219,7 @@ function hasOwnProperty(obj, key) { return false; } return Object.prototype.hasOwnProperty.call( - /** @type {!Object} */ (obj), key); + /** @type {!Object} */ (obj), + key + ); } diff --git a/src/layout-delay-meter.js b/src/layout-delay-meter.js index 37289a92001bf..e68a37a8ce607 100644 --- a/src/layout-delay-meter.js +++ b/src/layout-delay-meter.js @@ -27,7 +27,6 @@ const LABEL_MAP = { * "start to layout" of an element. */ export class LayoutDelayMeter { - /** * @param {!Window} win * @param {number} priority @@ -85,7 +84,9 @@ export class LayoutDelayMeter { return; } const delay = this.win_.Math.max( - this.firstLayoutTime_ - this.firstInViewportTime_, 0); + this.firstLayoutTime_ - this.firstInViewportTime_, + 0 + ); this.performance_.tickDelta(dev().assertString(this.label_), delay); this.performance_.throttledFlush(); this.done_ = true; diff --git a/src/layout-rect.js b/src/layout-rect.js index ac355d8a84af1..3ba75010901f3 100644 --- a/src/layout-rect.js +++ b/src/layout-rect.js @@ -14,7 +14,6 @@ * limitations under the License. */ - /** * The structure that combines position and size for an element. The exact * interpretation of position and size depends on the use case. @@ -32,7 +31,6 @@ */ export let LayoutRectDef; - /** * The structure that represents the margins of an Element. * @@ -45,7 +43,6 @@ export let LayoutRectDef; */ export let LayoutMarginsDef; - /** * The structure that represents a requested change to the margins of an * Element. Any new values specified will replace existing ones (rather than @@ -61,12 +58,12 @@ export let LayoutMarginsDef; export let LayoutMarginsChangeDef; /** -* RelativePositions -* -* Describes the relative position of an element to another (whether the -* first is inside the second, on top of the second or on the bottom -* @enum {string} -*/ + * RelativePositions + * + * Describes the relative position of an element to another (whether the + * first is inside the second, on top of the second or on the bottom + * @enum {string} + */ export const RelativePositions = { INSIDE: 'inside', TOP: 'top', @@ -95,7 +92,6 @@ export function layoutRectLtwh(left, top, width, height) { }; } - /** * Creates a layout rect based on the DOMRect, e.g. obtained from calling * getBoundingClientRect. @@ -103,8 +99,12 @@ export function layoutRectLtwh(left, top, width, height) { * @return {!LayoutRectDef} */ export function layoutRectFromDomRect(rect) { - return layoutRectLtwh(Number(rect.left), Number(rect.top), - Number(rect.width), Number(rect.height)); + return layoutRectLtwh( + Number(rect.left), + Number(rect.top), + Number(rect.width), + Number(rect.height) + ); } /** @@ -114,11 +114,14 @@ export function layoutRectFromDomRect(rect) { * @return {boolean} */ export function layoutRectsOverlap(r1, r2) { - return (r1.top <= r2.bottom && r2.top <= r1.bottom && - r1.left <= r2.right && r2.left <= r1.right); + return ( + r1.top <= r2.bottom && + r2.top <= r1.bottom && + r1.left <= r2.right && + r2.left <= r1.right + ); } - /** * Returns the intersection between a, b or null if there is none. * @param {...?LayoutRectDef|undefined} var_args @@ -173,14 +176,18 @@ export function layoutRectsRelativePos(r1, r2) { * @return {RelativePositions} */ export function layoutPositionRelativeToScrolledViewport( - layoutBox, viewport, scrollPos) { - const scrollLayoutBox = - layoutRectFromDomRect(/** @type {!ClientRect} */ ({ + layoutBox, + viewport, + scrollPos +) { + const scrollLayoutBox = layoutRectFromDomRect( + /** @type {!ClientRect} */ ({ top: scrollPos, bottom: scrollPos + viewport.getHeight(), left: 0, right: viewport.getWidth(), - })); + }) + ); if (layoutRectsOverlap(layoutBox, scrollLayoutBox)) { return RelativePositions.INSIDE; } else { @@ -196,10 +203,12 @@ export function layoutPositionRelativeToScrolledViewport( * @return {!LayoutRectDef} */ export function expandLayoutRect(rect, dw, dh) { - return layoutRectLtwh(rect.left - rect.width * dw, - rect.top - rect.height * dh, - rect.width * (1 + dw * 2), - rect.height * (1 + dh * 2)); + return layoutRectLtwh( + rect.left - rect.width * dw, + rect.top - rect.height * dh, + rect.width * (1 + dw * 2), + rect.height * (1 + dh * 2) + ); } /** @@ -210,25 +219,24 @@ export function expandLayoutRect(rect, dw, dh) { * @return {!LayoutRectDef} */ export function moveLayoutRect(rect, dx, dy) { - if ((dx == 0 && dy == 0) || - (rect.width == 0 && rect.height == 0)) { + if ((dx == 0 && dy == 0) || (rect.width == 0 && rect.height == 0)) { return rect; } - return layoutRectLtwh(rect.left + dx, rect.top + dy, - rect.width, rect.height); + return layoutRectLtwh(rect.left + dx, rect.top + dy, rect.width, rect.height); } - /** * @param {!LayoutMarginsDef} margins * @param {!LayoutMarginsChangeDef} change * @return {boolean} */ export function areMarginsChanged(margins, change) { - return (change.top !== undefined && change.top != margins.top) || - (change.right !== undefined && change.right != margins.right) || - (change.bottom !== undefined && change.bottom != margins.bottom) || - (change.left !== undefined && change.left != margins.left); + return ( + (change.top !== undefined && change.top != margins.top) || + (change.right !== undefined && change.right != margins.right) || + (change.bottom !== undefined && change.bottom != margins.bottom) || + (change.left !== undefined && change.left != margins.left) + ); } /** @@ -237,8 +245,7 @@ export function areMarginsChanged(margins, change) { * @return {boolean} */ export function layoutRectSizeEquals(from, to) { - return from.width == to.width && - from.height === to.height; + return from.width == to.width && from.height === to.height; } /** @@ -250,8 +257,12 @@ export function layoutRectEquals(r1, r2) { if (!r1 || !r2) { return false; } - return r1.left == r2.left && r1.top == r2.top && - r1.width == r2.width && r1.height == r2.height; + return ( + r1.left == r2.left && + r1.top == r2.top && + r1.width == r2.width && + r1.height == r2.height + ); } /** diff --git a/src/layout.js b/src/layout.js index 0a0babbe59a7b..a3e8112864ce1 100644 --- a/src/layout.js +++ b/src/layout.js @@ -40,7 +40,6 @@ export const Layout = { INTRINSIC: 'intrinsic', }; - /** * Layout priorities to use with BaseElement#getLayoutPriority() and * BaseElement#updateLayoutPriority(). @@ -53,14 +52,12 @@ export const LayoutPriority = { BACKGROUND: 3, }; - /** * CSS Length type. E.g. "1px" or "20vh". * @typedef {string} */ export let LengthDef; - /** * @typedef {{ * width: string, @@ -69,7 +66,6 @@ export let LengthDef; */ let DimensionsDef; - /** * The set of elements with natural dimensions, that is, elements * which have a known dimension either based on their value specified here, @@ -88,7 +84,6 @@ export const naturalDimensions_ = { 'AMP-SOCIAL-SHARE': {width: '60px', height: '44px'}, }; - /** * Elements that the progess can be shown for. This set has to be externalized * since the element's implementation may not be downloaded yet. @@ -116,7 +111,6 @@ export const LOADING_ELEMENTS_ = { 'AMP-VIMEO': true, }; - /** * All video player components must either have a) "video" or b) "player" in * their name. A few components don't follow this convention for historical @@ -125,7 +119,6 @@ export const LOADING_ELEMENTS_ = { */ const videoPlayerTagNameRe = /^amp\-(video|.+player)/i; - /** * @param {string} s * @return {Layout|undefined} Returns undefined in case of failure to parse @@ -140,7 +133,6 @@ export function parseLayout(s) { return undefined; } - /** * @param {!Layout} layout * @return {string} @@ -149,34 +141,33 @@ export function getLayoutClass(layout) { return 'i-amphtml-layout-' + layout; } - /** * Whether an element with this layout inherently defines the size. * @param {!Layout} layout * @return {boolean} */ export function isLayoutSizeDefined(layout) { - return (layout == Layout.FIXED || - layout == Layout.FIXED_HEIGHT || - layout == Layout.RESPONSIVE || - layout == Layout.FILL || - layout == Layout.FLEX_ITEM || - layout == Layout.FLUID || - layout == Layout.INTRINSIC); + return ( + layout == Layout.FIXED || + layout == Layout.FIXED_HEIGHT || + layout == Layout.RESPONSIVE || + layout == Layout.FILL || + layout == Layout.FLEX_ITEM || + layout == Layout.FLUID || + layout == Layout.INTRINSIC + ); } - /** * Whether the tag is an internal (service) AMP tag. * @param {!Node|string} tag * @return {boolean} */ export function isInternalElement(tag) { - const tagName = (typeof tag == 'string') ? tag : tag.tagName; + const tagName = typeof tag == 'string' ? tag : tag.tagName; return tagName && startsWith(tagName.toLowerCase(), 'i-'); } - /** * Parses the CSS length value. If no units specified, the assumed value is * "px". Returns undefined in case of parsing error. @@ -199,8 +190,6 @@ export function parseLength(s) { return s; } - - /** * Asserts that the supplied value is a non-percent CSS Length value. * @param {!LengthDef|string|null|undefined} length @@ -208,14 +197,13 @@ export function parseLength(s) { */ export function assertLength(length) { userAssert( - /^\d+(\.\d+)?(px|em|rem|vh|vw|vmin|vmax|cm|mm|q|in|pc|pt)$/.test(length), - 'Invalid length value: %s', length); + /^\d+(\.\d+)?(px|em|rem|vh|vw|vmin|vmax|cm|mm|q|in|pc|pt)$/.test(length), + 'Invalid length value: %s', + length + ); return /** @type {!LengthDef} */ (length); } - - - /** * Asserts that the supplied value is a CSS Length value * (including percent unit). @@ -223,12 +211,14 @@ export function assertLength(length) { * @return {!LengthDef} */ export function assertLengthOrPercent(length) { - userAssert(/^\d+(\.\d+)?(px|em|rem|vh|vw|vmin|vmax|%)$/.test(length), - 'Invalid length or percent value: %s', length); + userAssert( + /^\d+(\.\d+)?(px|em|rem|vh|vw|vmin|vmax|%)$/.test(length), + 'Invalid length or percent value: %s', + length + ); return length; } - /** * Returns units from the CSS length value. * @param {!LengthDef|string|null|undefined} length @@ -237,12 +227,14 @@ export function assertLengthOrPercent(length) { export function getLengthUnits(length) { assertLength(length); dev().assertString(length); - const m = userAssert(length.match(/[a-z]+/i), - 'Failed to read units from %s', length); + const m = userAssert( + length.match(/[a-z]+/i), + 'Failed to read units from %s', + length + ); return m[0]; } - /** * Returns the numeric value of a CSS length value. * @param {!LengthDef|string|null|undefined} length @@ -253,7 +245,6 @@ export function getLengthNumeral(length) { return isFiniteNumber(res) ? res : undefined; } - /** * Determines whether the tagName is a known element that has natural dimensions * in our runtime or the browser. @@ -265,7 +256,6 @@ export function hasNaturalDimensions(tagName) { return naturalDimensions_[tagName] !== undefined; } - /** * Determines the default dimensions for an element which could vary across * different browser implementations, like
    @@ -96,26 +104,30 @@ describe('amp-analytics', function() { `, - extensions: ['amp-analytics'], - }, env => { - let browser; - - beforeEach(() => { - browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should send request', () => { - const reqPromise = RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/?f=hello%20world&b=2'); + extensions: ['amp-analytics'], + }, + env => { + let browser; + + beforeEach(() => { + browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); }); - browser.click('a'); - return reqPromise; - }); - }); - describes.integration('scroll trigger', { - body: ` + it('should send request', () => { + const reqPromise = RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/?f=hello%20world&b=2'); + }); + browser.click('a'); + return reqPromise; + }); + } + ); + + describes.integration( + 'scroll trigger', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should trigger 1s after amp-analytics starts', () => { - const startTime = Date.now(); - return RequestBank.withdraw().then(req => { - const q = parseQueryString(req.url.substr(1)); - const timerStart = parseFloat(q['timerStart']); - expect(timerStart + 1000).to.be.at.most(Date.now()); - expect(timerStart + 1000).to.be.at.most(parseInt(q['timestamp'], 10)); - // Verify that timerStart is about current time - expect(timerStart - startTime).to.be.above(-1000).and.below(1000); - expect(parseFloat(q['timerDuration'])).to.be.at.least(950).below(1100); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); }); - }); - }); - describes.integration('CLIENT_ID new user', { - body: ` + it('should trigger 1s after amp-analytics starts', () => { + const startTime = Date.now(); + return RequestBank.withdraw().then(req => { + const q = parseQueryString(req.url.substr(1)); + const timerStart = parseFloat(q['timerStart']); + expect(timerStart + 1000).to.be.at.most(Date.now()); + expect(timerStart + 1000).to.be.at.most(parseInt(q['timestamp'], 10)); + // Verify that timerStart is about current time + expect(timerStart - startTime) + .to.be.above(-1000) + .and.below(1000); + expect(parseFloat(q['timerDuration'])) + .to.be.at.least(950) + .below(1100); + }); + }); + } + ); + + describes.integration( + 'CLIENT_ID new user', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - afterEach(() => { - // clean up written _cid cookie - document.cookie = '_cid=;expires=' + new Date(0).toUTCString(); - }); - - it('should assign new cid', () => { - return Promise.all([ - RequestBank.withdraw(1), - RequestBank.withdraw(2), - ]).then(reqs => { - const req1 = reqs[0]; - const req2 = reqs[1]; - expect(req1.url).to.match(/^\/\?cid=/); - expect(req2.url).to.match(/^\/\?cid=/); - const cid1 = req1.url.substr('/?cid='.length); - const cid2 = req2.url.substr('/?cid='.length); - expect(cid1).to.match(/^amp-/); - expect(cid2).to.equal(cid1); - expect(document.cookie).to.contain('_cid=' + cid1); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); + + afterEach(() => { + // clean up written _cid cookie + document.cookie = '_cid=;expires=' + new Date(0).toUTCString(); + }); + + it('should assign new cid', () => { + return Promise.all([ + RequestBank.withdraw(1), + RequestBank.withdraw(2), + ]).then(reqs => { + const req1 = reqs[0]; + const req2 = reqs[1]; + expect(req1.url).to.match(/^\/\?cid=/); + expect(req2.url).to.match(/^\/\?cid=/); + const cid1 = req1.url.substr('/?cid='.length); + const cid2 = req2.url.substr('/?cid='.length); + expect(cid1).to.match(/^amp-/); + expect(cid2).to.equal(cid1); + expect(document.cookie).to.contain('_cid=' + cid1); + }); }); - }); - }); + } + ); - describes.integration('batch', { - body: - ` + describes.integration( + 'batch', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should send request in batch', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/?a=1&b=AMP%20TEST&a=1&b=AMP%20TEST'); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); + + it('should send request in batch', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/?a=1&b=AMP%20TEST&a=1&b=AMP%20TEST'); + }); }); - }); - }); + } + ); - describes.integration('useBody', { - body: - ` + describes.integration( + 'useBody', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should send request use POST body payload', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/'); - expect(JSON.parse(req.body)).to.deep.equal({ - a: 2, - b: 'AMP TEST', - c: { - d: 'AMP TEST', - e: { - f: ['AMP TEST', 'AMP TEST'], + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); + + it('should send request use POST body payload', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/'); + expect(JSON.parse(req.body)).to.deep.equal({ + a: 2, + b: 'AMP TEST', + c: { + d: 'AMP TEST', + e: { + f: ['AMP TEST', 'AMP TEST'], + }, }, - }, - g: ['AMP TEST', 'AMP TEST'], - '_c_a': 1, - '_c_b': { - 'context.c': 'AMP TEST', - 'context.d': { - 'context.e': ['AMP TEST', 'AMP TEST'], + g: ['AMP TEST', 'AMP TEST'], + '_c_a': 1, + '_c_b': { + 'context.c': 'AMP TEST', + 'context.d': { + 'context.e': ['AMP TEST', 'AMP TEST'], + }, }, - }, + }); }); }); - }); - }); + } + ); - describes.integration('batch useBody', { - body: - ` + describes.integration( + 'batch useBody', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should send batch request use POST body payload', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/'); - expect(JSON.parse(req.body)).to.deep.equal([{ - a: 1, b: 'AMP TEST', - }, { - a: 1, b: 'AMP TEST', - }]); + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); }); - }); - }); - describes.integration('referrerPolicy', { - body: - ` + it('should send batch request use POST body payload', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/'); + expect(JSON.parse(req.body)).to.deep.equal([ + { + a: 1, + b: 'AMP TEST', + }, + { + a: 1, + b: 'AMP TEST', + }, + ]); + }); + }); + } + ); + + describes.integration( + 'referrerPolicy', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should remove referrer if referrerpolicy=no-referrer', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.equal('/'); - expect(req.headers.referer).to.not.be.ok; + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); }); - }); - }); - describes.integration('configRewriter', { - body: - ` + it('should remove referrer if referrerpolicy=no-referrer', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.equal('/'); + expect(req.headers.referer).to.not.be.ok; + }); + }); + } + ); + + describes.integration( + 'configRewriter', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should use config from server', () => { - return RequestBank.withdraw().then(req => { - // The config here should have been rewritten by the /analytics/rewriter - // endpoint. This logic is located in the file - // /build-system/routes/analytics.js - const body = JSON.parse(req.body); - expect(body.reqBody.configRewriter.vars).to.deep.equal({ - name: 'cats', - title: 'AMP TEST', - title2: 'AMP TEST', + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); + + it('should use config from server', () => { + return RequestBank.withdraw().then(req => { + // The config here should have been rewritten by the /analytics/rewriter + // endpoint. This logic is located in the file + // /build-system/routes/analytics.js + const body = JSON.parse(req.body); + expect(body.reqBody.configRewriter.vars).to.deep.equal({ + name: 'cats', + title: 'AMP TEST', + title2: 'AMP TEST', + }); + expect(body.rewritten).to.be.true; + expect(body.testId).to.equal(12358); }); - expect(body.rewritten).to.be.true; - expect(body.testId).to.equal(12358); }); - }); - }); + } + ); - describes.integration('configRewriter without publisher config', { - body: - ` + describes.integration( + 'configRewriter without publisher config', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - it('should use config from server', () => { - return RequestBank.withdraw().then(req => { - // The config here should have been rewritten by the /analytics/rewriter - // endpoint. This logic is located in the file - // /build-system/routes/analytics.js - const body = JSON.parse(req.body); - expect(body.reqBody.configRewriter.vars).to.deep.equal({ - title2: 'AMP TEST', + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); + + it('should use config from server', () => { + return RequestBank.withdraw().then(req => { + // The config here should have been rewritten by the /analytics/rewriter + // endpoint. This logic is located in the file + // /build-system/routes/analytics.js + const body = JSON.parse(req.body); + expect(body.reqBody.configRewriter.vars).to.deep.equal({ + title2: 'AMP TEST', + }); + expect(body.rewritten).to.be.true; + expect(body.testId).to.equal(12358); }); - expect(body.rewritten).to.be.true; - expect(body.testId).to.equal(12358); }); - }); - }); + } + ); - describes.integration('type=googleanalytics', { - body: ` + describes.integration( + 'type=googleanalytics', + { + body: ` `, - extensions: ['amp-analytics'], - }, env => { - beforeEach(() => { - const browser = new BrowserController(env.win); - return browser.waitForElementLayout('amp-analytics'); - }); - - afterEach(() => { - // clean up written _ga cookie - document.cookie = '_ga=;expires=' + new Date(0).toUTCString(); - }); - - it('should send request', () => { - return RequestBank.withdraw().then(req => { - expect(req.url).to.match(/^\/r\/collect\?/); - const queries = parseQueryString(req.url.substr('/r/collect'.length)); - // see vendors/googleanalytics.js "pageview" request for config - expect(queries).to.include({ - _v: 'a1', - _r: '1', - v: '1', - cid: '1427830804.1524174812', - dr: '', - ds: 'AMP', - dt: 'AMP TEST', - tid: 'UA-67833617-1', - t: 'pageview', + extensions: ['amp-analytics'], + }, + env => { + beforeEach(() => { + const browser = new BrowserController(env.win); + return browser.waitForElementLayout('amp-analytics'); + }); + + afterEach(() => { + // clean up written _ga cookie + document.cookie = '_ga=;expires=' + new Date(0).toUTCString(); + }); + + it('should send request', () => { + return RequestBank.withdraw().then(req => { + expect(req.url).to.match(/^\/r\/collect\?/); + const queries = parseQueryString(req.url.substr('/r/collect'.length)); + // see vendors/googleanalytics.js "pageview" request for config + expect(queries).to.include({ + _v: 'a1', + _r: '1', + v: '1', + cid: '1427830804.1524174812', + dr: '', + ds: 'AMP', + dt: 'AMP TEST', + tid: 'UA-67833617-1', + t: 'pageview', + }); + const isNumber = /^\d+$/; + const isRandomNumber = /^0\.\d+$/; + expect(queries['dl']).to.contain('/amp4test/compose-doc?'); // ${documentLocation} + expect(queries['_s']).to.match(isNumber); // ${requestCount} + expect(queries['_utmht']).to.match(isNumber); // ${timestamp} + expect(queries['sr']).to.match(/^\d+x\d+$/); // ${screenWidth}x${screenHeight} + expect(queries['sd']).to.match(isNumber); // ${screenColorDepth} + expect(queries['ul']).to.be.ok; // ${browserLanguage} + expect(queries['de']).to.be.ok; // ${documentCharset} + expect(queries['jid']).to.match(isRandomNumber); // ${random} + expect(queries['a']).to.match(isNumber); // ${pageViewId} + expect(queries['z']).to.match(isRandomNumber); // ${random} }); - const isNumber = /^\d+$/; - const isRandomNumber = /^0\.\d+$/; - expect(queries['dl']).to.contain('/amp4test/compose-doc?'); // ${documentLocation} - expect(queries['_s']).to.match(isNumber); // ${requestCount} - expect(queries['_utmht']).to.match(isNumber); // ${timestamp} - expect(queries['sr']).to.match(/^\d+x\d+$/); // ${screenWidth}x${screenHeight} - expect(queries['sd']).to.match(isNumber); // ${screenColorDepth} - expect(queries['ul']).to.be.ok; // ${browserLanguage} - expect(queries['de']).to.be.ok; // ${documentCharset} - expect(queries['jid']).to.match(isRandomNumber); // ${random} - expect(queries['a']).to.match(isNumber); // ${pageViewId} - expect(queries['z']).to.match(isRandomNumber); // ${random} }); - }); - }); + } + ); }); diff --git a/test/integration/test-amp-bind.js b/test/integration/test-amp-bind.js index 0512d2cc5c4e6..523e52aa81a1a 100644 --- a/test/integration/test-amp-bind.js +++ b/test/integration/test-amp-bind.js @@ -20,51 +20,60 @@ const TIMEOUT = 15000; // Skip Edge, which throws "Permission denied" errors when inspecting // element properties in the testing iframe (Edge 17, Windows 10). -describe.configure().skipEdge().run('amp-bind', function() { - this.timeout(TIMEOUT); - - // Helper that sets the poll timeout. - function poll(desc, condition, onError) { - return classicPoll(desc, condition, onError, TIMEOUT); - } - - describes.integration('basic', { - /* eslint-disable max-len */ - body: ` +describe + .configure() + .skipEdge() + .run('amp-bind', function() { + this.timeout(TIMEOUT); + + // Helper that sets the poll timeout. + function poll(desc, condition, onError) { + return classicPoll(desc, condition, onError, TIMEOUT); + } + + describes.integration( + 'basic', + { + /* eslint-disable max-len */ + body: `

    before_text

    `, - /* eslint-enable max-len */ - extensions: ['amp-bind'], - }, env => { - let browser; - let doc; - let text; - - beforeEach(() => { - doc = env.win.document; - text = doc.querySelector('p'); - browser = new BrowserController(env.win); - }); - - it('[text]', function*() { - expect(text.textContent).to.equal('before_text'); - yield browser.wait(200); - browser.click('#changeText'); - yield poll('[text]', () => text.textContent === 'after_text'); - }); - - it('[class]', function*() { - expect(text.className).to.equal('before_class'); - yield browser.wait(200); - browser.click('#changeClass'); - yield poll('[class]', () => text.className === 'after_class'); - }); - }); - - describes.integration('+ amp-img', { - body: ` + /* eslint-enable max-len */ + extensions: ['amp-bind'], + }, + env => { + let browser; + let doc; + let text; + + beforeEach(() => { + doc = env.win.document; + text = doc.querySelector('p'); + browser = new BrowserController(env.win); + }); + + it('[text]', function*() { + expect(text.textContent).to.equal('before_text'); + yield browser.wait(200); + browser.click('#changeText'); + yield poll('[text]', () => text.textContent === 'after_text'); + }); + + it('[class]', function*() { + expect(text.className).to.equal('before_class'); + yield browser.wait(200); + browser.click('#changeClass'); + yield poll('[class]', () => text.className === 'after_class'); + }); + } + ); + + describes.integration( + '+ amp-img', + { + body: ` `, - extensions: ['amp-bind'], - }, env => { - let doc, img; - - beforeEach(() => { - doc = env.win.document; - img = doc.querySelector('amp-img'); - }); - - it('[src] with valid URL', () => { - const button = doc.getElementById('changeSrc'); - expect(img.getAttribute('src')).to.equal('http://example.com/before.jpg'); - button.click(); - return poll('[src]', - () => img.getAttribute('src') === 'http://example.com/after.jpg'); - }); - - it('[alt]', () => { - const button = doc.getElementById('changeAlt'); - expect(img.getAttribute('alt')).to.equal('before_alt'); - button.click(); - return poll('[src]', () => img.getAttribute('alt') === 'after_alt'); - }); - - it('[width] and [height]', () => { - const button = doc.getElementById('changeSize'); - expect(img.getAttribute('width')).to.equal('1'); - expect(img.getAttribute('height')).to.equal('1'); - button.click(); - return Promise.all([ - poll('[width]', () => img.getAttribute('width') === '2'), - poll('[height]', () => img.getAttribute('height') === '2'), - ]); - }); - }); - - describes.integration('+ forms', { - /* eslint-disable max-len */ - body: ` + extensions: ['amp-bind'], + }, + env => { + let doc, img; + + beforeEach(() => { + doc = env.win.document; + img = doc.querySelector('amp-img'); + }); + + it('[src] with valid URL', () => { + const button = doc.getElementById('changeSrc'); + expect(img.getAttribute('src')).to.equal( + 'http://example.com/before.jpg' + ); + button.click(); + return poll( + '[src]', + () => img.getAttribute('src') === 'http://example.com/after.jpg' + ); + }); + + it('[alt]', () => { + const button = doc.getElementById('changeAlt'); + expect(img.getAttribute('alt')).to.equal('before_alt'); + button.click(); + return poll('[src]', () => img.getAttribute('alt') === 'after_alt'); + }); + + it('[width] and [height]', () => { + const button = doc.getElementById('changeSize'); + expect(img.getAttribute('width')).to.equal('1'); + expect(img.getAttribute('height')).to.equal('1'); + button.click(); + return Promise.all([ + poll('[width]', () => img.getAttribute('width') === '2'), + poll('[height]', () => img.getAttribute('height') === '2'), + ]); + }); + } + ); + + describes.integration( + '+ forms', + { + /* eslint-disable max-len */ + body: `

    before_range

    @@ -124,65 +141,69 @@ describe.configure().skipEdge().run('amp-bind', function() {

    before_radio

    `, - /* eslint-enable max-len */ - extensions: ['amp-bind'], - }, env => { - let doc; - - beforeEach(() => { - doc = env.win.document; - }); - - it('input[type=range] on:change', () => { - const rangeText = doc.getElementById('range'); - const range = doc.querySelector('input[type="range"]'); - expect(rangeText.textContent).to.equal('before_range'); - // Calling #click() on the range element will not generate a change event, - // so it must be generated manually. - range.value = 47; - range.dispatchEvent(new Event('change', {bubbles: true})); - poll('[text]', () => rangeText.textContent === '0 <= 47 <= 100'); - }); - - it('input[type=checkbox] on:change', () => { - const checkboxText = doc.getElementById('checkbox'); - const checkbox = doc.querySelector('input[type="checkbox"]'); - expect(checkboxText.textContent).to.equal('before_check'); - checkbox.click(); - poll('[text]', () => checkboxText.textContent === 'checked: true'); - }); - - it('[checked]', function*() { - const checkbox = doc.querySelector('input[type="checkbox"]'); - const button = doc.querySelector('button'); - - checkbox.click(); - // Note that attributes are initial values, properties are current values. - expect(checkbox.hasAttribute('checked')).to.be.false; - expect(checkbox.checked).to.be.true; - - button.click(); - yield poll('[checked]', () => !checkbox.checked); - expect(checkbox.hasAttribute('checked')).to.be.false; - - button.click(); - yield poll('[checked]', () => checkbox.checked); - // amp-bind sets both the attribute and property. - expect(checkbox.hasAttribute('checked')).to.be.true; - }); - - it('input[type=radio] on:change', () => { - const radioText = doc.getElementById('radio'); - const radio = doc.querySelector('input[type="radio"]'); - expect(radioText.textContent).to.equal('before_radio'); - radio.click(); - poll('[text]', () => radioText.textContent === 'checked: true'); - }); - }); - - describes.integration('+ amp-carousel', { - /* eslint-disable max-len */ - body: ` + /* eslint-enable max-len */ + extensions: ['amp-bind'], + }, + env => { + let doc; + + beforeEach(() => { + doc = env.win.document; + }); + + it('input[type=range] on:change', () => { + const rangeText = doc.getElementById('range'); + const range = doc.querySelector('input[type="range"]'); + expect(rangeText.textContent).to.equal('before_range'); + // Calling #click() on the range element will not generate a change event, + // so it must be generated manually. + range.value = 47; + range.dispatchEvent(new Event('change', {bubbles: true})); + poll('[text]', () => rangeText.textContent === '0 <= 47 <= 100'); + }); + + it('input[type=checkbox] on:change', () => { + const checkboxText = doc.getElementById('checkbox'); + const checkbox = doc.querySelector('input[type="checkbox"]'); + expect(checkboxText.textContent).to.equal('before_check'); + checkbox.click(); + poll('[text]', () => checkboxText.textContent === 'checked: true'); + }); + + it('[checked]', function*() { + const checkbox = doc.querySelector('input[type="checkbox"]'); + const button = doc.querySelector('button'); + + checkbox.click(); + // Note that attributes are initial values, properties are current values. + expect(checkbox.hasAttribute('checked')).to.be.false; + expect(checkbox.checked).to.be.true; + + button.click(); + yield poll('[checked]', () => !checkbox.checked); + expect(checkbox.hasAttribute('checked')).to.be.false; + + button.click(); + yield poll('[checked]', () => checkbox.checked); + // amp-bind sets both the attribute and property. + expect(checkbox.hasAttribute('checked')).to.be.true; + }); + + it('input[type=radio] on:change', () => { + const radioText = doc.getElementById('radio'); + const radio = doc.querySelector('input[type="radio"]'); + expect(radioText.textContent).to.equal('before_radio'); + radio.click(); + poll('[text]', () => radioText.textContent === 'checked: true'); + }); + } + ); + + describes.integration( + '+ amp-carousel', + { + /* eslint-disable max-len */ + body: `

    0

    `, - /* eslint-enable max-len */ - extensions: ['amp-bind', 'amp-carousel'], - }, env => { - let doc, carousel, slideText; - - beforeEach(() => { - doc = env.win.document; - carousel = doc.querySelector('amp-carousel'); - slideText = doc.querySelector('p'); - - const browserController = new BrowserController(env.win); - return browserController.waitForElementLayout('amp-carousel'); - }); - - it('on:slideChange', () => { - expect(slideText.textContent).to.equal('0'); - - const nextSlide = carousel.querySelector('div.amp-carousel-button-next'); - nextSlide.click(); - return poll('[slide]', () => slideText.textContent === '1'); - }); - - it('[slide]', function*() { - const slides = carousel.querySelectorAll( - '.i-amphtml-slide-item > amp-img'); - const first = slides[0]; - const second = slides[1]; - - expect(first.getAttribute('aria-hidden')).to.equal('false'); - expect(second.getAttribute('aria-hidden')).to.be.equal('true'); - - const button = doc.getElementById('goToSlideOne'); - button.click(); - - yield poll('[slide]', () => - first.getAttribute('aria-hidden') === 'true'); - yield poll('[slide]', () => - second.getAttribute('aria-hidden') === 'false'); - }); - }); + /* eslint-enable max-len */ + extensions: ['amp-bind', 'amp-carousel'], + }, + env => { + let doc, carousel, slideText; + + beforeEach(() => { + doc = env.win.document; + carousel = doc.querySelector('amp-carousel'); + slideText = doc.querySelector('p'); + + const browserController = new BrowserController(env.win); + return browserController.waitForElementLayout('amp-carousel'); + }); + + it('on:slideChange', () => { + expect(slideText.textContent).to.equal('0'); + + const nextSlide = carousel.querySelector( + 'div.amp-carousel-button-next' + ); + nextSlide.click(); + return poll('[slide]', () => slideText.textContent === '1'); + }); + + it('[slide]', function*() { + const slides = carousel.querySelectorAll( + '.i-amphtml-slide-item > amp-img' + ); + const first = slides[0]; + const second = slides[1]; + + expect(first.getAttribute('aria-hidden')).to.equal('false'); + expect(second.getAttribute('aria-hidden')).to.be.equal('true'); + + const button = doc.getElementById('goToSlideOne'); + button.click(); + + yield poll( + '[slide]', + () => first.getAttribute('aria-hidden') === 'true' + ); + yield poll( + '[slide]', + () => second.getAttribute('aria-hidden') === 'false' + ); + }); + } + ); - /* eslint-disable max-len */ - const list = ` + /* eslint-disable max-len */ + const list = ` `, - }, env => { - it('should layout amp-img, amp-pixel, amp-analytics', () => { - // See amp4test.js for creative content - return testAmpComponents(); - }); - - afterEach(() => { - unregisterIframe(env.win.document.getElementById('inabox')); - }); - }); - - describes.integration('AMPHTML ads rendered on non-AMP page BTF', { - amp: false, - body: ` + }, + env => { + it('should layout amp-img, amp-pixel, amp-analytics', () => { + // See amp4test.js for creative content + return testAmpComponents(); + }); + + afterEach(() => { + unregisterIframe(env.win.document.getElementById('inabox')); + }); + } + ); + + describes.integration( + 'AMPHTML ads rendered on non-AMP page BTF', + { + amp: false, + body: `
    c')) - .to.be.equal('ac'); - expect(purify('ac')) - .to.be.equal('ac'); + expect(purify('ac')).to.be.equal('ac'); + expect(purify('ac')).to.be.equal('ac'); + expect(purify('ac')).to.be.equal('ac'); }); it('should NOT output security-sensitive markup when broken', () => { @@ -210,41 +232,44 @@ function runSanitizerTests() { it('should output "on" attribute', () => { expect(purify('a
    b')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should output "data-, aria-, and role" attributes', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - purify('b') + purify('b') ); const expected = serialize( - 'b'); + 'b' + ); expectEqualNodeLists(actual, expected); }); it('should output "href" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. - const actual = serialize( - purify('ab') - ); + const actual = serialize(purify('ab')); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); it('should allow arbitrary protocols', () => { expect(purify('link')).to.be.equal( - 'link'); + 'link' + ); }); it('should output "rel" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - purify('ab') + purify('ab') ); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); @@ -271,167 +296,202 @@ function runSanitizerTests() { it('should default target to _top with href', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - purify('ac') + purify('ac') ); const expected = serialize( - 'ac'); + 'ac' + ); expectEqualNodeLists(actual, expected); }); it('should NOT default target to _top w/o href', () => { - expect(purify( - 'b' - + 'd' - )).to.equal( - 'b' - + 'd'); + expect(purify('b' + 'd')).to.equal( + 'b' + 'd' + ); }); it('should output a valid target', () => { - expect(purify('ab')) - .to.equal('ab'); + expect(purify('ab')).to.equal( + 'ab' + ); }); it('should output a valid target in different case', () => { - expect(purify('ab')) - .to.equal('ab'); + expect(purify('ab')).to.equal( + 'ab' + ); }); it('should override a unallowed target', () => { - expect(purify( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other' - )).to.equal( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other'); + expect( + purify( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ) + ).to.equal( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ); }); it('should NOT output security-sensitive attributes', () => { - expect(purify('ab')).to.be.equal( - 'ab'); + expect(purify('ab')).to.be.equal('ab'); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(purify('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should NOT output blacklisted values for class attributes', () => { allowConsoleError(() => { - expect(purify('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(purify('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(purify('

    hello

    ')).to.be - .equal('

    hello

    '); + expect(purify('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect(purify('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect(purify('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); }); }); it('should allow amp-subscriptions attributes', () => { - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); - expect(purify('
    link
    ')) - .to.equal('
    link
    '); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); + expect( + purify('
    link
    ') + ).to.equal('
    link
    '); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(purify('
    link
    ')).to.equal( + '
    link
    ' + ); }); it('should allow source::src with valid protocol', () => { - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); // TODO(choumx): HTTPS-only URI attributes are not enforced consistently // in the sanitizer yet. E.g. amp-video requires HTTPS, amp-img does not. // Unskip when this is fixed. it.skip('should not allow source::src with invalid protocol', () => { - expect(purify('')) - .to.equal(''); - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); + expect(purify('')).to.equal( + '' + ); }); it('should allow div::template', () => { - expect(purify('
    ')) - .to.equal('
    '); + expect(purify('
    ')).to.equal( + '
    ' + ); }); it('should allow form::action-xhr', () => { - expect(purify('')) - .to.equal('
    '); + expect(purify('
    ')).to.equal( + '
    ' + ); }); it('should allow input::mask-output', () => { - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); // Need to test this since DOMPurify doesn't offer a API for tag-specific // attribute whitelists. Instead, we hack around it with custom hooks. it('should not allow unsupported attributes after a valid one', () => { - const html = '
    ' + - '

    '; - expect(purify(html)) - .to.equal('

    '); + const html = + '
    ' + + '

    '; + expect(purify(html)).to.equal( + '

    ' + ); }); it('should allow -related attributes', () => { - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('
    ')) - .to.equal('
    '); - expect(purify('')) - .to.equal(''); - expect(purify('')) - .to.equal(''); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect(purify('
    ')).to.equal( + '
    ' + ); + expect( + purify('') + ).to.equal(''); + expect(purify('')).to.equal( + '' + ); }); it('should avoid disallowing default-supported attributes', () => { // We whitelist all attributes of AMP elements, but make sure we don't // remove default-supported attributes from the whitelist afterwards. const html = - '

    '; + '

    '; expect(purify(html)).to.equal(html); }); it('should allow attributes', () => { - expect(purify('')) - .to.equal(''); + expect(purify('')).to.equal( + '' + ); }); it('should output "i-amphtml-key" attribute if diffing is enabled', () => { // Elements with bindings should have i-amphtml-key="". expect(purify('

    ', true)).to.match( - /

    <\/p>/); + /

    <\/p>/ + ); // AMP elements should have i-amphtml-key="". expect(purify('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // AMP elements with bindings should have i-amphtml-key="". expect(purify('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // Other elements should NOT have i-amphtml-key-set. expect(purify('

    ')).to.equal('

    '); }); @@ -439,12 +499,10 @@ function runSanitizerTests() { it('should resolve URLs', () => { expect(purify('')).to.match(/http/); expect(purify('')).to.match(/http/); - expect(purify('')) - .to.match(/http/); + expect(purify('')).to.match(/http/); }); }); - describe('purifyTagsForTripleMustache', () => { it('should output basic text', () => { expect(purifyTagsForTripleMustache('abc')).to.be.equal('abc'); @@ -454,39 +512,47 @@ function runSanitizerTests() { const entity = '<tag>'; expect(purifyTagsForTripleMustache(entity)).to.be.equal(entity); // DOMPurify short-circuits when there are no '<' characters. - expect(purifyTagsForTripleMustache(`

    ${entity}

    `)) - .to.be.equal(`

    ${entity}

    `); + expect(purifyTagsForTripleMustache(`

    ${entity}

    `)).to.be.equal( + `

    ${entity}

    ` + ); }); it('should output valid markup', () => { - expect(purifyTagsForTripleMustache('abc')) - .to.be.equal('abc'); + expect(purifyTagsForTripleMustache('abc')).to.be.equal( + 'abc' + ); expect(purifyTagsForTripleMustache('ab
    c
    ')).to.be.equal( - 'ab
    c
    '); + 'ab
    c
    ' + ); expect(purifyTagsForTripleMustache('abc')).to.be.equal( - 'abc'); + 'abc' + ); const markupWithClassAttribute = '

    heading

    '; - expect(purifyTagsForTripleMustache(markupWithClassAttribute)) - .to.be.equal(markupWithClassAttribute); + expect(purifyTagsForTripleMustache(markupWithClassAttribute)).to.be.equal( + markupWithClassAttribute + ); const markupWithClassesAttribute = - '
    heading
    '; - expect(purifyTagsForTripleMustache(markupWithClassesAttribute)) - .to.be.equal(markupWithClassesAttribute); + '
    heading
    '; + expect( + purifyTagsForTripleMustache(markupWithClassesAttribute) + ).to.be.equal(markupWithClassesAttribute); const markupParagraph = '

    paragraph

    '; - expect(purifyTagsForTripleMustache(markupParagraph)) - .to.be.equal(markupParagraph); + expect(purifyTagsForTripleMustache(markupParagraph)).to.be.equal( + markupParagraph + ); }); it('should NOT output non-whitelisted markup', () => { - expect(purifyTagsForTripleMustache('ac')) - .to.be.equal('ac'); - expect(purifyTagsForTripleMustache('ac')) - .to.be.equal('ac'); + expect(purifyTagsForTripleMustache('ac')).to.be.equal( + 'ac' + ); + expect(purifyTagsForTripleMustache('ac')).to.be.equal('ac'); }); it('should compensate for broken markup', () => { expect(purifyTagsForTripleMustache('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should support list tags', () => { @@ -496,62 +562,70 @@ function runSanitizerTests() { it('should whitelist formatting related elements', () => { const nonWhiteListedTag = ''; - const whiteListedFormattingTags = 'abc
    def
    ' - + '
    ' - + '' - + '' - + '
    '; + const whiteListedFormattingTags = + 'abc
    def
    ' + + '
    ' + + '' + + '' + + '
    '; const html = `${whiteListedFormattingTags}${nonWhiteListedTag}`; // Expect the purifier to unescape the whitelisted tags and to sanitize // and remove the img tag. - expect(purifyTagsForTripleMustache(html)) - .to.be.equal(whiteListedFormattingTags); + expect(purifyTagsForTripleMustache(html)).to.be.equal( + whiteListedFormattingTags + ); }); it('should whitelist table related elements and anchor tags', () => { - const html = '' - + '' - + '' - + '' - + '' - + '' - + '' - + '
    caption
    header
    ' - + 'google' - + '
    footer
    '; + const html = + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
    caption
    header
    ' + + 'google' + + '
    footer
    '; expect(purifyTagsForTripleMustache(html)).to.be.equal(html); }); it('should sanitize tags, removing unsafe attributes', () => { - const html = 'test' - + ''; + const html = + 'test' + + ''; expect(purifyTagsForTripleMustache(html)).to.be.equal('test'); }); describe('should sanitize `style` attribute', () => { - it('should allow valid styles',() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + it('should allow valid styles', () => { + expect(purify('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); - it('should ignore styles containing `!important`',() => { + it('should ignore styles containing `!important`', () => { allowConsoleError(() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + expect( + purify('
    Test
    ') + ).to.equal('
    Test
    '); }); }); it('should ignore styles containing `position:fixed`', () => { allowConsoleError(() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + expect(purify('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); }); it('should ignore styles containing `position:sticky`', () => { allowConsoleError(() => { - expect(purify('
    Test
    ')) - .to.equal('
    Test
    '); + expect(purify('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); }); }); diff --git a/test/unit/test-render-delaying-services.js b/test/unit/test-render-delaying-services.js index de58a9caf8fd7..afc58a2ac9ee2 100644 --- a/test/unit/test-render-delaying-services.js +++ b/test/unit/test-render-delaying-services.js @@ -24,7 +24,6 @@ import { import {macroTask} from '../../testing/yield'; describe('waitForServices', () => { - let win; let sandbox; let clock; @@ -46,10 +45,9 @@ describe('waitForServices', () => { }, }; variantResolve = waitForService(getService, 'variant', variantService); - variantStub = sandbox.stub( - variantService, - 'whenReady' - ).returns(Promise.resolve()); + variantStub = sandbox + .stub(variantService, 'whenReady') + .returns(Promise.resolve()); return createIframePromise().then(iframe => { win = iframe.win; @@ -69,7 +67,7 @@ describe('waitForServices', () => { return expect(waitForServices(win)).to.eventually.have.lengthOf(0); }); - it('should timeout if some blocking services are missing', function* () { + it('should timeout if some blocking services are missing', function*() { addExtensionScript(win, 'amp-dynamic-css-classes'); win.document.body.appendChild(win.document.createElement('amp-experiment')); expect(hasRenderDelayingServices(win)).to.be.true; @@ -130,9 +128,11 @@ describe('waitForServices', () => { function waitForService(getService, serviceId, service) { let resolve = null; - getService.withArgs(sinon.match.any, serviceId).returns(new Promise(r => { - resolve = r.bind(this, service); - })); + getService.withArgs(sinon.match.any, serviceId).returns( + new Promise(r => { + resolve = r.bind(this, service); + }) + ); return resolve; } diff --git a/test/unit/test-resource.js b/test/unit/test-resource.js index 84e0395a725ea..d2dbcdf66e940 100644 --- a/test/unit/test-resource.js +++ b/test/unit/test-resource.js @@ -21,7 +21,6 @@ import {Resources} from '../../src/service/resources-impl'; import {Services} from '../../src/services'; import {layoutRectLtwh} from '../../src/layout-rect'; - describes.realWin('Resource', {amp: true}, env => { let win, doc; let element; @@ -35,14 +34,16 @@ describes.realWin('Resource', {amp: true}, env => { doc = win.document; element = env.createAmpElement('amp-ad'); - sandbox.stub(element, 'getLayoutPriority').callsFake( - () => LayoutPriority.ADS); + sandbox + .stub(element, 'getLayoutPriority') + .callsFake(() => LayoutPriority.ADS); elementMock = sandbox.mock(element); const viewer = Services.viewerForDoc(document); sandbox.stub(viewer, 'isRuntimeOn').callsFake(() => false); - sandbox.stub(Resources.prototype, 'rebuildDomWhenReady') - .callsFake(() => {}); + sandbox + .stub(Resources.prototype, 'rebuildDomWhenReady') + .callsFake(() => {}); resources = new Resources(new AmpDocSingle(window)); resource = new Resource(1, element, resources); viewportMock = sandbox.mock(resources.viewport_); @@ -55,8 +56,9 @@ describes.realWin('Resource', {amp: true}, env => { resources.win = { document, getComputedStyle: el => { - return el.fakeComputedStyle ? - el.fakeComputedStyle : window.getComputedStyle(el); + return el.fakeComputedStyle + ? el.fakeComputedStyle + : window.getComputedStyle(el); }, }; }); @@ -77,13 +79,20 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should initialize correctly when already built', () => { - elementMock.expects('isBuilt').returns(true).once(); + elementMock + .expects('isBuilt') + .returns(true) + .once(); expect(new Resource(1, element).getState()).to.equal( - ResourceState.NOT_LAID_OUT); + ResourceState.NOT_LAID_OUT + ); }); it('should not build before upgraded', () => { - elementMock.expects('isUpgraded').returns(false).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(false) + .atLeast(1); elementMock.expects('build').never(); elementMock.expects('updateLayoutBox').never(); @@ -91,11 +100,16 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); }); - it('should build after upgraded', () => { const buildPromise = Promise.resolve(); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(buildPromise).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(buildPromise) + .once(); elementMock.expects('updateLayoutBox').never(); return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); @@ -104,43 +118,65 @@ describes.realWin('Resource', {amp: true}, env => { it('should not build if permission is not granted', () => { let permission = false; - elementMock.expects('isUpgraded').returns(true).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); sandbox.stub(resources, 'grantBuildPermission').callsFake(() => permission); elementMock.expects('updateLayoutBox').never(); expect(resource.build()).to.be.null; expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); permission = true; - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); }); }); it('should blacklist on build failure', () => { - sandbox.stub(resource, 'maybeReportErrorOnBuildFailure') - .callsFake(() => {}); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build') - .returns(Promise.reject(new Error('intentional'))).once(); + sandbox + .stub(resource, 'maybeReportErrorOnBuildFailure') + .callsFake(() => {}); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.reject(new Error('intentional'))) + .once(); elementMock.expects('updateLayoutBox').never(); const buildPromise = resource.build(); expect(resource.isBuilding()).to.be.true; - return buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - expect(resource.isBuilding()).to.be.false; - expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); - }); + return buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + expect(resource.isBuilding()).to.be.false; + expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); + } + ); }); it('should mark as ready for layout if already measured', () => { const box = layoutRectLtwh(0, 0, 100, 200); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(box, true) - .once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs(box, true) + .once(); const stub = sandbox.stub(resource, 'hasBeenMeasured').returns(true); resource.layoutBox_ = box; return resource.build().then(() => { @@ -150,8 +186,14 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should mark as not laid out if not yet measured', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); const stub = sandbox.stub(resource, 'hasBeenMeasured').returns(false); return resource.build().then(() => { expect(stub.calledOnce).to.be.true; @@ -160,40 +202,65 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should track size changes on measure', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111, height: 222}) - .once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111, height: 222}) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 111 && data.height == 222; - }), true) - .once(); + }), + true + ) + .once(); resource.measure(); }); }); it('should track no size changes on measure', () => { layoutRectLtwh(0, 0, 0, 0); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - elementMock.expects('getBoundingClientRect') - .returns({left: 0, top: 0, width: 0, height: 0}) - .once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('getBoundingClientRect') + .returns({left: 0, top: 0, width: 0, height: 0}) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 0 && data.height == 0; - }), false) - .once(); + }), + false + ) + .once(); resource.measure(); }); }); it('should allow to measure when not upgraded', () => { - elementMock.expects('isUpgraded').returns(false).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(false) + .atLeast(1); const viewport = { getLayoutRect() { return layoutRectLtwh(0, 100, 300, 100); @@ -212,30 +279,47 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getLayoutBox()).to.eql(layoutRectLtwh(0, 100, 300, 100)); // pageLayoutBox == layoutBox expect(resource.getPageLayoutBox()).to.eql( - layoutRectLtwh(0, 100, 300, 100)); + layoutRectLtwh(0, 100, 300, 100) + ); }); it('should allow measure even when not built', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 0, 0)).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 0, 0)) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); expect(resource.isFixed()).to.be.false; }); it('should measure and update state', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111, height: 222}) - .once(); - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111, height: 222}) + .once(); + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 111 && data.height == 222; - }), true) - .once(); + }), + true + ) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); expect(resource.getLayoutBox().left).to.equal(11); @@ -247,17 +331,31 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should update initial box only on first measure', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); return resource.build().then(() => { - element.getBoundingClientRect = () => - ({left: 11, top: 12, width: 111, height: 222}); + element.getBoundingClientRect = () => ({ + left: 11, + top: 12, + width: 111, + height: 222, + }); resource.measure(); expect(resource.getLayoutBox().top).to.equal(12); expect(resource.getInitialLayoutBox().top).to.equal(12); - element.getBoundingClientRect = () => - ({left: 11, top: 22, width: 111, height: 222}); + element.getBoundingClientRect = () => ({ + left: 11, + top: 22, + width: 111, + height: 222, + }); resource.measure(); expect(resource.getLayoutBox().top).to.equal(22); expect(resource.getInitialLayoutBox().top).to.equal(12); @@ -280,12 +378,17 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should always layout if has not been laid out before', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); resource.state_ = ResourceState.NOT_LAID_OUT; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; - elementMock.expects('getBoundingClientRect') - .returns(resource.layoutBox_).once(); + elementMock + .expects('getBoundingClientRect') + .returns(resource.layoutBox_) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); }); @@ -295,55 +398,86 @@ describes.realWin('Resource', {amp: true}, env => { resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Left is not part of validation. - elementMock.expects('getBoundingClientRect') - .returns({left: 11 + 10, top: 12, width: 111, height: 222}).once(); + elementMock + .expects('getBoundingClientRect') + .returns({left: 11 + 10, top: 12, width: 111, height: 222}) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); expect(resource.getLayoutBox().left).to.equal(11 + 10); }); - it('should not relayout if box changed but element didn\'t opt in', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); + it("should not relayout if box changed but element didn't opt in", () => { + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); resource.state_ = ResourceState.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111 + 10, height: 222}).once(); - elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111 + 10, height: 222}) + .once(); + elementMock + .expects('isRelayoutNeeded') + .returns(false) + .atLeast(1); resource.measure(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should relayout if box changed when element opted in', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); resource.state_ = ResourceState.LAYOUT_COMPLETE; resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; // Width changed. - elementMock.expects('getBoundingClientRect') - .returns({left: 11, top: 12, width: 111 + 10, height: 222}).once(); - elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns({left: 11, top: 12, width: 111 + 10, height: 222}) + .once(); + elementMock + .expects('isRelayoutNeeded') + .returns(true) + .atLeast(1); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); expect(resource.getLayoutBox().width).to.equal(111 + 10); }); it('should calculate NOT fixed for non-displayed elements', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 0, 0)).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 0, 0)) + .once(); element.isAlwaysFixed = () => true; resource.measure(); expect(resource.isFixed()).to.be.false; }); it('should calculate fixed for always-fixed parent', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 10, 10)).once(); - viewportMock.expects('getScrollTop').returns(11).atLeast(0); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 10, 10)) + .once(); + viewportMock + .expects('getScrollTop') + .returns(11) + .atLeast(0); Object.defineProperty(element, 'offsetParent', { value: { isAlwaysFixed: () => true, @@ -357,22 +491,32 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should calculate fixed for fixed-style parent', () => { - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect').returns( - layoutRectLtwh(0, 0, 10, 10)).once(); - viewportMock.expects('getScrollTop').returns(11).atLeast(0); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns(layoutRectLtwh(0, 0, 10, 10)) + .once(); + viewportMock + .expects('getScrollTop') + .returns(11) + .atLeast(0); const fixedParent = doc.createElement('div'); fixedParent.style.position = 'fixed'; doc.body.appendChild(fixedParent); fixedParent.appendChild(element); - viewportMock.expects('isDeclaredFixed') - .withExactArgs(element) - .returns(false) - .once(); - viewportMock.expects('isDeclaredFixed') - .withExactArgs(fixedParent) - .returns(true) - .once(); + viewportMock + .expects('isDeclaredFixed') + .withExactArgs(element) + .returns(false) + .once(); + viewportMock + .expects('isDeclaredFixed') + .withExactArgs(fixedParent) + .returns(true) + .once(); resource.measure(); expect(resource.isFixed()).to.be.true; // layoutBox != pageLayoutBox @@ -391,15 +535,24 @@ describes.realWin('Resource', {amp: true}, env => { writable: true, }); element.parentElement.__AMP__RESOURCE = {}; - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('build').returns(Promise.resolve()).once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('build') + .returns(Promise.resolve()) + .once(); rect = {left: 11, top: 12, width: 111, height: 222}; resource = new Resource(1, element, resources); return resource.build(); }); it('should measure placeholder with stubbed parent', () => { - elementMock.expects('getBoundingClientRect').returns(rect).once(); + elementMock + .expects('getBoundingClientRect') + .returns(rect) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); @@ -420,7 +573,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should support abnormal case with no parent', () => { delete element.parentElement; - elementMock.expects('getBoundingClientRect').returns(rect).once(); + elementMock + .expects('getBoundingClientRect') + .returns(rect) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); @@ -430,7 +586,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should support abnormal case with non-AMP parent', () => { element.parentElement = document.createElement('div'); - elementMock.expects('getBoundingClientRect').returns(rect).once(); + elementMock + .expects('getBoundingClientRect') + .returns(rect) + .once(); resource.measure(); expect(resource.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); @@ -441,11 +600,14 @@ describes.realWin('Resource', {amp: true}, env => { it('should hide and update layout box on collapse', () => { resource.layoutBox_ = {left: 11, top: 12, width: 111, height: 222}; resource.isFixed_ = true; - elementMock.expects('updateLayoutBox') - .withExactArgs(sinon.match(data => { + elementMock + .expects('updateLayoutBox') + .withExactArgs( + sinon.match(data => { return data.width == 0 && data.height == 0; - })) - .once(); + }) + ) + .once(); const owner = { collapsedCallback: sandbox.spy(), }; @@ -471,41 +633,47 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.requestMeasure).to.be.calledOnce; }); - it('should ignore startLayout if already completed or failed or going', - () => { - elementMock.expects('layoutCallback').never(); + it('should ignore startLayout if already completed or failed or going', () => { + elementMock.expects('layoutCallback').never(); - resource.state_ = ResourceState.LAYOUT_COMPLETE; - resource.startLayout(); + resource.state_ = ResourceState.LAYOUT_COMPLETE; + resource.startLayout(); - resource.state_ = ResourceState.LAYOUT_FAILED; - resource.startLayout(); + resource.state_ = ResourceState.LAYOUT_FAILED; + resource.startLayout(); - resource.state_ = ResourceState.READY_FOR_LAYOUT; - resource.layoutPromise_ = {}; - resource.startLayout(); - }); + resource.state_ = ResourceState.READY_FOR_LAYOUT; + resource.layoutPromise_ = {}; + resource.startLayout(); + }); it('should fail startLayout if not built', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState.NOT_BUILT; - allowConsoleError(() => { expect(() => { - resource.startLayout(); - }).to.throw(/Not ready to start layout/); }); + allowConsoleError(() => { + expect(() => { + resource.startLayout(); + }).to.throw(/Not ready to start layout/); + }); }); it('should ignore startLayout if not visible', () => { elementMock.expects('layoutCallback').never(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 0, height: 0}; - allowConsoleError(() => { expect(() => { - resource.startLayout(); - }).to.throw(/Not displayed/); }); + allowConsoleError(() => { + expect(() => { + resource.startLayout(); + }).to.throw(/Not displayed/); + }); }); it('should force startLayout for first layout', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; @@ -519,24 +687,36 @@ describes.realWin('Resource', {amp: true}, env => { resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.layoutCount_ = 1; - elementMock.expects('isRelayoutNeeded').returns(false).atLeast(1); + elementMock + .expects('isRelayoutNeeded') + .returns(false) + .atLeast(1); resource.startLayout(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); it('should force startLayout for re-layout when opt-in', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; resource.layoutCount_ = 1; - elementMock.expects('isRelayoutNeeded').returns(true).atLeast(1); + elementMock + .expects('isRelayoutNeeded') + .returns(true) + .atLeast(1); resource.startLayout(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_SCHEDULED); }); it('should complete startLayout', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; @@ -553,8 +733,14 @@ describes.realWin('Resource', {amp: true}, env => { }); it('should complete startLayout with height == 0', () => { - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); - elementMock.expects('getLayout').returns('fluid').once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); + elementMock + .expects('getLayout') + .returns('fluid') + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 0}; @@ -572,8 +758,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should fail startLayout', () => { const error = new Error('intentional'); - elementMock.expects('layoutCallback') - .returns(Promise.reject(error)).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.reject(error)) + .once(); resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = {left: 11, top: 12, width: 10, height: 10}; @@ -581,22 +769,30 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.layoutPromise_).to.not.equal(null); expect(resource.getState()).to.equal(ResourceState.LAYOUT_SCHEDULED); - return promise.then(() => { - /* global fail: false */ - fail('should not be here'); - }, () => { - expect(resource.getState()).to.equal(ResourceState.LAYOUT_FAILED); - expect(resource.layoutPromise_).to.equal(null); - expect(resource.lastLayoutError_).to.equal(error); - - // Should fail with the same error again. - return resource.startLayout(); - }).then(() => { - /* global fail: false */ - fail('should not be here'); - }, reason => { - expect(reason).to.equal(error); - }); + return promise + .then( + () => { + /* global fail: false */ + fail('should not be here'); + }, + () => { + expect(resource.getState()).to.equal(ResourceState.LAYOUT_FAILED); + expect(resource.layoutPromise_).to.equal(null); + expect(resource.lastLayoutError_).to.equal(error); + + // Should fail with the same error again. + return resource.startLayout(); + } + ) + .then( + () => { + /* global fail: false */ + fail('should not be here'); + }, + reason => { + expect(reason).to.equal(error); + } + ); }); it('should record layout schedule time', () => { @@ -622,16 +818,20 @@ describes.realWin('Resource', {amp: true}, env => { it('should change size and update state', () => { expect(resource.isMeasureRequested()).to.be.false; resource.state_ = ResourceState.READY_FOR_LAYOUT; - elementMock.expects('changeSize').withExactArgs(111, 222, - {top: 1, right: 2, bottom: 3, left: 4}).once(); + elementMock + .expects('changeSize') + .withExactArgs(111, 222, {top: 1, right: 2, bottom: 3, left: 4}) + .once(); resource.changeSize(111, 222, {top: 1, right: 2, bottom: 3, left: 4}); expect(resource.isMeasureRequested()).to.be.true; }); it('should change size but not state', () => { resource.state_ = ResourceState.NOT_BUILT; - elementMock.expects('changeSize').withExactArgs(111, 222, - {top: 1, right: 2, bottom: 3, left: 4}).once(); + elementMock + .expects('changeSize') + .withExactArgs(111, 222, {top: 1, right: 2, bottom: 3, left: 4}) + .once(); resource.changeSize(111, 222, {top: 1, right: 2, bottom: 3, left: 4}); expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); }); @@ -652,11 +852,15 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getLayoutPriority()).to.equal(LayoutPriority.CONTENT); }); - describe('setInViewport', () => { let resolveWithinViewportSpy; - beforeEach(() => resolveWithinViewportSpy = - sandbox.spy(resource, 'resolveDeferredsWhenWithinViewports_')); + beforeEach( + () => + (resolveWithinViewportSpy = sandbox.spy( + resource, + 'resolveDeferredsWhenWithinViewports_' + )) + ); it('should call viewportCallback when not built', () => { resource.state_ = ResourceState.NOT_BUILT; @@ -700,11 +904,17 @@ describes.realWin('Resource', {amp: true}, env => { hasAttribute: () => false, isBuilt: () => false, contains: () => true, - getElementsByClassName: () => {return [];}, + getElementsByClassName: () => { + return []; + }, parentElement: child, }; - parent.getElementsByClassName = () => {return [child, grandChild];}; - child.getElementsByClassName = () => {return [grandChild];}; + parent.getElementsByClassName = () => { + return [child, grandChild]; + }; + child.getElementsByClassName = () => { + return [grandChild]; + }; resources = new Resources(new AmpDocSingle(window)); parentResource = new Resource(1, parent, resources); }); @@ -751,55 +961,94 @@ describes.realWin('Resource', {amp: true}, env => { expect(resource.getState()).to.equal(ResourceState.NOT_BUILT); }); - it('should call unlayoutCallback on built element and update state', - () => { - resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(true).once(); - elementMock.expects('togglePlaceholder').withArgs(true).once(); - resource.unlayout(); - expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); - }); + it('should call unlayoutCallback on built element and update state', () => { + resource.state_ = ResourceState.LAYOUT_COMPLETE; + elementMock + .expects('unlayoutCallback') + .returns(true) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .once(); + resource.unlayout(); + expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); + }); it('updated state should bypass isRelayoutNeeded', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(true).once(); - elementMock.expects('togglePlaceholder').withArgs(true).once(); - elementMock.expects('isUpgraded').returns(true).atLeast(1); - elementMock.expects('getBoundingClientRect') - .returns({left: 1, top: 1, width: 1, height: 1}).once(); + elementMock + .expects('unlayoutCallback') + .returns(true) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .once(); + elementMock + .expects('isUpgraded') + .returns(true) + .atLeast(1); + elementMock + .expects('getBoundingClientRect') + .returns({left: 1, top: 1, width: 1, height: 1}) + .once(); resource.unlayout(); - elementMock.expects('layoutCallback').returns(Promise.resolve()).once(); + elementMock + .expects('layoutCallback') + .returns(Promise.resolve()) + .once(); resource.measure(); resource.startLayout(); }); - it('should call unlayoutCallback on built element' + - ' but NOT update state', () => { - resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(false).once(); - elementMock.expects('togglePlaceholder').withArgs(true).never(); - resource.unlayout(); - expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); - }); + it( + 'should call unlayoutCallback on built element' + ' but NOT update state', + () => { + resource.state_ = ResourceState.LAYOUT_COMPLETE; + elementMock + .expects('unlayoutCallback') + .returns(false) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .never(); + resource.unlayout(); + expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); + } + ); it('should call viewportCallback when resource not in viewport', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('viewportCallback').withExactArgs(false).once(); + elementMock + .expects('viewportCallback') + .withExactArgs(false) + .once(); resource.unlayout(); }); it('should call viewportCallback when resource in viewport', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('viewportCallback').withExactArgs(false).once(); + elementMock + .expects('viewportCallback') + .withExactArgs(false) + .once(); resource.unlayout(); }); it('should delegate unload to unlayoutCallback', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; - elementMock.expects('unlayoutCallback').returns(false).once(); - elementMock.expects('togglePlaceholder').withArgs(true).never(); + elementMock + .expects('unlayoutCallback') + .returns(false) + .once(); + elementMock + .expects('togglePlaceholder') + .withArgs(true) + .never(); resource.unload(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); @@ -827,13 +1076,19 @@ describes.realWin('Resource', {amp: true}, env => { describe('when unlayoutOnPause', () => { beforeEach(() => { - elementMock.expects('unlayoutOnPause').returns(true).once(); + elementMock + .expects('unlayoutOnPause') + .returns(true) + .once(); }); it('should call unlayoutCallback and update state', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; elementMock.expects('pauseCallback').once(); - elementMock.expects('unlayoutCallback').returns(true).once(); + elementMock + .expects('unlayoutCallback') + .returns(true) + .once(); resource.pause(); expect(resource.getState()).to.equal(ResourceState.NOT_LAID_OUT); }); @@ -841,7 +1096,10 @@ describes.realWin('Resource', {amp: true}, env => { it('should call unlayoutCallback but NOT update state', () => { resource.state_ = ResourceState.LAYOUT_COMPLETE; elementMock.expects('pauseCallback').once(); - elementMock.expects('unlayoutCallback').returns(false).once(); + elementMock + .expects('unlayoutCallback') + .returns(false) + .once(); resource.pause(); expect(resource.getState()).to.equal(ResourceState.LAYOUT_COMPLETE); }); @@ -938,8 +1196,7 @@ describe('Resource idleRenderOutsideViewport', () => { }; resources = new Resources(new AmpDocSingle(window)); resource = new Resource(1, element, resources); - isWithinViewportRatio = - sandbox.stub(resource, 'isWithinViewportRatio'); + isWithinViewportRatio = sandbox.stub(resource, 'isWithinViewportRatio'); }); afterEach(() => { @@ -998,15 +1255,16 @@ describe('Resource renderOutsideViewport', () => { viewport = resources.viewport_; renderOutsideViewport = sandbox.stub(element, 'renderOutsideViewport'); sandbox.stub(viewport, 'getRect').returns(layoutRectLtwh(0, 0, 100, 100)); - resolveWithinViewportSpy = - sandbox.spy(resource, 'resolveDeferredsWhenWithinViewports_'); + resolveWithinViewportSpy = sandbox.spy( + resource, + 'resolveDeferredsWhenWithinViewports_' + ); }); afterEach(() => { sandbox.restore(); }); - describe('boolean API', () => { describe('when element returns true', () => { beforeEach(() => { @@ -2017,13 +2275,19 @@ describe('Resource renderOutsideViewport', () => { describe('whenWithinViewport', () => { it('should resolve correctly', () => { - sandbox.stub(resource, 'isWithinViewportRatio').withArgs(3) - .onCall(0).returns(false) - .onCall(1).returns(false) - .onCall(2).returns(true) - .onCall(3).callsFake(() => { - throw new Error('should not call!'); - }); + sandbox + .stub(resource, 'isWithinViewportRatio') + .withArgs(3) + .onCall(0) + .returns(false) + .onCall(1) + .returns(false) + .onCall(2) + .returns(true) + .onCall(3) + .callsFake(() => { + throw new Error('should not call!'); + }); const promise = resource.whenWithinViewport(3); // Multiple calls should return the same promise. expect(resource.whenWithinViewport(3)).to.equal(promise); @@ -2040,8 +2304,10 @@ describe('Resource renderOutsideViewport', () => { }); it('should resolve correctly with float', () => { - const isWithinViewportRatioStub = - sandbox.stub(resource, 'isWithinViewportRatio'); + const isWithinViewportRatioStub = sandbox.stub( + resource, + 'isWithinViewportRatio' + ); const ratio = {}; sandbox.stub(resource, 'getDistanceViewportRatio').returns(ratio); isWithinViewportRatioStub.withArgs(1.25).returns(false); diff --git a/test/unit/test-resources.js b/test/unit/test-resources.js index 029df6b0e3a51..902d5fe5fa85c 100644 --- a/test/unit/test-resources.js +++ b/test/unit/test-resources.js @@ -26,7 +26,6 @@ import {loadPromise} from '../../src/event-helper'; /*eslint "google-camelcase/google-camelcase": 0*/ describe('Resources', () => { - let sandbox; let clock; let resources; @@ -278,162 +277,182 @@ describe('Resources', () => { expect(resources.calcTaskTimeout_(task_p1)).to.equal(1000); }); - it('should not schedule non-prerenderable resource when' + - ' document is in prerender', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => true, - prerenderAllowed: () => false, - renderOutsideViewport: () => false, - startLayout: () => {}, - applySizesAndMediaQuery: () => {}, - }; - resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(0); - }); - - it('should schedule prerenderable resource when' + - ' document is in prerender', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => true, - prerenderAllowed: () => true, - renderOutsideViewport: () => true, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#P', - applySizesAndMediaQuery: () => {}, - }; - resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; - }); - - it('should not schedule prerenderable resource when' + - ' document is hidden', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => true, - prerenderAllowed: () => true, - renderOutsideViewport: () => true, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#P', - applySizesAndMediaQuery: () => {}, - }; - resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.HIDDEN - ); - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(0); - }); - - it('should not schedule non-renderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => false, - idleRenderOutsideViewport: () => false, - startLayout: () => {}, - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(0); - }); - - it('should force schedule non-renderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => false, - idleRenderOutsideViewport: () => false, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#L', - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true, 0, /* force */ true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.true; - }); - - it('should schedule renderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => true, - idleRenderOutsideViewport: () => false, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#L', - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; - }); - - it('should schedule idleRenderOutsideViewport resource when' + - ' resource is not visible', () => { - const resource = { - getState: () => ResourceState.READY_FOR_LAYOUT, - isDisplayed: () => true, - isFixed: () => false, - isInViewport: () => false, - prerenderAllowed: () => true, - renderOutsideViewport: () => false, - idleRenderOutsideViewport: () => true, - getLayoutPriority: () => LayoutPriority.METADATA, - startLayout: () => {}, - layoutScheduled: () => {}, - getTaskId: () => 'resource#L', - applySizesAndMediaQuery: () => {}, - }; - resources.scheduleLayoutOrPreload_(resource, true); - expect(resources.queue_.getSize()).to.equal(1); - expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; - }); + it( + 'should not schedule non-prerenderable resource when' + + ' document is in prerender', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => true, + prerenderAllowed: () => false, + renderOutsideViewport: () => false, + startLayout: () => {}, + applySizesAndMediaQuery: () => {}, + }; + resources.visible_ = false; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(0); + } + ); + + it( + 'should schedule prerenderable resource when' + ' document is in prerender', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => true, + prerenderAllowed: () => true, + renderOutsideViewport: () => true, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#P', + applySizesAndMediaQuery: () => {}, + }; + resources.visible_ = false; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; + } + ); + + it( + 'should not schedule prerenderable resource when' + ' document is hidden', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => true, + prerenderAllowed: () => true, + renderOutsideViewport: () => true, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#P', + applySizesAndMediaQuery: () => {}, + }; + resources.visible_ = false; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.HIDDEN); + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(0); + } + ); + + it( + 'should not schedule non-renderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => false, + idleRenderOutsideViewport: () => false, + startLayout: () => {}, + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(0); + } + ); + + it( + 'should force schedule non-renderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => false, + idleRenderOutsideViewport: () => false, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#L', + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true, 0, /* force */ true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.true; + } + ); + + it( + 'should schedule renderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => true, + idleRenderOutsideViewport: () => false, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#L', + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; + } + ); + + it( + 'should schedule idleRenderOutsideViewport resource when' + + ' resource is not visible', + () => { + const resource = { + getState: () => ResourceState.READY_FOR_LAYOUT, + isDisplayed: () => true, + isFixed: () => false, + isInViewport: () => false, + prerenderAllowed: () => true, + renderOutsideViewport: () => false, + idleRenderOutsideViewport: () => true, + getLayoutPriority: () => LayoutPriority.METADATA, + startLayout: () => {}, + layoutScheduled: () => {}, + getTaskId: () => 'resource#L', + applySizesAndMediaQuery: () => {}, + }; + resources.scheduleLayoutOrPreload_(resource, true); + expect(resources.queue_.getSize()).to.equal(1); + expect(resources.queue_.tasks_[0].forceOutsideViewport).to.be.false; + } + ); it('should require layout for non-scheduled element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 100, 100)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 100, 100)); const resource = new Resource(1, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); - const scheduleStub = sandbox.stub( - resources, 'scheduleLayoutOrPreload_').callsFake( - () => resource.loadPromiseResolve_()); + const scheduleStub = sandbox + .stub(resources, 'scheduleLayoutOrPreload_') + .callsFake(() => resource.loadPromiseResolve_()); const promise = resources.requireLayout(resource.element); resource.build(); return Promise.all([promise, resource.whenBuilt()]).then(() => { @@ -444,8 +463,9 @@ describe('Resources', () => { it('should require layout for scheduled element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 100, 100)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 100, 100)); const resource = new Resource(1, element, resources); resource.layoutScheduled(); const measureSpy = sandbox.spy(resource, 'measure'); @@ -461,8 +481,9 @@ describe('Resources', () => { it('should not require layout for undisplayed element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 0, 0)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 0, 0)); const resource = new Resource(1, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); const scheduleStub = sandbox.stub(resources, 'scheduleLayoutOrPreload_'); @@ -476,8 +497,9 @@ describe('Resources', () => { it('should not require layout for already completed element', () => { const element = createAmpElement(); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 0, 0)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 0, 0)); const resource = new Resource(1, element, resources); resource.layoutComplete_(true); const measureSpy = sandbox.spy(resource, 'measure'); @@ -494,15 +516,17 @@ describe('Resources', () => { const parentElement = createAmpElement(); const element = createAmpElement(); parentElement.appendChild(element); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 10, 10)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 10, 10)); sandbox.stub(element, 'isBuilt').callsFake(() => true); const parentResource = new Resource(1, parentElement, resources); const resource = new Resource(2, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); const scheduleStub = sandbox.stub(resources, 'scheduleLayoutOrPreload_'); - resources.scheduleLayoutOrPreloadForSubresources_( - parentResource, true, [element]); + resources.scheduleLayoutOrPreloadForSubresources_(parentResource, true, [ + element, + ]); expect(measureSpy).to.be.calledOnce; expect(scheduleStub).to.be.calledOnce; }); @@ -511,23 +535,28 @@ describe('Resources', () => { const parentElement = createAmpElement(); const element = createAmpElement(); parentElement.appendChild(element); - sandbox.stub(element, 'getBoundingClientRect').callsFake( - () => layoutRectLtwh(0, 0, 10, 10)); + sandbox + .stub(element, 'getBoundingClientRect') + .callsFake(() => layoutRectLtwh(0, 0, 10, 10)); sandbox.stub(element, 'isBuilt').callsFake(() => false); const parentResource = new Resource(1, parentElement, resources); const resource = new Resource(2, element, resources); const measureSpy = sandbox.spy(resource, 'measure'); const scheduleStub = sandbox.stub(resources, 'scheduleLayoutOrPreload_'); - resources.scheduleLayoutOrPreloadForSubresources_( - parentResource, true, [element]); + resources.scheduleLayoutOrPreloadForSubresources_(parentResource, true, [ + element, + ]); expect(measureSpy).to.not.be.called; expect(scheduleStub).to.not.be.called; - return resource.build().then(() => { - return element.whenBuilt(); - }).then(() => { - expect(measureSpy).to.be.calledOnce; - expect(scheduleStub).to.be.calledOnce; - }); + return resource + .build() + .then(() => { + return element.whenBuilt(); + }) + .then(() => { + expect(measureSpy).to.be.calledOnce; + expect(scheduleStub).to.be.calledOnce; + }); }); it('should update priority and schedule pass', () => { @@ -573,116 +602,137 @@ describe('Resources', () => { }); }); - -describes.fakeWin('Resources startup', { - win: { - readyState: 'loading', +describes.fakeWin( + 'Resources startup', + { + win: { + readyState: 'loading', + }, + amp: true, }, - amp: true, -}, env => { - let win; - let clock; - let sandbox; - let resources; - let schedulePassStub; - - beforeEach(() => { - win = env.win; - sandbox = sinon.sandbox; - clock = sandbox.useFakeTimers(); - resources = Services.resourcesForDoc(win.document.body); - resources.relayoutAll_ = false; - schedulePassStub = sandbox.stub(resources, 'schedulePass'); - }); + env => { + let win; + let clock; + let sandbox; + let resources; + let schedulePassStub; - afterEach(() => { - sandbox.restore(); - }); + beforeEach(() => { + win = env.win; + sandbox = sinon.sandbox; + clock = sandbox.useFakeTimers(); + resources = Services.resourcesForDoc(win.document.body); + resources.relayoutAll_ = false; + schedulePassStub = sandbox.stub(resources, 'schedulePass'); + }); - it('should run a full reload pass on window.onload', () => { - expect(resources.relayoutAll_).to.be.false; - expect(schedulePassStub).to.not.be.called; - win.readyState = 'complete'; - win.eventListeners.fire({type: 'load'}); - win.document.eventListeners.fire({type: 'readystatechange'}); - return resources.ampdoc.whenReady().then(() => { - return loadPromise(win); - }).then(() => { - expect(resources.relayoutAll_).to.be.true; - expect(schedulePassStub).to.have.been.called; + afterEach(() => { + sandbox.restore(); }); - }); - it('should run a full reload pass on fonts timeout', () => { - win.readyState = 'complete'; - win.document.eventListeners.fire({type: 'readystatechange'}); - return resources.ampdoc.whenReady().then(() => { + it('should run a full reload pass on window.onload', () => { expect(resources.relayoutAll_).to.be.false; expect(schedulePassStub).to.not.be.called; - clock.tick(3100); - }).then(() => { - expect(resources.relayoutAll_).to.be.true; - expect(schedulePassStub).to.have.been.called; + win.readyState = 'complete'; + win.eventListeners.fire({type: 'load'}); + win.document.eventListeners.fire({type: 'readystatechange'}); + return resources.ampdoc + .whenReady() + .then(() => { + return loadPromise(win); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.true; + expect(schedulePassStub).to.have.been.called; + }); }); - }); - it('should run a full reload pass on document.fonts.ready', () => { - win.readyState = 'interactive'; - win.document.eventListeners.fire({type: 'readystatechange'}); - win.document.fonts.status = 'loading'; - return resources.ampdoc.whenReady().then(() => { + it('should run a full reload pass on fonts timeout', () => { + win.readyState = 'complete'; + win.document.eventListeners.fire({type: 'readystatechange'}); + return resources.ampdoc + .whenReady() + .then(() => { + expect(resources.relayoutAll_).to.be.false; + expect(schedulePassStub).to.not.be.called; + clock.tick(3100); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.true; + expect(schedulePassStub).to.have.been.called; + }); + }); - }).then(() => { - // This is the regular remeasure on doc-ready. - expect(resources.relayoutAll_).to.be.true; - resources.relayoutAll_ = false; - return win.document.fonts.ready; - }).then(() => { - // Wait one micro task. - return Promise.resolve(); - }).then(() => { - expect(resources.relayoutAll_).to.be.true; - // Remeasure on doc-ready and fonts-ready. - expect(schedulePassStub).to.have.been.calledTwice; + it('should run a full reload pass on document.fonts.ready', () => { + win.readyState = 'interactive'; + win.document.eventListeners.fire({type: 'readystatechange'}); + win.document.fonts.status = 'loading'; + return resources.ampdoc + .whenReady() + .then(() => {}) + .then(() => { + // This is the regular remeasure on doc-ready. + expect(resources.relayoutAll_).to.be.true; + resources.relayoutAll_ = false; + return win.document.fonts.ready; + }) + .then(() => { + // Wait one micro task. + return Promise.resolve(); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.true; + // Remeasure on doc-ready and fonts-ready. + expect(schedulePassStub).to.have.been.calledTwice; + }); }); - }); - it('should not remeasure if fonts load before doc-ready', () => { - win.readyState = 'interactive'; - win.document.eventListeners.fire({type: 'readystatechange'}); - win.document.fonts.status = 'loaded'; - return resources.ampdoc.whenReady().then(() => { + it('should not remeasure if fonts load before doc-ready', () => { + win.readyState = 'interactive'; + win.document.eventListeners.fire({type: 'readystatechange'}); + win.document.fonts.status = 'loaded'; + return resources.ampdoc + .whenReady() + .then(() => {}) + .then(() => { + // This is the regular remeasure on doc-ready. + expect(resources.relayoutAll_).to.be.true; + resources.relayoutAll_ = false; + return win.document.fonts.ready; + }) + .then(() => { + // Wait one micro task. + return Promise.resolve(); + }) + .then(() => { + expect(resources.relayoutAll_).to.be.false; + // Only remeasure on doc-ready. + expect(schedulePassStub).to.have.been.calledOnce; + }); + }); - }).then(() => { - // This is the regular remeasure on doc-ready. - expect(resources.relayoutAll_).to.be.true; - resources.relayoutAll_ = false; - return win.document.fonts.ready; - }).then(() => { - // Wait one micro task. - return Promise.resolve(); - }).then(() => { + it('should run a full reload when a new element is connected', () => { expect(resources.relayoutAll_).to.be.false; - // Only remeasure on doc-ready. - expect(schedulePassStub).to.have.been.calledOnce; + expect(schedulePassStub).to.not.be.called; + const el = win.document.createElement('amp-img'); + el.isBuilt = () => { + return true; + }; + el.isUpgraded = () => { + return true; + }; + el.isRelayoutNeeded = () => { + return true; + }; + el.updateLayoutBox = () => {}; + win.document.body.appendChild(el); + resources.add(el); + expect(resources.relayoutAll_).to.be.false; + clock.tick(1000); + expect(resources.relayoutAll_).to.be.true; }); - }); - - it('should run a full reload when a new element is connected', () => { - expect(resources.relayoutAll_).to.be.false; - expect(schedulePassStub).to.not.be.called; - const el = win.document.createElement('amp-img'); - el.isBuilt = () => { return true; }; - el.isUpgraded = () => { return true; }; - el.isRelayoutNeeded = () => { return true; }; - el.updateLayoutBox = () => {}; - win.document.body.appendChild(el); - resources.add(el); - expect(resources.relayoutAll_).to.be.false; - clock.tick(1000); - expect(resources.relayoutAll_).to.be.true; - }); -}); + } +); describes.realWin('getElementLayoutBox', {}, env => { let win; @@ -691,9 +741,15 @@ describes.realWin('getElementLayoutBox', {}, env => { let vsyncSpy; function addResourceForElement(id, element) { - element.isBuilt = () => { return true; }; - element.isUpgraded = () => { return true; }; - element.isRelayoutNeeded = () => { return true; }; + element.isBuilt = () => { + return true; + }; + element.isUpgraded = () => { + return true; + }; + element.isRelayoutNeeded = () => { + return true; + }; element.updateLayoutBox = () => {}; const resource = new Resource(id, element, resources); resource.state_ = ResourceState.LAYOUT_COMPLETE; @@ -782,157 +838,159 @@ describes.realWin('getElementLayoutBox', {}, env => { }); }); +describes.realWin( + 'Resources pause/resume/unlayout scheduling', + { + amp: true, + }, + env => { + let win, doc; + let resources; + let parent; + let children; + let child0; + let child1; + let child2; -describes.realWin('Resources pause/resume/unlayout scheduling', { - amp: true, -}, env => { - let win, doc; - let resources; - let parent; - let children; - let child0; - let child1; - let child2; - - beforeEach(() => { - win = env.win; - doc = win.document; - resources = new Resources(env.ampdoc); - resources.isRuntimeOn_ = false; - const parentTuple = createElementWithResource(1); - parent = parentTuple[0]; - child0 = doc.createElement('div'); - child1 = createElementWithResource(2)[0]; - child2 = createElementWithResource(3)[0]; - children = [child0, child1, child2]; - children.forEach(child => { - parent.appendChild(child); + beforeEach(() => { + win = env.win; + doc = win.document; + resources = new Resources(env.ampdoc); + resources.isRuntimeOn_ = false; + const parentTuple = createElementWithResource(1); + parent = parentTuple[0]; + child0 = doc.createElement('div'); + child1 = createElementWithResource(2)[0]; + child2 = createElementWithResource(3)[0]; + children = [child0, child1, child2]; + children.forEach(child => { + parent.appendChild(child); + }); }); - }); - function createElement() { - const element = env.createAmpElement('amp-test'); - sandbox.stub(element, 'isBuilt').callsFake(() => true); - return element; - } + function createElement() { + const element = env.createAmpElement('amp-test'); + sandbox.stub(element, 'isBuilt').callsFake(() => true); + return element; + } - function createElementWithResource(id) { - const element = createElement(); - const resource = new Resource(id, element, resources); - resource.state_ = ResourceState.LAYOUT_COMPLETE; - resource.element['__AMP__RESOURCE'] = resource; - return [element, resource]; - } + function createElementWithResource(id) { + const element = createElement(); + const resource = new Resource(id, element, resources); + resource.state_ = ResourceState.LAYOUT_COMPLETE; + resource.element['__AMP__RESOURCE'] = resource; + return [element, resource]; + } - describe('schedulePause', () => { - it('should not throw with a single element', () => { - expect(() => { - resources.schedulePause(parent, child1); - }).to.not.throw(); - }); + describe('schedulePause', () => { + it('should not throw with a single element', () => { + expect(() => { + resources.schedulePause(parent, child1); + }).to.not.throw(); + }); - it('should not throw with an array of elements', () => { - expect(() => { - resources.schedulePause(parent, [child1, child2]); - }).to.not.throw(); - }); + it('should not throw with an array of elements', () => { + expect(() => { + resources.schedulePause(parent, [child1, child2]); + }).to.not.throw(); + }); + + it('should be ok with non amp children', () => { + expect(() => { + resources.schedulePause(parent, children); + resources.schedulePause(parent, child0); + }).to.not.throw(); + }); + + it('should call pauseCallback on custom element', () => { + const stub1 = sandbox.stub(child1, 'pauseCallback'); + const stub2 = sandbox.stub(child2, 'pauseCallback'); - it('should be ok with non amp children', () => { - expect(() => { resources.schedulePause(parent, children); - resources.schedulePause(parent, child0); - }).to.not.throw(); - }); + expect(stub1.calledOnce).to.be.true; + expect(stub2.calledOnce).to.be.true; + }); - it('should call pauseCallback on custom element', () => { - const stub1 = sandbox.stub(child1, 'pauseCallback'); - const stub2 = sandbox.stub(child2, 'pauseCallback'); + it('should call unlayoutCallback when unlayoutOnPause', () => { + const stub1 = sandbox.stub(child1, 'unlayoutCallback'); + const stub2 = sandbox.stub(child2, 'unlayoutCallback'); + sandbox.stub(child1, 'unlayoutOnPause').returns(true); - resources.schedulePause(parent, children); - expect(stub1.calledOnce).to.be.true; - expect(stub2.calledOnce).to.be.true; + resources.schedulePause(parent, children); + expect(stub1.calledOnce).to.be.true; + expect(stub2.calledOnce).to.be.false; + }); }); - it('should call unlayoutCallback when unlayoutOnPause', () => { - const stub1 = sandbox.stub(child1, 'unlayoutCallback'); - const stub2 = sandbox.stub(child2, 'unlayoutCallback'); - sandbox.stub(child1, 'unlayoutOnPause').returns(true); + describe('scheduleResume', () => { + beforeEach(() => { + // Pause one child. + resources.schedulePause(parent, child1); + }); - resources.schedulePause(parent, children); - expect(stub1.calledOnce).to.be.true; - expect(stub2.calledOnce).to.be.false; - }); - }); + it('should not throw with a single element', () => { + expect(() => { + resources.scheduleResume(parent, child1); + }).to.not.throw(); + }); - describe('scheduleResume', () => { - beforeEach(() => { - // Pause one child. - resources.schedulePause(parent, child1); - }); + it('should not throw with an array of elements', () => { + expect(() => { + resources.scheduleResume(parent, [child1, child2]); + }).to.not.throw(); + }); - it('should not throw with a single element', () => { - expect(() => { - resources.scheduleResume(parent, child1); - }).to.not.throw(); - }); + it('should be ok with non amp children', () => { + expect(() => { + resources.scheduleResume(parent, children); + resources.scheduleResume(parent, child0); + }).to.not.throw(); + }); - it('should not throw with an array of elements', () => { - expect(() => { - resources.scheduleResume(parent, [child1, child2]); - }).to.not.throw(); - }); + it('should call resumeCallback on paused custom elements', () => { + const stub1 = sandbox.stub(child1, 'resumeCallback'); - it('should be ok with non amp children', () => { - expect(() => { resources.scheduleResume(parent, children); - resources.scheduleResume(parent, child0); - }).to.not.throw(); - }); + expect(stub1.calledOnce).to.be.true; + }); - it('should call resumeCallback on paused custom elements', () => { - const stub1 = sandbox.stub(child1, 'resumeCallback'); + it('should call resumeCallback on non-paused custom elements', () => { + const stub2 = sandbox.stub(child2, 'resumeCallback'); - resources.scheduleResume(parent, children); - expect(stub1.calledOnce).to.be.true; + resources.scheduleResume(parent, children); + expect(stub2.calledOnce).to.be.true; + }); }); - it('should call resumeCallback on non-paused custom elements', () => { - const stub2 = sandbox.stub(child2, 'resumeCallback'); - - resources.scheduleResume(parent, children); - expect(stub2.calledOnce).to.be.true; - }); - }); + describe('scheduleUnlayout', () => { + it('should not throw with a single element', () => { + expect(() => { + resources.scheduleUnlayout(parent, child1); + }).to.not.throw(); + }); - describe('scheduleUnlayout', () => { - it('should not throw with a single element', () => { - expect(() => { - resources.scheduleUnlayout(parent, child1); - }).to.not.throw(); - }); + it('should not throw with an array of elements', () => { + expect(() => { + resources.scheduleUnlayout(parent, [child1, child2]); + }).to.not.throw(); + }); - it('should not throw with an array of elements', () => { - expect(() => { - resources.scheduleUnlayout(parent, [child1, child2]); - }).to.not.throw(); - }); + it('should be ok with non amp children', () => { + expect(() => { + resources.scheduleUnlayout(parent, children); + }).to.not.throw(); + }); - it('should be ok with non amp children', () => { - expect(() => { + it('should schedule on custom element with multiple children', () => { + const stub1 = sandbox.stub(child1, 'unlayoutCallback'); + const stub2 = sandbox.stub(child2, 'unlayoutCallback'); resources.scheduleUnlayout(parent, children); - }).to.not.throw(); - }); - - it('should schedule on custom element with multiple children', () => { - const stub1 = sandbox.stub(child1, 'unlayoutCallback'); - const stub2 = sandbox.stub(child2, 'unlayoutCallback'); - resources.scheduleUnlayout(parent, children); - expect(stub1.called).to.be.true; - expect(stub2.called).to.be.true; + expect(stub1.called).to.be.true; + expect(stub2.called).to.be.true; + }); }); - }); -}); - + } +); describes.realWin('Resources schedulePreload', {amp: true}, env => { let win, doc; @@ -1030,9 +1088,7 @@ describes.realWin('Resources schedulePreload', {amp: true}, env => { }); }); - describe('Resources discoverWork', () => { - function createElement(rect) { const signals = new Signals(); return { @@ -1097,8 +1153,9 @@ describe('Resources discoverWork', () => { resources.win = { document, getComputedStyle: el => { - return el.fakeComputedStyle ? - el.fakeComputedStyle : window.getComputedStyle(el); + return el.fakeComputedStyle + ? el.fakeComputedStyle + : window.getComputedStyle(el); }, }; @@ -1131,11 +1188,10 @@ describe('Resources discoverWork', () => { it('should measure unbuilt elements', () => { resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resource1.isBuilt = () => false; const mediaSpy = sandbox.stub(resource1.element, 'applySizesAndMediaQuery'); expect(resource1.hasBeenMeasured()).to.be.false; @@ -1149,11 +1205,10 @@ describe('Resources discoverWork', () => { it('should render two screens when visible', () => { resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1166,11 +1221,10 @@ describe('Resources discoverWork', () => { resource1.state_ = ResourceState.LAYOUT_COMPLETE; resource2.state_ = ResourceState.LAYOUT_COMPLETE; resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1182,18 +1236,17 @@ describe('Resources discoverWork', () => { resource2.state_ = ResourceState.LAYOUT_COMPLETE; resource1.hasBeenMeasured = () => true; resource2.hasBeenMeasured = () => true; - resource1.element.getBoundingClientRect = - () => layoutRectLtwh(10, 10, 100, 101); - resource2.element.getBoundingClientRect = - () => layoutRectLtwh(10, 1010, 100, 101); + resource1.element.getBoundingClientRect = () => + layoutRectLtwh(10, 10, 100, 101); + resource2.element.getBoundingClientRect = () => + layoutRectLtwh(10, 1010, 100, 101); resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); resources.relayoutAll_ = false; resources.relayoutTop_ = 1000; - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1206,12 +1259,11 @@ describe('Resources discoverWork', () => { it('should prerender only one screen with prerenderSize = 1', () => { resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); resources.prerenderSize_ = 1; - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 1009)); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 1009)); resources.discoverWork_(); @@ -1221,12 +1273,11 @@ describe('Resources discoverWork', () => { it('should NOT prerender anything with prerenderSize = 0', () => { resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); resources.prerenderSize_ = 0; - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resources.discoverWork_(); @@ -1238,17 +1289,18 @@ describe('Resources discoverWork', () => { resource1.state_ = ResourceState.LAYOUT_COMPLETE; resource2.state_ = ResourceState.LAYOUT_COMPLETE; resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); - - const resource1MeasureStub = sandbox.stub(resource1, 'measure').callsFake( - resource1.measure.bind(resource1)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); + + const resource1MeasureStub = sandbox + .stub(resource1, 'measure') + .callsFake(resource1.measure.bind(resource1)); const resource1UnloadStub = sandbox.stub(resource1, 'unload'); - const resource2MeasureStub = sandbox.stub(resource2, 'measure').callsFake( - resource2.measure.bind(resource2)); + const resource2MeasureStub = sandbox + .stub(resource2, 'measure') + .callsFake(resource2.measure.bind(resource2)); const resource2UnloadStub = sandbox.stub(resource2, 'unload'); // 1st pass: measure for the first time. @@ -1270,8 +1322,7 @@ describe('Resources discoverWork', () => { resource2.requestMeasure(); expect(resource1.isMeasureRequested()).to.be.true; expect(resource2.isMeasureRequested()).to.be.true; - resource2.element.getBoundingClientRect = - () => layoutRectLtwh(0, 0, 0, 0); // Equiv to display:none. + resource2.element.getBoundingClientRect = () => layoutRectLtwh(0, 0, 0, 0); // Equiv to display:none. resources.discoverWork_(); expect(resource1MeasureStub).to.have.callCount(2); expect(resource1UnloadStub).to.have.not.been.called; @@ -1293,11 +1344,13 @@ describe('Resources discoverWork', () => { resource1.layoutCallback = new Promise(unusedResolve => {}); resource1.unlayoutCallback = () => true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)).atLeast(1); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock + .expects('getRect') + .returns(layoutRectLtwh(0, 0, 300, 400)) + .atLeast(1); resources.discoverWork_(); expect(resources.queue_.getSize()).to.equal(2); @@ -1391,8 +1444,9 @@ describe('Resources discoverWork', () => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').callsFake( - () => 'prerender'); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .callsFake(() => 'prerender'); sandbox.stub(resource1, 'isInViewport').callsFake(() => true); sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => true); @@ -1411,8 +1465,9 @@ describe('Resources discoverWork', () => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').callsFake( - () => 'prerender'); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .callsFake(() => 'prerender'); sandbox.stub(resource1, 'isInViewport').callsFake(() => true); sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => false); @@ -1431,8 +1486,9 @@ describe('Resources discoverWork', () => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').callsFake( - () => 'hidden'); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .callsFake(() => 'hidden'); sandbox.stub(resource1, 'isInViewport').callsFake(() => true); sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => true); @@ -1449,11 +1505,10 @@ describe('Resources discoverWork', () => { // it.configure().skipSafari().run( it.skip('should update inViewport before scheduling layouts', () => { resources.visible_ = true; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.VISIBLE - ); - viewportMock.expects('getRect').returns( - layoutRectLtwh(0, 0, 300, 400)); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.VISIBLE); + viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); const setInViewport = sandbox.spy(resource1, 'setInViewport'); const schedule = sandbox.spy(resources, 'scheduleLayoutOrPreload_'); @@ -1465,8 +1520,9 @@ describe('Resources discoverWork', () => { it('should not grant permission to build when threshold reached', () => { let hasBeenVisible = false; - sandbox.stub(resources.viewer_, 'hasBeenVisible').callsFake( - () => hasBeenVisible); + sandbox + .stub(resources.viewer_, 'hasBeenVisible') + .callsFake(() => hasBeenVisible); for (let i = 0; i < 20; i++) { expect(resources.grantBuildPermission()).to.be.true; @@ -1489,7 +1545,9 @@ describe('Resources discoverWork', () => { expect(resource1.build).to.be.calledOnce; expect(buildResourceSpy).calledWithExactly( - resource1, /* schedulePass */ true); + resource1, + /* schedulePass */ true + ); }); it('should build resource when not built and before doc ready', () => { @@ -1506,12 +1564,15 @@ describe('Resources discoverWork', () => { expect(resource1.build).to.be.calledOnce; expect(buildResourceSpy).calledWithExactly( - resource1, /* schedulePass */ true); + resource1, + /* schedulePass */ true + ); }); it('should NOT build non-prerenderable resources in prerender', () => { - sandbox.stub(resources.viewer_, 'getVisibilityState') - .returns(VisibilityState.PRERENDER); + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; @@ -1543,7 +1604,6 @@ describe('Resources discoverWork', () => { }); describe('getResourcesInRect', () => { - beforeEach(() => { resources.isRuntimeOn_ = false; resources.ampdoc.signals().signal('ready-scan'); @@ -1600,8 +1660,8 @@ describe('Resources discoverWork', () => { it('should ignore invisible elements', () => { const rect = layoutRectLtwh(0, 0, 100, 1500); - resource2.element.getBoundingClientRect = - () => layoutRectLtwh(0, 0, 0, 0); + resource2.element.getBoundingClientRect = () => + layoutRectLtwh(0, 0, 0, 0); return resources.getResourcesInRect(window, rect).then(res => { expect(res).to.have.length(1); expect(res[0]).to.equal(resource1); @@ -1637,7 +1697,6 @@ describe('Resources discoverWork', () => { }); describe('onNextPass', () => { - it('should only run callbacks once.', () => { resources.isRuntimeOn_ = true; resources.documentReady_ = true; @@ -1655,82 +1714,86 @@ describe('Resources discoverWork', () => { }); }); -describes.realWin('Resources contentHeight', { - amp: { - runtimeOn: true, +describes.realWin( + 'Resources contentHeight', + { + amp: { + runtimeOn: true, + }, }, -}, env => { - let win; - let resources; - let viewerSendMessageStub, viewportContentHeightChangedStub; + env => { + let win; + let resources; + let viewerSendMessageStub, viewportContentHeightChangedStub; - beforeEach(() => { - win = env.win; - resources = win.services.resources.obj; - viewerSendMessageStub = sandbox.stub(resources.viewer_, 'sendMessage'); - viewportContentHeightChangedStub = - sandbox.stub(resources.viewport_, 'contentHeightChanged'); - sandbox.stub(resources.vsync_, 'run').callsFake(task => { - task.measure({}); + beforeEach(() => { + win = env.win; + resources = win.services.resources.obj; + viewerSendMessageStub = sandbox.stub(resources.viewer_, 'sendMessage'); + viewportContentHeightChangedStub = sandbox.stub( + resources.viewport_, + 'contentHeightChanged' + ); + sandbox.stub(resources.vsync_, 'run').callsFake(task => { + task.measure({}); + }); }); - }); - - it('should measure initial contentHeight', () => { - const contentHeight = resources.viewport_.getContentHeight(); - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.documentReady_).to.equal(true); - expect(resources.contentHeight_).to.equal(contentHeight); - }); - it('should send contentHeight to viewer if height was changed', () => { - sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { - return 200; + it('should measure initial contentHeight', () => { + const contentHeight = resources.viewport_.getContentHeight(); + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.documentReady_).to.equal(true); + expect(resources.contentHeight_).to.equal(contentHeight); }); - resources.maybeChangeHeight_ = true; - resources.doPass(); + it('should send contentHeight to viewer if height was changed', () => { + sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { + return 200; + }); + resources.maybeChangeHeight_ = true; - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.contentHeight_).to.equal(200); - expect(viewerSendMessageStub).to.be.calledOnce; - expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); - expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); - expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); - expect(viewportContentHeightChangedStub).to.be.calledOnce; - }); + resources.doPass(); - it('should not send contentHeight to viewer if height is not changed', () => { - const contentHeight = resources.viewport_.getContentHeight(); - resources.maybeChangeHeight_ = true; + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.contentHeight_).to.equal(200); + expect(viewerSendMessageStub).to.be.calledOnce; + expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); + expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); + expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); + expect(viewportContentHeightChangedStub).to.be.calledOnce; + }); - resources.doPass(); + it('should not send contentHeight to viewer if height is not changed', () => { + const contentHeight = resources.viewport_.getContentHeight(); + resources.maybeChangeHeight_ = true; - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.contentHeight_).to.equal(contentHeight); - expect(viewerSendMessageStub).to.not.be.called; - expect(viewportContentHeightChangedStub).to.not.be.called; - }); + resources.doPass(); - it('should send contentHeight to viewer if viewport resizes', () => { - sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { - return 200; + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.contentHeight_).to.equal(contentHeight); + expect(viewerSendMessageStub).to.not.be.called; + expect(viewportContentHeightChangedStub).to.not.be.called; }); - resources.viewport_.changed_(/* relayoutAll */ true, /* velocity */ 0); - resources.doPass(); - expect(resources.maybeChangeHeight_).to.equal(false); - expect(resources.contentHeight_).to.equal(200); - expect(viewerSendMessageStub).to.be.calledOnce; - expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); - expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); - expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); - expect(viewportContentHeightChangedStub).to.be.calledOnce; - }); + it('should send contentHeight to viewer if viewport resizes', () => { + sandbox.stub(resources.viewport_, 'getContentHeight').callsFake(() => { + return 200; + }); + resources.viewport_.changed_(/* relayoutAll */ true, /* velocity */ 0); + resources.doPass(); -}); + expect(resources.maybeChangeHeight_).to.equal(false); + expect(resources.contentHeight_).to.equal(200); + expect(viewerSendMessageStub).to.be.calledOnce; + expect(viewerSendMessageStub.lastCall.args[0]).to.equal('documentHeight'); + expect(viewerSendMessageStub.lastCall.args[1].height).to.equal(200); + expect(viewerSendMessageStub.lastCall.args[2]).to.equal(true); + expect(viewportContentHeightChangedStub).to.be.calledOnce; + }); + } +); describe('Resources changeSize', () => { - function createElement(rect) { const signals = new Signals(); return { @@ -1759,9 +1822,11 @@ describe('Resources changeSize', () => { contains: unused_otherElement => false, updateLayoutBox: () => {}, togglePlaceholder: () => sandbox.spy(), - overflowCallback: - (unused_overflown, unused_requestedHeight, unused_requestedWidth) => { - }, + overflowCallback: ( + unused_overflown, + unused_requestedHeight, + unused_requestedWidth + ) => {}, getLayoutPriority: () => LayoutPriority.CONTENT, signals: () => signals, fakeComputedStyle: { @@ -1795,8 +1860,9 @@ describe('Resources changeSize', () => { resources.isRuntimeOn_ = false; resources.win = { getComputedStyle: el => { - return el.fakeComputedStyle ? - el.fakeComputedStyle : window.getComputedStyle(el); + return el.fakeComputedStyle + ? el.fakeComputedStyle + : window.getComputedStyle(el); }, }; viewportMock = sandbox.mock(resources.viewport_); @@ -1858,8 +1924,13 @@ describe('Resources changeSize', () => { }); it('should schedule margin only size change', () => { - resources.scheduleChangeSize_(resource1, undefined, undefined, - {top: 1, right: 2, bottom: 3, left: 4}, false); + resources.scheduleChangeSize_( + resource1, + undefined, + undefined, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); resources.vsync_.runScheduledTasks_(); expect(resources.requestsChangeSize_.length).to.equal(1); expect(resources.requestsChangeSize_[0].resource).to.equal(resource1); @@ -1883,7 +1954,7 @@ describe('Resources changeSize', () => { expect(resources.requestsChangeSize_[0].force).to.equal(true); }); - it('should NOT change size if it didn\'t change', () => { + it("should NOT change size if it didn't change", () => { resources.scheduleChangeSize_(resource1, 100, 100, undefined, true); resources.mutateWork_(); expect(resources.relayoutTop_).to.equal(-1); @@ -1957,19 +2028,38 @@ describe('Resources changeSize', () => { resource1.element.overflowCallback = overflowCallbackSpy; viewportRect = {top: 2, left: 0, right: 100, bottom: 200, height: 200}; - viewportMock.expects('getRect').returns(viewportRect).atLeast(1); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 50, - height: 50}; + viewportMock + .expects('getRect') + .returns(viewportRect) + .atLeast(1); + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 50, + height: 50, + }; vsyncSpy = sandbox.stub(resources.vsync_, 'run'); resources.visible_ = true; }); it('should NOT change size when height is unchanged', () => { const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210, - height: 50}; - resources.scheduleChangeSize_(resource1, 50, /* width */ undefined, - undefined, false, callback); + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 50, + }; + resources.scheduleChangeSize_( + resource1, + 50, + /* width */ undefined, + undefined, + false, + callback + ); resources.mutateWork_(); expect(resource1.changeSize).to.not.been.called; expect(overflowCallbackSpy).to.not.been.called; @@ -1979,16 +2069,27 @@ describe('Resources changeSize', () => { it('should NOT change size when height and margins are unchanged', () => { const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210, - height: 50}; + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 50, + }; resource1.element.fakeComputedStyle = { marginTop: '1px', marginRight: '2px', marginBottom: '3px', marginLeft: '4px', }; - resources.scheduleChangeSize_(resource1, 50, /* width */ undefined, - {top: 1, right: 2, bottom: 3, left: 4}, false, callback); + resources.scheduleChangeSize_( + resource1, + 50, + /* width */ undefined, + {top: 1, right: 2, bottom: 3, left: 4}, + false, + callback + ); expect(vsyncSpy).to.be.calledOnce; const task = vsyncSpy.lastCall.args[0]; @@ -2003,16 +2104,27 @@ describe('Resources changeSize', () => { it('should change size when margins but not height changed', () => { const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 210, - height: 50}; + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 50, + }; resource1.element.fakeComputedStyle = { marginTop: '1px', marginRight: '2px', marginBottom: '3px', marginLeft: '4px', }; - resources.scheduleChangeSize_(resource1, 50, /* width */ undefined, - {top: 1, right: 2, bottom: 4, left: 4}, false, callback); + resources.scheduleChangeSize_( + resource1, + 50, + /* width */ undefined, + {top: 1, right: 2, bottom: 4, left: 4}, + false, + callback + ); expect(vsyncSpy).to.be.calledOnce; const task = vsyncSpy.lastCall.args[0]; @@ -2032,19 +2144,20 @@ describe('Resources changeSize', () => { }); // TODO (#16156): duplicate stub for getVisibilityState on Safari - it.configure().skipSafari() - .run('should change size when document is invisible', () => { - resources.visible_ = false; - sandbox.stub(resources.viewer_, 'getVisibilityState').returns( - VisibilityState.PRERENDER - ); - resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); - }); + it.configure() + .skipSafari() + .run('should change size when document is invisible', () => { + resources.visible_ = false; + sandbox + .stub(resources.viewer_, 'getVisibilityState') + .returns(VisibilityState.PRERENDER); + resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); + }); it('should change size when active', () => { resource1.element.contains = () => true; @@ -2057,8 +2170,13 @@ describe('Resources changeSize', () => { }); it('should change size when below the viewport', () => { - resource1.layoutBox_ = {top: 10, left: 0, right: 100, bottom: 1050, - height: 50}; + resource1.layoutBox_ = { + top: 10, + left: 0, + right: 100, + bottom: 1050, + height: 50, + }; resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resources.requestsChangeSize_).to.be.empty; @@ -2067,34 +2185,15 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); }); - it('should change size when below the viewport and top margin also changed', - () => { - resource1.layoutBox_ = {top: 200, left: 0, right: 100, bottom: 300, - height: 100}; - resources.scheduleChangeSize_(resource1, 111, 222, {top: 20}, false); - - expect(vsyncSpy).to.be.calledOnce; - const marginsTask = vsyncSpy.lastCall.args[0]; - marginsTask.measure({}); - - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); - }); - - it('should change size when box top below the viewport but top margin ' + - 'boundary is above viewport but top margin in unchanged', () => { - resource1.layoutBox_ = {top: 200, left: 0, right: 100, bottom: 300, - height: 100}; - resource1.element.fakeComputedStyle = { - marginTop: '100px', - marginRight: '0px', - marginBottom: '0px', - marginLeft: '0px', + it('should change size when below the viewport and top margin also changed', () => { + resource1.layoutBox_ = { + top: 200, + left: 0, + right: 100, + bottom: 300, + height: 100, }; - resources.scheduleChangeSize_(resource1, 111, 222, {top: 100}, false); + resources.scheduleChangeSize_(resource1, 111, 222, {top: 20}, false); expect(vsyncSpy).to.be.calledOnce; const marginsTask = vsyncSpy.lastCall.args[0]; @@ -2107,30 +2206,83 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); }); - it('should NOT change size when top margin boundary within viewport ' + - 'and top margin changed', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - - const callback = sandbox.spy(); - resource1.layoutBox_ = {top: 100, left: 0, right: 100, bottom: 300, - height: 200}; - resources.scheduleChangeSize_( - resource1, 111, 222, {top: 20}, false, callback); - - expect(vsyncSpy).to.be.calledOnce; - const task = vsyncSpy.lastCall.args[0]; - task.measure({}); + it( + 'should change size when box top below the viewport but top margin ' + + 'boundary is above viewport but top margin in unchanged', + () => { + resource1.layoutBox_ = { + top: 200, + left: 0, + right: 100, + bottom: 300, + height: 100, + }; + resource1.element.fakeComputedStyle = { + marginTop: '100px', + marginRight: '0px', + marginBottom: '0px', + marginLeft: '0px', + }; + resources.scheduleChangeSize_(resource1, 111, 222, {top: 100}, false); + + expect(vsyncSpy).to.be.calledOnce; + const marginsTask = vsyncSpy.lastCall.args[0]; + marginsTask.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); + } + ); - resources.mutateWork_(); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.not.been.called; - expect(callback).to.be.calledOnce; - expect(callback.args[0][0]).to.be.false; - }); + it( + 'should NOT change size when top margin boundary within viewport ' + + 'and top margin changed', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + + const callback = sandbox.spy(); + resource1.layoutBox_ = { + top: 100, + left: 0, + right: 100, + bottom: 300, + height: 200, + }; + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 20}, + false, + callback + ); + + expect(vsyncSpy).to.be.calledOnce; + const task = vsyncSpy.lastCall.args[0]; + task.measure({}); + + resources.mutateWork_(); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.not.been.called; + expect(callback).to.be.calledOnce; + expect(callback.args[0][0]).to.be.false; + } + ); it('should defer when above the viewport and scrolling on', () => { - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 10; resources.lastScrollTime_ = Date.now(); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2140,41 +2292,71 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy).to.not.been.called; }); - it('should defer change size if just inside viewport and viewport ' + - 'scrolled by user.', () => { - viewportRect.top = 2; - resource1.layoutBox_ = {top: -50, left: 0, right: 100, bottom: 1, - height: 51}; - resources.lastVelocity_ = 10; - resources.lastScrollTime_ = Date.now(); - resources.scheduleChangeSize_(resource1, 111, 222, false); - resources.mutateWork_(); - expect(resources.requestsChangeSize_.length).to.equal(1); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.not.been.called; - }); + it( + 'should defer change size if just inside viewport and viewport ' + + 'scrolled by user.', + () => { + viewportRect.top = 2; + resource1.layoutBox_ = { + top: -50, + left: 0, + right: 100, + bottom: 1, + height: 51, + }; + resources.lastVelocity_ = 10; + resources.lastScrollTime_ = Date.now(); + resources.scheduleChangeSize_(resource1, 111, 222, false); + resources.mutateWork_(); + expect(resources.requestsChangeSize_.length).to.equal(1); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.not.been.called; + } + ); - it('should NOT change size and call overflow callback if viewport not ' + - 'scrolled by user.', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - viewportRect.top = 1; - resource1.layoutBox_ = {top: -50, left: 0, right: 100, bottom: 0, - height: 51}; - resources.lastVelocity_ = 10; - resources.lastScrollTime_ = Date.now(); - resources.scheduleChangeSize_(resource1, 111, 222, false); - resources.mutateWork_(); - expect(resources.requestsChangeSize_.length).to.equal(0); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222); - }); + it( + 'should NOT change size and call overflow callback if viewport not ' + + 'scrolled by user.', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + viewportRect.top = 1; + resource1.layoutBox_ = { + top: -50, + left: 0, + right: 100, + bottom: 0, + height: 51, + }; + resources.lastVelocity_ = 10; + resources.lastScrollTime_ = Date.now(); + resources.scheduleChangeSize_(resource1, 111, 222, false); + resources.mutateWork_(); + expect(resources.requestsChangeSize_.length).to.equal(0); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222); + } + ); it('should change size when above the vp and adjust scrolling', () => { - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1777).once(); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1777) + .once(); + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2189,8 +2371,14 @@ describe('Resources changeSize', () => { expect(state.scrollTop).to.equal(1777); expect(state.scrollHeight).to.equal(2999); - viewportMock.expects('getScrollHeight').returns(3999).once(); - viewportMock.expects('setScrollTop').withExactArgs(2777).once(); + viewportMock + .expects('getScrollHeight') + .returns(3999) + .once(); + viewportMock + .expects('setScrollTop') + .withExactArgs(2777) + .once(); task.mutate(state); expect(resource1.changeSize).to.be.calledOnce; expect(resource1.changeSize).to.be.calledWith(111, 222); @@ -2198,8 +2386,13 @@ describe('Resources changeSize', () => { }); it('should NOT resize when above vp but cannot adjust scrolling', () => { - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1100, - height: 100}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1100, + height: 100, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 0, 222, undefined, false); @@ -2213,10 +2406,20 @@ describe('Resources changeSize', () => { }); it('should resize if multi request above vp can adjust scroll', () => { - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1100, - height: 100}; - resource2.layoutBox_ = {top: -1300, left: 0, right: 100, bottom: -1200, - height: 100}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1100, + height: 100, + }; + resource2.layoutBox_ = { + top: -1300, + left: 0, + right: 100, + bottom: -1200, + height: 100, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource2, 200, 222, undefined, false); @@ -2236,12 +2439,26 @@ describe('Resources changeSize', () => { resources.viewport_.getRect(); viewportMock.expects('getRect').returns({ - top: 10, left: 0, right: 100, bottom: 210, height: 200, + top: 10, + left: 0, + right: 100, + bottom: 210, + height: 200, }); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1100, - height: 100}; - resource2.layoutBox_ = {top: -1300, left: 0, right: 100, bottom: -1200, - height: 100}; + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1100, + height: 100, + }; + resource2.layoutBox_ = { + top: -1300, + left: 0, + right: 100, + bottom: -1200, + height: 100, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 92, 222, undefined, false); @@ -2255,10 +2472,21 @@ describe('Resources changeSize', () => { }); it('should NOT adjust scrolling if height not change above vp', () => { - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1777).once(); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1777) + .once(); + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2273,7 +2501,10 @@ describe('Resources changeSize', () => { expect(state.scrollTop).to.equal(1777); expect(state.scrollHeight).to.equal(2999); - viewportMock.expects('getScrollHeight').returns(2999).once(); + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); viewportMock.expects('setScrollTop').never(); task.mutate(state); expect(resource1.changeSize).to.be.calledOnce; @@ -2282,10 +2513,21 @@ describe('Resources changeSize', () => { }); it('should adjust scrolling if height change above vp', () => { - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1000).once(); - resource1.layoutBox_ = {top: -1200, left: 0, right: 100, bottom: -1050, - height: 50}; + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1000) + .once(); + resource1.layoutBox_ = { + top: -1200, + left: 0, + right: 100, + bottom: -1050, + height: 50, + }; resources.lastVelocity_ = 0; clock.tick(5000); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); @@ -2293,13 +2535,22 @@ describe('Resources changeSize', () => { const task = vsyncSpy.lastCall.args[0]; const state = {}; task.measure(state); - viewportMock.expects('getScrollHeight').returns(2000).once(); - viewportMock.expects('setScrollTop').withExactArgs(1).once(); + viewportMock + .expects('getScrollHeight') + .returns(2000) + .once(); + viewportMock + .expects('setScrollTop') + .withExactArgs(1) + .once(); task.mutate(state); }); it('in vp should NOT call overflowCallback if new height smaller', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); resources.scheduleChangeSize_(resource1, 10, 11, undefined, false); resources.mutateWork_(); expect(resources.requestsChangeSize_).to.be.empty; @@ -2307,79 +2558,93 @@ describe('Resources changeSize', () => { expect(overflowCallbackSpy).to.not.been.called; }); - it('in viewport should change size if in the last 15% and ' + - 'in the last 1000px', () => { - viewportRect.top = 9600; - viewportRect.bottom = 9800; - resource1.layoutBox_ = {top: 9650, left: 0, right: 100, bottom: 9700, - height: 50}; - resources.scheduleChangeSize_(resource1, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}, false); - - expect(vsyncSpy).to.be.calledOnce; - const marginsTask = vsyncSpy.lastCall.args[0]; - marginsTask.measure({}); - - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); - }); - - it('in viewport should NOT change size if in the last 15% but NOT ' + - 'in the last 1000px', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - viewportRect.top = 8600; - viewportRect.bottom = 8800; - resource1.layoutBox_ = {top: 8650, left: 0, right: 100, bottom: 8700, - height: 50}; - resources.scheduleChangeSize_(resource1, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}, false); - - expect(vsyncSpy).to.be.calledOnce; - const marginsTask = vsyncSpy.lastCall.args[0]; - marginsTask.measure({}); + it( + 'in viewport should change size if in the last 15% and ' + + 'in the last 1000px', + () => { + viewportRect.top = 9600; + viewportRect.bottom = 9800; + resource1.layoutBox_ = { + top: 9650, + left: 0, + right: 100, + bottom: 9700, + height: 50, + }; + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); + + expect(vsyncSpy).to.be.calledOnce; + const marginsTask = vsyncSpy.lastCall.args[0]; + marginsTask.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy.firstCall.args[0]).to.equal(false); + } + ); - resources.mutateWork_(); - expect(resources.requestsChangeSize_).to.be.empty; - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}); - }); + it( + 'in viewport should NOT change size if in the last 15% but NOT ' + + 'in the last 1000px', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + viewportRect.top = 8600; + viewportRect.bottom = 8800; + resource1.layoutBox_ = { + top: 8650, + left: 0, + right: 100, + bottom: 8700, + height: 50, + }; + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); + + expect(vsyncSpy).to.be.calledOnce; + const marginsTask = vsyncSpy.lastCall.args[0]; + marginsTask.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_).to.be.empty; + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, { + top: 1, + right: 2, + bottom: 3, + left: 4, + }); + } + ); it('in viewport should NOT change size and calls overflowCallback', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - resources.scheduleChangeSize_(resource1, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}, false); - - expect(vsyncSpy).to.be.calledOnce; - const task = vsyncSpy.lastCall.args[0]; - task.measure({}); - - resources.mutateWork_(); - expect(resources.requestsChangeSize_.length).to.equal(0); - expect(resource1.changeSize).to.not.been.called; - expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, - {top: 1, right: 2, bottom: 3, left: 4}); - expect(resource1.getPendingChangeSize()).to.jsonEqual( - {height: 111, width: 222, - margins: {top: 1, right: 2, bottom: 3, left: 4}}); - }); - - it('should NOT change size when resized margin in viewport and should ' + - 'call overflowCallback', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); - resource1.layoutBox_ = {top: -48, left: 0, right: 100, bottom: 2, - height: 50}; - resource1.element.fakeComputedStyle = { - marginBottom: '21px', - }; - - resources.scheduleChangeSize_(resource1, undefined, undefined, - {bottom: 22}, false); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + resources.scheduleChangeSize_( + resource1, + 111, + 222, + {top: 1, right: 2, bottom: 3, left: 4}, + false + ); expect(vsyncSpy).to.be.calledOnce; const task = vsyncSpy.lastCall.args[0]; @@ -2389,25 +2654,97 @@ describe('Resources changeSize', () => { expect(resources.requestsChangeSize_.length).to.equal(0); expect(resource1.changeSize).to.not.been.called; expect(overflowCallbackSpy).to.be.calledOnce; - expect(overflowCallbackSpy).to.be.calledWith(true, undefined, - undefined, {bottom: 22}); - expect(resource1.getPendingChangeSize()).to.jsonEqual( - {height: undefined, width: undefined, margins: {bottom: 22}}); + expect(overflowCallbackSpy).to.be.calledWith(true, 111, 222, { + top: 1, + right: 2, + bottom: 3, + left: 4, + }); + expect(resource1.getPendingChangeSize()).to.jsonEqual({ + height: 111, + width: 222, + margins: {top: 1, right: 2, bottom: 3, left: 4}, + }); }); + it( + 'should NOT change size when resized margin in viewport and should ' + + 'call overflowCallback', + () => { + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); + resource1.layoutBox_ = { + top: -48, + left: 0, + right: 100, + bottom: 2, + height: 50, + }; + resource1.element.fakeComputedStyle = { + marginBottom: '21px', + }; + + resources.scheduleChangeSize_( + resource1, + undefined, + undefined, + {bottom: 22}, + false + ); + + expect(vsyncSpy).to.be.calledOnce; + const task = vsyncSpy.lastCall.args[0]; + task.measure({}); + + resources.mutateWork_(); + expect(resources.requestsChangeSize_.length).to.equal(0); + expect(resource1.changeSize).to.not.been.called; + expect(overflowCallbackSpy).to.be.calledOnce; + expect(overflowCallbackSpy).to.be.calledWith( + true, + undefined, + undefined, + {bottom: 22} + ); + expect(resource1.getPendingChangeSize()).to.jsonEqual({ + height: undefined, + width: undefined, + margins: {bottom: 22}, + }); + } + ); + it('should change size when resized margin above viewport', () => { - resource1.layoutBox_ = {top: -49, left: 0, right: 100, bottom: 1, - height: 50}; + resource1.layoutBox_ = { + top: -49, + left: 0, + right: 100, + bottom: 1, + height: 50, + }; resource1.element.fakeComputedStyle = { marginBottom: '21px', }; - viewportMock.expects('getScrollHeight').returns(2999).once(); - viewportMock.expects('getScrollTop').returns(1777).once(); + viewportMock + .expects('getScrollHeight') + .returns(2999) + .once(); + viewportMock + .expects('getScrollTop') + .returns(1777) + .once(); resources.lastVelocity_ = 0; clock.tick(5000); - resources.scheduleChangeSize_(resource1, undefined, undefined, - {top: 1}, false); + resources.scheduleChangeSize_( + resource1, + undefined, + undefined, + {top: 1}, + false + ); expect(vsyncSpy).to.be.calledOnce; const marginsTask = vsyncSpy.lastCall.args[0]; @@ -2424,17 +2761,27 @@ describe('Resources changeSize', () => { expect(state.scrollTop).to.equal(1777); expect(state.scrollHeight).to.equal(2999); - viewportMock.expects('getScrollHeight').returns(3999).once(); - viewportMock.expects('setScrollTop').withExactArgs(2777).once(); + viewportMock + .expects('getScrollHeight') + .returns(3999) + .once(); + viewportMock + .expects('setScrollTop') + .withExactArgs(2777) + .once(); scrollAdjustTask.mutate(state); expect(resource1.changeSize).to.be.calledOnce; - expect(resource1.changeSize).to.be.calledWith(undefined, undefined, - {top: 1}); + expect(resource1.changeSize).to.be.calledWith(undefined, undefined, { + top: 1, + }); expect(resources.relayoutTop_).to.equal(resource1.layoutBox_.top); }); it('should reset pending change size when rescheduling', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resource1.getPendingChangeSize().height).to.equal(111); @@ -2445,11 +2792,16 @@ describe('Resources changeSize', () => { }); it('should force resize after focus', () => { - viewportMock.expects('getContentHeight').returns(10000).atLeast(1); + viewportMock + .expects('getContentHeight') + .returns(10000) + .atLeast(1); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); - expect(resource1.getPendingChangeSize()).to.jsonEqual( - {height: 111, width: 222}); + expect(resource1.getPendingChangeSize()).to.jsonEqual({ + height: 111, + width: 222, + }); expect(resources.requestsChangeSize_).to.be.empty; resources.checkPendingChangeSize_(resource1.element); @@ -2466,23 +2818,33 @@ describe('Resources changeSize', () => { }); describe('attemptChangeSize rules for element wrt document', () => { - beforeEach(() => { - viewportMock.expects('getRect').returns( - {top: 0, left: 0, right: 100, bottom: 10000, height: 200}); - resource1.layoutBox_ = resource1.initialLayoutBox_ = - layoutRectLtwh(0, 10, 100, 100); + viewportMock + .expects('getRect') + .returns({top: 0, left: 0, right: 100, bottom: 10000, height: 200}); + resource1.layoutBox_ = resource1.initialLayoutBox_ = layoutRectLtwh( + 0, + 10, + 100, + 100 + ); }); it('should NOT change size when far the bottom of the document', () => { - viewportMock.expects('getContentHeight').returns(10000).once(); + viewportMock + .expects('getContentHeight') + .returns(10000) + .once(); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resource1.changeSize).to.not.been.called; }); it('should change size when close to the bottom of the document', () => { - viewportMock.expects('getContentHeight').returns(110).once(); + viewportMock + .expects('getContentHeight') + .returns(110) + .once(); resources.scheduleChangeSize_(resource1, 111, 222, undefined, false); resources.mutateWork_(); expect(resource1.changeSize).to.be.calledOnce; @@ -2490,9 +2852,7 @@ describe('Resources changeSize', () => { }); }); - describe('Resources mutateElement and collapse', () => { - function createElement(rect, isAmp) { const signals = new Signals(); return { @@ -2533,9 +2893,10 @@ describe('Resources mutateElement and collapse', () => { function createResource(id, rect) { const resource = new Resource( - id, - createElement(rect, /* isAmp */ true), - resources); + id, + createElement(rect, /* isAmp */ true), + resources + ); resource.element['__AMP__RESOURCE'] = resource; resource.state_ = ResourceState.READY_FOR_LAYOUT; resource.layoutBox_ = rect; @@ -2590,10 +2951,14 @@ describe('Resources mutateElement and collapse', () => { resource1RequestMeasureStub = sandbox.stub(resource1, 'requestMeasure'); resource2RequestMeasureStub = sandbox.stub(resource2, 'requestMeasure'); - parent1 = createElement(layoutRectLtwh(10, 10, 100, 100), - /* isAmp */ false); - parent2 = createElement(layoutRectLtwh(10, 1010, 100, 100), - /* isAmp */ false); + parent1 = createElement( + layoutRectLtwh(10, 10, 100, 100), + /* isAmp */ false + ); + parent2 = createElement( + layoutRectLtwh(10, 1010, 100, 100), + /* isAmp */ false + ); parent1.getElementsByClassName = className => { if (className == 'i-amphtml-element') { @@ -2630,8 +2995,8 @@ describe('Resources mutateElement and collapse', () => { it('should mutate from visible to invisible on itself', () => { const mutateSpy = sandbox.spy(); const promise = resources.mutateElement(resource1.element, () => { - resource1.element.getBoundingClientRect = - () => layoutRectLtwh(0, 0, 0, 0); + resource1.element.getBoundingClientRect = () => + layoutRectLtwh(0, 0, 0, 0); mutateSpy(); }); return promise.then(() => { @@ -2711,8 +3076,13 @@ describe('Resources mutateElement and collapse', () => { index++; }); - resource1.layoutBox_ = {top: 1000, left: 0, right: 100, bottom: 1050, - height: 50}; + resource1.layoutBox_ = { + top: 1000, + left: 0, + right: 100, + bottom: 1050, + height: 50, + }; resources.lastVelocity_ = 0; resources.attemptCollapse(resource1.element); resources.mutateWork_(); @@ -2720,19 +3090,25 @@ describe('Resources mutateElement and collapse', () => { }); it('attemptCollapse should complete collapse if resize succeed', () => { - sandbox.stub(resources, 'scheduleChangeSize_').callsFake( + sandbox + .stub(resources, 'scheduleChangeSize_') + .callsFake( (resource, newHeight, newWidth, newMargins, force, callback) => { callback(true); - }); + } + ); resources.attemptCollapse(resource1.element); expect(resource1.completeCollapse).to.be.calledOnce; }); it('attemptCollapse should NOT complete collapse if resize fail', () => { - sandbox.stub(resources, 'scheduleChangeSize_').callsFake( + sandbox + .stub(resources, 'scheduleChangeSize_') + .callsFake( (resource, newHeight, newWidth, newMargins, force, callback) => { callback(false); - }); + } + ); resources.attemptCollapse(resource1.element); expect(resource1.completeCollapse).to.not.been.called; }); @@ -2754,7 +3130,6 @@ describe('Resources mutateElement and collapse', () => { }); }); - describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { let resources; let parent; @@ -2828,12 +3203,13 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { resource2 = child2['__AMP__RESOURCE']; }); - afterEach(() => { - }); + afterEach(() => {}); it('should enforce that viewport is ready for first add', () => { - const ensureViewportReady = sandbox.stub(resources.viewport_, - 'ensureReadyForElements'); + const ensureViewportReady = sandbox.stub( + resources.viewport_, + 'ensureReadyForElements' + ); resources.add(child1); expect(ensureViewportReady).to.be.calledOnce; @@ -2862,53 +3238,60 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { }); // TODO(jridgewell, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should not schedule pass when immediate ' + - 'build fails', () => { - const schedulePassStub = sandbox.stub(resources, 'schedulePass'); - child1.isBuilt = () => false; - const child1BuildSpy = sandbox.spy(); - child1.build = () => { - // Emulate an error happening during an element build. - child1BuildSpy(); - return Promise.reject(new Error('child1-build-error')); - }; - resources.documentReady_ = true; - resources.add(child1); - const resource1 = stubBuild(Resource.forElementOptional(child1)); - resources.upgraded(child1); - expect(resources.get()).to.contain(resource1); - return resource1.buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - expect(child1BuildSpy).to.be.calledOnce; - expect(schedulePassStub).to.not.be.called; - expect(resources.get()).to.not.contain(resource1); - }); - }); + it.configure().skipSafari( + 'should not schedule pass when immediate ' + 'build fails', + () => { + const schedulePassStub = sandbox.stub(resources, 'schedulePass'); + child1.isBuilt = () => false; + const child1BuildSpy = sandbox.spy(); + child1.build = () => { + // Emulate an error happening during an element build. + child1BuildSpy(); + return Promise.reject(new Error('child1-build-error')); + }; + resources.documentReady_ = true; + resources.add(child1); + const resource1 = stubBuild(Resource.forElementOptional(child1)); + resources.upgraded(child1); + expect(resources.get()).to.contain(resource1); + return resource1.buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + expect(child1BuildSpy).to.be.calledOnce; + expect(schedulePassStub).to.not.be.called; + expect(resources.get()).to.not.contain(resource1); + } + ); + } + ); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should add element to pending build when ' + - 'document is not ready', () => { - child1.isBuilt = () => false; - child2.isBuilt = () => false; - resources.buildReadyResources_ = sandbox.spy(); - resources.documentReady_ = false; - resources.add(child1); - resources.upgraded(child1); - expect(child1.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(1); - resources.add(child2); - resources.upgraded(child2); - expect(child2.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(2); - expect(resources.buildReadyResources_.calledTwice).to.be.true; - const resource1 = Resource.forElementOptional(child1); - const resource2 = Resource.forElementOptional(child2); - expect(resources.get()).to.contain(resource1); - expect(resources.get()).to.contain(resource2); - expect(resource1.isBuilding()).to.be.false; - expect(resource2.isBuilding()).to.be.false; - }); + it.configure().skipSafari( + 'should add element to pending build when ' + 'document is not ready', + () => { + child1.isBuilt = () => false; + child2.isBuilt = () => false; + resources.buildReadyResources_ = sandbox.spy(); + resources.documentReady_ = false; + resources.add(child1); + resources.upgraded(child1); + expect(child1.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(1); + resources.add(child2); + resources.upgraded(child2); + expect(child2.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(2); + expect(resources.buildReadyResources_.calledTwice).to.be.true; + const resource1 = Resource.forElementOptional(child1); + const resource2 = Resource.forElementOptional(child2); + expect(resources.get()).to.contain(resource1); + expect(resources.get()).to.contain(resource2); + expect(resource1.isBuilding()).to.be.false; + expect(resource2.isBuilding()).to.be.false; + } + ); describe('buildReadyResources_', () => { let schedulePassStub; @@ -2924,42 +3307,46 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { }); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should build ready resources and remove ' + - 'them from pending', () => { - resources.pendingBuildResources_ = [resource1, resource2]; - resources.buildReadyResources_(); - expect(child1.build.called).to.be.false; - expect(child2.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(2); - expect(resources.schedulePass.called).to.be.false; - - child1.nextSibling = child2; - resources.buildReadyResources_(); - expect(child1.build.called).to.be.true; - expect(child2.build.called).to.be.false; - expect(resources.pendingBuildResources_.length).to.be.equal(1); - expect(resources.pendingBuildResources_[0]).to.be.equal(resource2); - expect(resource1.isBuilding()).to.be.true; - expect(resource2.isBuilding()).to.be.false; - return resource1.buildPromise.then(() => { - expect(resources.schedulePass.calledOnce).to.be.true; + it.configure().skipSafari( + 'should build ready resources and remove ' + 'them from pending', + () => { + resources.pendingBuildResources_ = [resource1, resource2]; + resources.buildReadyResources_(); + expect(child1.build.called).to.be.false; + expect(child2.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(2); + expect(resources.schedulePass.called).to.be.false; - child2.parentNode = parent; - parent.nextSibling = true; + child1.nextSibling = child2; resources.buildReadyResources_(); - expect(child1.build).to.be.calledOnce; - expect(child2.build.called).to.be.true; - expect(resources.pendingBuildResources_.length).to.be.equal(0); - expect(resource2.isBuilding()).to.be.true; - return resource2.buildPromise; - }).then(() => { - expect(resources.get()).to.contain(resource1); - expect(resources.get()).to.contain(resource2); - expect(resource1.isBuilding()).to.be.false; + expect(child1.build.called).to.be.true; + expect(child2.build.called).to.be.false; + expect(resources.pendingBuildResources_.length).to.be.equal(1); + expect(resources.pendingBuildResources_[0]).to.be.equal(resource2); + expect(resource1.isBuilding()).to.be.true; expect(resource2.isBuilding()).to.be.false; - expect(resources.schedulePass.calledTwice).to.be.true; - }); - }); + return resource1.buildPromise + .then(() => { + expect(resources.schedulePass.calledOnce).to.be.true; + + child2.parentNode = parent; + parent.nextSibling = true; + resources.buildReadyResources_(); + expect(child1.build).to.be.calledOnce; + expect(child2.build.called).to.be.true; + expect(resources.pendingBuildResources_.length).to.be.equal(0); + expect(resource2.isBuilding()).to.be.true; + return resource2.buildPromise; + }) + .then(() => { + expect(resources.get()).to.contain(resource1); + expect(resources.get()).to.contain(resource2); + expect(resource1.isBuilding()).to.be.false; + expect(resource2.isBuilding()).to.be.false; + expect(resources.schedulePass.calledTwice).to.be.true; + }); + } + ); it('should NOT build past the root node when pending', () => { resources.pendingBuildResources_ = [resource1]; @@ -3015,62 +3402,76 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { }); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should build everything pending when ' + - 'document is ready', () => { - resources.documentReady_ = true; - resources.pendingBuildResources_ = [parentResource, resource1, resource2]; - const child1BuildSpy = sandbox.spy(); - child1.build = () => { - // Emulate an error happening during an element build. - child1BuildSpy(); - return Promise.reject(new Error('child1-build-error')); - }; - resources.buildReadyResources_(); - expect(child1BuildSpy.called).to.be.true; - expect(child2.build.called).to.be.true; - expect(parent.build.called).to.be.true; - expect(resources.pendingBuildResources_.length).to.be.equal(0); - return Promise.all([ - parentResource.buildPromise, - resource2.buildPromise, - resource1.buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - // Ignore error. - }), - ]).then(() => { - expect(schedulePassStub).to.be.calledTwice; - // Failed build. - expect(resources.get()).to.not.contain(resource1); - expect(resource1.isBuilding()).to.be.false; - // Successful build. - expect(resources.get()).to.contain(resource2); - expect(resource2.isBuilding()).to.be.false; - }); - }); + it.configure().skipSafari( + 'should build everything pending when ' + 'document is ready', + () => { + resources.documentReady_ = true; + resources.pendingBuildResources_ = [ + parentResource, + resource1, + resource2, + ]; + const child1BuildSpy = sandbox.spy(); + child1.build = () => { + // Emulate an error happening during an element build. + child1BuildSpy(); + return Promise.reject(new Error('child1-build-error')); + }; + resources.buildReadyResources_(); + expect(child1BuildSpy.called).to.be.true; + expect(child2.build.called).to.be.true; + expect(parent.build.called).to.be.true; + expect(resources.pendingBuildResources_.length).to.be.equal(0); + return Promise.all([ + parentResource.buildPromise, + resource2.buildPromise, + resource1.buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + // Ignore error. + } + ), + ]).then(() => { + expect(schedulePassStub).to.be.calledTwice; + // Failed build. + expect(resources.get()).to.not.contain(resource1); + expect(resource1.isBuilding()).to.be.false; + // Successful build. + expect(resources.get()).to.contain(resource2); + expect(resource2.isBuilding()).to.be.false; + }); + } + ); // TODO(amphtml, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari('should not schedule pass if all ' + - 'builds failed', () => { - resources.documentReady_ = true; - resources.pendingBuildResources_ = [resource1]; - const child1BuildSpy = sandbox.spy(); - child1.build = () => { - // Emulate an error happening during an element build. - child1BuildSpy(); - return Promise.reject(new Error('child1-build-error')); - }; - resources.buildReadyResources_(); - expect(child1BuildSpy.called).to.be.true; - expect(resources.pendingBuildResources_.length).to.be.equal(0); - return resource1.buildPromise.then(() => { - throw new Error('must have failed'); - }, () => { - expect(schedulePassStub).to.not.be.called; - expect(resources.get()).to.not.contain(resource1); - expect(resource1.isBuilding()).to.be.false; - }); - }); + it.configure().skipSafari( + 'should not schedule pass if all ' + 'builds failed', + () => { + resources.documentReady_ = true; + resources.pendingBuildResources_ = [resource1]; + const child1BuildSpy = sandbox.spy(); + child1.build = () => { + // Emulate an error happening during an element build. + child1BuildSpy(); + return Promise.reject(new Error('child1-build-error')); + }; + resources.buildReadyResources_(); + expect(child1BuildSpy.called).to.be.true; + expect(resources.pendingBuildResources_.length).to.be.equal(0); + return resource1.buildPromise.then( + () => { + throw new Error('must have failed'); + }, + () => { + expect(schedulePassStub).to.not.be.called; + expect(resources.get()).to.not.contain(resource1); + expect(resource1.isBuilding()).to.be.false; + } + ); + } + ); }); describe('remove', () => { @@ -3108,7 +3509,9 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, env => { beforeEach(() => { scheduleBuildStub = sandbox.stub( - resources, 'buildOrScheduleBuildForResource_'); + resources, + 'buildOrScheduleBuildForResource_' + ); child1.isBuilt = () => true; resources.add(child1); resources.upgraded(child1); diff --git a/test/unit/test-runtime.js b/test/unit/test-runtime.js index 0c846619683ba..feb2a3e0399e6 100644 --- a/test/unit/test-runtime.js +++ b/test/unit/test-runtime.js @@ -20,11 +20,7 @@ import * as styles from '../../src/style-installer'; import {AmpDocShadow, AmpDocSingle} from '../../src/service/ampdoc-impl'; import {ElementStub} from '../../src/element-stub'; import {Services} from '../../src/services'; -import { - adopt, - adoptShadowMode, - installAmpdocServices, -} from '../../src/runtime'; +import {adopt, adoptShadowMode, installAmpdocServices} from '../../src/runtime'; import {createShadowRoot} from '../../src/shadow-embed'; import {deactivateChunking, runChunksForTesting} from '../../src/chunk'; import { @@ -38,1697 +34,1860 @@ import {installTimerService} from '../../src/service/timer-impl'; import {toggleExperiment} from '../../src/experiments'; import {vsyncForTesting} from '../../src/service/vsync-impl'; +describes.fakeWin( + 'runtime', + { + location: 'https://cdn.ampproject.org/c/s/www.example.com/path', + }, + env => { + let win; + let clock; + let ampdocService; + let ampdocServiceMock; + let extensionElementIndex; -describes.fakeWin('runtime', { - location: 'https://cdn.ampproject.org/c/s/www.example.com/path', -}, env => { - let win; - let clock; - let ampdocService; - let ampdocServiceMock; - let extensionElementIndex; - - beforeEach(() => { - win = env.win; - clock = env.sandbox.useFakeTimers(); - extensionElementIndex = 0; - ampdocService = { - isSingleDoc: () => true, - getAmpDoc: () => null, - installShadowDoc_: () => null, - }; - ampdocServiceMock = sandbox.mock(ampdocService); - win.AMP = []; - win.services = { - ampdoc: {obj: ampdocService}, - }; - const ampdoc = new AmpDocSingle(win); - ampdocService.getAmpDoc = () => ampdoc; - installDocumentStateService(win); - installPlatformService(win); - installTimerService(win); - vsyncForTesting(win); - installAmpdocServices(ampdoc); - }); - - - function regularExtension(fn, opt_version) { - return { - n: 'amp-test-element' + extensionElementIndex++, - f: fn, - // Default version of uncompiled sources. - v: opt_version || '$internalRuntimeVersion$', - }; - } + beforeEach(() => { + win = env.win; + clock = env.sandbox.useFakeTimers(); + extensionElementIndex = 0; + ampdocService = { + isSingleDoc: () => true, + getAmpDoc: () => null, + installShadowDoc_: () => null, + }; + ampdocServiceMock = sandbox.mock(ampdocService); + win.AMP = []; + win.services = { + ampdoc: {obj: ampdocService}, + }; + const ampdoc = new AmpDocSingle(win); + ampdocService.getAmpDoc = () => ampdoc; + installDocumentStateService(win); + installPlatformService(win); + installTimerService(win); + vsyncForTesting(win); + installAmpdocServices(ampdoc); + }); + + function regularExtension(fn, opt_version) { + return { + n: 'amp-test-element' + extensionElementIndex++, + f: fn, + // Default version of uncompiled sources. + v: opt_version || '$internalRuntimeVersion$', + }; + } - afterEach(() => { - ampdocServiceMock.verify(); - }); - - it('should convert AMP from array to AMP object in single-doc', () => { - expect(win.AMP.push).to.equal([].push); - adopt(win); - expect(win.AMP.push).to.not.equal([].push); - expect(win.AMP_TAG).to.be.true; - }); - - it('should convert AMP from array to AMP object in shadow-doc', () => { - expect(win.AMP.push).to.equal([].push); - adoptShadowMode(win); - expect(win.AMP.push).to.not.equal([].push); - expect(win.AMP_TAG).to.be.true; - }); - - it('should install legacy stubs in single-doc', () => { - const initial = win.ampExtendedElements || {}; - expect(initial['amp-ad']).to.be.undefined; - expect(initial['amp-embed']).to.be.undefined; - expect(initial['amp-video']).to.be.undefined; - adopt(win); - expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); - }); - - it('should install legacy stubs in shadow-doc', () => { - const initial = win.ampExtendedElements || {}; - expect(initial['amp-ad']).to.be.undefined; - expect(initial['amp-embed']).to.be.undefined; - expect(initial['amp-video']).to.be.undefined; - adoptShadowMode(win); - expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); - expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); - }); - - it('should NOT set cursor:pointer on document element on non-IOS', () => { - const platform = Services.platformFor(win); - sandbox.stub(platform, 'isIos').returns(false); - adopt(win); - expect(win.document.documentElement.style.cursor).to.not.be.ok; - }); - - it('should set cursor:pointer on document element on IOS', () => { - const platform = Services.platformFor(win); - sandbox.stub(platform, 'isIos').returns(true); - adopt(win); - expect(win.document.documentElement.style.cursor).to.equal('pointer'); - }); - - it('should set cursor:pointer on IOS in shadow-doc', () => { - const platform = Services.platformFor(win); - sandbox.stub(platform, 'isIos').returns(true); - adoptShadowMode(win); - expect(win.document.documentElement.style.cursor).to.equal('pointer'); - }); - - function extensionRegistrationTest() { - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '2'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - expect(queueExtensions).to.have.length(3); - const promise = adopt(win); - runChunksForTesting(win.document); - return promise.then(() => { - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal('123'); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('1234'); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '5'; - })); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('12345'); - expect(queueExtensions).to.have.length(0); + afterEach(() => { + ampdocServiceMock.verify(); }); - } - it('should execute scheduled extensions & execute new extensions', - extensionRegistrationTest); - - it('should not maybePumpEarlyFrame when body not yet present', () => { - toggleExperiment(win, 'pump-early-frame', true); - // Make document.body be null on first invocation to simulate - // JS executing before the rest of the doc has been parsed. - const {body} = win.document; - let accessedOnce = false; - Object.defineProperty(win.document, 'body', { - get: () => { - if (accessedOnce) { - return body; - } - accessedOnce = true; - return null; - }, + it('should convert AMP from array to AMP object in single-doc', () => { + expect(win.AMP.push).to.equal([].push); + adopt(win); + expect(win.AMP.push).to.not.equal([].push); + expect(win.AMP_TAG).to.be.true; }); - extensionRegistrationTest(); - }); - - it('should not maybePumpEarlyFrame ' + - 'when a renderDelayingExtension is present', () => { - toggleExperiment(win, 'pump-early-frame', true); - win.document.body.appendChild( - document.createElement('amp-experiment')); - extensionRegistrationTest(); - }); - - it('should maybePumpEarlyFrame and delay extension execution', () => { - toggleExperiment(win, 'pump-early-frame', true); - let progress = ''; - const queueExtensions = win.AMP; - const highPriority = regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += 'high'; - }); - highPriority.p = 'high'; - win.AMP.push(highPriority); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '2'; - })); - win.AMP.push(() => { - progress += 'function'; - }); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - expect(queueExtensions).to.have.length(5); - const promise = adopt(win); - runChunksForTesting(win.document); - return promise.then(() => { - // Skip a microtask. - return Promise.resolve(); - }).then(() => { - expect(progress).to.equal('highfunction'); - expect(queueExtensions).to.have.length(3); - clock.tick(); - expect(queueExtensions).to.have.length(3); - expect(progress).to.equal('highfunction'); - // New extension arrives before inital ran. - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - expect(queueExtensions).to.have.length(4); - clock.tick(1); - expect(queueExtensions).to.have.length(0); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('highfunction1234'); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '5'; - })); - runChunksForTesting(win.document); - return promise; - }).then(() => { - expect(progress).to.equal('highfunction12345'); - expect(queueExtensions).to.have.length(0); - }); - }); - - it('support struct AMP.push raw functions and high priority', () => { - // New format: {n:string, f:function()}. - let progress = ''; - const queueExtensions = win.AMP; - // Queue mode. - win.AMP.push(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - }); - win.AMP.push({ - n: 'ext2', - p: 'high', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'HIGH'; - }, - }); - expect(queueExtensions).to.have.length(2); - expect(progress).to.equal(''); - const promise = adopt(win); - return promise.then(() => { - // Notice the queue is down to 0 but there is a micro task to execute - // raw and high prio functions. Also notice that no `runChunksForTesting` - // was called to process the queue. - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal(''); - // Even though raw functions and high priority don't go through chunking - // there is a micro task for its queue. - return Promise.resolve(); - }).then(() => { - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal('1HIGH'); - win.AMP.push({ - n: 'ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'A'; - }, - }); - runChunksForTesting(win.document); - return promise.then(() => { - expect(progress).to.equal('1HIGHA'); - }); + it('should convert AMP from array to AMP object in shadow-doc', () => { + expect(win.AMP.push).to.equal([].push); + adoptShadowMode(win); + expect(win.AMP.push).to.not.equal([].push); + expect(win.AMP_TAG).to.be.true; }); - }); - - it('loads and waits for a single intermediate bundles', () => { - // New format: {n:string, f:function(), i: }. - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push({ - n: 'ext2', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'C'; - }, - i: 'ext1', - }); - win.AMP.push({ - n: 'ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'A'; - }, - i: '_base_ext', + it('should install legacy stubs in single-doc', () => { + const initial = win.ampExtendedElements || {}; + expect(initial['amp-ad']).to.be.undefined; + expect(initial['amp-embed']).to.be.undefined; + expect(initial['amp-video']).to.be.undefined; + adopt(win); + expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); }); - win.AMP.push({ - n: '_base_ext', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'B'; - }, + it('should install legacy stubs in shadow-doc', () => { + const initial = win.ampExtendedElements || {}; + expect(initial['amp-ad']).to.be.undefined; + expect(initial['amp-embed']).to.be.undefined; + expect(initial['amp-video']).to.be.undefined; + adoptShadowMode(win); + expect(win.ampExtendedElements['amp-ad']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-embed']).to.equal(ElementStub); + expect(win.ampExtendedElements['amp-video']).to.equal(ElementStub); }); - let script = win.document.querySelector('[data-script=_base_ext]'); - expect(script).to.be.null; - const promise = adopt(win); - const e = Services.extensionsFor(win); - - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal(''); - runChunksForTesting(win.document); - script = win.document.querySelector('[data-script=_base_ext]'); - expect(script).to.be.not.null; - return promise.then(() => { - // ext1 should not be executed yet and needs to wait on _base_ext - expect(progress).to.equal('B'); - return e.waitForExtension(win, '_base_ext').then(() => { - return e.waitForExtension(win, 'ext1').then(() => { - expect(progress).to.equal('BA'); - return e.waitForExtension(win, 'ext2').then(() => { - expect(progress).to.equal('BAC'); - }); - }); - }); - }); - }); - - it('loads and waits for a multiple intermediate bundles', () => { - // New format: {n:string, f:function(), i: }. - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push({ - n: 'ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'A'; - }, - i: ['_base_ext1', '_base_ext2'], + it('should NOT set cursor:pointer on document element on non-IOS', () => { + const platform = Services.platformFor(win); + sandbox.stub(platform, 'isIos').returns(false); + adopt(win); + expect(win.document.documentElement.style.cursor).to.not.be.ok; }); - win.AMP.push({ - n: '_base_ext2', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'B'; - }, - i: ['_base_ext1'], + it('should set cursor:pointer on document element on IOS', () => { + const platform = Services.platformFor(win); + sandbox.stub(platform, 'isIos').returns(true); + adopt(win); + expect(win.document.documentElement.style.cursor).to.equal('pointer'); }); - win.AMP.push({ - n: '_base_ext1', - f: amp => { - expect(amp).to.equal(win.AMP); - progress += 'C'; - }, + it('should set cursor:pointer on IOS in shadow-doc', () => { + const platform = Services.platformFor(win); + sandbox.stub(platform, 'isIos').returns(true); + adoptShadowMode(win); + expect(win.document.documentElement.style.cursor).to.equal('pointer'); }); - let script1 = win.document.querySelector('[data-script=_base_ext1]'); - let script2 = win.document.querySelector('[data-script=_base_ext2]'); - expect(script1).to.be.null; - expect(script2).to.be.null; - const promise = adopt(win); - const e = Services.extensionsFor(win); - - expect(queueExtensions).to.have.length(0); - expect(progress).to.equal(''); - runChunksForTesting(win.document); - script1 = win.document.querySelector('[data-script=_base_ext1]'); - script2 = win.document.querySelector('[data-script=_base_ext2]'); - expect(script1).to.not.be.null; - expect(script2).to.not.be.null; - - return promise.then(() => { - // ext1 should not be executed yet and needs to wait on _base_ext - // Notice that ext0 executes before A - expect(progress).to.equal('C'); + function extensionRegistrationTest() { + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '2'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + expect(queueExtensions).to.have.length(3); + const promise = adopt(win); runChunksForTesting(win.document); - return e.waitForExtension(win, '_base_ext2').then(() => { - expect(progress).to.equal('CB'); - }).then(() => { - return e.waitForExtension(win, 'ext1').then(() => { - expect(progress).to.equal('CBA'); + return promise + .then(() => { + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal('123'); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('1234'); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '5'; + }) + ); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('12345'); + expect(queueExtensions).to.have.length(0); }); - }); - }); - }); - - it('should wait for body before processing extensions', function* () { - let bodyResolver; - const bodyPromise = new Promise(resolve => { - bodyResolver = resolve; - }); - sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); - - function skipMicro() { - return Promise.resolve().then(() => Promise.resolve()); - } - function waitNext(promise) { - return Promise.race([promise, skipMicro()]); - } - - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '2'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - const promise = adopt(win); - runChunksForTesting(win.document); - - yield waitNext(promise); - // Extensions are still unprocessed - expect(progress).to.equal(''); - - // Add one more - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal(''); - - // Body is available now. - bodyResolver(); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal('1234'); - expect(queueExtensions).to.have.length(0); - }); - - it('should load correct extension version', function* () { - self.AMP_MODE = { - rtvVersion: 'test-version', - }; - toggleExperiment(win, 'version-locking', true); - function addExisting(index) { - const s = document.createElement('script'); - s.setAttribute('custom-element', 'amp-test-element' + index); - win.document.head.appendChild(s); - return s; } - const s1 = addExisting(1); - const s2 = addExisting(4); - const s3 = addExisting(5); - let bodyResolver; - const bodyPromise = new Promise(resolve => { - bodyResolver = resolve; + it( + 'should execute scheduled extensions & execute new extensions', + extensionRegistrationTest + ); + + it('should not maybePumpEarlyFrame when body not yet present', () => { + toggleExperiment(win, 'pump-early-frame', true); + // Make document.body be null on first invocation to simulate + // JS executing before the rest of the doc has been parsed. + const {body} = win.document; + let accessedOnce = false; + Object.defineProperty(win.document, 'body', { + get: () => { + if (accessedOnce) { + return body; + } + accessedOnce = true; + return null; + }, + }); + extensionRegistrationTest(); }); - sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); - - function skipMicro() { - return Promise.resolve().then(() => Promise.resolve()); - } - function waitNext(promise) { - return Promise.race([promise, skipMicro()]); - } - let progress = ''; - const queueExtensions = win.AMP; - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '1'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += 'not expected 1'; - }, 'version123')); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '3'; - })); - const promise = adopt(win); - runChunksForTesting(win.document); - - yield waitNext(promise); - // Extensions are still unprocessed - expect(progress).to.equal(''); - - // Add one more - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '4'; - })); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += 'not expected 2'; - }, 'version123')); - // Add legacy element (5) and eagarly ask for its load as ElementStub does. - Services.extensionsFor(win).preloadExtension('amp-test-element5', false); - win.AMP.push(regularExtension(amp => { - expect(amp).to.equal(win.AMP); - progress += '5'; - }, 'version123')); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal(''); - - // Body is available now. - bodyResolver(); - runChunksForTesting(win.document); - - yield waitNext(promise); - expect(progress).to.equal('134'); - expect(queueExtensions).to.have.length(0); - expect(s1.getAttribute('custom-element')).to.be.null; - expect(s2.getAttribute('custom-element')).to.be.null; - expect(s3.getAttribute('custom-element')).to.be.null; - expect(s1.getAttribute('i-amphtml-loaded-new-version')) - .to.equal('amp-test-element1'); - expect(s2.getAttribute('i-amphtml-loaded-new-version')) - .to.equal('amp-test-element4'); - expect(s3.getAttribute('i-amphtml-loaded-new-version')) - .to.equal('amp-test-element5'); - const inserted = win.document.head.querySelectorAll( - '[i-amphtml-inserted]'); - expect(inserted).to.have.length(3); - expect(inserted[0].getAttribute('src')).to.equal( - 'https://cdn.ampproject.org/rtv/test-version' + - '/v0/amp-test-element1-0.1.js'); - expect(inserted[1].getAttribute('src')).to.equal( - 'https://cdn.ampproject.org/rtv/test-version' + - '/v0/amp-test-element4-0.1.js'); - expect(inserted[2].getAttribute('src')).to.equal( - 'https://cdn.ampproject.org/rtv/test-version' + - '/v0/amp-test-element5-0.1.js'); - }); - - it('should be robust against errors in early extensions', function* () { - let progress = ''; - win.AMP.push(regularExtension(() => { - progress += '1'; - })); - win.AMP.push(regularExtension(() => { - throw new Error('extension error'); - })); - win.AMP.push(regularExtension(() => { - progress += '3'; - })); - const promise = adopt(win); - runChunksForTesting(win.document); - yield promise; - expect(progress).to.equal('13'); - }); - - describe('single-mode', () => { - let extensions; + it( + 'should not maybePumpEarlyFrame ' + + 'when a renderDelayingExtension is present', + () => { + toggleExperiment(win, 'pump-early-frame', true); + win.document.body.appendChild(document.createElement('amp-experiment')); + extensionRegistrationTest(); + } + ); - beforeEach(() => { + it('should maybePumpEarlyFrame and delay extension execution', () => { + toggleExperiment(win, 'pump-early-frame', true); + let progress = ''; + const queueExtensions = win.AMP; + const highPriority = regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += 'high'; + }); + highPriority.p = 'high'; + win.AMP.push(highPriority); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '2'; + }) + ); + win.AMP.push(() => { + progress += 'function'; + }); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + expect(queueExtensions).to.have.length(5); const promise = adopt(win); - ext.installExtensionsService(win); - extensions = Services.extensionsFor(win); - return promise; - }); - - it('should export properties to global AMP object', () => { - expect(win.AMP.BaseElement).to.be.a('function'); - expect(win.AMP.BaseTemplate).to.be.a('function'); - expect(win.AMP.registerElement).to.be.a('function'); - expect(win.AMP.registerTemplate).to.be.a('function'); - expect(win.AMP.setTickFunction).to.be.a('function'); - expect(win.AMP.win).to.equal(win); - - expect(win.AMP.viewer).to.be.a('object'); - expect(win.AMP.viewport).to.be.a('object'); - // Single-doc mode does not create `attachShadowDoc`. - expect(win.AMP.attachShadowDoc).to.not.exist; - expect(win.AMP.attachShadowDocAsStream).to.not.exist; + runChunksForTesting(win.document); + return promise + .then(() => { + // Skip a microtask. + return Promise.resolve(); + }) + .then(() => { + expect(progress).to.equal('highfunction'); + expect(queueExtensions).to.have.length(3); + clock.tick(); + expect(queueExtensions).to.have.length(3); + expect(progress).to.equal('highfunction'); + // New extension arrives before inital ran. + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + expect(queueExtensions).to.have.length(4); + clock.tick(1); + expect(queueExtensions).to.have.length(0); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('highfunction1234'); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '5'; + }) + ); + runChunksForTesting(win.document); + return promise; + }) + .then(() => { + expect(progress).to.equal('highfunction12345'); + expect(queueExtensions).to.have.length(0); + }); }); - it('should register element without CSS', function* () { - const ampdoc = ampdocService.getAmpDoc(); - const servicePromise = getServicePromise(win, 'amp-ext'); - const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); + it('support struct AMP.push raw functions and high priority', () => { + // New format: {n:string, f:function()}. + let progress = ''; + const queueExtensions = win.AMP; - ampdoc.declareExtension('amp-ext'); + // Queue mode. + win.AMP.push(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }); win.AMP.push({ - n: 'amp-ext', + n: 'ext2', + p: 'high', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement); + expect(amp).to.equal(win.AMP); + progress += 'HIGH'; }, }); - runChunksForTesting(win.document); - yield extensions.waitForExtension(win, 'amp-ext'); - - // Extension is added immediately. Can't find for micro-tasks here. - const ext = extensions.extensions_['amp-ext'].extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - - // No installStyles calls. - expect(installStylesStub).to.have.not.been.called; - - // Register is called immediately as well. - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); + expect(queueExtensions).to.have.length(2); + expect(progress).to.equal(''); + const promise = adopt(win); + return promise + .then(() => { + // Notice the queue is down to 0 but there is a micro task to execute + // raw and high prio functions. Also notice that no `runChunksForTesting` + // was called to process the queue. + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal(''); + // Even though raw functions and high priority don't go through chunking + // there is a micro task for its queue. + return Promise.resolve(); + }) + .then(() => { + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal('1HIGH'); + win.AMP.push({ + n: 'ext1', + f: amp => { + expect(amp).to.equal(win.AMP); + progress += 'A'; + }, + }); + runChunksForTesting(win.document); + return promise.then(() => { + expect(progress).to.equal('1HIGHA'); + }); + }); }); - it('should register element with CSS', function* () { - const ampdoc = Services.ampdocServiceFor(win).getAmpDoc(); - const servicePromise = getServicePromise(win, 'amp-ext'); - let installStylesCallback; - const installStylesStub = - sandbox.stub(styles, 'installStylesForDoc').callsFake( - (doc, cssText, cb) => { - installStylesCallback = cb; - }); - - ampdoc.declareExtension('amp-ext'); + it('loads and waits for a single intermediate bundles', () => { + // New format: {n:string, f:function(), i: }. + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push({ - n: 'amp-ext', + n: 'ext2', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + expect(amp).to.equal(win.AMP); + progress += 'C'; }, + i: 'ext1', }); - runChunksForTesting(win.document); - - // Extension is added immediately. Can't find for micro-tasks here. - yield extensions.waitForExtension(win, 'amp-ext'); - const ext = extensions.extensions_['amp-ext'].extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - expect(ext.elements['amp-ext'].css).to.equal('a{}'); - - expect(installStylesStub).to.be.calledOnce; - expect(installStylesStub).to.be.calledWithExactly( - ampdoc, - 'a{}', - installStylesCallback, - /* isRuntimeCss */ false, - /* ext */ 'amp-ext'); - - // Element resistration is not done until callback. - expect(win.ampExtendedElements['amp-ext']).to.be.undefined; - installStylesCallback(); - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); - }); - - it('should register doc-service as ctor and install imm', function* () { - class Service1 {} - const ampdoc = new AmpDocSingle(win); - ampdoc.declareExtension('amp-ext'); - ampdocServiceMock.expects('getAmpDoc') - .returns(ampdoc) - .atLeast(1); win.AMP.push({ - n: 'amp-ext', + n: 'ext1', f: amp => { - amp.registerServiceForDoc('service1', Service1); + expect(amp).to.equal(win.AMP); + progress += 'A'; }, + i: '_base_ext', }); - runChunksForTesting(win.document); - - // No factories - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - expect(extHolder.docFactories).to.have.length(1); - - // Already installed. - expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); - - // The main top-level service is also pinged to unblock render. - yield getServicePromise(win, 'service1'); - }); - it('should register doc-service factory and install', function* () { - let count = 0; - function factory() { - count++; - return {str: 'A'}; - } - const ampdoc = new AmpDocSingle(win); - ampdoc.declareExtension('amp-ext'); - ampdocServiceMock.expects('getAmpDoc') - .returns(ampdoc) - .atLeast(1); win.AMP.push({ - n: 'amp-ext', + n: '_base_ext', f: amp => { - amp.registerServiceForDoc('service1', factory); + expect(amp).to.equal(win.AMP); + progress += 'B'; }, }); - runChunksForTesting(win.document); - - // No factories - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - expect(extHolder.docFactories).to.have.length(1); - // Already installed. - expect(count).to.equal(1); - expect(getServiceForDoc(ampdoc, 'service1')).to.deep.equal({str: 'A'}); - }); - }); - - describe('shadow-mode', () => { - let extensions; - - beforeEach(() => { - const promise = adoptShadowMode(win); - ext.installExtensionsService(win); - extensions = Services.extensionsFor(win); - return promise; - }); - - it('should export properties to global AMP object', () => { - expect(win.AMP.BaseElement).to.be.a('function'); - expect(win.AMP.BaseTemplate).to.be.a('function'); - expect(win.AMP.registerElement).to.be.a('function'); - expect(win.AMP.registerTemplate).to.be.a('function'); - expect(win.AMP.setTickFunction).to.be.a('function'); - expect(win.AMP.win).to.equal(win); - - expect(win.AMP.attachShadowDoc).to.be.a('function'); - expect(win.AMP.attachShadowDocAsStream).to.be.a('function'); + let script = win.document.querySelector('[data-script=_base_ext]'); + expect(script).to.be.null; + const promise = adopt(win); + const e = Services.extensionsFor(win); - expect(win.AMP.viewer).to.not.exist; - expect(win.AMP.viewport).to.not.exist; + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal(''); + runChunksForTesting(win.document); + script = win.document.querySelector('[data-script=_base_ext]'); + expect(script).to.be.not.null; + return promise.then(() => { + // ext1 should not be executed yet and needs to wait on _base_ext + expect(progress).to.equal('B'); + return e.waitForExtension(win, '_base_ext').then(() => { + return e.waitForExtension(win, 'ext1').then(() => { + expect(progress).to.equal('BA'); + return e.waitForExtension(win, 'ext2').then(() => { + expect(progress).to.equal('BAC'); + }); + }); + }); + }); }); - it('should register element without CSS', function* () { - const servicePromise = getServicePromise(win, 'amp-ext'); - const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); - + it('loads and waits for a multiple intermediate bundles', () => { + // New format: {n:string, f:function(), i: }. + let progress = ''; + const queueExtensions = win.AMP; win.AMP.push({ - n: 'amp-ext', + n: 'ext1', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement); + expect(amp).to.equal(win.AMP); + progress += 'A'; }, + i: ['_base_ext1', '_base_ext2'], }); - runChunksForTesting(win.document); - - // Extension is added immediately. Can't find for micro-tasks here. - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - const ext = extHolder.extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - - // No installStyles calls and no factories. - expect(installStylesStub).to.not.be.called; - expect(extHolder.docFactories).to.have.length(1); - expect(win.ampExtendedElements['amp-ext']).to.be.undefined; - - // Execute factory to install style. - const shadowRoot = document.createDocumentFragment(); - const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); - extHolder.docFactories[0](ampdoc); - expect(installStylesStub).to.not.be.called; - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); - }); - - it('should register element with CSS', function* () { - const servicePromise = getServicePromise(win, 'amp-ext'); - let installStylesCallback; - const installStylesStub = - sandbox.stub(styles, 'installStylesForDoc').callsFake( - (doc, cssText, cb) => { - installStylesCallback = cb; - }); win.AMP.push({ - n: 'amp-ext', + n: '_base_ext2', f: amp => { - amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + expect(amp).to.equal(win.AMP); + progress += 'B'; }, + i: ['_base_ext1'], }); - runChunksForTesting(win.document); - - // Extension is added immediately. Can't find for micro-tasks here. - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - const ext = extHolder.extension; - expect(ext.elements['amp-ext']).exist; - expect(ext.elements['amp-ext'].implementationClass) - .to.equal(win.AMP.BaseElement); - expect(ext.elements['amp-ext'].css).to.equal('a{}'); - // No installations yet, but there's a factory. - expect(extHolder.docFactories).to.have.length(1); - expect(win.ampExtendedElements['amp-ext']).to.be.undefined; - expect(installStylesStub).to.have.not.been.called; - - // Execute factory to install style. - const shadowRoot = document.createDocumentFragment(); - const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); - extHolder.docFactories[0](ampdoc); - expect(installStylesStub).to.be.calledOnce; - expect(installStylesStub).to.be.calledWithExactly( - ampdoc, - 'a{}', - installStylesCallback, - /* isRuntimeCss */ false, - /* ext */ 'amp-ext'); - - // Run install. - installStylesCallback(); - expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); - // Service and extensions are resolved. - yield Promise.all([ - extensions.waitForExtension(win, 'amp-ext'), - servicePromise]); - }); - - it('should register doc-service as ctor and defer install', function* () { - class Service1 {} win.AMP.push({ - n: 'amp-ext', + n: '_base_ext1', f: amp => { - amp.registerServiceForDoc('service1', Service1); + expect(amp).to.equal(win.AMP); + progress += 'C'; }, }); - runChunksForTesting(win.document); - // Factory recorded. - yield extensions.waitForExtension(win, 'amp-ext'); - const extHolder = extensions.extensions_['amp-ext']; - expect(extHolder.docFactories).to.have.length(1); - - const shadowRoot = document.createDocumentFragment(); - const ampdoc = new AmpDocShadow(win, 'https://a.org/', shadowRoot); + let script1 = win.document.querySelector('[data-script=_base_ext1]'); + let script2 = win.document.querySelector('[data-script=_base_ext2]'); + expect(script1).to.be.null; + expect(script2).to.be.null; + const promise = adopt(win); + const e = Services.extensionsFor(win); - // Not installed. - expect(getServicePromiseOrNullForDoc(ampdoc, 'service1')).to.be.null; + expect(queueExtensions).to.have.length(0); + expect(progress).to.equal(''); + runChunksForTesting(win.document); + script1 = win.document.querySelector('[data-script=_base_ext1]'); + script2 = win.document.querySelector('[data-script=_base_ext2]'); + expect(script1).to.not.be.null; + expect(script2).to.not.be.null; - // Install. - extHolder.docFactories[0](ampdoc); - expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); + return promise.then(() => { + // ext1 should not be executed yet and needs to wait on _base_ext + // Notice that ext0 executes before A + expect(progress).to.equal('C'); + runChunksForTesting(win.document); + return e + .waitForExtension(win, '_base_ext2') + .then(() => { + expect(progress).to.equal('CB'); + }) + .then(() => { + return e.waitForExtension(win, 'ext1').then(() => { + expect(progress).to.equal('CBA'); + }); + }); + }); }); - }); -}); + it('should wait for body before processing extensions', function*() { + let bodyResolver; + const bodyPromise = new Promise(resolve => { + bodyResolver = resolve; + }); + sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); + + function skipMicro() { + return Promise.resolve().then(() => Promise.resolve()); + } + function waitNext(promise) { + return Promise.race([promise, skipMicro()]); + } -describes.realWin('runtime multidoc', { - amp: {ampdoc: 'multi'}, -}, env => { - let win; - let extensions; - let extensionsMock; - let ampdocServiceMock; + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '2'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + const promise = adopt(win); + runChunksForTesting(win.document); - beforeEach(() => { - win = env.win; - extensions = env.extensions; - extensionsMock = sandbox.mock(extensions); - ampdocServiceMock = sandbox.mock(env.ampdocService); - }); + yield waitNext(promise); + // Extensions are still unprocessed + expect(progress).to.equal(''); - afterEach(() => { - extensionsMock.verify(); - ampdocServiceMock.verify(); - }); + // Add one more + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + runChunksForTesting(win.document); - describe('attachShadowDoc', () => { - const docUrl = 'https://example.org/doc1'; + yield waitNext(promise); + expect(progress).to.equal(''); - let clock; - let importDoc; - let hostElement; - let ampdoc; + // Body is available now. + bodyResolver(); + runChunksForTesting(win.document); - beforeEach(() => { - deactivateChunking(); - clock = sandbox.useFakeTimers(); - hostElement = win.document.createElement('div'); - importDoc = win.document.implementation.createHTMLDocument(''); - importDoc.body.appendChild(win.document.createElement('child')); - const shadowRoot = createShadowRoot(hostElement); - ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - - ampdocServiceMock.expects('installShadowDoc') - .withExactArgs( - docUrl, - sinon.match(arg => arg == getShadowRoot(hostElement))) - .returns(ampdoc) - .atLeast(0); - ampdocServiceMock.expects('getAmpDoc') - .withExactArgs(sinon.match(arg => arg == getShadowRoot(hostElement))) - .returns(ampdoc) - .atLeast(0); + yield waitNext(promise); + expect(progress).to.equal('1234'); + expect(queueExtensions).to.have.length(0); }); - it('should install services and styles', () => { - const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(ret).to.exist; - - const shadowRoot = getShadowRoot(hostElement); + it('should load correct extension version', function*() { + self.AMP_MODE = { + rtvVersion: 'test-version', + }; + toggleExperiment(win, 'version-locking', true); + function addExisting(index) { + const s = document.createElement('script'); + s.setAttribute('custom-element', 'amp-test-element' + index); + win.document.head.appendChild(s); + return s; + } + const s1 = addExisting(1); + const s2 = addExisting(4); + const s3 = addExisting(5); - // URL is set. - expect(shadowRoot.AMP.url).to.equal(docUrl); + let bodyResolver; + const bodyPromise = new Promise(resolve => { + bodyResolver = resolve; + }); + sandbox.stub(dom, 'waitForBodyPromise').callsFake(() => bodyPromise); - // Stylesheet has been installed. - expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; + function skipMicro() { + return Promise.resolve().then(() => Promise.resolve()); + } + function waitNext(promise) { + return Promise.race([promise, skipMicro()]); + } - // Doc services have been installed. - expect(ampdoc.services.action).to.exist; - expect(ampdoc.services.action.obj).to.exist; - expect(ampdoc.services.viewer).to.exist; - expect(ampdoc.services.viewer.obj).to.exist; + let progress = ''; + const queueExtensions = win.AMP; + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += 'not expected 1'; + }, 'version123') + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '3'; + }) + ); + const promise = adopt(win); + runChunksForTesting(win.document); - // Single-doc bidings have been installed. - expect(ret.ampdoc).to.equal(ampdoc); - expect(ret.viewer).to.not.exist; - }); + yield waitNext(promise); + // Extensions are still unprocessed + expect(progress).to.equal(''); - it('should install doc services', () => { - class Service1 {} - win.AMP.push({ - n: 'amp-ext', - f: amp => { - amp.registerServiceForDoc('service1', Service1); - }, - }); + // Add one more + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '4'; + }) + ); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += 'not expected 2'; + }, 'version123') + ); + // Add legacy element (5) and eagarly ask for its load as ElementStub does. + Services.extensionsFor(win).preloadExtension('amp-test-element5', false); + win.AMP.push( + regularExtension(amp => { + expect(amp).to.equal(win.AMP); + progress += '5'; + }, 'version123') + ); + runChunksForTesting(win.document); - const script = win.document.createElement('script'); - script.setAttribute('custom-element', 'amp-ext'); - script.setAttribute('src', ''); - importDoc.head.appendChild(script); + yield waitNext(promise); + expect(progress).to.equal(''); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + // Body is available now. + bodyResolver(); + runChunksForTesting(win.document); - return extensions.waitForExtension(win, 'amp-ext').then(() => { - // Factories have been applied. - expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); - }); + yield waitNext(promise); + expect(progress).to.equal('134'); + expect(queueExtensions).to.have.length(0); + expect(s1.getAttribute('custom-element')).to.be.null; + expect(s2.getAttribute('custom-element')).to.be.null; + expect(s3.getAttribute('custom-element')).to.be.null; + expect(s1.getAttribute('i-amphtml-loaded-new-version')).to.equal( + 'amp-test-element1' + ); + expect(s2.getAttribute('i-amphtml-loaded-new-version')).to.equal( + 'amp-test-element4' + ); + expect(s3.getAttribute('i-amphtml-loaded-new-version')).to.equal( + 'amp-test-element5' + ); + const inserted = win.document.head.querySelectorAll( + '[i-amphtml-inserted]' + ); + expect(inserted).to.have.length(3); + expect(inserted[0].getAttribute('src')).to.equal( + 'https://cdn.ampproject.org/rtv/test-version' + + '/v0/amp-test-element1-0.1.js' + ); + expect(inserted[1].getAttribute('src')).to.equal( + 'https://cdn.ampproject.org/rtv/test-version' + + '/v0/amp-test-element4-0.1.js' + ); + expect(inserted[2].getAttribute('src')).to.equal( + 'https://cdn.ampproject.org/rtv/test-version' + + '/v0/amp-test-element5-0.1.js' + ); + }); + + it('should be robust against errors in early extensions', function*() { + let progress = ''; + win.AMP.push( + regularExtension(() => { + progress += '1'; + }) + ); + win.AMP.push( + regularExtension(() => { + throw new Error('extension error'); + }) + ); + win.AMP.push( + regularExtension(() => { + progress += '3'; + }) + ); + const promise = adopt(win); + runChunksForTesting(win.document); + yield promise; + expect(progress).to.equal('13'); }); - it('should pass init parameters to viewer', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { - 'test1': '12', - }); + describe('single-mode', () => { + let extensions; - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getParam('test1')).to.equal('12'); - }); + beforeEach(() => { + const promise = adopt(win); + ext.installExtensionsService(win); + extensions = Services.extensionsFor(win); + return promise; + }); - it('should update host visibility', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + it('should export properties to global AMP object', () => { + expect(win.AMP.BaseElement).to.be.a('function'); + expect(win.AMP.BaseTemplate).to.be.a('function'); + expect(win.AMP.registerElement).to.be.a('function'); + expect(win.AMP.registerTemplate).to.be.a('function'); + expect(win.AMP.setTickFunction).to.be.a('function'); + expect(win.AMP.win).to.equal(win); + + expect(win.AMP.viewer).to.be.a('object'); + expect(win.AMP.viewport).to.be.a('object'); + // Single-doc mode does not create `attachShadowDoc`. + expect(win.AMP.attachShadowDoc).to.not.exist; + expect(win.AMP.attachShadowDocAsStream).to.not.exist; + }); - // Document is invisible at first. - expect(hostElement.style.visibility).to.equal('hidden'); + it('should register element without CSS', function*() { + const ampdoc = ampdocService.getAmpDoc(); + const servicePromise = getServicePromise(win, 'amp-ext'); + const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); + + ampdoc.declareExtension('amp-ext'); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement); + }, + }); + runChunksForTesting(win.document); + yield extensions.waitForExtension(win, 'amp-ext'); + + // Extension is added immediately. Can't find for micro-tasks here. + const ext = extensions.extensions_['amp-ext'].extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + + // No installStyles calls. + expect(installStylesStub).to.have.not.been.called; + + // Register is called immediately as well. + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); + }); - // After timeout the doc rendered is started. - clock.tick(3000); - expect(hostElement.style.visibility).to.equal('visible'); - expect(ampdoc.signals().get('render-start')).to.be.ok; + it('should register element with CSS', function*() { + const ampdoc = Services.ampdocServiceFor(win).getAmpDoc(); + const servicePromise = getServicePromise(win, 'amp-ext'); + let installStylesCallback; + const installStylesStub = sandbox + .stub(styles, 'installStylesForDoc') + .callsFake((doc, cssText, cb) => { + installStylesCallback = cb; + }); - return ampdoc.whenReady().then(() => { - expect(ampdoc.isReady()).to.be.true; + ampdoc.declareExtension('amp-ext'); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + }, + }); + runChunksForTesting(win.document); + + // Extension is added immediately. Can't find for micro-tasks here. + yield extensions.waitForExtension(win, 'amp-ext'); + const ext = extensions.extensions_['amp-ext'].extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + expect(ext.elements['amp-ext'].css).to.equal('a{}'); + + expect(installStylesStub).to.be.calledOnce; + expect(installStylesStub).to.be.calledWithExactly( + ampdoc, + 'a{}', + installStylesCallback, + /* isRuntimeCss */ false, + /* ext */ 'amp-ext' + ); + + // Element resistration is not done until callback. + expect(win.ampExtendedElements['amp-ext']).to.be.undefined; + installStylesCallback(); + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); }); - }); - it('should import body', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - const body = shadowRoot.querySelector('body') || - shadowRoot.querySelector('amp-body'); - expect(body).to.exist; - expect(body).to.have.class('amp-shadow'); - expect(body.style.position).to.equal('relative'); - expect(body.querySelector('child')).to.exist; - expect(ampdoc.getBody()).to.exist; - }); + it('should register doc-service as ctor and install imm', function*() { + class Service1 {} + const ampdoc = new AmpDocSingle(win); + ampdoc.declareExtension('amp-ext'); + ampdocServiceMock + .expects('getAmpDoc') + .returns(ampdoc) + .atLeast(1); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); + runChunksForTesting(win.document); - it('should read title element', () => { - const titleEl = win.document.createElement('title'); - titleEl.textContent = 'test title'; - importDoc.head.appendChild(titleEl); - const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(ret.title).to.equal('test title'); - expect(getShadowRoot(hostElement).AMP.title).to.equal('test title'); - }); + // No factories + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + expect(extHolder.docFactories).to.have.length(1); - it('should read canonical element', () => { - const canonicalEl = win.document.createElement('link'); - canonicalEl.setAttribute('rel', 'canonical'); - canonicalEl.setAttribute('href', 'http://example.org/canonical'); - importDoc.head.appendChild(canonicalEl); - const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(ret.canonicalUrl).to.equal('http://example.org/canonical'); - }); + // Already installed. + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); - it('should import fonts', () => { - const fontEl1 = win.document.createElement('link'); - fontEl1.setAttribute('rel', 'stylesheet'); - fontEl1.setAttribute('href', 'http://example.org/font1'); - importDoc.head.appendChild(fontEl1); - const fontEl2 = win.document.createElement('link'); - fontEl2.setAttribute('rel', 'stylesheet'); - fontEl2.setAttribute('href', 'http://example.org/font2'); - importDoc.head.appendChild(fontEl2); - win.document.head.appendChild(fontEl2.cloneNode(true)); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector( - 'link[href="http://example.org/font1"]')).to.exist; - // Duplicates are ignored. - expect(win.document.querySelectorAll( - 'link[href="http://example.org/font2"]')).to.have.length(1); - - const fontEl = win.document.querySelector( - 'link[href="http://example.org/font1"]'); - expect(fontEl.getAttribute('type')).to.equal('text/css'); - expect(fontEl.getAttribute('rel')).to.equal('stylesheet'); - fontEl.parentElement.removeChild(fontEl); - }); + // The main top-level service is also pinged to unblock render. + yield getServicePromise(win, 'service1'); + }); - it('should ignore boilerplate style', () => { - const styleEl = win.document.createElement('style'); - styleEl.setAttribute('amp-boilerplate', ''); - importDoc.head.appendChild(styleEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - expect(shadowRoot.querySelector('style[amp-boilerplate]')).to.not.exist; - }); + it('should register doc-service factory and install', function*() { + let count = 0; + function factory() { + count++; + return {str: 'A'}; + } + const ampdoc = new AmpDocSingle(win); + ampdoc.declareExtension('amp-ext'); + ampdocServiceMock + .expects('getAmpDoc') + .returns(ampdoc) + .atLeast(1); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', factory); + }, + }); + runChunksForTesting(win.document); - it('should import custom style', () => { - const styleEl = win.document.createElement('style'); - styleEl.setAttribute('amp-custom', ''); - styleEl.textContent = '.custom{}'; - importDoc.head.appendChild(styleEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - expect(shadowRoot.querySelector('style[amp-custom]')).to.exist; - expect(shadowRoot.querySelector('style[amp-custom]').textContent) - .to.contain('.custom'); - }); + // No factories + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + expect(extHolder.docFactories).to.have.length(1); - it('should import keyframes style', () => { - const styleEl = win.document.createElement('style'); - styleEl.setAttribute('amp-keyframes', ''); - styleEl.textContent = '.keyframes{}'; - importDoc.head.appendChild(styleEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const shadowRoot = getShadowRoot(hostElement); - expect(shadowRoot.querySelector('style[amp-custom]')).to.not.exist; - expect(shadowRoot.querySelector('style[amp-keyframes]')).to.exist; - expect(shadowRoot.querySelector('style[amp-keyframes]').textContent) - .to.contain('.keyframes'); + // Already installed. + expect(count).to.equal(1); + expect(getServiceForDoc(ampdoc, 'service1')).to.deep.equal({str: 'A'}); + }); }); - it('should ignore runtime extension', () => { - extensionsMock.expects('preloadExtension').never(); + describe('shadow-mode', () => { + let extensions; - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('src', 'https://cdn.ampproject.org/v0.js'); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - }); + beforeEach(() => { + const promise = adoptShadowMode(win); + ext.installExtensionsService(win); + extensions = Services.extensionsFor(win); + return promise; + }); - it('should ignore unknown script', () => { - extensionsMock.expects('preloadExtension').never(); + it('should export properties to global AMP object', () => { + expect(win.AMP.BaseElement).to.be.a('function'); + expect(win.AMP.BaseTemplate).to.be.a('function'); + expect(win.AMP.registerElement).to.be.a('function'); + expect(win.AMP.registerTemplate).to.be.a('function'); + expect(win.AMP.setTickFunction).to.be.a('function'); + expect(win.AMP.win).to.equal(win); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('data-id', 'unknown1'); - scriptEl.setAttribute('src', 'https://cdn.ampproject.org/other.js'); - importDoc.head.appendChild(scriptEl); - allowConsoleError(() => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - }); - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="unknown1"]')).to.not.exist; - expect(win.document.querySelector('script[data-id="unknown1"]')) - .to.not.exist; - }); + expect(win.AMP.attachShadowDoc).to.be.a('function'); + expect(win.AMP.attachShadowDocAsStream).to.be.a('function'); - it('should import extension element', () => { - extensionsMock.expects('preloadExtension') - .withExactArgs('amp-ext1', '0.1') - .returns(Promise.resolve({ - elements: { - 'amp-ext1': function() {}, - }, - })) - .once(); + expect(win.AMP.viewer).to.not.exist; + expect(win.AMP.viewport).to.not.exist; + }); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('custom-element', 'amp-ext1'); - scriptEl.setAttribute('src', ''); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector('script[custom-element="amp-ext1"]')) - .to.not.exist; - }); + it('should register element without CSS', function*() { + const servicePromise = getServicePromise(win, 'amp-ext'); + const installStylesStub = sandbox.stub(styles, 'installStylesForDoc'); - it('should import extension element with version ≠ 0.1', () => { - extensionsMock.expects('preloadExtension') - .withExactArgs('amp-ext1', '1.0') - .returns(Promise.resolve({ - elements: { - 'amp-ext1': function() { }, - }, - })) - .once(); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement); + }, + }); + runChunksForTesting(win.document); + + // Extension is added immediately. Can't find for micro-tasks here. + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + const ext = extHolder.extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + + // No installStyles calls and no factories. + expect(installStylesStub).to.not.be.called; + expect(extHolder.docFactories).to.have.length(1); + expect(win.ampExtendedElements['amp-ext']).to.be.undefined; + + // Execute factory to install style. + const shadowRoot = document.createDocumentFragment(); + const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); + extHolder.docFactories[0](ampdoc); + expect(installStylesStub).to.not.be.called; + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); + }); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('custom-element', 'amp-ext1'); - scriptEl.setAttribute('src', 'https://cdn.ampproject.org/v0/amp-ext1-1.0.js'); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector('script[custom-element="amp-ext1"]')) - .to.not.exist; - }); + it('should register element with CSS', function*() { + const servicePromise = getServicePromise(win, 'amp-ext'); + let installStylesCallback; + const installStylesStub = sandbox + .stub(styles, 'installStylesForDoc') + .callsFake((doc, cssText, cb) => { + installStylesCallback = cb; + }); - it('should import extension template', () => { - extensionsMock.expects('preloadExtension') - .withExactArgs('amp-ext1', '0.1') - .returns(Promise.resolve({elements: {}})) - .once(); + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerElement('amp-ext', win.AMP.BaseElement, 'a{}'); + }, + }); + runChunksForTesting(win.document); + + // Extension is added immediately. Can't find for micro-tasks here. + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + const ext = extHolder.extension; + expect(ext.elements['amp-ext']).exist; + expect(ext.elements['amp-ext'].implementationClass).to.equal( + win.AMP.BaseElement + ); + expect(ext.elements['amp-ext'].css).to.equal('a{}'); + // No installations yet, but there's a factory. + expect(extHolder.docFactories).to.have.length(1); + expect(win.ampExtendedElements['amp-ext']).to.be.undefined; + expect(installStylesStub).to.have.not.been.called; + + // Execute factory to install style. + const shadowRoot = document.createDocumentFragment(); + const ampdoc = new AmpDocShadow(win, 'https://acme.org/', shadowRoot); + extHolder.docFactories[0](ampdoc); + expect(installStylesStub).to.be.calledOnce; + expect(installStylesStub).to.be.calledWithExactly( + ampdoc, + 'a{}', + installStylesCallback, + /* isRuntimeCss */ false, + /* ext */ 'amp-ext' + ); + + // Run install. + installStylesCallback(); + expect(win.ampExtendedElements['amp-ext']).to.equal(AMP.BaseElement); + + // Service and extensions are resolved. + yield Promise.all([ + extensions.waitForExtension(win, 'amp-ext'), + servicePromise, + ]); + }); - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('custom-template', 'amp-ext1'); - scriptEl.setAttribute('src', ''); - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(win.document.querySelector('script[custom-template="amp-ext1"]')) - .to.not.exist; - }); + it('should register doc-service as ctor and defer install', function*() { + class Service1 {} + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); + runChunksForTesting(win.document); - it('should import inline script', () => { - const scriptEl = win.document.createElement('script'); - scriptEl.setAttribute('type', 'application/json'); - scriptEl.setAttribute('data-id', 'test1'); - scriptEl.textContent = '{}'; - importDoc.head.appendChild(scriptEl); - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.exist; - expect(getShadowRoot(hostElement).querySelector( - 'script[data-id="test1"]').textContent).to.equal('{}'); - }); + // Factory recorded. + yield extensions.waitForExtension(win, 'amp-ext'); + const extHolder = extensions.extensions_['amp-ext']; + expect(extHolder.docFactories).to.have.length(1); - it('should ignore inline script if javascript', () => { - const scriptEl1 = win.document.createElement('script'); - scriptEl1.setAttribute('type', 'application/javascript'); - scriptEl1.setAttribute('data-id', 'test1'); - importDoc.head.appendChild(scriptEl1); - const scriptEl2 = win.document.createElement('script'); - scriptEl2.setAttribute('data-id', 'test1'); - importDoc.head.appendChild(scriptEl2); - allowConsoleError(() => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - }); - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.not.exist; - }); + const shadowRoot = document.createDocumentFragment(); + const ampdoc = new AmpDocShadow(win, 'https://a.org/', shadowRoot); - it('should start as visible by default', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getVisibilityState()).to.equal('visible'); - }); + // Not installed. + expect(getServicePromiseOrNullForDoc(ampdoc, 'service1')).to.be.null; - it('should start as prerender when requested', () => { - win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { - 'visibilityState': 'prerender', + // Install. + extHolder.docFactories[0](ampdoc); + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf(Service1); }); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getVisibilityState()).to.equal('prerender'); }); + } +); + +describes.realWin( + 'runtime multidoc', + { + amp: {ampdoc: 'multi'}, + }, + env => { + let win; + let extensions; + let extensionsMock; + let ampdocServiceMock; - it('should expose visibility method', () => { - const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(amp.setVisibilityState).to.be.a('function'); - expect(viewer.getVisibilityState()).to.equal('visible'); - - amp.setVisibilityState('inactive'); - expect(viewer.getVisibilityState()).to.equal('inactive'); + beforeEach(() => { + win = env.win; + extensions = env.extensions; + extensionsMock = sandbox.mock(extensions); + ampdocServiceMock = sandbox.mock(env.ampdocService); }); - it('should expose close method and dispose services', () => { - const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(amp.close).to.be.a('function'); - expect(viewer.getVisibilityState()).to.equal('visible'); - - viewer.dispose = sandbox.spy(); - amp.close(); - expect(viewer.getVisibilityState()).to.equal('inactive'); - expect(viewer.dispose).to.be.calledOnce; + afterEach(() => { + extensionsMock.verify(); + ampdocServiceMock.verify(); }); - }); + describe('attachShadowDoc', () => { + const docUrl = 'https://example.org/doc1'; - describe('attachShadowDocAsStream', () => { - const docUrl = 'https://example.org/doc1'; + let clock; + let importDoc; + let hostElement; + let ampdoc; - let hostElement; - let ampdoc; - let shadowDoc; - let writer; + beforeEach(() => { + deactivateChunking(); + clock = sandbox.useFakeTimers(); + hostElement = win.document.createElement('div'); + importDoc = win.document.implementation.createHTMLDocument(''); + importDoc.body.appendChild(win.document.createElement('child')); + const shadowRoot = createShadowRoot(hostElement); + ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - beforeEach(() => { - deactivateChunking(); - hostElement = win.document.createElement('div'); - const shadowRoot = createShadowRoot(hostElement); - ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - - ampdocServiceMock.expects('installShadowDoc') + ampdocServiceMock + .expects('installShadowDoc') .withExactArgs( - docUrl, - sinon.match(arg => arg == getShadowRoot(hostElement))) + docUrl, + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) .returns(ampdoc) .atLeast(0); - ampdocServiceMock.expects('getAmpDoc') + ampdocServiceMock + .expects('getAmpDoc') .withExactArgs(sinon.match(arg => arg == getShadowRoot(hostElement))) .returns(ampdoc) .atLeast(0); - }); + }); - it('should install services and styles', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; + it('should install services and styles', () => { + const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(ret).to.exist; - const shadowRoot = getShadowRoot(hostElement); + const shadowRoot = getShadowRoot(hostElement); - // URL is set. - expect(shadowRoot.AMP.url).to.equal(docUrl); + // URL is set. + expect(shadowRoot.AMP.url).to.equal(docUrl); - // Stylesheet has been installed. - expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; + // Stylesheet has been installed. + expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; - // Doc services have been installed. - expect(ampdoc.services.action).to.exist; - expect(ampdoc.services.action.obj).to.exist; - expect(ampdoc.services.viewer).to.exist; - expect(ampdoc.services.viewer.obj).to.exist; + // Doc services have been installed. + expect(ampdoc.services.action).to.exist; + expect(ampdoc.services.action.obj).to.exist; + expect(ampdoc.services.viewer).to.exist; + expect(ampdoc.services.viewer.obj).to.exist; - // Single-doc bidings have been installed. - expect(shadowDoc.ampdoc).to.equal(ampdoc); - expect(shadowDoc.viewer).to.not.exist; - }); + // Single-doc bidings have been installed. + expect(ret.ampdoc).to.equal(ampdoc); + expect(ret.viewer).to.not.exist; + }); - it('should install doc services', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; + it('should install doc services', () => { + class Service1 {} + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); - class Service1 {} - win.AMP.push({ - n: 'amp-ext', - f: amp => { - amp.registerServiceForDoc('service1', Service1); - }, - }); + const script = win.document.createElement('script'); + script.setAttribute('custom-element', 'amp-ext'); + script.setAttribute('src', ''); + importDoc.head.appendChild(script); - writer.write(''); - writer.write(''); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - return ampdoc.whenBodyAvailable().then(() => { return extensions.waitForExtension(win, 'amp-ext').then(() => { // Factories have been applied. - expect(getServiceForDoc(ampdoc, 'service1')) - .to.be.instanceOf(Service1); + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf( + Service1 + ); }); }); - }); - it('should pass init parameters to viewer', () => { - win.AMP.attachShadowDocAsStream(hostElement, docUrl, { - 'test1': '12', - }); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(viewer.getParam('test1')).to.equal('12'); - }); + it('should pass init parameters to viewer', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { + 'test1': '12', + }); - it('should update host visibility', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getParam('test1')).to.equal('12'); + }); - writer.write('
    '); + it('should update host visibility', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - // Document is invisible at first. - expect(hostElement.style.visibility).to.equal('hidden'); + // Document is invisible at first. + expect(hostElement.style.visibility).to.equal('hidden'); - return ampdoc.whenBodyAvailable().then(() => { // After timeout the doc rendered is started. - expect(hostElement.style.visibility).to.equal('hidden'); - return ampdoc.signals().whenSignal('render-start'); - }).then(() => { + clock.tick(3000); expect(hostElement.style.visibility).to.equal('visible'); + expect(ampdoc.signals().get('render-start')).to.be.ok; + + return ampdoc.whenReady().then(() => { + expect(ampdoc.isReady()).to.be.true; + }); }); - }); - it('should import body', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should import body', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const shadowRoot = getShadowRoot(hostElement); - const body = shadowRoot.querySelector('body') || - shadowRoot.querySelector('amp-body'); + const body = + shadowRoot.querySelector('body') || + shadowRoot.querySelector('amp-body'); expect(body).to.exist; expect(body).to.have.class('amp-shadow'); expect(body.style.position).to.equal('relative'); - env.flushVsync(); expect(body.querySelector('child')).to.exist; expect(ampdoc.getBody()).to.exist; }); - }); - - it('should mark doc as ready', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { - expect(ampdoc.isReady()).to.be.false; - writer.write(''); - writer.write(''); - writer.close(); - return ampdoc.whenReady().then(() => { - expect(ampdoc.isReady()).to.be.true; - }); - }); - }); - it('should read title element', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write('test title'); - return ampdoc.whenBodyAvailable().then(() => { - expect(shadowDoc.title).to.equal('test title'); + it('should read title element', () => { + const titleEl = win.document.createElement('title'); + titleEl.textContent = 'test title'; + importDoc.head.appendChild(titleEl); + const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(ret.title).to.equal('test title'); expect(getShadowRoot(hostElement).AMP.title).to.equal('test title'); }); - }); - it('should read canonical element', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write( - ''); - return ampdoc.whenBodyAvailable().then(() => { - expect(shadowDoc.canonicalUrl).to.equal('http://example.org/canonical'); + it('should read canonical element', () => { + const canonicalEl = win.document.createElement('link'); + canonicalEl.setAttribute('rel', 'canonical'); + canonicalEl.setAttribute('href', 'http://example.org/canonical'); + importDoc.head.appendChild(canonicalEl); + const ret = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(ret.canonicalUrl).to.equal('http://example.org/canonical'); }); - }); - it('should import fonts', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - - writer.write( - ''); - writer.write( - ''); - const fontEl2 = win.document.createElement('link'); - fontEl2.setAttribute('rel', 'stylesheet'); - fontEl2.setAttribute('href', 'http://example.org/font2'); - win.document.head.appendChild(fontEl2); - - return ampdoc.whenBodyAvailable().then(() => { - expect(win.document.querySelector( - 'link[href="http://example.org/font1"]')).to.exist; + it('should import fonts', () => { + const fontEl1 = win.document.createElement('link'); + fontEl1.setAttribute('rel', 'stylesheet'); + fontEl1.setAttribute('href', 'http://example.org/font1'); + importDoc.head.appendChild(fontEl1); + const fontEl2 = win.document.createElement('link'); + fontEl2.setAttribute('rel', 'stylesheet'); + fontEl2.setAttribute('href', 'http://example.org/font2'); + importDoc.head.appendChild(fontEl2); + win.document.head.appendChild(fontEl2.cloneNode(true)); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect( + win.document.querySelector('link[href="http://example.org/font1"]') + ).to.exist; // Duplicates are ignored. - expect(win.document.querySelectorAll( - 'link[href="http://example.org/font2"]')).to.have.length(1); + expect( + win.document.querySelectorAll('link[href="http://example.org/font2"]') + ).to.have.length(1); const fontEl = win.document.querySelector( - 'link[href="http://example.org/font1"]'); + 'link[href="http://example.org/font1"]' + ); expect(fontEl.getAttribute('type')).to.equal('text/css'); expect(fontEl.getAttribute('rel')).to.equal('stylesheet'); fontEl.parentElement.removeChild(fontEl); }); - }); - it('should ignore boilerplate style', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should ignore boilerplate style', () => { + const styleEl = win.document.createElement('style'); + styleEl.setAttribute('amp-boilerplate', ''); + importDoc.head.appendChild(styleEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const shadowRoot = getShadowRoot(hostElement); expect(shadowRoot.querySelector('style[amp-boilerplate]')).to.not.exist; }); - }); - it('should import custom style', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should import custom style', () => { + const styleEl = win.document.createElement('style'); + styleEl.setAttribute('amp-custom', ''); + styleEl.textContent = '.custom{}'; + importDoc.head.appendChild(styleEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const shadowRoot = getShadowRoot(hostElement); expect(shadowRoot.querySelector('style[amp-custom]')).to.exist; - expect(shadowRoot.querySelector('style[amp-custom]').textContent) - .to.contain('.custom'); + expect( + shadowRoot.querySelector('style[amp-custom]').textContent + ).to.contain('.custom'); }); - }); - it('should ignore runtime extension', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension').never(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable(); - }); + it('should import keyframes style', () => { + const styleEl = win.document.createElement('style'); + styleEl.setAttribute('amp-keyframes', ''); + styleEl.textContent = '.keyframes{}'; + importDoc.head.appendChild(styleEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + const shadowRoot = getShadowRoot(hostElement); + expect(shadowRoot.querySelector('style[amp-custom]')).to.not.exist; + expect(shadowRoot.querySelector('style[amp-keyframes]')).to.exist; + expect( + shadowRoot.querySelector('style[amp-keyframes]').textContent + ).to.contain('.keyframes'); + }); - it('should ignore unknown script', () => { - expectAsyncConsoleError( - '[runtime] - unknown script: [object HTMLScriptElement] ' + - 'https://cdn.ampproject.org/other.js'); + it('should ignore runtime extension', () => { + extensionsMock.expects('preloadExtension').never(); - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension').never(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="unknown1"]')).to.not.exist; - expect(win.document.querySelector('script[data-id="unknown1"]')) - .to.not.exist; + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('src', 'https://cdn.ampproject.org/v0.js'); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + }); + + it('should ignore unknown script', () => { + extensionsMock.expects('preloadExtension').never(); + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('data-id', 'unknown1'); + scriptEl.setAttribute('src', 'https://cdn.ampproject.org/other.js'); + importDoc.head.appendChild(scriptEl); + allowConsoleError(() => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + }); + expect( + getShadowRoot(hostElement).querySelector('script[data-id="unknown1"]') + ).to.not.exist; + expect(win.document.querySelector('script[data-id="unknown1"]')).to.not + .exist; }); - }); - it('should import extension element', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension') + it('should import extension element', () => { + extensionsMock + .expects('preloadExtension') .withExactArgs('amp-ext1', '0.1') - .returns(Promise.resolve({ - elements: { - 'amp-ext1': function() {}, - }, - })) + .returns( + Promise.resolve({ + elements: { + 'amp-ext1': function() {}, + }, + }) + ) .once(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('custom-element', 'amp-ext1'); + scriptEl.setAttribute('src', ''); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); expect(win.document.querySelector('script[custom-element="amp-ext1"]')) - .to.not.exist; + .to.not.exist; + }); + + it('should import extension element with version ≠ 0.1', () => { + extensionsMock + .expects('preloadExtension') + .withExactArgs('amp-ext1', '1.0') + .returns( + Promise.resolve({ + elements: { + 'amp-ext1': function() {}, + }, + }) + ) + .once(); + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('custom-element', 'amp-ext1'); + scriptEl.setAttribute( + 'src', + 'https://cdn.ampproject.org/v0/amp-ext1-1.0.js' + ); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect(win.document.querySelector('script[custom-element="amp-ext1"]')) + .to.not.exist; }); - }); - it('should import extension template', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - extensionsMock.expects('preloadExtension') + it('should import extension template', () => { + extensionsMock + .expects('preloadExtension') .withExactArgs('amp-ext1', '0.1') .returns(Promise.resolve({elements: {}})) .once(); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('custom-template', 'amp-ext1'); + scriptEl.setAttribute('src', ''); + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); expect(win.document.querySelector('script[custom-template="amp-ext1"]')) - .to.not.exist; + .to.not.exist; }); - }); - it('should import inline script', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.exist; - expect(getShadowRoot(hostElement).querySelector( - 'script[data-id="test1"]').textContent).to.equal('{}'); + it('should import inline script', () => { + const scriptEl = win.document.createElement('script'); + scriptEl.setAttribute('type', 'application/json'); + scriptEl.setAttribute('data-id', 'test1'); + scriptEl.textContent = '{}'; + importDoc.head.appendChild(scriptEl); + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.exist; + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + .textContent + ).to.equal('{}'); }); - }); - it('should ignore inline script if javascript', () => { - expectAsyncConsoleError( - '[runtime] - unallowed inline javascript: ' + - '[object HTMLScriptElement]', 2); - - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write( - ''); - writer.write( - ''); - writer.write(''); - return ampdoc.whenBodyAvailable(() => { - expect(getShadowRoot(hostElement) - .querySelector('script[data-id="test1"]')).to.not.exist; + it('should ignore inline script if javascript', () => { + const scriptEl1 = win.document.createElement('script'); + scriptEl1.setAttribute('type', 'application/javascript'); + scriptEl1.setAttribute('data-id', 'test1'); + importDoc.head.appendChild(scriptEl1); + const scriptEl2 = win.document.createElement('script'); + scriptEl2.setAttribute('data-id', 'test1'); + importDoc.head.appendChild(scriptEl2); + allowConsoleError(() => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + }); + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.not.exist; }); - }); - it('should start as visible by default', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should start as visible by default', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const viewer = getServiceForDoc(ampdoc, 'viewer'); expect(viewer.getVisibilityState()).to.equal('visible'); }); - }); - it('should start as prerender when requested', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl, { - 'visibilityState': 'prerender', - }); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should start as prerender when requested', () => { + win.AMP.attachShadowDoc(hostElement, importDoc, docUrl, { + 'visibilityState': 'prerender', + }); const viewer = getServiceForDoc(ampdoc, 'viewer'); expect(viewer.getVisibilityState()).to.equal('prerender'); }); - }); - it('should expose visibility method', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should expose visibility method', () => { + const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(shadowDoc.setVisibilityState).to.be.a('function'); + expect(amp.setVisibilityState).to.be.a('function'); expect(viewer.getVisibilityState()).to.equal('visible'); - shadowDoc.setVisibilityState('inactive'); + amp.setVisibilityState('inactive'); expect(viewer.getVisibilityState()).to.equal('inactive'); }); - }); - it('should expose close method and dispose services', () => { - shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); - writer = shadowDoc.writer; - writer.write(''); - return ampdoc.whenBodyAvailable().then(() => { + it('should expose close method and dispose services', () => { + const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); const viewer = getServiceForDoc(ampdoc, 'viewer'); - expect(shadowDoc.close).to.be.a('function'); + expect(amp.close).to.be.a('function'); expect(viewer.getVisibilityState()).to.equal('visible'); viewer.dispose = sandbox.spy(); - shadowDoc.close(); + amp.close(); expect(viewer.getVisibilityState()).to.equal('inactive'); expect(viewer.dispose).to.be.calledOnce; }); }); - }); - - - describes.repeated('messaging', { - 'document.contains is the browser implementation': false, - 'document.contains is a stubbed implementation': true, - }, (name, isStubbedDocumentContains) => { - let doc1, doc2, doc3; - beforeEach(() => { - if (isStubbedDocumentContains) { - // Some browsers implement document.contains wrong, and it returns - // `false` even when this is incorrect. Repeat these tests with the - // faulty implementation. - sandbox.stub(win.document, 'contains').returns(false); - } + describe('attachShadowDocAsStream', () => { + const docUrl = 'https://example.org/doc1'; - doc1 = attach('https://example.org/doc1'); - doc2 = attach('https://example.org/doc2'); - doc3 = attach('https://example.org/doc3'); - }); + let hostElement; + let ampdoc; + let shadowDoc; + let writer; - function attach(docUrl) { - const hostElement = win.document.createElement('div'); - win.document.body.appendChild(hostElement); - const importDoc = win.document.implementation.createHTMLDocument(''); - const shadowRoot = createShadowRoot(hostElement); - const ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); + beforeEach(() => { + deactivateChunking(); + hostElement = win.document.createElement('div'); + const shadowRoot = createShadowRoot(hostElement); + ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - ampdocServiceMock.expects('installShadowDoc') + ampdocServiceMock + .expects('installShadowDoc') .withExactArgs( - docUrl, - sinon.match(arg => arg == getShadowRoot(hostElement))) + docUrl, + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) .returns(ampdoc) .atLeast(0); - ampdocServiceMock.expects('getAmpDoc') + ampdocServiceMock + .expects('getAmpDoc') .withExactArgs(sinon.match(arg => arg == getShadowRoot(hostElement))) .returns(ampdoc) .atLeast(0); + }); - const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); - const viewer = getServiceForDoc(ampdoc, 'viewer'); - const broadcastReceived = sandbox.spy(); - viewer.onBroadcast(broadcastReceived); - const onMessage = sandbox.stub(); - amp.onMessage(function(eventType, data) { - if (eventType == 'ignore') { - return Promise.resolve(); - } - return onMessage(eventType, data); + it('should install services and styles', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + const shadowRoot = getShadowRoot(hostElement); + + // URL is set. + expect(shadowRoot.AMP.url).to.equal(docUrl); + + // Stylesheet has been installed. + expect(shadowRoot.querySelector('style[amp-runtime]')).to.exist; + + // Doc services have been installed. + expect(ampdoc.services.action).to.exist; + expect(ampdoc.services.action.obj).to.exist; + expect(ampdoc.services.viewer).to.exist; + expect(ampdoc.services.viewer.obj).to.exist; + + // Single-doc bidings have been installed. + expect(shadowDoc.ampdoc).to.equal(ampdoc); + expect(shadowDoc.viewer).to.not.exist; }); - return {hostElement, amp, ampdoc, viewer, broadcastReceived, onMessage}; - } - it('should broadcast to all but sender', () => { - doc1.viewer.broadcast({test: 1}); - return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { - // Sender is not called. - expect(doc1.broadcastReceived).to.not.be.called; + it('should install doc services', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + class Service1 {} + win.AMP.push({ + n: 'amp-ext', + f: amp => { + amp.registerServiceForDoc('service1', Service1); + }, + }); - // All others are called. - expect(doc2.broadcastReceived).to.be.calledOnce; - expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); - expect(doc3.broadcastReceived).to.be.calledOnce; - expect(doc3.broadcastReceived.args[0][0]).deep.equal({test: 1}); + writer.write(''); + writer.write(''); - // None of the onMessage are called. - expect(doc1.onMessage).to.not.be.called; - expect(doc2.onMessage).to.not.be.called; - expect(doc3.onMessage).to.not.be.called; + return ampdoc.whenBodyAvailable().then(() => { + return extensions.waitForExtension(win, 'amp-ext').then(() => { + // Factories have been applied. + expect(getServiceForDoc(ampdoc, 'service1')).to.be.instanceOf( + Service1 + ); + }); + }); }); - }); - it('should stop broadcasting after close', () => { - doc3.amp.close(); - doc1.viewer.broadcast({test: 1}); - return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { - // Sender is not called, closed is not called. - expect(doc1.broadcastReceived).to.not.be.called; - expect(doc3.broadcastReceived).to.not.be.called; + it('should pass init parameters to viewer', () => { + win.AMP.attachShadowDocAsStream(hostElement, docUrl, { + 'test1': '12', + }); + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getParam('test1')).to.equal('12'); + }); + + it('should update host visibility', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + writer.write('
    '); - // All others are called. - expect(doc2.broadcastReceived).to.be.calledOnce; - expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + // Document is invisible at first. + expect(hostElement.style.visibility).to.equal('hidden'); + + return ampdoc + .whenBodyAvailable() + .then(() => { + // After timeout the doc rendered is started. + expect(hostElement.style.visibility).to.equal('hidden'); + return ampdoc.signals().whenSignal('render-start'); + }) + .then(() => { + expect(hostElement.style.visibility).to.equal('visible'); + }); + }); + + it('should import body', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const shadowRoot = getShadowRoot(hostElement); + const body = + shadowRoot.querySelector('body') || + shadowRoot.querySelector('amp-body'); + expect(body).to.exist; + expect(body).to.have.class('amp-shadow'); + expect(body.style.position).to.equal('relative'); + env.flushVsync(); + expect(body.querySelector('child')).to.exist; + expect(ampdoc.getBody()).to.exist; + }); + }); + + it('should mark doc as ready', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect(ampdoc.isReady()).to.be.false; + writer.write(''); + writer.write(''); + writer.close(); + return ampdoc.whenReady().then(() => { + expect(ampdoc.isReady()).to.be.true; + }); + }); + }); + + it('should read title element', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write('test title'); + return ampdoc.whenBodyAvailable().then(() => { + expect(shadowDoc.title).to.equal('test title'); + expect(getShadowRoot(hostElement).AMP.title).to.equal('test title'); + }); + }); + + it('should read canonical element', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write( + '' + ); + return ampdoc.whenBodyAvailable().then(() => { + expect(shadowDoc.canonicalUrl).to.equal( + 'http://example.org/canonical' + ); + }); + }); + + it('should import fonts', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + + writer.write( + '' + ); + writer.write( + '' + ); + const fontEl2 = win.document.createElement('link'); + fontEl2.setAttribute('rel', 'stylesheet'); + fontEl2.setAttribute('href', 'http://example.org/font2'); + win.document.head.appendChild(fontEl2); + + return ampdoc.whenBodyAvailable().then(() => { + expect( + win.document.querySelector('link[href="http://example.org/font1"]') + ).to.exist; + // Duplicates are ignored. + expect( + win.document.querySelectorAll( + 'link[href="http://example.org/font2"]' + ) + ).to.have.length(1); + + const fontEl = win.document.querySelector( + 'link[href="http://example.org/font1"]' + ); + expect(fontEl.getAttribute('type')).to.equal('text/css'); + expect(fontEl.getAttribute('rel')).to.equal('stylesheet'); + fontEl.parentElement.removeChild(fontEl); + }); + }); + + it('should ignore boilerplate style', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const shadowRoot = getShadowRoot(hostElement); + expect(shadowRoot.querySelector('style[amp-boilerplate]')).to.not + .exist; + }); + }); + + it('should import custom style', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const shadowRoot = getShadowRoot(hostElement); + expect(shadowRoot.querySelector('style[amp-custom]')).to.exist; + expect( + shadowRoot.querySelector('style[amp-custom]').textContent + ).to.contain('.custom'); + }); + }); + + it('should ignore runtime extension', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock.expects('preloadExtension').never(); + writer.write( + '' + ); + writer.write(''); + return ampdoc.whenBodyAvailable(); + }); + + it('should ignore unknown script', () => { + expectAsyncConsoleError( + '[runtime] - unknown script: [object HTMLScriptElement] ' + + 'https://cdn.ampproject.org/other.js' + ); + + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock.expects('preloadExtension').never(); + writer.write( + '' + ); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + getShadowRoot(hostElement).querySelector( + 'script[data-id="unknown1"]' + ) + ).to.not.exist; + expect(win.document.querySelector('script[data-id="unknown1"]')).to + .not.exist; + }); + }); + + it('should import extension element', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock + .expects('preloadExtension') + .withExactArgs('amp-ext1', '0.1') + .returns( + Promise.resolve({ + elements: { + 'amp-ext1': function() {}, + }, + }) + ) + .once(); + writer.write(''); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + win.document.querySelector('script[custom-element="amp-ext1"]') + ).to.not.exist; + }); + }); + + it('should import extension template', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + extensionsMock + .expects('preloadExtension') + .withExactArgs('amp-ext1', '0.1') + .returns(Promise.resolve({elements: {}})) + .once(); + writer.write(''); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + win.document.querySelector('script[custom-template="amp-ext1"]') + ).to.not.exist; + }); + }); + + it('should import inline script', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write( + '' + ); + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.exist; + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + .textContent + ).to.equal('{}'); + }); + }); + + it('should ignore inline script if javascript', () => { + expectAsyncConsoleError( + '[runtime] - unallowed inline javascript: ' + + '[object HTMLScriptElement]', + 2 + ); + + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write( + '' + ); + writer.write(''); + writer.write(''); + return ampdoc.whenBodyAvailable(() => { + expect( + getShadowRoot(hostElement).querySelector('script[data-id="test1"]') + ).to.not.exist; + }); + }); + + it('should start as visible by default', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getVisibilityState()).to.equal('visible'); + }); + }); + + it('should start as prerender when requested', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl, { + 'visibilityState': 'prerender', + }); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(viewer.getVisibilityState()).to.equal('prerender'); + }); }); - }); - it('should stop broadcasting after force-close', () => { - doc3.hostElement.parentNode.removeChild(doc3.hostElement); - doc1.viewer.broadcast({test: 1}); - return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { - // Sender is not called, closed is not called. - expect(doc1.broadcastReceived).to.not.be.called; - expect(doc3.broadcastReceived).to.not.be.called; + it('should expose visibility method', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(shadowDoc.setVisibilityState).to.be.a('function'); + expect(viewer.getVisibilityState()).to.equal('visible'); + + shadowDoc.setVisibilityState('inactive'); + expect(viewer.getVisibilityState()).to.equal('inactive'); + }); + }); - // All others are called. - expect(doc2.broadcastReceived).to.be.calledOnce; - expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + it('should expose close method and dispose services', () => { + shadowDoc = win.AMP.attachShadowDocAsStream(hostElement, docUrl); + writer = shadowDoc.writer; + writer.write(''); + return ampdoc.whenBodyAvailable().then(() => { + const viewer = getServiceForDoc(ampdoc, 'viewer'); + expect(shadowDoc.close).to.be.a('function'); + expect(viewer.getVisibilityState()).to.equal('visible'); + + viewer.dispose = sandbox.spy(); + shadowDoc.close(); + expect(viewer.getVisibilityState()).to.equal('inactive'); + expect(viewer.dispose).to.be.calledOnce; + }); }); }); + describes.repeated( + 'messaging', + { + 'document.contains is the browser implementation': false, + 'document.contains is a stubbed implementation': true, + }, + (name, isStubbedDocumentContains) => { + let doc1, doc2, doc3; + + beforeEach(() => { + if (isStubbedDocumentContains) { + // Some browsers implement document.contains wrong, and it returns + // `false` even when this is incorrect. Repeat these tests with the + // faulty implementation. + sandbox.stub(win.document, 'contains').returns(false); + } + + doc1 = attach('https://example.org/doc1'); + doc2 = attach('https://example.org/doc2'); + doc3 = attach('https://example.org/doc3'); + }); + + function attach(docUrl) { + const hostElement = win.document.createElement('div'); + win.document.body.appendChild(hostElement); + const importDoc = win.document.implementation.createHTMLDocument(''); + const shadowRoot = createShadowRoot(hostElement); + const ampdoc = new AmpDocShadow(win, docUrl, shadowRoot); - it('should send message', () => { - doc1.onMessage.returns(Promise.resolve()); - return doc1.viewer.sendMessageAwaitResponse('test3', {test: 3}).then( - () => { - expect(doc1.onMessage).to.be.calledOnce; - expect(doc1.onMessage.args[0][0]).to.equal('test3'); - expect(doc1.onMessage.args[0][1]).to.deep.equal({test: 3}); + ampdocServiceMock + .expects('installShadowDoc') + .withExactArgs( + docUrl, + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) + .returns(ampdoc) + .atLeast(0); + ampdocServiceMock + .expects('getAmpDoc') + .withExactArgs( + sinon.match(arg => arg == getShadowRoot(hostElement)) + ) + .returns(ampdoc) + .atLeast(0); + + const amp = win.AMP.attachShadowDoc(hostElement, importDoc, docUrl); + const viewer = getServiceForDoc(ampdoc, 'viewer'); + const broadcastReceived = sandbox.spy(); + viewer.onBroadcast(broadcastReceived); + const onMessage = sandbox.stub(); + amp.onMessage(function(eventType, data) { + if (eventType == 'ignore') { + return Promise.resolve(); + } + return onMessage(eventType, data); }); - }); + return { + hostElement, + amp, + ampdoc, + viewer, + broadcastReceived, + onMessage, + }; + } - it('should receive message', () => { - doc1.amp.postMessage('broadcast', {test: 4}, true); - expect(doc1.broadcastReceived).to.be.calledOnce; - expect(doc1.broadcastReceived.args[0][0]).to.deep.equal({test: 4}); - }); - }); -}); + it('should broadcast to all but sender', () => { + doc1.viewer.broadcast({test: 1}); + return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { + // Sender is not called. + expect(doc1.broadcastReceived).to.not.be.called; + + // All others are called. + expect(doc2.broadcastReceived).to.be.calledOnce; + expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + expect(doc3.broadcastReceived).to.be.calledOnce; + expect(doc3.broadcastReceived.args[0][0]).deep.equal({test: 1}); + + // None of the onMessage are called. + expect(doc1.onMessage).to.not.be.called; + expect(doc2.onMessage).to.not.be.called; + expect(doc3.onMessage).to.not.be.called; + }); + }); + + it('should stop broadcasting after close', () => { + doc3.amp.close(); + doc1.viewer.broadcast({test: 1}); + return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { + // Sender is not called, closed is not called. + expect(doc1.broadcastReceived).to.not.be.called; + expect(doc3.broadcastReceived).to.not.be.called; + + // All others are called. + expect(doc2.broadcastReceived).to.be.calledOnce; + expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + }); + }); + + it('should stop broadcasting after force-close', () => { + doc3.hostElement.parentNode.removeChild(doc3.hostElement); + doc1.viewer.broadcast({test: 1}); + return doc1.viewer.sendMessageAwaitResponse('ignore', {}).then(() => { + // Sender is not called, closed is not called. + expect(doc1.broadcastReceived).to.not.be.called; + expect(doc3.broadcastReceived).to.not.be.called; + + // All others are called. + expect(doc2.broadcastReceived).to.be.calledOnce; + expect(doc2.broadcastReceived.args[0][0]).deep.equal({test: 1}); + }); + }); + + it('should send message', () => { + doc1.onMessage.returns(Promise.resolve()); + return doc1.viewer + .sendMessageAwaitResponse('test3', {test: 3}) + .then(() => { + expect(doc1.onMessage).to.be.calledOnce; + expect(doc1.onMessage.args[0][0]).to.equal('test3'); + expect(doc1.onMessage.args[0][1]).to.deep.equal({test: 3}); + }); + }); + + it('should receive message', () => { + doc1.amp.postMessage('broadcast', {test: 4}, true); + expect(doc1.broadcastReceived).to.be.calledOnce; + expect(doc1.broadcastReceived.args[0][0]).to.deep.equal({test: 4}); + }); + } + ); + } +); function getShadowRoot(hostElement) { return hostElement.shadowRoot || hostElement.__AMP_SHADOW_ROOT; diff --git a/test/unit/test-sanitizer.js b/test/unit/test-sanitizer.js index 2bc54a35fbabf..033227864ad51 100644 --- a/test/unit/test-sanitizer.js +++ b/test/unit/test-sanitizer.js @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - sanitizeHtml, - sanitizeTagsForTripleMustache, -} from '../../src/sanitizer'; +import {sanitizeHtml, sanitizeTagsForTripleMustache} from '../../src/sanitizer'; describe('Caja-based', () => { runSanitizerTests(); @@ -27,15 +24,17 @@ describe('Caja-based', () => { it('should apply html4/caja restrictions', () => { expect(sanitizeHtml('abc')).to.be.equal('ac'); expect(sanitizeHtml('abdc')).to.be.equal('ac'); - expect(sanitizeHtml('
    b
    ')).to.be - .equal('
    b
    '); + expect(sanitizeHtml('
    b
    ')).to.be.equal( + '
    b
    ' + ); }); // DOMPurify doesn't do special whitespace handling in attribute values. it('should catch attribute value whitespace variations', () => { allowConsoleError(() => { - expect(sanitizeHtml('ab')) - .to.be.equal('ab'); + expect( + sanitizeHtml('ab') + ).to.be.equal('ab'); }); }); @@ -43,31 +42,37 @@ describe('Caja-based', () => { it('should ignore invalid characters in attributes', () => { allowConsoleError(() => { expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); }); }); describe('for ', () => { it('should output [text] and [class] attributes', () => { - expect(sanitizeHtml('

    ')).to.be - .equal('

    '); + expect(sanitizeHtml('

    ')).to.be.equal( + '

    ' + ); }); it('should add "i-amphtml-binding" for data-amp-bind-*', () => { - expect(sanitizeHtml('

    ')).to.be - .equal('

    '); + expect(sanitizeHtml('

    ')).to.be.equal( + '

    ' + ); }); it('should NOT rewrite values of binding attributes', () => { // Should not change "foo.bar". Adding `target` attribute is not necessary // (but harmless) since will use rewriteAttributesForElement(). expect(sanitizeHtml('link')).to.equal( - 'link'); + 'link' + ); }); }); }); @@ -102,15 +107,20 @@ function runSanitizerTests() { it('should output valid markup', () => { expect(sanitizeHtml('

    abc

    ')).to.be.equal('

    abc

    '); expect(sanitizeHtml('

    abc

    ')).to.be.equal( - '

    abc

    '); + '

    abc

    ' + ); expect(sanitizeHtml('

    ab
    c

    ')).to.be.equal( - '

    ab
    c

    '); - expect(sanitizeHtml( + '

    ab
    c

    ' + ); + expect( + sanitizeHtml( '

    abc' + - '

    ')) - .to.be.equal( - '

    abc' + - '

    '); + '' + ) + ).to.be.equal( + '

    abc' + + '

    ' + ); }); it('should NOT output security-sensitive markup', () => { @@ -127,12 +137,13 @@ function runSanitizerTests() { }); it('should NOT output security-sensitive markup when nested', () => { - expect(sanitizeHtml('ac')) - .to.be.equal('ac'); - expect(sanitizeHtml('ac')) - .to.be.equal('ac'); - expect(sanitizeHtml('ac')) - .to.be.equal('ac'); + expect(sanitizeHtml('ac')).to.be.equal( + 'ac' + ); + expect(sanitizeHtml('ac')).to.be.equal( + 'ac' + ); + expect(sanitizeHtml('ac')).to.be.equal('ac'); }); it('should NOT output security-sensitive markup when broken', () => { @@ -142,36 +153,40 @@ function runSanitizerTests() { it('should output "on" attribute', () => { expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); it('should output "data-, aria-, and role" attributes', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitizeHtml('b') + sanitizeHtml('b') ); const expected = serialize( - 'b'); + 'b' + ); expectEqualNodeLists(actual, expected); }); it('should output "href" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitizeHtml('ab') + sanitizeHtml('ab') ); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); it('should output "rel" attribute', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitizeHtml('ab') + sanitizeHtml('ab') ); const expected = serialize( - 'ab'); + 'ab' + ); expectEqualNodeLists(actual, expected); }); @@ -198,145 +213,178 @@ function runSanitizerTests() { it('should default target to _top with href', () => { // Can't use string equality since DOMPurify will reorder attributes. const actual = serialize( - sanitizeHtml('ac') + sanitizeHtml('ac') ); const expected = serialize( - 'ac'); + 'ac' + ); expectEqualNodeLists(actual, expected); }); it('should NOT default target to _top w/o href', () => { - expect(sanitizeHtml( - 'b' - + 'd' - )).to.equal( - 'b' - + 'd'); + expect(sanitizeHtml('b' + 'd')).to.equal( + 'b' + 'd' + ); }); it('should output a valid target', () => { - expect(sanitizeHtml('ab')) - .to.equal('ab'); + expect( + sanitizeHtml('ab') + ).to.equal('ab'); }); it('should output a valid target in different case', () => { - expect(sanitizeHtml('ab')) - .to.equal('ab'); + expect( + sanitizeHtml('ab') + ).to.equal('ab'); }); it('should override a unallowed target', () => { - expect(sanitizeHtml( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other' - )).to.equal( - '_self' - + '_parent' - + '_other' - + '_OTHER' - + 'other'); + expect( + sanitizeHtml( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ) + ).to.equal( + '_self' + + '_parent' + + '_other' + + '_OTHER' + + 'other' + ); }); it('should NOT output security-sensitive attributes', () => { allowConsoleError(() => { expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); expect(sanitizeHtml('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); }); it('should NOT output blacklisted values for class attributes', () => { allowConsoleError(() => { - expect(sanitizeHtml('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(sanitizeHtml('

    hello

    ')).to.be - .equal('

    hello

    '); - expect(sanitizeHtml('

    hello

    ')).to.be - .equal('

    hello

    '); + expect(sanitizeHtml('

    hello

    ')).to.be.equal( + '

    hello

    ' + ); + expect( + sanitizeHtml('

    hello

    ') + ).to.be.equal('

    hello

    '); + expect( + sanitizeHtml('

    hello

    ') + ).to.be.equal('

    hello

    '); }); }); it('should allow amp-subscriptions attributes', () => { - expect(sanitizeHtml('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitizeHtml('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitizeHtml('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitizeHtml('
    link
    ')) - .to.equal('
    link
    '); - expect(sanitizeHtml('
    link
    ')) - .to.equal('
    link
    '); + expect( + sanitizeHtml('
    link
    ') + ).to.equal('
    link
    '); + expect( + sanitizeHtml('
    link
    ') + ).to.equal('
    link
    '); + expect(sanitizeHtml('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(sanitizeHtml('
    link
    ')).to.equal( + '
    link
    ' + ); + expect(sanitizeHtml('
    link
    ')).to.equal( + '
    link
    ' + ); }); it('should allow source::src with valid protocol', () => { - expect(sanitizeHtml('')) - .to.equal(''); + expect(sanitizeHtml('')).to.equal( + '' + ); }); // TODO(choumx): HTTPS-only URI attributes are not enforced consistently // in the sanitizer yet. E.g. amp-video requires HTTPS, amp-img does not. // Unskip when this is fixed. it.skip('should not allow source::src with invalid protocol', () => { - expect(sanitizeHtml('')) - .to.equal(''); - expect(sanitizeHtml('')) - .to.equal(''); + expect(sanitizeHtml('')).to.equal( + '' + ); + expect(sanitizeHtml('')).to.equal( + '' + ); }); it('should allow div::template', () => { - expect(sanitizeHtml('
    ')) - .to.equal('
    '); + expect(sanitizeHtml('
    ')).to.equal( + '
    ' + ); }); it('should allow form::action-xhr', () => { - expect(sanitizeHtml('
    ')) - .to.equal('
    '); + expect( + sanitizeHtml('
    ') + ).to.equal('
    '); }); it('should allow -related attributes', () => { - expect(sanitizeHtml('
    ')) - .to.equal('
    '); - expect(sanitizeHtml('
    ')) - .to.equal('
    '); - expect(sanitizeHtml('
    ')) - .to.equal('
    '); - expect(sanitizeHtml('
    ')) - .to.equal('
    '); - expect(sanitizeHtml('')) - .to.equal(''); - expect(sanitizeHtml('')) - .to.equal(''); + expect(sanitizeHtml('
    ')).to.equal( + '
    ' + ); + expect(sanitizeHtml('
    ')).to.equal( + '
    ' + ); + expect(sanitizeHtml('
    ')).to.equal( + '
    ' + ); + expect(sanitizeHtml('
    ')).to.equal( + '
    ' + ); + expect( + sanitizeHtml('') + ).to.equal(''); + expect(sanitizeHtml('')).to.equal( + '' + ); }); it('should allow attributes', () => { - expect(sanitizeHtml('')) - .to.equal(''); + expect(sanitizeHtml('')).to.equal( + '' + ); }); it('should output "i-amphtml-key" attribute if diffing is enabled', () => { // Elements with bindings should have i-amphtml-key="". expect(sanitizeHtml('

    ', true)).to.match( - /

    <\/p>/); + /

    <\/p>/ + ); // AMP elements should have i-amphtml-key="". expect(sanitizeHtml('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // AMP elements with bindings should have i-amphtml-key="". expect(sanitizeHtml('', true)).to.match( - /<\/amp-img>/); + /<\/amp-img>/ + ); // Other elements should NOT have i-amphtml-key-set. expect(sanitizeHtml('

    ')).to.equal('

    '); }); @@ -348,60 +396,71 @@ function runSanitizerTests() { }); it('should output valid markup', () => { - expect(sanitizeTagsForTripleMustache('abc')) - .to.be.equal('abc'); + expect(sanitizeTagsForTripleMustache('abc')).to.be.equal( + 'abc' + ); expect(sanitizeTagsForTripleMustache('ab
    c
    ')).to.be.equal( - 'ab
    c
    '); + 'ab
    c
    ' + ); expect(sanitizeTagsForTripleMustache('abc')).to.be.equal( - 'abc'); + 'abc' + ); const markupWithClassAttribute = '

    heading

    '; - expect(sanitizeTagsForTripleMustache(markupWithClassAttribute)) - .to.be.equal(markupWithClassAttribute); + expect( + sanitizeTagsForTripleMustache(markupWithClassAttribute) + ).to.be.equal(markupWithClassAttribute); const markupWithClassesAttribute = - '
    heading
    '; - expect(sanitizeTagsForTripleMustache(markupWithClassesAttribute)) - .to.be.equal(markupWithClassesAttribute); + '
    heading
    '; + expect( + sanitizeTagsForTripleMustache(markupWithClassesAttribute) + ).to.be.equal(markupWithClassesAttribute); const markupParagraph = '

    paragraph

    '; - expect(sanitizeTagsForTripleMustache(markupParagraph)) - .to.be.equal(markupParagraph); + expect(sanitizeTagsForTripleMustache(markupParagraph)).to.be.equal( + markupParagraph + ); }); it('should NOT output non-whitelisted markup', () => { - expect(sanitizeTagsForTripleMustache('ac')) - .to.be.equal('ac'); - expect(sanitizeTagsForTripleMustache('ac')) - .to.be.equal('ac'); + expect(sanitizeTagsForTripleMustache('ac')).to.be.equal( + 'ac' + ); + expect(sanitizeTagsForTripleMustache('ac')).to.be.equal('ac'); }); it('should compensate for broken markup', () => { expect(sanitizeTagsForTripleMustache('ab')).to.be.equal( - 'ab'); + 'ab' + ); }); describe('should sanitize `style` attribute', () => { - it('should allow valid styles',() => { - expect(sanitizeHtml('
    Test
    ')) - .to.equal('
    Test
    '); + it('should allow valid styles', () => { + expect(sanitizeHtml('
    Test
    ')).to.equal( + '
    Test
    ' + ); }); - it('should ignore styles containing `!important`',() => { + it('should ignore styles containing `!important`', () => { allowConsoleError(() => { - expect(sanitizeHtml('
    Test
    ')) - .to.equal('
    Test
    '); + expect( + sanitizeHtml('
    Test
    ') + ).to.equal('
    Test
    '); }); }); it('should ignore styles containing `position:fixed`', () => { allowConsoleError(() => { - expect(sanitizeHtml('
    Test
    ')) - .to.equal('
    Test
    '); + expect( + sanitizeHtml('
    Test
    ') + ).to.equal('
    Test
    '); }); }); it('should ignore styles containing `position:sticky`', () => { allowConsoleError(() => { - expect(sanitizeHtml('
    Test
    ')) - .to.equal('
    Test
    '); + expect( + sanitizeHtml('
    Test
    ') + ).to.equal('
    Test
    '); }); }); }); diff --git a/test/unit/test-service.js b/test/unit/test-service.js index 756aae5bdf555..db1ef5f2fdcde 100644 --- a/test/unit/test-service.js +++ b/test/unit/test-service.js @@ -37,9 +37,7 @@ import { } from '../../src/service'; import {loadPromise} from '../../src/event-helper'; - describe('service', () => { - let sandbox; beforeEach(() => { @@ -51,7 +49,6 @@ describe('service', () => { }); describe('disposable interface', () => { - let disposable; let nonDisposable; @@ -69,13 +66,13 @@ describe('service', () => { expect(assertDisposable(disposable)).to.equal(disposable); allowConsoleError(() => { expect(() => assertDisposable(nonDisposable)).to.throw( - /required to implement Disposable/); + /required to implement Disposable/ + ); }); }); }); describe('window singletons', () => { - let Class; let count; let factory; @@ -139,15 +136,19 @@ describe('service', () => { }); it('should throw before creation if factory is not provided', () => { - allowConsoleError(() => { expect(() => { - getService(window, 'c'); - }).to.throw(); }); + allowConsoleError(() => { + expect(() => { + getService(window, 'c'); + }).to.throw(); + }); }); it('should fail without factory on initial setup', () => { - allowConsoleError(() => { expect(() => { - getService(window, 'not-present'); - }).to.throw(/Expected service not-present to be registered/); }); + allowConsoleError(() => { + expect(() => { + getService(window, 'not-present'); + }).to.throw(/Expected service not-present to be registered/); + }); }); it('should provide a promise that resolves when instantiated', () => { @@ -226,7 +227,6 @@ describe('service', () => { }); describe('ampdoc singletons', () => { - let windowApi; let ampdoc; let ampdocMock; @@ -264,7 +264,10 @@ describe('service', () => { }); it('should make per ampdoc singletons and store them in window', () => { - ampdocMock.expects('isSingleDoc').returns(true).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(true) + .atLeast(1); registerServiceBuilderForDoc(node, 'a', factory); const a1 = getServiceForDoc(node, 'a'); registerServiceBuilderForDoc(node, 'a', factory); @@ -288,7 +291,10 @@ describe('service', () => { }); it('should make per ampdoc singletons via ampdoc', () => { - ampdocMock.expects('isSingleDoc').returns(true).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(true) + .atLeast(1); registerServiceBuilderForDoc(ampdoc, 'a', factory); const a1 = getServiceForDoc(ampdoc, 'a'); registerServiceBuilderForDoc(ampdoc, 'a', factory); @@ -302,7 +308,10 @@ describe('service', () => { }); it('should make per ampdoc singletons and store them in ampdoc', () => { - ampdocMock.expects('isSingleDoc').returns(false).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(false) + .atLeast(1); registerServiceBuilderForDoc(node, 'a', factory); const a1 = getServiceForDoc(node, 'a'); registerServiceBuilderForDoc(node, 'a', factory); @@ -345,9 +354,11 @@ describe('service', () => { }); it('should fail without factory on initial setup', () => { - allowConsoleError(() => { expect(() => { - getServiceForDoc(node, 'not-present'); - }).to.throw(/Expected service not-present to be registered/); }); + allowConsoleError(() => { + expect(() => { + getServiceForDoc(node, 'not-present'); + }).to.throw(/Expected service not-present to be registered/); + }); }); it('should provide a promise that resolves when instantiated', () => { @@ -405,21 +416,28 @@ describe('service', () => { }); it('should resolve service for a child window', () => { - ampdocMock.expects('isSingleDoc').returns(true).atLeast(1); + ampdocMock + .expects('isSingleDoc') + .returns(true) + .atLeast(1); registerServiceBuilderForDoc(node, 'c', factory); const c = getServiceForDoc(node, 'c'); // A child. const childWin = {}; - const childWinNode = - {nodeType: 1, ownerDocument: {defaultView: childWin}}; + const childWinNode = { + nodeType: 1, + ownerDocument: {defaultView: childWin}, + }; setParentWindow(childWin, windowApi); expect(getServiceForDoc(childWinNode, 'c')).to.equal(c); // A grandchild. const grandchildWin = {}; - const grandChildWinNode = - {nodeType: 1, ownerDocument: {defaultView: grandchildWin}}; + const grandChildWinNode = { + nodeType: 1, + ownerDocument: {defaultView: grandchildWin}, + }; setParentWindow(grandchildWin, childWin); expect(getServiceForDoc(grandChildWinNode, 'c')).to.equal(c); }); @@ -481,14 +499,15 @@ describe('service', () => { beforeEach(() => { // A child. childWin = {}; - childWinNode = - {nodeType: 1, ownerDocument: {defaultView: childWin}}; + childWinNode = {nodeType: 1, ownerDocument: {defaultView: childWin}}; setParentWindow(childWin, windowApi); // A grandchild. grandchildWin = {}; - grandChildWinNode = - {nodeType: 1, ownerDocument: {defaultView: grandchildWin}}; + grandChildWinNode = { + nodeType: 1, + ownerDocument: {defaultView: grandchildWin}, + }; setParentWindow(grandchildWin, childWin); registerServiceBuilderForDoc(ampdoc, 'c', factory); @@ -501,12 +520,16 @@ describe('service', () => { }); it('should not fallback when opt_fallbackToTopWin is false', () => { - const fromChildNode = - getExistingServiceForDocInEmbedScope(childWinNode, 'c'); + const fromChildNode = getExistingServiceForDocInEmbedScope( + childWinNode, + 'c' + ); expect(fromChildNode).to.be.null; - const fromGrandchildNode = - getExistingServiceForDocInEmbedScope(grandChildWinNode, 'c'); + const fromGrandchildNode = getExistingServiceForDocInEmbedScope( + grandChildWinNode, + 'c' + ); expect(fromGrandchildNode).to.be.null; }); @@ -514,37 +537,55 @@ describe('service', () => { const fallbackToTopWin = true; const fromNode = getExistingServiceForDocInEmbedScope( - node, 'c', fallbackToTopWin); + node, + 'c', + fallbackToTopWin + ); expect(fromNode).to.equal(topService); const fromChildNode = getExistingServiceForDocInEmbedScope( - childWinNode, 'c', fallbackToTopWin); + childWinNode, + 'c', + fallbackToTopWin + ); expect(fromChildNode).to.equal(topService); const fromGrandchildNode = getExistingServiceForDocInEmbedScope( - grandChildWinNode, 'c', fallbackToTopWin); + grandChildWinNode, + 'c', + fallbackToTopWin + ); expect(fromGrandchildNode).to.equal(topService); }); it('should return overriden service', () => { const overridenService = {}; installServiceInEmbedScope(childWin, 'c', overridenService); - expect(getExistingServiceForDocInEmbedScope(childWinNode, 'c')) - .to.equal(overridenService); + expect( + getExistingServiceForDocInEmbedScope(childWinNode, 'c') + ).to.equal(overridenService); // Top-level service doesn't change. - expect(getExistingServiceForDocInEmbedScope( - node, 'c', /* opt_fallbackToTopWin */ true)) - .to.equal(topService); + expect( + getExistingServiceForDocInEmbedScope( + node, + 'c', + /* opt_fallbackToTopWin */ true + ) + ).to.equal(topService); // Notice that only direct overrides are allowed for now. This is // arbitrary can change in the future to allow hierarchical lookup // up the window chain. - expect(getExistingServiceForDocInEmbedScope(grandChildWinNode, 'c')) - .to.be.null; - expect(getExistingServiceForDocInEmbedScope( - grandChildWinNode, 'c', /* opt_fallbackToTopWin */ true)) - .to.equal(topService); + expect(getExistingServiceForDocInEmbedScope(grandChildWinNode, 'c')).to + .be.null; + expect( + getExistingServiceForDocInEmbedScope( + grandChildWinNode, + 'c', + /* opt_fallbackToTopWin */ true + ) + ).to.equal(topService); }); }); @@ -586,7 +627,6 @@ describe('service', () => { }); }); - describe('getParentWindowFrameElement', () => { let iframe; @@ -631,7 +671,9 @@ describe('service', () => { const childWin = {}; Object.defineProperties(childWin, { frameElement: { - get: () => {throw new Error('intentional');}, + get: () => { + throw new Error('intentional'); + }, }, }); setParentWindow(childWin, window); diff --git a/test/unit/test-shadow-embed.js b/test/unit/test-shadow-embed.js index 62419b37116ed..9ea629b894586 100644 --- a/test/unit/test-shadow-embed.js +++ b/test/unit/test-shadow-embed.js @@ -39,35 +39,43 @@ describes.sandboxed('shadow-embed', {}, () => { setShadowDomSupportedVersionForTesting(undefined); }); - [ShadowDomVersion.NONE, ShadowDomVersion.V0, ShadowDomVersion.V1] - .forEach(scenario => { - describe('shadow APIs', () => { - let hostElement; - - beforeEach(function() { - hostElement = document.createElement('div'); - setShadowDomSupportedVersionForTesting(scenario); - setShadowCssSupportedForTesting(undefined); - }); - - describe(scenario, function() { - before(function() { - if (scenario == ShadowDomVersion.V0 && - !Element.prototype.createShadowRoot) { - this.skipTest(); - } + [ShadowDomVersion.NONE, ShadowDomVersion.V0, ShadowDomVersion.V1].forEach( + scenario => { + describe('shadow APIs', () => { + let hostElement; + + beforeEach(function() { + hostElement = document.createElement('div'); + setShadowDomSupportedVersionForTesting(scenario); + setShadowCssSupportedForTesting(undefined); + }); - if (scenario == ShadowDomVersion.V1 && - !Element.prototype.attachShadow) { - this.skipTest(); - } - }); + describe(scenario, function() { + before(function() { + if ( + scenario == ShadowDomVersion.V0 && + !Element.prototype.createShadowRoot + ) { + this.skipTest(); + } + + if ( + scenario == ShadowDomVersion.V1 && + !Element.prototype.attachShadow + ) { + this.skipTest(); + } + }); - it('should transform CSS installStylesForDoc ' + - 'for shadow root', () => { + it( + 'should transform CSS installStylesForDoc ' + 'for shadow root', + () => { const shadowRoot = createShadowRoot(hostElement); const ampdoc = new AmpDocShadow( - window, 'https://a.org/', shadowRoot); + window, + 'https://a.org/', + shadowRoot + ); const style = installStylesForDoc(ampdoc, 'body {}', null, true); expect(shadowRoot.contains(style)).to.be.true; const css = style.textContent.replace(/\s/g, ''); @@ -76,170 +84,177 @@ describes.sandboxed('shadow-embed', {}, () => { } else { expect(css).to.equal('body{}'); } + } + ); + + describe('createShadowRoot', () => { + it('should clear duplicate root', () => { + const shadowRoot1 = createShadowRoot(hostElement); + const span = document.createElement('span'); + shadowRoot1.appendChild(span); + expect(shadowRoot1.contains(span)).to.be.true; + + const shadowRoot2 = createShadowRoot(hostElement); + expect(shadowRoot2).to.equal(shadowRoot1); + expect(shadowRoot2.contains(span)).to.be.false; }); - describe('createShadowRoot', () => { - it('should clear duplicate root', () => { - const shadowRoot1 = createShadowRoot(hostElement); - const span = document.createElement('span'); - shadowRoot1.appendChild(span); - expect(shadowRoot1.contains(span)).to.be.true; + it('should have host', () => { + const shadowRoot = createShadowRoot(hostElement); + expect(shadowRoot.host).to.equal(hostElement); + }); - const shadowRoot2 = createShadowRoot(hostElement); - expect(shadowRoot2).to.equal(shadowRoot1); - expect(shadowRoot2.contains(span)).to.be.false; - }); + it('should have getElementById', () => { + const shadowRoot = createShadowRoot(hostElement); + expect(shadowRoot.getElementById).to.be.ok; - it('should have host', () => { + const spanId = 'test' + Math.floor(Math.random() * 10000); + const span = document.createElement('span'); + span.id = spanId; + shadowRoot.appendChild(span); + expect(shadowRoot.getElementById(spanId)).to.equal(span); + }); + + if (scenario == ShadowDomVersion.NONE) { + it('should add id for polyfill', () => { const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.host).to.equal(hostElement); + expect(shadowRoot.tagName).to.equal('I-AMPHTML-SHADOW-ROOT'); + expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); }); - it('should have getElementById', () => { + it('should add host style for polyfill', () => { + const doc = hostElement.ownerDocument; + doc.body.appendChild(hostElement); + const slot = doc.createElement('div'); + hostElement.appendChild(slot); + expect(slot).to.have.display('block'); const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.getElementById).to.be.ok; - - const spanId = 'test' + Math.floor(Math.random() * 10000); - const span = document.createElement('span'); - span.id = spanId; - shadowRoot.appendChild(span); - expect(shadowRoot.getElementById(spanId)).to.equal(span); + expect(hostElement).to.have.class( + 'i-amphtml-shadow-host-polyfill' + ); + expect(slot).to.have.display('none'); + expect(shadowRoot).to.not.have.display('none'); + doc.body.removeChild(hostElement); }); + } - if (scenario == ShadowDomVersion.NONE) { - it('should add id for polyfill', () => { - const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.tagName).to.equal('I-AMPHTML-SHADOW-ROOT'); - expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); - }); - - it('should add host style for polyfill', () => { - const doc = hostElement.ownerDocument; - doc.body.appendChild(hostElement); - const slot = doc.createElement('div'); - hostElement.appendChild(slot); - expect(slot).to.have.display('block'); - const shadowRoot = createShadowRoot(hostElement); - expect(hostElement).to.have.class( - 'i-amphtml-shadow-host-polyfill'); - expect(slot).to.have.display('none'); - expect(shadowRoot).to.not.have.display('none'); - doc.body.removeChild(hostElement); - }); - } - - // Test scenarios where Shadow Css is not supported - it('Should add an id and class for CSS \ + // Test scenarios where Shadow Css is not supported + it('Should add an id and class for CSS \ encapsulation to the shadow root', () => { - setShadowCssSupportedForTesting(false); - const shadowRoot = createShadowRoot(hostElement); - expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); - // Browserify does not support arrow functions with params. - // Using Old School for - const shadowRootClassListArray = - toArray(shadowRoot.host.classList); - let foundShadowCssClass = false; - for (let i = 0; i < shadowRootClassListArray.length; i++) { - if (shadowRootClassListArray[i].match(/i-amphtml-sd-\d+/)) { - foundShadowCssClass = true; - break; - } + setShadowCssSupportedForTesting(false); + const shadowRoot = createShadowRoot(hostElement); + expect(shadowRoot.id).to.match(/i-amphtml-sd-\d+/); + // Browserify does not support arrow functions with params. + // Using Old School for + const shadowRootClassListArray = toArray( + shadowRoot.host.classList + ); + let foundShadowCssClass = false; + for (let i = 0; i < shadowRootClassListArray.length; i++) { + if (shadowRootClassListArray[i].match(/i-amphtml-sd-\d+/)) { + foundShadowCssClass = true; + break; } - expect(foundShadowCssClass).to.be.ok; - }); + } + expect(foundShadowCssClass).to.be.ok; + }); - it('Should transform CSS for the shadow root', () => { - setShadowCssSupportedForTesting(false); - const shadowRoot = createShadowRoot(hostElement); - const ampdoc = new AmpDocShadow( - window, 'https://a.org/', shadowRoot); - const style = - installStylesForDoc(ampdoc, 'body {}', null, true); - expect(shadowRoot.contains(style)).to.be.true; - const css = style.textContent.replace(/\s/g, ''); - expect(css).to.match(/amp-body/); - }); + it('Should transform CSS for the shadow root', () => { + setShadowCssSupportedForTesting(false); + const shadowRoot = createShadowRoot(hostElement); + const ampdoc = new AmpDocShadow( + window, + 'https://a.org/', + shadowRoot + ); + const style = installStylesForDoc(ampdoc, 'body {}', null, true); + expect(shadowRoot.contains(style)).to.be.true; + const css = style.textContent.replace(/\s/g, ''); + expect(css).to.match(/amp-body/); }); + }); - describe('stylesheets', () => { - let parentStylesheet; + describe('stylesheets', () => { + let parentStylesheet; - beforeEach(() => { - parentStylesheet = document.createElement('style'); - parentStylesheet.textContent = '.x {background: red}'; - document.body.appendChild(parentStylesheet); - document.body.appendChild(hostElement); - }); + beforeEach(() => { + parentStylesheet = document.createElement('style'); + parentStylesheet.textContent = '.x {background: red}'; + document.body.appendChild(parentStylesheet); + document.body.appendChild(hostElement); + }); - afterEach(() => { - document.body.removeChild(parentStylesheet); - document.body.removeChild(hostElement); - }); + afterEach(() => { + document.body.removeChild(parentStylesheet); + document.body.removeChild(hostElement); + }); - it('should have shadow stylesheets and not global', () => { - const shadowRoot = createShadowRoot(hostElement); - const shadowStyle = document.createElement('style'); - shadowStyle.textContent = '.x {background: green}'; - shadowRoot.appendChild(shadowStyle); - - const {styleSheets} = shadowRoot; - expect(styleSheets).to.exist; - expect(styleSheets).to.have.length(1); - expect(styleSheets[0].ownerNode).to.equal(shadowStyle); - }); + it('should have shadow stylesheets and not global', () => { + const shadowRoot = createShadowRoot(hostElement); + const shadowStyle = document.createElement('style'); + shadowStyle.textContent = '.x {background: green}'; + shadowRoot.appendChild(shadowStyle); + + const {styleSheets} = shadowRoot; + expect(styleSheets).to.exist; + expect(styleSheets).to.have.length(1); + expect(styleSheets[0].ownerNode).to.equal(shadowStyle); }); + }); - // TODO(aghassemi, #12499): Make this work with latest mocha / karma - describe.skip('importShadowBody', () => { - let shadowRoot, source, child1, child2; - - beforeEach(() => { - shadowRoot = createShadowRoot(hostElement); - source = document.createElement('body'); - child1 = document.createElement('div'); - child1.id = 'child1'; - child2 = document.createElement('div'); - child2.id = 'child2'; - source.appendChild(child1); - source.appendChild(child2); - }); + // TODO(aghassemi, #12499): Make this work with latest mocha / karma + describe.skip('importShadowBody', () => { + let shadowRoot, source, child1, child2; + + beforeEach(() => { + shadowRoot = createShadowRoot(hostElement); + source = document.createElement('body'); + child1 = document.createElement('div'); + child1.id = 'child1'; + child2 = document.createElement('div'); + child2.id = 'child2'; + source.appendChild(child1); + source.appendChild(child2); + }); - it('should import body with all children', () => { - expect(shadowRoot.body).to.be.undefined; - const body = importShadowBody(shadowRoot, source, true); - expect(shadowRoot.body).to.equal(body); - expect(body.tagName).to.equal( - scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY'); - expect(body.style.position).to.equal('relative'); - if (scenario == ShadowDomVersion.NONE) { - expect(body.style.display).to.equal('block'); - } - expect(shadowRoot.contains(body)).to.be.true; - expect(body.children).to.have.length(2); - expect(body.children[0].id).to.equal('child1'); - expect(body.children[1].id).to.equal('child2'); - }); + it('should import body with all children', () => { + expect(shadowRoot.body).to.be.undefined; + const body = importShadowBody(shadowRoot, source, true); + expect(shadowRoot.body).to.equal(body); + expect(body.tagName).to.equal( + scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY' + ); + expect(body.style.position).to.equal('relative'); + if (scenario == ShadowDomVersion.NONE) { + expect(body.style.display).to.equal('block'); + } + expect(shadowRoot.contains(body)).to.be.true; + expect(body.children).to.have.length(2); + expect(body.children[0].id).to.equal('child1'); + expect(body.children[1].id).to.equal('child2'); + }); - it('should import shallow body', () => { - expect(shadowRoot.body).to.be.undefined; - const body = importShadowBody(shadowRoot, source, false); - expect(shadowRoot.body).to.equal(body); - expect(body.tagName).to.equal( - scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY'); - expect(body.style.position).to.equal('relative'); - if (scenario == ShadowDomVersion.NONE) { - expect(body.style.display).to.equal('block'); - } - expect(shadowRoot.contains(body)).to.be.true; - expect(body.children).to.have.length(0); - }); + it('should import shallow body', () => { + expect(shadowRoot.body).to.be.undefined; + const body = importShadowBody(shadowRoot, source, false); + expect(shadowRoot.body).to.equal(body); + expect(body.tagName).to.equal( + scenario == ShadowDomVersion.NONE ? 'AMP-BODY' : 'BODY' + ); + expect(body.style.position).to.equal('relative'); + if (scenario == ShadowDomVersion.NONE) { + expect(body.style.display).to.equal('block'); + } + expect(shadowRoot.contains(body)).to.be.true; + expect(body.children).to.have.length(0); }); }); }); }); + } + ); describe('isShadowRoot', () => { - it('should yield false for non-nodes', () => { expect(isShadowRoot(null)).to.be.false; expect(isShadowRoot(undefined)).to.be.false; @@ -274,8 +289,8 @@ describes.sandboxed('shadow-embed', {}, () => { }); it('should yield true for polyfill', () => { - expect(isShadowRoot(document.createElement( - 'i-amphtml-shadow-root'))).to.be.true; + expect(isShadowRoot(document.createElement('i-amphtml-shadow-root'))).to + .be.true; }); }); @@ -318,8 +333,7 @@ describes.sandboxed('shadow-embed', {}, () => { it('should replace root selectors', () => { expect(scope('html {}')).to.equal('.h amp-html {}'); expect(scope('body {}')).to.equal('.h amp-body {}'); - expect(scope('html {} body {}')).to.equal( - '.h amp-html {}.h amp-body {}'); + expect(scope('html {} body {}')).to.equal('.h amp-html {}.h amp-body {}'); expect(scope('html, body {}')).to.equal('.h amp-html, .h amp-body {}'); expect(scope('body.x {}')).to.equal('.h amp-body.x {}'); expect(scope('body::after {}')).to.equal('.h amp-body::after {}'); @@ -369,26 +383,24 @@ describes.sandboxed('shadow-embed', {}, () => { }); it('should resolve to streamer', () => { - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterStreamer); + expect(createShadowDomWriter(win)).to.be.instanceOf( + ShadowDomWriterStreamer + ); expect(createHTMLDocumentSpy).to.be.calledOnce; expect(createHTMLDocumentSpy).to.be.calledWith(''); }); it('should resolve to bulk without API', () => { delete win.document.implementation.createHTMLDocument; - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterBulk); + expect(createShadowDomWriter(win)).to.be.instanceOf(ShadowDomWriterBulk); delete win.document.implementation; - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterBulk); + expect(createShadowDomWriter(win)).to.be.instanceOf(ShadowDomWriterBulk); expect(createHTMLDocumentSpy).to.not.be.called; }); it('should resolve to bulk on firefox', () => { isFirefox = true; - expect(createShadowDomWriter(win)) - .to.be.instanceOf(ShadowDomWriterBulk); + expect(createShadowDomWriter(win)).to.be.instanceOf(ShadowDomWriterBulk); expect(createHTMLDocumentSpy).to.not.be.called; }); }); @@ -485,8 +497,9 @@ describes.sandboxed('shadow-embed', {}, () => { }); it('should not parse noscript as markup', () => { - writer.write(''); + writer.write( + '' + ); return waitForNextBodyChunk().then(() => { expect(win.document.body.querySelector('child1')).to.exist; expect(win.document.body.querySelector('child2')).not.to.exist; diff --git a/test/unit/test-size-list.js b/test/unit/test-size-list.js index 7c6ea36eecf5d..de0eaf2bb3c10 100644 --- a/test/unit/test-size-list.js +++ b/test/unit/test-size-list.js @@ -16,9 +16,7 @@ import {SizeList, parseSizeList} from '../../src/size-list'; - describe('SizeList parseSizeList', () => { - it('should accept single option', () => { const res = parseSizeList(' \n 111px \n '); expect(res.sizes_.length).to.equal(1); @@ -48,12 +46,14 @@ describe('SizeList parseSizeList', () => { it('should accept complicated media conditions', () => { const res = parseSizeList( - ' \n screen and (min-width: 1000px) \t ' + + ' \n screen and (min-width: 1000px) \t ' + ' and (max-width: 2000px) 222px \n,' + - ' 111px \n'); + ' 111px \n' + ); expect(res.sizes_.length).to.equal(2); expect(res.sizes_[0].mediaQuery).to.equal( - 'screen and (min-width: 1000px) and (max-width: 2000px)'); + 'screen and (min-width: 1000px) and (max-width: 2000px)' + ); expect(res.sizes_[0].size).to.equal('222px'); expect(res.sizes_[1].mediaQuery).to.equal(undefined); expect(res.sizes_[1].size).to.equal('111px'); @@ -74,8 +74,9 @@ describe('SizeList parseSizeList', () => { }); it('should accept CSS functions', () => { - const res = parseSizeList('screen calc(111vw + 10px) \n' + - ', ca_1-C((50vw+20px) / 2) '); + const res = parseSizeList( + 'screen calc(111vw + 10px) \n' + ', ca_1-C((50vw+20px) / 2) ' + ); expect(res.sizes_.length).to.equal(2); expect(res.sizes_[0].mediaQuery).to.equal('screen'); expect(res.sizes_[0].size).to.equal('calc(111vw + 10px)'); @@ -84,8 +85,9 @@ describe('SizeList parseSizeList', () => { }); it('should tolerate right paren', () => { - const res = parseSizeList('(min-width:2000px)calc(11px)' + - ',(min-width:1000px)11px,12px'); + const res = parseSizeList( + '(min-width:2000px)calc(11px)' + ',(min-width:1000px)11px,12px' + ); expect(res.sizes_.length).to.equal(3); expect(res.sizes_[0].mediaQuery).to.equal('(min-width:2000px)'); expect(res.sizes_[0].size).to.equal('calc(11px)'); @@ -97,47 +99,59 @@ describe('SizeList parseSizeList', () => { it('should fail on invalid CSS functions', () => { // Spaces are not allowed between function name and `(`. - allowConsoleError(() => { expect(() => { - parseSizeList('screen calc (111vw + 10px) \n, 10px '); - }).to.throw(/Invalid CSS function/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList('screen calc (111vw + 10px) \n, 10px '); + }).to.throw(/Invalid CSS function/); + }); // Parens don't match. - allowConsoleError(() => { expect(() => { - parseSizeList('screen calc(111vw + 10px)) \n, 10px '); - }).to.throw(/Invalid CSS function/); }); - allowConsoleError(() => { expect(() => { - parseSizeList('screen calc((111vw + 10px) \n, 10px '); - }).to.throw(/Invalid CSS function/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList('screen calc(111vw + 10px)) \n, 10px '); + }).to.throw(/Invalid CSS function/); + }); + allowConsoleError(() => { + expect(() => { + parseSizeList('screen calc((111vw + 10px) \n, 10px '); + }).to.throw(/Invalid CSS function/); + }); }); it('should accept percent when allowed', () => { - const res = parseSizeList(' \n 111% \n ', - /* opt_allowPercentAsLength */ true); + const res = parseSizeList( + ' \n 111% \n ', + /* opt_allowPercentAsLength */ true + ); expect(res.sizes_.length).to.equal(1); expect(res.sizes_[0].mediaQuery).to.equal(undefined); expect(res.sizes_[0].size).to.equal('111%'); }); it('should not accept percent', () => { - allowConsoleError(() => { expect(() => { - parseSizeList(' \n 111% \n ', /* opt_allowPercentAsLength */ false); - }).to.throw(/Invalid length value/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList(' \n 111% \n ', /* opt_allowPercentAsLength */ false); + }).to.throw(/Invalid length value/); + }); }); it('should fail bad length', () => { - allowConsoleError(() => { expect(() => { - parseSizeList(' \n 111 \n '); - }).to.throw(/Invalid length value/); }); + allowConsoleError(() => { + expect(() => { + parseSizeList(' \n 111 \n '); + }).to.throw(/Invalid length value/); + }); }); }); - describe('SizeList construct', () => { - it('should have at least one option', () => { - allowConsoleError(() => { expect(() => { - new SizeList([]); - }).to.throw(/SizeList must have at least one option/); }); + allowConsoleError(() => { + expect(() => { + new SizeList([]); + }).to.throw(/SizeList must have at least one option/); + }); }); it('the last option must not have a query', () => { @@ -146,22 +160,25 @@ describe('SizeList construct', () => { new SizeList([{mediaQuery: 'screen', size: '111px'}]); }).to.throw(/The last option must not have a media condition/); expect(() => { - new SizeList([{mediaQuery: 'print', size: '222px'}, - {mediaQuery: 'screen', size: '111px'}]); + new SizeList([ + {mediaQuery: 'print', size: '222px'}, + {mediaQuery: 'screen', size: '111px'}, + ]); }).to.throw(/The last option must not have a media condition/); }); }); it('non-last options must have media query', () => { - allowConsoleError(() => { expect(() => { - new SizeList([{size: '222px'}, {size: '111px'}]); - }).to.throw( - /All options except for the last must have a media condition/); + allowConsoleError(() => { + expect(() => { + new SizeList([{size: '222px'}, {size: '111px'}]); + }).to.throw( + /All options except for the last must have a media condition/ + ); }); }); }); - describe('SizeList select', () => { it('should select default last option', () => { const sizeList = new SizeList([ @@ -170,11 +187,13 @@ describe('SizeList select', () => { {mediaQuery: 'media3', size: '222px'}, {size: '111px'}, ]); - expect(sizeList.select({ - matchMedia: () => { - return {}; - }, - })).to.equal('111px'); + expect( + sizeList.select({ + matchMedia: () => { + return {}; + }, + }) + ).to.equal('111px'); }); it('should select a matching option', () => { @@ -184,14 +203,16 @@ describe('SizeList select', () => { {mediaQuery: 'media3', size: '222px'}, {size: '111px'}, ]); - expect(sizeList.select({ - matchMedia: mq => { - if (mq == 'media2') { - return {matches: true}; - } - return {}; - }, - })).to.equal('333px'); + expect( + sizeList.select({ + matchMedia: mq => { + if (mq == 'media2') { + return {matches: true}; + } + return {}; + }, + }) + ).to.equal('333px'); }); it('should select first matching option', () => { @@ -201,13 +222,15 @@ describe('SizeList select', () => { {mediaQuery: 'media3', size: '222px'}, {size: '111px'}, ]); - expect(sizeList.select({ - matchMedia: mq => { - if (mq == 'media1' || mq == 'media2') { - return {matches: true}; - } - return {}; - }, - })).to.equal('444px'); + expect( + sizeList.select({ + matchMedia: mq => { + if (mq == 'media1' || mq == 'media2') { + return {matches: true}; + } + return {}; + }, + }) + ).to.equal('444px'); }); }); diff --git a/test/unit/test-srcset.js b/test/unit/test-srcset.js index 7194f4b96d2c3..51fac6db4f8df 100644 --- a/test/unit/test-srcset.js +++ b/test/unit/test-srcset.js @@ -21,10 +21,8 @@ import { srcsetFromSrc, } from '../../src/srcset'; - describe('Srcset', () => { describe('parseSrcset', () => { - function test(s, expected) { const res = parseSrcset(s); expect(res.sources_.length).to.equal(expected.length); @@ -38,18 +36,12 @@ describe('Srcset', () => { } it('should accept single source, default to 1px', () => { - test(' \n image \n ', [ - {url: 'image', dpr: 1}, - ]); + test(' \n image \n ', [{url: 'image', dpr: 1}]); }); it('should ignore empty source', () => { - test(' \n image \n, ', [ - {url: 'image', dpr: 1}, - ]); - test(' , \n image \n, ', [ - {url: 'image', dpr: 1}, - ]); + test(' \n image \n, ', [{url: 'image', dpr: 1}]); + test(' , \n image \n, ', [{url: 'image', dpr: 1}]); }); it('should accept multiple sources, default to 1x', () => { @@ -124,12 +116,8 @@ describe('Srcset', () => { {url: 'image,1x', dpr: 1}, {url: 'image,2x', dpr: 2}, ]); - test(' \n image,1 \n ', [ - {url: 'image,1', dpr: 1}, - ]); - test(' \n image,1x \n ', [ - {url: 'image,1x', dpr: 1}, - ]); + test(' \n image,1 \n ', [{url: 'image,1', dpr: 1}]); + test(' \n image,1x \n ', [{url: 'image,1x', dpr: 1}]); }); it('should accept no-whitestpace', () => { @@ -145,12 +133,8 @@ describe('Srcset', () => { {url: 'image,2', dpr: 1}, {url: 'image,1', dpr: 2}, ]); - test('image,2 2x', [ - {url: 'image,2', dpr: 2}, - ]); - test('image,1', [ - {url: 'image,1', dpr: 1}, - ]); + test('image,2 2x', [{url: 'image,2', dpr: 2}]); + test('image,1', [{url: 'image,1', dpr: 1}]); }); it('should accept other special chars in URLs', () => { @@ -169,35 +153,32 @@ describe('Srcset', () => { {url: 'image,2x', dpr: 1}, {url: 'image,1x', dpr: 2}, ]); - test(' \n image,1x \n ', [ - {url: 'image,1x', dpr: 1}, - ]); - test(' \n image,1w \n ', [ - {url: 'image,1w', dpr: 1}, - ]); + test(' \n image,1x \n ', [{url: 'image,1x', dpr: 1}]); + test(' \n image,1w \n ', [{url: 'image,1w', dpr: 1}]); }); it('should not accept mixed sources', () => { - allowConsoleError(() => { expect(() => { - parseSrcset(' \n image1 100w\n , \n image2 1.5x\n , image3 '); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + parseSrcset(' \n image1 100w\n , \n image2 1.5x\n , image3 '); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should parse misc examples', () => { - test('image-1x.png 1x, image-2x.png 2x, image-3x.png 3x, image-4x.png 4x', - [ - {url: 'image-1x.png', dpr: 1}, - {url: 'image-2x.png', dpr: 2}, - {url: 'image-3x.png', dpr: 3}, - {url: 'image-4x.png', dpr: 4}, - ]); - test('image,one.png', [ - {url: 'image,one.png', dpr: 1}, - ]); + test( + 'image-1x.png 1x, image-2x.png 2x, image-3x.png 3x, image-4x.png 4x', + [ + {url: 'image-1x.png', dpr: 1}, + {url: 'image-2x.png', dpr: 2}, + {url: 'image-3x.png', dpr: 3}, + {url: 'image-4x.png', dpr: 4}, + ] + ); + test('image,one.png', [{url: 'image,one.png', dpr: 1}]); }); }); - describe('srcsetFromElement', () => { function test(srcset, src, expected) { const element = document.createElement('div'); @@ -233,15 +214,11 @@ describe('Srcset', () => { }); it('should select src when only src available', () => { - test(undefined, 'image-0.png', [ - {url: 'image-0.png', dpr: 1}, - ]); + test(undefined, 'image-0.png', [{url: 'image-0.png', dpr: 1}]); }); it('should select src when only srcset is empty', () => { - test('', 'image-0.png', [ - {url: 'image-0.png', dpr: 1}, - ]); + test('', 'image-0.png', [{url: 'image-0.png', dpr: 1}]); }); it('should prefer srcset to src', () => { @@ -252,21 +229,20 @@ describe('Srcset', () => { }); it('should allow non-compliant src with space', () => { - test(undefined, 'image 0.png', [ - {url: 'image 0.png', dpr: 1}, - ]); + test(undefined, 'image 0.png', [{url: 'image 0.png', dpr: 1}]); }); it('should require srcset or src to be available', () => { - allowConsoleError(() => { expect(() => { - srcsetFromElement(document.createElement('div')); - }).to.throw( - /Either non-empty "srcset" or "src" attribute must be specified/); + allowConsoleError(() => { + expect(() => { + srcsetFromElement(document.createElement('div')); + }).to.throw( + /Either non-empty "srcset" or "src" attribute must be specified/ + ); }); }); }); - describe('srcsetFromSrc', () => { it('should construct with undefined width and 1 dpr', () => { const srcset = srcsetFromSrc('image-0.png'); @@ -279,49 +255,61 @@ describe('Srcset', () => { }); }); - describe('construct', () => { it('should enforce only one type of descriptor per source', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', width: 100, dpr: 2}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image-1000', width: 100, dpr: 2}]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should not allow 0-width descriptor', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', width: 0}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image-1000', width: 0}]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should not allow 0-dpr descriptor', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', dpr: 0}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image-1000', dpr: 0}]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should enforce only one type of descriptor total', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image-1000', width: 100}, - {url: 'image-2x', dpr: 2}]); - }).to.throw(/Srcset must have width or dpr sources, but not both/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([ + {url: 'image-1000', width: 100}, + {url: 'image-2x', dpr: 2}, + ]); + }).to.throw(/Srcset must have width or dpr sources, but not both/); + }); }); it('should not allow duplicate sources', () => { - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image', width: 100}, - {url: 'image', width: 100}]); - }).to.throw(/Duplicate width/); }); - allowConsoleError(() => { expect(() => { - new Srcset([{url: 'image', dpr: 2}, {url: 'image', dpr: 2}]); - }).to.throw(/Duplicate dpr/); }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image', width: 100}, {url: 'image', width: 100}]); + }).to.throw(/Duplicate width/); + }); + allowConsoleError(() => { + expect(() => { + new Srcset([{url: 'image', dpr: 2}, {url: 'image', dpr: 2}]); + }).to.throw(/Duplicate dpr/); + }); }); }); - describe('select', () => { it('select by width', () => { const srcset = parseSrcset( - 'image-1000 1000w, image-500 500w, image-250 250w, image 50w'); + 'image-1000 1000w, image-500 500w, image-250 250w, image 50w' + ); // DPR = 1 expect(srcset.select(2000, 1)).to.equal('image-1000'); @@ -359,7 +347,8 @@ describe('Srcset', () => { it('select by width with preference toward higher width', () => { const srcset = parseSrcset( - 'image-1000 1000w, image-500 500w, image-250 250w, image 50w'); + 'image-1000 1000w, image-500 500w, image-250 250w, image 50w' + ); // For DPR=1 and 2. // Bull's eye. diff --git a/test/unit/test-ssr-template-helper.js b/test/unit/test-ssr-template-helper.js index 77a472fa1e586..ae3246bec0ac5 100644 --- a/test/unit/test-ssr-template-helper.js +++ b/test/unit/test-ssr-template-helper.js @@ -17,153 +17,189 @@ import {Services} from '../../src/services'; import {SsrTemplateHelper} from '../../src/ssr-template-helper'; -describes.fakeWin('ssr-template-helper', { - amp: true, -}, env => { - let ampdoc; - let hasCapabilityStub; - let sandbox; - let ssrTemplateHelper; - const sourceComponent = 'amp-list'; - let maybeFindTemplateStub; - let templates; - let viewer; - let win; +describes.fakeWin( + 'ssr-template-helper', + { + amp: true, + }, + env => { + let ampdoc; + let hasCapabilityStub; + let sandbox; + let ssrTemplateHelper; + const sourceComponent = 'amp-list'; + let maybeFindTemplateStub; + let templates; + let viewer; + let win; - beforeEach(() => { - ampdoc = env.ampdoc; - sandbox = sinon.sandbox; - win = env.win; - templates = Services.templatesFor(win); - viewer = Services.viewerForDoc(ampdoc); - hasCapabilityStub = sandbox.stub(viewer, 'hasCapability'); - maybeFindTemplateStub = sandbox.stub(templates, 'maybeFindTemplate'); - ssrTemplateHelper = - new SsrTemplateHelper(sourceComponent, viewer, templates); - }); - - afterEach(() => { - win.document.documentElement.removeAttribute( - 'allow-viewer-render-template'); - sandbox.restore(); - }); - - describe('isSupported', () => { - it('should return true if doc level opt-in', () => { - win.document.documentElement.setAttribute( - 'allow-viewer-render-template', true); - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); - expect(ssrTemplateHelper.isSupported()).to.be.true; + beforeEach(() => { + ampdoc = env.ampdoc; + sandbox = sinon.sandbox; + win = env.win; + templates = Services.templatesFor(win); + viewer = Services.viewerForDoc(ampdoc); + hasCapabilityStub = sandbox.stub(viewer, 'hasCapability'); + maybeFindTemplateStub = sandbox.stub(templates, 'maybeFindTemplate'); + ssrTemplateHelper = new SsrTemplateHelper( + sourceComponent, + viewer, + templates + ); }); - it('should return false if not doc level opt-in', () => { - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); - expect(ssrTemplateHelper.isSupported()).to.be.false; + afterEach(() => { + win.document.documentElement.removeAttribute( + 'allow-viewer-render-template' + ); + sandbox.restore(); }); - it('should return false if doc level opt-in but viewer does not have ' - + 'capability', () => { - win.document.documentElement.setAttribute( - 'allow-viewer-render-template', true); - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); - expect(ssrTemplateHelper.isSupported()).to.be.false; + describe('isSupported', () => { + it('should return true if doc level opt-in', () => { + win.document.documentElement.setAttribute( + 'allow-viewer-render-template', + true + ); + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); + expect(ssrTemplateHelper.isSupported()).to.be.true; + }); + + it('should return false if not doc level opt-in', () => { + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); + expect(ssrTemplateHelper.isSupported()).to.be.false; + }); + + it( + 'should return false if doc level opt-in but viewer does not have ' + + 'capability', + () => { + win.document.documentElement.setAttribute( + 'allow-viewer-render-template', + true + ); + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); + expect(ssrTemplateHelper.isSupported()).to.be.false; + } + ); }); - }); - describe('fetchAndRenderTemplate', () => { - it('should build payload', () => { - const request = { - 'xhrUrl': 'https://www.abracadabra.org/some-json', - 'fetchOpt': { - 'body': {}, - 'credentials': undefined, - 'headers': undefined, - 'method': 'GET', - 'requireAmpResponseSourceOrigin': false, - 'ampCors': true, - }, - }; - const sendMessage = sandbox.spy(viewer, 'sendMessageAwaitResponse'); - maybeFindTemplateStub.returns(null); - const templates = { - successTemplate: {'innerHTML': '
    much success
    '}, - errorTemplate: {'innerHTML': '
    try again
    '}, - }; - ssrTemplateHelper.fetchAndRenderTemplate( - {}, request, templates, {attr: 'test'}); - expect(sendMessage).calledWith('viewerRenderTemplate', { - 'ampComponent': { - 'type': 'amp-list', - 'successTemplate': { - 'type': 'amp-mustache', - 'payload': '
    much success
    ', - }, - 'errorTemplate': { - 'type': 'amp-mustache', - 'payload': '
    try again
    ', - }, - 'attr': 'test', - }, - 'originalRequest': { - 'init': { - 'ampCors': true, + describe('fetchAndRenderTemplate', () => { + it('should build payload', () => { + const request = { + 'xhrUrl': 'https://www.abracadabra.org/some-json', + 'fetchOpt': { 'body': {}, 'credentials': undefined, 'headers': undefined, 'method': 'GET', 'requireAmpResponseSourceOrigin': false, + 'ampCors': true, + }, + }; + const sendMessage = sandbox.spy(viewer, 'sendMessageAwaitResponse'); + maybeFindTemplateStub.returns(null); + const templates = { + successTemplate: {'innerHTML': '
    much success
    '}, + errorTemplate: {'innerHTML': '
    try again
    '}, + }; + ssrTemplateHelper.fetchAndRenderTemplate({}, request, templates, { + attr: 'test', + }); + expect(sendMessage).calledWith('viewerRenderTemplate', { + 'ampComponent': { + 'type': 'amp-list', + 'successTemplate': { + 'type': 'amp-mustache', + 'payload': '
    much success
    ', + }, + 'errorTemplate': { + 'type': 'amp-mustache', + 'payload': '
    try again
    ', + }, + 'attr': 'test', + }, + 'originalRequest': { + 'init': { + 'ampCors': true, + 'body': {}, + 'credentials': undefined, + 'headers': undefined, + 'method': 'GET', + 'requireAmpResponseSourceOrigin': false, + }, + 'input': 'https://www.abracadabra.org/some-json', }, - 'input': 'https://www.abracadabra.org/some-json', - }, + }); }); }); - }); - describe('rendering templates', () => { - let findAndSetHtmlForTemplate; - let findAndRenderTemplate; - let findAndRenderTemplateArray; - beforeEach(() => { - win.document.documentElement.setAttribute( - 'allow-viewer-render-template', true); - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); - findAndSetHtmlForTemplate = - sandbox.stub(templates, 'findAndSetHtmlForTemplate'); - findAndRenderTemplate = - sandbox.stub(templates, 'findAndRenderTemplate'); - findAndRenderTemplateArray = - sandbox.stub(templates, 'findAndRenderTemplateArray'); - }); - - describe('renderTemplate', () => { - it('should set html template', () => { - ssrTemplateHelper.renderTemplate( - {}, {html: '
    some template
    '}); - expect(findAndSetHtmlForTemplate) - .to.have.been.calledWith({}, '
    some template
    '); + describe('rendering templates', () => { + let findAndSetHtmlForTemplate; + let findAndRenderTemplate; + let findAndRenderTemplateArray; + beforeEach(() => { + win.document.documentElement.setAttribute( + 'allow-viewer-render-template', + true + ); + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(true); + findAndSetHtmlForTemplate = sandbox.stub( + templates, + 'findAndSetHtmlForTemplate' + ); + findAndRenderTemplate = sandbox.stub( + templates, + 'findAndRenderTemplate' + ); + findAndRenderTemplateArray = sandbox.stub( + templates, + 'findAndRenderTemplateArray' + ); }); - it('should throw error if html template is not defined', () => { - allowConsoleError(() => { expect(() => { - ssrTemplateHelper.renderTemplate({}, {html: null}); - }).to.throw(/Server side html response must be defined/); }); - }); + describe('renderTemplate', () => { + it('should set html template', () => { + ssrTemplateHelper.renderTemplate( + {}, + {html: '
    some template
    '} + ); + expect(findAndSetHtmlForTemplate).to.have.been.calledWith( + {}, + '
    some template
    ' + ); + }); - it('should render template ', () => { - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); - ssrTemplateHelper.renderTemplate( - {}, {data: '
    some template
    '}); - expect(findAndRenderTemplate) - .to.have.been.calledWith({}, {data: '
    some template
    '}); - }); + it('should throw error if html template is not defined', () => { + allowConsoleError(() => { + expect(() => { + ssrTemplateHelper.renderTemplate({}, {html: null}); + }).to.throw(/Server side html response must be defined/); + }); + }); + + it('should render template ', () => { + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); + ssrTemplateHelper.renderTemplate( + {}, + {data: '
    some template
    '} + ); + expect(findAndRenderTemplate).to.have.been.calledWith( + {}, + {data: '
    some template
    '} + ); + }); - it('should set template array ', () => { - hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); - ssrTemplateHelper.renderTemplate( - {}, [{data: '
    some template
    '}]); - expect(findAndRenderTemplateArray) - .to.have.been.calledWith({}, [{data: '
    some template
    '}]); + it('should set template array ', () => { + hasCapabilityStub.withArgs('viewerRenderTemplate').returns(false); + ssrTemplateHelper.renderTemplate({}, [ + {data: '
    some template
    '}, + ]); + expect(findAndRenderTemplateArray).to.have.been.calledWith({}, [ + {data: '
    some template
    '}, + ]); + }); }); }); - }); -}); + } +); diff --git a/test/unit/test-standard-actions.js b/test/unit/test-standard-actions.js index a51af83a6dd53..48daba4e9123d 100644 --- a/test/unit/test-standard-actions.js +++ b/test/unit/test-standard-actions.js @@ -48,13 +48,14 @@ describes.sandboxed('StandardActions', {}, () => { } function stubMutate(methodName) { - return sandbox.stub(standardActions.resources_, methodName).callsFake( - (unusedElement, mutator) => mutator()); + return sandbox + .stub(standardActions.resources_, methodName) + .callsFake((unusedElement, mutator) => mutator()); } function expectElementMutatedAsync(element) { - expect(mutateElementStub.withArgs(element, sinon.match.any)) - .to.be.calledOnce; + expect(mutateElementStub.withArgs(element, sinon.match.any)).to.be + .calledOnce; } function expectElementToHaveBeenHidden(element) { @@ -114,35 +115,43 @@ describes.sandboxed('StandardActions', {}, () => { standardActions = new StandardActions(ampdoc); mutateElementStub = stubMutate('mutateElement'); scrollStub = sandbox.stub( - standardActions.viewport_, - 'animateScrollIntoView'); - + standardActions.viewport_, + 'animateScrollIntoView' + ); }); describe('getAutofocusElementForShowAction', () => { - let html; beforeEach(() => { html = htmlFor(document); }); it('returns element (direct)', () => { - const el = html``; + const el = html` + + `; expect(getAutofocusElementForShowAction(el)).to.equal(el); }); it('returns element (wrapped)', () => { - const el = html``; - const wrapper = html`
    `; + const el = html` + + `; + const wrapper = html` +
    + `; wrapper.firstElementChild.appendChild(el); expect(getAutofocusElementForShowAction(wrapper)).to.equal(el); }); it('returns null', () => { - const el = html`
    `; + const el = html` +
    +
    +
    + `; expect(getAutofocusElementForShowAction(el)).to.be.null; }); - }); describe('"hide" action', () => { @@ -195,7 +204,6 @@ describes.sandboxed('StandardActions', {}, () => { }); describe('iOS force sync', () => { - let html; beforeEach(() => { html = htmlFor(document); @@ -203,46 +211,57 @@ describes.sandboxed('StandardActions', {}, () => { }); it('executes asynchronously when no autofocus (wrapped)', () => { - const node = html`
    `; + const node = html` +
    +
    +
    + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ false); }); it('executes asynchronously when no autofocus (direct)', () => { - const node = html``; + const node = html` + + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ false); }); it('executes synchronously when autofocus (wrapped)', () => { - const node = html`
    `; + const node = html` +
    +
    +
    + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ true); }); it('executes synchronously when autofocus (direct)', () => { - const node = html``; + const node = html` + + `; standardActions.handleShow_(trustedInvocation({node})); expectElementToHaveBeenShown(node, /* sync */ true); }); - }); describe('autofocus', () => { - let html; beforeEach(() => { html = htmlFor(document); }); describe('iOS force sync', () => { - beforeEach(() => { stubPlatformIsIos(); }); it('focuses [autofocus] element synchronously (direct)', () => { - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -252,8 +271,12 @@ describes.sandboxed('StandardActions', {}, () => { }); it('focuses [autofocus] element synchronously (wrapped)', () => { - const wrapper = html`
    `; - const node = html``; + const wrapper = html` +
    + `; + const node = html` + + `; node.focus = sandbox.spy(); wrapper.firstElementChild.appendChild(node); @@ -264,7 +287,9 @@ describes.sandboxed('StandardActions', {}, () => { }); it('does not focus element', () => { - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -272,13 +297,14 @@ describes.sandboxed('StandardActions', {}, () => { expectElementMutatedAsync(node); expect(node.focus).to.not.have.been.called; }); - }); it('focuses [autofocus] element asynchronously (direct)', () => { stubPlatformIsIos(false); - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -290,8 +316,12 @@ describes.sandboxed('StandardActions', {}, () => { it('focuses [autofocus] element asynchronously (wrapped)', () => { stubPlatformIsIos(false); - const wrapper = html`
    `; - const node = html``; + const wrapper = html` +
    + `; + const node = html` + + `; node.focus = sandbox.spy(); wrapper.firstElementChild.appendChild(node); @@ -302,7 +332,9 @@ describes.sandboxed('StandardActions', {}, () => { }); it('does not focus element', () => { - const node = html``; + const node = html` + + `; node.focus = sandbox.spy(); standardActions.handleShow_(trustedInvocation({node})); @@ -310,9 +342,7 @@ describes.sandboxed('StandardActions', {}, () => { expectElementMutatedAsync(node); expect(node.focus).to.not.have.been.called; }); - }); - }); describe('"toggle" action', () => { @@ -373,7 +403,8 @@ describes.sandboxed('StandardActions', {}, () => { satisfiesTrust: () => true, args: { 'class': dummyClass, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToHaveClass(element, dummyClass); }); @@ -386,7 +417,8 @@ describes.sandboxed('StandardActions', {}, () => { satisfiesTrust: () => true, args: { 'class': dummyClass, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToDropClass(element, dummyClass); }); @@ -399,7 +431,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': true, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToHaveClass(element, dummyClass); }); @@ -413,7 +446,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': true, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToHaveClass(element, dummyClass); }); @@ -426,7 +460,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': false, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToDropClass(element, dummyClass); }); @@ -440,7 +475,8 @@ describes.sandboxed('StandardActions', {}, () => { args: { 'class': dummyClass, 'force': false, - }}; + }, + }; standardActions.handleToggleClass_(invocation); expectElementToDropClass(element, dummyClass); }); @@ -524,7 +560,11 @@ describes.sandboxed('StandardActions', {}, () => { return standardActions.handleAmpTarget_(invocation).then(() => { expect(navigator.navigateTo).to.be.calledOnce; expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); }); }); @@ -535,23 +575,34 @@ describes.sandboxed('StandardActions', {}, () => { return standardActions.handleAmpTarget_(invocation).then(() => { expect(navigator.navigateTo).to.be.calledOnce; expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); }); }); - it('should pass if node does not have throwIfCannotNavigate(), ' + - 'given target', () => { - invocation.caller.tagName = 'AMP-FOO'; - invocation.caller.getImpl = () => Promise.resolve({}); - invocation.args['target'] = '_blank'; - invocation.args['opener'] = true; - - return standardActions.handleAmpTarget_(invocation).then(() => { - expect(navigator.navigateTo).to.be.calledOnce; - expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: '_blank', opener: true}); - }); - }); + it( + 'should pass if node does not have throwIfCannotNavigate(), ' + + 'given target', + () => { + invocation.caller.tagName = 'AMP-FOO'; + invocation.caller.getImpl = () => Promise.resolve({}); + invocation.args['target'] = '_blank'; + invocation.args['opener'] = true; + + return standardActions.handleAmpTarget_(invocation).then(() => { + expect(navigator.navigateTo).to.be.calledOnce; + expect(navigator.navigateTo).to.be.calledWithExactly( + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: '_blank', opener: true} + ); + }); + } + ); it('should check throwIfCannotNavigate() for AMP elements', function*() { const userError = sandbox.stub(user(), 'error'); @@ -563,25 +614,36 @@ describes.sandboxed('StandardActions', {}, () => { yield standardActions.handleAmpTarget_(invocation); expect(navigator.navigateTo).to.be.calledOnce; expect(navigator.navigateTo).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); // Should succeed if throwIfCannotNavigate() returns null. - invocation.caller.getImpl = () => Promise.resolve({ - throwIfCannotNavigate: () => null, - }); + invocation.caller.getImpl = () => + Promise.resolve({ + throwIfCannotNavigate: () => null, + }); yield standardActions.handleAmpTarget_(invocation); expect(navigator.navigateTo).to.be.calledTwice; expect(navigator.navigateTo.getCall(1)).to.be.calledWithExactly( - win, 'http://bar.com', 'AMP.navigateTo', {target: undefined, opener: undefined}); + win, + 'http://bar.com', + 'AMP.navigateTo', + {target: undefined, opener: undefined} + ); // Should fail if throwIfCannotNavigate() throws an error. - invocation.caller.getImpl = () => Promise.resolve({ - throwIfCannotNavigate: () => { throw new Error('Fake error.'); }, - }); + invocation.caller.getImpl = () => + Promise.resolve({ + throwIfCannotNavigate: () => { + throw new Error('Fake error.'); + }, + }); yield standardActions.handleAmpTarget_(invocation); expect(navigator.navigateTo).to.be.calledTwice; - expect(userError).to.be.calledWith('STANDARD-ACTIONS', - 'Fake error.'); + expect(userError).to.be.calledWith('STANDARD-ACTIONS', 'Fake error.'); }); }); @@ -591,11 +653,14 @@ describes.sandboxed('StandardActions', {}, () => { let winCloseStub; beforeEach(() => { - navigateToStub = sandbox.stub(standardActions, 'handleNavigateTo_') - .returns(Promise.resolve()); + navigateToStub = sandbox + .stub(standardActions, 'handleNavigateTo_') + .returns(Promise.resolve()); - closeOrNavigateToSpy = sandbox.spy(standardActions, - 'handleCloseOrNavigateTo_'); + closeOrNavigateToSpy = sandbox.spy( + standardActions, + 'handleCloseOrNavigateTo_' + ); winCloseStub = sandbox.stub(win, 'close'); winCloseStub.callsFake(() => { @@ -609,45 +674,43 @@ describes.sandboxed('StandardActions', {}, () => { invocation.caller = {tagName: 'DIV'}; }); - it('should be implemented', async() => { + it('should be implemented', async () => { await standardActions.handleAmpTarget_(invocation); expect(closeOrNavigateToSpy).to.be.calledOnce; expect(closeOrNavigateToSpy).to.be.calledWithExactly(invocation); }); - it('should close window if allowed', async() => { + it('should close window if allowed', async () => { win.opener = {}; win.parent = win; await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.calledOnce; expect(navigateToStub).to.be.not.called; - }); - it('should NOT close if no opener', async() => { + it('should NOT close if no opener', async () => { win.opener = null; win.parent = win; await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.not.called; }); - it('should NOT close if has a parent', async() => { + it('should NOT close if has a parent', async () => { win.opener = {}; win.parent = {}; await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.not.called; }); - it('should NOT close if in multi-doc', async() => { + it('should NOT close if in multi-doc', async () => { win.opener = {}; win.parent = win; sandbox.stub(ampdoc, 'isSingleDoc').returns(false); await standardActions.handleAmpTarget_(invocation); expect(winCloseStub).to.be.not.called; - }); - it('should navigate if not allowed to close', async() => { + it('should navigate if not allowed to close', async () => { win.opener = null; win.parent = win; sandbox.stub(ampdoc, 'isSingleDoc').returns(false); @@ -656,7 +719,7 @@ describes.sandboxed('StandardActions', {}, () => { expect(navigateToStub).to.be.called; }); - it('should navigate if win.close rejects', async() => { + it('should navigate if win.close rejects', async () => { win.opener = {}; win.parent = win; winCloseStub.callsFake(() => { @@ -676,7 +739,6 @@ describes.sandboxed('StandardActions', {}, () => { expect(goBackStub).to.be.calledOnce; }); - it('should implement optoutOfCid', function*() { const cid = cidServiceForDocForTesting(ampdoc); const optoutStub = sandbox.stub(cid, 'optOut'); @@ -693,8 +755,10 @@ describes.sandboxed('StandardActions', {}, () => { // Bind.invoke() doesn't actually resolve with a value, // but add one here to check that the promise is chained. bind.invoke.returns(Promise.resolve('set-state-complete')); - sandbox.stub(Services, 'bindForDocOrNull') - .withArgs(element).returns(Promise.resolve(bind)); + sandbox + .stub(Services, 'bindForDocOrNull') + .withArgs(element) + .returns(Promise.resolve(bind)); invocation.method = 'setState'; invocation.args = { @@ -716,8 +780,10 @@ describes.sandboxed('StandardActions', {}, () => { // Bind.invoke() doesn't actually resolve with a value, // but add one here to check that the promise is chained. bind.invoke.returns(Promise.resolve('push-state-complete')); - sandbox.stub(Services, 'bindForDocOrNull') - .withArgs(element).returns(Promise.resolve(bind)); + sandbox + .stub(Services, 'bindForDocOrNull') + .withArgs(element) + .returns(Promise.resolve(bind)); invocation.method = 'pushState'; invocation.args = { @@ -755,10 +821,10 @@ describes.sandboxed('StandardActions', {}, () => { }; invocation.node = ampdoc; const element = createElement(); - const elStub = sandbox.stub(ampdoc, 'getElementById') - .returns(element); - const scrollStub = sandbox.stub(standardActions, 'handleScrollTo_') - .returns('scrollToResponsePromise'); + const elStub = sandbox.stub(ampdoc, 'getElementById').returns(element); + const scrollStub = sandbox + .stub(standardActions, 'handleScrollTo_') + .returns('scrollToResponsePromise'); const result = standardActions.handleAmpTarget_(invocation); expect(elStub).to.be.calledWith('testIdElement'); invocation.node = element; @@ -780,8 +846,10 @@ describes.sandboxed('StandardActions', {}, () => { addGlobalMethodHandler: sandbox.spy(), }; const embedElement = embedWin.document.documentElement; - sandbox.stub(Services, 'actionServiceForDoc') - .withArgs(embedElement).returns(embedActions); + sandbox + .stub(Services, 'actionServiceForDoc') + .withArgs(embedElement) + .returns(embedActions); }); it('should configured the embedded actions service', () => { diff --git a/test/unit/test-static-template.js b/test/unit/test-static-template.js index 3d742c49adf89..6a7a5abbe329c 100644 --- a/test/unit/test-static-template.js +++ b/test/unit/test-static-template.js @@ -34,7 +34,9 @@ describe('Static Template', () => { it('works as a variable', () => { const html = htmlFor(document); - const div = html`

    `; + const div = html` +

    + `; expect(div.tagName).to.equal('DIV'); expect(div.getAttribute('attr')).to.equal('test'); @@ -54,13 +56,17 @@ describe('Static Template', () => { const iDoc = iframe.contentDocument; const html = htmlFor(document); - let div = html`
    `; + let div = html` +
    + `; expect(div.ownerDocument).to.equal(document); div = htmlFor(iDoc)`
    `; expect(div.ownerDocument).to.equal(iDoc); - div = html`
    `; + div = html` +
    + `; expect(div.ownerDocument).to.equal(iDoc); // Cleanup @@ -106,8 +112,8 @@ describe('Static Template', () => { it('finds all elements with ref attribute', () => { // Prove it doesn't need html helper const el = document.createElement('div'); - el./*TEST*/innerHTML = '
    ' + - '
    '; + el./*TEST*/ innerHTML = + '
    ' + '
    '; const refs = htmlRefs(el); expect(refs).to.deep.equal({ diff --git a/test/unit/test-storage.js b/test/unit/test-storage.js index 8826af7decf99..ba1ef7e71063a 100644 --- a/test/unit/test-storage.js +++ b/test/unit/test-storage.js @@ -23,7 +23,6 @@ import { } from '../../src/service/storage-impl'; import {dev} from '../../src/log'; - describe('Storage', () => { let sandbox; let storage; @@ -70,10 +69,12 @@ describe('Storage', () => { function expectStorage(keyValues) { const list = []; for (const k in keyValues) { - list.push(storage.get(k).then(value => { - const expectedValue = keyValues[k]; - expect(value).to.equal(expectedValue, `For "${k}"`); - })); + list.push( + storage.get(k).then(value => { + const expectedValue = keyValues[k]; + expect(value).to.equal(expectedValue, `For "${k}"`); + }) + ); } return Promise.all(list); } @@ -82,54 +83,67 @@ describe('Storage', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - return storage.get('key1').then(() => { - return storage.storePromise_; - }).then(store => { - expect(store.maxValues_).to.equal(8); - }); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + return storage + .get('key1') + .then(() => { + return storage.storePromise_; + }) + .then(store => { + expect(store.maxValues_).to.equal(8); + }); }); it('should initialize empty store with prototype-less objects', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(null)) - .once(); - return storage.get('key1').then(() => { - return storage.storePromise_; - }).then(store => { - expect(store.obj.__proto__).to.be.undefined; - expect(store.values_.__proto__).to.be.undefined; - }); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(null)) + .once(); + return storage + .get('key1') + .then(() => { + return storage.storePromise_; + }) + .then(store => { + expect(store.obj.__proto__).to.be.undefined; + expect(store.values_.__proto__).to.be.undefined; + }); }); it('should restore store with prototype-less objects', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - return storage.get('key1').then(() => { - return storage.storePromise_; - }).then(store => { - expect(store.obj.__proto__).to.be.undefined; - expect(store.values_.__proto__).to.be.undefined; - }); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + return storage + .get('key1') + .then(() => { + return storage.storePromise_; + }) + .then(store => { + expect(store.obj.__proto__).to.be.undefined; + expect(store.values_.__proto__).to.be.undefined; + }); }); it('should get the value first time and reuse store', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -146,10 +160,11 @@ describe('Storage', () => { }); it('should get the value from first ever request and reuse store', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(null)) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(null)) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -166,10 +181,11 @@ describe('Storage', () => { }); it('should recover from binding failure', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.reject('intentional')) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.reject('intentional')) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -179,10 +195,11 @@ describe('Storage', () => { }); it('should recover from binding error', () => { - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve('UNKNOWN FORMAT')) - .once(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve('UNKNOWN FORMAT')) + .once(); expect(storage.storePromise_).to.not.exist; const promise = storage.get('key1'); return promise.then(value => { @@ -195,88 +212,114 @@ describe('Storage', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - bindingMock.expects('saveBlob') - .withExactArgs('https://acme.com', sinon.match(arg => { + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + bindingMock + .expects('saveBlob') + .withExactArgs( + 'https://acme.com', + sinon.match(arg => { const store2 = new Store(JSON.parse(atob(arg))); - return (store2.get('key1') !== undefined && - store2.get('key2') !== undefined); - })) - .returns(Promise.resolve()) - .twice(); - viewerMock.expects('broadcast') - .withExactArgs(sinon.match(arg => { - return (arg['type'] == 'amp-storage-reset' && - arg['origin'] == 'https://acme.com'); - })) - .twice(); + return ( + store2.get('key1') !== undefined && store2.get('key2') !== undefined + ); + }) + ) + .returns(Promise.resolve()) + .twice(); + viewerMock + .expects('broadcast') + .withExactArgs( + sinon.match(arg => { + return ( + arg['type'] == 'amp-storage-reset' && + arg['origin'] == 'https://acme.com' + ); + }) + ) + .twice(); expect(storage.storePromise_).to.not.exist; const promise = storage.set('key1', true); - return promise.then(() => { - const store1Promise = storage.storePromise_; - expect(store1Promise).to.exist; - - // Repeat. - return storage.set('key2', true).then(() => { - expect(storage.storePromise_).to.equal(store1Promise); - }); - }).then(() => { - return expectStorage({ - 'key1': true, - 'key2': true, + return promise + .then(() => { + const store1Promise = storage.storePromise_; + expect(store1Promise).to.exist; + + // Repeat. + return storage.set('key2', true).then(() => { + expect(storage.storePromise_).to.equal(store1Promise); + }); + }) + .then(() => { + return expectStorage({ + 'key1': true, + 'key2': true, + }); }); - }); }); it('should remove the key first time and reuse store', () => { const store1 = new Store({}); store1.set('key1', 'value1'); store1.set('key2', 'value2'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .once(); - bindingMock.expects('saveBlob') - .withExactArgs('https://acme.com', sinon.match(arg => { + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .once(); + bindingMock + .expects('saveBlob') + .withExactArgs( + 'https://acme.com', + sinon.match(arg => { const store2 = new Store(JSON.parse(atob(arg))); - return (store2.get('key1') === undefined); - })) - .returns(Promise.resolve()) - .twice(); - viewerMock.expects('broadcast') - .withExactArgs(sinon.match(arg => { - return (arg['type'] == 'amp-storage-reset' && - arg['origin'] == 'https://acme.com'); - })) - .twice(); + return store2.get('key1') === undefined; + }) + ) + .returns(Promise.resolve()) + .twice(); + viewerMock + .expects('broadcast') + .withExactArgs( + sinon.match(arg => { + return ( + arg['type'] == 'amp-storage-reset' && + arg['origin'] == 'https://acme.com' + ); + }) + ) + .twice(); expect(storage.storePromise_).to.not.exist; const promise = storage.remove('key1'); - return promise.then(() => { - const store1Promise = storage.storePromise_; - expect(store1Promise).to.exist; - - // Repeat. - return storage.remove('key2').then(() => { - expect(storage.storePromise_).to.equal(store1Promise); - }); - }).then(() => { - return expectStorage({ - 'key1': undefined, - 'key2': undefined, + return promise + .then(() => { + const store1Promise = storage.storePromise_; + expect(store1Promise).to.exist; + + // Repeat. + return storage.remove('key2').then(() => { + expect(storage.storePromise_).to.equal(store1Promise); + }); + }) + .then(() => { + return expectStorage({ + 'key1': undefined, + 'key2': undefined, + }); }); - }); }); it('should react to reset messages', () => { const store1 = new Store({}); store1.set('key1', 'value1'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .twice(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .twice(); return storage.get('key1').then(value => { expect(value).to.equal('value1'); const store1Promise = storage.storePromise_; @@ -298,10 +341,11 @@ describe('Storage', () => { it('should ignore unrelated reset messages', () => { const store1 = new Store({}); store1.set('key1', 'value1'); - bindingMock.expects('loadBlob') - .withExactArgs('https://acme.com') - .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) - .twice(); + bindingMock + .expects('loadBlob') + .withExactArgs('https://acme.com') + .returns(Promise.resolve(btoa(JSON.stringify(store1.obj)))) + .twice(); return storage.get('key1').then(value => { expect(value).to.equal('value1'); const store1Promise = storage.storePromise_; @@ -317,7 +361,6 @@ describe('Storage', () => { }); }); - describe('Store', () => { let sandbox; let clock; @@ -438,7 +481,6 @@ describe('Store', () => { }); }); - describe('LocalStorageBinding', () => { let sandbox; let windowApi; @@ -475,20 +517,22 @@ describe('LocalStorageBinding', () => { }); it('should load store when available', () => { - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .returns('BLOB1') - .once(); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .returns('BLOB1') + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.equal('BLOB1'); }); }); it('should load default store when not yet available', () => { - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .returns(undefined) - .once(); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .returns(undefined) + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.not.exist; }); @@ -496,89 +540,107 @@ describe('LocalStorageBinding', () => { it('should reject on local storage failure w/ localStorage support', () => { binding.isLocalStorageSupported_ = true; - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .throws(new Error('unknown')) - .once(); - return binding.loadBlob('https://acme.com') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .throws(new Error('unknown')) + .once(); + return binding + .loadBlob('https://acme.com') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); it('should succeed loadBlob w/o localStorage support', () => { binding.isLocalStorageSupported_ = false; - localStorageMock.expects('getItem') - .withExactArgs('amp-store:https://acme.com') - .throws(new Error('unknown')) - .once(); - return binding.loadBlob('https://acme.com') - .then(res => `SUCCESS ${res}`, () => 'ERROR').then(res => { - // Resolves with null - expect(res).to.equal('SUCCESS null'); - }); + localStorageMock + .expects('getItem') + .withExactArgs('amp-store:https://acme.com') + .throws(new Error('unknown')) + .once(); + return binding + .loadBlob('https://acme.com') + .then(res => `SUCCESS ${res}`, () => 'ERROR') + .then(res => { + // Resolves with null + expect(res).to.equal('SUCCESS null'); + }); }); it('should bypass loading from localStorage if getItem throws', () => { - localStorageMock.expects('getItem') - .throws(new Error('unknown')) - .once(); + localStorageMock + .expects('getItem') + .throws(new Error('unknown')) + .once(); binding = new LocalStorageBinding(windowApi); localStorageMock.expects('getItem').never(); - return binding.loadBlob('https://acme.com') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('SUCCESS'); - }); + return binding + .loadBlob('https://acme.com') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('SUCCESS'); + }); }); it('should save store', () => { - localStorageMock.expects('setItem') - .withExactArgs('amp-store:https://acme.com', 'BLOB1') - .once(); + localStorageMock + .expects('setItem') + .withExactArgs('amp-store:https://acme.com', 'BLOB1') + .once(); return binding.saveBlob('https://acme.com', 'BLOB1'); }); it('should reject on save store failure', () => { - localStorageMock.expects('setItem') - .withExactArgs('amp-store:https://acme.com', 'BLOB1') - .throws(new Error('unknown')) - .once(); - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + localStorageMock + .expects('setItem') + .withExactArgs('amp-store:https://acme.com', 'BLOB1') + .throws(new Error('unknown')) + .once(); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); it('should succeed saveBlob w/o localStorage support', () => { binding.isLocalStorageSupported_ = false; - localStorageMock.expects('setItem') - .withExactArgs('amp-store:https://acme.com', 'BLOB1') - .throws(new Error('unknown')) - .once(); + localStorageMock + .expects('setItem') + .withExactArgs('amp-store:https://acme.com', 'BLOB1') + .throws(new Error('unknown')) + .once(); // Never reaches setItem - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('SUCCESS'); - }); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('SUCCESS'); + }); }); it('should bypass saving to localStorage if getItem throws', () => { const setItemSpy = sandbox.spy(windowApi.localStorage, 'setItem'); - localStorageMock.expects('getItem') - .throws(new Error('unknown')) - .once(); + localStorageMock + .expects('getItem') + .throws(new Error('unknown')) + .once(); binding = new LocalStorageBinding(windowApi); // Never reaches setItem - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(setItemSpy).to.have.not.been.called; - expect(res).to.equal('SUCCESS'); - }); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(setItemSpy).to.have.not.been.called; + expect(res).to.equal('SUCCESS'); + }); }); }); - describe('ViewerStorageBinding', () => { let sandbox; let viewer; @@ -599,61 +661,81 @@ describe('ViewerStorageBinding', () => { }); it('should load store from viewer', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('loadStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com'); - })) - .returns(Promise.resolve({'blob': 'BLOB1'})) - .once(); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'loadStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com'; + }) + ) + .returns(Promise.resolve({'blob': 'BLOB1'})) + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.equal('BLOB1'); }); }); it('should load default store when not yet available', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('loadStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com'); - })) - .returns(Promise.resolve({})) - .once(); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'loadStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com'; + }) + ) + .returns(Promise.resolve({})) + .once(); return binding.loadBlob('https://acme.com').then(blob => { expect(blob).to.not.exist; }); }); it('should reject on viewer failure', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('loadStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com'); - })) - .returns(Promise.reject('unknown')) - .once(); - return binding.loadBlob('https://acme.com') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'loadStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com'; + }) + ) + .returns(Promise.reject('unknown')) + .once(); + return binding + .loadBlob('https://acme.com') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); it('should save store', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('saveStore', sinon.match(arg => { - return (arg['origin'] == 'https://acme.com' && - arg['blob'] == 'BLOB1'); - })) - .returns(Promise.resolve()) - .once(); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs( + 'saveStore', + sinon.match(arg => { + return arg['origin'] == 'https://acme.com' && arg['blob'] == 'BLOB1'; + }) + ) + .returns(Promise.resolve()) + .once(); return binding.saveBlob('https://acme.com', 'BLOB1'); }); it('should reject on save store failure', () => { - viewerMock.expects('sendMessageAwaitResponse') - .withExactArgs('saveStore', sinon.match(() => true)) - .returns(Promise.reject('unknown')) - .once(); - return binding.saveBlob('https://acme.com', 'BLOB1') - .then(() => 'SUCCESS', () => 'ERROR').then(res => { - expect(res).to.equal('ERROR'); - }); + viewerMock + .expects('sendMessageAwaitResponse') + .withExactArgs('saveStore', sinon.match(() => true)) + .returns(Promise.reject('unknown')) + .once(); + return binding + .saveBlob('https://acme.com', 'BLOB1') + .then(() => 'SUCCESS', () => 'ERROR') + .then(res => { + expect(res).to.equal('ERROR'); + }); }); }); diff --git a/test/unit/test-string.js b/test/unit/test-string.js index ab4ca3186d039..3a60b25714df5 100644 --- a/test/unit/test-string.js +++ b/test/unit/test-string.js @@ -66,7 +66,6 @@ describe('includes', () => { }); describe('expandTemplate', () => { - const data = { 'x': 'Test 1', 'y': 'Test 2', @@ -112,12 +111,11 @@ describe('expandTemplate', () => { it('should handle multiple iterations when asked to.', () => { expect(expandTemplate('${tox}', testGetter, 2)).to.equal('Test 1'); expect(expandTemplate('${toxy}', testGetter, 2)).to.equal('Test 1Test 2'); - expect(expandTemplate('${totoxy}', testGetter, 2)).to.equal( - '${x}${y}'); - expect(expandTemplate('${totoxy}', testGetter, 3)).to.equal( - 'Test 1Test 2'); + expect(expandTemplate('${totoxy}', testGetter, 2)).to.equal('${x}${y}'); + expect(expandTemplate('${totoxy}', testGetter, 3)).to.equal('Test 1Test 2'); expect(expandTemplate('${totoxy}', testGetter, 10)).to.equal( - 'Test 1Test 2'); + 'Test 1Test 2' + ); }); it('should handle circular expansions without hanging', () => { diff --git a/test/unit/test-style-installer.js b/test/unit/test-style-installer.js index 7a5a0ea4b027a..8baa75b8b252f 100644 --- a/test/unit/test-style-installer.js +++ b/test/unit/test-style-installer.js @@ -24,9 +24,7 @@ import {installPerformanceService} from '../../src/service/performance-impl'; import {isAnimationNone} from '../../testing/test-helper'; import {setShadowDomSupportedVersionForTesting} from '../../src/web-components'; - describe('Styles', () => { - describes.realWin('makeBodyVisible', {amp: true}, env => { let win, doc, ampdoc; let resources; @@ -75,8 +73,9 @@ describe('Styles', () => { expect(getStyle(doc.body, 'animation')).to.equal(''); expect(ampdoc.signals().get('render-start')).to.be.null; - waitForServicesStub.withArgs(win) - .returns(Promise.resolve(['service1', 'service2'])); + waitForServicesStub + .withArgs(win) + .returns(Promise.resolve(['service1', 'service2'])); styles.makeBodyVisible(doc); return new Promise(resolve => { setTimeout(resolve, 0); @@ -103,239 +102,276 @@ describe('Styles', () => { }); }); - describes.repeated('installStylesForDoc', { - 'single': {}, - 'shadow native': {}, - 'shadow polyfill': {}, - }, variantName => { - const url = 'https://acme.org/doc1'; - - describes.realWin(' ', {}, env => { - let win, doc, ampdoc; - let head; - - beforeEach(() => { - win = env.win; - doc = win.document; - - // Don't install AMP runtime itself, because test fixtures automatically - // setup stylesheets as well. - if (variantName == 'single') { - ampdoc = new AmpDocSingle(win); - } else { - const hostElement = doc.createElement('div'); - doc.body.appendChild(hostElement); - setShadowDomSupportedVersionForTesting(undefined); - if (variantName == 'shadow polyfill') { - setShadowDomSupportedVersionForTesting('none'); + describes.repeated( + 'installStylesForDoc', + { + 'single': {}, + 'shadow native': {}, + 'shadow polyfill': {}, + }, + variantName => { + const url = 'https://acme.org/doc1'; + + describes.realWin(' ', {}, env => { + let win, doc, ampdoc; + let head; + + beforeEach(() => { + win = env.win; + doc = win.document; + + // Don't install AMP runtime itself, because test fixtures automatically + // setup stylesheets as well. + if (variantName == 'single') { + ampdoc = new AmpDocSingle(win); + } else { + const hostElement = doc.createElement('div'); + doc.body.appendChild(hostElement); + setShadowDomSupportedVersionForTesting(undefined); + if (variantName == 'shadow polyfill') { + setShadowDomSupportedVersionForTesting('none'); + } + const shadowRoot = createShadowRoot(hostElement); + ampdoc = new AmpDocShadow(win, url, shadowRoot); } - const shadowRoot = createShadowRoot(hostElement); - ampdoc = new AmpDocShadow(win, url, shadowRoot); - } - head = ampdoc.getHeadNode(); - }); - - afterEach(() => { - setShadowDomSupportedVersionForTesting(undefined); - }); - - /** - * @param {!Document} doc - * @param {string} cssText - * @param {boolean} isRuntimeCss - * @param {string=} opt_ext - * @return {!Promise} - */ - function installStylesAsPromise(cssText, isRuntimeCss, opt_ext) { - return new Promise(resolve => { - styles.installStylesForDoc(ampdoc, cssText, resolve, - isRuntimeCss, opt_ext); - }); - } - - it('should install runtime styles', () => { - const cssText = 'amp-element{}'; - return installStylesAsPromise(cssText, true).then(styleEl => { - expect(styleEl.parentNode).to.equal(head); - expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); - expect(styleEl.hasAttribute('amp-runtime')).to.be.true; - expect(styleEl.textContent).to.match(/amp-element\s*\{/); + head = ampdoc.getHeadNode(); }); - }); - it('should install extension styles after runtime', () => { - const runtimeCssText = 'amp-runtime{}'; - const extCssText = 'amp-ext1{}'; - return installStylesAsPromise(runtimeCssText, true).then(() => { - const otherEl = doc.createElement('link'); - head.appendChild(otherEl); - // Install extension styles. - return installStylesAsPromise(extCssText, false, 'amp-ext1'); - }).then(styleEl => { - expect(styleEl.parentNode).to.equal(head); - expect(styleEl.previousElementSibling) - .to.equal(head.__AMP_CSS_SM['amp-runtime']); - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.match(/amp-ext1\s*\{/); + afterEach(() => { + setShadowDomSupportedVersionForTesting(undefined); }); - }); + /** + * @param {!Document} doc + * @param {string} cssText + * @param {boolean} isRuntimeCss + * @param {string=} opt_ext + * @return {!Promise} + */ + function installStylesAsPromise(cssText, isRuntimeCss, opt_ext) { + return new Promise(resolve => { + styles.installStylesForDoc( + ampdoc, + cssText, + resolve, + isRuntimeCss, + opt_ext + ); + }); + } + it('should install runtime styles', () => { + const cssText = 'amp-element{}'; + return installStylesAsPromise(cssText, true).then(styleEl => { + expect(styleEl.parentNode).to.equal(head); + expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); + expect(styleEl.hasAttribute('amp-runtime')).to.be.true; + expect(styleEl.textContent).to.match(/amp-element\s*\{/); + }); + }); - it('should install user styles after everything else', () => { - const runtimeCssText = 'amp-runtime{}'; - const userCssText = 'user{}'; - const otherEl = doc.createElement('link'); - return installStylesAsPromise(runtimeCssText, true).then(() => { - head.appendChild(otherEl); - return installStylesAsPromise(userCssText, false, 'amp-custom'); - }).then(styleEl => { - expect(styleEl.parentNode).to.equal(head); - expect(styleEl.previousElementSibling).to.equal(otherEl); - expect(styleEl.hasAttribute('amp-custom')).to.be.true; - expect(styleEl.hasAttribute('amp-extension')).to.be.false; - expect(styleEl.textContent).to.match(/user\s*\{/); + it('should install extension styles after runtime', () => { + const runtimeCssText = 'amp-runtime{}'; + const extCssText = 'amp-ext1{}'; + return installStylesAsPromise(runtimeCssText, true) + .then(() => { + const otherEl = doc.createElement('link'); + head.appendChild(otherEl); + // Install extension styles. + return installStylesAsPromise(extCssText, false, 'amp-ext1'); + }) + .then(styleEl => { + expect(styleEl.parentNode).to.equal(head); + expect(styleEl.previousElementSibling).to.equal( + head.__AMP_CSS_SM['amp-runtime'] + ); + expect(styleEl.getAttribute('amp-extension')).to.equal( + 'amp-ext1' + ); + expect(styleEl.textContent).to.match(/amp-ext1\s*\{/); + }); }); - }); - it('should not create duplicate runtime style', () => { - let firstStyleEl; - return installStylesAsPromise('', true).then(styleEl => { - firstStyleEl = styleEl; - // Duplicate call. - return installStylesAsPromise('other{}', true); - }).then(styleEl => { - expect(styleEl).to.equal(firstStyleEl); - expect(styleEl.textContent).to.equal('other{}'); - expect(head.querySelectorAll('style[amp-runtime]')) - .to.have.length(1); + it('should install user styles after everything else', () => { + const runtimeCssText = 'amp-runtime{}'; + const userCssText = 'user{}'; + const otherEl = doc.createElement('link'); + return installStylesAsPromise(runtimeCssText, true) + .then(() => { + head.appendChild(otherEl); + return installStylesAsPromise(userCssText, false, 'amp-custom'); + }) + .then(styleEl => { + expect(styleEl.parentNode).to.equal(head); + expect(styleEl.previousElementSibling).to.equal(otherEl); + expect(styleEl.hasAttribute('amp-custom')).to.be.true; + expect(styleEl.hasAttribute('amp-extension')).to.be.false; + expect(styleEl.textContent).to.match(/user\s*\{/); + }); }); - }); - it('should discover existing runtime style', () => { - const serverEl = doc.createElement('style'); - serverEl.setAttribute('amp-runtime', ''); - head.appendChild(serverEl); - return installStylesAsPromise('other{}', true).then(styleEl => { - expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(serverEl); - expect(styleEl).to.equal(serverEl); - expect(styleEl.textContent).to.equal('other{}'); - expect(head.querySelectorAll('style[amp-runtime]')) - .to.have.length(1); + it('should not create duplicate runtime style', () => { + let firstStyleEl; + return installStylesAsPromise('', true) + .then(styleEl => { + firstStyleEl = styleEl; + // Duplicate call. + return installStylesAsPromise('other{}', true); + }) + .then(styleEl => { + expect(styleEl).to.equal(firstStyleEl); + expect(styleEl.textContent).to.equal('other{}'); + expect( + head.querySelectorAll('style[amp-runtime]') + ).to.have.length(1); + }); }); - }); - it('should re-create runtime style if absent', () => { - return installStylesAsPromise('other{}', true).then(styleEl => { - expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-runtime]')) - .to.have.length(1); + it('should discover existing runtime style', () => { + const serverEl = doc.createElement('style'); + serverEl.setAttribute('amp-runtime', ''); + head.appendChild(serverEl); + return installStylesAsPromise('other{}', true).then(styleEl => { + expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(serverEl); + expect(styleEl).to.equal(serverEl); + expect(styleEl.textContent).to.equal('other{}'); + expect(head.querySelectorAll('style[amp-runtime]')).to.have.length( + 1 + ); + }); }); - }); - it('should discover existing extension style', () => { - const serverEl = doc.createElement('style'); - serverEl.setAttribute('amp-extension', 'amp-ext1'); - head.appendChild(serverEl); - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(head.__AMP_CSS_SM['amp-runtime']).to.not.exist; - expect(styleEl).to.equal(serverEl); - expect(styleEl.textContent).to.equal('other{}'); - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(1); + it('should re-create runtime style if absent', () => { + return installStylesAsPromise('other{}', true).then(styleEl => { + expect(head.__AMP_CSS_SM['amp-runtime']).to.equal(styleEl); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect(head.querySelectorAll('style[amp-runtime]')).to.have.length( + 1 + ); + }); }); - }); - it('should re-create extension style', () => { - installStylesAsPromise('runtime{}', true); - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(1); + it('should discover existing extension style', () => { + const serverEl = doc.createElement('style'); + serverEl.setAttribute('amp-extension', 'amp-ext1'); + head.appendChild(serverEl); + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(head.__AMP_CSS_SM['amp-runtime']).to.not.exist; + expect(styleEl).to.equal(serverEl); + expect(styleEl.textContent).to.equal('other{}'); + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(1); + }); }); - }); - it('should re-create extension style w/o cache', () => { - const runtimeStyle = doc.createElement('style'); - runtimeStyle.setAttribute('amp-runtime', ''); - head.appendChild(runtimeStyle); - // Additional element to test the correct insertion order. - head.appendChild(doc.createElement('link')); - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(1); - expect(styleEl.previousElementSibling).to.equal(runtimeStyle); + it('should re-create extension style', () => { + installStylesAsPromise('runtime{}', true); + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(1); + }); }); - }); - it('should use the cached extension style', () => { - const cachedExtStyle = doc.createElement('style'); - cachedExtStyle.textContent = 'ext1{}'; - head.appendChild(cachedExtStyle); - head.__AMP_CSS_SM = { - 'amp-extension=amp-ext1': cachedExtStyle, - }; - const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); - return promise.then(styleEl => { - expect(styleEl).to.equal(cachedExtStyle); - expect(head.__AMP_CSS_SM['amp-extension=amp-ext1']) - .to.equal(cachedExtStyle); - // Ensure the style is not re-inserted. - expect(head.querySelectorAll('style[amp-extension=amp-ext1]')) - .to.have.length(0); + it('should re-create extension style w/o cache', () => { + const runtimeStyle = doc.createElement('style'); + runtimeStyle.setAttribute('amp-runtime', ''); + head.appendChild(runtimeStyle); + // Additional element to test the correct insertion order. + head.appendChild(doc.createElement('link')); + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(1); + expect(styleEl.previousElementSibling).to.equal(runtimeStyle); + }); }); - }); - it('should create a amp-custom style', () => { - const promise = installStylesAsPromise( - 'other{}', false, 'amp-custom'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-custom')).to.equal(''); - expect(head.lastElementChild).to.equal(styleEl); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-custom]')) - .to.have.length(1); + it('should use the cached extension style', () => { + const cachedExtStyle = doc.createElement('style'); + cachedExtStyle.textContent = 'ext1{}'; + head.appendChild(cachedExtStyle); + head.__AMP_CSS_SM = { + 'amp-extension=amp-ext1': cachedExtStyle, + }; + const promise = installStylesAsPromise('other{}', false, 'amp-ext1'); + return promise.then(styleEl => { + expect(styleEl).to.equal(cachedExtStyle); + expect(head.__AMP_CSS_SM['amp-extension=amp-ext1']).to.equal( + cachedExtStyle + ); + // Ensure the style is not re-inserted. + expect( + head.querySelectorAll('style[amp-extension=amp-ext1]') + ).to.have.length(0); + }); }); - }); - it('should create a amp-keyframes style', () => { - const promise = installStylesAsPromise( - 'other{}', false, 'amp-keyframes'); - return promise.then(styleEl => { - expect(styleEl.getAttribute('amp-keyframes')).to.equal(''); - expect(head.lastElementChild).to.equal(styleEl); - expect(styleEl.textContent).to.match(/other\s*\{/); - expect(head.querySelectorAll('style[amp-keyframes]')) - .to.have.length(1); + it('should create a amp-custom style', () => { + const promise = installStylesAsPromise( + 'other{}', + false, + 'amp-custom' + ); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-custom')).to.equal(''); + expect(head.lastElementChild).to.equal(styleEl); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect(head.querySelectorAll('style[amp-custom]')).to.have.length( + 1 + ); + }); }); - }); - it('should use a transform', () => { - styles.installCssTransformer(head, function(css) { - return css.toUpperCase(); + it('should create a amp-keyframes style', () => { + const promise = installStylesAsPromise( + 'other{}', + false, + 'amp-keyframes' + ); + return promise.then(styleEl => { + expect(styleEl.getAttribute('amp-keyframes')).to.equal(''); + expect(head.lastElementChild).to.equal(styleEl); + expect(styleEl.textContent).to.match(/other\s*\{/); + expect( + head.querySelectorAll('style[amp-keyframes]') + ).to.have.length(1); + }); }); - const promise1 = installStylesAsPromise('style1{}', true); - const promise2 = installStylesAsPromise( - 'style2{}', false, 'amp-ext1'); - const promise3 = installStylesAsPromise( - 'style3{}', false, 'amp-custom'); - return Promise.all([promise1, promise2, promise3]).then(styleEls => { - expect(styleEls).to.have.length(3); - expect(styleEls[0].textContent).to.contain('STYLE1'); - expect(styleEls[1].textContent).to.contain('STYLE2'); - expect(styleEls[2].textContent).to.contain('STYLE3'); + + it('should use a transform', () => { + styles.installCssTransformer(head, function(css) { + return css.toUpperCase(); + }); + const promise1 = installStylesAsPromise('style1{}', true); + const promise2 = installStylesAsPromise( + 'style2{}', + false, + 'amp-ext1' + ); + const promise3 = installStylesAsPromise( + 'style3{}', + false, + 'amp-custom' + ); + return Promise.all([promise1, promise2, promise3]).then(styleEls => { + expect(styleEls).to.have.length(3); + expect(styleEls[0].textContent).to.contain('STYLE1'); + expect(styleEls[1].textContent).to.contain('STYLE2'); + expect(styleEls[2].textContent).to.contain('STYLE3'); + }); }); }); - }); - }); - + } + ); describes.realWin('installStylesLegacy', {}, env => { let win, doc; @@ -354,8 +390,13 @@ describe('Styles', () => { */ function installStylesAsPromise(cssText, isRuntimeCss, opt_ext) { return new Promise(resolve => { - styles.installStylesLegacy(doc, cssText, resolve, - isRuntimeCss, opt_ext); + styles.installStylesLegacy( + doc, + cssText, + resolve, + isRuntimeCss, + opt_ext + ); }); } @@ -372,18 +413,21 @@ describe('Styles', () => { it('should install extension styles after runtime', () => { const runtimeCssText = '/*amp-runtime*/'; const extCssText = '/*amp-ext1*/'; - return installStylesAsPromise(runtimeCssText, true).then(() => { - const otherEl = doc.createElement('link'); - doc.head.appendChild(otherEl); - // Install extension styles. - return installStylesAsPromise(extCssText, false, 'amp-ext1'); - }).then(styleEl => { - expect(styleEl.parentElement).to.equal(doc.head); - expect(styleEl.previousElementSibling) - .to.equal(doc.head.__AMP_CSS_SM['amp-runtime']); - expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); - expect(styleEl.textContent).to.equal(extCssText); - }); + return installStylesAsPromise(runtimeCssText, true) + .then(() => { + const otherEl = doc.createElement('link'); + doc.head.appendChild(otherEl); + // Install extension styles. + return installStylesAsPromise(extCssText, false, 'amp-ext1'); + }) + .then(styleEl => { + expect(styleEl.parentElement).to.equal(doc.head); + expect(styleEl.previousElementSibling).to.equal( + doc.head.__AMP_CSS_SM['amp-runtime'] + ); + expect(styleEl.getAttribute('amp-extension')).to.equal('amp-ext1'); + expect(styleEl.textContent).to.equal(extCssText); + }); }); it('should create a amp-custom style', () => { @@ -392,8 +436,9 @@ describe('Styles', () => { expect(styleEl.getAttribute('amp-custom')).to.equal(''); expect(doc.head.lastElementChild).to.equal(styleEl); expect(styleEl.textContent).to.equal('/*other*/'); - expect(doc.head.querySelectorAll('style[amp-custom]')) - .to.have.length(1); + expect(doc.head.querySelectorAll('style[amp-custom]')).to.have.length( + 1 + ); }); }); }); diff --git a/test/unit/test-style.js b/test/unit/test-style.js index 5e2c716754da4..59c18acf6883e 100644 --- a/test/unit/test-style.js +++ b/test/unit/test-style.js @@ -17,7 +17,6 @@ import * as st from '../../src/style'; describe('Style', () => { - let sandbox; beforeEach(() => { @@ -72,21 +71,25 @@ describe('Style', () => { width: st.px(101), }); expect(element.style.width).to.equal('101px'); - expect(element.style.getPropertyPriority('width')) - .to.equal('important'); + expect(element.style.getPropertyPriority('width')).to.equal('important'); }); it('setImportantStyles with vendor prefix', () => { const spy = sandbox.spy(); - const element = {style: { - WebkitTransitionDurationImportant: '', - setProperty: spy, - }}; + const element = { + style: { + WebkitTransitionDurationImportant: '', + setProperty: spy, + }, + }; st.setImportantStyles(element, { transitionDurationImportant: '1s', }); - expect(spy).to.have.been.calledWith('WebkitTransitionDurationImportant', - '1s', 'important'); + expect(spy).to.have.been.calledWith( + 'WebkitTransitionDurationImportant', + '1s', + 'important' + ); }); it('px', () => { @@ -113,59 +116,75 @@ describe('Style', () => { it('removeAlphaFromColor', () => { expect(st.removeAlphaFromColor('rgba(1, 1, 1, 0)')).to.equal( - 'rgba(1, 1, 1, 1)'); - expect(st.removeAlphaFromColor('rgb(1, 1, 1)')).to.equal( - 'rgb(1, 1, 1)'); + 'rgba(1, 1, 1, 1)' + ); + expect(st.removeAlphaFromColor('rgb(1, 1, 1)')).to.equal('rgb(1, 1, 1)'); expect(st.removeAlphaFromColor('rgba(0, 0, 0,-0.5)')).to.equal( - 'rgba(0, 0, 0, 1)'); + 'rgba(0, 0, 0, 1)' + ); }); describe('getVendorJsPropertyName', () => { - it('no prefix', () => { const element = {style: {transitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('transitionDuration'); }); it('should use cached previous result', () => { let element = {style: {transitionDuration: ''}}; - let prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration'); + let prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration' + ); expect(prop).to.equal('transitionDuration'); element = {style: {WebkitTransitionDuration: ''}}; - prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration'); + prop = st.getVendorJsPropertyName(element.style, 'transitionDuration'); expect(prop).to.equal('transitionDuration'); }); it('Webkit', () => { const element = {style: {WebkitTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('WebkitTransitionDuration'); }); it('Moz', () => { const element = {style: {MozTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('MozTransitionDuration'); }); it('ms', () => { const element = {style: {msTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('msTransitionDuration'); }); it('O opera', () => { const element = {style: {OTransitionDuration: ''}}; - const prop = st - .getVendorJsPropertyName(element.style, 'transitionDuration', true); + const prop = st.getVendorJsPropertyName( + element.style, + 'transitionDuration', + true + ); expect(prop).to.equal('OTransitionDuration'); }); }); diff --git a/test/unit/test-task-queue.js b/test/unit/test-task-queue.js index f12877c6867be..c89de301c1558 100644 --- a/test/unit/test-task-queue.js +++ b/test/unit/test-task-queue.js @@ -16,9 +16,7 @@ import {TaskQueue} from '../../src/service/task-queue'; - describe('TaskQueue', () => { - let sandbox; let clock; let queue; @@ -45,9 +43,11 @@ describe('TaskQueue', () => { expect(queue.getLastEnqueueTime()).to.equal(1000); expect(queue.getLastDequeueTime()).to.equal(0); - allowConsoleError(() => { expect(() => { - queue.enqueue({id: '1'}); - }).to.throw(/Task already enqueued/); }); + allowConsoleError(() => { + expect(() => { + queue.enqueue({id: '1'}); + }).to.throw(/Task already enqueued/); + }); queue.dequeue({id: '1'}); expect(queue.getTaskById('1')).to.equal(null); diff --git a/test/unit/test-template.js b/test/unit/test-template.js index 432d6f06ce447..e51c3c76695a8 100644 --- a/test/unit/test-template.js +++ b/test/unit/test-template.js @@ -73,8 +73,11 @@ describes.fakeWin('Template', {amp: true}, env => { it('should render immediately', () => { const templateElement = createTemplateElement(); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); return templates.renderTemplate(templateElement, {value: 1}).then(res => { expect(res.textContent).to.equal('abc1'); }); @@ -84,45 +87,65 @@ describes.fakeWin('Template', {amp: true}, env => { const templateElement = createTemplateElement(); // Use TemplateImplCheckingViewer to make sure viewerCanRenderTemplates // works correctly when the template is later detached. - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImplCheckingViewer); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImplCheckingViewer + ); const viewerService = getServiceForDoc(templateElement, 'viewer'); env.sandbox.stub(viewerService, 'hasCapability').returns(true); - return templates.renderTemplate(templateElement, {value: 1}).then(() => { - templateElement.parentElement.removeChild(templateElement); - return templates.renderTemplate(templateElement, {value: 2}); - }).then(res => { - expect(res.textContent).to.equal('abc2'); - }); + return templates + .renderTemplate(templateElement, {value: 1}) + .then(() => { + templateElement.parentElement.removeChild(templateElement); + return templates.renderTemplate(templateElement, {value: 2}); + }) + .then(res => { + expect(res.textContent).to.equal('abc2'); + }); }); it('should render array', () => { const templateElement = createTemplateElement(); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); - return templates.renderTemplateArray(templateElement, - [{value: 1}, {value: 2}]).then(res => { - expect(res).to.have.length.of(2); - expect(res[0].textContent).to.equal('abc1'); - expect(res[1].textContent).to.equal('abc2'); - }); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); + return templates + .renderTemplateArray(templateElement, [{value: 1}, {value: 2}]) + .then(res => { + expect(res).to.have.length.of(2); + expect(res[0].textContent).to.equal('abc1'); + expect(res[1].textContent).to.equal('abc2'); + }); }); it('should NOT allow registering template class twice', () => { const templateElement = createTemplateElement(); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); - allowConsoleError(() => { expect(() => { - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); - }).to.throw(/Duplicate template type/); }); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); + allowConsoleError(() => { + expect(() => { + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); + }).to.throw(/Duplicate template type/); + }); }); it('should block render until template registered', () => { const templateElement = createTemplateElement(); const scriptElement = doc.createElement('script'); - scriptElement.setAttribute('custom-template', - templateElement.getAttribute('type')); + scriptElement.setAttribute( + 'custom-template', + templateElement.getAttribute('type') + ); doc.body.appendChild(scriptElement); let result = undefined; templates.renderTemplate(templateElement, {value: 0}).then(res => { @@ -138,12 +161,17 @@ describes.fakeWin('Template', {amp: true}, env => { it('should unblock render when template registered', () => { const templateElement = createTemplateElement(); const scriptElement = doc.createElement('script'); - scriptElement.setAttribute('custom-template', - templateElement.getAttribute('type')); + scriptElement.setAttribute( + 'custom-template', + templateElement.getAttribute('type') + ); doc.body.appendChild(scriptElement); const p = templates.renderTemplate(templateElement, {value: 1}); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); return p.then(res => { expect(res.textContent).to.equal('abc1'); }); @@ -152,22 +180,29 @@ describes.fakeWin('Template', {amp: true}, env => { it('should unblock render for parallel templates', () => { const templateElement = createTemplateElement(); const scriptElement = doc.createElement('script'); - scriptElement.setAttribute('custom-template', - templateElement.getAttribute('type')); + scriptElement.setAttribute( + 'custom-template', + templateElement.getAttribute('type') + ); doc.body.appendChild(scriptElement); const p1 = templates.renderTemplate(templateElement, {value: 1}); const p2 = templates.renderTemplate(templateElement, {value: 2}); - registerExtendedTemplate(win, templateElement.getAttribute('type'), - TemplateImpl); + registerExtendedTemplate( + win, + templateElement.getAttribute('type'), + TemplateImpl + ); // This is just a complicated way to say Promise -> all. - return p1.then(res1 => { - return p2.then(res2 => { - return [res1, res2]; + return p1 + .then(res1 => { + return p2.then(res2 => { + return [res1, res2]; + }); + }) + .then(res => { + expect(res[0].textContent).to.equal('abc1'); + expect(res[1].textContent).to.equal('abc2'); }); - }).then(res => { - expect(res[0].textContent).to.equal('abc1'); - expect(res[1].textContent).to.equal('abc2'); - }); }); it('should discover template via ID', () => { @@ -181,10 +216,11 @@ describes.fakeWin('Template', {amp: true}, env => { const parentElement = doc.createElement('div'); parentElement.setAttribute('template', id); doc.body.appendChild(parentElement); - return templates.findAndRenderTemplate(parentElement, {value: 1}).then( - res => { - expect(res.textContent).to.equal('abc1'); - }); + return templates + .findAndRenderTemplate(parentElement, {value: 1}) + .then(res => { + expect(res.textContent).to.equal('abc1'); + }); }); it('should require discovered template via ID to be "template"', () => { @@ -197,11 +233,14 @@ describes.fakeWin('Template', {amp: true}, env => { parentElement.setAttribute('template', id); doc.body.appendChild(parentElement); const regexError = new RegExp( - 'Template must be defined in a