diff --git a/.circleci/OWNERS b/.circleci/OWNERS index 002922bb9cc9..69a88bfe0386 100644 --- a/.circleci/OWNERS +++ b/.circleci/OWNERS @@ -4,7 +4,7 @@ { rules: [ { - owners: [{name: 'ampproject/wg-infra'}, {name: 'rsimha', notify: true}], + owners: [{name: 'ampproject/wg-infra'}], }, ], } diff --git a/.circleci/check_config.sh b/.circleci/check_config.sh index 3732eeeef7c5..f2bce219d9b5 100755 --- a/.circleci/check_config.sh +++ b/.circleci/check_config.sh @@ -11,8 +11,8 @@ RED() { echo -e "\033[0;31m$1\033[0m"; } YELLOW() { echo -e "\033[0;33m$1\033[0m"; } CYAN() { echo -e "\033[0;36m$1\033[0m"; } -# Push builds are only run against the main branch and amp-release branches. -if [[ "$CIRCLE_BRANCH" == "main" || "$CIRCLE_BRANCH" =~ ^amp-release-* ]]; then +# Push builds are only run against the main, nightly, and amp-release branches. +if [[ "$CIRCLE_BRANCH" == "main" || "$CIRCLE_BRANCH" == "nightly" || "$CIRCLE_BRANCH" =~ ^amp-release-* ]]; then echo $(GREEN "Nothing to do because $CIRCLE_BRANCH is not a PR branch.") exit 0 fi diff --git a/.circleci/config.yml b/.circleci/config.yml index 0fa4cc316e17..9b22ac658fbe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,10 @@ version: 2.1 orbs: - browser-tools: circleci/browser-tools@1.2.3 - node: circleci/node@4.7.0 + browser-tools: circleci/browser-tools@1.5.1 + codecov: codecov/codecov@5.0.0 + macos: circleci/macos@2.5.2 + node: circleci/node@6.3.0 push_and_pr_builds: &push_and_pr_builds filters: @@ -10,13 +12,6 @@ push_and_pr_builds: &push_and_pr_builds ignore: - nightly -push_builds_only: &push_builds_only - filters: - branches: - only: - - main - - /^amp-release-.*$/ - release_builds_only: &release_builds_only filters: branches: @@ -24,14 +19,6 @@ release_builds_only: &release_builds_only - nightly - /^amp-release-.*$/ -pr_builds_only: &pr_builds_only - filters: - branches: - ignore: - - main - - /^amp-release-.*$/ - - nightly - experiment_job: &experiment_job parameters: exp: @@ -39,12 +26,30 @@ experiment_job: &experiment_job type: enum enum: ['A', 'B', 'C'] environment: - EXP: << parameters.exp >> + FLAVOR: experiment<< parameters.exp >> + +dist_job: &dist_job + parameters: + module: + description: 'Whether to build Module or Nomodule' + type: enum + enum: ['Module', 'Nomodule'] + purpose: + description: 'What is the downstream purpose of this build' + type: enum + enum: ['Test', 'Bundle Size'] + +test_types_job: &test_types_job + parameters: + test_type: + description: 'Which test type to run' + type: enum + enum: ['Unit', 'Integration', 'End-to-End'] executors: base-docker-small: docker: - - image: cimg/base:stable + - image: cimg/base:current resource_class: small node-docker-medium: docker: @@ -58,76 +63,118 @@ executors: docker: - image: cimg/node:lts-browsers resource_class: xlarge - jdk-docker-xlarge: + jdk-docker-2xlarge: docker: - - image: cimg/openjdk:17.0-node - resource_class: xlarge + - image: cimg/openjdk:21.0.5-node + resource_class: 2xlarge macos-medium: macos: - xcode: 12.4.0 - resource_class: medium + xcode: 15.3.0 + resource_class: macos.m1.medium.gen1 commands: checkout_repo: - parameters: - save-git-cache: - type: boolean - default: false steps: - restore_cache: - name: 'Restore Git Cache' + name: '♻️ Restore Git Cache' keys: - - git-cache-{{ arch }}-v2-{{ .Branch }}-{{ .Revision }} - - git-cache-{{ arch }}-v2-{{ .Branch }}- - - git-cache-{{ arch }}-v2- + - git-cache-{{ arch }}-v3-main-{{ .Revision }} + - git-cache-{{ arch }}-v3-main- + - git-cache-{{ arch }}-v3- - checkout - when: - condition: << parameters.save-git-cache >> + condition: + equal: ['main', << pipeline.git.branch >>] steps: + - run: + name: '🗜️ Garbage Collection for Git' + command: git gc --auto - save_cache: - name: 'Save Git Cache' - key: git-cache-{{ arch }}-v2-{{ .Branch }}-{{ .Revision }} + name: '💾 Save Git Cache' + key: git-cache-{{ arch }}-v3-main-{{ .Revision }} paths: - .git - setup_node_environment: - steps: - - node/install: - lts: true - install-npm: false - - node/install-packages setup_vm: + parameters: + is-initializing-job: + description: 'True when this is an initializing job, which would perform several caching and workspace setup steps' + type: boolean + default: false + node-version: + type: string + default: 'lts/*' steps: - - attach_workspace: - at: /tmp - - run: - name: 'Configure Temporary Workspace' - command: | - mv /tmp/workspace /tmp/restored-workspace - mkdir -p /tmp/workspace - - run: - name: 'Maybe Gracefully Halt' - command: /tmp/restored-workspace/maybe_gracefully_halt.sh - - checkout_repo + - unless: + condition: << parameters.is-initializing-job >> + steps: + - checkout_repo + - attach_workspace: + at: /tmp + - run: + name: '⚙️ Configure Temporary Workspace' + command: | + mv /tmp/workspace /tmp/restored-workspace + mkdir -p /tmp/workspace + - run: + name: '❓ Maybe Gracefully Halt' + command: /tmp/restored-workspace/maybe_gracefully_halt.sh + - run: + name: '⚙️ Configure Development Environment' + command: | + ./.circleci/fetch_merge_commit.sh + ./.circleci/restore_build_output.sh + cat ./build-system/test-configs/hosts | sudo tee -a /etc/hosts + - restore_cache: + name: '♻️ Restore nvm Cache' + keys: + - nvm-cache-{{ arch }}-v2-<< parameters.node-version >>- - run: - name: 'Configure Development Environment' - command: | - ./.circleci/fetch_merge_commit.sh - ./.circleci/restore_build_output.sh - cat ./build-system/test-configs/hosts | sudo tee -a /etc/hosts - - setup_node_environment + name: '♻️ Create .nvmrc file' + command: echo << parameters.node-version >> > .nvmrc + - node/install + - when: + condition: << parameters.is-initializing-job >> + steps: + - run: + name: '⚙️ Create nvm Cache Checksum File' + command: node -v > ~/.node-version + - save_cache: + name: '💾 Save nvm Cache' + key: nvm-cache-{{ arch }}-v2-<< parameters.node-version >>-{{ checksum "~/.node-version" }} + paths: + - ~/.nvm/.cache + - restore_cache: + name: '♻️ Restore node_modules/ Cache' + keys: + - node-modules-cache-{{ arch }}-v3-{{ checksum "package-lock.json" }} + - when: + condition: << parameters.is-initializing-job >> + steps: + - run: + name: '💿 Install npm Packages' + command: if [[ ! -d node_modules/ ]]; then npm ci; fi + - save_cache: + name: '💾 Save node_modules/ Cache' + key: node-modules-cache-{{ arch }}-v3-{{ checksum "package-lock.json" }} + paths: + - node_modules/ teardown_vm: steps: - persist_to_workspace: + name: '📁 Persist Temporary Workspace' root: /tmp paths: - workspace install_chrome: steps: - run: - name: 'Get Pinned Chrome Version' + name: '⚙️ Get Pinned Chrome Version' command: ./.circleci/get_pinned_chrome_version.sh + - run: + name: '💿 Update list of available apt packages' + command: sudo apt update - browser-tools/install-chrome: - chrome-version: ${CHROME_VERSION} + # chrome-version is set in ./.circleci/get_pinned_chrome_version.sh, see files for details. replace-existing: true - browser-tools/install-chromedriver install_firefox: @@ -136,25 +183,17 @@ commands: install_edge: steps: - run: - name: 'Install Microsoft Edge' + name: '💿 Install Microsoft Edge' command: ./.circleci/install_microsoft_edge.sh - enable_safari_automation: - steps: - - run: - name: 'Enable Safari Automation' - command: | - defaults write com.apple.Safari AllowRemoteAutomation 1 - defaults write com.apple.Safari IncludeDevelopMenu 1 - sudo safaridriver --enable store_test_output: steps: - - store_artifacts: - path: result-reports - store_test_results: + name: '⬆️ Store Test Results' path: result-reports store_filelist: steps: - store_artifacts: + name: '⬆️ Store Artifact filelist.txt' path: /tmp/filelist.txt skip_on_push_builds: steps: @@ -165,26 +204,34 @@ commands: value: << pipeline.git.branch >> steps: - run: - name: 'Skip Job on Push Builds' + name: '➡️ Skip Job on Push Builds' command: circleci-agent step halt jobs: initialize_repository: executor: - name: base-docker-small + name: node-docker-medium steps: - - checkout_repo: - save-git-cache: true + - checkout_repo - run: - name: 'Initialize Repository' + name: '⚙️ Initialize Repository' command: ./.circleci/initialize_repo.sh - run: - name: 'Check Config' + name: '🔍 Check Config' command: ./.circleci/check_config.sh - run: - name: 'Initialize Workspace' + name: '⚙️ Initialize Temporary Workspace' command: cp .circleci/maybe_gracefully_halt.sh /tmp/workspace + - setup_vm: + is-initializing-job: true - teardown_vm + initialize_mac_os: + executor: + name: macos-medium + steps: + - checkout_repo + - setup_vm: + is-initializing-job: true checks: executor: name: node-docker-medium @@ -192,7 +239,7 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Checks ⭐' + name: '⭐⭐⭐ Checks ⭐⭐⭐' command: node build-system/pr-check/checks.js - teardown_vm unminified_build: @@ -201,55 +248,37 @@ jobs: steps: - setup_vm - run: - name: '⭐ Unminified Build ⭐' + name: '⭐⭐⭐ Unminified Build ⭐⭐⭐' command: node build-system/pr-check/unminified-build.js - teardown_vm - nomodule_build_test: + dist: executor: name: node-docker-xlarge + <<: *dist_job + parallelism: 3 steps: - setup_vm - run: - name: '⭐ Nomodule Build ⭐' - command: node build-system/pr-check/nomodule-build.js + name: '⭐⭐⭐ << parameters.module >> Build (<< parameters.purpose >>) ⭐⭐⭐' + command: node build-system/pr-check/dist.js --type "<< parameters.module >> Build (<< parameters.purpose >>)" - teardown_vm - module_build_test: + dist_3p: executor: name: node-docker-xlarge + <<: *dist_job steps: - setup_vm + - when: + condition: + and: + - equal: ['Module', <>] + - equal: ['Test', <>] + steps: + # Required in the edge case where we need to run `amp visual-diff --empty` in this step. See build-system/pr-check/dist.js for details. + - install_chrome - run: - name: '⭐ Module Build ⭐' - command: node build-system/pr-check/module-build.js - - teardown_vm - nomodule_build_prod: - executor: - name: node-docker-xlarge - steps: - - setup_vm - - run: - name: '⭐ Nomodule Build ⭐' - command: node build-system/pr-check/bundle-size-nomodule-build.js - - teardown_vm - module_build_prod: - executor: - name: node-docker-xlarge - steps: - - setup_vm - - run: - name: '⭐ Module Build ⭐' - command: node build-system/pr-check/bundle-size-module-build.js - - teardown_vm - pr_deploy: - executor: - name: node-docker-medium - steps: - - setup_vm - - run: - name: '⭐ PR Deploy ⭐' - command: node build-system/pr-check/pr-deploy.js - - store_artifacts: - path: /tmp/artifacts/amp_nomodule_build.tar.gz + name: '⭐⭐⭐ << parameters.module >> 3p Build (<< parameters.purpose >>) ⭐⭐⭐' + command: node build-system/pr-check/dist.js --type "<< parameters.module >> 3p Build (<< parameters.purpose >>)" - teardown_vm bundle_size: executor: @@ -257,19 +286,19 @@ jobs: steps: - setup_vm - run: - name: '⭐ Bundle Size ⭐' + name: '⭐⭐⭐ Bundle Size ⭐⭐⭐' command: node build-system/pr-check/bundle-size.js - teardown_vm validator_tests: executor: - name: jdk-docker-xlarge + name: jdk-docker-2xlarge steps: - setup_vm - run: name: 'Install Validator Dependencies' command: ./.circleci/install_validator_dependencies.sh - run: - name: '⭐ Validator Tests ⭐' + name: '⭐⭐⭐ Validator Tests ⭐⭐⭐' command: node build-system/pr-check/validator-tests.js - store_test_output - teardown_vm @@ -278,8 +307,9 @@ jobs: name: node-docker-large steps: - setup_vm + - install_chrome - run: - name: '⭐ Visual Diff Tests ⭐' + name: '⭐⭐⭐ Visual Diff Tests ⭐⭐⭐' command: node build-system/pr-check/visual-diff-tests.js - store_test_output - teardown_vm @@ -291,20 +321,24 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Local Unit Tests ⭐' + name: '⭐⭐⭐ Local Unit Tests ⭐⭐⭐' command: node build-system/pr-check/unit-tests-local.js - store_test_output - teardown_vm all_unit_tests: executor: - name: node-docker-medium + name: node-docker-large parallelism: 6 steps: - setup_vm - install_chrome - run: - name: '⭐ All Unit Tests ⭐' + name: '⭐⭐⭐ All Unit Tests ⭐⭐⭐' command: node build-system/pr-check/unit-tests.js + - codecov/upload: + disable_search: true + files: test/coverage/lcov-unit.info + flags: unit_tests - store_test_output - store_filelist - teardown_vm @@ -315,8 +349,12 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Unminified Tests ⭐' + name: '⭐⭐⭐ Unminified Tests ⭐⭐⭐' command: node build-system/pr-check/unminified-tests.js + - codecov/upload: + disable_search: true + files: test/coverage/lcov-integration.info + flags: integration_tests - store_test_output - teardown_vm nomodule_tests: @@ -331,7 +369,7 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Nomodule Tests (<< parameters.config >>) ⭐' + name: '⭐⭐⭐ Nomodule Tests (<< parameters.config >>) ⭐⭐⭐' command: node build-system/pr-check/nomodule-tests.js --config=<< parameters.config >> - store_test_output - teardown_vm @@ -347,19 +385,19 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Module Tests (<< parameters.config >>) ⭐' + name: '⭐⭐⭐ Module Tests (<< parameters.config >>) ⭐⭐⭐' command: node build-system/pr-check/module-tests.js --config=<< parameters.config >> - store_test_output - teardown_vm end_to_end_tests: executor: - name: node-docker-medium + name: node-docker-large parallelism: 6 steps: - setup_vm - install_chrome - run: - name: '⭐ End-to-End Tests ⭐' + name: '⭐⭐⭐ End-to-End Tests ⭐⭐⭐' command: node build-system/pr-check/e2e-tests.js - store_test_output - store_filelist @@ -367,45 +405,38 @@ jobs: browser_tests_safari: executor: name: macos-medium + <<: *test_types_job steps: - setup_vm - - enable_safari_automation + - macos/install-rosetta + - macos/add-safari-permissions - run: - name: '⭐ Browser Tests (Safari) ⭐' - command: node build-system/pr-check/browser-tests.js --browser=safari + name: '⭐⭐⭐ << parameters.test_type >> Tests (Safari) ⭐⭐⭐' + command: node build-system/pr-check/browser-tests.js --browser=safari --type=<< parameters.test_type >> - store_test_output - teardown_vm browser_tests_firefox: executor: name: node-docker-medium + <<: *test_types_job steps: - setup_vm - install_firefox - run: - name: '⭐ Browser Tests (Firefox) ⭐' - command: node build-system/pr-check/browser-tests.js --browser=firefox + name: '⭐⭐⭐ << parameters.test_type >> Tests (Firefox) ⭐⭐⭐' + command: node build-system/pr-check/browser-tests.js --browser=firefox --type=<< parameters.test_type >> - store_test_output - teardown_vm browser_tests_edge: executor: name: node-docker-medium + <<: *test_types_job steps: - setup_vm - install_edge - run: - name: '⭐ Browser Tests (Edge) ⭐' - command: node build-system/pr-check/browser-tests.js --browser=edge - - store_test_output - - teardown_vm - performance_tests: - executor: - name: node-docker-xlarge - steps: - - setup_vm - - install_chrome - - run: - name: '⭐ Performance Tests ⭐' - command: node build-system/pr-check/performance-tests.js + name: '⭐⭐⭐ << parameters.test_type >> Tests (Edge) ⭐⭐⭐' + command: node build-system/pr-check/browser-tests.js --browser=edge --type=<< parameters.test_type >> - store_test_output - teardown_vm experiment_build: @@ -415,7 +446,7 @@ jobs: steps: - setup_vm - run: - name: '⭐ Experiment << parameters.exp >> Build ⭐' + name: '⭐⭐⭐ Experiment << parameters.exp >> Build ⭐⭐⭐' command: node build-system/pr-check/experiment-build.js --experiment=experiment<< parameters.exp >> - teardown_vm experiment_integration_tests: @@ -426,7 +457,7 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Experiment << parameters.exp >> Integration Tests ⭐' + name: '⭐⭐⭐ Experiment << parameters.exp >> Integration Tests ⭐⭐⭐' command: node build-system/pr-check/experiment-integration-tests.js --experiment=experiment<< parameters.exp >> - store_test_output - teardown_vm @@ -439,7 +470,7 @@ jobs: - setup_vm - install_chrome - run: - name: '⭐ Experiment << parameters.exp >> End-to-End Tests ⭐' + name: '⭐⭐⭐ Experiment << parameters.exp >> End-to-End Tests ⭐⭐⭐' command: node build-system/pr-check/experiment-e2e-tests.js --experiment=experiment<< parameters.exp >> - store_test_output - store_filelist @@ -457,13 +488,12 @@ jobs: type: enum enum: ['no-esm', 'esm'] environment: - EXP: << parameters.flavor >> FLAVOR: << parameters.flavor >> ESM: << parameters.esm >> steps: - setup_vm - run: - name: '⭐ amp release ⭐' + name: '⭐⭐⭐ amp release ⭐⭐⭐' command: node --unhandled-rejections=strict build-system/release-workflows/build-release.js - teardown_vm upload_release: @@ -472,11 +502,21 @@ jobs: steps: - setup_vm - run: - name: '⭐ Upload Release Artifacts ⭐' + name: '⭐⭐⭐ Upload Release Artifacts (to Cloudflare R2) ⭐⭐⭐' command: node --unhandled-rejections=strict build-system/release-workflows/upload-release.js - store_artifacts: + name: '⬆️ Upload Release Artifacts (to CircleCI)' path: /tmp/release.tar.gz - teardown_vm + trigger_promote: + executor: + name: base-docker-small + steps: + - setup_vm + - run: + name: '⭐⭐⭐ Trigger Promote Workflow ⭐⭐⭐' + command: node --unhandled-rejections=strict build-system/release-workflows/trigger-promote.js + - teardown_vm workflows: version: 2 @@ -486,6 +526,9 @@ workflows: - initialize_repository: name: 'Initialize Repository' <<: *push_and_pr_builds + - initialize_mac_os: + name: 'Initialize for Mac OS' + <<: *push_and_pr_builds - checks: name: 'Checks' <<: *push_and_pr_builds @@ -496,37 +539,32 @@ workflows: <<: *push_and_pr_builds requires: - 'Initialize Repository' - - nomodule_build_test: - name: 'Nomodule Build (Test)' - <<: *push_and_pr_builds - requires: - - 'Initialize Repository' - - module_build_test: - name: 'Module Build (Test)' - <<: *push_and_pr_builds - requires: - - 'Initialize Repository' - - nomodule_build_prod: - name: 'Nomodule Build (Prod)' + - dist: + matrix: + parameters: + module: ['Module', 'Nomodule'] + purpose: ['Test', 'Bundle Size'] + name: '⛓️ << matrix.module >> Build (<< matrix.purpose >>)' <<: *push_and_pr_builds requires: - 'Initialize Repository' - - module_build_prod: - name: 'Module Build (Prod)' + - dist_3p: + matrix: + parameters: + module: ['Module', 'Nomodule'] + purpose: ['Test', 'Bundle Size'] + name: '<< matrix.module >> 3p Build (<< matrix.purpose >>)' <<: *push_and_pr_builds requires: - 'Initialize Repository' - - pr_deploy: - name: 'PR Deploy' - <<: *pr_builds_only - requires: - - 'Nomodule Build (Test)' - bundle_size: name: 'Bundle Size' <<: *push_and_pr_builds requires: - - 'Nomodule Build (Prod)' - - 'Module Build (Prod)' + - '⛓️ Nomodule Build (Bundle Size)' + - 'Nomodule 3p Build (Bundle Size)' + - '⛓️ Module Build (Bundle Size)' + - 'Module 3p Build (Bundle Size)' - validator_tests: name: 'Validator Tests' <<: *push_and_pr_builds @@ -536,7 +574,10 @@ workflows: name: 'Visual Diff Tests' <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' + - '⛓️ Module Build (Test)' + - 'Module 3p Build (Test)' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - local_unit_tests: name: 'Local Unit Tests' <<: *push_and_pr_builds @@ -559,7 +600,8 @@ workflows: config: ['prod', 'canary'] <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - module_tests: name: 'Module Tests (<< matrix.config >>)' matrix: @@ -567,28 +609,45 @@ workflows: config: ['prod', 'canary'] <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' - - 'Module Build (Test)' + - '⛓️ Module Build (Test)' + - 'Module 3p Build (Test)' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - end_to_end_tests: name: '⛓️ End-to-End Tests' <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - browser_tests_safari: - name: 'Browser Tests (Safari)' + name: '<< matrix.test_type >> Tests (Safari)' + matrix: + parameters: + test_type: ['Unit', 'Integration', 'End-to-End'] <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' + - 'Initialize for Mac OS' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - browser_tests_firefox: - name: 'Browser Tests (Firefox)' + name: '<< matrix.test_type >> Tests (Firefox)' + matrix: + parameters: + test_type: ['Unit', 'Integration', 'End-to-End'] <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - browser_tests_edge: - name: 'Browser Tests (Edge)' + name: '<< matrix.test_type >> Tests (Edge)' + matrix: + parameters: + # Note: we can't run e2e tests on Edge. + test_type: ['Unit', 'Integration'] <<: *push_and_pr_builds requires: - - 'Nomodule Build (Test)' + - '⛓️ Nomodule Build (Test)' + - 'Nomodule 3p Build (Test)' - experiment_build: name: 'Experiment << matrix.exp >> Build' matrix: @@ -613,11 +672,6 @@ workflows: <<: *push_and_pr_builds requires: - 'Experiment << matrix.exp >> Build' - # TODO(wg-performance, #12128): This takes 30 mins and fails regularly. - # - performance_tests: - # <<: *push_builds_only - # requires: - # - 'Nomodule Build (Test)' 'Release': jobs: @@ -640,3 +694,10 @@ workflows: - amp_release context: - release-build-uploader + - trigger_promote: + name: 'Trigger Promote' + <<: *release_builds_only + requires: + - 'Upload Release' + context: + - release-build-uploader diff --git a/.circleci/get_pinned_chrome_version.sh b/.circleci/get_pinned_chrome_version.sh index e83d83981001..51b6ff9b3445 100755 --- a/.circleci/get_pinned_chrome_version.sh +++ b/.circleci/get_pinned_chrome_version.sh @@ -46,12 +46,9 @@ echo "$(GREEN "Chrome version history URL is") $(CYAN "${CHROME_VERSION_HISTORY_ # Determine the Chrome version # See https://developer.chrome.com/docs/versionhistory/guide echo "$(GREEN "Determining Chrome version...")" -CHROME_VERSION="$(curl -sS ${CHROME_VERSION_HISTORY_URL} | jq -r ".versions[]|.version" | grep -m 1 "${CHROME_MAJOR_VERSION}\.")" -if [[ -z "$CHROME_VERSION" ]]; then - echo "$(RED "Could not determine Chrome version")" - exit 1 -fi +CHROME_VERSION="$(curl -sS --retry 3 ${CHROME_VERSION_HISTORY_URL} | jq -r ".versions[]|.version" | grep -m 1 "${CHROME_MAJOR_VERSION}\.[[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+")" echo "$(GREEN "Chrome version is") $(CYAN "${CHROME_VERSION}")" -echo "export CHROME_VERSION=$CHROME_VERSION" >> $BASH_ENV +# Workaround for https://github.com/CircleCI-Public/browser-tools-orb/issues/70 +echo "export ORB_PARAM_CHROME_VERSION=$CHROME_VERSION" >> $BASH_ENV echo $(GREEN "Successfully determined pinned version of Chrome") diff --git a/.circleci/get_pr_number.sh b/.circleci/get_pr_number.sh index c77f38513fbf..b9cde864da92 100755 --- a/.circleci/get_pr_number.sh +++ b/.circleci/get_pr_number.sh @@ -9,8 +9,8 @@ err=0 GREEN() { echo -e "\033[0;32m$1\033[0m"; } YELLOW() { echo -e "\033[0;33m$1\033[0m"; } -# Push builds are only run against the main branch and amp-release branches. -if [[ "$CIRCLE_BRANCH" == "main" || "$CIRCLE_BRANCH" =~ ^amp-release-* ]]; then +# Push builds are only run against the main, nightly, and amp-release branches. +if [[ "$CIRCLE_BRANCH" == "main" || "$CIRCLE_BRANCH" == "nightly" || "$CIRCLE_BRANCH" =~ ^amp-release-* ]]; then echo $(GREEN "Nothing to do because $CIRCLE_BRANCH is not a PR branch.") # Warn if the build is linked to a PR on a different repo (known CircleCI bug). if [[ -n "$CIRCLE_PULL_REQUEST" && ! "$CIRCLE_PULL_REQUEST" =~ ^https://github.com/ampproject/amphtml* ]]; then diff --git a/.circleci/install_microsoft_edge.sh b/.circleci/install_microsoft_edge.sh index c7405d1fb295..99151e64a706 100755 --- a/.circleci/install_microsoft_edge.sh +++ b/.circleci/install_microsoft_edge.sh @@ -1,8 +1,7 @@ #!/bin/bash # -# Script used by AMP's CI builds to install Microsoft Edge Beta on CircleCI. +# Script used by AMP's CI builds to install Microsoft Edge Stable on CircleCI. # Reference: https://www.microsoftedgeinsider.com/en-us/download?platform=linux-deb -# TODO(rsimha): Switch from Beta to Stable once it's available. set -e @@ -13,7 +12,7 @@ curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microso sudo install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/ sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list' sudo rm microsoft.gpg -sudo apt update && sudo apt install microsoft-edge-beta -EDGE_BETA_BIN=`which microsoft-edge-beta` -echo "export EDGE_BETA_BIN=${EDGE_BETA_BIN}" >> $BASH_ENV +sudo apt update && sudo apt install microsoft-edge-stable +EDGE_STABLE_BIN=`which microsoft-edge-stable` +echo "export EDGE_STABLE_BIN=${EDGE_STABLE_BIN}" >> $BASH_ENV echo $(GREEN "Installation complete.") diff --git a/.circleci/install_validator_dependencies.sh b/.circleci/install_validator_dependencies.sh index 0bc77becfee2..e6e13bbcd490 100755 --- a/.circleci/install_validator_dependencies.sh +++ b/.circleci/install_validator_dependencies.sh @@ -13,7 +13,7 @@ sudo mv bazel.gpg /etc/apt/trusted.gpg.d/ echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list echo $(GREEN "Updating and installing apt packages...") -sudo apt update && sudo apt install bazel clang python3 python3-pip protobuf-compiler +sudo apt update && sudo apt install bazel-5.4.0 clang python3 python3-pip protobuf-compiler echo $(GREEN "Installing protobuf python module...") -pip3 install protobuf +pip3 install protobuf==3.19.4 diff --git a/.circleci/maybe_gracefully_halt.sh b/.circleci/maybe_gracefully_halt.sh index 4fe3ea84330d..656388355701 100755 --- a/.circleci/maybe_gracefully_halt.sh +++ b/.circleci/maybe_gracefully_halt.sh @@ -15,7 +15,7 @@ if ls /tmp/restored-workspace/.CI_GRACEFULLY_HALT_* 1>/dev/null 2>&1; then exit 0 fi -if [[ ${EXP} && ${EXP} != 'base' ]]; then +if [[ ${FLAVOR} && ${FLAVOR} != 'base' ]]; then # Extract the commit SHA. For PR jobs, this is written to .CIRCLECI_MERGE_COMMIT. if [[ -f /tmp/restored-workspace/.CIRCLECI_MERGE_COMMIT ]]; then COMMIT_SHA="$(cat /tmp/restored-workspace/.CIRCLECI_MERGE_COMMIT)" @@ -24,9 +24,9 @@ if [[ ${EXP} && ${EXP} != 'base' ]]; then fi # Do not proceed if the experiment config is missing a valid name, constant, or date. - EXPERIMENT_JSON=$(curl -sS "https://raw.githubusercontent.com/ampproject/amphtml/${COMMIT_SHA}/build-system/global-configs/experiments-config.json" | jq ".experiment${EXP}") + EXPERIMENT_JSON=$(curl -sS "https://raw.githubusercontent.com/ampproject/amphtml/${COMMIT_SHA}/build-system/global-configs/experiments-config.json" | jq ".${FLAVOR}") if ! echo "${EXPERIMENT_JSON}" | jq -e '.name,.define_experiment_constant,.expiration_date_utc'; then - echo $(YELLOW "Experiment ${EXP} is misconfigured, or does not exist.") + echo $(YELLOW "Flavor ${FLAVOR} is misconfigured, or does not exist.") echo $(GREEN "Gracefully halting this job") circleci-agent step halt exit 0 @@ -36,7 +36,7 @@ if [[ ${EXP} && ${EXP} != 'base' ]]; then CURRENT_TIMESTAMP=$(date --utc +'%s') EXPERIMENT_EXPIRATION_TIMESTAMP=$(date --utc --date $(echo "${EXPERIMENT_JSON}" | jq -er '.expiration_date_utc') +'%s') if [[ $CURRENT_TIMESTAMP -gt $EXPERIMENT_EXPIRATION_TIMESTAMP ]]; then - echo $(YELLOW "Experiment ${EXP} is expired.") + echo $(YELLOW "Flavor ${FLAVOR} is expired.") echo $(GREEN "Gracefully halting this job") circleci-agent step halt exit 0 diff --git a/.circleci/restore_build_output.sh b/.circleci/restore_build_output.sh index e94fbc0a1793..688d989dcba4 100755 --- a/.circleci/restore_build_output.sh +++ b/.circleci/restore_build_output.sh @@ -25,13 +25,6 @@ if [[ -d "${WORKSPACE_DIR}/builds" ]]; then rsync -a "${RESTORED_DIR}/" "./${OUTPUT_DIR}" fi done - # Bento components are compiled inside the extension source file. - for RESTORED_COMPONENT_DIR in ${RESTORED_BUILD_DIR}/extensions/*/?.?/dist; do - OUTPUT_DIR=${RESTORED_COMPONENT_DIR##$RESTORED_BUILD_DIR/} - echo "*" $(GREEN "Merging") $(CYAN "${RESTORED_COMPONENT_DIR}") $(GREEN "into") $(CYAN "./${OUTPUT_DIR}") - mkdir -p $RESTORED_DIR - rsync -a "${RESTORED_COMPONENT_DIR}/" "./${OUTPUT_DIR}" - done done else echo $(YELLOW "Workspace does not contain any build outputs to restore") diff --git a/.eslintplugin.js b/.eslint-plugin-local.js similarity index 100% rename from .eslintplugin.js rename to .eslint-plugin-local.js diff --git a/.eslintignore b/.eslintignore index e5e331241b6b..3ff512febbea 100644 --- a/.eslintignore +++ b/.eslintignore @@ -19,8 +19,6 @@ validator/** .*cache .amp-dep-check build -build-system/tasks/performance/cache -build-system/tasks/performance/results.json dist dist.3p dist.tools diff --git a/.eslintrc.js b/.eslintrc.js index 5457c9ae8f8a..35cfcc7747ef 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,12 @@ const fs = require('fs'); +const { + getImportResolver, +} = require('./build-system/babel-config/import-resolver'); const { forbiddenTermsGlobal, forbiddenTermsSrcInclusive, } = require('./build-system/test-configs/forbidden-terms'); -const { - getImportResolver, -} = require('./build-system/babel-config/import-resolver'); const importAliases = getImportResolver().alias; @@ -28,7 +28,7 @@ function getExperimentGlobals() { module.exports = { 'root': true, - 'parser': '@babel/eslint-parser', + 'parser': '@typescript-eslint/parser', 'plugins': [ 'chai-expect', 'import', @@ -39,24 +39,25 @@ module.exports = { 'react', 'react-hooks', 'sort-destructure-keys', - 'sort-requires', + '@typescript-eslint', ], 'env': { 'es6': true, 'browser': true, }, 'parserOptions': { - 'ecmaVersion': 6, 'jsx': true, 'sourceType': 'module', }, 'globals': { ...getExperimentGlobals(), 'IS_ESM': 'readonly', + 'IS_SSR_CSS': 'readonly', 'IS_SXG': 'readonly', 'IS_MINIFIED': 'readonly', 'IS_PROD': 'readonly', 'INTERNAL_RUNTIME_VERSION': 'readonly', + 'AMP_STORY_SUPPORTED_LANGUAGES': 'readonly', 'AMP': 'readonly', 'context': 'readonly', 'global': 'readonly', @@ -165,7 +166,7 @@ module.exports = { 'local/await-expect': 2, 'local/camelcase': 2, 'local/closure-type-primitives': 2, - 'local/dict-string-keys': 2, + 'local/core-dom-jsx': 2, 'local/enums': 2, 'local/get-mode-usage': 2, 'local/html-template': 2, @@ -176,7 +177,6 @@ module.exports = { 'local/no-bigint': 2, 'local/no-deep-destructuring': 2, 'local/no-duplicate-import': 2, - 'local/no-duplicate-name-typedef': 2, 'local/no-dynamic-import': 2, 'local/no-es2015-number-props': 2, 'local/no-export-side-effect': 2, @@ -197,7 +197,6 @@ module.exports = { 'local/no-mixed-interpolation': 2, 'local/no-mixed-operators': 2, 'local/no-module-exports': 2, - 'local/no-static-this': 2, 'local/no-style-display': 2, 'local/no-style-property-setting': 2, 'local/no-swallow-return-from-allow-console-error': 2, @@ -255,12 +254,39 @@ module.exports = { 'no-native-reassign': 2, 'no-redeclare': 2, 'no-restricted-globals': [2, 'error', 'event', 'Animation'], + 'no-restricted-syntax': [ + 2, + // Ban all TS features that don't have a direct ECMAScript equivalent. + { + 'selector': 'TSEnumDeclaration', + 'message': 'Enums are banned.', + }, + { + 'selector': 'TSModuleDeclaration', + 'message': 'Namespaces are banned.', + }, + { + 'selector': 'TSParameterProperty', + 'message': 'Parameter properties are banned.', + }, + { + 'selector': 'Decorator', + 'message': 'Decorators are banned.', + }, + { + 'selector': 'PropertyDefinition[declare="false"]:not([value])', + 'message': + 'Class properties should be declared or initialized. ' + + 'See https://github.com/ampproject/amphtml/pull/37387#discussion_r791232943', + }, + ], 'no-script-url': 2, 'no-self-compare': 2, 'no-sequences': 2, 'no-throw-literal': 2, 'no-unused-expressions': 0, - 'no-unused-vars': [ + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ 2, { 'argsIgnorePattern': '^(var_args$|opt_|unused)', @@ -299,9 +325,7 @@ module.exports = { ], 'sort-destructure-keys/sort-destructure-keys': 2, 'import/order': [ - // Disabled for now, so individual folders can opt-in one PR at a time and - // minimize disruption/merge conflicts - 0, + 2, { // Split up imports groups with exactly one newline 'newlines-between': 'always', @@ -341,9 +365,19 @@ module.exports = { 'ignoreDeclarationSort': true, }, ], - 'sort-requires/sort-requires': 2, }, 'overrides': [ + { + 'files': ['**/*.ts', '**/*.tsx'], + 'rules': { + 'require-jsdoc': 0, + 'jsdoc/require-param': 0, + 'jsdoc/require-param-type': 0, + 'jsdoc/require-returns': 0, + 'no-undef': 0, + 'import/no-unresolved': 0, + }, + }, { 'files': [ 'test/**/*.js', @@ -432,7 +466,7 @@ module.exports = { 'rules': { 'no-var': 0, 'no-undef': 0, - 'no-unused-vars': 0, + '@typescript-eslint/no-unused-vars': 0, 'prefer-const': 0, 'require-jsdoc': 0, 'jsdoc/check-tag-names': 0, @@ -452,13 +486,18 @@ module.exports = { }, }, { - 'files': ['3p/**/*.js', 'src/**/*.js', 'test/**/*.js', 'testing/**/*.js'], - 'rules': {'import/order': 2}, + 'files': ['build-system/**/*.js'], + 'rules': {'import/order': 0}, }, { - 'files': ['src/preact/**', 'extensions/**/1.0/**', '**/storybook/**'], + 'files': ['extensions/**/1.0/**', 'src/preact/**', '**/storybook/**'], 'rules': {'local/preact-preferred-props': 2}, }, + { + // src/preact can directly import from 'preact' without issue. + 'files': ['src/preact/**'], + 'rules': {'local/no-import': 0}, + }, { // Files that use JSX for plain DOM nodes instead of Preact 'files': [ @@ -469,5 +508,10 @@ module.exports = { ], 'rules': {'local/preact': [2, '#core/dom/jsx']}, }, + { + // Allow sinon stub for release tagger tests + 'files': ['build-system/release-tagger/test/**'], + 'rules': {'local/no-forbidden-terms': 0}, + }, ], }; diff --git a/.github/ISSUE_TEMPLATE/bento-component-tracker.yml b/.github/ISSUE_TEMPLATE/bento-component-tracker.yml deleted file mode 100644 index 2b939957e9b9..000000000000 --- a/.github/ISSUE_TEMPLATE/bento-component-tracker.yml +++ /dev/null @@ -1,97 +0,0 @@ ---- -name: Bento Component Tracker -description: Track a new Bento component. -labels: 'WG: bento' -title: "\U0001F371 [amp-component-name:version] Bento tracking issue" -body: - - type: markdown - id: header - attributes: - value: | - This is a tracker meant to be used by AMP developers working on new Bento components. - - See AMP's [documentation](https://github.com/ampproject/amphtml/blob/main/docs/building-a-bento-amp-extension.md) to learn about how to write a Bento component. - - See [additional documentation](https://github.com/ampproject/amphtml/blob/main/docs/building-a-bento-video-player.md) for considerations when building a Bento video player. - - See past [tracking issues](https://github.com/ampproject/amphtml/issues?q=is%3Aopen+label%3A%22WG%3A+bento%22+%22bento+tracking+issue%22) for information about other Bento components. - - type: textarea - id: requirements - attributes: - label: High-Level Requirements - description: Matrix of considerations to be addressed. - value: | - - - - _To be updated as information becomes available._ - - | Consideration | Ready When | Status | - | ----------------------- | ------------------------------------------ | ------- | - | Component Compatibility | Feature checklist is covered | ✅ / ✖️ | - | Testing | Unit tests and pre-existing e2e tests pass | ✅ / ✖️ | - | Internationalization | | ✅ / ✖️ | - | Analytics | | ✅ / ✖️ | - | Accessibility | Audit performed | ✅ / ✖️ | - | Page Experience | Audit performed | ✅ / ✖️ | - | Documentation | Written | ✅ / ✖️ | - | Storybook | Samples written in Preact and AMP | ✅ / ✖️ | - validations: - required: true - - type: textarea - id: feature_checklist - attributes: - label: Feature Checklist - description: Checklist of features of the component. - value: | - _To be updated as information becomes available._ - - ### Existing attributes - - [ ] `attribute 1` - - [ ] `attribute 2` - - [ ] `attribute 3` - - ### Actions - - [ ] `action 1` - - [ ] `action 2` - - [ ] `action 3` - - ### Events - - [ ] `event 1` - - [ ] `event 2` - - [ ] `event 3` - - ### Analytics Events - - [ ] `analytics event 1` - - [ ] `analytics event 2` - - [ ] `analytics event 3` - validations: - required: true - - type: textarea - id: migration_notes - attributes: - label: Migration Notes - description: Relevant information for document authors using the classic version of the component. - placeholder: Provide information about any API changes that might prevent document authors from upgrading a classic version component to the new Bento version. - validations: - required: true - - type: textarea - id: open_tasks - attributes: - label: Open Tasks - description: Checklist of tasks to be completed before this component can be shipped. - value: | - _To be updated as information becomes available._ - - - [ ] open task 1 - - [ ] open task 2 - - [ ] open task 3 - - [ ] open task 4 - - [ ] open task 5 - validations: - required: true - - type: textarea - id: notifications - attributes: - label: Notifications - description: Add working groups or individuals you want to notify about this component. - value: /cc @ampproject/wg-bento - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/cherry-pick-request.yml b/.github/ISSUE_TEMPLATE/cherry-pick-request.yml index 905641ec05b5..feec1cd320d1 100644 --- a/.github/ISSUE_TEMPLATE/cherry-pick-request.yml +++ b/.github/ISSUE_TEMPLATE/cherry-pick-request.yml @@ -36,11 +36,10 @@ body: validations: required: true - type: input - id: release_tracker + id: amp_versions attributes: - label: Release Tracker(s) - description: The [tracker issue(s)](https://github.com/ampproject/amphtml/labels/Type%3A%20Release) for the release to which the cherry-pick will be applied. Remember to update the issue title with this info. - placeholder: 'e.g. #44444, #55555' + label: AMP Version(s) + description: The AMP version(s) to which the cherry-pick will be applied. validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/ga4-bug-report.yml b/.github/ISSUE_TEMPLATE/ga4-bug-report.yml new file mode 100644 index 000000000000..58f6603627f0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ga4-bug-report.yml @@ -0,0 +1,74 @@ +--- +name: Google Analytics 4 and AMP Bug Report +description: Report a bug in GA4 and AMP integration. +labels: ['Type: Bug', 'Component: amp-analytics', 'Component: GA4'] +title: "\U0001F41B Google Analytics 4 Related Bug: " +body: + - type: markdown + id: header + attributes: + value: | + Thanks for filling out this bug report. Bugs related to Google Analytics 4 and amp-analytics can be reported using the form below. + - Bugs related to the [AMP](https://amp.dev) format and cache can be reported using the form below. + - Bugs related to the [AMP WordPress Plugin](https://wordpress.org/plugins/amp/) can be reported at the [support forum](https://wordpress.org/support/plugin/amp/) or at the [`amp-wp`](https://github.com/ampproject/amp-wp/issues) repository. + - Questions about AMP uage can be asked at the [`#using-amp`](https://amphtml.slack.com/archives/C9HPA6HGB) Slack channel or at the [`amp-html`](http://stackoverflow.com/questions/tagged/amp-html) tag at Stack Overflow. + - Questions about Google Search can be asked at Google's [Help Community](https://goo.gl/utQ1KZ). + - type: textarea + id: description + attributes: + label: Description + description: A brief description of the bug. + placeholder: Describe the expected vs. the current behavior, so this issue can be directed to the correct working group for investigation. + validations: + required: true + - type: input + id: example + attributes: + label: URL where we can debug and reproduce the problem + description: If applicable, give an example web page where we can reproduce the problem. + placeholder: e.g. https://amp-dev.cdn.ampproject.org/c/s/amp.dev/ + - type: textarea + id: repro_steps + attributes: + label: Reproduction Steps + description: Step-by-step instructions for reproducing the issue. + placeholder: Provide a publicly accessible URL and a reduced set of steps that clearly demonstrate the issue. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant Logs + description: Relevant logging output. + placeholder: Paste any plain-text logs here (e.g. console warnings or errors from Chrome DevTools). They will automatically be formatted as code. + render: shell + - type: dropdown + id: browsers + attributes: + label: Browser(s) Affected + description: If applicable, specify which browser(s) are affected. Select one or more options below. + multiple: true + options: + - Chrome + - Firefox + - Safari + - Edge + - UC Browser + - type: input + id: operating_systems + attributes: + label: OS(s) Affected + description: If applicable, specify which operating system(s) are affected. + placeholder: e.g. Android 11 + - type: input + id: devices + attributes: + label: Device(s) Affected + description: If applicable, specify which device(s) are affected. + placeholder: e.g. Pixel 3 + - type: input + id: version + attributes: + label: AMP Version Affected + description: If applicable, specify which version is affected, in the format YYMMDDHHMMXXX. + placeholder: e.g. 2101280515000 diff --git a/.github/ISSUE_TEMPLATE/release-tracker.yml b/.github/ISSUE_TEMPLATE/release-tracker.yml deleted file mode 100644 index 6f4b60ad5fb0..000000000000 --- a/.github/ISSUE_TEMPLATE/release-tracker.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Release Tracker -description: Manually track a new AMP release. (This is automatically updated by the release-tagger GitHub Action). -labels: 'Type: Release' -title: "\U0001F684 Release VERSION" -body: - - type: textarea - id: amp_version - attributes: - label: AMP Version - description: Replace VERSION with AMP version. - value: | - [VERSION](https://github.com/ampproject/amphtml/releases/tag/VERSION) - validations: - required: true - - type: textarea - id: promotions - attributes: - label: Promotions - description: Replace `VERSION` with AMP version and `` with CL submit time. - value: | - - [ ] VERSION promoted to Experimental and Beta (opt-in) channels - - [ ] VERSION promoted to Experimental and Beta (1% traffic) channels - - [ ] VERSION promoted to Stable channel - - [ ] VERSION promoted to LTS channel - - /cc @ampproject/release-on-duty - validations: - required: true diff --git a/.github/codeql/config.yml b/.github/codeql/config.yml new file mode 100644 index 000000000000..9cef4632165b --- /dev/null +++ b/.github/codeql/config.yml @@ -0,0 +1,2 @@ +paths-ignore: + - 'extensions/**/test/**' diff --git a/.github/create_issue_on_error.md b/.github/create_issue_on_error.md new file mode 100644 index 000000000000..2993ac83cf8f --- /dev/null +++ b/.github/create_issue_on_error.md @@ -0,0 +1,8 @@ +--- +title: 'Workflow "{{ env.WORKFLOW_NAME }}" failed (run_id: {{ env.RUN_ID }})' +--- + +The workflow "**{{ env.WORKFLOW_NAME }}**" failed. See logs: +https://github.com/{{ env.REPO_SLUG }}/actions/runs/{{ env.RUN_ID }} + +// cc: {{ env.MENTION }} — please triage and resolve this issue diff --git a/.github/stale.yml b/.github/stale.yml index b8ae45e3fa44..d81b2367de32 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,4 +1,4 @@ -daysUntilStale: 540 # 1.5Y - 7d +daysUntilStale: 358 # 1Y - 7d daysUntilClose: 7 staleLabel: Stale markComment: > diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000000..09160f77f7d0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,49 @@ +name: 'CodeQL' + +on: + push: + branches: ['main'] + pull_request: + # The branches below must be a subset of the branches above + branches: ['main'] + schedule: + - cron: '0 0 * * 1' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + # TODO: add `cpp` for Validator; don't forget to add a build step + language: ['go', 'javascript', 'python', 'typescript'] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - name: Checkout repository + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + with: + config-file: ./.github/codeql/config.yml + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/cross-platform-builds.yml b/.github/workflows/cross-platform-builds.yml index 221996e84cce..75e9836b916a 100644 --- a/.github/workflows/cross-platform-builds.yml +++ b/.github/workflows/cross-platform-builds.yml @@ -5,6 +5,9 @@ on: branches: - main +permissions: + contents: read + jobs: compile: if: github.repository == 'ampproject/amphtml' @@ -12,15 +15,48 @@ jobs: matrix: platform: [ubuntu, macos, windows] flavor: [Build, Dist] + fail-fast: false runs-on: ${{ matrix.platform }}-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + - name: Checkout Repo - uses: actions/checkout@v2 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set Up Node - uses: actions/setup-node@v2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: - node-version: 16 + node-version: lts/* - name: Install Dependencies run: bash ./.github/workflows/install_dependencies.sh - name: ${{ matrix.flavor }} run: node build-system/pr-check/cross-platform-builds.js --flavor=${{ matrix.flavor }} + + create-issue-on-error: + if: failure() + needs: compile + permissions: + contents: read + issues: write + runs-on: ubuntu-latest + environment: create_issue_on_error + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - name: Create issue on error + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + with: + filename: .github/create_issue_on_error.md + env: + GITHUB_TOKEN: ${{ secrets.AMPPROJECTBOT }} + WORKFLOW_NAME: ${{ github.workflow }} + MENTION: '@ampproject/release-on-duty' + REPO_SLUG: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} diff --git a/.github/workflows/cut-nightly.yml b/.github/workflows/cut-nightly.yml index cb3a60dd997a..f053b0cc0f40 100644 --- a/.github/workflows/cut-nightly.yml +++ b/.github/workflows/cut-nightly.yml @@ -6,24 +6,33 @@ on: schedule: # 1 a.m. PST / 12 a.m. PDT, Tuesdays through Saturdays. - cron: '0 8 * * 2-6' + workflow_dispatch: + +permissions: + contents: read jobs: cut-nightly: - environment: release_tagger + environment: cut_nightly if: github.repository == 'ampproject/amphtml' name: Cut Nightly Branch runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + - name: Checkout Repo - uses: actions/checkout@v2 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 100 - name: Set Up Node - uses: actions/setup-node@v2 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: - node-version: 16 + node-version: lts/* - name: Set Up Environment run: sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share} @@ -35,3 +44,30 @@ jobs: run: node --unhandled-rejections=strict build-system/release-workflows/cut-nightly.js env: GITHUB_TOKEN: ${{ secrets.AMPPROJECTBOT }} + + create-issue-on-error: + if: failure() + needs: cut-nightly + permissions: + contents: read + issues: write + runs-on: ubuntu-latest + environment: create_issue_on_error + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - name: Create issue on error + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + with: + filename: .github/create_issue_on_error.md + env: + GITHUB_TOKEN: ${{ secrets.AMPPROJECTBOT }} + WORKFLOW_NAME: ${{ github.workflow }} + MENTION: '@ampproject/release-on-duty' + REPO_SLUG: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000000..9ad36ee2b313 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,27 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - name: 'Checkout Repository' + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - name: 'Dependency Review' + uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml deleted file mode 100644 index a50f8cd6a726..000000000000 --- a/.github/workflows/publish-npm-packages.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Publish Bento Packages on npm -on: - workflow_dispatch: - inputs: - ampversion: - description: 'AMP version' - required: true - tag: - description: 'npm package tag (latest | nightly)' - required: true -env: - REPO: 'https://raw.githubusercontent.com/ampproject/amphtml/main' - DIR: 'build-system/npm-publish' -jobs: - setup: - runs-on: ubuntu-latest - outputs: - extensions: ${{ steps.get-extensions.outputs.extensions }} - steps: - - name: Print inputs - run: | - echo "AMP version: ${{ github.event.inputs.ampversion }}" - echo "npm tag: ${{ github.event.inputs.tag }}" - - uses: actions/checkout@v2 - with: - ref: ${{ github.events.inputs.ampversion }} - - name: Get latest scripts - run: wget -q "${{ env.REPO }}/${{ env.DIR }}/get-extensions.js" -O ${{ env.DIR }}/get-extensions.js - - name: Get extensions to publish - id: get-extensions - run: | - EXTENSIONS=$(node ${{ env.DIR }}/get-extensions.js) - echo "::set-output name=extensions::{\"include\":${EXTENSIONS}}" - publish: - if: github.repository == 'ampproject/amphtml' - environment: bento_publishing - needs: setup - runs-on: ubuntu-latest - strategy: - matrix: ${{ fromJson(needs.setup.outputs.extensions) }} - fail-fast: false - steps: - - uses: actions/checkout@v2 - with: - ref: ${{ github.events.inputs.ampversion }} - - uses: actions/setup-node@v2 - with: - registry-url: https://registry.npmjs.org - node-version: 16 - - name: Install dependencies - run: bash ./.github/workflows/install_dependencies.sh - - name: Get latest scripts - run: | - wget -q "${{ env.REPO }}/${{ env.DIR }}/utils.js" -O ${{ env.DIR }}/utils.js - wget -q "${{ env.REPO }}/${{ env.DIR }}/build-npm-binaries.js" -O ${{ env.DIR }}/build-npm-binaries.js - wget -q "${{ env.REPO }}/${{ env.DIR }}/write-package-files.js" -O ${{ env.DIR }}/write-package-files.js - - name: Build package - run: | - node ${{ env.DIR }}/build-npm-binaries.js ${{ matrix.extension }} - node ${{ env.DIR }}/write-package-files.js ${{ matrix.extension }} ${{ github.event.inputs.ampversion }} ${{ matrix.version }} - - name: Publish package for Nightly releases - if: github.event.inputs.tag != 'latest' - run: npm publish ./extensions/${{ matrix.extension }}/${{ matrix.version }} --access public --tag ${{ github.event.inputs.tag }} - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Tag package as latest for Stable releases - if: github.event.inputs.tag == 'latest' - run: | - NPM_PACKAGE=$(jq -r .name ./extensions/${{ matrix.extension }}/${{ matrix.version }}/package.json) - NPM_VERSION=$(jq -r .version ./extensions/${{ matrix.extension }}/${{ matrix.version }}/package.json) - echo "Processing ${NPM_PACKAGE}@${NPM_VERSION}" - if [[ $(npm view ${NPM_PACKAGE}@${NPM_VERSION}) ]]; then - npm dist-tag add ${NPM_PACKAGE}@${NPM_VERSION} latest - else - npm publish ./extensions/${{ matrix.extension }}/${{ matrix.version }} --access public --tag latest - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-tagger.yml b/.github/workflows/release-tagger.yml index 8f87865c9215..13c821d93ffe 100644 --- a/.github/workflows/release-tagger.yml +++ b/.github/workflows/release-tagger.yml @@ -14,21 +14,26 @@ on: channel: description: 'release channel (beta-opt-in | beta-percent | stable | lts)' required: true - time: - description: 'action time' - required: true sha: description: 'commit sha' required: true +permissions: + contents: read + jobs: tagger: environment: release_tagger runs-on: ubuntu-latest steps: - - uses: actions/setup-node@v2 + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: - node-version: 16 - - uses: actions/checkout@v2 + node-version: lts/* + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - run: bash ./.github/workflows/install_dependencies.sh - name: Run tagger run: | @@ -37,7 +42,33 @@ jobs: --head ${{ github.event.inputs.head }} \ --base ${{ github.event.inputs.base }} \ --channel ${{ github.event.inputs.channel }} \ - --time ${{ github.event.inputs.time }} \ --sha ${{ github.event.inputs.sha }} env: GITHUB_TOKEN: ${{ secrets.AMPPROJECTBOT }} + + create-issue-on-error: + if: failure() + needs: tagger + permissions: + contents: read + issues: write + runs-on: ubuntu-latest + environment: create_issue_on_error + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - name: Create issue on error + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + with: + filename: .github/create_issue_on_error.md + env: + GITHUB_TOKEN: ${{ secrets.AMPPROJECTBOT }} + WORKFLOW_NAME: ${{ github.workflow }} + MENTION: '@ampproject/wg-infra' + REPO_SLUG: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..81784684028b --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,77 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '16 14 * * 1' + push: + branches: ['main'] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - name: 'Checkout code' + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + with: + persist-credentials: false + + - name: 'Run analysis' + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: 'Upload artifact' + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: 'Upload to code-scanning' + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + with: + sarif_file: results.sarif diff --git a/.github/workflows/status-page.yml b/.github/workflows/status-page.yml index 59bf3f2f4266..1d126e19f1c0 100644 --- a/.github/workflows/status-page.yml +++ b/.github/workflows/status-page.yml @@ -4,16 +4,24 @@ on: types: [opened] issue_comment: types: [edited] +permissions: + contents: read + jobs: status-page: if: contains(github.event.issue.title, '🌸 Cherry-pick request') runs-on: ubuntu-latest environment: status_page steps: - - uses: actions/setup-node@v2 + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: - node-version: 16 - - uses: actions/checkout@v2 + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + with: + node-version: lts/* + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Add progress comment to cherry-pick issue for Stable and LTS if: github.event_name == 'issues' && github.event.action == 'opened' run: | @@ -33,3 +41,30 @@ jobs: COMMENT_AFTER: ${{ github.event.comment.body }} STATUS_PAGE_ID: ${{ secrets.STATUS_PAGE_ID }} STATUS_PAGE_TOKEN: ${{ secrets.STATUS_PAGE_TOKEN }} + + create-issue-on-error: + if: failure() + needs: status-page + permissions: + contents: read + issues: write + runs-on: ubuntu-latest + environment: create_issue_on_error + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - name: Create issue on error + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + with: + filename: .github/create_issue_on_error.md + env: + GITHUB_TOKEN: ${{ secrets.AMPPROJECTBOT }} + WORKFLOW_NAME: ${{ github.workflow }} + MENTION: '@ampproject/wg-infra' + REPO_SLUG: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} diff --git a/.github/workflows/sweep-experiments.yml b/.github/workflows/sweep-experiments.yml deleted file mode 100644 index 4d093ff48e9e..000000000000 --- a/.github/workflows/sweep-experiments.yml +++ /dev/null @@ -1,79 +0,0 @@ -# Executes `amp sweep-experiments` on a schedule. -# If experiments are swept, a PR is created. - -name: Sweep Experiments - -on: - schedule: - # First day of the month at 00:00:00 - - cron: '0 0 1 * *' - -jobs: - sweep-experiments: - if: github.repository == 'ampproject/amphtml' - name: Sweep Experiments - runs-on: ubuntu-latest - - steps: - - name: Checkout Repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set Up Node - uses: actions/setup-node@v2 - with: - node-version: 16 - - - name: Set Up Environment - run: sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share} - - - name: Install Dependencies - run: npm ci - - - name: Sweep - id: sweep - run: | - git config --global user.name "${GITHUB_ACTOR}" - git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" - - amp sweep-experiments - - title=$(git log -1 --format=%s) - title="${title//'%'/'%25'}" - title="${title//$'\n'/'%0A'}" - title="${title//$'\r'/'%0D'}" - echo "::set-output name=title::$(echo "$title")" - - body=$(git log -1 --format=%b) - body="${body//'%'/'%25'}" - body="${body//$'\n'/'%0A'}" - body="${body//$'\r'/'%0D'}" - echo "::set-output name=body::$(echo "$body")" - - hash=$(git log -1 --format=%h) - hash="${hash//'%'/'%25'}" - hash="${hash//$'\n'/'%0A'}" - hash="${hash//$'\r'/'%0D'}" - echo "::set-output name=branch::$(echo "sweep-experiments-${hash}")" - - - name: Create Pull Request - id: pull-request - uses: peter-evans/create-pull-request@v3 - with: - draft: true - title: ${{ steps.sweep.outputs.title }} - body: ${{ steps.sweep.outputs.body }} - branch: ${{ steps.sweep.outputs.branch }} - - - name: Comment on Pull Request - if: ${{ steps.pull-request.outputs.pull-request-number }} - uses: peter-evans/create-or-update-comment@v1 - with: - issue-number: ${{ steps.pull-request.outputs.pull-request-number }} - body: | - You may checkout this pull request to follow-up manually: - - ``` - git checkout -t upstream/${{ steps.sweep.outputs.branch }} - ``` diff --git a/.github/workflows/sync-client-side-experiments-config-to-cdn.yml b/.github/workflows/sync-client-side-experiments-config-to-cdn.yml deleted file mode 100644 index fc7a99eb3915..000000000000 --- a/.github/workflows/sync-client-side-experiments-config-to-cdn.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: CDN config sync - -on: - push: - branches: - - main - paths: - - build-system/global-configs/client-side-experiments-config.json - -jobs: - sync: - if: github.repository == 'ampproject/amphtml' - name: client-side-experiments-config.json - runs-on: ubuntu-latest - environment: wrangler - - steps: - - name: Checkout Repo - uses: actions/checkout@v2 - with: - fetch-depth: 1 - - - name: Install Dependencies - run: npm i -g @cloudflare/wrangler - - - name: ⭐ Sync client-side-experiments-config.json to the CDN ⭐ - run: wrangler kv:key put AMP_EXP "$(cat build-system/global-configs/client-side-experiments-config.json)" --config .github/workflows/wrangler.toml --binding AMP_EXP - env: - CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} diff --git a/.github/workflows/sync-versioning-to-cdn.yml b/.github/workflows/sync-versioning-to-cdn.yml deleted file mode 100644 index ee3b965a9ab6..000000000000 --- a/.github/workflows/sync-versioning-to-cdn.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: CDN config sync - -on: - push: - branches: - - main - paths: - - build-system/global-configs/versioning.json - -jobs: - sync: - if: github.repository == 'ampproject/amphtml' - name: versioning.json - runs-on: ubuntu-latest - environment: wrangler - - steps: - - name: Checkout Repo - uses: actions/checkout@v2 - with: - fetch-depth: 1 - - - name: Install Dependencies - run: npm i -g @cloudflare/wrangler - - - name: ⭐ Sync versioning.json to the CDN ⭐ - run: | - for CHANNEL in $(jq 'keys[]' build-system/global-configs/versioning.json --raw-output) - do - RTV=$(jq ".[\"${CHANNEL}\"]" build-system/global-configs/versioning.json --raw-output) - if [[ RTV != "null" ]] - then - echo "Setting ${CHANNEL} to ${RTV}" - wrangler kv:key put "${CHANNEL}" "${RTV}" --config .github/workflows/wrangler.toml --binding RTV - else - echo "Unsetting ${CHANNEL}" - wrangler kv:key delete "${CHANNEL}" --config .github/workflows/wrangler.toml --binding RTV --force - fi - done - env: - CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} diff --git a/.github/workflows/update-session-issues.yml b/.github/workflows/update-session-issues.yml index 8fa2fe3fb518..2f22bca167b6 100644 --- a/.github/workflows/update-session-issues.yml +++ b/.github/workflows/update-session-issues.yml @@ -8,16 +8,25 @@ on: schedule: - cron: '30 16,17 * * 3' # Africa/Europe/western Asia - cron: '0 21,22 * * 3' # Americas - - cron: '0 1,2 * * 4' # Asia/Oceania + +permissions: + contents: read jobs: update-session-issues: + permissions: + issues: write if: github.repository == 'ampproject/amphtml' name: Update Session Issues runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + - name: Checkout Repo - uses: actions/checkout@v2 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Update Session Issues run: | node ./build-system/common/update-session-issues diff --git a/.github/workflows/wrangler.toml b/.github/workflows/wrangler.toml deleted file mode 100644 index 16efb1c5052c..000000000000 --- a/.github/workflows/wrangler.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "cdn-worker" -type = "javascript" -zone_id = "" -account_id = "78e1d5140b47fc9dab18dc8b25351b7a" -compatibility_date = "2021-10-08" -route = "" -workers_dev = true -kv_namespaces = [ - { binding = "RTV", id = "f33b97f2e1434004975c0d8553f70488", preview_id = "192df3dff1b345ce8e7c794329bb7ccd" }, - { binding = "AMP_EXP", id = "b03e5db05f3e4d068508da267e248949", preview_id = "69318ded67c0477b85d73ec801b47543" } -] diff --git a/.gitignore b/.gitignore index d78aa770a5bd..06bcedd0dd80 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,6 @@ firebase.json .*cache .amp-dep-check build -build-system/tasks/performance/cache -build-system/tasks/performance/results.json dist dist.3p dist.tools diff --git a/.prettierignore b/.prettierignore index 1afc6c879ad8..1f3a2890a373 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,8 +10,6 @@ .*cache .amp-dep-check build -build-system/tasks/performance/cache -build-system/tasks/performance/results.json dist dist.3p dist.tools diff --git a/.renovaterc.json b/.renovaterc.json index f6f72b856aa2..1b42148a80aa 100644 --- a/.renovaterc.json +++ b/.renovaterc.json @@ -1,8 +1,5 @@ { - "extends": ["config:base"], - "node": { - "supportPolicy": ["lts_latest"] - }, + "extends": ["config:recommended"], "ignoreDeps": ["ubuntu-2004"], "commitMessagePrefix": "📦", "timezone": "America/Los_Angeles", @@ -20,13 +17,13 @@ "packageRules": [ { "groupName": "subpackage devDependencies", - "matchPaths": ["**/*"], + "matchFileNames": ["**/*"], "rebaseWhen": "never", "automerge": true }, { "groupName": "chrome and chromedriver", - "matchFiles": ["build-system/tasks/e2e/package.json"], + "matchFileNames": ["build-system/tasks/e2e/package.json"], "matchPackageNames": ["chromedriver"], "labels": ["WG: infra"], "rebaseWhen": "never", @@ -34,81 +31,86 @@ }, { "groupName": "build-system devDependencies", - "matchPaths": ["build-system/**"], + "matchFileNames": ["build-system/**"], "labels": ["WG: infra"], "rebaseWhen": "never", - "automerge": true + "automerge": true, + "matchPackageNames": ["!chromedriver"] }, { "groupName": "validator devDependencies", - "matchPaths": ["validator/**"], + "matchFileNames": ["validator/**"], "labels": ["WG: caching"], "rebaseWhen": "never", "automerge": true }, { "groupName": "validator webui", - "matchPaths": ["validator/js/webui/**"], + "matchFileNames": ["validator/js/webui/**"], "enabled": false }, { "groupName": "core devDependencies", - "matchFiles": ["package.json"], + "matchFileNames": ["package.json"], "labels": ["WG: infra"], "rebaseWhen": "never", "automerge": true }, { "groupName": "linting devDependencies", - "matchFiles": ["package.json"], - "matchPackagePatterns": ["\\b(prettier|eslint)\\b"], + "matchFileNames": ["package.json"], "labels": ["WG: infra"], "rebaseWhen": "never", - "automerge": true + "automerge": true, + "matchPackageNames": ["/\\b(prettier|eslint)\\b/"] }, { "groupName": "babel devDependencies", - "matchFiles": ["package.json"], - "matchPackagePatterns": ["\\bbabel"], - "major": {"automerge": false, "assignAutomerge": false}, + "matchFileNames": ["package.json"], + "major": { + "automerge": false, + "assignAutomerge": false + }, "labels": ["WG: infra", "WG: performance"], "rebaseWhen": "never", - "automerge": true + "automerge": true, + "matchPackageNames": ["/\\bbabel/"] }, { "groupName": "esbuild devDependencies", - "matchFiles": ["package.json"], - "matchPackagePatterns": ["\\besbuild\\b"], + "matchFileNames": ["package.json"], "labels": ["WG: infra", "WG: performance"], "rebaseWhen": "never", - "automerge": true + "automerge": true, + "matchPackageNames": ["/\\besbuild\\b/"] }, { "groupName": "ampproject devDependencies", - "matchFiles": ["package.json"], - "matchPackagePatterns": ["^@ampproject/"], + "matchFileNames": ["package.json"], "matchDepTypes": ["devDependencies"], - "labels": ["WG: bento", "WG: components", "WG: performance"], + "labels": ["WG: components", "WG: performance"], "rebaseWhen": "never", - "automerge": true + "automerge": true, + "matchPackageNames": ["/^@ampproject//"] }, { "groupName": "ampproject dependencies", - "matchFiles": ["package.json"], - "matchPackagePatterns": ["^@ampproject/"], + "matchFileNames": ["package.json"], "matchDepTypes": ["dependencies"], - "labels": ["WG: bento", "WG: components", "WG: performance"], + "labels": ["WG: components", "WG: performance"], "rebaseWhen": "never", - "automerge": false + "automerge": false, + "schedule": null, + "matchPackageNames": ["/^@ampproject//"] }, { "groupName": "core dependencies", - "matchFiles": ["package.json"], - "excludePackagePatterns": ["^@ampproject/"], + "matchFileNames": ["package.json"], "matchDepTypes": ["dependencies"], - "labels": ["WG: bento", "WG: components", "WG: performance"], + "labels": ["WG: components", "WG: performance"], "rebaseWhen": "never", - "automerge": false + "automerge": false, + "matchPackageNames": ["!/^@ampproject//"] } ] } diff --git a/.vscode/OWNERS b/.vscode/OWNERS index 002922bb9cc9..69a88bfe0386 100644 --- a/.vscode/OWNERS +++ b/.vscode/OWNERS @@ -4,7 +4,7 @@ { rules: [ { - owners: [{name: 'ampproject/wg-infra'}, {name: 'rsimha', notify: true}], + owners: [{name: 'ampproject/wg-infra'}], }, ], } diff --git a/.vscode/settings.json b/.vscode/settings.json index d2299dd8882e..da066d95027f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,12 +7,19 @@ // Enable JSON auto-formatting for these files. ".prettierrc": "json", - ".renovaterc.json": "json" + ".renovaterc.json": "json", + + // Allow comments in JSON schema files. + "*.schema.json": "jsonc" }, + "files.insertFinalNewline": true, + // Auto-fix JS files with ESLint using amphtml's custom settings. Needs // https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint - "editor.codeActionsOnSave": {"source.fixAll.eslint": true}, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, "[js]": {"editor.formatOnSave": false}, // Auto-fix non-JS files with Prettier using amphtml's custom settings. Needs @@ -40,20 +47,10 @@ "fileMatch": ["build-system/global-configs/caches.json"], "url": "./build-system/json-schemas/caches.json" }, - { - "fileMatch": [ - "build-system/global-configs/client-side-experiments-config.json" - ], - "url": "./build-system/json-schemas/client-side-experiments-config.json" - }, { "fileMatch": ["build-system/tasks/bundle-size/filesize.json"], "url": "./build-system/json-schemas/filesize.json" }, - { - "fileMatch": ["build-system/global-configs/versioning.json"], - "url": "./build-system/json-schemas/versioning.json" - }, { "fileMatch": ["test/visual-diff/visual-tests.jsonc"], "url": "./build-system/json-schemas/visual-tests.json" diff --git a/3p/3d-gltf/index.js b/3p/3d-gltf/index.js index 86f0a874b0ab..001b3242ce9b 100644 --- a/3p/3d-gltf/index.js +++ b/3p/3d-gltf/index.js @@ -3,7 +3,6 @@ import {loadScript} from '#3p/3p'; import {listenParent, nonSensitiveDataPostMessage} from '#3p/messaging'; -import {dict} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; import {user} from '#utils/log'; @@ -54,22 +53,16 @@ export function gltfViewer(global) { if (!e.lengthComputable) { return; } - nonSensitiveDataPostMessage( - 'progress', - dict({ - 'total': e.total, - 'loaded': e.loaded, - }) - ); + nonSensitiveDataPostMessage('progress', { + 'total': e.total, + 'loaded': e.loaded, + }); }, onerror: (err) => { user().error('3DGLTF', err); - nonSensitiveDataPostMessage( - 'error', - dict({ - 'error': (err || '').toString(), - }) - ); + nonSensitiveDataPostMessage('error', { + 'error': (err || '').toString(), + }); }, }); listenParent(global, 'action', (msg) => { diff --git a/3p/3p.js b/3p/3p.js index e223027de451..9bf84f0eca66 100644 --- a/3p/3p.js +++ b/3p/3p.js @@ -15,7 +15,7 @@ import {devAssert, userAssert} from '#utils/log'; let ThirdPartyFunctionDef; /** - * @const {!Object} + * @const {!{[key: string]: ThirdPartyFunctionDef}} * @visibleForTesting */ let registrations; @@ -285,7 +285,7 @@ function validateAllowedFields(data, allowedFields) { } } -/** @private {!Object} */ +/** @private {!{[key: string]: boolean}} */ let experimentToggles = {}; /** @@ -299,7 +299,7 @@ export function isExperimentOn(experimentId) { /** * Set experiment toggles. - * @param {!Object} toggles + * @param {!{[key: string]: boolean}} toggles */ export function setExperimentToggles(toggles) { experimentToggles = toggles; diff --git a/3p/ampcontext-integration.js b/3p/ampcontext-integration.js index aea522c64b2e..dea3e3f28e4a 100644 --- a/3p/ampcontext-integration.js +++ b/3p/ampcontext-integration.js @@ -1,5 +1,3 @@ -import {dict} from '#core/types/object'; - import {dev, user, userAssert} from '#utils/log'; import {computeInMasterFrame} from './3p'; @@ -114,12 +112,9 @@ 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', { + 'id': user().assertString(entityId), + }); } /** diff --git a/3p/ampcontext.js b/3p/ampcontext.js index 87f922dbbd6b..55e8eb1f6d17 100644 --- a/3p/ampcontext.js +++ b/3p/ampcontext.js @@ -1,8 +1,8 @@ -import {MessageType} from '#core/3p-frame-messaging'; -import {AmpEvents} from '#core/constants/amp-events'; +import {MessageType_Enum} from '#core/3p-frame-messaging'; +import {AmpEvents_Enum} from '#core/constants/amp-events'; import {Deferred} from '#core/data-structures/promise'; import {isObject} from '#core/types'; -import {dict, map} from '#core/types/object'; +import {map} from '#core/types/object'; import {tryParseJson} from '#core/types/object/json'; import {dev, devAssert} from '#utils/log'; @@ -51,7 +51,7 @@ export class AbstractAmpContext { /** @type {?Object} */ this.consentSharedData = null; - /** @type {?Object} */ + /** @type {?{[key: string]: *}} */ this.data = null; /** @type {?string} */ @@ -102,7 +102,7 @@ export class AbstractAmpContext { /** @type {?string} */ this.tagName = null; - /** @type {!Object} */ + /** @type {!{[key: number]: Deferred}} */ this.resizeIdToDeferred_ = map(); /** @type {number} */ @@ -129,8 +129,8 @@ export class AbstractAmpContext { /** Registers an general handler for page visibility. */ listenForPageVisibility_() { this.client_.makeRequest( - MessageType.SEND_EMBED_STATE, - MessageType.EMBED_STATE, + MessageType_Enum.SEND_EMBED_STATE, + MessageType_Enum.EMBED_STATE, (data) => { this.hidden = data['pageHidden']; this.dispatchVisibilityChangeEvent_(); @@ -145,7 +145,7 @@ export class AbstractAmpContext { dispatchVisibilityChangeEvent_() { const event = this.win_.document.createEvent('Event'); event.data = {hidden: this.hidden}; - event.initEvent(AmpEvents.VISIBILITY_CHANGE, true, true); + event.initEvent(AmpEvents_Enum.VISIBILITY_CHANGE, true, true); this.win_.dispatchEvent(event); } @@ -157,9 +157,12 @@ export class AbstractAmpContext { * every time we receive a page visibility message. */ onPageVisibilityChange(callback) { - return this.client_.registerCallback(MessageType.EMBED_STATE, (data) => { - callback({hidden: data['pageHidden']}); - }); + return this.client_.registerCallback( + MessageType_Enum.EMBED_STATE, + (data) => { + callback({hidden: data['pageHidden']}); + } + ); } /** @@ -171,8 +174,8 @@ export class AbstractAmpContext { */ observeIntersection(callback) { return this.client_.makeRequest( - MessageType.SEND_INTERSECTIONS, - MessageType.INTERSECTION, + MessageType_Enum.SEND_INTERSECTIONS, + MessageType_Enum.INTERSECTION, (intersection) => { callback(intersection['changes']); } @@ -188,11 +191,11 @@ export class AbstractAmpContext { */ getHtml(selector, attributes, callback) { this.client_.getData( - MessageType.GET_HTML, - dict({ + MessageType_Enum.GET_HTML, + { 'selector': selector, 'attributes': attributes, - }), + }, callback ); } @@ -203,7 +206,7 @@ export class AbstractAmpContext { * @param {function(*)} callback */ getConsentState(callback) { - this.client_.getData(MessageType.GET_CONSENT_STATE, null, callback); + this.client_.getData(MessageType_Enum.GET_CONSENT_STATE, null, callback); } /** @@ -216,15 +219,12 @@ export class AbstractAmpContext { */ requestResize(width, height, hasOverflow) { const requestId = this.nextResizeRequestId_++; - this.client_.sendMessage( - MessageType.EMBED_SIZE, - dict({ - 'id': requestId, - 'width': width, - 'height': height, - 'hasOverflow': hasOverflow, - }) - ); + this.client_.sendMessage(MessageType_Enum.EMBED_SIZE, { + 'id': requestId, + 'width': width, + 'height': height, + 'hasOverflow': hasOverflow, + }); const deferred = new Deferred(); this.resizeIdToDeferred_[requestId] = deferred; return deferred.promise; @@ -234,21 +234,27 @@ export class AbstractAmpContext { * Set up listeners to handle responses from request size. */ listenToResizeResponse_() { - this.client_.registerCallback(MessageType.EMBED_SIZE_CHANGED, (data) => { - const id = data['id']; - if (id !== undefined) { - this.resizeIdToDeferred_[id].resolve(); - delete this.resizeIdToDeferred_[id]; + this.client_.registerCallback( + MessageType_Enum.EMBED_SIZE_CHANGED, + (data) => { + const id = data['id']; + if (id !== undefined) { + this.resizeIdToDeferred_[id].resolve(); + delete this.resizeIdToDeferred_[id]; + } } - }); + ); - this.client_.registerCallback(MessageType.EMBED_SIZE_DENIED, (data) => { - const id = data['id']; - if (id !== undefined) { - this.resizeIdToDeferred_[id].reject('Resizing is denied'); - delete this.resizeIdToDeferred_[id]; + this.client_.registerCallback( + MessageType_Enum.EMBED_SIZE_DENIED, + (data) => { + const id = data['id']; + if (id !== undefined) { + this.resizeIdToDeferred_[id].reject('Resizing is denied'); + delete this.resizeIdToDeferred_[id]; + } } - }); + ); } /** @@ -256,13 +262,10 @@ export class AbstractAmpContext { * @private */ sendDeprecationNotice_(endpoint) { - this.client_.sendMessage( - MessageType.USER_ERROR_IN_IFRAME, - dict({ - 'message': `${endpoint} is deprecated`, - 'expected': true, - }) - ); + this.client_.sendMessage(MessageType_Enum.USER_ERROR_IN_IFRAME, { + 'message': `${endpoint} is deprecated`, + 'expected': true, + }); } /** @@ -273,9 +276,12 @@ export class AbstractAmpContext { * request succeeds. */ onResizeSuccess(callback) { - this.client_.registerCallback(MessageType.EMBED_SIZE_CHANGED, (obj) => { - callback(obj['requestedHeight'], obj['requestedWidth']); - }); + this.client_.registerCallback( + MessageType_Enum.EMBED_SIZE_CHANGED, + (obj) => { + callback(obj['requestedHeight'], obj['requestedWidth']); + } + ); this.sendDeprecationNotice_('onResizeSuccess'); } @@ -287,7 +293,7 @@ export class AbstractAmpContext { * request is denied. */ onResizeDenied(callback) { - this.client_.registerCallback(MessageType.EMBED_SIZE_DENIED, (obj) => { + this.client_.registerCallback(MessageType_Enum.EMBED_SIZE_DENIED, (obj) => { callback(obj['requestedHeight'], obj['requestedWidth']); }); this.sendDeprecationNotice_('onResizeDenied'); @@ -297,7 +303,7 @@ export class AbstractAmpContext { * Make the ad interactive. */ signalInteractive() { - this.client_.sendMessage(MessageType.SIGNAL_INTERACTIVE); + this.client_.sendMessage(MessageType_Enum.SIGNAL_INTERACTIVE); } /** @@ -314,13 +320,13 @@ export class AbstractAmpContext { * Notifies the parent document of no content available inside embed. */ noContentAvailable() { - this.client_.sendMessage(MessageType.NO_CONTENT); + this.client_.sendMessage(MessageType_Enum.NO_CONTENT); } /** * Parse the metadata attributes from the name and add them to * the class instance. - * @param {!Object|string} data + * @param {string} data * @private */ setupMetadata_(data) { @@ -340,6 +346,17 @@ export class AbstractAmpContext { delete this.data['_context']; } + this.setupMetadataFromContext_(context); + + this.embedType_ = dataObject.type || null; + } + + /** + * Set the metadata attributes from the "context" instance directly. + * @param {!Object} context + * @private + */ + setupMetadataFromContext_(context) { this.canary = context.canary; this.canonicalUrl = context.canonicalUrl; this.clientId = context.clientId; @@ -361,8 +378,6 @@ export class AbstractAmpContext { this.sourceUrl = context.sourceUrl; this.startTime = context.startTime; this.tagName = context.tagName; - - this.embedType_ = dataObject.type || null; } /** @@ -397,8 +412,11 @@ export class AbstractAmpContext { } else if (this.win_.AMP_CONTEXT_DATA) { if (typeof this.win_.AMP_CONTEXT_DATA == 'string') { this.sentinel = this.win_.AMP_CONTEXT_DATA; + // If AMP_CONTEXT_DATA is an Object, Assume that it is the Context Object + // and not inside the attributes._context which is the structure + // parsed from "name" on other instances. } else if (isObject(this.win_.AMP_CONTEXT_DATA)) { - this.setupMetadata_(this.win_.AMP_CONTEXT_DATA); + this.setupMetadataFromContext_(this.win_.AMP_CONTEXT_DATA); } } else { this.setupMetadata_(this.win_.name); @@ -413,12 +431,9 @@ export class AbstractAmpContext { if (!e.message) { return; } - this.client_.sendMessage( - MessageType.USER_ERROR_IN_IFRAME, - dict({ - 'message': e.message, - }) - ); + this.client_.sendMessage(MessageType_Enum.USER_ERROR_IN_IFRAME, { + 'message': e.message, + }); } } diff --git a/3p/bodymovinanimation.js b/3p/bodymovinanimation.js index c60a3b2f3120..aafcadd54a5a 100644 --- a/3p/bodymovinanimation.js +++ b/3p/bodymovinanimation.js @@ -1,20 +1,19 @@ import {setStyles} from '#core/dom/style'; import {tryPlay} from '#core/dom/video'; -import {dict} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; import {getData} from '#utils/event-helper'; import {loadScript} from './3p'; -const libSourceUrl = dict({ +const libSourceUrl = { 'canvas': 'https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.7.6/lottie_canvas.min.js', 'html': 'https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.7.6/lottie_html.min.js', 'svg': 'https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.7.6/lottie_svg.min.js', -}); +}; /** * Produces the AirBnB Bodymovin Player SDK object for the passed in callback. @@ -84,11 +83,9 @@ export function bodymovinanimation(global) { autoplay: dataReceived['autoplay'], animationData: dataReceived['animationData'], }); - const message = JSON.stringify( - dict({ - 'action': 'ready', - }) - ); + const message = JSON.stringify({ + 'action': 'ready', + }); global.addEventListener('message', parseMessage, false); global.parent./*OK*/ postMessage(message, '*'); }); diff --git a/3p/environment.js b/3p/environment.js index 63be7559461c..b67105cd7055 100644 --- a/3p/environment.js +++ b/3p/environment.js @@ -18,7 +18,11 @@ export function setInViewportForTesting(inV) { // Active intervals. Must be global, because people clear intervals // with clearInterval from a different window. const intervals = {}; -let intervalId = 1; + +// TODO(40235): This is so we avoid the ID range of setTimeout. We +// should also patch setTimeout and clearTimeout to avoid having to +// use this. +let intervalId = 10000000; /** * Add instrumentation to a window and all child iframes. diff --git a/3p/facebook.js b/3p/facebook.js index 83fd622a2a97..6206888b860f 100644 --- a/3p/facebook.js +++ b/3p/facebook.js @@ -1,6 +1,5 @@ import {setStyle} from '#core/dom/style'; import {isEnumValue} from '#core/types/enum'; -import {dict} from '#core/types/object'; import {dashToUnderline} from '#core/types/string'; import {devAssert} from '#utils/log'; @@ -9,9 +8,6 @@ import {loadScript} from './3p'; /** @const @enum {string} */ export const FacebookEmbedType = { - // Embeds a single comment or reply to a comment on a post rendered by - // amp-facebook. - COMMENT: 'comment', // Allows users to comment on the embedded content using their Facebook // accounts. Correlates to amp-facebook-comments. COMMENTS: 'comments', @@ -93,25 +89,6 @@ function getVideoContainer(global, data) { return container; } -/** - * Create DOM element for the Facebook embedded content plugin for comments or - * comment replies. - * @see https://developers.facebook.com/docs/plugins/embedded-comments - * @param {!Window} global - * @param {!Object} data The element data - * @return {!Element} div - */ -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); - return container; -} - /** * Gets the default type to embed as, if not specified. * @param {string} href @@ -197,8 +174,6 @@ function getEmbedContainer(global, data, embedAs) { return getLikeContainer(global, data); case FacebookEmbedType.COMMENTS: return getCommentsContainer(global, data); - case FacebookEmbedType.COMMENT: - return getCommentContainer(global, data); case FacebookEmbedType.VIDEO: return getVideoContainer(global, data); default: @@ -232,14 +207,12 @@ export function facebook(global, data) { ); }); - FB.init({xfbml: true, version: 'v2.5'}); + FB.init({xfbml: true, version: 'v17.0'}); // Report to parent that the SDK has loaded and is ready to paint - const message = JSON.stringify( - dict({ - 'action': 'ready', - }) - ); + const message = JSON.stringify({ + '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 e7f65673e120..6bb2e21f2f24 100644 --- a/3p/frame-metadata.js +++ b/3p/frame-metadata.js @@ -1,5 +1,4 @@ import {once} from '#core/types/function'; -import {dict} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; import {dev} from '#utils/log'; @@ -32,11 +31,11 @@ import {parseUrlDeprecated} from '../src/url'; export let ContextStateDef; /** @const {!JsonObject} */ -const FALLBACK = dict({ - 'attributes': dict({ - '_context': dict(), - }), -}); +const FALLBACK = { + 'attributes': { + '_context': {}, + }, +}; /** * Gets metadata encoded in iframe name attribute. @@ -72,8 +71,8 @@ export function getAmpConfig() { /** * @return {!JsonObject} */ -const getAttributeDataImpl_ = once(() => { - const data = Object.assign(dict({}), allMetadata()['attributes']); +const getAttributeData = once(() => { + const data = Object.assign(Object.create(null), allMetadata()['attributes']); // TODO(alanorozco): don't delete _context. refactor data object structure. if ('_context' in data) { @@ -83,13 +82,7 @@ const getAttributeDataImpl_ = once(() => { return data; }); -/** - * @return {!JsonObject} - */ -export function getAttributeData() { - // using indirect invocation to prevent no-export-side-effect issue - return getAttributeDataImpl_(); -} +export {getAttributeData}; /** * @return {!Location} diff --git a/3p/frame.max.html b/3p/frame.max.html index 85783273e917..c8955d4d68d2 100644 --- a/3p/frame.max.html +++ b/3p/frame.max.html @@ -7,7 +7,9 @@ try { url = JSON.parse(window.name).bootstrap; } catch (e) {} -document.write(' diff --git a/3p/iframe-messaging-client.js b/3p/iframe-messaging-client.js index f4215813adea..68608bc75cbe 100644 --- a/3p/iframe-messaging-client.js +++ b/3p/iframe-messaging-client.js @@ -5,7 +5,7 @@ import { serializeMessage, } from '#core/3p-frame-messaging'; import {Observable} from '#core/data-structures/observable'; -import {dict, map} from '#core/types/object'; +import {map} from '#core/types/object'; import {getData} from '#utils/event-helper'; import {dev} from '#utils/log'; @@ -55,7 +55,7 @@ export class IframeMessagingClient { callback(result[CONSTANTS.contentFieldName]); } }); - const data = dict(); + const data = {}; data[CONSTANTS.payloadFieldName] = payload; data[CONSTANTS.messageIdFieldName] = messageId; this.sendMessage(requestType, data); @@ -156,16 +156,12 @@ export class IframeMessagingClient { /** * @param {!Window} win * @param {string} msg - * @suppress {checkTypes} // Can be removed after closure compiler update their externs. */ postMessageWithUserActivation_(win, msg) { - win./*OK*/ postMessage( - msg, - dict({ - 'targetOrigin': '*', - 'includeUserActivation': true, - }) - ); + win./*OK*/ postMessage(msg, { + 'targetOrigin': '*', + 'includeUserActivation': true, + }); } /** @@ -223,7 +219,7 @@ export class IframeMessagingClient { /** * @param {string} messageType - * @param {Object} message + * @param {object} message */ fireObservable_(messageType, message) { if (messageType in this.observableFor_) { diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index 58d7c26dc24c..cac728bcc7a2 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -1,10 +1,12 @@ -import {IframeTransportEventDef, MessageType} from '#core/3p-frame-messaging'; +import {MessageType_Enum} from '#core/3p-frame-messaging'; import {tryParseJson} from '#core/types/object/json'; import {dev, devAssert, user, userAssert} from '#utils/log'; import {IframeMessagingClient} from './iframe-messaging-client'; +/** @typedef {import('#core/3p-frame-messaging').IframeTransportEventDef} IframeTransportDef */ + /** @private @const {string} */ const TAG_ = 'iframe-transport-client'; @@ -18,7 +20,7 @@ export class IframeTransportClient { /** @private {!Window} */ this.win_ = win; - /** @private {!Object} */ + /** @private {!{[key: string]: IframeTransportContext}} */ this.creativeIdToContext_ = {}; const parsedFrameName = tryParseJson(this.win_.name); @@ -46,8 +48,8 @@ export class IframeTransportClient { ) ); this.iframeMessagingClient_.makeRequest( - MessageType.SEND_IFRAME_TRANSPORT_EVENTS, - MessageType.IFRAME_TRANSPORT_EVENTS, + MessageType_Enum.SEND_IFRAME_TRANSPORT_EVENTS, + MessageType_Enum.IFRAME_TRANSPORT_EVENTS, (eventData) => { const events = /** @type {!Array} */ ( eventData['events'] @@ -159,11 +161,11 @@ export class IframeTransportContext { /** * Sends a response message back to the creative. - * @param {!Object} data + * @param {!{[key: string]: string}} data */ sendResponseToCreative(data) { this.iframeMessagingClient_./*OK*/ sendMessage( - MessageType.IFRAME_TRANSPORT_RESPONSE, + MessageType_Enum.IFRAME_TRANSPORT_RESPONSE, /** @type {!JsonObject} */ ({message: data, ...this.baseMessage_}) ); diff --git a/3p/integration-lib.js b/3p/integration-lib.js index 36f9a2d3f373..5076d6ed8440 100644 --- a/3p/integration-lib.js +++ b/3p/integration-lib.js @@ -1,5 +1,4 @@ import * as mode from '#core/mode'; -import {dict} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; import {endsWith} from '#core/types/string'; @@ -15,12 +14,12 @@ import {IntegrationAmpContext} from './ampcontext-integration'; import {installEmbedStateListener, manageWin} from './environment'; import {getAmpConfig, getEmbedType, getLocation} from './frame-metadata'; -import {urls} from '../src/config'; +import * as urls from '../src/config/urls'; import {getSourceUrl, isProxyOrigin, parseUrlDeprecated} from '../src/url'; /** * Whether the embed type may be used with amp-embed tag. - * @const {!Object} + * @const {!{[key: string]: boolean}} */ const AMP_EMBED_ALLOWED = { _ping_: true, @@ -49,7 +48,6 @@ const AMP_EMBED_ALLOWED = { myua: true, mywidget: true, nativery: true, - lentainform: true, opinary: true, outbrain: true, plista: true, @@ -314,7 +312,7 @@ export function parseFragment(fragment) { if (json.startsWith('{%22')) { json = decodeURIComponent(json); } - return /** @type {!JsonObject} */ (json ? parseJson(json) : dict()); + return /** @type {!JsonObject} */ (json ? parseJson(json) : {}); } catch (err) { return null; } diff --git a/3p/integration.js b/3p/integration.js index c8a07dea8761..14dcc4d90bc4 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -33,6 +33,7 @@ import {_ping_} from '#ads/vendors/_ping_'; // 3P Ad Networks - please keep in alphabetic order import {_1wo} from '#ads/vendors/1wo'; import {_24smi} from '#ads/vendors/24smi'; +import {_4wmarketplace} from '#ads/vendors/4wmarketplace'; import {a8} from '#ads/vendors/a8'; import {a9} from '#ads/vendors/a9'; import {accesstrade} from '#ads/vendors/accesstrade'; @@ -48,6 +49,7 @@ import {adincube} from '#ads/vendors/adincube'; import {adition} from '#ads/vendors/adition'; import {adman} from '#ads/vendors/adman'; import {admanmedia} from '#ads/vendors/admanmedia'; +import {admatic} from '#ads/vendors/admatic'; import {admixer} from '#ads/vendors/admixer'; import {adnuntius} from '#ads/vendors/adnuntius'; import {adocean} from '#ads/vendors/adocean'; @@ -57,6 +59,7 @@ import {adplugg} from '#ads/vendors/adplugg'; import {adpon} from '#ads/vendors/adpon'; import {adpushup} from '#ads/vendors/adpushup'; import {adreactor} from '#ads/vendors/adreactor'; +import {ads2bid} from '#ads/vendors/ads2bid'; import {adsensor} from '#ads/vendors/adsensor'; import {adservsolutions} from '#ads/vendors/adservsolutions'; import {adsloom} from '#ads/vendors/adsloom'; @@ -80,6 +83,7 @@ import {aja} from '#ads/vendors/aja'; import {amoad} from '#ads/vendors/amoad'; import {aniview} from '#ads/vendors/aniview'; import {anyclip} from '#ads/vendors/anyclip'; +import {appmonsta} from '#ads/vendors/appmonsta'; import {appnexus} from '#ads/vendors/appnexus'; import {appvador} from '#ads/vendors/appvador'; import {atomx} from '#ads/vendors/atomx'; @@ -155,7 +159,6 @@ import {ketshwa} from '#ads/vendors/ketshwa'; import {kiosked} from '#ads/vendors/kiosked'; import {kixer} from '#ads/vendors/kixer'; import {kuadio} from '#ads/vendors/kuadio'; -import {lentainform} from '#ads/vendors/lentainform'; import {ligatus} from '#ads/vendors/ligatus'; import {lockerdome} from '#ads/vendors/lockerdome'; import {logly} from '#ads/vendors/logly'; @@ -196,6 +199,7 @@ import {openadstream} from '#ads/vendors/openadstream'; import {openx} from '#ads/vendors/openx'; import {opinary} from '#ads/vendors/opinary'; import {outbrain} from '#ads/vendors/outbrain'; +import {pixad} from '#ads/vendors/pixad'; import {pixels} from '#ads/vendors/pixels'; import {playstream} from '#ads/vendors/playstream'; import {plista} from '#ads/vendors/plista'; @@ -233,11 +237,11 @@ import {runative} from '#ads/vendors/runative'; import {sas} from '#ads/vendors/sas'; import {seedingalliance} from '#ads/vendors/seedingalliance'; import {sekindo} from '#ads/vendors/sekindo'; +import {sevio} from '#ads/vendors/sevio'; import {sharethrough} from '#ads/vendors/sharethrough'; import {shemedia} from '#ads/vendors/shemedia'; import {sklik} from '#ads/vendors/sklik'; import {slimcutmedia} from '#ads/vendors/slimcutmedia'; -import {smartadserver} from '#ads/vendors/smartadserver'; import {smartclip} from '#ads/vendors/smartclip'; import {smi2} from '#ads/vendors/smi2'; import {smilewanted} from '#ads/vendors/smilewanted'; @@ -283,7 +287,6 @@ import {wisteria} from '#ads/vendors/wisteria'; import {wpmedia} from '#ads/vendors/wpmedia'; import {wunderkind} from '#ads/vendors/wunderkind'; import {xlift} from '#ads/vendors/xlift'; -import {yahoo} from '#ads/vendors/yahoo'; import {yahoofedads} from '#ads/vendors/yahoofedads'; import {yahoojp} from '#ads/vendors/yahoojp'; import {yahoonativeads} from '#ads/vendors/yahoonativeads'; @@ -314,6 +317,7 @@ if (getMode().test || getMode().localDev) { register('1wo', _1wo); register('24smi', _24smi); register('3d-gltf', gltfViewer); +register('4wmarketplace', _4wmarketplace); register('a8', a8); register('a9', a9); register('accesstrade', accesstrade); @@ -329,6 +333,7 @@ register('adincube', adincube); register('adition', adition); register('adman', adman); register('admanmedia', admanmedia); +register('admatic', admatic); register('admixer', admixer); register('adnuntius', adnuntius); register('adocean', adocean); @@ -339,6 +344,7 @@ register('adpon', adpon); register('adpushup', adpushup); register('adreactor', adreactor); register('adsensor', adsensor); +register('ads2bid', ads2bid); register('adservsolutions', adservsolutions); register('adsloom', adsloom); register('adsnative', adsnative); @@ -361,6 +367,7 @@ register('aja', aja); register('amoad', amoad); register('aniview', aniview); register('anyclip', anyclip); +register('appmonsta', appmonsta); register('appnexus', appnexus); register('appvador', appvador); register('atomx', atomx); @@ -441,7 +448,6 @@ register('ketshwa', ketshwa); register('kiosked', kiosked); register('kixer', kixer); register('kuadio', kuadio); -register('lentainform', lentainform); register('ligatus', ligatus); register('lockerdome', lockerdome); register('logly', logly); @@ -485,6 +491,7 @@ register('openadstream', openadstream); register('openx', openx); register('opinary', opinary); register('outbrain', outbrain); +register('pixad', pixad); register('pixels', pixels); register('playstream', playstream); register('plista', plista); @@ -523,12 +530,12 @@ register('runative', runative); register('sas', sas); register('seedingalliance', seedingalliance); register('sekindo', sekindo); +register('sevio', sevio); register('sharethrough', sharethrough); register('shemedia', shemedia); register('sklik', sklik); register('ssp', ssp); register('slimcutmedia', slimcutmedia); -register('smartadserver', smartadserver); register('smartclip', smartclip); register('smi2', smi2); register('smilewanted', smilewanted); @@ -575,7 +582,6 @@ register('wisteria', wisteria); register('wunderkind', wunderkind); register('wpmedia', wpmedia); register('xlift', xlift); -register('yahoo', yahoo); register('yahoofedads', yahoofedads); register('yahoojp', yahoojp); register('yahoonativeads', yahoonativeads); diff --git a/3p/recaptcha.js b/3p/recaptcha.js index 5af6e6cf2e6d..3797bf140907 100644 --- a/3p/recaptcha.js +++ b/3p/recaptcha.js @@ -1,7 +1,7 @@ // src/polyfills.js must be the first import. import './polyfills'; -import {dict, hasOwn} from '#core/types/object'; +import {hasOwn} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; import { @@ -142,7 +142,7 @@ function initializeIframeMessagingClient(win, grecaptcha, dataObject) { * * @param {Window} win * @param {*} grecaptcha - * @param {Object} data + * @param {object} data */ function actionTypeHandler(win, grecaptcha, data) { doesOriginDomainMatchIframeSrc(win, data) @@ -154,13 +154,10 @@ function actionTypeHandler(win, grecaptcha, data) { // .then() promise pollyfilled by recaptcha api script executePromise./*OK*/ then( function (token) { - iframeMessagingClient./*OK*/ sendMessage( - 'amp-recaptcha-token', - dict({ - 'id': data.id, - 'token': token, - }) - ); + iframeMessagingClient./*OK*/ sendMessage('amp-recaptcha-token', { + 'id': data.id, + 'token': token, + }); }, function (err) { let message = @@ -170,13 +167,10 @@ function actionTypeHandler(win, grecaptcha, data) { message = err.toString(); } user().error(TAG, '%s', message); - iframeMessagingClient./*OK*/ sendMessage( - 'amp-recaptcha-error', - dict({ - 'id': data.id, - 'error': message, - }) - ); + iframeMessagingClient./*OK*/ sendMessage('amp-recaptcha-error', { + 'id': data.id, + 'error': message, + }); } ); }) @@ -189,7 +183,7 @@ function actionTypeHandler(win, grecaptcha, data) { * Function to verify our origin domain from the * parent window. * @param {Window} win - * @param {Object} data + * @param {object} data * @return {!Promise} */ export function doesOriginDomainMatchIframeSrc(win, data) { diff --git a/3p/vendors/4wmarketplace.js b/3p/vendors/4wmarketplace.js new file mode 100644 index 000000000000..e1efe0efb1c3 --- /dev/null +++ b/3p/vendors/4wmarketplace.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {_4wmarketplace} from '#ads/vendors/4wmarketplace'; + +init(window); +register('4wmarketplace', _4wmarketplace); + +window.draw3p = draw3p; diff --git a/3p/vendors/adenza.js b/3p/vendors/adenza.js new file mode 100644 index 000000000000..df68846aca40 --- /dev/null +++ b/3p/vendors/adenza.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {adenza} from '#ads/vendors/adenza'; + +init(window); +register('adenza', adenza); + +window.draw3p = draw3p; diff --git a/3p/vendors/admatic.js b/3p/vendors/admatic.js new file mode 100644 index 000000000000..725738aa2a47 --- /dev/null +++ b/3p/vendors/admatic.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {admatic} from '#ads/vendors/admatic'; + +init(window); +register('admatic', admatic); + +window.draw3p = draw3p; diff --git a/3p/vendors/ads2bid.js b/3p/vendors/ads2bid.js new file mode 100644 index 000000000000..52fe84f21acc --- /dev/null +++ b/3p/vendors/ads2bid.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {ads2bid} from '#ads/vendors/ads2bid'; + +init(window); +register('ads2bid', ads2bid); + +window.draw3p = draw3p; diff --git a/3p/vendors/adsviu.js b/3p/vendors/adsviu.js new file mode 100644 index 000000000000..2bd3e00c4bac --- /dev/null +++ b/3p/vendors/adsviu.js @@ -0,0 +1,28 @@ +/** + * Copyright 2023 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {adsviu} from '#ads/vendors/adsviu'; + +init(window); +register('adsviu', adsviu); + +window.draw3p = draw3p; diff --git a/3p/vendors/affinity.js b/3p/vendors/affinity.js new file mode 100644 index 000000000000..5730b83da6a8 --- /dev/null +++ b/3p/vendors/affinity.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {affinity} from '#ads/vendors/affinity'; + +init(window); +register('affinity', affinity); + +window.draw3p = draw3p; diff --git a/3p/vendors/appmonsta.js b/3p/vendors/appmonsta.js new file mode 100644 index 000000000000..642b7b4717f8 --- /dev/null +++ b/3p/vendors/appmonsta.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {appmonsta} from '#ads/vendors/appmonsta'; + +init(window); +register('appmonsta', appmonsta); + +window.draw3p = draw3p; diff --git a/3p/vendors/bidgear.js b/3p/vendors/bidgear.js new file mode 100644 index 000000000000..79ec95df9b1b --- /dev/null +++ b/3p/vendors/bidgear.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {bidgear} from '#ads/vendors/bidgear'; + +init(window); +register('bidgear', bidgear); + +window.draw3p = draw3p; diff --git a/3p/vendors/clever.js b/3p/vendors/clever.js new file mode 100644 index 000000000000..b01e369227cb --- /dev/null +++ b/3p/vendors/clever.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {clever} from '#ads/vendors/clever'; + +init(window); +register('clever', clever); + +window.draw3p = draw3p; diff --git a/3p/vendors/cognativex.js b/3p/vendors/cognativex.js new file mode 100644 index 000000000000..b34fea36016d --- /dev/null +++ b/3p/vendors/cognativex.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {cognativex} from '#ads/vendors/cognativex'; + +init(window); +register('cognativex', cognativex); + +window.draw3p = draw3p; diff --git a/3p/vendors/dex.js b/3p/vendors/dex.js new file mode 100644 index 000000000000..247668faa382 --- /dev/null +++ b/3p/vendors/dex.js @@ -0,0 +1,11 @@ +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {dex} from '#ads/vendors/dex'; + +init(window); +register('dex', dex); + +window.draw3p = draw3p; diff --git a/3p/vendors/fairground.js b/3p/vendors/fairground.js new file mode 100644 index 000000000000..c74b28279e49 --- /dev/null +++ b/3p/vendors/fairground.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {fairground} from '#ads/vendors/fairground'; + +init(window); +register('fairground', fairground); + +window.draw3p = draw3p; diff --git a/3p/vendors/geozo.js b/3p/vendors/geozo.js new file mode 100644 index 000000000000..ff832726a711 --- /dev/null +++ b/3p/vendors/geozo.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {geozo} from '#ads/vendors/geozo'; + +init(window); +register('geozo', geozo); + +window.draw3p = draw3p; diff --git a/3p/vendors/incrementx.js b/3p/vendors/incrementx.js new file mode 100644 index 000000000000..d9a7fde3a56e --- /dev/null +++ b/3p/vendors/incrementx.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {incrementx} from '#ads/vendors/incrementx'; + +init(window); +register('incrementx', incrementx); + +window.draw3p = draw3p; diff --git a/3p/vendors/insurads.js b/3p/vendors/insurads.js new file mode 100644 index 000000000000..0ff98bc358b4 --- /dev/null +++ b/3p/vendors/insurads.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {insurads} from '#ads/vendors/insurads'; + +init(window); +register('insurads', insurads); + +window.draw3p = draw3p; diff --git a/3p/vendors/jioads.js b/3p/vendors/jioads.js new file mode 100644 index 000000000000..05f171aaba73 --- /dev/null +++ b/3p/vendors/jioads.js @@ -0,0 +1,11 @@ +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {jioads} from '#ads/vendors/jioads'; + +init(window); +register('jioads', jioads); + +window.draw3p = draw3p; diff --git a/3p/vendors/lentainform.js b/3p/vendors/lentainform.js deleted file mode 100644 index 88b18c1081e4..000000000000 --- a/3p/vendors/lentainform.js +++ /dev/null @@ -1,12 +0,0 @@ -// src/polyfills.js must be the first import. -import '#3p/polyfills'; - -import {register} from '#3p/3p'; -import {draw3p, init} from '#3p/integration-lib'; - -import {lentainform} from '#ads/vendors/lentainform'; - -init(window); -register('lentainform', lentainform); - -window.draw3p = draw3p; diff --git a/3p/vendors/momagic.js b/3p/vendors/momagic.js new file mode 100644 index 000000000000..7b0c769c1852 --- /dev/null +++ b/3p/vendors/momagic.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {momagic} from '#ads/vendors/momagic'; + +init(window); +register('momagic', momagic); + +window.draw3p = draw3p; diff --git a/3p/vendors/piberica.js b/3p/vendors/piberica.js new file mode 100644 index 000000000000..4eb1888eb471 --- /dev/null +++ b/3p/vendors/piberica.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {piberica} from '#ads/vendors/piberica'; + +init(window); +register('piberica', piberica); + +window.draw3p = draw3p; diff --git a/3p/vendors/pixad.js b/3p/vendors/pixad.js new file mode 100644 index 000000000000..6a90617263a9 --- /dev/null +++ b/3p/vendors/pixad.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {pixad} from '#ads/vendors/pixad'; + +init(window); +register('pixad', pixad); + +window.draw3p = draw3p; diff --git a/3p/vendors/pubfuture.js b/3p/vendors/pubfuture.js new file mode 100644 index 000000000000..81764096b740 --- /dev/null +++ b/3p/vendors/pubfuture.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {pubfuture} from '#ads/vendors/pubfuture'; + +init(window); +register('pubfuture', pubfuture); + +window.draw3p = draw3p; diff --git a/3p/vendors/pubscale.js b/3p/vendors/pubscale.js new file mode 100644 index 000000000000..2e8eb9f38e16 --- /dev/null +++ b/3p/vendors/pubscale.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {pubscale} from '#ads/vendors/pubscale'; + +init(window); +register('pubscale', pubscale); + +window.draw3p = draw3p; diff --git a/3p/vendors/r9x.js b/3p/vendors/r9x.js new file mode 100644 index 000000000000..6a1e789678c3 --- /dev/null +++ b/3p/vendors/r9x.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {r9x} from '#ads/vendors/r9x'; + +init(window); +register('r9x', r9x); + +window.draw3p = draw3p; diff --git a/3p/vendors/sevio.js b/3p/vendors/sevio.js new file mode 100644 index 000000000000..3a8b56ceea1a --- /dev/null +++ b/3p/vendors/sevio.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {sevio} from '#ads/vendors/sevio'; + +init(window); +register('sevio', sevio); + +window.draw3p = draw3p; diff --git a/3p/vendors/skoiy.js b/3p/vendors/skoiy.js new file mode 100644 index 000000000000..8b6dc9d55523 --- /dev/null +++ b/3p/vendors/skoiy.js @@ -0,0 +1,12 @@ +// src/polyfills.js must be the first import. +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {skoiy} from '#ads/vendors/skoiy'; + +init(window); +register('skoiy', skoiy); + +window.draw3p = draw3p; diff --git a/3p/vendors/smartadserver.js b/3p/vendors/smartadserver.js deleted file mode 100644 index b93432464cc0..000000000000 --- a/3p/vendors/smartadserver.js +++ /dev/null @@ -1,12 +0,0 @@ -// src/polyfills.js must be the first import. -import '#3p/polyfills'; - -import {register} from '#3p/3p'; -import {draw3p, init} from '#3p/integration-lib'; - -import {smartadserver} from '#ads/vendors/smartadserver'; - -init(window); -register('smartadserver', smartadserver); - -window.draw3p = draw3p; diff --git a/3p/vendors/vox.js b/3p/vendors/vox.js new file mode 100644 index 000000000000..af0dbc25dd48 --- /dev/null +++ b/3p/vendors/vox.js @@ -0,0 +1,11 @@ +import '#3p/polyfills'; + +import {register} from '#3p/3p'; +import {draw3p, init} from '#3p/integration-lib'; + +import {vox} from '#ads/vendors/vox'; + +init(window); +register('vox', vox); + +window.draw3p = draw3p; diff --git a/3p/vendors/yahoo.js b/3p/vendors/yahoo.js deleted file mode 100644 index 2d0f5475c193..000000000000 --- a/3p/vendors/yahoo.js +++ /dev/null @@ -1,12 +0,0 @@ -// src/polyfills.js must be the first import. -import '#3p/polyfills'; - -import {register} from '#3p/3p'; -import {draw3p, init} from '#3p/integration-lib'; - -import {yahoo} from '#ads/vendors/yahoo'; - -init(window); -register('yahoo', yahoo); - -window.draw3p = draw3p; diff --git a/3p/viqeoplayer.js b/3p/viqeoplayer.js index 803630a34024..28e6381b6959 100644 --- a/3p/viqeoplayer.js +++ b/3p/viqeoplayer.js @@ -7,7 +7,7 @@ import {loadScript} from './3p'; /** * @param {Window} global - * @param {Object} VIQEO + * @param {object} VIQEO * @private */ function viqeoPlayerInitLoaded(global, VIQEO) { diff --git a/OWNERS b/OWNERS index a7a8022bd378..c67a35cc8f26 100644 --- a/OWNERS +++ b/OWNERS @@ -6,10 +6,11 @@ rules: [ { owners: [ - {name: 'cramforce', requestReviews: false}, - {name: 'dvoytenko', requestReviews: false}, - {name: 'jridgewell', requestReviews: false}, - {name: 'kristoferbaxter', requestReviews: false}, + {name: 'erwinmombay', requestReviews: true}, + {name: 'danielrozenberg', requestReviews: true}, + {name: 'powerivq', requestReviews: true}, + {name: 'ychsieh', requestReviews: true}, + {name: 'banaag', requestReviews: true}, ], }, { @@ -44,12 +45,31 @@ }, { pattern: '**/OWNERS', - owners: [{name: 'mrjoro', requestReviews: false}], + owners: [ + {name: 'erwinmombay', requestReviews: false}, + {name: 'mrjoro', requestReviews: false}, + {name: 'newmuis', requestReviews: false}, + ], }, { // Locked down to minimize new import aliases being added - pattern: 'tsconfig.json', + pattern: 'tsconfig{,.base}.json', owners: [{name: 'ampproject/wg-performance', required: true}], }, + { + pattern: '**/tsconfig.json', + owners: [{name: 'ampproject/wg-performance'}], + }, + { + pattern: '**/*.d.ts', + owners: [ + {name: 'ampproject/wg-infra'}, + {name: 'ampproject/wg-performance'}, + ], + }, + { + pattern: '**/build-dom.js', + owners: [{name: 'erwinmombay'}], + }, ], } diff --git a/README.md b/README.md index eab68c33433d..de7ac422d9f4 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,6 @@ [![Build Status](https://img.shields.io/circleci/build/github/ampproject/amphtml/main 'Build Status')](https://app.circleci.com/pipelines/github/ampproject/amphtml?branch=main) [![GitHub Release](https://img.shields.io/github/release/ampproject/amphtml.svg?logo=GitHub&style=flat-square 'GitHub Release')](https://github.com/ampproject/amphtml/releases/latest) [![Commits](https://img.shields.io/github/commit-activity/m/ampproject/amphtml.svg?logo=GitHub&style=flat-square 'Commits')](https://github.com/ampproject/amphtml/pulse/monthly) -[![Badges](https://img.shields.io/badge/badges-16-brightgreen?logo=GitHub&style=flat-square)](#) - -
- -Metrics - - - -[![Absolute Code Coverage](https://img.shields.io/endpoint.svg?logo=Codecov&logoColor=white&style=flat-square&url=https%3A%2F%2Famp-project-metrics.appspot.com%2Fapi%2Fbadge%2FAbsoluteCoverageMetric 'Test coverage for the repository as computed by CodeCov')](https://codecov.io/gh/ampproject/amphtml/) -[![Cherrypick Issue Count](https://img.shields.io/endpoint.svg?logo=GitHub&logoColor=white&style=flat-square&url=https%3A%2F%2Famp-project-metrics.appspot.com%2Fapi%2Fbadge%2FCherrypickIssueCountMetric 'Number of cherry-pick issues in the last 90 days')](https://github.com/ampproject/amphtml/issues?utf8=%E2%9C%93&q=is%3Aissue+title%3A+%22Cherry-pick%22) -[![Release Granularity](https://img.shields.io/endpoint.svg?logo=GitHub&logoColor=white&style=flat-square&url=https%3A%2F%2Famp-project-metrics.appspot.com%2Fapi%2Fbadge%2FReleaseGranularityMetric 'Average commits per release over the last 90 days')](https://github.com/ampproject/amphtml/releases) - -
@@ -28,7 +15,6 @@ Tooling [![Percy](https://img.shields.io/badge/%F0%9F%A6%94%20visual%20testing-Percy-violet.svg?style=flat-square 'Percy')](https://percy.io/ampproject/amphtml) [![Prettier](https://img.shields.io/badge/code_style-Prettier-ff69b4.svg?logo=Prettier&logoColor=white&style=flat-square 'Prettier')](https://github.com/prettier/prettier) [![Codecov](https://img.shields.io/badge/test_coverage-CodeCov-f01f7a.svg?logo=Codecov&logoColor=white&style=flat-square 'Codecov')](https://codecov.io/gh/ampproject/amphtml/) -[![LGTM](https://img.shields.io/lgtm/alerts/github/ampproject/amphtml.svg?logo=lgtm&style=flat-square 'LGTM')](https://lgtm.com/projects/g/ampproject/amphtml/) [![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen.svg?logo=dependabot&style=flat-square 'Renovate')](https://renovateapp.com/)
diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..821e914eb7c1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/ampproject/amphtml/security/advisories/new). + +This project is maintained by a team on a best effort basis. As such, vulnerability reports will be investigated and fixed or disclosed as soon as possible. diff --git a/ads/_a4a-config.js b/ads/_a4a-config.js index 9611f0d01f20..9d7aab2fadea 100644 --- a/ads/_a4a-config.js +++ b/ads/_a4a-config.js @@ -11,13 +11,13 @@ import {map} from '#core/types/object'; * Otherwise, it will attempt to render the ad via the existing "3p iframe" * pathway (delay load into a cross-domain iframe). * - * @type {!Object} + * @type {!{[key: string]: function(!Window, !Element): boolean}} */ let a4aRegistry; /** * Returns the a4a registry map - * @return {Object} + * @return {object} */ export function getA4ARegistry() { if (!a4aRegistry) { @@ -27,7 +27,11 @@ export function getA4ARegistry() { 'dianomi': () => true, 'doubleclick': () => true, 'fake': () => true, + 'mgid': (win, adTag) => + !adTag.hasAttribute('data-container') && + !adTag.hasAttribute('data-website'), 'nws': () => true, + 'smartadserver': () => true, 'valueimpression': () => true, // TODO: Add new ad network implementation "is enabled" functions here. // Note: if you add a function here that requires a new "import", above, @@ -42,9 +46,11 @@ export function getA4ARegistry() { /** * An object mapping signing server names to their corresponding URLs. - * @type {!Object} + * @type {!{[key: string]: string}} */ export const signingServerURLs = { + /* eslint-disable local/no-forbidden-terms */ 'google': 'https://cdn.ampproject.org/amp-ad-verifying-keyset.json', 'google-dev': 'https://cdn.ampproject.org/amp-ad-verifying-keyset-dev.json', + /* eslint-enable local/no-forbidden-terms */ }; diff --git a/ads/_config.js b/ads/_config.js index e91d7a43ab6d..560ba24f2e9f 100755 --- a/ads/_config.js +++ b/ads/_config.js @@ -46,7 +46,7 @@ let AdNetworkConfigDef; * fullWidthHeightRatio: number * } * - * @const {!Object} + * @const {!{[key: string]: !JsonObject}} */ const adConfig = jsonConfiguration({ '_ping_': { @@ -62,6 +62,10 @@ const adConfig = jsonConfiguration({ preconnect: 'https://data.24smi.net', }, + '4wmarketplace': { + renderStartImplemented: true, + }, + 'a8': { prefetch: 'https://statics.a8.net/amp/ad.js', renderStartImplemented: true, @@ -97,6 +101,10 @@ const adConfig = jsonConfiguration({ prefetch: 'https://servedbyadbutler.com/app.js', }, + 'adenza': { + renderStartImplemented: true, + }, + 'adform': {}, 'adfox': { @@ -129,6 +137,8 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'admatic': {}, + 'admixer': { renderStartImplemented: true, preconnect: ['https://inv-nets.admixer.net', 'https://cdn.admixer.net'], @@ -166,6 +176,10 @@ const adConfig = jsonConfiguration({ 'adreactor': {}, + 'ads2bid': { + renderStartImplemented: true, + }, + 'adsensor': { prefetch: 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js', clientIdScope: 'amp_ecid_adensor', @@ -228,6 +242,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'adsviu': { + prefetch: 'https://widget.adsviu.com/adsviuAMP.js', + preconnect: ['https://api.adsviu.com'], + }, + 'adunity': { preconnect: ['https://content.adunity.com'], renderStartImplemented: true, @@ -274,6 +293,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'affinity': { + prefetch: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + preconnect: 'https://cdn4-hbs.affinitymatrix.com', + }, + 'aja': { prefetch: [ 'https://cdn.as.amanad.adtdp.com/sdk/asot-amp.js', @@ -326,6 +350,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'appmonsta': { + preconnect: 'https://ssp.appmonsta.ai', + }, + 'appnexus': { prefetch: 'https://acdn.adnxs.com/ast/ast.js', preconnect: 'https://ib.adnxs.com', @@ -354,6 +382,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'bidgear': { + prefetch: 'https://platform.bidgear.com/bidgear-amp.js', + renderStartImplemented: true, + }, + 'bidtellect': {}, 'blade': { @@ -402,6 +435,8 @@ const adConfig = jsonConfiguration({ 'chargeads': {}, // Deprecated, to be removed on 2019-05-23 + 'cognativex': {}, + 'colombia': { prefetch: 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js', }, @@ -430,6 +465,10 @@ const adConfig = jsonConfiguration({ prefetch: 'https://www.google.com/adsense/search/ads.js', }, + 'clever': { + renderStartImplemented: true, + }, + 'dable': { preconnect: [ 'https://static.dable.io', @@ -439,6 +478,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'dex': { + renderStartImplemented: true, + }, + 'digiteka': { renderStartImplemented: true, }, @@ -508,6 +551,8 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'fairground': {}, + 'fake': {}, 'fake-delayed': { @@ -566,6 +611,10 @@ const adConfig = jsonConfiguration({ prefetch: 'https://js.gsspcln.jp/l/amp.js', }, + 'geozo': { + renderStartImplemented: true, + }, + 'giraff': { renderStartImplemented: true, }, @@ -614,6 +663,11 @@ const adConfig = jsonConfiguration({ }, 'improvedigital': {}, + 'incrementx': { + prefetch: 'https://cdn.incrementxserv.com/ixamp.js', + renderStartImplemented: true, + }, + 'industrybrains': { prefetch: 'https://web.industrybrains.com/js/ads/async/show.js', preconnect: [ @@ -639,6 +693,18 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'insurads': { + prefetch: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + preconnect: [ + 'https://tpc.googlesyndication.com', + 'https://cdn.insurads.com', + 'https://services.insurads.com', + 'https://messaging.insurads.com', + ], + renderStartImplemented: true, + consentHandlingOverride: true, + }, + 'invibes': { prefetch: 'https://k.r66net.com/GetAmpLink', renderStartImplemented: true, @@ -664,11 +730,13 @@ const adConfig = jsonConfiguration({ 'kiosked': { renderStartImplemented: true, }, - + 'jioads': { + renderStartImplemented: true, + }, 'jixie': { - prefetch: ['https://scripts.jixie.io/jxamp.min.js'], + prefetch: ['https://scripts.jixie.media/jxamp.min.js'], clientIdScope: '__jxamp', - clientIdCookieName: '_jx', + clientIdCookieName: '_jxx', renderStartImplemented: true, }, @@ -679,15 +747,6 @@ const adConfig = jsonConfiguration({ 'kuadio': {}, - 'lentainform': { - renderStartImplemented: true, - preconnect: [ - 'https://jsc.lentainform.com', - 'https://servicer.lentainform.com', - 'https://s-img.lentainform.com', - ], - }, - 'ligatus': { prefetch: 'https://ssl.ligatus.com/render/ligrend.js', renderStartImplemented: true, @@ -746,7 +805,6 @@ const adConfig = jsonConfiguration({ 'https://marfeel-d.openx.net', 'https://ice.360yield.com', 'https://mbid.marfeelrev.com', - 'https://adservice.google.com', ], consentHandlingOverride: true, }, @@ -814,6 +872,11 @@ const adConfig = jsonConfiguration({ preconnect: ['https://player1.mixpo.com', 'https://player2.mixpo.com'], }, + 'momagic': { + prefetch: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + preconnect: ['https://amp.truereach.co.in/'], + }, + 'monetizer101': { renderStartImplemented: true, }, @@ -933,6 +996,13 @@ const adConfig = jsonConfiguration({ consentHandlingOverride: true, }, + 'piberica': { + preconnect: ['https://trafico.prensaiberica.es'], + renderStartImplemented: true, + }, + + 'pixad': {}, + 'pixels': { prefetch: 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', clientIdCookieName: '__AF', @@ -973,6 +1043,10 @@ const adConfig = jsonConfiguration({ 'pubexchange': {}, + 'pubfuture': { + renderStartImplemented: true, + }, + 'pubguru': { renderStartImplemented: true, }, @@ -987,6 +1061,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'pubscale': { + renderStartImplemented: true, + }, + 'puffnetwork': { prefetch: 'https://static.puffnetwork.com/amp_ad.js', renderStartImplemented: true, @@ -1013,6 +1091,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'r9x': { + prefetch: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + preconnect: 'https://cdn.r9x.in', + }, + 'rakutenunifiedads': { prefetch: 'https://s-cdn.rmp.rakuten.co.jp/js/amp.js', renderStartImplemented: true, @@ -1025,6 +1108,7 @@ const adConfig = jsonConfiguration({ 'rcmwidget': { prefetch: 'https://rcmjs.rambler.ru/static/rcmw/rcmw-amp.js', renderStartImplemented: true, + clientIdScope: '__rcmw_amp', }, 'readmo': { @@ -1101,7 +1185,7 @@ const adConfig = jsonConfiguration({ 'seedingalliance': {}, 'seedtag': { - prefetch: 'https://config.seedtag.com/omid/bridge/bridge.js', + prefetch: 'https://t.seedtag.com/c/loader.js', preconnect: ['https://s.seedtag.com'], consentHandlingOverride: true, renderStartImplemented: true, @@ -1111,6 +1195,12 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'sevio': { + preconnect: ['https://request.adx.ws'], + prefetch: ['https://cdn.adx.ws/scripts/amp.js'], + renderStartImplemented: true, + }, + 'sharethrough': { renderStartImplemented: true, }, @@ -1132,6 +1222,10 @@ const adConfig = jsonConfiguration({ prefetch: 'https://c.imedia.cz/js/amp.js', }, + 'skoiy': { + preconnect: ['https://svas.skoiy.xyz'], + }, + 'slimcutmedia': { preconnect: [ 'https://sb.freeskreen.com', @@ -1220,8 +1314,20 @@ const adConfig = jsonConfiguration({ }, 'sunmedia': { - prefetch: 'https://vod.addevweb.com/sunmedia/amp/ads/sunmedia.js', - preconnect: 'https://static.addevweb.com', + preconnect: [ + 'https://static.sunmedia.tv', + 'https://services.sunmedia.tv', + 'https://creative.sunmedia.tv', + 'https://vod.sunmedia.tv', + 'https://mx-sunmedia.videoplaza.tv', + 'https://es-sunicontent.videoplaza.tv', + 'https://es-sunelespanol.videoplaza.tv', + 'https://es-suncopperland.videoplaza.tv', + 'https://search.spotxchange.com', + 'https://tpc.googlesyndication.com', + ], + clientIdScope: 'AMP_ECID_SUNMEDIA', + fullWidthHeightRatio: 1 / 1, renderStartImplemented: true, }, @@ -1252,10 +1358,9 @@ const adConfig = jsonConfiguration({ }, 'teads': { - prefetch: 'https://s8t.teads.tv/media/format/v3/teads-format.min.js', + prefetch: 'https://a.teads.tv/media/format/v3/teads-format.min.js', preconnect: [ 'https://cdn2.teads.tv', - 'https://a.teads.tv', 'https://t.teads.tv', 'https://r.teads.tv', ], @@ -1343,6 +1448,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'vox': { + renderStartImplemented: true, + }, + 'webediads': { prefetch: 'https://eu1.wbdds.com/amp.min.js', preconnect: ['https://goutee.top', 'https://mediaathay.org.uk'], @@ -1375,6 +1484,7 @@ const adConfig = jsonConfiguration({ 'wunderkind': { preconnect: ['https://tag.wknd.ai', 'https://api.bounceexchange.com'], renderStartImplemented: true, + fullWidthHeightRatio: 4 / 3, }, 'xlift': { @@ -1382,11 +1492,6 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, - 'yahoo': { - prefetch: 'https://s.yimg.com/aaq/ampad/display.js', - preconnect: 'https://us.adserver.yahoo.com', - }, - 'yahoofedads': { renderStartImplemented: true, }, diff --git a/ads/ads.extern.js b/ads/ads.extern.js index ea11210c43c1..084ec9302f34 100644 --- a/ads/ads.extern.js +++ b/ads/ads.extern.js @@ -319,6 +319,23 @@ window.andbeyond.initAmp = function ( extras ) {}; +// affinity.js +window.affinity = {}; +window.affinity.initAMP = function (global, data) {}; +data.width; +data.height; +data.adtype; +data.adslot; +data.slot; +data.affLayout; +data.multiSize; +data.jsontargeting; +data.affSticky; +data.affTitle; +data.affJson; +data.affRtcConfig; +data.extras; + // adreactor.js data.zid; data.pid; @@ -534,6 +551,9 @@ data.local; data.enablemraid; data.jsplayer; +// momagic +window.momagicAmpInit; + // nativo.js var PostRelease; PostRelease.Start; @@ -576,6 +596,12 @@ data.dfpSlot; data.nc; data.auid; +// piberica.js +data.height; +data.publisher; +data.slot; +data.width; + // pixels.js var pixelsAd; var pixelsAMPAd; @@ -616,6 +642,19 @@ data.section; // pulsepoint.js window.PulsePointHeaderTag; +// r9x.js +window.r9x = {}; +window.r9x.initAmp = function ( + global, + width, + height, + siteid, + slotpath, + totalampslots, + jsontargeting, + extras +) {}; + // rubicon.js data.method; data.width; @@ -631,9 +670,6 @@ data.callback; // seedtag.js data.adunitId; -data.placement; -data.publisherId; -data.forceCreative; // sharethrough.js data.pkey; diff --git a/ads/alp/handler.js b/ads/alp/handler.js index 524f7c6086aa..b52b6f7f70ff 100644 --- a/ads/alp/handler.js +++ b/ads/alp/handler.js @@ -1,15 +1,16 @@ +import {closest} from '#core/dom/query'; +import {parseQueryString} from '#core/types/string/url'; + +import {dev} from '#utils/log'; + +import * as urls from '../../src/config/urls'; +import {openWindowDialog} from '../../src/open-window-dialog'; import { addParamToUrl, isLocalhostOrigin, isProxyOrigin, parseUrlDeprecated, } from '../../src/url'; -import {closest} from '#core/dom/query'; -import {dev} from '#utils/log'; -import {dict} from '#core/types/object'; -import {openWindowDialog} from '../../src/open-window-dialog'; -import {parseQueryString} from '#core/types/string/url'; -import {urls} from '../../src/config'; /** * Install a click listener that transforms navigation to the AMP cache @@ -144,11 +145,9 @@ function navigateTo(win, a, url) { if (a2aAncestor) { a2aAncestor.win./*OK*/ postMessage( 'a2a;' + - JSON.stringify( - dict({ - 'url': url, - }) - ), + JSON.stringify({ + 'url': url, + }), a2aAncestor.origin ); return; diff --git a/ads/alp/install-alp.js b/ads/alp/install-alp.js index 0350b02661cd..df4bd74d860b 100644 --- a/ads/alp/install-alp.js +++ b/ads/alp/install-alp.js @@ -1,7 +1,9 @@ // Utility file that generates URLs suitable for AMP's impression tracking. import {initLogConstructor, setReportError} from '#utils/log'; + import {installAlpClickHandler, warmupStatic} from './handler'; + import {reportError} from '../../src/error-reporting'; initLogConstructor(); diff --git a/ads/google/a4a/cookie-utils.js b/ads/google/a4a/cookie-utils.js new file mode 100644 index 000000000000..cab14c824f2f --- /dev/null +++ b/ads/google/a4a/cookie-utils.js @@ -0,0 +1,78 @@ +import {setCookie} from 'src/cookies'; +import {isProxyOrigin} from 'src/url'; + +/** @type {string} */ +export const AMP_GFP_SET_COOKIES_HEADER_NAME = 'amp-ff-set-cookies'; + +/** + * Returns the given domain if the current origin is not the AMP proxy origin, + * otherwise returns the empty string. + * + * On proxy origin, we want cookies to be partitioned by subdomain to prevent + * sharing across unrelated publishers, in which case we want to set the domain + * equal to the empty string (leave it unset). + * + * @param {!Window} win + * @param {string} domain + * @return {string} + */ +function getProxySafeDomain(win, domain) { + return isProxyOrigin(win.location) ? '' : domain; +} + +/** + * @param {!Window} win + * @param {!Response} fetchResponse + */ +export function maybeSetCookieFromAdResponse(win, fetchResponse) { + if (!fetchResponse.headers.has(AMP_GFP_SET_COOKIES_HEADER_NAME)) { + return; + } + let cookiesToSet = /** @type {!Array} */ []; + try { + cookiesToSet = JSON.parse( + fetchResponse.headers.get(AMP_GFP_SET_COOKIES_HEADER_NAME) + ); + } catch {} + for (const cookieInfo of cookiesToSet) { + const cookieName = + (cookieInfo['_version_'] ?? 1) === 2 ? '__gpi' : '__gads'; + const value = cookieInfo['_value_']; + // On proxy origin, we want cookies to be partitioned by subdomain to + // prevent sharing across unrelated publishers, so we don't set a domain. + const domain = getProxySafeDomain(win, cookieInfo['_domain_']); + const expiration = Math.max(cookieInfo['_expiration_'], 0); + setCookie(win, cookieName, value, expiration, { + domain, + secure: false, + }); + } +} + +/** + * Sets up postmessage listener for cookie opt out signal. + * @param {!Window} win + * @param {!Event} event + */ +export function handleCookieOptOutPostMessage(win, event) { + try { + const message = JSON.parse(event.data); + if (message['googMsgType'] === 'gpi-uoo') { + const userOptOut = !!message['userOptOut']; + const clearAdsData = !!message['clearAdsData']; + const domain = getProxySafeDomain(win, win.location.hostname); + setCookie( + win, + '__gpi_opt_out', + userOptOut ? '1' : '0', + // Last valid date for 32-bit browsers; 2038-01-19 + 2147483646 * 1000, + {domain} + ); + if (userOptOut || clearAdsData) { + setCookie(win, '__gads', 'delete', Date.now() - 1000, {domain}); + setCookie(win, '__gpi', 'delete', Date.now() - 1000, {domain}); + } + } + } catch {} +} diff --git a/ads/google/a4a/experiment-utils.js b/ads/google/a4a/experiment-utils.js index a6144e7a1ecb..6ed550f55b13 100644 --- a/ads/google/a4a/experiment-utils.js +++ b/ads/google/a4a/experiment-utils.js @@ -1,10 +1,10 @@ -import {addExperimentIdToElement} from './traffic-experiments'; import { forceExperimentBranch, getExperimentBranch, randomlySelectUnsetExperiments, } from '#experiments'; -import {ExperimentInfoDef} from '#experiments/experiments.type'; + +import {addExperimentIdToElement} from './traffic-experiments'; /** * Attempts to select into experiment and forces branch if selected. diff --git a/ads/google/a4a/line-delimited-response-handler.js b/ads/google/a4a/line-delimited-response-handler.js index 07290e24466d..ac193af2fb14 100644 --- a/ads/google/a4a/line-delimited-response-handler.js +++ b/ads/google/a4a/line-delimited-response-handler.js @@ -55,14 +55,14 @@ 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 + * @param {function(string, !{[key: string]: *}, boolean)} callback * @return {function(string, boolean)} */ export function metaJsonCreativeGrouper(callback) { let first; return function (line, done) { if (first) { - const metadata = /** @type {!Object} */ ( + const metadata = /** @type {!{[key: string]: *}} */ ( tryParseJson(first) || {} ); const lowerCasedMetadata = Object.keys(metadata).reduce((newObj, key) => { diff --git a/ads/google/a4a/shared/content-recommendation.js b/ads/google/a4a/shared/content-recommendation.js index 04d1d27f501e..3c309dee6c01 100644 --- a/ads/google/a4a/shared/content-recommendation.js +++ b/ads/google/a4a/shared/content-recommendation.js @@ -63,7 +63,7 @@ const MAX_PUB_CONTROL_DIMENSION = 1500; * image_card_sidebyside: https://screenshot.googleplex.com/v3qOZY61tFm * text: https://screenshot.googleplex.com/taeRQn7DUhq * text_card: https://screenshot.googleplex.com/ur45m96Tv0D - * @const {!Object} + * @const {!{[key: !LayoutType]: number}} */ const LAYOUT_ASPECT_RATIO_MAP = { [LayoutType.IMAGE_STACKED]: 1 / 1.91, @@ -84,7 +84,7 @@ const LAYOUT_ASPECT_RATIO_MAP = { * FontSize * LineHeight * NumTitle + Padding * 2 + UrlBoxHeight. * image_stacked: https://screenshot.googleplex.com/74S09gFO82b * image_card_stacked: https://screenshot.googleplex.com/vedjTonVaDT - * @const {!Object} + * @const {!{[key: !LayoutType]: number}} */ const LAYOUT_TEXT_HEIGHT_MAP = { [LayoutType.IMAGE_STACKED]: 80, @@ -101,7 +101,7 @@ const LAYOUT_TEXT_HEIGHT_MAP = { /** * The layout - minimal width map for pub control UIs. We will adjust column * numbers according to minimal width. - * @const {!Object} + * @const {!{[key: !LayoutType]: number}} */ const LAYOUT_AD_WIDTH_MAP = { [LayoutType.PUB_CONTROL_IMAGE_STACKED]: 100, diff --git a/ads/google/a4a/shared/url-builder.js b/ads/google/a4a/shared/url-builder.js index 2cfc9663847c..983b9366432c 100644 --- a/ads/google/a4a/shared/url-builder.js +++ b/ads/google/a4a/shared/url-builder.js @@ -5,7 +5,7 @@ export let QueryParameterDef; * Builds a URL from query parameters, truncating to a maximum length if * necessary. * @param {string} baseUrl scheme, domain, and path for the URL. - * @param {!Object} queryParams query parameters for + * @param {!{[key: string]: string|number|null}} queryParams query parameters for * the URL. * @param {number} maxLength length to truncate the URL to if necessary. * @param {?QueryParameterDef=} opt_truncationQueryParam query parameter to diff --git a/ads/google/a4a/test/test-cookie-utils.js b/ads/google/a4a/test/test-cookie-utils.js new file mode 100644 index 000000000000..f1488f570255 --- /dev/null +++ b/ads/google/a4a/test/test-cookie-utils.js @@ -0,0 +1,101 @@ +import { + AMP_GFP_SET_COOKIES_HEADER_NAME, + handleCookieOptOutPostMessage, + maybeSetCookieFromAdResponse, +} from '#ads/google/a4a/cookie-utils'; + +import {getCookie, setCookie} from 'src/cookies'; + +describes.fakeWin('#maybeSetCookieFromAdResponse', {amp: true}, (env) => { + it('should set cookies based on ad response header', () => { + maybeSetCookieFromAdResponse(env.win, { + headers: { + has: (header) => { + return header === AMP_GFP_SET_COOKIES_HEADER_NAME; + }, + get: (header) => { + if (header !== AMP_GFP_SET_COOKIES_HEADER_NAME) { + return; + } + + return JSON.stringify([ + { + '_version_': 1, + '_value_': 'val1', + '_domain_': 'foo.com', + '_expiration_': Date.now() + 100_000, + }, + { + '_version_': 2, + '_value_': 'val2', + '_domain_': 'foo.com', + '_expiration_': Date.now() + 100_000, + }, + ]); + }, + }, + }); + + expect(getCookie(env.win, '__gads')).to.equal('val1'); + expect(getCookie(env.win, '__gpi')).to.equal('val2'); + }); +}); + +describes.fakeWin('#handleCookieOptOutPostMessage', {amp: true}, (env) => { + it('should clear cookies as specified in creative response, with opt out', () => { + setCookie(env.win, '__gads', '__gads_val', Date.now() + 100_000); + setCookie(env.win, '__gpi', '__gpi_val', Date.now() + 100_000); + expect(getCookie(env.win, '__gads')).to.equal('__gads_val'); + expect(getCookie(env.win, '__gpi')).to.equal('__gpi_val'); + + handleCookieOptOutPostMessage(env.win, { + data: JSON.stringify({ + googMsgType: 'gpi-uoo', + userOptOut: true, + clearAdsData: true, + }), + }); + + expect(getCookie(env.win, '__gpi_opt_out')).to.equal('1'); + expect(getCookie(env.win, '__gads')).to.be.null; + expect(getCookie(env.win, '__gpi')).to.be.null; + }); + + it('should clear cookies as specified in creative response, without opt out', () => { + setCookie(env.win, '__gads', '__gads_val', Date.now() + 100_000); + setCookie(env.win, '__gpi', '__gpi_val', Date.now() + 100_000); + expect(getCookie(env.win, '__gads')).to.equal('__gads_val'); + expect(getCookie(env.win, '__gpi')).to.equal('__gpi_val'); + + handleCookieOptOutPostMessage(env.win, { + data: JSON.stringify({ + googMsgType: 'gpi-uoo', + userOptOut: false, + clearAdsData: true, + }), + }); + + expect(getCookie(env.win, '__gpi_opt_out')).to.equal('0'); + expect(getCookie(env.win, '__gads')).to.be.null; + expect(getCookie(env.win, '__gpi')).to.be.null; + }); + + it('should not clear cookies as specified in creative response, without opt out or clear ads', () => { + setCookie(env.win, '__gads', '__gads_val', Date.now() + 100_000); + setCookie(env.win, '__gpi', '__gpi_val', Date.now() + 100_000); + expect(getCookie(env.win, '__gads')).to.equal('__gads_val'); + expect(getCookie(env.win, '__gpi')).to.equal('__gpi_val'); + + handleCookieOptOutPostMessage(env.win, { + data: JSON.stringify({ + googMsgType: 'gpi-uoo', + userOptOut: false, + clearAdsData: false, + }), + }); + + expect(getCookie(env.win, '__gpi_opt_out')).to.equal('0'); + expect(getCookie(env.win, '__gads')).to.equal('__gads_val'); + expect(getCookie(env.win, '__gpi')).to.equal('__gpi_val'); + }); +}); 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 01db3e0d6774..582ca59fa9b3 100644 --- a/ads/google/a4a/test/test-line-delimited-response-handler.js +++ b/ads/google/a4a/test/test-line-delimited-response-handler.js @@ -154,38 +154,28 @@ describes.sandboxed('#line-delimited-response-handler', {}, (env) => { }; }); - // TODO(lannka, #15748): Fails on Safari 11.1.0. - it.configure().skipSafari( - 'should handle empty streamed response properly', - () => { - slotData = []; - setup(); - return executeAndVerifyResponse(); - } - ); + it('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', () => { + it('should handle no fill response properly', () => { slotData = [{headers: {}, creative: ''}]; setup(); return executeAndVerifyResponse(); }); - // 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('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', () => { + it('should stream properly', () => { slotData = [ {headers: {}, creative: ''}, { diff --git a/ads/google/a4a/test/test-traffic-experiments.js b/ads/google/a4a/test/test-traffic-experiments.js index 09e80dd3398c..4f526b3fd21a 100644 --- a/ads/google/a4a/test/test-traffic-experiments.js +++ b/ads/google/a4a/test/test-traffic-experiments.js @@ -1,13 +1,13 @@ -import { - AMP_EXPERIMENT_ATTRIBUTE, - EXPERIMENT_ATTRIBUTE, -} from '#ads/google/a4a/utils'; import { addAmpExperimentIdToElement, addExperimentIdToElement, isInExperiment, validateExperimentIds, } from '#ads/google/a4a/traffic-experiments'; +import { + AMP_EXPERIMENT_ATTRIBUTE, + EXPERIMENT_ATTRIBUTE, +} from '#ads/google/a4a/utils'; describes.sandboxed('all-traffic-experiments-tests', {}, () => { describe('#validateExperimentIds', () => { diff --git a/ads/google/a4a/test/test-utils.js b/ads/google/a4a/test/test-utils.js index 56c4e09aeebd..a81d9263c3a7 100644 --- a/ads/google/a4a/test/test-utils.js +++ b/ads/google/a4a/test/test-utils.js @@ -1,7 +1,8 @@ import * as fakeTimers from '@sinonjs/fake-timers'; + import '../../../../extensions/amp-ad/0.1/amp-ad-ui'; import '../../../../extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler'; -import * as IniLoad from '../../../../src/ini-load'; +import {buildUrl} from '#ads/google/a4a/shared/url-builder'; import { AMP_EXPERIMENT_ATTRIBUTE, EXPERIMENT_ATTRIBUTE, @@ -15,28 +16,29 @@ import { getCorrelator, getCsiAmpAnalyticsVariables, getEnclosingContainerTypes, - getIdentityToken, - getIdentityTokenRequestUrl, getServeNpaPromise, googleAdUrl, groupAmpAdsByType, maybeAppendErrorParameter, - maybeInsertOriginTrialToken, mergeExperimentIds, } from '#ads/google/a4a/utils'; -import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; -import {GEO_IN_GROUP} from '../../../../extensions/amp-geo/0.1/amp-geo-in-group'; -import {MockA4AImpl} from '../../../../extensions/amp-a4a/0.1/test/utils'; -import {Services} from '#service'; -import {buildUrl} from '#ads/google/a4a/shared/url-builder'; + import {createElementWithAttributes} from '#core/dom'; -import {createIframePromise} from '#testing/iframe'; + +import {toggleExperiment} from '#experiments'; + +import {Services} from '#service'; import {installDocService} from '#service/ampdoc-impl'; import {installExtensionsService} from '#service/extensions-impl'; -import {installXhrService} from '#service/xhr-impl'; -import {toggleExperiment} from '#experiments'; + import {user} from '#utils/log'; +import {createIframePromise} from '#testing/iframe'; + +import {MockA4AImpl} from '../../../../extensions/amp-a4a/0.1/test/utils'; +import {GEO_IN_GROUP} from '../../../../extensions/amp-geo/0.1/amp-geo-in-group'; +import * as IniLoad from '../../../../src/ini-load'; + function setupForAdTesting(fixture) { installDocService(fixture.win, /* isSingleDoc */ true); installExtensionsService(fixture.win); @@ -645,13 +647,15 @@ describes.sandboxed('Google A4A utils', {}, (env) => { model: 'Pixel', uaFullVersion: 3.14159, bitness: 42, + fullVersionList: [{brand: 'Chrome', version: '3.14159'}], + wow64: true, }), }, }); return fixture.addElement(elem).then(() => { return googleAdUrl(impl, '', Date.now(), [], []).then((url) => { expect(url).to.match( - /[&?]uap=Windows&uapv=10&uaa=x86&uam=Pixel&uafv=3.14159&uab=42[&$]/ + /[&?]uap=Windows&uapv=10&uaa=x86&uam=Pixel&uafv=3.14159&uab=42&uafvl=%5B%7B%22brand%22%3A%22Chrome%22%2C%22version%22%3A%223.14159%22%7D%5D&uaw=true[&$]/ ); }); }); @@ -681,7 +685,7 @@ describes.sandboxed('Google A4A utils', {}, (env) => { const promise = googleAdUrl(impl, '', Date.now(), [], []).then( (url) => { expect(url).to.not.match( - /[&?]uap=Windows&uapv=10&uaa=x86&uam=Pixel&uafv=3.14159&uab=42[&$]/ + /[&?]uap=Windows&uapv=10&uaa=x86&uam=Pixel&uafv=3.14159&uab=42&uafvl=%5B%7B%22brand%22%3A%22Chrome%22%2C%22version%22%3A%223.14159%22%7D%5D&uaw=true[&$]/ ); } ); @@ -774,228 +778,6 @@ describes.sandboxed('Google A4A utils', {}, (env) => { }); }); - describes.fakeWin('#getIdentityTokenRequestUrl', {}, (env) => { - let doc; - let fakeWin; - beforeEach(() => { - const documentInfoStub = env.sandbox.stub(Services, 'documentInfoForDoc'); - doc = {}; - fakeWin = {location: {}}; - 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' - ); - }); - - 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' - ); - }); - - 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' - ); - }); - - it('should use google ancestor origin based top domain', () => { - 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' - ); - }); - - 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' - ); - }); - }); - - describes.fakeWin( - '#getIdentityToken', - {amp: true, mockFetch: true}, - (env) => { - beforeEach(() => { - installXhrService(env.win); - const documentInfoStub = env.sandbox.stub( - Services, - 'documentInfoForDoc' - ); - documentInfoStub - .withArgs(env.ampdoc) - .returns({canonicalUrl: 'http://f.blah.com?some_site'}); - }); - - afterEach(() => { - // Verify fetch mocks are all consumed. - expect(env.fetchMock.done()).to.be.true; - }); - - const getUrl = (domain) => { - domain = domain || 'google.com'; - return ( - `https:\/\/adservice\.${domain}\/adsid\/integrator\.json\?` + - 'domain=f.blah.com' - ); - }; - - it('should ignore response if required fields are missing', () => { - env.expectFetch(getUrl(), JSON.stringify({newToken: 'abc'})); - return getIdentityToken(env.win, env.ampdoc).then((result) => { - expect(result.token).to.not.be.ok; - expect(result.jar).to.not.be.ok; - expect(result.pucrd).to.not.be.ok; - expect(result.freshLifetimeSecs).to.not.be.ok; - expect(result.validLifetimeSecs).to.not.be.ok; - expect(result.fetchTimeMs).to.be.at.least(0); - }); - }); - - 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', - }) - ); - return getIdentityToken(env.win, env.ampdoc).then((result) => { - expect(result.token).to.equal('abc'); - expect(result.jar).to.equal('some_jar'); - expect(result.pucrd).to.equal('some_pucrd'); - expect(result.freshLifetimeSecs).to.equal(1234); - expect(result.validLifetimeSecs).to.equal(5678); - expect(result.fetchTimeMs).to.be.at.least(0); - }); - }); - - 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', - }) - ); - return getIdentityToken(env.win, env.ampdoc, '').then((result) => { - expect(result.token).to.equal('abc'); - expect(result.jar).to.equal(''); - expect(result.pucrd).to.equal(''); - expect(result.freshLifetimeSecs).to.equal(1234); - expect(result.validLifetimeSecs).to.equal(5678); - expect(result.fetchTimeMs).to.be.at.least(0); - }); - }); - - it('should stop after 1 redirect', () => { - env.expectFetch(getUrl(), JSON.stringify({altDomain: '.google.fr'})); - env.expectFetch( - 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; - expect(result.pucrd).to.not.be.ok; - expect(result.fetchTimeMs).to.be.at.least(0); - }); - }); - - it('should use previous execution', () => { - const ident = { - newToken: 'foo', - freshLifetimeSecs: '1234', - validLifetimeSecs: '5678', - }; - env.win['goog_identity_prom'] = Promise.resolve(ident); - return getIdentityToken(env.win, env.ampdoc).then((result) => - expect(result).to.jsonEqual(ident) - ); - }); - - it('should handle fetch error', () => { - env.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.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') - ); - }); - - it.configure() - .skipFirefox() - .run('should not fetch if INSUFFICIENT consent', () => { - env.sandbox - .stub(Services, 'consentPolicyServiceForDocOrNull') - .returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.INSUFFICIENT, - }) - ); - return expect( - getIdentityToken(env.win, env.ampdoc, 'default') - ).to.eventually.jsonEqual({}); - }); - - it.configure() - .skipFirefox() - .run('should not fetch if UNKNOWN consent', () => { - env.sandbox - .stub(Services, 'consentPolicyServiceForDocOrNull') - .returns( - Promise.resolve({ - whenPolicyResolved: () => CONSENT_POLICY_STATE.UNKNOWN, - }) - ); - return expect( - getIdentityToken(env.win, env.ampdoc, 'default') - ).to.eventually.jsonEqual({}); - }); - } - ); - describe('variables for amp-analytics', () => { let a4a; let ampdoc; @@ -1285,27 +1067,3 @@ describes.realWin('#groupAmpAdsByType', {amp: true}, (env) => { }); }); }); - -describes.realWin('maybeInsertOriginTrialToken', {}, (env) => { - let doc; - let win; - beforeEach(() => { - win = env.win; - doc = win.document; - }); - - it('should insert the token', () => { - expect(doc.querySelector('meta[http-equiv=origin-trial]')).to.not.exist; - maybeInsertOriginTrialToken(win); - expect(doc.querySelector('meta[http-equiv=origin-trial]')).to.exist; - }); - - it('should only insert the token once on multiple calls', () => { - maybeInsertOriginTrialToken(win); - maybeInsertOriginTrialToken(win); - maybeInsertOriginTrialToken(win); - expect( - doc.querySelectorAll('meta[http-equiv=origin-trial]').length - ).to.equal(1); - }); -}); diff --git a/ads/google/a4a/traffic-experiments.js b/ads/google/a4a/traffic-experiments.js index 339a0a8dec04..9f50b69b0405 100644 --- a/ads/google/a4a/traffic-experiments.js +++ b/ads/google/a4a/traffic-experiments.js @@ -6,13 +6,15 @@ * impacts on click-throughs. */ +import {parseQueryString} from '#core/types/string/url'; + +import {Services} from '#service'; + import { AMP_EXPERIMENT_ATTRIBUTE, EXPERIMENT_ATTRIBUTE, mergeExperimentIds, } from './utils'; -import {Services} from '#service'; -import {parseQueryString} from '#core/types/string/url'; /** @typedef {{ * control: string, diff --git a/ads/google/a4a/utils.js b/ads/google/a4a/utils.js index e7ecb80836d3..f1cbc944d0cb 100644 --- a/ads/google/a4a/utils.js +++ b/ads/google/a4a/utils.js @@ -1,21 +1,22 @@ -import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; +import {whenUpgradedToCustomElement} from '#core/dom/amp-element-helpers'; import {DomFingerprint} from '#core/dom/fingerprint'; -import {GEO_IN_GROUP} from '../../../extensions/amp-geo/0.1/amp-geo-in-group'; +import {getPageLayoutBoxBlocking} from '#core/dom/layout/page-layout-box'; +import * as mode from '#core/mode'; +import {parseJson} from '#core/types/object/json'; + +import {getBinaryType, isExperimentOn, toggleExperiment} from '#experiments'; + import {Services} from '#service'; -import {buildUrl} from './shared/url-builder'; +import {getTimingDataSync} from '#service/variable-source'; + import {dev, devAssert, user} from '#utils/log'; -import {dict} from '#core/types/object'; -import {getBinaryType, isExperimentOn, toggleExperiment} from '#experiments'; -import {getConsentPolicyState} from '../../../src/consent'; + +import {buildUrl} from './shared/url-builder'; + +import {GEO_IN_GROUP} from '../../../extensions/amp-geo/0.1/amp-geo-in-group'; +import {getOrCreateAdCid} from '../../../src/ad-cid'; import {getMeasuredResources} from '../../../src/ini-load'; import {getMode} from '../../../src/mode'; -import {getOrCreateAdCid} from '../../../src/ad-cid'; -import {getPageLayoutBoxBlocking} from '#core/dom/layout/page-layout-box'; -import {getTimingDataSync} from '#service/variable-source'; -import * as mode from '#core/mode'; -import {parseJson} from '#core/types/object/json'; -import {whenUpgradedToCustomElement} from '#core/dom/amp-element-helpers'; -import {createElementWithAttributes} from '#core/dom'; /** @type {string} */ const AMP_ANALYTICS_HEADER = 'X-AmpAnalytics'; @@ -39,8 +40,8 @@ export const ValidAdContainerTypes = { }; /** - * See `VisibilityState` enum. - * @const {!Object} + * See `VisibilityState_Enum` enum. + * @const {!{[key: string]: string}} */ const visibilityStateCodes = { 'visible': '1', @@ -94,30 +95,10 @@ export let NameframeExperimentConfig; */ export const TRUNCATION_PARAM = {name: 'trunc', value: '1'}; -/** @const {Object} */ +/** @const {object} */ const CDN_PROXY_REGEXP = /^https:\/\/([a-zA-Z0-9_-]+\.)?cdn\.ampproject\.org((\/.*)|($))+/; -/** @const {string} */ -export const TOKEN_VALUE = - 'A2dINGotLJuPqM6Wgp0s4V3te749O/VZEHqN0YsG4pfY+1pcjS5UaX1Bvcyz4aiShd8ZXPcT5spJazIzrbi5AwUAAACVeyJvcmlnaW4iOiJodHRwczovL2FtcHByb2plY3Qub3JnOjQ0MyIsImZlYXR1cmUiOiJDb252ZXJzaW9uTWVhc3VyZW1lbnQiLCJleHBpcnkiOjE2MzQwODMxOTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWUsInVzYWdlIjoic3Vic2V0In0='; - -/** - * Inserts origin-trial token for `attribution-reporting` if not already - * present in the DOM. - * @param {!Window} win - */ -export function maybeInsertOriginTrialToken(win) { - if (win.document.head.querySelector(`meta[content='${TOKEN_VALUE}']`)) { - return; - } - const metaEl = createElementWithAttributes(win.document, 'meta', { - 'http-equiv': 'origin-trial', - content: TOKEN_VALUE, - }); - win.document.head.appendChild(metaEl); -} - /** * Returns the value of some navigation timing parameter. * Feature detection is used for safety on browsers that do not support the @@ -196,7 +177,7 @@ export function isReportingEnabled(ampElement) { * @param {!Array=} opt_experimentIds Any experiments IDs (in addition * to those specified on the ad element) that should be included in the * request. - * @return {!Object} block level parameters + * @return {!{[key: string]: null|number|string}} block level parameters */ export function googleBlockParameters(a4a, opt_experimentIds) { const {element: adElement, win} = a4a; @@ -231,7 +212,7 @@ export function googleBlockParameters(a4a, opt_experimentIds) { * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampdoc * @param {string} type matching typing attribute. * @param {function(!Element):string} groupFn - * @return {!Promise>>>} + * @return {!Promise>>} */ export function groupAmpAdsByType(ampdoc, type, groupFn) { // Look for amp-ad elements of correct type or those contained within @@ -284,7 +265,7 @@ export function groupAmpAdsByType(ampdoc, type, groupFn) { /** * @param {! ../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a * @param {number} startTime - * @return {!Promise>} + * @return {!Promise} */ export function googlePageParameters(a4a, startTime) { const {win} = a4a; @@ -364,6 +345,8 @@ export function googlePageParameters(a4a, startTime) { 'uam': uaDataValues?.model, 'uafv': uaDataValues?.uaFullVersion, 'uab': uaDataValues?.bitness, + 'uafvl': JSON.stringify(uaDataValues?.fullVersionList), + 'uaw': uaDataValues?.wow64, }; }); } @@ -372,7 +355,7 @@ export function googlePageParameters(a4a, startTime) { * @param {!../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a * @param {string} baseUrl * @param {number} startTime - * @param {!Object} parameters + * @param {!{[key: string]: null|number|string}} parameters * @param {!Array=} opt_experimentIds Any experiments IDs (in addition * to those specified on the ad element) that should be included in the * request. @@ -395,7 +378,7 @@ export function googleAdUrl( /** * @param {string} baseUrl - * @param {!Object} parameters + * @param {!{[key: string]: null|number|string}} parameters * @param {number} startTime * @return {string} */ @@ -582,11 +565,11 @@ export function additionalDimensions(win, viewportSize) { /** * Returns amp-analytics config for a new CSI trigger. * @param {string} on The name of the analytics trigger. - * @param {!Object} params Params to be included on the ping. + * @param {!{[key: string]: string}} params Params to be included on the ping. * @return {!JsonObject} */ function csiTrigger(on, params) { - return dict({ + return { 'on': on, 'request': 'csi', 'sampleSpec': { @@ -599,7 +582,7 @@ function csiTrigger(on, params) { 'selector': 'amp-ad', 'selectionMethod': 'closest', 'extraUrlParams': params, - }); + }; } /** @@ -607,7 +590,7 @@ function csiTrigger(on, params) { * @return {!JsonObject} */ export function getCsiAmpAnalyticsConfig() { - return dict({ + return { 'requests': { 'csi': 'https://csi.gstatic.com/csi?', }, @@ -643,7 +626,7 @@ export function getCsiAmpAnalyticsConfig() { // evaluated when the URL is built by amp-analytics. 'puid': '${requestCount}~${timestamp}', }, - }); + }; } /** @@ -708,11 +691,11 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { if (!hasActiveViewRequests && !hasBeginToRenderRequests) { return null; } - const config = dict({ + const config = { 'transport': {'beacon': false, 'xhrpost': false}, 'requests': {}, 'triggers': {}, - }); + }; if (hasActiveViewRequests) { generateActiveViewRequest(config, acUrls); } @@ -736,7 +719,7 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { * @param {!Array} urls */ function generateActiveViewRequest(config, urls) { - config['triggers']['continuousVisible'] = dict({ + config['triggers']['continuousVisible'] = { 'request': [], 'on': 'visible', 'visibilitySpec': { @@ -745,7 +728,7 @@ function generateActiveViewRequest(config, urls) { 'visiblePercentageMin': 50, 'continuousTimeMin': 1000, }, - }); + }; for (let idx = 0; idx < urls.length; idx++) { // TODO: Ensure url is valid and not freeform JS? config['requests'][`visibility${idx + 1}`] = `${urls[idx]}`; @@ -760,12 +743,12 @@ function generateActiveViewRequest(config, urls) { * @param {!Array} urls */ function generateBeginToRenderRequest(config, urls) { - config['triggers']['beginToRender'] = dict({ + config['triggers']['beginToRender'] = { 'request': [], 'on': 'ini-load', 'selector': 'amp-ad', 'selectionMethod': 'closest', - }); + }; for (let idx = 0; idx < urls.length; idx++) { // TODO: Ensure url is valid and not freeform JS? @@ -936,130 +919,6 @@ export function getBinaryTypeNumericalCode(type) { ); } -/** @const {!RegExp} */ -const IDENTITY_DOMAIN_REGEXP_ = /\.google\.(?:com?\.)?[a-z]{2,3}$/; - -/** @typedef {{ - token: (string|undefined), - jar: (string|undefined), - pucrd: (string|undefined), - freshLifetimeSecs: (number|undefined), - validLifetimeSecs: (number|undefined), - fetchTimeMs: (number|undefined) - }} */ -export let IdentityToken; - -/** - * @param {!Window} win - * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampDoc - * @param {?string} consentPolicyId - * @return {!Promise} - */ -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']); -} - -/** - * @param {!Window} win - * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampDoc - * @param {number=} redirectsRemaining (default 1) - * @param {string=} domain - * @param {number=} startTime - * @return {!Promise} - */ -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, - }; - } - // returning empty - return {fetchTimeMs}; - }) - .catch((unusedErr) => { - // TODO log? - return {}; - }); -} - -/** - * @param {!Window} win - * @param {!../../../src/service/ampdoc-impl.AmpDoc} ampDoc - * @param {string=} domain - * @return {string} url - * @visibleForTesting - */ -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] - ); - domain = (matches && matches[0]) || undefined; - } - domain = domain || '.google.com'; - const canonical = extractHost( - Services.documentInfoForDoc(ampDoc).canonicalUrl - ); - return `https://adservice${domain}/adsid/integrator.json?domain=${canonical}`; -} - /** * Returns whether we are running on the AMP CDN. * @param {!Window} win @@ -1090,7 +949,7 @@ export function setNameframeExperimentConfigs(headers, nameframeConfig) { * than 32 capabilities to this enum. * @enum {number} */ -const Capability = { +const Capability_Enum = { SVG_SUPPORTED: 1 << 0, SANDBOXING_ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION_SUPPORTED: 1 << 1, SANDBOXING_ALLOW_POPUPS_TO_ESCAPE_SANDBOX_SUPPORTED: 1 << 2, @@ -1105,17 +964,17 @@ function getBrowserCapabilitiesBitmap(win) { let browserCapabilities = 0; const doc = win.document; if (win.SVGElement && doc.createElementNS) { - browserCapabilities |= Capability.SVG_SUPPORTED; + browserCapabilities |= Capability_Enum.SVG_SUPPORTED; } const iframeEl = doc.createElement('iframe'); if (iframeEl.sandbox && iframeEl.sandbox.supports) { if (iframeEl.sandbox.supports('allow-top-navigation-by-user-activation')) { browserCapabilities |= - Capability.SANDBOXING_ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION_SUPPORTED; + Capability_Enum.SANDBOXING_ALLOW_TOP_NAVIGATION_BY_USER_ACTIVATION_SUPPORTED; } if (iframeEl.sandbox.supports('allow-popups-to-escape-sandbox')) { browserCapabilities |= - Capability.SANDBOXING_ALLOW_POPUPS_TO_ESCAPE_SANDBOX_SUPPORTED; + Capability_Enum.SANDBOXING_ALLOW_POPUPS_TO_ESCAPE_SANDBOX_SUPPORTED; } } return browserCapabilities; diff --git a/ads/google/ima/OWNERS b/ads/google/ima/OWNERS index cae983b4cc00..ce67a2eecfaf 100644 --- a/ads/google/ima/OWNERS +++ b/ads/google/ima/OWNERS @@ -5,7 +5,6 @@ rules: [ { owners: [ - {name: 'ampproject/wg-bento'}, {name: 'ampproject/wg-components'}, {name: 'ampproject/wg-monetization'}, ], diff --git a/ads/google/ima/ima-video.js b/ads/google/ima/ima-video.js index 030345f722a6..bbc4ab0c6f28 100644 --- a/ads/google/ima/ima-video.js +++ b/ads/google/ima/ima-video.js @@ -1,13 +1,22 @@ -import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; -import {ImaPlayerData} from './ima-player-data'; -import {camelCaseToTitleCase, setStyle, toggle} from '#core/dom/style'; -import {getData} from '#utils/event-helper'; +import {loadScript} from '#3p/3p'; + +import { + CONSENT_POLICY_STATE, + CONSENT_STRING_TYPE, +} from '#core/constants/consent-state'; import {htmlFor, htmlRefs, svgFor} from '#core/dom/static-template'; +import {camelCaseToTitleCase, setStyle, toggle} from '#core/dom/style'; import {isArray, isObject} from '#core/types'; -import {loadScript} from '#3p/3p'; import {throttle} from '#core/types/function'; import {tryParseJson} from '#core/types/object/json'; + +import {getData} from '#utils/event-helper'; + // Source for this constant is css/amp-ima-video-iframe.css +import {addParamToUrl} from 'src/url'; + +import {ImaPlayerData} from './ima-player-data'; + import {cssText} from '../../../build/amp-ima-video-iframe.css'; /** @@ -173,8 +182,8 @@ let adsRequested; // Flag that tracks if the user tapped and dragged on the overlay button. let userTappedAndDragged; -// User consent state. -let consentState; +// global context +let context; // Throttle for the showControls() function let showControlsThrottled = throttle(window, showControls, 1000); @@ -205,7 +214,7 @@ function toggleRootDataAttribute(state, active) { /** * @param {Document} elementOrDoc - * @return {!Object} + * @return {!{[key: string]: !Element}} */ function renderElements(elementOrDoc) { const html = htmlFor(elementOrDoc); @@ -322,6 +331,8 @@ function maybeAppendChildren(document, parent, childrenDef) { * @param {!Object} data */ export function imaVideo(global, data) { + context = global.context; + insertCss(global.document.head, cssText); videoWidth = global./*OK*/ innerWidth; @@ -430,10 +441,7 @@ export function imaVideo(global, data) { ); }); - consentState = global.context.initialConsentState; - - if (consentState == 4) { - // UNKNOWN + if (context.initialConsentState == CONSENT_POLICY_STATE.UNKNOWN) { // On unknown consent state, do not load IMA. Treat this the same as if IMA // failed to load. onImaLoadFail(); @@ -549,7 +557,7 @@ function onImaLoadSuccess(global, data) { requestAds(); } else { // Let amp-ima-video know that we are done set-up. - postMessage({event: VideoEvents.LOAD}); + postMessage({event: VideoEvents_Enum.LOAD}); } } @@ -564,7 +572,7 @@ function onImaLoadFail() { showControlsThrottled ); imaLoadAllowed = false; - postMessage({event: VideoEvents.LOAD}); + postMessage({event: VideoEvents_Enum.LOAD}); } /** @@ -622,16 +630,40 @@ function onOverlayButtonTouchMove() { export function requestAds() { adsRequested = true; adRequestFailed = false; - if (consentState == CONSENT_POLICY_STATE.UNKNOWN) { + const {initialConsentState} = context; + if (initialConsentState == CONSENT_POLICY_STATE.UNKNOWN) { // We're unaware of the user's consent state - do not request ads. imaLoadAllowed = false; return; - } else if (consentState == CONSENT_POLICY_STATE.INSUFFICIENT) { + } + adsRequest.adTagUrl = addParamsToAdTagUrl(adsRequest.adTagUrl); + adsLoader.requestAds(adsRequest); +} + +/** + * @param {string} url + * @return {string} + */ +function addParamsToAdTagUrl(url) { + const {initialConsentMetadata, initialConsentState, initialConsentValue} = + context; + if (initialConsentState == CONSENT_POLICY_STATE.INSUFFICIENT) { // User has provided consent state but has not consented to personalized // ads. - adsRequest.adTagUrl += '&npa=1'; + url = addParamToUrl(url, 'npa', '1'); } - adsLoader.requestAds(adsRequest); + const {additionalConsent, consentStringType} = initialConsentMetadata || {}; + const isGdpr = + consentStringType != null && + consentStringType !== CONSENT_STRING_TYPE.US_PRIVACY_STRING; + if (isGdpr && initialConsentValue != null) { + url = addParamToUrl(url, 'gdpr', '1'); + url = addParamToUrl(url, 'gdpr_consent', initialConsentValue); + } + if (additionalConsent != null) { + url = addParamToUrl(url, 'addtl_consent', additionalConsent); + } + return url; } /** @@ -688,8 +720,8 @@ export function onContentEnded() { toggle(elements['overlayButton'], true); } - postMessage({event: VideoEvents.PAUSE}); - postMessage({event: VideoEvents.ENDED}); + postMessage({event: VideoEvents_Enum.PAUSE}); + postMessage({event: VideoEvents_Enum.ENDED}); } /** @@ -737,7 +769,7 @@ export function onAdsManagerLoaded(global, adsManagerLoadedEvent) { if (muteAdsManagerOnLoaded) { adsManager.setVolume(0); } - postMessage({event: VideoEvents.LOAD}); + postMessage({event: VideoEvents_Enum.LOAD}); } /** @@ -750,7 +782,7 @@ export function onAdsLoaderError() { // Send this message to trigger auto-play for failed pre-roll requests - // 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}); + postMessage({event: VideoEvents_Enum.LOAD}); addHoverEventToElement( /** @type {!Element} */ (elements['video']), showControlsThrottled @@ -766,7 +798,7 @@ export function onAdsLoaderError() { * @visibleForTesting */ export function onAdError() { - postMessage({event: VideoEvents.AD_END}); + postMessage({event: VideoEvents_Enum.AD_END}); currentAd = null; if (adsManager) { adsManager.destroy(); @@ -824,7 +856,7 @@ export function onContentPauseRequested(global) { } adsActive = true; playerState = PlayerStates.PLAYING; - postMessage({event: VideoEvents.AD_START}); + postMessage({event: VideoEvents_Enum.AD_START}); toggle(elements['adContainer'], true); showAdControls(); @@ -849,7 +881,7 @@ export function onContentResumeRequested() { /** @type {!Element} */ (video), showControlsThrottled ); - postMessage({event: VideoEvents.AD_END}); + postMessage({event: VideoEvents_Enum.AD_END}); resetControlsAfterAd(); if (!contentComplete) { // CONTENT_RESUME will fire after post-rolls as well, and we don't want to @@ -1096,7 +1128,7 @@ export function playVideo() { video.play(); } playerState = PlayerStates.PLAYING; - postMessage({event: VideoEvents.PLAYING}); + postMessage({event: VideoEvents_Enum.PLAYING}); toggleRootDataAttribute('playing', true); } @@ -1121,7 +1153,7 @@ export function pauseVideo(event = null) { } } playerState = PlayerStates.PAUSED; - postMessage({event: VideoEvents.PAUSE}); + postMessage({event: VideoEvents_Enum.PAUSE}); toggleRootDataAttribute('playing', false); } @@ -1168,11 +1200,13 @@ export function toggleMuted(video, muted) { muteAdsManagerOnLoaded = muted; } toggleRootDataAttribute('muted', muted); - postMessage({event: muted ? VideoEvents.MUTED : VideoEvents.UNMUTED}); + postMessage({ + event: muted ? VideoEvents_Enum.MUTED : VideoEvents_Enum.UNMUTED, + }); } /** - * @param {Object} global + * @param {object} global */ function exitFullscreen(global) { // The video is currently in fullscreen mode @@ -1187,7 +1221,7 @@ function exitFullscreen(global) { } /** - * @param {Object} global + * @param {object} global */ function enterFullscreen(global) { // Try to enter fullscreen mode in the browser @@ -1216,7 +1250,7 @@ function enterFullscreen(global) { } /** - * @param {Object} global + * @param {object} global */ function toggleFullscreen(global) { if (fullscreen) { @@ -1228,7 +1262,7 @@ function toggleFullscreen(global) { /** * Called when the fullscreen mode of the browser or content player changes. - * @param {Object} global + * @param {object} global */ function onFullscreenChange(global) { if (fullscreen) { @@ -1609,12 +1643,13 @@ export function setHideControlsTimeoutForTesting(newTimeout) { } /** - * Sets the consent state. - * @param {*} newConsentState + * @param {object} newContext * @visibleForTesting */ -export function setConsentStateForTesting(newConsentState) { - consentState = newConsentState; +export function setContextForTesting(newContext) { + for (const k in newContext) { + context[k] = newContext[k]; + } } /** @@ -1622,10 +1657,10 @@ export function setConsentStateForTesting(newConsentState) { * * Copied from src/video-interface.js. * - * @const {!Object} + * @enum {string} */ // TODO(aghassemi, #9216): Use video-interface.js -const VideoEvents = { +const VideoEvents_Enum = { /** * load * diff --git a/ads/google/utils.js b/ads/google/utils.js index e51735b4e594..c46c034f3cc1 100644 --- a/ads/google/utils.js +++ b/ads/google/utils.js @@ -1,10 +1,11 @@ +import {user} from '#utils/log'; + import { ExternalCorePubVars, MIN_PUB_CONTROL_WIDTH_OF_DESKTOP, getAutoConfig, getPubControlConfig, } from './a4a/shared/content-recommendation'; -import {user} from '#utils/log'; /** * Approved height for AdSense full-width responsive ads. diff --git a/ads/inabox/frame-overlay-helper.js b/ads/inabox/frame-overlay-helper.js index 06fe2519a33b..751b3a452396 100644 --- a/ads/inabox/frame-overlay-helper.js +++ b/ads/inabox/frame-overlay-helper.js @@ -1,14 +1,11 @@ -import { - LayoutRectDef, - layoutRectFromDomRect, - layoutRectLtwh, -} from '#core/dom/layout/rect'; +import {layoutRectFromDomRect, layoutRectLtwh} from '#core/dom/layout/rect'; +import {resetStyles, setImportantStyles} from '#core/dom/style'; + import { centerFrameUnderVsyncMutate, collapseFrameUnderVsyncMutate, expandFrameUnderVsyncMutate, } from './full-overlay-frame-helper'; -import {resetStyles, setImportantStyles} from '#core/dom/style'; import {restrictedVsync, timer} from './util'; const CENTER_TRANSITION_TIME_MS = 150; diff --git a/ads/inabox/inabox-host.js b/ads/inabox/inabox-host.js index bfe15e55f88a..93c00fa7546e 100644 --- a/ads/inabox/inabox-host.js +++ b/ads/inabox/inabox-host.js @@ -3,9 +3,11 @@ * its embed AMP content (such as an ad created in AMP). */ -import {InaboxMessagingHost} from './inabox-messaging-host'; -import {dev, initLogConstructor, setReportError, user} from '#utils/log'; import {getData} from '#utils/event-helper'; +import {dev, initLogConstructor, setReportError, user} from '#utils/log'; + +import {InaboxMessagingHost} from './inabox-messaging-host'; + import {reportError} from '../../src/error-reporting'; /** @const {string} */ diff --git a/ads/inabox/inabox-messaging-host.js b/ads/inabox/inabox-messaging-host.js index 60cb766179f3..3e848bf797a9 100644 --- a/ads/inabox/inabox-messaging-host.js +++ b/ads/inabox/inabox-messaging-host.js @@ -1,20 +1,22 @@ import { - MessageType, + MessageType_Enum, deserializeMessage, serializeMessage, } from '#core/3p-frame-messaging'; -import {canInspectWindow} from '../../src/iframe-helper'; -import {dev, devAssert} from '#utils/log'; -import {dict} from '#core/types/object'; + import {getData} from '#utils/event-helper'; +import {dev, devAssert} from '#utils/log'; + import {getFrameOverlayManager} from './frame-overlay-manager'; import {getPositionObserver} from './position-observer'; +import {canInspectWindow} from '../../src/iframe-helper'; + /** @const */ const TAG = 'InaboxMessagingHost'; /** @const */ -const READ_ONLY_MESSAGES = [MessageType.SEND_POSITIONS]; +const READ_ONLY_MESSAGES = [MessageType_Enum.SEND_POSITIONS]; /** Simple helper for named callbacks. */ class NamedObservable { @@ -22,7 +24,7 @@ class NamedObservable { * Creates an instance of NamedObservable. */ constructor() { - /** @private {!Object} */ + /** @private {!{[key: string]: !Function}} */ this.map_ = {}; } @@ -70,7 +72,7 @@ export class InaboxMessagingHost { /** @private {!Array} */ this.iframes_ = iframes; - /** @private {!Object} */ + /** @private {!{[key: string]: !AdFrameDef}} */ this.iframeMap_ = Object.create(null); /** @private {!./position-observer.PositionObserver} */ @@ -83,17 +85,17 @@ export class InaboxMessagingHost { this.frameOverlayManager_ = getFrameOverlayManager(hostWin); this.msgObservable_.listen( - MessageType.SEND_POSITIONS, + MessageType_Enum.SEND_POSITIONS, this.handleSendPositions_ ); this.msgObservable_.listen( - MessageType.FULL_OVERLAY_FRAME, + MessageType_Enum.FULL_OVERLAY_FRAME, this.handleEnterFullOverlay_ ); this.msgObservable_.listen( - MessageType.CANCEL_FULL_OVERLAY_FRAME, + MessageType_Enum.CANCEL_FULL_OVERLAY_FRAME, this.handleCancelFullOverlay_ ); } @@ -156,14 +158,10 @@ export class InaboxMessagingHost { handleSendPositions_(iframe, request, source, unusedOrigin) { const viewportRect = this.positionObserver_.getViewportRect(); const targetRect = this.positionObserver_.getTargetRect(iframe); - this.sendPosition_( - request, - source, - dict({ - 'viewportRect': viewportRect, - 'targetRect': targetRect, - }) - ); + this.sendPosition_(request, source, { + 'viewportRect': viewportRect, + 'targetRect': targetRect, + }); devAssert(this.iframeMap_[request.sentinel]); this.iframeMap_[request.sentinel].observeUnregisterFn = @@ -183,7 +181,7 @@ export class InaboxMessagingHost { sendPosition_(request, source, data) { dev().fine(TAG, 'Sent position data to [%s] %s', request.sentinel, data); source./*OK*/ postMessage( - serializeMessage(MessageType.POSITION, request.sentinel, data), + serializeMessage(MessageType_Enum.POSITION, request.sentinel, data), // We don't need to restrict what origin we send the data to because (a) // we've already verified that this iframe is allowed to learn its position, // and (b) we're post messaging back directly to the requesting frame. @@ -207,12 +205,12 @@ export class InaboxMessagingHost { this.frameOverlayManager_.expandFrame(iframe, (boxRect) => { source./*OK*/ postMessage( serializeMessage( - MessageType.FULL_OVERLAY_FRAME_RESPONSE, + MessageType_Enum.FULL_OVERLAY_FRAME_RESPONSE, request.sentinel, - dict({ + { 'success': true, 'boxRect': boxRect, - }) + } ), origin ); @@ -232,12 +230,12 @@ export class InaboxMessagingHost { this.frameOverlayManager_.collapseFrame(iframe, (boxRect) => { source./*OK*/ postMessage( serializeMessage( - MessageType.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, + MessageType_Enum.CANCEL_FULL_OVERLAY_FRAME_RESPONSE, request.sentinel, - dict({ + { 'success': true, 'boxRect': boxRect, - }) + } ), origin ); diff --git a/ads/inabox/position-observer.js b/ads/inabox/position-observer.js index 1a2d29fddf76..98a4aa0252f9 100644 --- a/ads/inabox/position-observer.js +++ b/ads/inabox/position-observer.js @@ -1,10 +1,9 @@ +import {Observable} from '#core/data-structures/observable'; import { - LayoutRectDef, layoutRectFromDomRect, layoutRectLtwh, moveLayoutRect, } from '#core/dom/layout/rect'; -import {Observable} from '#core/data-structures/observable'; import {throttle} from '#core/types/function'; /** @@ -15,6 +14,8 @@ import {throttle} from '#core/types/function'; */ let PositionEntryDef; +/** @typedef {import('#core/dom/layout/rect').LayoutRectDef} LayoutRectDef */ + /** @const */ const MIN_EVENT_INTERVAL_IN_MS = 100; diff --git a/ads/vendors/4wmarketplace.js b/ads/vendors/4wmarketplace.js new file mode 100644 index 000000000000..9cfb384cce90 --- /dev/null +++ b/ads/vendors/4wmarketplace.js @@ -0,0 +1,75 @@ +import {loadScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function _4wmarketplace(global, data) { + const $4wm = global; + const containerDiv = $4wm.document.createElement('div'); + const containerId = 'fwcontainer'; + containerDiv.id = containerId; + $4wm.document.getElementById('c').appendChild(containerDiv); + + if (typeof data.adtype != 'undefined' && data.adtype == 'ad-custom') { + $4wm.fwtag = { + cmd: [], + }; + $4wm.fwtag.cmd.push(function () { + $4wm.fwtag.addSlotNative({ + id: containerId, + sid: data.sid, + n: data.n ? data.n : null, + hd: data.hd ? data.hd : null, + r: data.r ? data.r : null, + imgsize: data.imgsize ? data.imgsize : null, + excludeMobCss: data.excludemobcss ? data.excludemobcss : null, + css: data.css ? data.css : null, + class: data.class ? data.class : null, + size: data.size ? data.size : null, + amp: 1, + }); + }); + loadScript($4wm, 'https://static-adsr.4wnetwork.com/js/fwloader.js', () => { + window.addEventListener('message', (e) => { + if ( + e.data.message == 'RESIZE_AMP' && + typeof e.data.height != 'undefined' + ) { + $4wm.context.requestResize(undefined, e.data.height); + } + }); + }); + } else { + const obj = { + cid: containerId, + ic: data.id, + amp: 1, + format: data.format ? data.format : null, + position: data.position ? data.position : null, + dim: data.dim ? data.dim : null, + nid: data.nid ? data.nid : null, + }; + for (const key in obj) { + if (obj[key] == null) { + delete obj[key]; + } + } + + $4wm.objFw = []; + $4wm.objFw.push(obj); + loadScript($4wm, 'https://static.4wnetwork.com/js/sdk.min.js', () => { + window.addEventListener('message', (e) => { + if ( + e.data.message == 'RESIZE_AMP' && + typeof e.data.height != 'undefined' + ) { + $4wm.context.requestResize(undefined, e.data.height); + } + if (e.data.message == 'CLOSE_AMP_STILL') { + $4wm.context.noContentAvailable(); + } + }); + }); + } +} diff --git a/ads/vendors/4wmarketplace.md b/ads/vendors/4wmarketplace.md new file mode 100644 index 000000000000..7552b87a5be2 --- /dev/null +++ b/ads/vendors/4wmarketplace.md @@ -0,0 +1,28 @@ +# 4wMarketplace + +4wMarketplace support for AMP covers Passback technologies. + +For configuration details and to generate your tags, please refer to [your publisher account](https://publisher.4wmarketplace.com) + +## Example - Passback + +html + + +## Configuration + +The ad size is based on the setup of your 4wMarketplace space. The `width` and `height` attributes of the `amp-ad` tag should match that. + +### Passback + +Supported parameters: + +- `data-dim`: identifies the tag dimensions. Required. +- `data-id`: your 4wMarketplace space identifier. Required. diff --git a/ads/vendors/_fakedelayed_.js b/ads/vendors/_fakedelayed_.js index 5f064bc549fe..61ce8190aa22 100644 --- a/ads/vendors/_fakedelayed_.js +++ b/ads/vendors/_fakedelayed_.js @@ -1,6 +1,7 @@ -import {setStyles} from '#core/dom/style'; import {validateData, writeScript} from '#3p/3p'; +import {setStyles} from '#core/dom/style'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/_ping_.js b/ads/vendors/_ping_.js index fd0276167e06..a81caf9af7da 100644 --- a/ads/vendors/_ping_.js +++ b/ads/vendors/_ping_.js @@ -1,6 +1,7 @@ -import {dev, devAssert, userAssert} from '#utils/log'; import {validateData} from '#3p/3p'; +import {dev, devAssert, userAssert} from '#utils/log'; + /** * A fake ad network integration that is mainly used for testing * and demo purposes. This implementation gets stripped out in compiled diff --git a/ads/vendors/a9.js b/ads/vendors/a9.js index 042e784abe0c..9c996b5d8ee4 100644 --- a/ads/vendors/a9.js +++ b/ads/vendors/a9.js @@ -1,5 +1,6 @@ -import {hasOwn} from '#core/types/object'; import {loadScript, validateData, writeScript} from '#3p/3p'; + +import {hasOwn} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; const mandatoryParams = [], diff --git a/ads/vendors/adenza.js b/ads/vendors/adenza.js new file mode 100644 index 000000000000..1292d96d3fba --- /dev/null +++ b/ads/vendors/adenza.js @@ -0,0 +1,65 @@ +import {validateData, writeScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adenza(global, data) { + validateData(data, ['blockId']); + + const url = + 'https://adenza.network/network/data/teasers/' + + encodeURIComponent(data['blockId']) + + '/script?async=1&div=c'; + + const mainBlock = global.document.getElementById('c'); + const insertionBlock = global.document.createElement('div'); + insertionBlock.setAttribute('id', 'pw-net-' + data.blockId); + mainBlock.append(insertionBlock); + + window.context.observeIntersection(function (changes) { + changes.forEach(function () { + const findIframe = global.document + .getElementById('c') + .querySelector('iframe'); + if (findIframe) { + const styleString = findIframe.getAttribute('style'); + const height = getStyleAdenza(styleString, 'height'); + if (height) { + window.context.requestResize(undefined, height); + } + } + }); + }); + + writeScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} + +/** + * @param {string} style + * @param {string} prop + * @return {string} + */ +function getStyleAdenza(style, prop) { + const styleArr = style.split(';'); + const res = styleArr.filter(function (value) { + return value.includes(prop); + }); + if (res) { + return res + .join('') + .replace(prop + ':', '') + .replace(/\s+/g, ''); + } else { + return '480px'; + } +} diff --git a/ads/vendors/adenza.md b/ads/vendors/adenza.md new file mode 100644 index 000000000000..c64f489d9e06 --- /dev/null +++ b/ads/vendors/adenza.md @@ -0,0 +1,19 @@ +# Adenza + +## Example + +```html + +``` + +## Configuration + +For more information, please [see FAQ](https://adenza.network/faq). + +Required parameters: + +- width +- height +- type +- layout +- data-block-id diff --git a/ads/vendors/adincube.js b/ads/vendors/adincube.js index dba5a35322b1..47d8ad6c2276 100644 --- a/ads/vendors/adincube.js +++ b/ads/vendors/adincube.js @@ -1,6 +1,7 @@ -import {hasOwn} from '#core/types/object'; import {loadScript, validateData} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/adman.js b/ads/vendors/adman.js index aecdb8258840..7381c211f800 100644 --- a/ads/vendors/adman.js +++ b/ads/vendors/adman.js @@ -5,15 +5,24 @@ import {validateData} from '#3p/3p'; * @param {!Object} data */ export function adman(global, data) { - validateData(data, ['ws', 'host', 's'], []); - + validateData(data, ['ws', 'host'], []); + const {host, s, ws} = data; const script = global.document.createElement('script'); - script.setAttribute('data-ws', data.ws); - script.setAttribute('data-h', data.host); - script.setAttribute('data-s', data.s); - script.setAttribute('data-tech', 'amp'); - script.src = 'https://static.adman.gr/adman.js'; - global.document.body.appendChild(script); + + if (host.match(/grxchange/)) { + script.onload = function () { + window.Adman.adunit({ + id: ws, + h: 'https://' + host, + elementId: 'c', + }); + }; + return; + } + script.setAttribute('data-ws', ws); + script.setAttribute('data-h', host); + script.setAttribute('data-s', s); + script.setAttribute('data-tech', 'amp'); } diff --git a/ads/vendors/admatic.js b/ads/vendors/admatic.js new file mode 100644 index 000000000000..7b45ace6b593 --- /dev/null +++ b/ads/vendors/admatic.js @@ -0,0 +1,37 @@ +import {validateData, writeScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function admatic(global, data) { + validateData(data, ['adNetwork', 'adPublisher', 'adTypeId']); + global._admatic = global._admatic || { + publisher: data['adNetwork'], + adNetwork: data['adPublisher'], + adTypeId: data['adTypeId'], + host: `static.cdn.pixad.com.tr`, + prefix: `px`, + }; + + if (global._admatic.publisher.indexOf('adm-pub') != -1) { + global._admatic.host = `static.cdn.admatic.com.tr`; + global._admatic.prefix = `adm`; + } + + const ins = global.document.createElement('ins'); + ins.setAttribute('data-publisher', global._admatic.publisher); + if (global._admatic.adTypeId == 'standard') { + ins.setAttribute('data-ad-size', `[[${data.width},${data.height}]]`); + } + ins.setAttribute('data-ad-network', global._admatic.adNetwork); + ins.setAttribute('data-ad-type-id', global._admatic.adTypeId); + ins.setAttribute('class', `${global._admatic.prefix}-ads-area`); + global.document.getElementById('c').appendChild(ins); + ins.parentNode.addEventListener( + 'eventAdbladeRenderStart', + global.context.renderStart() + ); + + writeScript(global, `https://${global._admatic.host}/showad/showad.min.js`); +} diff --git a/ads/vendors/admatic.md b/ads/vendors/admatic.md new file mode 100644 index 000000000000..2e30b894b95c --- /dev/null +++ b/ads/vendors/admatic.md @@ -0,0 +1,51 @@ +# AdMatic + +## Example of AdMatic's model implementation + +### Basic + +```html + + +``` + +### Sticky Ad + +```html + + + + +``` + +Note that `` component requires the following script to be included in the page: + +```html + +``` + +## Configuration + +For details on the configuration semantics, see [AdMatic documentation](https://www.admatic.com.tr/). + +### Required parameters + +- `data-ad-network`: Network ID +- `data-ad-publisher`: Publisher ID +- `data-ad-type-id`: Model ID diff --git a/ads/vendors/admixer.js b/ads/vendors/admixer.js index 806fa858becb..7a09b4b17605 100644 --- a/ads/vendors/admixer.js +++ b/ads/vendors/admixer.js @@ -1,6 +1,7 @@ -import {tryParseJson} from '#core/types/object/json'; import {validateData, writeScript} from '#3p/3p'; +import {tryParseJson} from '#core/types/object/json'; + /** * @param {!Window} global * @param {!Object} data @@ -8,7 +9,7 @@ import {validateData, writeScript} from '#3p/3p'; export function admixer(global, data) { validateData(data, ['zone'], ['sizes']); /** - * @type {Object} + * @type {object} */ const payload = { imps: [], diff --git a/ads/vendors/adocean.js b/ads/vendors/adocean.js index d03716cad8b8..8eae482f4183 100644 --- a/ads/vendors/adocean.js +++ b/ads/vendors/adocean.js @@ -1,9 +1,13 @@ -import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; import {computeInMasterFrame, validateData, writeScript} from '#3p/3p'; + +import { + CONSENT_POLICY_STATE, + CONSENT_STRING_TYPE, +} from '#core/constants/consent-state'; import {parseJson} from '#core/types/object/json'; /** - * @const {Object} + * @const {{[key: string]: string}} */ const ADO_JS_PATHS = { 'sync': '/files/js/ado.js', @@ -21,7 +25,7 @@ function isFalseString(str) { /** * @param {string} mode * @param {!Window} global - * @param {boolean} consent + * @param {object} consent */ function setupAdoConfig(mode, global, consent) { if (global['ado']) { @@ -31,7 +35,9 @@ function setupAdoConfig(mode, global, consent) { fif: { enabled: mode != 'sync', }, - consent, + consent: consent.accepted, + gdprApplies: consent.gdprApplies, + gdprConsent: consent.consentString, }; global['ado']['config'](config); @@ -202,7 +208,7 @@ function requestCodes(masterId, data, global, callback) { class AdoBuffer { /** * - * @param {Object} container + * @param {object} container * @param {!Window} global */ constructor(container, global) { @@ -305,14 +311,24 @@ export function adocean(global, data) { const adoUrl = 'https://' + data['aoEmitter'] + ADO_JS_PATHS[mode]; const ctx = global.context; - /* - * INSUFFICIENT and UNKNOWN should be treated as INSUFFICIENT - * not defined states should be treated as INSUFFICIENT - */ - 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; + const consent = { + accepted: + /* INSUFFICIENT and UNKNOWN should be treated as INSUFFICIENT + * not defined states should be treated as INSUFFICIENT */ + ctx.initialConsentState === + null /* tags without data-block-on-consent */ || + ctx.initialConsentState === CONSENT_POLICY_STATE.SUFFICIENT || + ctx.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED, + gdprApplies: + typeof ctx.initialConsentMetadata?.gdprApplies === 'boolean' + ? ctx.initialConsentMetadata.gdprApplies + : undefined, + consentString: + ctx.initialConsentMetadata?.consentStringType === + CONSENT_STRING_TYPE.TCF_V2 + ? ctx.initialConsentValue + : undefined, + }; writeScript(global, adoUrl, () => { setupAdoConfig(mode, global, consent); diff --git a/ads/vendors/adplugg.js b/ads/vendors/adplugg.js index d0fb9d2f4117..16274f28a094 100644 --- a/ads/vendors/adplugg.js +++ b/ads/vendors/adplugg.js @@ -1,6 +1,7 @@ -import {hasOwn} from '#core/types/object'; import {loadScript, validateData} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; + /** * Make an AdPlugg iframe. * @param {!Window} global diff --git a/ads/vendors/adpushup.js b/ads/vendors/adpushup.js index e2289e8660c7..2d20013efd69 100644 --- a/ads/vendors/adpushup.js +++ b/ads/vendors/adpushup.js @@ -8,28 +8,29 @@ export function adpushup(global, data) { validateData( data, ['siteid', 'slotpath', 'width', 'height'], - ['totalampslots', 'jsontargeting', 'extras'] + ['totalampslots', 'jsontargeting', 'extras', 'loadalternate'] ); loadScript( global, 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', () => { - loadScript( - global, - 'https://cdn.adpushup.com/' + data.siteid + '/amp.js', - () => { - window.adpushup.initAmp( - global, - data.width, - data.height, - data.siteid, - data.slotpath, - data.totalampslots, - data.jsontargeting, - data.extras - ); - } - ); + const domain = + data.loadalternate === 'true' + ? 'https://assets.adpushup.com/' + : 'https://cdn.adpushup.com/'; + const url = domain + data.siteid + '/amp.js'; + loadScript(global, url, () => { + window.adpushup.initAmp( + global, + data.width, + data.height, + data.siteid, + data.slotpath, + data.totalampslots, + data.jsontargeting, + data.extras + ); + }); } ); } diff --git a/ads/vendors/ads2bid.js b/ads/vendors/ads2bid.js new file mode 100644 index 000000000000..f251cfccdf03 --- /dev/null +++ b/ads/vendors/ads2bid.js @@ -0,0 +1,22 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ads2bid(global, data) { + validateData(data, ['blockId', 'siteId', 'src']); + const {blockId, siteId, src} = data; + const url = src + `/html/amp?site_id=${siteId}&blocks=${blockId}`; + createContainer(global); + loadScript(global, url); +} + +/** + * @param {!Window} global + */ +function createContainer(global) { + const div = global.document.createElement('div'); + div.setAttribute('data-ads2bid', 1); + global.document.getElementById('c').appendChild(div); +} diff --git a/ads/vendors/ads2bid.md b/ads/vendors/ads2bid.md new file mode 100644 index 000000000000..11b41668516e --- /dev/null +++ b/ads/vendors/ads2bid.md @@ -0,0 +1,21 @@ +# ads2.bid + +Provides support for [ads2.bid](https://ads2.bid/) widgets. + +### Required parameters + +- `src` +- `data-block-id` +- `data-site-id` + +## Example + +```html + + +``` diff --git a/ads/vendors/adskeeper.js b/ads/vendors/adskeeper.js index 789a68ef252b..13773f424816 100644 --- a/ads/vendors/adskeeper.js +++ b/ads/vendors/adskeeper.js @@ -21,41 +21,60 @@ import {loadScript, validateData} from '#3p/3p'; * @param {!Object} data */ export function adskeeper(global, data) { - validateData(data, ['publisher', 'widget', 'container'], ['url', 'options']); - - const scriptRoot = global.document.createElement('div'); - scriptRoot.id = data.container; - - global.document.body.appendChild(scriptRoot); - - /** - * Returns path for provided js filename - * @param {string} publisher js filename - * @return {string} Path to provided filename. - */ - function getResourceFilePath(publisher) { - const publisherStr = publisher.replace(/[^A-z0-9]/g, ''); - return `${publisherStr[0]}/${publisherStr[1]}`; - } - - const url = - `https://jsc.adskeeper.com/${getResourceFilePath(data.publisher)}/` + - `${encodeURIComponent(data.publisher)}.` + - `${encodeURIComponent(data.widget)}.js`; + validateData( + data, + [['publisher', 'website'], ['container', 'website'], 'widget'], + ['url', 'options'] + ); global.uniqId = ( '00000' + Math.round(Math.random() * 100000).toString(16) ).slice(-5); - global['ampOptions' + data.widget + '_' + global.uniqId] = data.options; + window['ampOptions' + data.widget + '_' + global.uniqId] = data.options; global.context.observeIntersection(function (changes) { /** @type {!Array} */ (changes).forEach(function (c) { - global['intersectionRect' + data.widget + '_' + global.uniqId] = + window['intersectionRect' + data.widget + '_' + global.uniqId] = c.intersectionRect; - global['boundingClientRect' + data.widget + '_' + global.uniqId] = + window['boundingClientRect' + data.widget + '_' + global.uniqId] = c.boundingClientRect; }); }); - loadScript(global, data.url || url); + if (data.website) { + const widgetContainer = document.createElement('div'); + widgetContainer.dataset.type = '_mgwidget'; + widgetContainer.dataset.widgetId = data.widget; + document.body.appendChild(widgetContainer); + + const url = + `https://jsc.adskeeper.com/site/` + + `${encodeURIComponent(data.website)}.js?t=` + + Math.floor(Date.now() / 36e5); + + loadScript(global, data.url || url); + } else { + const scriptRoot = document.createElement('div'); + scriptRoot.id = data.container; + + document.body.appendChild(scriptRoot); + + /** + * Returns path for provided js filename + * @param {string} publisher js filename + * @return {string} Path to provided filename. + */ + function getResourceFilePath(publisher) { + const publisherStr = publisher.replace(/[^a-zA-Z0-9]/g, ''); + return `${publisherStr[0]}/${publisherStr[1]}`; + } + + const url = + `https://jsc.adskeeper.com/${getResourceFilePath(data.publisher)}/` + + `${encodeURIComponent(data.publisher)}.` + + `${encodeURIComponent(data.widget)}.js?t=` + + Math.floor(Date.now() / 36e5); + + loadScript(global, data.url || url); + } } diff --git a/ads/vendors/adskeeper.md b/ads/vendors/adskeeper.md index 4e292fc80387..a15e7f4a42f5 100644 --- a/ads/vendors/adskeeper.md +++ b/ads/vendors/adskeeper.md @@ -20,6 +20,19 @@ limitations under the License. ### Basic +Latest version: +```html + + +``` + +Legacy version: ```html + +# Adsviu + +## Example + +```html + + +``` + +## Configuration + +Supported parameters: + +- `data-src`(mandatory): url with the image to be analized +- `data-site-id`(mandatory): id supplied by Adsviu to identify the site where this widget is going to be used diff --git a/ads/vendors/adventive.js b/ads/vendors/adventive.js index 76cf4df6bc41..2f3dfe7580da 100644 --- a/ads/vendors/adventive.js +++ b/ads/vendors/adventive.js @@ -1,8 +1,10 @@ -import {addParamsToUrl} from '../../src/url'; -import {dict, hasOwn} from '#core/types/object'; -import {endsWith} from '#core/types/string'; import {loadScript, validateData, writeScript} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; +import {endsWith} from '#core/types/string'; + +import {addParamsToUrl} from '../../src/url'; + /** * @param {!Window} global * @param {!Object} data @@ -141,11 +143,11 @@ function reduceSearch(ns, placementId, click, referrer) { 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/vendors/adverticum.js b/ads/vendors/adverticum.js index 0ac494ef6ace..c3c4301b14d9 100644 --- a/ads/vendors/adverticum.js +++ b/ads/vendors/adverticum.js @@ -1,5 +1,6 @@ -import {setStyle} from '#core/dom/style'; import {validateData, writeScript} from '#3p/3p'; + +import {setStyle} from '#core/dom/style'; /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/affinity.js b/ads/vendors/affinity.js new file mode 100644 index 000000000000..28ff615d9e43 --- /dev/null +++ b/ads/vendors/affinity.js @@ -0,0 +1,112 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function affinity(global, data) { + validateData( + data, + ['width', 'height', 'adtype', ['adslot', 'slot']], + [ + 'affLayout', + 'multiSize', + 'affSticky', + 'affTitle', + 'affJson', + 'affRtcConfig', + 'jsontargeting', + 'extras', + ] + ); + + const runV1 = function (g, d) { + if (false === g.isInitCalled) { + g.isInitCalled = true; + loadScript(g, gGPT, () => { + loadScript(g, affCDN + '/amp/v2022/amp.js', () => { + (function () { + window.affinity.initAMP(g, d); + })(); + }); + }); + } + }, + O2s = Object.prototype.toString, + isObject = function (val) { + return '[object Object]' == O2s.call(val); + }, + chkIsvalidContext = function (mixContext) { + if ('string' == typeof mixContext) { + try { + mixContext = JSON.parse(mixContext); + } catch (e) { + return false; + } + } + if (isObject(mixContext) && mixContext.sourceUrl) { + return true; + } + return false; + }, + getAmpContext = function () { + if (chkIsvalidContext(W.context)) { + return W.context; + } + if (chkIsvalidContext(W.AMP_CONTEXT_DATA)) { + return W.AMP_CONTEXT_DATA; + } + + return undefined; + }, + jsonParse = function (strJson) { + let ret = null; + try { + ret = JSON.parse(strJson); + } catch (e) { + try { + ret = JSON.parse(strJson.split("'").join('"')); + } catch (e2) {} + } + return ret; + }; + const W = global, + affCDN = 'https://cdn4-hbs.affinitymatrix.com', + gGPT = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + extras = jsonParse(String(data.extras)); + + global.affinity = global.affinity || {task: []}; + global.isInitCalled = false; + global.affinity.initAMP = function (global, data) { + if (global.isInitCalled == true) { + return; + } + global.isInitCalled = true; + try { + const dtObj = new Date(); + const cb = String(dtObj.getDate()) + dtObj.getMonth() + dtObj.getHours(); + const dmn = extras.d.replace('www.', ''); + + const cfgData = {...data, ...extras}; + + const libUrl = affCDN + '/amplib/' + dmn + '/a' + cb + '/amp.php?t=' + cb; + loadScript(global, libUrl); + global.affinity.task.push(function () { + global.affinity.init(cfgData); + }); + } catch (e) { + runV1(global, data); + } + }; + try { + if (getAmpContext() !== undefined) { + if (extras && extras.ver && extras.ver == 1) { + // console.log('extras.ver == 1'); + global.affinity.initAMP(global, data); + data.adStatus = Date.now(); + return; + } + } + } catch (e) {} + runV1(global, data); +} diff --git a/ads/vendors/affinity.md b/ads/vendors/affinity.md new file mode 100644 index 000000000000..1c060a1e0c0c --- /dev/null +++ b/ads/vendors/affinity.md @@ -0,0 +1,24 @@ +# Affinity + +Adtype Affinity support for AMP + +## Example + +```html + + +``` + +## Configuration + +- `data-adtype`: (Mandatory) Type of the adformat +- `data-adslot`: (Mandatory) Name of of adslot +- `data-multi-size`: (Optional) Multi-size support +- `data-jsontargeting`: (Optional) Targeting related paramterers +- `data-extras`: (Optional) Extra data diff --git a/ads/vendors/appmonsta.js b/ads/vendors/appmonsta.js new file mode 100644 index 000000000000..253f389dfa20 --- /dev/null +++ b/ads/vendors/appmonsta.js @@ -0,0 +1,30 @@ +import {validateData, writeScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function appmonsta(global, data) { + validateData(data, ['placementId']); + + const {location} = global.context; + let url = 'https://ssp.appmonsta.ai?c=b&m=amp'; + + const params = [ + ['placementId', encodeURIComponent(data.placementId)], + ['ua', encodeURIComponent(global.navigator?.userAgent)], + ['w', data.width], + ['h', data.height], + ['domain', location.host], + ['page', location.pathname], + ['secure', location.protocol === 'https:' ? 1 : 0], + ['language', global.navigator?.language], + ]; + + for (let i = 0; i < params.length; i++) { + const param = params[i]; + url = `${url}&${param[0]}=${param[1] ?? ''}`; + } + + writeScript(global, url); +} diff --git a/ads/vendors/appmonsta.md b/ads/vendors/appmonsta.md new file mode 100644 index 000000000000..e22783dce9d5 --- /dev/null +++ b/ads/vendors/appmonsta.md @@ -0,0 +1,17 @@ +# AppMonsta + +## Basic example + +```html + + +``` + +### Required parameters + +- `placement-id`: Placement Id diff --git a/ads/vendors/appnexus.js b/ads/vendors/appnexus.js index c7381dd2a1a7..fd109899210e 100644 --- a/ads/vendors/appnexus.js +++ b/ads/vendors/appnexus.js @@ -1,4 +1,5 @@ import {loadScript, validateData, writeScript} from '#3p/3p'; + import {setStyles} from '#core/dom/style'; const APPNEXUS_AST_URL = 'https://acdn.adnxs.com/ast/ast.js'; diff --git a/ads/vendors/bidgear.js b/ads/vendors/bidgear.js new file mode 100644 index 000000000000..1901c7de7c67 --- /dev/null +++ b/ads/vendors/bidgear.js @@ -0,0 +1,35 @@ +import {loadScript, validateData} from '#3p/3p'; + +const requiredParams = ['zoneid']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function bidgear(global, data) { + validateData(data, requiredParams); + + const container = document.getElementById('c'); + const adDivId = 'bg-ssp-' + encodeURIComponent(data.zoneid); + const adDiv = document.createElement('div'); + adDiv.setAttribute('id', adDivId); + container.appendChild(adDiv); + + loadScript( + global, + 'https://platform.bidgear.com/bidgear-amp.js', + () => { + // Bidgear has been loaded + window.pubbidgeartag = window.pubbidgeartag || []; + window.pubbidgeartag.push({ + zoneid: encodeURIComponent(data.zoneid), + id: encodeURIComponent(adDivId), + wu: window.location.href, + }); + }, + () => { + // Cannot load bidgear-amp.js + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/vendors/bidgear.md b/ads/vendors/bidgear.md new file mode 100644 index 000000000000..4bfa1d37a917 --- /dev/null +++ b/ads/vendors/bidgear.md @@ -0,0 +1,22 @@ +# Bidgear + +## Examples + +```html + + +``` + +## Configuration + +**Required:** + +`width` + `height` - Required for all `` units. Specifies the ad size. + +`type` - Always set to "bidgear". + +`data-zoneid`: Zone Id. diff --git a/ads/vendors/blade.js b/ads/vendors/blade.js index 95d93bcde9e6..c16ec25f6dd5 100644 --- a/ads/vendors/blade.js +++ b/ads/vendors/blade.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {tryParseJson} from '#core/types/object/json'; /** diff --git a/ads/vendors/broadstreetads.js b/ads/vendors/broadstreetads.js index 124888600355..80b5d72e44d4 100644 --- a/ads/vendors/broadstreetads.js +++ b/ads/vendors/broadstreetads.js @@ -8,12 +8,14 @@ export function broadstreetads(global, data) { validateData( data, ['network', 'zone', 'width', 'height'], - ['keywords', 'place'] + ['keywords', 'place', 'initurl', 'initoptions'] ); data.place = data.place || 0; const placeholderID = 'placement_' + data.zone + '_' + data.place; + const initUrl = + data.initurl || 'https://cdn.broadstreetads.com/init-2.min.js'; // placeholder div const d = global.document.createElement('div'); @@ -36,7 +38,8 @@ export function broadstreetads(global, data) { softKeywords: true, width: data.width, zoneId: data.zone, + options: data.initoptions, }); }); - loadScript(global, 'https://cdn.broadstreetads.com/init-2.min.js'); + loadScript(global, initUrl); } diff --git a/ads/vendors/cedato.js b/ads/vendors/cedato.js index fe1129214390..6ee937078ecf 100644 --- a/ads/vendors/cedato.js +++ b/ads/vendors/cedato.js @@ -1,7 +1,9 @@ -import {parseUrlDeprecated} from '../../src/url'; -import {setStyles} from '#core/dom/style'; import {validateData} from '#3p/3p'; +import {setStyles} from '#core/dom/style'; + +import {parseUrlDeprecated} from '../../src/url'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/clever.js b/ads/vendors/clever.js new file mode 100644 index 000000000000..684a3f838218 --- /dev/null +++ b/ads/vendors/clever.js @@ -0,0 +1,23 @@ +import {parseUrlDeprecated} from '../../src/url'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function clever(global, data) { + if (!data || !data.id || !data.hash) { + global.context.noContentAvailable(); + return; + } + + const domain = parseUrlDeprecated(global.context.sourceUrl).origin; + const c = document.createElement('script'); + + c.id = 'CleverCoreLoader' + data.id; + c.src = '//scripts.cleverwebserver.com/' + data.hash + '.js'; + c.type = 'text/javascript'; + c.setAttribute('data-target', global.window.name); + c.setAttribute('data-origin', domain); + + global.document.getElementsByTagName('body')[0].append(c); +} diff --git a/ads/vendors/clever.md b/ads/vendors/clever.md new file mode 100644 index 000000000000..3db370354a9e --- /dev/null +++ b/ads/vendors/clever.md @@ -0,0 +1,23 @@ +# Clever + +## Example + +```html + + +``` + +## Configuration + +For additional details and support contact `suport@cleveradvertising.com`. + +### Required parameters + +- `data-id`: the id of campaign +- `data-hash`: MD5 hash of `data-id` diff --git a/ads/vendors/cognativex.js b/ads/vendors/cognativex.js new file mode 100644 index 000000000000..69178a987174 --- /dev/null +++ b/ads/vendors/cognativex.js @@ -0,0 +1,34 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function cognativex(global, data) { + validateData(data, ['appdomain', 'widgetid']); + global.COGNATIVEX = global.COGNATIVEX || {}; + global.COGNATIVEX.config = { + appdomain: data['appdomain'], + }; + (global.COGNATIVEX.widgetIDs = global.COGNATIVEX.widgetIDs || []).push({ + id: data['widgetid'], + isRendered: false, + }); + const d = global.document.createElement('div'); + d.classList.add('cognativex-widget'); + d.id = 'cognativex-widget-' + data['widgetid']; + global.document.getElementById('c').appendChild(d); + const td = new Date(); + const forCache = + td.getFullYear() + + '-' + + (td.getMonth() + 1) + + '-' + + td.getDate() + + '--' + + td.getHours(); + loadScript( + global, + 'https://static.cognativex.com/scripts/cx_script_amp.js?v=' + forCache + ); +} diff --git a/ads/vendors/cognativex.md b/ads/vendors/cognativex.md new file mode 100644 index 000000000000..48e003188d43 --- /dev/null +++ b/ads/vendors/cognativex.md @@ -0,0 +1,23 @@ +# Cognativex + +Cognativex support for AMP. + +For configuration details, additional information or support please contact support@cognativex.com + +## Example + +```html + + +``` + +parameters: + +- `data-widgetid`: widget id. Required. diff --git a/ads/vendors/connatix.js b/ads/vendors/connatix.js index 0a4a2781581a..8de2f97c810c 100644 --- a/ads/vendors/connatix.js +++ b/ads/vendors/connatix.js @@ -1,6 +1,7 @@ +import {validateData} from '#3p/3p'; + import {hasOwn} from '#core/types/object'; import {tryParseJson} from '#core/types/object/json'; -import {validateData} from '#3p/3p'; /** * @param {!Window} global diff --git a/ads/vendors/contentad.js b/ads/vendors/contentad.js index 3fc3d0cbb92a..89c8edc98515 100644 --- a/ads/vendors/contentad.js +++ b/ads/vendors/contentad.js @@ -1,6 +1,7 @@ -import {parseUrlDeprecated} from '../../src/url'; import {validateData, writeScript} from '#3p/3p'; +import {parseUrlDeprecated} from '../../src/url'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/criteo.js b/ads/vendors/criteo.js index 4e70c654624a..13f85886737a 100644 --- a/ads/vendors/criteo.js +++ b/ads/vendors/criteo.js @@ -1,6 +1,7 @@ -import {dev} from '#utils/log'; import {loadScript} from '#3p/3p'; +import {dev} from '#utils/log'; + /* global Criteo: false */ /** @const {string} */ diff --git a/ads/vendors/csa.js b/ads/vendors/csa.js index a4d2a9301ff5..67daf8794b27 100644 --- a/ads/vendors/csa.js +++ b/ads/vendors/csa.js @@ -1,8 +1,10 @@ -import {devAssert} from '#utils/log'; -import {getStyle, setStyle, setStyles} from '#core/dom/style'; import {loadScript, validateData} from '#3p/3p'; + +import {getStyle, setStyle, setStyles} from '#core/dom/style'; import {tryParseJson} from '#core/types/object/json'; +import {devAssert} from '#utils/log'; + // Keep track of current height of AMP iframe let currentAmpHeight = null; @@ -104,7 +106,7 @@ function orientationChangeHandler(global, containerDiv) { const oldHeight = getStyle(containerDiv, 'height'); global.setTimeout(() => { // Force DOM reflow and repaint. - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars const ignore = global.document.body./*OK*/ offsetHeight; // Capture new height. const newHeight = getStyle(containerDiv, 'height'); diff --git a/ads/vendors/dable.js b/ads/vendors/dable.js index dd2251142f42..61481828594a 100644 --- a/ads/vendors/dable.js +++ b/ads/vendors/dable.js @@ -17,7 +17,10 @@ export function dable(global, data) { 'setService', data['serviceName'] || global.window.context.location.hostname ); - global.dable('setURL', global.window.context.sourceUrl); + global.dable( + 'setURL', + global.window.context.canonicalUrl || global.window.context.sourceUrl + ); global.dable('setRef', global.window.context.referrer); const slot = global.document.createElement('div'); @@ -30,16 +33,43 @@ export function dable(global, data) { } const itemId = data['itemId'] || ''; - const opts = {}; + const channel = data['channel'] || ''; + const articleSection = data['articleSection'] || ''; + const articleSection2 = data['articleSection2'] || ''; + const articleSection3 = data['articleSection3'] || ''; + const orgServiceId = data['orgServiceId'] || ''; + const widgetOpts = {}; + const logOpts = {}; + + if (channel) { + widgetOpts.channel = channel; + } + if (articleSection) { + widgetOpts.category1 = articleSection; + logOpts.category1 = articleSection; + } + if (articleSection2) { + widgetOpts.category2 = articleSection2; + logOpts.category2 = articleSection2; + } + if (articleSection3) { + widgetOpts.category3 = articleSection3; + logOpts.category3 = articleSection3; + } + if (orgServiceId) { + widgetOpts.orgServiceId = orgServiceId; + logOpts.orgServiceId = orgServiceId; + } if (itemId) { - global.dable('sendLog', 'view', {id: itemId}); + logOpts.id = itemId; + global.dable('sendLog', 'view', logOpts); } else { - opts.ignoreItems = true; + widgetOpts.ignoreItems = true; } // call render widget - global.dable('renderWidget', slot.id, itemId, opts, function (hasAd) { + global.dable('renderWidget', slot.id, itemId, widgetOpts, function (hasAd) { if (hasAd) { global.context.renderStart(); } else { diff --git a/ads/vendors/dable.md b/ads/vendors/dable.md index 83216aa55f66..4c287a5db5c2 100644 --- a/ads/vendors/dable.md +++ b/ads/vendors/dable.md @@ -10,7 +10,11 @@ height="200" type="dable" data-widget-id="jobgqR7W" + data-service-name="testservice" data-item-id="testitem" + data-article-section="politics" + data-article-section2="global" + data-org-service-id="1" > ``` @@ -22,8 +26,13 @@ For configuration details and to generate your tags, please contact https://admi ### Required parameters - `data-widget-id` +- `data-service-name` ### Optional parameters - `data-item-id` -- `data-service-name` +- `data-channel` +- `data-article-section` +- `data-article-section2` +- `data-article-section3` +- `data-org-service-id` diff --git a/ads/vendors/dex.js b/ads/vendors/dex.js new file mode 100644 index 000000000000..8a3d49f449bc --- /dev/null +++ b/ads/vendors/dex.js @@ -0,0 +1,28 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function dex(global, data) { + validateData(data, ['zoneid']); + loadScript(global, buildUrl(data)); +} + +/** + * @param {!Object} data + * @return {string} + */ +function buildUrl(data) { + let url = `https://cdnb.4strokemedia.com/amp/amp_tag.js?zoneid=${data['zoneid']}`; + + if (data['videoid']) { + url += `&videoid=${data['videoid']}`; + } + + if (data['audioid']) { + url += `&audioid=${data['audioid']}`; + } + + return url; +} diff --git a/ads/vendors/dex.md b/ads/vendors/dex.md new file mode 100644 index 000000000000..903f498dec2c --- /dev/null +++ b/ads/vendors/dex.md @@ -0,0 +1,16 @@ +# DigitalExchange + +## Example + +```html + + +``` + +### Required parameters + +- `data-zoneid` diff --git a/ads/vendors/directadvert.js b/ads/vendors/directadvert.js index bdb7d5778faf..0a6108328471 100644 --- a/ads/vendors/directadvert.js +++ b/ads/vendors/directadvert.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {serializeQueryString} from '../../src/url'; /** diff --git a/ads/vendors/fairground.js b/ads/vendors/fairground.js new file mode 100644 index 000000000000..beb128c792b0 --- /dev/null +++ b/ads/vendors/fairground.js @@ -0,0 +1,20 @@ +import {validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function fairground(global, data) { + validateData(data, ['project', 'hash']); + + const c = document.createElement('script'); + c.src = + 'https://amp.thefairground.com/' + + data.project + + '/' + + data.hash + + '/amp.script.js'; + c.type = 'text/javascript'; + + global.document.getElementsByTagName('body')[0].append(c); +} diff --git a/ads/vendors/fairground.md b/ads/vendors/fairground.md new file mode 100644 index 000000000000..a9b59ffbc93e --- /dev/null +++ b/ads/vendors/fairground.md @@ -0,0 +1,16 @@ +# Fairground + +Provides support for Fairground ads. + +### Required parameters + +- `project` +- `hash` + +## Example + +```html + + +``` diff --git a/ads/vendors/feedad.js b/ads/vendors/feedad.js index b5f5aa6ccbfa..26578fd6b519 100644 --- a/ads/vendors/feedad.js +++ b/ads/vendors/feedad.js @@ -15,6 +15,7 @@ */ import {loadScript, validateData} from '#3p/3p'; + import {setStyle} from '#core/dom/style'; /** @@ -25,7 +26,7 @@ import {setStyle} from '#core/dom/style'; */ /** - * @typedef {Object} FeedAdAsync + * @typedef {object} FeedAdAsync * @private * * @property {FeedAd} [sdk] @@ -33,7 +34,7 @@ import {setStyle} from '#core/dom/style'; */ /** - * @typedef {Object} FeedAd + * @typedef {object} FeedAd * @private * * @property {function(string)} init @@ -41,14 +42,14 @@ import {setStyle} from '#core/dom/style'; */ /** - * @typedef {Object} FeedAdResponse + * @typedef {object} FeedAdResponse * @private * * @property {function():HTMLElement} createAdContainer() */ /** - * @typedef {Object} FeedAdData + * @typedef {object} FeedAdData * @private * * @property {string} clientToken diff --git a/ads/vendors/firstimpression.js b/ads/vendors/firstimpression.js index d67b4f4d4023..50e72616e597 100644 --- a/ads/vendors/firstimpression.js +++ b/ads/vendors/firstimpression.js @@ -1,6 +1,7 @@ -import {parseQueryString} from '#core/types/string/url'; import {validateData, writeScript} from '#3p/3p'; +import {parseQueryString} from '#core/types/string/url'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/geozo.js b/ads/vendors/geozo.js new file mode 100644 index 000000000000..e2cd25538004 --- /dev/null +++ b/ads/vendors/geozo.js @@ -0,0 +1,22 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function geozo(global, data) { + validateData(data, ['src', 'gzBlock']); + const {src} = data; + createContainer(global, data); + loadScript(global, src); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function createContainer(global, data) { + const d = global.document.createElement('div'); + d.setAttribute('data-gz-block', data['gzBlock']); + global.document.getElementById('c').appendChild(d); +} diff --git a/ads/vendors/geozo.md b/ads/vendors/geozo.md new file mode 100644 index 000000000000..1f79a708ef8b --- /dev/null +++ b/ads/vendors/geozo.md @@ -0,0 +1,22 @@ +# Geozo + +Provides support for [Geozo](https://geozo.com) widgets. + +### Required parameters + +- `src` +- `data-gz-block` + +## Example + +```html + + +``` diff --git a/ads/vendors/gumgum.js b/ads/vendors/gumgum.js index 6a9f8518ef35..1d1d18f79f53 100644 --- a/ads/vendors/gumgum.js +++ b/ads/vendors/gumgum.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {setStyles} from '#core/dom/style'; /** diff --git a/ads/vendors/idealmedia.js b/ads/vendors/idealmedia.js index 84250f4f553f..f24d97fcde52 100644 --- a/ads/vendors/idealmedia.js +++ b/ads/vendors/idealmedia.js @@ -5,28 +5,11 @@ import {loadScript, validateData} from '#3p/3p'; * @param {!Object} data */ export function idealmedia(global, data) { - validateData(data, ['publisher', 'widget', 'container'], ['url', 'options']); - - const scriptRoot = document.createElement('div'); - scriptRoot.id = data.container; - - document.body.appendChild(scriptRoot); - - /** - * Returns path for provided js filename - * @param {string} publisher The first number. - * @return {string} Path to provided filename. - */ - function getResourceFilePath(publisher) { - const publisherStr = publisher.replace(/[^A-z0-9]/g, ''); - return `${publisherStr[0]}/${publisherStr[1]}`; - } - - const url = - `https://jsc.idealmedia.io/${getResourceFilePath(data.publisher)}/` + - `${encodeURIComponent(data.publisher)}.` + - `${encodeURIComponent(data.widget)}.js?t=` + - Math.floor(Date.now() / 36e5); + validateData( + data, + [['publisher', 'website'], ['container', 'website'], 'widget'], + ['url', 'options'] + ); global.uniqId = ( '00000' + Math.round(Math.random() * 100000).toString(16) @@ -42,5 +25,40 @@ export function idealmedia(global, data) { }); }); - loadScript(global, data.url || url); + if (data.website) { + const widgetContainer = document.createElement('div'); + widgetContainer.dataset.type = '_mgwidget'; + widgetContainer.dataset.widgetId = data.widget; + document.body.appendChild(widgetContainer); + + const url = + `https://jsc.idealmedia.io/site/` + + `${encodeURIComponent(data.website)}.js?t=` + + Math.floor(Date.now() / 36e5); + + loadScript(global, data.url || url); + } else { + const scriptRoot = document.createElement('div'); + scriptRoot.id = data.container; + + document.body.appendChild(scriptRoot); + + /** + * Returns path for provided js filename + * @param {string} publisher js filename + * @return {string} Path to provided filename. + */ + function getResourceFilePath(publisher) { + const publisherStr = publisher.replace(/[^a-zA-Z0-9]/g, ''); + return `${publisherStr[0]}/${publisherStr[1]}`; + } + + const url = + `https://jsc.idealmedia.io/${getResourceFilePath(data.publisher)}/` + + `${encodeURIComponent(data.publisher)}.` + + `${encodeURIComponent(data.widget)}.js?t=` + + Math.floor(Date.now() / 36e5); + + loadScript(global, data.url || url); + } } diff --git a/ads/vendors/idealmedia.md b/ads/vendors/idealmedia.md index ea1a4ec1c2ce..3bcdbccf5923 100644 --- a/ads/vendors/idealmedia.md +++ b/ads/vendors/idealmedia.md @@ -4,6 +4,19 @@ ### Basic +Latest version: +```html + + +``` + +Legacy version: ```html + +``` + +## Configuration + +For configuration details and to generate your tags, please contact https://www.incrementx.com/contact-us. + +Supported parameters: + +- `data-vzid` diff --git a/ads/vendors/inmobi.js b/ads/vendors/inmobi.js index 16010a5f13d6..eeb536417ecf 100644 --- a/ads/vendors/inmobi.js +++ b/ads/vendors/inmobi.js @@ -1,6 +1,7 @@ -import {setStyle} from '#core/dom/style'; import {validateData, writeScript} from '#3p/3p'; +import {setStyle} from '#core/dom/style'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/insurads.js b/ads/vendors/insurads.js new file mode 100644 index 000000000000..708d9acc4944 --- /dev/null +++ b/ads/vendors/insurads.js @@ -0,0 +1,22 @@ +import {loadScript} from '#3p/3p'; + +/** + * @param {!Window} global + */ +export function insurads(global) { + /** + * Create and modify a Date object to return the first day of the current week. + * @return {Date} The altered date. + */ + function getCacheBuster() { + const t = new Date(); + t.setDate(t.getDate() - t.getDay()); + t.setHours(0, 0, 0, 0) / 1000; + return Number(t); + } + + // For simplicity and flexibility, all validations are performed in the + // InsurAds's URL based on the data received + const url = 'https://cdn.insurads.com/amp-init.js?_=' + getCacheBuster(); + loadScript(global, url); +} diff --git a/ads/vendors/insurads.md b/ads/vendors/insurads.md new file mode 100644 index 000000000000..52acd978dc85 --- /dev/null +++ b/ads/vendors/insurads.md @@ -0,0 +1,61 @@ +# InsurAds + +## Example + +### Basic sample + +```html + + +``` + +### Sample with multisize + +```html + + +``` + +### Sample with targeting + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +## Supported parameters + +| Parameter name | Description | Required | +| --------------- | ----------------------------------- | -------- | +| width | Primary size width | Yes | +| height | Primary size height | Yes | +| data-public-id | Application public id | Yes | +| data-slot | Ad unit code | Yes | +| data-multi-size | Comma separated list of other sizes | No | +| json | Custom targeting map | No | + +Note: if any of the required parameters is not present, the ad slot will not be filled. diff --git a/ads/vendors/iprom.js b/ads/vendors/iprom.js index ce274f9d0a6c..2feb542aa9cb 100644 --- a/ads/vendors/iprom.js +++ b/ads/vendors/iprom.js @@ -1,6 +1,7 @@ -import {parseJson} from '#core/types/object/json'; import {validateData, writeScript} from '#3p/3p'; +import {parseJson} from '#core/types/object/json'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/ix.js b/ads/vendors/ix.js index 272a3dd65560..e045e3ac6f4c 100644 --- a/ads/vendors/ix.js +++ b/ads/vendors/ix.js @@ -1,6 +1,8 @@ +import {loadScript, writeScript} from '#3p/3p'; + import {doubleclick} from '#ads/google/doubleclick'; + import {hasOwn} from '#core/types/object'; -import {loadScript, writeScript} from '#3p/3p'; const DEFAULT_TIMEOUT = 500; // ms const EVENT_SUCCESS = 0; diff --git a/ads/vendors/jioads.js b/ads/vendors/jioads.js new file mode 100644 index 000000000000..2a2b11a80e11 --- /dev/null +++ b/ads/vendors/jioads.js @@ -0,0 +1,82 @@ +import {loadScript, validateData, writeScript} from '#3p/3p'; + +import {setStyle} from '#core/dom/style'; +import {hasOwn} from '#core/types/object'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function jioads(global, data) { + global.context.container = `uid${Math.floor(Date.now())}`; + global.context.initialViewAbility = false; + validateData( + data, + ['adspot', 'pkgName'], + ['adMetaData', 'refreshRate', 'videoAd'] + ); + if (!data) { + global.context.noContentAvailable(); + return; + } + writeScript( + global, + 'https://mercury.akamaized.net/jioads/websdk/amp/jioAds.js?rnd=' + + Math.random() + ); + loadScript( + global, + 'https://mercury.akamaized.net/jioads/websdk/amp/ampwrapper.js?rnd=' + + Math.random(), + () => { + let refresh = ''; + let adMetaData = ''; + let video = ''; + let videocontainer = ''; + if (hasOwn(data, 'refreshRate')) { + refresh = ` data-refresh-rate="${data['refreshRate']}"`; + } + if (hasOwn(data, 'adMetaData')) { + adMetaData = ` data-adMetaData="${data['adMetaData']}"`; + } + if (hasOwn(data, 'videoAd') && data['videoAd'] == '1') { + video = `
`; + videocontainer = ` data-container-id="instreamContainer"`; + } + let html = ''; + const container = document.createElement('div'); + container.classList.add('jads-flex-center', 'jads-f-align-center'); + container.setAttribute('id', 'jads_amp_ad'); + setStyle( + container, + 'width', + `${global.context.initialIntersection.boundingClientRect.width}px` + ); + setStyle( + container, + 'height', + `${global.context.initialIntersection.boundingClientRect.height}px` + ); + html = `${video} `; + container./*OK*/ innerHTML = html; + global.document.getElementById('c').appendChild(container); + }, + () => { + global.context.noContentAvailable(); + } + ); + // addon observation on entering/leaving the view when scroll vertically and horizontally + window.context.observeIntersection(function (newrequest) { + /** @type {!Array} */ + (newrequest).forEach(function (data) { + if (data.intersectionRatio >= 0.5) { + global.context.initialViewAbility = true; + } else { + global.context.initialViewAbility = false; + } + }); + }); +} diff --git a/ads/vendors/jioads.md b/ads/vendors/jioads.md new file mode 100644 index 000000000000..b814b9137cbb --- /dev/null +++ b/ads/vendors/jioads.md @@ -0,0 +1,74 @@ +# JIOADS + +## Example of JIOADS AD's implementation + +```html +

Native Banner (Fixed Size W320 x H50)

+ + +
+

Native Billboard (Fixed Size W300 x H250)

+ +
Loading....
+
Something went wrong in ad load
+
+

Dynamic Display Billboard (Responsive Size Min W320 x H 300)

+ +
Loading....
+
Something went wrong in ad load
+
+

Instream / Video (Responsive Size Min W320 x H280)

+ +
Loading....
+
Something went wrong in ad load
+
+``` + +## Configuration + +For details on the configuration semantics, please contact JioAds + +### Required parameters + +- `data-adspot`: ADSPOT (publisher will get from jioads campaign dashboard) +- `data-pkg-name`: PACKAGE NAME (publisher will get from jioads campaign dashboard as unique for each ad) + +### Optional parameters + +- `data-video-ad`: VIDEO AD boolean, only for instream ad (publisher will get from jioads campaign dashboard and decide taht want to publish video) +- `data-refersh-rate`: REFRESH RATE integer seconds, defaut refresh rate is 30 seconds, manually set should be greater than 30 (publisher can set refresh rate in seconds, so ad will be refreshed according to given seconds) +- `data-ad-meta-data`: AD META DATA json format (publisher can set json data like category etc, full example on campaign dashboard) diff --git a/ads/vendors/jixie.js b/ads/vendors/jixie.js index 12511aa5479d..7c6f30fd4bfe 100644 --- a/ads/vendors/jixie.js +++ b/ads/vendors/jixie.js @@ -6,5 +6,5 @@ import {loadScript} from '#3p/3p'; export function jixie(global) { // For flexibility, all validations are performed in the // Jixie side based on the data on the page for the amp-ad - loadScript(global, 'https://scripts.jixie.io/jxamp.min.js'); + loadScript(global, 'https://scripts.jixie.media/jxamp.min.js'); } diff --git a/ads/vendors/kiosked.js b/ads/vendors/kiosked.js index 2f1d1b67eb43..c1e0070400a3 100644 --- a/ads/vendors/kiosked.js +++ b/ads/vendors/kiosked.js @@ -1,6 +1,7 @@ -import {hasOwn} from '#core/types/object'; import {validateData, writeScript} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/kixer.js b/ads/vendors/kixer.js index c131d0e722c4..c46046686d9e 100644 --- a/ads/vendors/kixer.js +++ b/ads/vendors/kixer.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseUrlDeprecated} from '../../src/url'; /* global diff --git a/ads/vendors/lentainform.js b/ads/vendors/lentainform.js deleted file mode 100644 index 1953bd49b7a3..000000000000 --- a/ads/vendors/lentainform.js +++ /dev/null @@ -1,39 +0,0 @@ -import {loadScript, validateData} from '#3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function lentainform(global, data) { - validateData(data, ['publisher', 'widget', 'container'], ['url', 'options']); - - const scriptRoot = document.createElement('div'); - scriptRoot.id = data.container; - - document.body.appendChild(scriptRoot); - - const publisherStr = data.publisher.replace(/[^A-z0-9]/g, ''); - - const url = - `https://jsc.lentainform.com/${encodeURIComponent(publisherStr[0])}/` + - `${encodeURIComponent(publisherStr[1])}/` + - `${encodeURIComponent(data.publisher)}.` + - `${encodeURIComponent(data.widget)}.js?t=` + - Math.floor(Date.now() / 36e5); - - global.uniqId = ( - '00000' + Math.round(Math.random() * 100000).toString(16) - ).slice(-5); - window['ampOptions' + data.widget + '_' + global.uniqId] = data.options; - - global.context.observeIntersection(function (changes) { - /** @type {!Array} */ (changes).forEach(function (c) { - window['intersectionRect' + data.widget + '_' + global.uniqId] = - c.intersectionRect; - window['boundingClientRect' + data.widget + '_' + global.uniqId] = - c.boundingClientRect; - }); - }); - - loadScript(global, data.url || url); -} diff --git a/ads/vendors/lentainform.md b/ads/vendors/lentainform.md deleted file mode 100644 index ef5acac84e51..000000000000 --- a/ads/vendors/lentainform.md +++ /dev/null @@ -1,32 +0,0 @@ -# Lentainform - -## Example - -### Basic - -```html - - -``` - -## Configuration - -For details on the configuration semantics, please contact the ad network or refer to their documentation. - -### Required parameters - -- `data-publisher` -- `data-widget` -- `data-container` - -### Optional parameters - -- `data-url` -- `data-options` diff --git a/ads/vendors/medianet.js b/ads/vendors/medianet.js index 0a6ef53c8f0f..4be2102f4b9a 100644 --- a/ads/vendors/medianet.js +++ b/ads/vendors/medianet.js @@ -1,7 +1,9 @@ import {computeInMasterFrame, validateData, writeScript} from '#3p/3p'; -import {getSourceUrl, parseUrlDeprecated} from '../../src/url'; + import {hasOwn} from '#core/types/object'; +import {getSourceUrl, parseUrlDeprecated} from '../../src/url'; + const mandatoryParams = ['tagtype', 'cid'], optionalParams = [ 'timeout', diff --git a/ads/vendors/medyanet.js b/ads/vendors/medyanet.js index 53b1b7bcb2f3..7c25f6072719 100644 --- a/ads/vendors/medyanet.js +++ b/ads/vendors/medyanet.js @@ -1,6 +1,7 @@ -import {setStyles} from '#core/dom/style'; import {validateData} from '#3p/3p'; +import {setStyles} from '#core/dom/style'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/mgid.js b/ads/vendors/mgid.js index 84317509af99..f0f4b643869b 100644 --- a/ads/vendors/mgid.js +++ b/ads/vendors/mgid.js @@ -5,28 +5,11 @@ import {loadScript, validateData} from '#3p/3p'; * @param {!Object} data */ export function mgid(global, data) { - validateData(data, ['publisher', 'widget', 'container'], ['url', 'options']); - - const scriptRoot = document.createElement('div'); - scriptRoot.id = data.container; - - document.body.appendChild(scriptRoot); - - /** - * Returns path for provided js filename - * @param {string} publisher js filename - * @return {string} Path to provided filename. - */ - function getResourceFilePath(publisher) { - const publisherStr = publisher.replace(/[^A-z0-9]/g, ''); - return `${publisherStr[0]}/${publisherStr[1]}`; - } - - const url = - `https://jsc.mgid.com/${getResourceFilePath(data.publisher)}/` + - `${encodeURIComponent(data.publisher)}.` + - `${encodeURIComponent(data.widget)}.js?t=` + - Math.floor(Date.now() / 36e5); + validateData( + data, + [['publisher', 'website'], ['container', 'website'], 'widget'], + ['url', 'options'] + ); global.uniqId = ( '00000' + Math.round(Math.random() * 100000).toString(16) @@ -42,5 +25,40 @@ export function mgid(global, data) { }); }); - loadScript(global, data.url || url); + if (data.website) { + const widgetContainer = document.createElement('div'); + widgetContainer.dataset.type = '_mgwidget'; + widgetContainer.dataset.widgetId = data.widget; + document.body.appendChild(widgetContainer); + + const url = + `https://jsc.mgid.com/site/` + + `${encodeURIComponent(data.website)}.js?t=` + + Math.floor(Date.now() / 36e5); + + loadScript(global, data.url || url); + } else { + const scriptRoot = document.createElement('div'); + scriptRoot.id = data.container; + + document.body.appendChild(scriptRoot); + + /** + * Returns path for provided js filename + * @param {string} publisher js filename + * @return {string} Path to provided filename. + */ + function getResourceFilePath(publisher) { + const publisherStr = publisher.replace(/[^a-zA-Z0-9]/g, ''); + return `${publisherStr[0]}/${publisherStr[1]}`; + } + + const url = + `https://jsc.mgid.com/${getResourceFilePath(data.publisher)}/` + + `${encodeURIComponent(data.publisher)}.` + + `${encodeURIComponent(data.widget)}.js?t=` + + Math.floor(Date.now() / 36e5); + + loadScript(global, data.url || url); + } } diff --git a/ads/vendors/mgid.md b/ads/vendors/mgid.md index 67f9824787c6..83f967eaa7bd 100644 --- a/ads/vendors/mgid.md +++ b/ads/vendors/mgid.md @@ -4,6 +4,19 @@ ### Basic +Latest version: +```html + + +``` + +Legacy version: ```html + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +### Required parameters + +- `data-publisher` +- `data-container` + + +### Optional parameters +- `data-format` +- `data-url` +- `data-extras` \ No newline at end of file diff --git a/ads/vendors/mytarget.js b/ads/vendors/mytarget.js index e159e28621e2..ceaaa32827ad 100644 --- a/ads/vendors/mytarget.js +++ b/ads/vendors/mytarget.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {setStyles} from '#core/dom/style'; /** diff --git a/ads/vendors/myua.js b/ads/vendors/myua.js index 3f41faac6dbe..9684010eee6b 100644 --- a/ads/vendors/myua.js +++ b/ads/vendors/myua.js @@ -5,15 +5,15 @@ import {validateData} from '#3p/3p'; * @param {!Object} data */ export function myua(global, data) { - validateData(data, ['sid', 'iid'], ['demo', 'options']); + validateData(data, ['sid', 'iid'], ['env', 'options']); const informerTag = global.document.createElement('div'); informerTag.setAttribute('data-top-iid', data.iid); global.document.body.appendChild(informerTag); - const demoSuffix = data.demo ? 'dev.' : ''; + const env = data.env ? `${data.env}.` : ''; const scriptTag = global.document.createElement('script'); - scriptTag.src = `https://amp.top-js-metrics.top.${demoSuffix}my.ua/script.js`; + scriptTag.src = `https://amp.top-js-metrics.top.${env}my.ua/script.js`; scriptTag.setAttribute('async', 'true'); scriptTag.setAttribute('data-top-sid', data.sid); diff --git a/ads/vendors/myua.md b/ads/vendors/myua.md index 72eab091b360..426e87eb69e7 100644 --- a/ads/vendors/myua.md +++ b/ads/vendors/myua.md @@ -26,5 +26,5 @@ For details on the configuration semantics, please contact the ad network or ref ### Optional parameters -- `data-demo` +- `data-env` - `data-options` diff --git a/ads/vendors/nativery.js b/ads/vendors/nativery.js index 8fd27d3a90d1..d06116ba47a0 100644 --- a/ads/vendors/nativery.js +++ b/ads/vendors/nativery.js @@ -1,6 +1,7 @@ -import {toArray} from '#core/types/array'; import {validateData, writeScript} from '#3p/3p'; +import {toArray} from '#core/types/array'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/nativo.js b/ads/vendors/nativo.js index d8b2d9fa3fe3..d054fd321edc 100644 --- a/ads/vendors/nativo.js +++ b/ads/vendors/nativo.js @@ -82,10 +82,13 @@ export function nativo(global, data) { */ function loadAdWhenTimedout() { const g = global; - setTimeout(function () { - g.PostRelease.Start(); - delayedAdLoad = true; - }, parseInt(data.delayByTime, 10)); + setTimeout( + function () { + g.PostRelease.Start(); + delayedAdLoad = true; + }, + parseInt(data.delayByTime, 10) + ); } /** * @param {*} positions diff --git a/ads/vendors/navegg.js b/ads/vendors/navegg.js index b9ed93c79283..cdcf54d82cdb 100644 --- a/ads/vendors/navegg.js +++ b/ads/vendors/navegg.js @@ -1,12 +1,17 @@ -import {doubleclick} from '#ads/google/doubleclick'; import {loadScript, validateData} from '#3p/3p'; +import {doubleclick} from '#ads/google/doubleclick'; + /** * @param {!Window} global * @param {!Object} data */ export function navegg(global, data) { - validateData(data, ['acc']); + const requiredParams = ['acc', 'wst', 'wct', 'wla']; + const optionalParams = []; + + validateData(data, requiredParams, optionalParams); + const {acc} = data; let seg, nvg = function () {}; @@ -16,6 +21,9 @@ export function navegg(global, data) { loadScript(global, 'https://tag.navdmp.com/amp.1.0.0.min.js', () => { nvg = global[`nvg${acc}`] = new global['AMPNavegg']({ acc, + wst: data.wst ? '1' : '0', + wct: data.wct ? '1' : '0', + wla: data.wla ? '1' : '0', }); nvg.getProfile((nvgTargeting) => { for (seg in nvgTargeting) { diff --git a/ads/vendors/navegg.md b/ads/vendors/navegg.md index ab60804481de..574764a7973f 100644 --- a/ads/vendors/navegg.md +++ b/ads/vendors/navegg.md @@ -18,4 +18,4 @@ To get Navegg integration working you only need to specify the `rtc-config` para The Navegg adapter only supports DoubleClick for now. For the most up-to-date list of DoubleClick supported parameters and usage, refer to the [DoubleClick reference guide](https://github.com/ampproject/amphtml/blob/main/ads/google/doubleclick.md). -For any help, please contact [Navegg](https://www.navegg.com/en/institutional/#contact). +For any help, please contact [Navegg](https://www.navegg.com/en/about-us/contacts). diff --git a/ads/vendors/netletix.js b/ads/vendors/netletix.js index befd8bcfde8f..0ea6205c6a65 100644 --- a/ads/vendors/netletix.js +++ b/ads/vendors/netletix.js @@ -1,8 +1,9 @@ -import {addParamsToUrl, assertHttpsUrl} from '../../src/url'; -import {dev} from '#utils/log'; -import {dict} from '#core/types/object'; import {loadScript, validateData, writeScript} from '#3p/3p'; +import {dev} from '#utils/log'; + +import {addParamsToUrl, assertHttpsUrl} from '../../src/url'; + const NX_URL_HOST = 'https://call.adadapter.netzathleten-media.de'; const NX_URL_PATHPREFIX = '/pb/'; const NX_URL_FULL = NX_URL_HOST + NX_URL_PATHPREFIX; @@ -36,14 +37,14 @@ export function netletix(global, data) { 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 ); diff --git a/ads/vendors/openx.js b/ads/vendors/openx.js index a68661e92db6..d75b019debcf 100644 --- a/ads/vendors/openx.js +++ b/ads/vendors/openx.js @@ -1,6 +1,7 @@ -import {doubleclick} from '#ads/google/doubleclick'; import {loadScript, validateData, writeScript} from '#3p/3p'; +import {doubleclick} from '#ads/google/doubleclick'; + const {hasOwnProperty} = Object.prototype; /** diff --git a/ads/vendors/piberica.js b/ads/vendors/piberica.js new file mode 100644 index 000000000000..9ee70f5b09fc --- /dev/null +++ b/ads/vendors/piberica.js @@ -0,0 +1,25 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function piberica(global, data) { + /*eslint "local/camelcase": 0*/ + global._piberica_amp = { + allowed_data: ['publisher', 'slot'], + mandatory_data: ['publisher', 'slot'], + data, + }; + + validateData( + data, + global._piberica_amp.mandatory_data, + global._piberica_amp.allowed_data + ); + + loadScript( + global, + `https://trafico.prensaiberica.es/adm/min/intext/${data.publisher}/${data.slot}.js` + ); +} diff --git a/ads/vendors/piberica.md b/ads/vendors/piberica.md new file mode 100644 index 000000000000..a2059549e176 --- /dev/null +++ b/ads/vendors/piberica.md @@ -0,0 +1,24 @@ +# PIberica + +## Example + +```html + +``` + +## Configuration + +For further information, please contact [PIberica](https://www.prensaiberica360.es). + +### Required parameters + +- `data-publisher`: Publisher Integration ID provided by PIberica +- `data-slot`: Slot Integration ID provided by PIberica diff --git a/ads/vendors/pixad.js b/ads/vendors/pixad.js new file mode 100644 index 000000000000..ceb8d87a3c1c --- /dev/null +++ b/ads/vendors/pixad.js @@ -0,0 +1,37 @@ +import {validateData, writeScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pixad(global, data) { + validateData(data, ['adNetwork', 'adPublisher', 'adTypeId']); + global._pixad = global._pixad || { + publisher: data['adNetwork'], + adNetwork: data['adPublisher'], + adTypeId: data['adTypeId'], + host: `static.cdn.pixad.com.tr`, + prefix: `px`, + }; + + if (global._pixad.publisher.indexOf('adm-pub') != -1) { + global._pixad.host = `static.cdn.admatic.com.tr`; + global._pixad.prefix = `adm`; + } + + const ins = global.document.createElement('ins'); + ins.setAttribute('data-publisher', global._pixad.publisher); + if (global._pixad.adTypeId == 'standard') { + ins.setAttribute('data-ad-size', `[[${data.width},${data.height}]]`); + } + ins.setAttribute('data-ad-network', global._pixad.adNetwork); + ins.setAttribute('data-ad-type-id', global._pixad.adTypeId); + ins.setAttribute('class', `${global._pixad.prefix}-ads-area`); + global.document.getElementById('c').appendChild(ins); + ins.parentNode.addEventListener( + 'eventAdbladeRenderStart', + global.context.renderStart() + ); + + writeScript(global, `https://${global._pixad.host}/showad/showad.min.js`); +} diff --git a/ads/vendors/pixad.md b/ads/vendors/pixad.md new file mode 100644 index 000000000000..6452c2ca07ca --- /dev/null +++ b/ads/vendors/pixad.md @@ -0,0 +1,51 @@ +# Pixad + +## Example of Pixad's model implementation + +### Basic + +```html + + +``` + +### Sticky Ad + +```html + + + + +``` + +Note that `` component requires the following script to be included in the page: + +```html + +``` + +## Configuration + +For details on the configuration semantics, see [Pixad documentation](https://developer.pixad.com.tr/). + +### Required parameters + +- `data-ad-network`: Network ID +- `data-ad-publisher`: Publisher ID +- `data-ad-type-id`: Model ID diff --git a/ads/vendors/promoteiq.js b/ads/vendors/promoteiq.js index 943d67f27ab1..8f72d458e1ea 100644 --- a/ads/vendors/promoteiq.js +++ b/ads/vendors/promoteiq.js @@ -1,5 +1,7 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; + import {user} from '#utils/log'; const TAG = 'PROMOTEIQ'; diff --git a/ads/vendors/pubfuture.js b/ads/vendors/pubfuture.js new file mode 100644 index 000000000000..561797076b46 --- /dev/null +++ b/ads/vendors/pubfuture.js @@ -0,0 +1,15 @@ +import {validateData, writeScript} from '#3p/3p'; + +const requiredParams = ['id']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pubfuture(global, data) { + validateData(data, requiredParams); + writeScript(global, 'https://cdn.pubfuture-ad.com/amp/js/pt.js'); + global.document.write( + '' + ); +} diff --git a/ads/vendors/pubfuture.md b/ads/vendors/pubfuture.md new file mode 100644 index 000000000000..d6b409c08ad6 --- /dev/null +++ b/ads/vendors/pubfuture.md @@ -0,0 +1,22 @@ +# Pubfuture + +## Examples + +```html + + +``` + +## Configuration + +**Required:** + +`width` + `height` - Required for all `` units. Specifies the ad size. + +`type` - Always set to "pubfuture". + +`data-id`: Tag Id. diff --git a/ads/vendors/pubmine.js b/ads/vendors/pubmine.js index 13d2f34c4d73..436aa2482191 100644 --- a/ads/vendors/pubmine.js +++ b/ads/vendors/pubmine.js @@ -1,6 +1,7 @@ -import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; import {loadScript, validateData} from '#3p/3p'; +import {CONSENT_POLICY_STATE} from '#core/constants/consent-state'; + const pubmineOptional = [ 'section', 'pt', diff --git a/ads/vendors/pubscale.js b/ads/vendors/pubscale.js new file mode 100644 index 000000000000..3f2c199620cf --- /dev/null +++ b/ads/vendors/pubscale.js @@ -0,0 +1,21 @@ +import {loadScript, validateData, writeScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pubscale(global, data) { + // TODO: check mandatory fields + validateData( + data, + ['pid'], + ['format', 'appid', 'size', 'params', 'load-strategy', 'version'] + ); + global.pubscale = data; + const VERSION = data['version'] || 'v1'; + + const URL = `https://static.pubscale.com/lib/amp/${VERSION}/index.js`; + const load = data['load-strategy'] !== 'sync' ? writeScript : loadScript; + + load(global, URL); +} diff --git a/ads/vendors/pubscale.md b/ads/vendors/pubscale.md new file mode 100644 index 000000000000..fa0522600584 --- /dev/null +++ b/ads/vendors/pubscale.md @@ -0,0 +1,32 @@ +![pubscale logo](https://static.pubscale.com/assets/logo/pubscale-w-bg.svg) + +[PubScale](https://pubscale.com) is an all-in-one platform enabling fast monetization and user acquisition for apps, websites, and games while retaining positive user experiences. With PubScale, you can uplift revenue and analyze business growth all in a single place. + +## Example + +```html + + +``` + +### Required parameters + +- `data-pid` + +### Optional parameters + +- `data-format` +- `data-appid` +- `data-size` +- `data-params` +- `data-load-strategy` +- `data-version` diff --git a/ads/vendors/pulsepoint.js b/ads/vendors/pulsepoint.js index 70fec83e513c..4227caeb0a15 100644 --- a/ads/vendors/pulsepoint.js +++ b/ads/vendors/pulsepoint.js @@ -1,6 +1,7 @@ -import {doubleclick} from '#ads/google/doubleclick'; import {loadScript, validateData, writeScript} from '#3p/3p'; +import {doubleclick} from '#ads/google/doubleclick'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/r9x.js b/ads/vendors/r9x.js new file mode 100644 index 000000000000..b34b374e2af9 --- /dev/null +++ b/ads/vendors/r9x.js @@ -0,0 +1,35 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function r9x(global, data) { + validateData( + data, + ['siteid', 'slotpath', 'width', 'height'], + ['totalampslots', 'jsontargeting', 'extras'] + ); + loadScript( + global, + 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + () => { + loadScript( + global, + 'https://rtbcdn.r9x.media/prod-global-' + data.siteid + '.js', + () => { + window.r9x.initAmp( + global, + data.width, + data.height, + data.siteid, + data.slotpath, + data.totalampslots, + data.jsontargeting, + data.extras + ); + } + ); + } + ); +} diff --git a/ads/vendors/r9x.md b/ads/vendors/r9x.md new file mode 100644 index 000000000000..8a6c875b08df --- /dev/null +++ b/ads/vendors/r9x.md @@ -0,0 +1,18 @@ +# r9x + +## Example + +```html + + +``` + +## Configuration + +- `data-slot`: The GAM ad unit path corresponding to the ad position +- `siteid`: r9x Site ID +- `totalAmpSlots`: Total r9x amp-ad slots on the page diff --git a/ads/vendors/rakutenunifiedads.js b/ads/vendors/rakutenunifiedads.js index 9f3543973a62..0f1cd90216bb 100644 --- a/ads/vendors/rakutenunifiedads.js +++ b/ads/vendors/rakutenunifiedads.js @@ -1,6 +1,7 @@ -import {hasOwn} from '#core/types/object'; import {validateData, writeScript} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/rcmwidget.js b/ads/vendors/rcmwidget.js index dfefcea6aaaf..bf307d77ac43 100644 --- a/ads/vendors/rcmwidget.js +++ b/ads/vendors/rcmwidget.js @@ -1,7 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; -const WIDGET_DEFAULT_NODE_ID = 'rcm-widget'; - /** * @param {!Window} global * @param {!Object} data @@ -9,25 +7,18 @@ const WIDGET_DEFAULT_NODE_ID = 'rcm-widget'; export function rcmwidget(global, data) { validateData( data, - ['rcmId', 'nodeId', 'blockId', 'templateName', 'projectId'], - ['contextItemId'] + ['rcmId', 'blockId', 'templateName', 'projectId'], + [ + 'contextItemId', + 'customStyles', + 'itemExcludedIds', + 'itemExcludedUrls', + 'params', + ] ); global.rcmWidgetInit = data; - createContainer(global, data.nodeId); - // load the rcmwidget initializer asynchronously loadScript(global, 'https://rcmjs.rambler.ru/static/rcmw/rcmw-amp.js'); } - -/** - * @param {!Window} global - * @param {string} nodeId - */ -function createContainer(global, nodeId = WIDGET_DEFAULT_NODE_ID) { - const container = global.document.createElement('div'); - container.id = nodeId; - - global.document.getElementById('c').appendChild(container); -} diff --git a/ads/vendors/rcmwidget.md b/ads/vendors/rcmwidget.md index 1801c53a5c8e..0ddf7823ff57 100644 --- a/ads/vendors/rcmwidget.md +++ b/ads/vendors/rcmwidget.md @@ -3,13 +3,33 @@ ## Example ```html - + + ``` +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + ### Required parameters - `data-rcm-id` -- `data-node-id` - `data-block-id` - `data-template-name` - `data-project-id` + +### Optional parameters + +- `data-context-item-id` +- `data-custom-styles` +- `data-item-excluded-ids` +- `data-item-excluded-urls` +- `data-params` diff --git a/ads/vendors/relap.js b/ads/vendors/relap.js index 575b8379d57e..a74c856592a8 100644 --- a/ads/vendors/relap.js +++ b/ads/vendors/relap.js @@ -1,5 +1,7 @@ import {loadScript, validateData} from '#3p/3p'; +import {setStyle} from '#core/dom/style'; + /** * @param {!Window} global * @param {!Object} data @@ -10,13 +12,6 @@ export function relap(global, data) { const urlParam = data['url'] || window.context.canonicalUrl; if (data['version'] === 'v7') { - window.onRelapAPIReady = function (relapAPI) { - relapAPI['init']({ - token: data['token'], - url: urlParam, - }); - }; - window.onRelapAPIInit = function (relapAPI) { relapAPI['addWidget']({ cfgId: data['anchorid'], @@ -27,13 +22,21 @@ export function relap(global, data) { window.context.renderStart(); }, onNoContent: function () { + relapAPI['destroy'](); window.context.noContentAvailable(); }, }, }); }; - loadScript(global, 'https://relap.io/v7/relap.js'); + const iframeEl = document.createElement('iframe'); + setStyle(iframeEl, 'position', 'absolute'); + setStyle(iframeEl, 'visibility', 'hidden'); + setStyle(iframeEl, 'left', '-9999px'); + setStyle(iframeEl, 'top', '-9999px'); + iframeEl.className = 'relap-runtime-iframe'; + iframeEl.srcdoc = ``; + global.document.body.appendChild(iframeEl); } else { window.relapV6WidgetReady = function () { window.context.renderStart(); diff --git a/ads/vendors/relap.md b/ads/vendors/relap.md index d7cf5bb74a5d..1ece5d4a4df2 100644 --- a/ads/vendors/relap.md +++ b/ads/vendors/relap.md @@ -5,12 +5,13 @@ ```html ``` diff --git a/ads/vendors/revcontent.js b/ads/vendors/revcontent.js index 68981da1610b..a5e3989be1c8 100644 --- a/ads/vendors/revcontent.js +++ b/ads/vendors/revcontent.js @@ -1,60 +1,250 @@ -import {loadScript, validateData, writeScript} from '#3p/3p'; +import {loadScript, validateData} from '#3p/3p'; + +import {setStyle, setStyles} from '#core/dom/style'; + +// Required/optional parameters for legacy/standard builds are consolidated here +// to provide a more managable way of interacting with them. Most of the legacy +// optional attributes are deprecated but there's still tags live that might have +// them so they'll stay in this list to pass validation +const options = { + endpoints: { + legacy: + 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js', + standard: 'https://assets.revcontent.com/master/delivery.js', + evergreen: 'https://delivery.revcontent.com/', + }, + params: { + legacy: { + required: ['id', 'height'], + optional: [ + 'wrapper', + 'subIds', + 'revcontent', + 'env', + 'loadscript', + 'api', + 'key', + 'ssl', + 'adxw', + 'adxh', + 'rows', + 'cols', + 'domain', + 'source', + 'testing', + 'endpoint', + 'publisher', + 'branding', + 'font', + 'css', + 'sizer', + 'debug', + 'ampcreative', + 'gdpr', + 'gdprConsent', + 'usPrivacy', + 'gamEnabled', + ], + }, + evergreen: { + required: ['widgetId', 'pubId', 'height'], + optional: ['endpoint'], + }, + }, +}; /** - * @param {!Window} global + * Helper function to get endpoint string based on how tag is configured. + * * @param {!Object} data + * @return {string} */ -export function revcontent(global, data) { - let endpoint = - 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js'; - +function getEndpoint(data) { if (typeof data.revcontent !== 'undefined') { if (typeof data.env === 'undefined') { - endpoint = 'https://assets.revcontent.com/master/delivery.js'; - } else if (data.env == 'dev') { - endpoint = 'https://performante.revcontent.dev/delivery.js'; - } else { - endpoint = 'https://assets.revcontent.com/' + data.env + '/delivery.js'; + return options.endpoints.standard; + } + if (data.env == 'dev') { + return 'https://preact.revcontent.dev/delivery.js'; } + return `https://assets.revcontent.com/${data.env}/delivery.js`; } - const required = ['id', 'height']; - const optional = [ - 'wrapper', - 'subIds', - 'revcontent', - 'env', - 'loadscript', - 'api', - 'key', - 'ssl', - 'adxw', - 'adxh', - 'rows', - 'cols', - 'domain', - 'source', - 'testing', - 'endpoint', - 'publisher', - 'branding', - 'font', - 'css', - 'sizer', - 'debug', - 'ampcreative', - 'gdpr', - 'gdprConsent', - 'usPrivacy', - ]; + if ( + typeof data.placementType !== 'undefined' && + data.placementType === 'evergreen' + ) { + if (typeof data.devUrl !== 'undefined') { + return data.devUrl; + } - data.endpoint = data.endpoint ? data.endpoint : 'trends.revcontent.com'; + return `${options.endpoints.evergreen}/${data.pubId}/${data.widgetId}/widget.js`; + } - validateData(data, required, optional); - global.data = data; - if (data.loadscript) { - loadScript(window, endpoint); + return options.endpoints.legacy; +} + +/** + * Internal handler for calling requestResize + * @param {!Window} global + * @param {!Element} container + * @param {number} height + */ +function handleResize(global, container, height) { + global.context + .requestResize(undefined, height, true) + .then(() => { + handleResizeSuccess(global); + //handleResizeDenied(global, container, height); + }) + .catch(() => { + handleResizeDenied(global, container, height); + }); +} + +/** + * Helper function for handling denied resize requests. Creates an overflow element if one does not exist + * @param {!Window} global + * @param {!Element} container + * @param {number} height + */ +function handleResizeDenied(global, container, height) { + const overflowElement = global.document.getElementById('overflow'); + + if (overflowElement) { + setStyle(overflowElement, 'display', ''); } else { - writeScript(window, endpoint); + createOverflowElement(global, container, height); + } +} + +/** + * Helper function for handling successful resize requests + * @param {!Window} global + */ +function handleResizeSuccess(global) { + const overflow = global.document.getElementById('overflow'); + if (overflow) { + setStyle(overflow, 'display', 'none'); } } + +/** + * Helper function to create an overflow element if one doesn't exist + * @param {!Window} global + * @param {!Element} container + * @param {number} height + */ +function createOverflowElement(global, container, height) { + const overflow = global.document.createElement('div'); + overflow.id = 'overflow'; + + overflow.addEventListener('click', () => { + handleResize(global, container, height); + }); + + setStyles(overflow, { + position: 'absolute', + height: `60px`, + bottom: '0', + left: '0', + right: '0', + background: 'green', + cursor: 'pointer', + }); + + const indicator = ``; + + const chevron = global.document.createElement('div'); + setStyles(chevron, { + width: '40px', + height: '40px', + margin: '0 auto', + display: 'block', + }); + + chevron./*OK*/ innerHTML = indicator; + overflow.appendChild(chevron); + container.appendChild(overflow); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function revcontent(global, data) { + global.revcontent = global.revcontent || { + boundingClientRect: {}, + detectedWidth: 0, + detectedHeight: 0, + widgetData: { + ...data, + }, + requestedSize: 0, + }; + + const requiredParams = []; + const optionalParams = []; + const containerDiv = global.document.getElementById('c'); + + if ( + typeof data.placementType !== 'undefined' && + data.placementType === 'evergreen' + ) { + // Revcontent evergreen builds do not have an explicit group of optional parameters + // to support configuration flexibility. Add any present attributes that are not + // part of the required ones to the optional list so tag passes validation. + const required = options?.params?.evergreen?.required; + const optional = Object.keys(data).filter((entry) => { + if (!required.includes(entry)) { + return entry; + } + }); + + optional.push(...options?.params?.evergreen?.optional); + requiredParams.push(...required); + optionalParams.push(...optional); + + global.context.observeIntersection(function (changes) { + /** @type {!Array} */ changes.forEach(function (c) { + if (c.intersectionRect.height) { + if (global.revcontent?.boundingClientRect !== c.boundingClientRect) { + Object.assign( + global.revcontent?.boundingClientRect, + c.boundingClientRect + ); + } + + if (c.boundingClientRect.height < global.revcontent.requestedSize) { + handleResize(global, containerDiv, global.revcontent.requestedSize); + } + } + }); + }); + } else { + const required = options?.params?.legacy?.required; + const optional = options?.params?.legacy?.optional; + + requiredParams.push(...required); + optionalParams.push(...optional); + } + + const endpoint = getEndpoint(data); + data.endpoint = data.endpoint ? data.endpoint : 'trends.revcontent.com'; + + validateData(data, requiredParams, optionalParams); + + global.data = data; + loadScript(global, endpoint, () => { + window.addEventListener('message', (e) => { + if ( + e.data.source == 'revcontent' && + e.data.action == 'RESIZE_AMP_TAG' && + typeof e.data.height != 'undefined' + ) { + global.revcontent.requestedSize = e.data.height; + handleResize(global, containerDiv, e.data.height); + } + }); + }); +} diff --git a/ads/vendors/revcontent.md b/ads/vendors/revcontent.md index 4b313313ce6a..72b3e94f6cb8 100644 --- a/ads/vendors/revcontent.md +++ b/ads/vendors/revcontent.md @@ -1,49 +1,67 @@ # Revcontent -## Example +## Examples + +### Standard Tag + +The standard AMP tag for Revcontent is the basic AMP ads integration and is used as shown here: ```html -
Loading ...
+ data-id="{YOUR_WIDGET_ID}"> +
+``` + +### Evergreen Tag + +The evergreen AMP tag leverages new and improved ad code from Revcontent and supports greater performance and customization capabilities: + +```html + ``` ## Configuration -For semantics of configuration, please see [Revcontent's documentation](https://faq.revcontent.com/). +For help with configuration, please contact Revcontent or refer to their documentation. -Supported parameters: +### Standard Tag Options + +#### Required Parameters - `data-id` -- `data-revcontent` -- `data-env` -- `data-wrapper` -- `data-endpoint` -- `data-ssl` -- `data-testing` -- `data-loadscript` +- `width` +- `height` + +#### Optional Parameters + - `data-sub-ids` +- `data-gam-enabled` +- `data-gdpr` +- `data-gdpr-consent` +- `data-us-privacy` + +### Evergreen Tag Options + +#### Required Parameters + +- `data-widget-id` +- `data-pub-id` +- `width` +- `height` -## Auto-sizing of Ads +#### Optional Parameters -Revcontent's AMP service will be updated to support resizing of ads for improved rendering, no additional tag parameters are required at this time. +Please contact Revcontent for more information about optional parameters for evergreen tags. diff --git a/ads/vendors/runative.js b/ads/vendors/runative.js index d42d182581de..4828d35f7b3a 100644 --- a/ads/vendors/runative.js +++ b/ads/vendors/runative.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; const requiredParams = ['spot']; diff --git a/ads/vendors/sas.js b/ads/vendors/sas.js index a2b0aa992f67..98b71432f73a 100644 --- a/ads/vendors/sas.js +++ b/ads/vendors/sas.js @@ -1,6 +1,8 @@ +import {validateData, writeScript} from '#3p/3p'; + import {getMultiSizeDimensions} from '#ads/google/utils'; + import {parseJson} from '#core/types/object/json'; -import {validateData, writeScript} from '#3p/3p'; /** * @param {!Window} global diff --git a/ads/vendors/seedtag.js b/ads/vendors/seedtag.js index 6e988e9bf1fe..d7954767520f 100644 --- a/ads/vendors/seedtag.js +++ b/ads/vendors/seedtag.js @@ -24,7 +24,7 @@ export function seedtag(global, data) { /*eslint "local/camelcase": 0*/ global._seedtag_amp = { allowed_data: ['adunitId', 'placement', 'publisherId', 'forceCreative'], - mandatory_data: ['adunitId', 'placement', 'publisherId'], + mandatory_data: ['adunitId'], data, }; @@ -34,5 +34,5 @@ export function seedtag(global, data) { global._seedtag_amp.allowed_data ); - loadScript(global, 'https://config.seedtag.com/omid/bridge/bridge.js'); + loadScript(global, `https://t.seedtag.com/a/${data.adunitId}.js`); } diff --git a/ads/vendors/seedtag.md b/ads/vendors/seedtag.md index 9308a30b27a7..721c1ea01958 100644 --- a/ads/vendors/seedtag.md +++ b/ads/vendors/seedtag.md @@ -19,15 +19,11 @@ limitations under the License. ## Example ```html - ``` @@ -39,41 +35,9 @@ For configuration semantics, please contact [Seedtag](https://www.seedtag.com/co Supported parameters: - `data-adunit-id` mandatory -- `data-publisher-id` mandatory -- `data-placement` mandatory -## Testing - -You can force a creative for test using this parameters - -- `data-force-creative` optional - -Currently you can test those format : -| Format | value | -| ------ | ----- | -| display 300x250 | https://creatives.seedtag.com/vast/ssp-responses/display-OM300x250.json | -| display 300x50 | https://creatives.seedtag.com/vast/ssp-responses/display-OM300x50.json | -| display 320x480 | https://creatives.seedtag.com/vast/ssp-responses/display-OM320x480.json | -| display 970x90 | https://creatives.seedtag.com/vast/ssp-responses/display-OM970x90.json | -| display 970x250 | https://creatives.seedtag.com/vast/ssp-responses/display-OM970x250.json | -| video outstream | https://creatives.seedtag.com/vast/ssp-responses/video-cov.json | - -this way : - -```html - - -``` ## User Consent Integration - When [user consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required, Seedtag ad approaches user consent in the following ways: - `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. diff --git a/ads/vendors/sekindo.js b/ads/vendors/sekindo.js index 3d950ca8e803..4b453e50afe9 100644 --- a/ads/vendors/sekindo.js +++ b/ads/vendors/sekindo.js @@ -1,6 +1,7 @@ -import {hasOwn} from '#core/types/object'; import {loadScript, validateData} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/sevio.js b/ads/vendors/sevio.js new file mode 100644 index 000000000000..fb8bef0b6d03 --- /dev/null +++ b/ads/vendors/sevio.js @@ -0,0 +1,33 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sevio(global, data) { + validateData(data, ['zone']); + + const container = document.getElementById('c'); + const adDivId = 'sevioads-amp-' + encodeURIComponent(data.zone); + const adDiv = document.createElement('div'); + adDiv.setAttribute('id', adDivId); + container.appendChild(adDiv); + + loadScript( + global, + 'https://cdn.adx.ws/scripts/amp.js', + () => { + window.sevioAmpLoader = window.sevioAmpLoader || []; + window.sevioAmpLoader.push({ + zoneId: encodeURIComponent(data.zone), + placeholderId: encodeURIComponent(adDivId), + wu: window.location.href, + width: data.width, + height: data.height, + }); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/vendors/sevio.md b/ads/vendors/sevio.md new file mode 100644 index 000000000000..d49a0a3df890 --- /dev/null +++ b/ads/vendors/sevio.md @@ -0,0 +1,19 @@ +# Sevio + +## Example of Sevio's model implementation + +### Basic + +```html + + +``` + +### Required parameters + +- `data-zone`: Zone ID diff --git a/ads/vendors/skoiy.js b/ads/vendors/skoiy.js new file mode 100644 index 000000000000..10c248586a4b --- /dev/null +++ b/ads/vendors/skoiy.js @@ -0,0 +1,17 @@ +import {validateData, writeScript} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function skoiy(global, data) { + // TODO: check mandatory fields + validateData(data, ['token', 'format', 'id'], ['query']); + + const {format, id, query = '', token} = data; + + const options = query ? '&' + query : ''; + + const url = `https://svas.skoiy.xyz/${format}/${token}/${id}?amp${options}`; + writeScript(global, url); +} diff --git a/ads/vendors/skoiy.md b/ads/vendors/skoiy.md new file mode 100644 index 000000000000..b98a6afd7296 --- /dev/null +++ b/ads/vendors/skoiy.md @@ -0,0 +1,26 @@ +![skoiy logo](https://skoiy.com/img/logos/logo-skoiy.svg) + +Skoiy is an integrated suite of platforms that simplify and centralize the management, distribution, and monetization of digital media across a suite of independent platforms that fit the needs of each project. + +## Example + +```html + + +``` + +### Required parameters + +- `data-token` +- `data-format` +- `data-id` + +### Optional parameters + +- `data-query` diff --git a/ads/vendors/smartadserver.js b/ads/vendors/smartadserver.js deleted file mode 100644 index fc27c18b698f..000000000000 --- a/ads/vendors/smartadserver.js +++ /dev/null @@ -1,13 +0,0 @@ -import {loadScript} from '#3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function smartadserver(global, data) { - // For more flexibility, we construct the call to SmartAdServer's URL in the - // external loader, based on the data received from the AMP tag. - loadScript(global, 'https://ec-ns.sascdn.com/diff/js/amp.v0.js', () => { - global.sas.callAmpAd(data); - }); -} diff --git a/ads/vendors/smartclip.md b/ads/vendors/smartclip.md index 4f63a6fffa59..e6409f0f44ef 100644 --- a/ads/vendors/smartclip.md +++ b/ads/vendors/smartclip.md @@ -9,6 +9,7 @@ type="smartclip" data-plc="68005" data-sz="400x320" + data-block-on-consent="_till_responded" > ``` diff --git a/ads/vendors/sona.js b/ads/vendors/sona.js index c5d00220d628..e5caecf11407 100644 --- a/ads/vendors/sona.js +++ b/ads/vendors/sona.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; /** diff --git a/ads/vendors/spotx.js b/ads/vendors/spotx.js index bdb5d6a8e8b6..dadfc54122ff 100644 --- a/ads/vendors/spotx.js +++ b/ads/vendors/spotx.js @@ -1,6 +1,7 @@ -import {hasOwn} from '#core/types/object'; import {validateData} from '#3p/3p'; +import {hasOwn} from '#core/types/object'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/springAds.js b/ads/vendors/springAds.js index efd0115a3d35..206a954601d3 100644 --- a/ads/vendors/springAds.js +++ b/ads/vendors/springAds.js @@ -1,4 +1,5 @@ import {computeInMasterFrame, loadScript} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; /** diff --git a/ads/vendors/ssp.js b/ads/vendors/ssp.js index f5e110ebb443..b92d27b6f33e 100644 --- a/ads/vendors/ssp.js +++ b/ads/vendors/ssp.js @@ -15,8 +15,9 @@ */ import {computeInMasterFrame, loadScript, validateData} from '#3p/3p'; -import {parseJson} from '#core/types/object/json'; + import {setStyle, setStyles} from '#core/dom/style'; +import {parseJson} from '#core/types/object/json'; /* * How to develop: @@ -27,7 +28,7 @@ import {setStyle, setStyles} from '#core/dom/style'; * @param {!Array.} array * @param {!Function} iteratee * - * @return {Object} + * @return {object} */ export function keyBy(array, iteratee) { return array.reduce( @@ -52,7 +53,7 @@ export function runWhenFetchingSettled(fetchingSSPs, cb) { /** * @param {!Element} element * @param {boolean} center - * @param {Object} dimensions + * @param {object} dimensions */ export function handlePosition(element, center, dimensions) { const styles = { @@ -199,7 +200,6 @@ export function ssp(global, data) { // todo on SSP side (option to register error callback) // requestErrorCallback: () => {}, AMPcallback: (ads) => { - /** @suppress {checkTypes} */ const adById = keyBy(ads, (item) => item.id); const ad = adById[position['id']]; diff --git a/ads/vendors/sunmedia.js b/ads/vendors/sunmedia.js index 62213af8b07e..3757837cf5c2 100644 --- a/ads/vendors/sunmedia.js +++ b/ads/vendors/sunmedia.js @@ -7,7 +7,7 @@ import {loadScript, validateData} from '#3p/3p'; export function sunmedia(global, data) { /*eslint "local/camelcase": 0*/ global._sunmedia_amp = { - allowed_data: ['cskp', 'crst', 'cdb', 'cid'], + allowed_data: ['cid'], mandatory_data: ['cid'], data, }; @@ -20,6 +20,6 @@ export function sunmedia(global, data) { loadScript( global, - 'https://vod.addevweb.com/sunmedia/amp/ads/SMIntextAMP.js' + `https://static.sunmedia.tv/integrations/${data.cid}/${data.cid}.js` ); } diff --git a/ads/vendors/sunmedia.md b/ads/vendors/sunmedia.md index cfff4ed59b01..7df310ac5026 100644 --- a/ads/vendors/sunmedia.md +++ b/ads/vendors/sunmedia.md @@ -4,15 +4,13 @@ ```html - + data-cid="5898aec7-32f5-4e3b-bd67-41f68b58b412" + data-block-on-consent="_till_responded" +>
``` ## Configuration @@ -21,9 +19,4 @@ For further information, please contact [SunMedia](http://sunmedia.tv/#contact). ### Required parameters -- `data-cid`: Client ID provided by SunMedia - -### Optional parameters - -- `data-cskp`: Indicates skip button enabled -- `data-crst`: Indicates restart option enabled +- `data-cid`: Integration ID provided by SunMedia diff --git a/ads/vendors/teads.js b/ads/vendors/teads.js index 8002eecd3cf2..358748efb646 100644 --- a/ads/vendors/teads.js +++ b/ads/vendors/teads.js @@ -5,34 +5,15 @@ import {loadScript, validateData} from '#3p/3p'; * @param {!Object} data */ export function teads(global, data) { + validateData(data, ['pid']); + /*eslint "local/camelcase": 0*/ global._teads_amp = { - allowed_data: ['pid', 'tag'], - mandatory_data: ['pid'], - mandatory_tag_data: ['tta', 'ttp'], data, }; - validateData( - data, - global._teads_amp.mandatory_data, - global._teads_amp.allowed_data + loadScript( + global, + 'https://a.teads.tv/page/' + encodeURIComponent(data.pid) + '/tag' ); - - if (data.tag) { - validateData(data.tag, global._teads_amp.mandatory_tag_data); - global._tta = data.tag.tta; - global._ttp = data.tag.ttp; - - loadScript( - global, - 'https://s8t.teads.tv/media/format/' + - encodeURI(data.tag.js || 'v3/teads-format.min.js') - ); - } else { - loadScript( - global, - 'https://a.teads.tv/page/' + encodeURIComponent(data.pid) + '/tag' - ); - } } diff --git a/ads/vendors/torimochi.js b/ads/vendors/torimochi.js index e57f4a2a353c..837b51e42ff0 100644 --- a/ads/vendors/torimochi.js +++ b/ads/vendors/torimochi.js @@ -1,6 +1,7 @@ -import {parseJson} from '#core/types/object/json'; import {validateData, writeScript} from '#3p/3p'; +import {parseJson} from '#core/types/object/json'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/trafficstars.js b/ads/vendors/trafficstars.js index 1baf6886e2c3..b904e6092c17 100644 --- a/ads/vendors/trafficstars.js +++ b/ads/vendors/trafficstars.js @@ -1,14 +1,25 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; const requiredParams = ['spot']; -const jsonParams = ['nativeSettings', 'wrapperStyles', 'iFrameStyles']; +const jsonParams = [ + 'nativeSettings', + 'wrapperStyles', + 'iFrameStyles', + 'queriesParams', +]; const params = [ 'uploadLink', - 'queriesParams', 'onLoadResponseHook', 'onSpotRenderedHook', 'onLoadErrorHook', + 'subid', + 'subid_1', + 'subid_2', + 'subid_3', + 'subid_4', + 'subid_5', ]; const optionalParams = params.concat(jsonParams); const adContainerId = 'trafficstars_id'; diff --git a/ads/vendors/trafficstars.md b/ads/vendors/trafficstars.md index bab8bdcbc8dd..6880a2b52bfc 100644 --- a/ads/vendors/trafficstars.md +++ b/ads/vendors/trafficstars.md @@ -18,6 +18,12 @@ Serves ads from the [TrafficStars](https://www.trafficstars.com/). data-on-load-response-hook="{(data: TMasterSpotData) => void}" data-on-spot-rendered-hook="{(element: HTMLElement) => void}" data-on-load-error-hook="{() => void}" + data-subid="{string}" + data-subid_1="{string}" + data-subid_2="{string}" + data-subid_3="{string}" + data-subid_4="{string}" + data-subid_5="{string}" >
``` @@ -40,3 +46,9 @@ For details on the configuration semantics, please contact the ad network or ref - `onLoadResponseHook` - hook after ad request success - `onSpotRenderedHook` - hook after ad render success - `onLoadErrorHook` - hook after ad request error +- `subid` - attribute of data +- `subid_1` - attribute of data +- `subid_2` - attribute of data +- `subid_3` - attribute of data +- `subid_4` - attribute of data +- `subid_5` - attribute of data diff --git a/ads/vendors/uas.js b/ads/vendors/uas.js index 933ef6f67f2b..9a90aa192794 100644 --- a/ads/vendors/uas.js +++ b/ads/vendors/uas.js @@ -14,9 +14,10 @@ * limitations under the License. */ -import {hasOwn} from '#core/types/object'; import {loadScript, validateData} from '#3p/3p'; + import {setStyles} from '#core/dom/style'; +import {hasOwn} from '#core/types/object'; /** * @param {!Object} theObject diff --git a/ads/vendors/uzou.js b/ads/vendors/uzou.js index e7da69f3f6d5..eda5fc0d7e75 100644 --- a/ads/vendors/uzou.js +++ b/ads/vendors/uzou.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; /** diff --git a/ads/vendors/videonow.js b/ads/vendors/videonow.js index 74c0f770c04e..35ed7b578bb0 100644 --- a/ads/vendors/videonow.js +++ b/ads/vendors/videonow.js @@ -1,4 +1,5 @@ import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; import {tryDecodeUriComponent} from '#core/types/string/url'; diff --git a/ads/vendors/viralize.js b/ads/vendors/viralize.js index e39abc1621d2..1058c2117b82 100644 --- a/ads/vendors/viralize.js +++ b/ads/vendors/viralize.js @@ -1,7 +1,9 @@ -import {addParamsToUrl} from '../../src/url'; import {loadScript, validateData} from '#3p/3p'; + import {parseJson} from '#core/types/object/json'; +import {addParamsToUrl} from '../../src/url'; + /** * @param {!Window} global * @param {!Object} data diff --git a/ads/vendors/vox.js b/ads/vendors/vox.js new file mode 100644 index 000000000000..1096a2fdbd82 --- /dev/null +++ b/ads/vendors/vox.js @@ -0,0 +1,13 @@ +import {loadScript, validateData} from '#3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function vox(global, data) { + const scriptUrl = 'https://st.hbrd.io/amp-helper.js?t=' + Date.now(); + validateData(data, ['placementid']); + loadScript(global, scriptUrl); + global._voxParams = data['voxParams']; + global._voxPlacementId = data['placementid']; +} diff --git a/ads/vendors/vox.md b/ads/vendors/vox.md new file mode 100644 index 000000000000..fced18ef782f --- /dev/null +++ b/ads/vendors/vox.md @@ -0,0 +1,15 @@ +# VOX + +## Example + +```html + + +``` + +### Required parameters + +- `data-placementid` diff --git a/ads/vendors/yahoo.js b/ads/vendors/yahoo.js deleted file mode 100644 index 3ef287d28235..000000000000 --- a/ads/vendors/yahoo.js +++ /dev/null @@ -1,17 +0,0 @@ -import {validateData, writeScript} from '#3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yahoo(global, data) { - if (data.sid) { - validateData(data, ['sid', 'site', 'sa']); - global.yadData = data; - writeScript(global, 'https://s.yimg.com/aaq/ampad/display.js'); - } else if (data.config) { - validateData(data, ['config']); - global.jacData = data; - writeScript(global, 'https://jac.yahoosandbox.com/amp/jac.js'); - } -} diff --git a/ads/vendors/yahoo.md b/ads/vendors/yahoo.md deleted file mode 100644 index 97eea2222b91..000000000000 --- a/ads/vendors/yahoo.md +++ /dev/null @@ -1,64 +0,0 @@ -# yahoo - -## Display ad - -```html - - -``` - -```html - - -``` - -### Configuration - -For configuration details, please contact https://advertising.yahoo.com/contact. - -Supported parameters: - -- `height` -- `width` -- `data-sid` -- `data-site` -- `data-sa` -- `data-config` -- `data-stylesheet` - -### Required Parameters: - -`data-config` - Config for ad call JAC -`data-sa` - Config for ad call DARLA - -### Optional parameters: - -`data-stylesheet` - stylesheet to use inside iframe - -### Configuration Details - -For JAC ads : Required -"adServer":{"1AS":{region":"US"}}, -"positions":{"FB":{alias:"1111111"},"sizes":["300x250"]}}, -"site":{name:{"autoblogAMP"}},"spaceId":"111111"} - -Alias, Sizes, SiteName and spaceId should be replaced by correct values. -NOTE: SiteName should be site name + "AMP" - -Optional -"params":{"name":"value"} diff --git a/ads/vendors/yektanet.js b/ads/vendors/yektanet.js index ca543a6d6522..10010bd27de7 100644 --- a/ads/vendors/yektanet.js +++ b/ads/vendors/yektanet.js @@ -26,7 +26,7 @@ export function yektanet(global, data) { ].join('0'); const scriptSrc = isBanner - ? 'https://cdn.yektanet.com/template/bnrs/yn_bnr.min.js' + ? 'https://cdn.yektanet.com/template/bnrs/yn_bnr.amp.js' : `https://cdn.yektanet.com/js/${encodeURIComponent( data['publisherName'] )}/${encodeURIComponent(data['scriptName'])}`; diff --git a/ads/vendors/yieldbot.js b/ads/vendors/yieldbot.js index f47938bfd0e6..1689e1a04d90 100644 --- a/ads/vendors/yieldbot.js +++ b/ads/vendors/yieldbot.js @@ -1,6 +1,9 @@ -import {getMultiSizeDimensions} from '#ads/google/utils'; import {loadScript, validateData} from '#3p/3p'; + +import {getMultiSizeDimensions} from '#ads/google/utils'; + import {rethrowAsync} from '#core/error'; + import {user} from '#utils/log'; /** diff --git a/amp.js b/amp.js index 76d16c21900c..c1549f481b99 100755 --- a/amp.js +++ b/amp.js @@ -36,9 +36,7 @@ createTask('check-renovate-config', 'checkRenovateConfig'); createTask('check-sourcemaps', 'checkSourcemaps'); createTask('check-types', 'checkTypes'); createTask('check-video-interface-list', 'checkVideoInterfaceList'); -createTask('cherry-pick', 'cherryPick'); createTask('clean'); -createTask('codecov-upload', 'codecovUpload'); createTask('compile-jison', 'compileJison'); createTask('css'); createTask('default', 'defaultTask', 'default-task'); @@ -51,8 +49,6 @@ createTask('integration'); createTask('lint'); createTask('make-extension', 'makeExtension'); createTask('markdown-toc', 'markdownToc'); -createTask('performance'); -createTask('performance-urls', 'performanceUrls'); createTask('pr-check', 'prCheck'); createTask('prepend-global', 'prependGlobal'); createTask('presubmit', 'presubmit'); @@ -62,7 +58,6 @@ createTask('serve'); createTask('server-tests', 'serverTests'); createTask('storybook'); createTask('sweep-experiments', 'sweepExperiments'); -createTask('test-report-upload', 'testReportUpload'); createTask('unit'); createTask('validate-html-fixtures', 'validateHtmlFixtures'); createTask('validator'); diff --git a/babel.config.js b/babel.config.js index 0f9e69f3f0a2..404d0bc9c7db 100644 --- a/babel.config.js +++ b/babel.config.js @@ -10,6 +10,7 @@ 'use strict'; const {cyan, yellow} = require('kleur/colors'); + const {log} = require('./build-system/common/logging'); /** @@ -18,15 +19,18 @@ const {log} = require('./build-system/common/logging'); */ const babelTransforms = new Map([ ['babel-jest', 'getEmptyConfig'], - ['post-closure', 'getPostClosureConfig'], - ['pre-closure', 'getPreClosureConfig'], + ['nomodule-loader', 'getNoModuleLoaderConfig'], ['test', 'getTestConfig'], ['unminified', 'getUnminifiedConfig'], + ['unminified-ssr-css', 'getUnminifiedSsrCssConfig'], ['minified', 'getMinifiedConfig'], + ['minified-ssr-css', 'getMinifiedSsrCssConfig'], ['jss', 'getJssConfig'], ['@babel/eslint-parser', 'getEslintConfig'], ['is-enum-value', 'getEmptyConfig'], ['import-resolver', 'getEmptyConfig'], + ['react-minified', 'getReactMinifiedConfig'], + ['react-unminified', 'getReactUnminifiedConfig'], ]); /** diff --git a/build-system/babel-config/helpers.js b/build-system/babel-config/helpers.js index 4d1a6d6a768e..8ff8f67a052a 100644 --- a/build-system/babel-config/helpers.js +++ b/build-system/babel-config/helpers.js @@ -27,9 +27,10 @@ function getExperimentConstant() { /** * Computes options for minify-replace and returns the plugin object. * + * @param {!Object=} opt_overrides overrides for BUILD_CONSTANTS * @return {Array} */ -function getReplacePlugin() { +function getReplacePlugin(opt_overrides) { /** * @param {string} identifierName the identifier name to replace * @param {boolean|string} value the value to replace with @@ -43,7 +44,8 @@ function getReplacePlugin() { return {identifierName, replacement}; } - const replacements = Object.entries(BUILD_CONSTANTS).map(([ident, val]) => + const constants = Object.assign({}, BUILD_CONSTANTS, opt_overrides); + const replacements = Object.entries(constants).map(([ident, val]) => createReplacement(ident, val) ); diff --git a/build-system/babel-config/import-resolver.js b/build-system/babel-config/import-resolver.js index 9ccd99bf4480..cf7d731cacee 100644 --- a/build-system/babel-config/import-resolver.js +++ b/build-system/babel-config/import-resolver.js @@ -2,8 +2,9 @@ const fs = require('fs'); const path = require('path'); +const moduleResolver = require('babel-plugin-module-resolver'); -const TSCONFIG_PATH = path.join(__dirname, '..', '..', 'tsconfig.json'); +const TSCONFIG_PATH = path.join(__dirname, '..', '..', 'tsconfig.base.json'); let tsConfigPaths = null; /** @@ -20,7 +21,7 @@ let tsConfigPaths = null; * '#foo': './src/foo', * '#bar': './bar', * } - * @return {!Object} + * @return {!{[key: string]: string}} */ function readJsconfigPaths() { if (!tsConfigPaths) { @@ -40,14 +41,35 @@ function readJsconfigPaths() { return tsConfigPaths; } +/** + * Remap external modules that rely on React if building for Preact. + * @param {'preact' | 'react'} buildFor + * @return {object} + */ +function moduleAliases(buildFor) { + if (buildFor === 'react') { + return {}; + } + return { + 'react': './src/react', + 'react-dom': './src/react/dom', + }; +} + /** * Import map configuration. - * @return {!Object} + * @param {'preact' | 'react' | null} buildFor determines whether to include preact or react aliases or neither. By default, uses preact aliases (in addition to jsconfig paths). + * @return {object} */ -function getImportResolver() { +function getImportResolver(buildFor = 'preact') { return { root: ['.'], - alias: readJsconfigPaths(), + alias: { + ...readJsconfigPaths(), + ...(buildFor ? moduleAliases(buildFor) : {}), + }, + extensions: ['.js', '.jsx', '.ts', '.tsx'], + stripExtensions: [], babelOptions: { caller: { name: 'import-resolver', @@ -59,27 +81,47 @@ function getImportResolver() { /** * Produces an alias map with paths relative to the provided root. * @param {string} rootDir - * @return {!Object} + * @param {'preact' | 'react'} buildFor + * @return {!{[key: string]: string}} */ -function getRelativeAliasMap(rootDir) { +function getRelativeAliasMap(rootDir, buildFor = 'preact') { return Object.fromEntries( - Object.entries(getImportResolver().alias).map(([alias, destPath]) => [ - alias, - path.join(rootDir, destPath), - ]) + Object.entries(getImportResolver(buildFor).alias).map( + ([alias, destPath]) => [alias, path.join(rootDir, destPath)] + ) ); } /** * Import resolver Babel plugin configuration. + * @param {'preact' | 'react'} buildFor * @return {!Array} */ -function getImportResolverPlugin() { - return ['module-resolver', getImportResolver()]; +function getImportResolverPlugin(buildFor = 'preact') { + return ['module-resolver', getImportResolver(buildFor)]; +} + +/** + * Resolves a filepath using the same logic as the rest of our build pipeline (babel module-resolver). + * The return value is a relative path from the amphtml folder. + * + * @param {string} filepath + * @param {'preact' | 'react' | null} buildFor + * @return {string} + */ +function resolvePath(filepath, buildFor = 'preact') { + // 2nd arg is a file from which to make a relative path. + // The actual file doesn't need to exist. In this case it is process.cwd()/anything + return moduleResolver.resolvePath( + filepath, + 'anything', + getImportResolver(buildFor) + ); } module.exports = { getImportResolver, getImportResolverPlugin, getRelativeAliasMap, + resolvePath, }; diff --git a/build-system/babel-config/jss-config.js b/build-system/babel-config/jss-config.js deleted file mode 100644 index 7d996ec0297c..000000000000 --- a/build-system/babel-config/jss-config.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -// Babel cannot directly return a valid css file. -// Therefore we provide and export this options object to allow extraction -// of the created css file via side effect from running babel.transfrorm(). -const jssOptions = {css: 'REPLACED_BY_BABEL'}; - -/** - * Gets the config for transforming a JSS file to CSS - * Only used to generate CSS files for Bento components. - * - * @return {!Object} - */ -function getJssConfig() { - return { - plugins: [ - ['./build-system/babel-plugins/babel-plugin-transform-jss', jssOptions], - ], - }; -} - -module.exports = { - getJssConfig, - jssOptions, -}; diff --git a/build-system/babel-config/minified-config.js b/build-system/babel-config/minified-config.js index e75e6016ad22..e72f361a55c7 100644 --- a/build-system/babel-config/minified-config.js +++ b/build-system/babel-config/minified-config.js @@ -8,9 +8,12 @@ const {getReplacePlugin} = require('./helpers'); /** * Gets the config for minified babel transforms run, used by 3p vendors. * + * @param {'preact' | 'react'} buildFor + * @param {!Object=} opt_replacePluginOverrides * @return {!Object} */ -function getMinifiedConfig() { +function getMinifiedConfig(buildFor = 'preact', opt_replacePluginOverrides) { + const isEsmBuild = argv.esm || argv.sxg; const isProd = argv._.includes('dist') && !argv.fortesting; const reactJsxPlugin = [ @@ -21,11 +24,14 @@ function getMinifiedConfig() { useSpread: true, }, ]; - const replacePlugin = getReplacePlugin(); + const replacePlugin = getReplacePlugin(opt_replacePluginOverrides); const plugins = [ 'optimize-objstr', - getImportResolverPlugin(), + './build-system/babel-plugins/babel-plugin-deep-pure', + './build-system/babel-plugins/babel-plugin-mangle-object-values', + './build-system/babel-plugins/babel-plugin-jsx-style-object', + getImportResolverPlugin(buildFor), argv.coverage ? 'babel-plugin-istanbul' : null, './build-system/babel-plugins/babel-plugin-imported-helpers', './build-system/babel-plugins/babel-plugin-transform-inline-isenumvalue', @@ -34,7 +40,7 @@ function getMinifiedConfig() { './build-system/babel-plugins/babel-plugin-transform-rename-privates', './build-system/babel-plugins/babel-plugin-dom-jsx-svg-namespace', reactJsxPlugin, - (argv.esm || argv.sxg) && + isEsmBuild && './build-system/babel-plugins/babel-plugin-transform-dev-methods', // TODO(alanorozco): Remove `replaceCallArguments` once serving infra is up. [ @@ -45,6 +51,7 @@ function getMinifiedConfig() { './build-system/babel-plugins/babel-plugin-transform-amp-extension-call', './build-system/babel-plugins/babel-plugin-transform-html-template', './build-system/babel-plugins/babel-plugin-transform-jss', + './build-system/babel-plugins/babel-plugin-amp-story-supported-languages', replacePlugin, './build-system/babel-plugins/babel-plugin-transform-amp-asserts', // TODO(erwinm, #28698): fix this in fixit week @@ -56,27 +63,36 @@ function getMinifiedConfig() { './build-system/babel-plugins/babel-plugin-amp-mode-transformer', BUILD_CONSTANTS, ], - ['@babel/plugin-transform-for-of', {loose: true, allowArrayLike: true}], + !isEsmBuild + ? ['@babel/plugin-transform-for-of', {loose: true, allowArrayLike: true}] + : null, ].filter(Boolean); const presetEnv = [ '@babel/preset-env', { bugfixes: true, modules: false, - targets: argv.esm || argv.sxg ? {esmodules: true} : {ie: 11, chrome: 41}, + targets: isEsmBuild ? {esmodules: true} : {ie: 11, chrome: 41}, + shippedProposals: true, + exclude: isEsmBuild ? ['@babel/plugin-transform-for-of'] : [], }, ]; + const presetTypescript = [ + '@babel/preset-typescript', + {jsxPragma: 'Preact', jsxPragmaFrag: 'Preact.Fragment'}, + ]; return { compact: false, plugins, - sourceMaps: 'inline', - presets: [presetEnv], + sourceMaps: true, + presets: [presetTypescript, presetEnv], retainLines: true, assumptions: { constantSuper: true, noClassCalls: true, setClassMethods: true, + setPublicClassFields: true, }, }; } diff --git a/build-system/babel-config/minified-ssr-css-config.js b/build-system/babel-config/minified-ssr-css-config.js new file mode 100644 index 000000000000..7874daa5c58e --- /dev/null +++ b/build-system/babel-config/minified-ssr-css-config.js @@ -0,0 +1,18 @@ +'use strict'; + +const {getMinifiedConfig} = require('./minified-config'); + +/** + * Gets the config for minified babel transforms run, used by 3p vendors with + * IS_SSR set to true. + * + * @return {!Object} + */ +function getMinifiedSsrCssConfig() { + // We use the default `buildFor` which is preact. + return getMinifiedConfig('preact', {IS_SSR_CSS: true}); +} + +module.exports = { + getMinifiedSsrCssConfig, +}; diff --git a/build-system/babel-config/nomodule-loader-config.js b/build-system/babel-config/nomodule-loader-config.js new file mode 100644 index 000000000000..0c38ee3579f7 --- /dev/null +++ b/build-system/babel-config/nomodule-loader-config.js @@ -0,0 +1,10 @@ +/** @return {{[string: string]: any}} */ +function getNoModuleLoaderConfig() { + return { + plugins: ['./build-system/babel-plugins/babel-plugin-nomodule-loader'], + }; +} + +module.exports = { + getNoModuleLoaderConfig, +}; diff --git a/build-system/babel-config/post-closure-config.js b/build-system/babel-config/post-closure-config.js deleted file mode 100644 index 1b06061f828a..000000000000 --- a/build-system/babel-config/post-closure-config.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); - -/** - * Gets the config for post-closure babel transforms run during `amp dist`. - * - * @return {!Object} - */ -function getPostClosureConfig() { - const postClosurePlugins = [ - argv.esm || argv.sxg - ? './build-system/babel-plugins/babel-plugin-const-transformer' - : null, - argv.esm || argv.sxg - ? './build-system/babel-plugins/babel-plugin-transform-remove-directives' - : null, - argv.esm || argv.sxg - ? './build-system/babel-plugins/babel-plugin-transform-stringish-literals' - : null, - './build-system/babel-plugins/babel-plugin-transform-minified-comments', - ].filter(Boolean); - - return { - compact: false, - inputSourceMap: false, - plugins: postClosurePlugins, - retainLines: false, - sourceMaps: true, - }; -} - -module.exports = { - getPostClosureConfig, -}; diff --git a/build-system/babel-config/pre-closure-config.js b/build-system/babel-config/pre-closure-config.js deleted file mode 100644 index f37d2c46df32..000000000000 --- a/build-system/babel-config/pre-closure-config.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const {BUILD_CONSTANTS} = require('../compile/build-constants'); -const {getImportResolverPlugin} = require('./import-resolver'); -const {getReplacePlugin} = require('./helpers'); - -/** - * Gets the config for pre-closure babel transforms run during `amp dist`. - * - * @return {!Object} - */ -function getPreClosureConfig() { - const isCheckTypes = argv._.includes('check-types'); - const isProd = argv._.includes('dist') && !argv.fortesting; - - const reactJsxPlugin = [ - '@babel/plugin-transform-react-jsx', - { - pragma: 'Preact.createElement', - pragmaFrag: 'Preact.Fragment', - useSpread: true, - }, - ]; - const replacePlugin = getReplacePlugin(); - const preClosurePlugins = [ - 'optimize-objstr', - getImportResolverPlugin(), - argv.coverage ? 'babel-plugin-istanbul' : null, - './build-system/babel-plugins/babel-plugin-imported-helpers', - './build-system/babel-plugins/babel-plugin-transform-inline-isenumvalue', - './build-system/babel-plugins/babel-plugin-transform-fix-leading-comments', - './build-system/babel-plugins/babel-plugin-transform-promise-resolve', - './build-system/babel-plugins/babel-plugin-dom-jsx-svg-namespace', - reactJsxPlugin, - argv.esm || argv.sxg - ? './build-system/babel-plugins/babel-plugin-transform-dev-methods' - : null, - // TODO(alanorozco): Remove `replaceCallArguments` once serving infra is up. - [ - './build-system/babel-plugins/babel-plugin-transform-log-methods', - {replaceCallArguments: false}, - ], - './build-system/babel-plugins/babel-plugin-transform-parenthesize-expression', - './build-system/babel-plugins/babel-plugin-transform-json-import', - './build-system/babel-plugins/babel-plugin-transform-amp-extension-call', - './build-system/babel-plugins/babel-plugin-transform-html-template', - './build-system/babel-plugins/babel-plugin-transform-jss', - './build-system/babel-plugins/babel-plugin-transform-default-assignment', - replacePlugin, - './build-system/babel-plugins/babel-plugin-transform-amp-asserts', - // TODO(erwinm, #28698): fix this in fixit week - // argv.esm - //? './build-system/babel-plugins/babel-plugin-transform-function-declarations' - //: null, - !isCheckTypes && - './build-system/babel-plugins/babel-plugin-transform-json-configuration', - isProd && [ - './build-system/babel-plugins/babel-plugin-amp-mode-transformer', - BUILD_CONSTANTS, - ], - ].filter(Boolean); - const presetEnv = [ - '@babel/preset-env', - { - bugfixes: true, - modules: false, - targets: {esmodules: true}, - }, - ]; - const preClosurePresets = argv.esm || argv.sxg ? [presetEnv] : []; - const preClosureConfig = { - compact: false, - plugins: preClosurePlugins, - presets: preClosurePresets, - retainLines: true, - sourceMaps: true, - }; - return preClosureConfig; -} - -module.exports = { - getPreClosureConfig, -}; diff --git a/build-system/babel-config/react-config.js b/build-system/babel-config/react-config.js new file mode 100644 index 000000000000..c722cc2454e0 --- /dev/null +++ b/build-system/babel-config/react-config.js @@ -0,0 +1,43 @@ +'use strict'; + +const path = require('path'); +const {getMinifiedConfig} = require('./minified-config'); +const {getUnminifiedConfig} = require('./unminified-config'); + +/** + * @param {!Object} config + * @return {object} + */ +function mergeReactBabelConfig(config) { + const rootDir = path.join(__dirname, '../../'); + return { + ...config, + plugins: [ + path.join( + rootDir, + './build-system/babel-plugins/babel-plugin-react-style-props' + ), + ...(config.plugins || []), + ], + }; +} + +/** + * @return {!Object} + */ +function getReactUnminifiedConfig() { + return mergeReactBabelConfig(getUnminifiedConfig('react')); +} + +/** + * @return {!Object} + */ +function getReactMinifiedConfig() { + return mergeReactBabelConfig(getMinifiedConfig('react')); +} + +module.exports = { + getReactMinifiedConfig, + getReactUnminifiedConfig, + mergeReactBabelConfig, +}; diff --git a/build-system/babel-config/test-config.js b/build-system/babel-config/test-config.js index 9f42956bd5ae..439dc4921652 100644 --- a/build-system/babel-config/test-config.js +++ b/build-system/babel-config/test-config.js @@ -38,30 +38,43 @@ function getTestConfig() { modules: 'commonjs', loose: true, targets: {'browsers': ['Last 2 versions']}, + shippedProposals: true, }, ]; + const presetTypescript = [ + '@babel/preset-typescript', + {jsxPragma: 'Preact', jsxPragmaFrag: 'Preact.Fragment'}, + ]; const replacePlugin = getReplacePlugin(); const replaceGlobalsPlugin = getReplaceGlobalsPlugin(); const testPlugins = [ getImportResolverPlugin(), argv.coverage ? instanbulPlugin : null, + './build-system/babel-plugins/babel-plugin-amp-story-supported-languages', replacePlugin, replaceGlobalsPlugin, + './build-system/babel-plugins/babel-plugin-jsx-style-object', + './build-system/babel-plugins/babel-plugin-mangle-object-values', './build-system/babel-plugins/babel-plugin-imported-helpers', './build-system/babel-plugins/babel-plugin-transform-json-import', './build-system/babel-plugins/babel-plugin-transform-json-configuration', './build-system/babel-plugins/babel-plugin-transform-jss', './build-system/babel-plugins/babel-plugin-transform-promise-resolve', - '@babel/plugin-transform-classes', './build-system/babel-plugins/babel-plugin-dom-jsx-svg-namespace', reactJsxPlugin, ].filter(Boolean); - const testPresets = [presetEnv]; + const testPresets = [presetTypescript, presetEnv]; return { compact: false, plugins: testPlugins, presets: testPresets, sourceMaps: 'inline', + assumptions: { + constantSuper: true, + noClassCalls: true, + setClassMethods: true, + setPublicClassFields: true, + }, }; } diff --git a/build-system/babel-config/unminified-config.js b/build-system/babel-config/unminified-config.js index 38bea6abcfb3..e763b5bc6ebf 100644 --- a/build-system/babel-config/unminified-config.js +++ b/build-system/babel-config/unminified-config.js @@ -7,9 +7,13 @@ const {getReplacePlugin} = require('./helpers'); /** * Gets the config for babel transforms run during `amp build`. * + * @param {'preact' | 'react'} buildFor + * @param {!Object=} opt_replacePluginOverrides * @return {!Object} */ -function getUnminifiedConfig() { +function getUnminifiedConfig(buildFor = 'preact', opt_replacePluginOverrides) { + const isEsmBuild = argv.esm || argv.sxg; + const reactJsxPlugin = [ '@babel/plugin-transform-react-jsx', { @@ -19,21 +23,27 @@ function getUnminifiedConfig() { }, ]; - const targets = - argv.esm || argv.sxg ? {esmodules: true} : {browsers: ['Last 2 versions']}; const presetEnv = [ '@babel/preset-env', { bugfixes: true, modules: false, loose: true, - targets, + targets: isEsmBuild ? {esmodules: true} : {browsers: ['Last 2 versions']}, + shippedProposals: true, + exclude: isEsmBuild ? ['@babel/plugin-transform-for-of'] : [], }, ]; - const replacePlugin = getReplacePlugin(); + const presetTypescript = [ + '@babel/preset-typescript', + {jsxPragma: 'Preact', jsxPragmaFrag: 'Preact.Fragment'}, + ]; + const replacePlugin = getReplacePlugin(opt_replacePluginOverrides); const unminifiedPlugins = [ - getImportResolverPlugin(), + './build-system/babel-plugins/babel-plugin-jsx-style-object', + getImportResolverPlugin(buildFor), argv.coverage ? 'babel-plugin-istanbul' : null, + './build-system/babel-plugins/babel-plugin-amp-story-supported-languages', replacePlugin, './build-system/babel-plugins/babel-plugin-transform-json-import', './build-system/babel-plugins/babel-plugin-transform-json-configuration', @@ -41,20 +51,20 @@ function getUnminifiedConfig() { './build-system/babel-plugins/babel-plugin-transform-fix-leading-comments', './build-system/babel-plugins/babel-plugin-transform-promise-resolve', './build-system/babel-plugins/babel-plugin-transform-amp-extension-call', - '@babel/plugin-transform-classes', './build-system/babel-plugins/babel-plugin-dom-jsx-svg-namespace', reactJsxPlugin, ].filter(Boolean); - const unminifiedPresets = [presetEnv]; + const unminifiedPresets = [presetTypescript, presetEnv]; return { compact: false, plugins: unminifiedPlugins, presets: unminifiedPresets, - sourceMaps: 'inline', + sourceMaps: true, assumptions: { constantSuper: true, noClassCalls: true, setClassMethods: true, + setPublicClassFields: true, }, }; } diff --git a/build-system/babel-config/unminified-ssr-css-config.js b/build-system/babel-config/unminified-ssr-css-config.js new file mode 100644 index 000000000000..f06995b2207d --- /dev/null +++ b/build-system/babel-config/unminified-ssr-css-config.js @@ -0,0 +1,17 @@ +'use strict'; + +const {getUnminifiedConfig} = require('./unminified-config'); + +/** + * Gets the config for babel transforms run during `amp build` with `IS_SSR` + * set to true. + * + * @return {!Object} + */ +function getUnminifiedSsrCssConfig() { + return getUnminifiedConfig('preact', {IS_SSR_CSS: true}); +} + +module.exports = { + getUnminifiedSsrCssConfig, +}; diff --git a/build-system/babel-plugins/OWNERS b/build-system/babel-plugins/OWNERS index eeccea4248aa..8bd1335073dc 100644 --- a/build-system/babel-plugins/OWNERS +++ b/build-system/babel-plugins/OWNERS @@ -8,7 +8,6 @@ {name: 'ampproject/wg-infra'}, {name: 'ampproject/wg-performance'}, {name: 'erwinmombay', notify: true}, - {name: 'jridgewell', notify: true}, ], }, ], diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/index.js b/build-system/babel-plugins/babel-plugin-amp-config-urls/index.js new file mode 100644 index 000000000000..52f482d6ab12 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/index.js @@ -0,0 +1,67 @@ +/** + * @fileoverview + * Replaces imports of `src/config/urls` into references to self.AMP.config.urls + * This allows us to re-use the value defined by the runtime in extensions. + * Binaries that don't depend on the runtime should not use this transform, so + * that they can directly use the values from `src/config/urls`. + */ +const {dirname, join, posix, relative, sep} = require('path'); + +const importSource = join(process.cwd(), 'src', 'config', 'urls'); +const reference = 'self.AMP.config.urls'; + +/** + * @param {string} fromFilename + * @param {string} toModule + * @return {string} + */ +function relativeModule(fromFilename, toModule) { + const resolved = relative(dirname(fromFilename), toModule); + const resolvedPosix = resolved.split(sep).join(posix.sep); + return resolvedPosix.startsWith('.') ? resolvedPosix : `./${resolvedPosix}`; +} + +/** + * @param {import('@babel/core')} babel + * @return {import('@babel/core').PluginObj} + */ +module.exports = function (babel) { + let importSourceRelative; + const {template, types: t} = babel; + const buildNamespace = template.statement( + `const name = /* #__PURE__ */ (() => ${reference})()`, + {preserveComments: true, placeholderPattern: /^name$/} + ); + return { + name: 'amp-config-urls', + visitor: { + Program: { + enter(_, state) { + const {filename} = state; + if (!filename) { + throw new Error( + 'babel-plugin-amp-config-urls must be called with a filename' + ); + } + importSourceRelative = relativeModule(filename, importSource); + }, + }, + ImportDeclaration(path) { + const {source} = path.node; + if (!t.isStringLiteral(source, {value: importSourceRelative})) { + return; + } + for (const specifier of path.get('specifiers')) { + if (!specifier.isImportNamespaceSpecifier()) { + throw specifier.buildCodeFrameError( + `Unresolvable specifier. You must import \`urls\` as a namespace:\n` + + `\`import * as urls from '${source.value}';\`` + ); + } + const {name} = specifier.node.local; + path.replaceWith(buildNamespace({name})); + } + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-default-import/input.mjs b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-default-import/input.mjs new file mode 100644 index 000000000000..a9c30acc1622 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-default-import/input.mjs @@ -0,0 +1 @@ +import urls from '../../../../../../../src/config/urls'; diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-default-import/options.json b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-default-import/options.json new file mode 100644 index 000000000000..0cdb369f01da --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-default-import/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../.." + ], + "throws": "Unresolvable specifier. You must import `urls` as a namespace:\n`import * as urls from '../../../../../../../src/config/urls';`" +} diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-named-import/input.mjs b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-named-import/input.mjs new file mode 100644 index 000000000000..366039f5536a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-named-import/input.mjs @@ -0,0 +1 @@ +import {named, foo as bar} from '../../../../../../../src/config/urls'; diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-named-import/options.json b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-named-import/options.json new file mode 100644 index 000000000000..0cdb369f01da --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/error-named-import/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../.." + ], + "throws": "Unresolvable specifier. You must import `urls` as a namespace:\n`import * as urls from '../../../../../../../src/config/urls';`" +} diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/input.mjs b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/input.mjs new file mode 100644 index 000000000000..4e5dda86a5a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/input.mjs @@ -0,0 +1 @@ +import * as urls from 'something other than src/config/urls'; diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/options.json b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/output.mjs b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/output.mjs new file mode 100644 index 000000000000..4e5dda86a5a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/ignored-import-source/output.mjs @@ -0,0 +1 @@ +import * as urls from 'something other than src/config/urls'; diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/input.mjs b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/input.mjs new file mode 100644 index 000000000000..dd2a9fb445a0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/input.mjs @@ -0,0 +1 @@ +import * as urls from '../../../../../../../src/config/urls'; diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/options.json b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/output.mjs b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/output.mjs new file mode 100644 index 000000000000..4310e0c1f4d1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/fixtures/transform/transform-namespace/output.mjs @@ -0,0 +1 @@ +const urls = /* #__PURE__ */(() => self.AMP.config.urls)(); diff --git a/build-system/babel-plugins/babel-plugin-amp-config-urls/test/index.js b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/index.js new file mode 100644 index 000000000000..23251129fae9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-config-urls/test/index.js @@ -0,0 +1,3 @@ +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); 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 021c9787928f..d2e28cab9d72 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 @@ -3,7 +3,7 @@ /** * Changes the values of getMode().test, getMode().localDev to false * and getMode().localDev to true. - * @param {Object} babelTypes + * @param {object} babelTypes */ const {dirname, join, relative, resolve} = require('path').posix; diff --git a/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/index.js b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/index.js new file mode 100644 index 000000000000..1cea6f43e135 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/index.js @@ -0,0 +1,34 @@ +const fastGlob = require('fast-glob'); +const pathMod = require('path'); +const LOCALES_DIR = 'extensions/amp-story/1.0/_locales/*.json'; +const AMP_STORY_SUPPORTED_LANGUAGES = fastGlob.sync(LOCALES_DIR).map((x) => { + return pathMod.basename(x, '.json'); +}); + +/** + * @fileoverview + * We need to ensure that the language files we request from the Google AMP + * cache is supported on the client before making the request. To do this + * we embed the supported list of language codes through this symbol + * replacement which can be used anywhere in the codebase. + */ + +/** + * @interface {babel.PluginPass} + * @param {babel} babel + * @return {babel.PluginObj} + */ +module.exports = function ({types: t}) { + return { + name: 'amp-story-supported-languages', + visitor: { + ReferencedIdentifier(path) { + if (path.node.name !== 'AMP_STORY_SUPPORTED_LANGUAGES') { + return; + } + + path.replaceWith(t.valueToNode(AMP_STORY_SUPPORTED_LANGUAGES)); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/input.js b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/input.js new file mode 100644 index 000000000000..ebe5f3a7f2eb --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/input.js @@ -0,0 +1,8 @@ +let AMP_STORY_SUPPORTED_LANGUAGES; +AMP_STORY_SUPPORTED_LANGUAGES = 'test'; + +const a = AMP_STORY_SUPPORTED_LANGUAGES; + +if (AMP_STORY_SUPPORTED_LANGUAGES.join(' ')) { + console.log(AMP_STORY_SUPPORTED_LANGUAGES); +} diff --git a/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/options.json b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/output.js b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/output.js new file mode 100644 index 000000000000..8043b39db9d8 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/fixtures/transform/replace/output.js @@ -0,0 +1,7 @@ +let AMP_STORY_SUPPORTED_LANGUAGES; +AMP_STORY_SUPPORTED_LANGUAGES = 'test'; +const a = ["af", "am", "ar", "bg", "bn", "bs", "ca", "cs", "da", "de", "el", "en-GB", "en", "es-419", "es", "et", "eu", "fa", "fi", "fil", "fr", "gl", "gu", "hi", "hr", "hu", "id", "is", "it", "iw", "ja", "ka", "km", "kn", "ko", "lo", "lt", "lv", "mk", "ml", "mn", "mr", "ms", "my", "ne", "nl", "no", "pa", "pl", "pt-BR", "pt-PT", "ro", "ru", "si", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tr", "uk", "ur", "vi", "zh-CN", "zh-TW", "zu"]; + +if (["af", "am", "ar", "bg", "bn", "bs", "ca", "cs", "da", "de", "el", "en-GB", "en", "es-419", "es", "et", "eu", "fa", "fi", "fil", "fr", "gl", "gu", "hi", "hr", "hu", "id", "is", "it", "iw", "ja", "ka", "km", "kn", "ko", "lo", "lt", "lv", "mk", "ml", "mn", "mr", "ms", "my", "ne", "nl", "no", "pa", "pl", "pt-BR", "pt-PT", "ro", "ru", "si", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tr", "uk", "ur", "vi", "zh-CN", "zh-TW", "zu"].join(' ')) { + console.log(["af", "am", "ar", "bg", "bn", "bs", "ca", "cs", "da", "de", "el", "en-GB", "en", "es-419", "es", "et", "eu", "fa", "fi", "fil", "fr", "gl", "gu", "hi", "hr", "hu", "id", "is", "it", "iw", "ja", "ka", "km", "kn", "ko", "lo", "lt", "lv", "mk", "ml", "mn", "mr", "ms", "my", "ne", "nl", "no", "pa", "pl", "pt-BR", "pt-PT", "ro", "ru", "si", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tr", "uk", "ur", "vi", "zh-CN", "zh-TW", "zu"]); +} diff --git a/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/index.js b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/index.js new file mode 100644 index 000000000000..23251129fae9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-amp-story-supported-languages/test/index.js @@ -0,0 +1,3 @@ +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/index.js b/build-system/babel-plugins/babel-plugin-deep-pure/index.js new file mode 100644 index 000000000000..e81ea32d2d6d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/index.js @@ -0,0 +1,89 @@ +const pureFnName = 'pure'; +const pureFnImportSourceRe = new RegExp( + '(^#|/)core/types/pure(\\.{js,jsx,ts,tsx})?$' +); + +/** + * Deeply adds #__PURE__ comments to an expression passed to a function named + * `pure()`. + * @param {import('@babel/core')} babel + * @return {import('@babel/core').PluginObj} + */ +module.exports = function (babel) { + const {types: t} = babel; + + /** + * @param {import('@babel/core').NodePath} path + * @return {boolean} + */ + function referencesPureFnImport(path) { + if (!path.isIdentifier()) { + return false; + } + const binding = path.scope.getBinding(path.node.name); + if (!binding || binding.kind !== 'module') { + return false; + } + const bindingPath = binding.path; + const parent = bindingPath.parentPath; + if ( + !parent?.isImportDeclaration() || + !pureFnImportSourceRe.test(parent?.node.source.value) + ) { + return false; + } + return ( + bindingPath.isImportSpecifier() && + t.isIdentifier(bindingPath.node.imported, {name: pureFnName}) + ); + } + + /** + * @param {import('@babel/core').NodePath} path + * @return {boolean} + */ + function isPureFnCallExpression(path) { + return ( + path.node.arguments.length === 1 && + referencesPureFnImport(path.get('callee')) + ); + } + + /** @param {import('@babel/core').NodePath} path */ + function addPureComment(path) { + path.addComment('leading', ' #__PURE__ '); + } + + /** @param {import('@babel/core').NodePath} path */ + function replaceWithFirstArgument(path) { + path.replaceWith(path.node.arguments[0]); + } + + return { + name: 'deep-pure', + visitor: { + CallExpression(path) { + if (isPureFnCallExpression(path)) { + path.traverse({ + NewExpression(path) { + addPureComment(path); + }, + CallExpression(path) { + if (isPureFnCallExpression(path)) { + replaceWithFirstArgument(path); + } else { + addPureComment(path); + } + }, + MemberExpression(path) { + throw path.buildCodeFrameError( + `${pureFnName}() expressions cannot contain member expressions` + ); + }, + }); + replaceWithFirstArgument(path); + } + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/error-member-expression/input.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/error-member-expression/input.mjs new file mode 100644 index 000000000000..ed635b154ed1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/error-member-expression/input.mjs @@ -0,0 +1,2 @@ +import {pure} from '#core/types/pure'; +pure(error.memberExpression); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/error-member-expression/options.json b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/error-member-expression/options.json new file mode 100644 index 000000000000..90cfd0869484 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/error-member-expression/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../.." + ], + "throws": "pure() expressions cannot contain member expressions" +} diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/input.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/input.mjs new file mode 100644 index 000000000000..88ef5e8f5e4e --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/input.mjs @@ -0,0 +1,4 @@ +import {pure} from 'something other than core/types/pure'; +const ignored = pure( + foo() || new Bar() || pure(pure(foo('bar', bar(), new Baz())) || 'foo') +); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/options.json b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/output.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/output.mjs new file mode 100644 index 000000000000..2f1bf185e592 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-import-source/output.mjs @@ -0,0 +1,2 @@ +import { pure } from 'something other than core/types/pure'; +const ignored = pure(foo() || new Bar() || pure(pure(foo('bar', bar(), new Baz())) || 'foo')); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/input.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/input.mjs new file mode 100644 index 000000000000..897e0c8c815d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/input.mjs @@ -0,0 +1,4 @@ +const pure = (v) => v; +const ignored = pure( + foo() || new Bar() || pure(pure(foo('bar', bar(), new Baz())) || 'foo') +); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/options.json b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/output.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/output.mjs new file mode 100644 index 000000000000..195af8bae9b6 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/ignored-not-import/output.mjs @@ -0,0 +1,3 @@ +const pure = v => v; + +const ignored = pure(foo() || new Bar() || pure(pure(foo('bar', bar(), new Baz())) || 'foo')); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/input.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/input.mjs new file mode 100644 index 000000000000..4cfbe91c349b --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/input.mjs @@ -0,0 +1,6 @@ +import {pure} from '#core/types/pure'; +const ignored = foo(); +const a = pure( + foo() || new Bar() || pure(pure(foo('bar', bar(), new Baz())) || 'foo') +); +const b = pure('foo'); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/options.json b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/output.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/output.mjs new file mode 100644 index 000000000000..8b3ef0f0ac19 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-aliased-path/output.mjs @@ -0,0 +1,4 @@ +import { pure } from '#core/types/pure'; +const ignored = foo(); +const a = /* #__PURE__ */foo() || /* #__PURE__ */new Bar() || /* #__PURE__ */foo('bar', /* #__PURE__ */bar(), /* #__PURE__ */new Baz()) || 'foo'; +const b = 'foo'; diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/input.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/input.mjs new file mode 100644 index 000000000000..bfe5e959b860 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/input.mjs @@ -0,0 +1,6 @@ +import {pure} from '../../../../../../../src/core/types/pure'; +const ignored = foo(); +const a = pure( + foo() || new Bar() || pure(pure(foo('bar', bar(), new Baz())) || 'foo') +); +const b = pure('foo'); diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/options.json b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/output.mjs b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/output.mjs new file mode 100644 index 000000000000..7b5127c163dc --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/fixtures/transform/transform-relative-path/output.mjs @@ -0,0 +1,4 @@ +import { pure } from '../../../../../../../src/core/types/pure'; +const ignored = foo(); +const a = /* #__PURE__ */foo() || /* #__PURE__ */new Bar() || /* #__PURE__ */foo('bar', /* #__PURE__ */bar(), /* #__PURE__ */new Baz()) || 'foo'; +const b = 'foo'; diff --git a/build-system/babel-plugins/babel-plugin-deep-pure/test/index.js b/build-system/babel-plugins/babel-plugin-deep-pure/test/index.js new file mode 100644 index 000000000000..23251129fae9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-deep-pure/test/index.js @@ -0,0 +1,3 @@ +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-imported-helpers/index.js b/build-system/babel-plugins/babel-plugin-imported-helpers/index.js index bf53e4d93e1a..4cbca20b0c05 100644 --- a/build-system/babel-plugins/babel-plugin-imported-helpers/index.js +++ b/build-system/babel-plugins/babel-plugin-imported-helpers/index.js @@ -19,7 +19,7 @@ const helperMap = { * Cache of identifiers to each helper's ImportDeclaration, per file. * @const {!WeakMap< * import('@babel/core').BabelFileResult, - * Object + * {[key: string]: import('@babel/core').types.Identifier} * >} */ const importNamesPerFile = new WeakMap(); diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/index.js b/build-system/babel-plugins/babel-plugin-jsx-style-object/index.js new file mode 100644 index 000000000000..85cda949e591 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/index.js @@ -0,0 +1,195 @@ +/** + * @fileoverview + * Converts Object Expression syntax in `style={{foo: 'bar'}}` to a concatenated + * string expression. + * + * This only transforms a file if it imports the `baseModule` for JSX below, + * which lacks runtime support for converting the Object into a string. + * This transform takes that responsibility instead. + */ + +const {addNamed} = require('@babel/helper-module-imports'); + +const baseModule = 'core/dom/jsx'; +const helperModule = '#core/dom/jsx/style-property-string'; +const helperFnName = 'jsxStylePropertyString'; + +// All values from here, converted to dash-case: +// https://github.com/facebook/react/blob/a7c5726/packages/react-dom/src/shared/CSSProperty.js +const nonDimensional = new Set([ + 'animation-iteration-count', + 'aspect-ratio', + 'border-image-outset', + 'border-image-slice', + 'border-image-width', + 'box-flex', + 'box-flex-group', + 'box-ordinal-group', + 'column-count', + 'columns', + 'flex', + 'flex-grow', + 'flex-positive', + 'flex-shrink', + 'flex-negative', + 'flex-order', + 'grid-area', + 'grid-row', + 'grid-row-end', + 'grid-row-span', + 'grid-row-start', + 'grid-column', + 'grid-column-end', + 'grid-column-span', + 'grid-column-start', + 'font-weight', + 'line-clamp', + 'line-height', + 'opacity', + 'order', + 'orphans', + 'tab-size', + 'widows', + 'z-index', + 'zoom', + + // SVG-related properties + 'fill-opacity', + 'flood-opacity', + 'stop-opacity', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-miterlimit', + 'stroke-opacity', + 'stroke-width', +]); + +module.exports = function (babel) { + const {types: t} = babel; + + const dashCase = (camelCase) => + camelCase.replace(/[A-Z]/g, '-$&').toLowerCase(); + + let hasBaseModule = false; + + /** + * @param {babel.NodePath} path + * @return {?babel.Node} + */ + function transformProp(path) { + // @ts-ignore + const name = path.node.key.name || path.node.key.value; + const cssName = dashCase(name); + const isDimensional = !nonDimensional.has(cssName); + const value = path.get('value'); + const evaluated = value.evaluate(); + + // If we can evaluate the value, return a composed string. + if (evaluated.confident) { + const {value} = evaluated; + if (value == null || value === '') { + return null; + } + const withUnit = + isDimensional && typeof value === 'number' ? `${value}px` : value; + return t.stringLiteral(`${cssName}:${withUnit};`); + } + + // Otherwise call the helper function to evaluate nullish and dimensional values. + const helperFn = addNamed(path, helperFnName, helperModule); + const args = [t.stringLiteral(cssName), value.node]; + if (isDimensional) { + args.push(t.booleanLiteral(true)); + } + return t.callExpression(helperFn, args); + } + + /** + * @param {(babel.Node|null)[]} props + * @return {?babel.Node} + */ + function mergeBinaryConcat(props) { + let expr = null; + while (props.length) { + const part = props.shift(); + if (part) { + if (!expr) { + expr = part; + } else { + expr = t.binaryExpression('+', expr, part); + } + } + } + return expr; + } + + /** @param {babel.NodePath} path */ + function replaceExpression(path) { + if (path.isLogicalExpression()) { + if ( + path.node.operator === '&&' || + path.node.operator === '||' || + path.node.operator === '??' + ) { + replaceExpression(path.get('right')); + } + return; + } + if (path.isConditionalExpression()) { + replaceExpression(path.get('consequent')); + replaceExpression(path.get('alternate')); + return; + } + if (!path.isObjectExpression()) { + return; + } + path = /** @type {babel.NodePath} */ (path); + // @ts-ignore - expects NodePath|NodePath[] but it's always NodePath[] + const props = path.get('properties').map((prop) => { + if (prop.isSpreadElement()) { + throw prop.buildCodeFrameError( + 'You should not use spread properties in style object expressions.' + ); + } + if (prop.node.computed) { + throw prop + .get('key') + .buildCodeFrameError( + 'You should not use computed props in style object expressions. Instead, use multiple properties directly. They can be "null" when unwanted.' + ); + } + return transformProp(prop); + }); + const merged = mergeBinaryConcat(props); + path.replaceWith(merged || t.stringLiteral('')); + } + + return { + name: 'jsx-style-object', + visitor: { + Program: { + enter(path) { + hasBaseModule = false; + path.traverse({ + ImportDeclaration(path) { + if (path.node.source.value.endsWith(baseModule)) { + hasBaseModule = true; + path.stop(); + } + }, + }); + }, + }, + JSXAttribute(path) { + if (!hasBaseModule) { + return; + } + if (!t.isJSXIdentifier(path.node.name, {name: 'style'})) { + return; + } + const expression = path.get('value.expression'); + replaceExpression(expression); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/input.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/input.mjs new file mode 100644 index 000000000000..7152cb70656b --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/input.mjs @@ -0,0 +1,94 @@ +import * as jsx from 'ANYWHERE_LEADING_TO/core/dom/jsx'; + +const dimensional = () =>
; + +const nonDimensional = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+); diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/options.json b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/options.json new file mode 100644 index 000000000000..4bbe336b0476 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../..", + "@babel/plugin-syntax-jsx" + ] +} diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/output.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/output.mjs new file mode 100644 index 000000000000..7f891887d13b --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/dimensional/output.mjs @@ -0,0 +1,136 @@ +import { jsxStylePropertyString as _jsxStylePropertyString44 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString43 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString42 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString41 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString40 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString39 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString38 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString37 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString36 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString35 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString34 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString33 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString32 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString31 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString30 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString29 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString28 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString27 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString26 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString25 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString24 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString23 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString22 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString21 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString20 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString19 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString18 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString17 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString16 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString15 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString14 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString13 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString12 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString11 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString10 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString9 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString8 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString7 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString6 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString5 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString4 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString3 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString2 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString } from "#core/dom/jsx/style-property-string"; +import * as jsx from 'ANYWHERE_LEADING_TO/core/dom/jsx'; + +const dimensional = () =>
; + +const nonDimensional = () =>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
; diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/input.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/input.mjs new file mode 100644 index 000000000000..0ae71b93ede0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/input.mjs @@ -0,0 +1,11 @@ +const ignoredBecauseNamespaceIsNotImported = () => ( +
+); diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/options.json b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/options.json new file mode 100644 index 000000000000..4bbe336b0476 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../..", + "@babel/plugin-syntax-jsx" + ] +} diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/output.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/output.mjs new file mode 100644 index 000000000000..b751701b9dc3 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/ignored/output.mjs @@ -0,0 +1,7 @@ +const ignoredBecauseNamespaceIsNotImported = () =>
; diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/input.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/input.mjs new file mode 100644 index 000000000000..c22dfc2906d2 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/input.mjs @@ -0,0 +1,13 @@ +import * as jsx from 'ANYWHERE_LEADING_TO/core/dom/jsx'; + +const logicalAnd = () =>
; + +const logicalAndDeep = () =>
; + +const logicalOr = () =>
; + +const ternary = () =>
; + +const ternaryNested = () => ( +
+); diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/options.json b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/options.json new file mode 100644 index 000000000000..4bbe336b0476 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../..", + "@babel/plugin-syntax-jsx" + ] +} diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/output.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/output.mjs new file mode 100644 index 000000000000..356b533f6558 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/nested/output.mjs @@ -0,0 +1,11 @@ +import * as jsx from 'ANYWHERE_LEADING_TO/core/dom/jsx'; + +const logicalAnd = () =>
; + +const logicalAndDeep = () =>
; + +const logicalOr = () =>
; + +const ternary = () =>
; + +const ternaryNested = () =>
; diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/input.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/input.mjs new file mode 100644 index 000000000000..92d68cf6c28e --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/input.mjs @@ -0,0 +1,67 @@ +import * as jsx from 'ANYWHERE_LEADING_TO/core/dom/jsx'; + +const randomObjectExpressionsAreUnmodified = () => ({ + background, + color: null, +}); + +const nonObjectExpressionsAreUnmodified = () => ( +
+
+
+
+); + +const red = 'red'; +const otherAttributesAreUnmodified = () => ( +
+); + +const modified = () => ( +
+); + +const emptyStringValueIsRemoved = () =>
; + +let dynamic = 0; +function modifyDynamicValue() { + dynamic = 1; +} + +let backgroundColor = 'blue'; + +const constants = () => ( +
+); + +const empty = () => ( +
+); diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/options.json b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/options.json new file mode 100644 index 000000000000..4bbe336b0476 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "../../../..", + "@babel/plugin-syntax-jsx" + ] +} diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/output.mjs b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/output.mjs new file mode 100644 index 000000000000..0af9fc73dbce --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/fixtures/transform/transformed/output.mjs @@ -0,0 +1,39 @@ +import { jsxStylePropertyString as _jsxStylePropertyString2 } from "#core/dom/jsx/style-property-string"; +import { jsxStylePropertyString as _jsxStylePropertyString } from "#core/dom/jsx/style-property-string"; +import * as jsx from 'ANYWHERE_LEADING_TO/core/dom/jsx'; + +const randomObjectExpressionsAreUnmodified = () => ({ + background, + color: null +}); + +const nonObjectExpressionsAreUnmodified = () =>
+
+
+
; + +const red = 'red'; + +const otherAttributesAreUnmodified = () =>
; + +const modified = () =>
; + +const emptyStringValueIsRemoved = () =>
; + +let dynamic = 0; + +function modifyDynamicValue() { + dynamic = 1; +} + +let backgroundColor = 'blue'; + +const constants = () =>
; + +const empty = () =>
; diff --git a/build-system/babel-plugins/babel-plugin-jsx-style-object/test/index.js b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/index.js new file mode 100644 index 000000000000..23251129fae9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-jsx-style-object/test/index.js @@ -0,0 +1,3 @@ +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-mangle-object-values/index.js b/build-system/babel-plugins/babel-plugin-mangle-object-values/index.js new file mode 100644 index 000000000000..e2a91b357fd5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-mangle-object-values/index.js @@ -0,0 +1,45 @@ +/** + * @fileoverview Mangles the values of an object expression into their numeric + * indices + 1. + * This is useful when defining large enums, as it allows us to use descriptive + * values during development, but use short mangled values during production. + */ + +const fnName = 'mangleObjectValues'; + +module.exports = function (babel) { + const {types: t} = babel; + + return { + name: 'mangle-object-values', + visitor: { + CallExpression(path) { + const callee = path.get('callee'); + if (!callee.isIdentifier({name: fnName})) { + return; + } + const {name} = callee.node.name; + const args = path.node.arguments; + if (args.length !== 1 || !t.isObjectExpression(args[0])) { + throw path.buildCodeFrameError( + `${name}() should take a single argument that's an object expression.` + ); + } + const objectExpression = args[0]; + const seen = {}; + for (const [i, prop] of objectExpression.properties.entries()) { + if (!t.isStringLiteral(prop.value)) { + throw path.buildCodeFrameError( + `${name}() should only be used on object expressions with string values.` + ); + } + const {value} = prop.value; + // Skip 0 because it's falsy + const mangled = (seen[value] = seen[value] || i + 1); + prop.value = t.valueToNode(mangled); + } + path.replaceWith(objectExpression); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/mangle/input.js b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/mangle/input.js new file mode 100644 index 000000000000..0928f9ae4a5f --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/mangle/input.js @@ -0,0 +1,132 @@ +// not mangled: +const x = { + foo: 'bar', + bar: 'qux', + [x]: 'y', +}; + +// not mangled: +const y = notMangled({ + foo: 'bar', + bar: 'qux', + [x]: 'y', +}); + +// mangled: +const z = mangleObjectValues({ + foo: 'bar', + bar: 'qux', + [x]: 'y', +}); + +// dupes: +const a = mangleObjectValues({ + foo: 'bar', + baz: 'qux', + // should be same as foo: + bar: 'bar', +}); + +// 100 elements: +const b = mangleObjectValues({ + k0: 'v_0', + k1: 'v_1', + k2: 'v_2', + k3: 'v_3', + k4: 'v_4', + k5: 'v_5', + k6: 'v_6', + k7: 'v_7', + k8: 'v_8', + k9: 'v_9', + k10: 'v_10', + k11: 'v_11', + k12: 'v_12', + k13: 'v_13', + k14: 'v_14', + k15: 'v_15', + k16: 'v_16', + k17: 'v_17', + k18: 'v_18', + k19: 'v_19', + k20: 'v_20', + k21: 'v_21', + k22: 'v_22', + k23: 'v_23', + k24: 'v_24', + k25: 'v_25', + k26: 'v_26', + k27: 'v_27', + k28: 'v_28', + k29: 'v_29', + k30: 'v_30', + k31: 'v_31', + k32: 'v_32', + k33: 'v_33', + k34: 'v_34', + k35: 'v_35', + k36: 'v_36', + k37: 'v_37', + k38: 'v_38', + k39: 'v_39', + k40: 'v_40', + k41: 'v_41', + k42: 'v_42', + k43: 'v_43', + k44: 'v_44', + k45: 'v_45', + k46: 'v_46', + k47: 'v_47', + k48: 'v_48', + k49: 'v_49', + k50: 'v_50', + k51: 'v_51', + k52: 'v_52', + k53: 'v_53', + k54: 'v_54', + k55: 'v_55', + k56: 'v_56', + k57: 'v_57', + k58: 'v_58', + k59: 'v_59', + k60: 'v_60', + k61: 'v_61', + k62: 'v_62', + k63: 'v_63', + k64: 'v_64', + k65: 'v_65', + k66: 'v_66', + k67: 'v_67', + k68: 'v_68', + k69: 'v_69', + k70: 'v_70', + k71: 'v_71', + k72: 'v_72', + k73: 'v_73', + k74: 'v_74', + k75: 'v_75', + k76: 'v_76', + k77: 'v_77', + k78: 'v_78', + k79: 'v_79', + k80: 'v_80', + k81: 'v_81', + k82: 'v_82', + k83: 'v_83', + k84: 'v_84', + k85: 'v_85', + k86: 'v_86', + k87: 'v_87', + k88: 'v_88', + k89: 'v_89', + k90: 'v_90', + k91: 'v_91', + k92: 'v_92', + k93: 'v_93', + k94: 'v_94', + k95: 'v_95', + k96: 'v_96', + k97: 'v_97', + k98: 'v_98', + k99: 'v_99', +}); diff --git a/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/mangle/output.js b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/mangle/output.js new file mode 100644 index 000000000000..deb34a065a1e --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/mangle/output.js @@ -0,0 +1,128 @@ +// not mangled: +const x = { + foo: 'bar', + bar: 'qux', + [x]: 'y' +}; // not mangled: + +const y = notMangled({ + foo: 'bar', + bar: 'qux', + [x]: 'y' +}); // mangled: + +const z = { + foo: 1, + bar: 2, + [x]: 3 +}; // dupes: + +const a = { + foo: 1, + baz: 2, + // should be same as foo: + bar: 1 +}; // 100 elements: + +const b = { + k0: 1, + k1: 2, + k2: 3, + k3: 4, + k4: 5, + k5: 6, + k6: 7, + k7: 8, + k8: 9, + k9: 10, + k10: 11, + k11: 12, + k12: 13, + k13: 14, + k14: 15, + k15: 16, + k16: 17, + k17: 18, + k18: 19, + k19: 20, + k20: 21, + k21: 22, + k22: 23, + k23: 24, + k24: 25, + k25: 26, + k26: 27, + k27: 28, + k28: 29, + k29: 30, + k30: 31, + k31: 32, + k32: 33, + k33: 34, + k34: 35, + k35: 36, + k36: 37, + k37: 38, + k38: 39, + k39: 40, + k40: 41, + k41: 42, + k42: 43, + k43: 44, + k44: 45, + k45: 46, + k46: 47, + k47: 48, + k48: 49, + k49: 50, + k50: 51, + k51: 52, + k52: 53, + k53: 54, + k54: 55, + k55: 56, + k56: 57, + k57: 58, + k58: 59, + k59: 60, + k60: 61, + k61: 62, + k62: 63, + k63: 64, + k64: 65, + k65: 66, + k66: 67, + k67: 68, + k68: 69, + k69: 70, + k70: 71, + k71: 72, + k72: 73, + k73: 74, + k74: 75, + k75: 76, + k76: 77, + k77: 78, + k78: 79, + k79: 80, + k80: 81, + k81: 82, + k82: 83, + k83: 84, + k84: 85, + k85: 86, + k86: 87, + k87: 88, + k88: 89, + k89: 90, + k90: 91, + k91: 92, + k92: 93, + k93: 94, + k94: 95, + k95: 96, + k96: 97, + k97: 98, + k98: 99, + k99: 100 +}; diff --git a/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/options.json b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/options.json new file mode 100644 index 000000000000..dc8441ad032a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/fixtures/transform/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-mangle-object-values/test/index.js b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/index.js new file mode 100644 index 000000000000..23251129fae9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-mangle-object-values/test/index.js @@ -0,0 +1,3 @@ +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/define-template.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/define-template.js new file mode 100644 index 000000000000..f38132273411 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/define-template.js @@ -0,0 +1,60 @@ +// @ts-nocheck +/* eslint-disable no-var */ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +/* global __MODULE_NAME__ */ +/* global __HAS_EXPORTS__ */ +/* global __IMPORT_NAMES__ */ +/* global __SINGLE_IMPORT_NO_EXPORTS__ */ +/* global __ONLY_EXPORTS__ */ + +/** + * An async module loader similar to define() in AMD. + * Our implementation varies in that it can be compiled down to a minimal form + * based on the module's needs, rather than bundling a full implementation. + * @param {object} defineCallback + */ +(function defineish(defineCallback) { + // self.BENTO maps module names to callbacks to execute with their contents. + // interface ModuleCallbacks { + // [name: string]: ((Object) => void)[], + // } + var callbacks = (self.BENTO = self.BENTO || {}); + var exec = __HAS_EXPORTS__ + ? function (_exports) { + defineCallback.apply(null, arguments); + var name = __MODULE_NAME__; + var awaiting = (callbacks[name] = callbacks[name] || []); + while (awaiting.length) { + awaiting.pop()(_exports); + } + awaiting.push = function (callback) { + callback(_exports); + }; + } + : defineCallback; + // The most common cases are ONLY_EXPORTS and SINGLE_IMPORT_NO_EXPORTS. + // We provide them with single-purpose implementations whose output is + // significantly smaller than the worst case. + if (__ONLY_EXPORTS__) { + exec({}); + } else if (__SINGLE_IMPORT_NO_EXPORTS__) { + var name = __SINGLE_IMPORT_NO_EXPORTS__; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + // Fallback general purpose implementation. + Promise.all( + __IMPORT_NAMES__.map(function (name) { + // exports is identified as the number 0 + if (__HAS_EXPORTS__ && name === 0) { + return {}; + } + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + }) + ).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (__CALLBACK_ARGS__) {}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/index.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/index.js new file mode 100644 index 000000000000..0e6ecf379d37 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/index.js @@ -0,0 +1,125 @@ +/** + * @fileoverview + * Transforms ESM import statements into an async loader meant for `nomodule` + * builds. + */ + +const { + buildNamespaceInitStatements, + ensureStatementsHoisted, + hasExports, + isModule, + rewriteModuleStatementsAndPrepareHeader, +} = require('@babel/helper-module-transforms'); +const {readFileSync} = require('fs'); +const {join: pathJoin, posix, relative} = require('path'); + +let wrapperTemplate; + +module.exports = function (babel) { + const {template, types: t} = babel; + + const pathToModuleName = (filename) => + filename.replace(/^(\.\/)?dist\//, '').replace(/(\.max)?\.m?js$/, ''); + + const resolveModuleName = (filename, source) => + pathToModuleName(posix.join(posix.dirname(filename), source)); + + /** + * @param {object} replacements + * @return {babel.types.Statement} + */ + function buildWrapper(replacements) { + if (!wrapperTemplate) { + const templateSource = readFileSync( + pathJoin(__dirname, 'define-template.js'), + 'utf8' + ); + wrapperTemplate = template(templateSource, { + placeholderPattern: /^__[A-Z0-9_]+__$/, + }); + } + return wrapperTemplate(replacements); + } + + /** + * @param {babel.NodePath} path + * @param {babel.types.Statement} wrapper + */ + function injectWrapper(path, wrapper) { + const {body, directives} = path.node; + path.node.directives = []; + path.node.body = []; + const wrapperPath = path.pushContainer('body', wrapper)[0]; + const callback = wrapperPath + .get('expression.arguments') + // @ts-ignore + .filter((arg) => arg.isFunctionExpression())[0] + .get('body'); + callback.pushContainer('directives', directives); + callback.pushContainer('body', body); + } + + return { + name: 'nomodule-loader', + visitor: { + Program: { + enter(path, state) { + // We can stop since this should be the last transform step. + // See nomodule-loader-config.js + path.stop(); + + if (!isModule(path)) { + throw new Error(); + } + const loose = true; + const noInterop = true; + const {headers, meta} = rewriteModuleStatementsAndPrepareHeader( + path, + {loose, noInterop} + ); + + const filename = relative(process.cwd(), state.filename); + const importNames = []; + const callbackArgs = []; + const metaHasExports = hasExports(meta); + if (metaHasExports) { + // exports is identified as the number 0 + importNames.push(t.numericLiteral(0)); + callbackArgs.push(t.identifier(meta.exportName)); + } + for (const [source, metadata] of meta.source) { + importNames.push( + t.stringLiteral(resolveModuleName(filename, source)) + ); + callbackArgs.push(t.identifier(metadata.name)); + headers.push( + ...buildNamespaceInitStatements(meta, metadata, loose) + ); + } + if (importNames.length < 1) { + return; + } + ensureStatementsHoisted(headers); + path.unshiftContainer('body', headers); + injectWrapper( + path, + buildWrapper({ + __MODULE_NAME__: t.stringLiteral(pathToModuleName(filename)), + __HAS_EXPORTS__: t.booleanLiteral(metaHasExports), + __ONLY_EXPORTS__: t.booleanLiteral( + metaHasExports && importNames.length === 1 + ), + __IMPORT_NAMES__: t.arrayExpression(importNames), + __SINGLE_IMPORT_NO_EXPORTS__: + importNames.length === 1 && !metaHasExports + ? t.cloneNode(importNames[0]) + : t.nullLiteral(), + __CALLBACK_ARGS__: callbackArgs, + }) + ); + }, + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/input.mjs new file mode 100644 index 000000000000..9385f7349188 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/input.mjs @@ -0,0 +1 @@ +export default function fn() {} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/output.js new file mode 100644 index 000000000000..b93af0d76598 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/output.js @@ -0,0 +1,42 @@ +(function defineish(defineCallback) { + var callbacks = self.BENTO = self.BENTO || {}; + var exec = true ? function (_exports) { + defineCallback.apply(null, arguments); + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/input"; + var awaiting = callbacks[name] = callbacks[name] || []; + + while (awaiting.length) { + awaiting.pop()(_exports); + } + + awaiting.push = function (callback) { + callback(_exports); + }; + } : defineCallback; + + if (true) { + exec({}); + } else if (null) { + var name = null; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + Promise.all([0].map(function (name) { + if (true && name === 0) { + return {}; + } + + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + })).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (_exports) { + "use strict"; + + _exports.__esModule = true; + _exports.default = fn; + + function fn() {} +}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/input.mjs new file mode 100644 index 000000000000..2f81f2527127 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/input.mjs @@ -0,0 +1 @@ +export {x, y, z} from './abc'; diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/output.js new file mode 100644 index 000000000000..94dc3e894dfe --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/output.js @@ -0,0 +1,43 @@ +(function defineish(defineCallback) { + var callbacks = self.BENTO = self.BENTO || {}; + var exec = true ? function (_exports) { + defineCallback.apply(null, arguments); + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/input"; + var awaiting = callbacks[name] = callbacks[name] || []; + + while (awaiting.length) { + awaiting.pop()(_exports); + } + + awaiting.push = function (callback) { + callback(_exports); + }; + } : defineCallback; + + if (false) { + exec({}); + } else if (null) { + var name = null; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + Promise.all([0, "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/abc"].map(function (name) { + if (true && name === 0) { + return {}; + } + + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + })).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (_exports, _abc) { + "use strict"; + + _exports.__esModule = true; + _exports.z = _exports.y = _exports.x = void 0; + _exports.x = _abc.x; + _exports.y = _abc.y; + _exports.z = _abc.z; +}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/input.mjs new file mode 100644 index 000000000000..70e7fa2cda07 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/input.mjs @@ -0,0 +1,2 @@ +import x from 'x'; +console.log(x); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/output.js new file mode 100644 index 000000000000..dfbc4e1eb1ac --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/output.js @@ -0,0 +1,39 @@ +(function defineish(defineCallback) { + var callbacks = self.BENTO = self.BENTO || {}; + var exec = false ? function (_exports) { + defineCallback.apply(null, arguments); + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/input"; + var awaiting = callbacks[name] = callbacks[name] || []; + + while (awaiting.length) { + awaiting.pop()(_exports); + } + + awaiting.push = function (callback) { + callback(_exports); + }; + } : defineCallback; + + if (false) { + exec({}); + } else if ("build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/x") { + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/x"; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + Promise.all(["build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/import-default/x"].map(function (name) { + if (false && name === 0) { + return {}; + } + + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + })).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (_x) { + "use strict"; + + console.log(_x.default); +}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/input.mjs new file mode 100644 index 000000000000..76861af0119d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/input.mjs @@ -0,0 +1,3 @@ +import {foo, bar} from './foo'; + +export const baz = {foo, bar, baz: 'baz'}; diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/output.js new file mode 100644 index 000000000000..0a95557176a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/output.js @@ -0,0 +1,46 @@ +(function defineish(defineCallback) { + var callbacks = self.BENTO = self.BENTO || {}; + var exec = true ? function (_exports) { + defineCallback.apply(null, arguments); + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/input"; + var awaiting = callbacks[name] = callbacks[name] || []; + + while (awaiting.length) { + awaiting.pop()(_exports); + } + + awaiting.push = function (callback) { + callback(_exports); + }; + } : defineCallback; + + if (false) { + exec({}); + } else if (null) { + var name = null; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + Promise.all([0, "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/imports-and-exports/foo"].map(function (name) { + if (true && name === 0) { + return {}; + } + + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + })).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (_exports, _foo) { + "use strict"; + + _exports.__esModule = true; + _exports.baz = void 0; + const baz = { + foo: _foo.foo, + bar: _foo.bar, + baz: 'baz' + }; + _exports.baz = baz; +}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/input.mjs new file mode 100644 index 000000000000..2d847b16e7d5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/input.mjs @@ -0,0 +1 @@ +function x() {} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/output.js new file mode 100644 index 000000000000..2f817152daa8 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/no-transform/output.js @@ -0,0 +1,3 @@ +"use strict"; + +function x() {} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/input.mjs new file mode 100644 index 000000000000..eee8cd5a00a7 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/input.mjs @@ -0,0 +1,2 @@ +export const x = 'y'; +export function y() {} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/output.js new file mode 100644 index 000000000000..05c8140a1004 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/output.js @@ -0,0 +1,45 @@ +(function defineish(defineCallback) { + var callbacks = self.BENTO = self.BENTO || {}; + var exec = true ? function (_exports) { + defineCallback.apply(null, arguments); + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/only-exports/input"; + var awaiting = callbacks[name] = callbacks[name] || []; + + while (awaiting.length) { + awaiting.pop()(_exports); + } + + awaiting.push = function (callback) { + callback(_exports); + }; + } : defineCallback; + + if (true) { + exec({}); + } else if (null) { + var name = null; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + Promise.all([0].map(function (name) { + if (true && name === 0) { + return {}; + } + + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + })).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (_exports) { + "use strict"; + + _exports.__esModule = true; + _exports.y = y; + _exports.x = void 0; + const x = 'y'; + _exports.x = x; + + function y() {} +}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/input.mjs b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/input.mjs new file mode 100644 index 000000000000..be8d6e914b2c --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/input.mjs @@ -0,0 +1,3 @@ +import {foo, bar} from './foo'; + +console.log({foo, bar}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/options.json b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/options.json new file mode 100644 index 000000000000..3ebeb6374116 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/options.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "../../../.." + ] +} diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/output.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/output.js new file mode 100644 index 000000000000..33ec139133d7 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/output.js @@ -0,0 +1,42 @@ +(function defineish(defineCallback) { + var callbacks = self.BENTO = self.BENTO || {}; + var exec = false ? function (_exports) { + defineCallback.apply(null, arguments); + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/input"; + var awaiting = callbacks[name] = callbacks[name] || []; + + while (awaiting.length) { + awaiting.pop()(_exports); + } + + awaiting.push = function (callback) { + callback(_exports); + }; + } : defineCallback; + + if (false) { + exec({}); + } else if ("build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/foo") { + var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/foo"; + (callbacks[name] = callbacks[name] || []).push(exec); + } else { + Promise.all(["build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/single-import-name/foo"].map(function (name) { + if (false && name === 0) { + return {}; + } + + return new Promise(function (resolve) { + (callbacks[name] = callbacks[name] || []).push(resolve); + }); + })).then(function (modules) { + exec.apply(null, modules); + }); + } +})(function (_foo) { + "use strict"; + + console.log({ + foo: _foo.foo, + bar: _foo.bar + }); +}); diff --git a/build-system/babel-plugins/babel-plugin-nomodule-loader/test/index.js b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/index.js new file mode 100644 index 000000000000..23251129fae9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-nomodule-loader/test/index.js @@ -0,0 +1,3 @@ +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-react-style-props/index.js b/build-system/babel-plugins/babel-plugin-react-style-props/index.js new file mode 100644 index 000000000000..1c9a4cfaca85 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-react-style-props/index.js @@ -0,0 +1,40 @@ +/** + * @fileoverview + * Transforms Preact-style props ("class") into React-style ("className") + */ +const {ATTRIBUTES_PREACT_TO_REACT} = require('../../common/preact-prop-names'); + +const propNameFn = 'propName'; + +module.exports = function (babel) { + const {types: t} = babel; + + /** + * @param {string} name + * @return {?string} + */ + function getReactStyle(name) { + return ATTRIBUTES_PREACT_TO_REACT[name] ?? name; + } + + return { + name: 'react-style-props', + visitor: { + JSXAttribute(path) { + const reactStyle = getReactStyle(path.node.name.name); + path.node.name.name = reactStyle; + }, + CallExpression(path) { + if (!t.isIdentifier(path.node.callee, {name: propNameFn})) { + return; + } + const arg = path.get('arguments.0'); + if (!arg.isStringLiteral()) { + throw arg.buildCodeFrameError('Should be string literal'); + } + const reactStyle = getReactStyle(arg.node.value); + path.replaceWith(t.stringLiteral(reactStyle)); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-react-style-props/test/fixtures/transform/class/input.js b/build-system/babel-plugins/babel-plugin-react-style-props/test/fixtures/transform/class/input.js new file mode 100644 index 000000000000..976ffe9574a4 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-react-style-props/test/fixtures/transform/class/input.js @@ -0,0 +1 @@ +