diff --git a/.circleci/OWNERS b/.circleci/OWNERS new file mode 100644 index 0000000000000..002922bb9cc93 --- /dev/null +++ b/.circleci/OWNERS @@ -0,0 +1,10 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [{name: 'ampproject/wg-infra'}, {name: 'rsimha', notify: true}], + }, + ], +} diff --git a/.circleci/check_config.sh b/.circleci/check_config.sh new file mode 100755 index 0000000000000..45441bfccf399 --- /dev/null +++ b/.circleci/check_config.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# This script checks if a PR branch is using the most recent CircleCI config. +# Reference: https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables + +set -e +err=0 + +GREEN() { echo -e "\n\033[0;32m$1\033[0m"; } +RED() { echo -e "\n\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 + echo $(GREEN "Nothing to do because $CIRCLE_BRANCH is not a PR branch.") + exit 0 +fi + +# Check if the PR branch contains the most recent revision the CircleCI config. +CONFIG_REV=`git rev-list main -1 -- .circleci/config.yml` +(set -x && git merge-base --is-ancestor $CONFIG_REV $CIRCLE_SHA1) || err=$? + +if [[ "$err" -ne "0" ]]; then + echo "$(RED "ERROR:") $(CYAN $CIRCLE_BRANCH) is missing the latest CircleCI config revision $(CYAN $CONFIG_REV)." + echo -e "\n" + + echo $(YELLOW "This can be fixed in three ways:") + echo -e "\n" + + echo "1. Click the $(CYAN "\"Update branch\"") button on GitHub and follow instructions." + echo " ⤷ It can be found towards the bottom of the PR, after the list of checks." + echo -e "\n" + + echo "2. Pull the latest commits from $(CYAN "main") and re-push the PR branch." + echo " ⤷ Follow these steps:" + echo "" + echo " $(CYAN "git checkout main")" + echo " $(CYAN "git pull")" + echo " $(CYAN "git checkout ")" + echo " $(CYAN "git merge main")" + echo " $(CYAN "git push origin")" + echo -e "\n" + + echo "3. Rebase on $(CYAN "main") and re-push the PR branch." + echo " ⤷ Follow these steps:" + echo "" + echo " $(CYAN "git checkout main")" + echo " $(CYAN "git pull")" + echo " $(CYAN "git checkout ")" + echo " $(CYAN "git rebase main")" + echo " $(CYAN "git push origin --force")" + echo -e "\n" + exit 1 +fi + +echo $(GREEN "$CIRCLE_BRANCH is using the latest CircleCI config.") diff --git a/.circleci/compute_merge_commit.sh b/.circleci/compute_merge_commit.sh new file mode 100755 index 0000000000000..b1c143ab24135 --- /dev/null +++ b/.circleci/compute_merge_commit.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# This script establishes the merge commit at the start of a CircleCI build so +# all stages use the same commit. + +set -e +err=0 + +GREEN() { echo -e "\n\033[0;32m$1\033[0m"; } + +# Ensure the CircleCI workspace directory exists. +mkdir -p /tmp/workspace + +# Try to determine the PR number. +curl -sS https://raw.githubusercontent.com/ampproject/amphtml/main/.circleci/get_pr_number.sh | bash +if [[ -f "$BASH_ENV" ]]; then + source $BASH_ENV +fi + +# If PR_NUMBER doesn't exist, there is nothing more to do. +if [[ -z "$PR_NUMBER" ]]; then + exit 0 +fi + +# GitHub provides refs/pull//merge, an up-to-date merge branch for +# every PR branch that can be cleanly merged to the main branch. For more +# details, see: https://discuss.circleci.com/t/show-test-results-for-prospective-merge-of-a-github-pr/1662 +MERGE_BRANCH="refs/pull/$PR_NUMBER/merge" +echo $(GREEN "Computing merge SHA of $MERGE_BRANCH...") +CIRCLE_MERGE_SHA="$(git ls-remote https://github.com/ampproject/amphtml.git "$MERGE_BRANCH" | awk '{print $1}')" + +# Store the merge commit info in the CircleCI workspace for use by followup +# jobs. +echo "$CIRCLE_MERGE_SHA" > /tmp/workspace/.CIRCLECI_MERGE_COMMIT +echo $(GREEN "Stored merge SHA $CIRCLE_MERGE_SHA in /tmp/workspace/.CIRCLECI_MERGE_COMMIT.") +exit 0 diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..685f613d10ca5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,414 @@ +version: 2.1 + +orbs: + browser-tools: circleci/browser-tools@1.1.3 + +push_and_pr_builds: &push_and_pr_builds + filters: + branches: + ignore: + - nightly + +push_builds_only: &push_builds_only + filters: + branches: + only: + - main + - /^amp-release-.*$/ + +executors: + amphtml-docker-small-executor: + docker: + - image: cimg/base:2021.05 + resource_class: small + amphtml-medium-executor: + machine: + image: ubuntu-2004:202010-01 + resource_class: medium + amphtml-large-executor: + machine: + image: ubuntu-2004:202010-01 + resource_class: large + amphtml-xlarge-executor: + machine: + image: ubuntu-2004:202010-01 + resource_class: xlarge + +commands: + restore_workspace: + steps: + - attach_workspace: + at: /tmp + - run: + name: 'Rename Restored Workspace' + command: | + if [[ -d /tmp/workspace ]]; then + mv /tmp/workspace /tmp/restored-workspace + fi + maybe_gracefully_halt: + steps: + - run: + name: 'Maybe Gracefully Halt' + command: curl -sS https://raw.githubusercontent.com/ampproject/amphtml/main/.circleci/maybe_gracefully_halt.sh | bash + setup_vm: + steps: + - run: + name: 'Create Workspace' + command: mkdir -p /tmp/workspace + - checkout + - run: + name: 'Fetch Merge Commit' + command: ./.circleci/fetch_merge_commit.sh + - run: + name: 'Check Config' + command: ./.circleci/check_config.sh + - run: + name: 'Configure Hosts' + command: cat ./build-system/test-configs/hosts | sudo tee -a /etc/hosts + - run: + name: 'Install Dependencies' + command: ./.circleci/install_dependencies.sh + - run: + name: 'Restore Build Output' + command: ./.circleci/restore_build_output.sh + teardown_vm: + steps: + - persist_to_workspace: + root: /tmp + paths: + - workspace + install_chrome: + steps: + - browser-tools/install-chrome: + replace-existing: true + fail_fast: + steps: + - run: + name: 'Fail Fast' + when: on_fail + command: ./.circleci/fail_fast.sh + store_test_output: + steps: + - store_artifacts: + path: result-reports + - store_test_results: + path: result-reports + +jobs: + 'Compute Merge Commit': + executor: + name: amphtml-docker-small-executor + steps: + - run: + name: 'Compute Merge Commit' + command: curl -sS https://raw.githubusercontent.com/ampproject/amphtml/main/.circleci/compute_merge_commit.sh | bash + - teardown_vm + 'Checks': + executor: + name: amphtml-medium-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Checks' + command: node build-system/pr-check/checks.js + - fail_fast + - teardown_vm + 'Unminified Build': + executor: + name: amphtml-xlarge-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Unminified Build' + command: node build-system/pr-check/unminified-build.js + - fail_fast + - teardown_vm + 'Nomodule Build': + executor: + name: amphtml-xlarge-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Create Artifacts Directory' + command: mkdir -p /tmp/artifacts + - run: + name: 'Nomodule Build' + command: node build-system/pr-check/nomodule-build.js + - store_artifacts: + path: /tmp/artifacts/amp_nomodule_build.tar.gz + - fail_fast + - teardown_vm + 'Module Build': + executor: + name: amphtml-xlarge-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Module Build' + command: node build-system/pr-check/module-build.js + - fail_fast + - teardown_vm + 'Bundle Size': + executor: + name: amphtml-xlarge-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Bundle Size' + command: node build-system/pr-check/bundle-size.js + - fail_fast + - teardown_vm + 'Validator Tests': + executor: + name: amphtml-xlarge-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Install Validator Dependencies' + command: ./.circleci/install_validator_dependencies.sh + - run: + name: 'Validator Tests' + command: node build-system/pr-check/validator-tests.js + - store_test_output + - fail_fast + - teardown_vm + 'Visual Diff Tests': + executor: + name: amphtml-large-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Visual Diff Tests' + command: node build-system/pr-check/visual-diff-tests.js + - store_test_output + - fail_fast + - teardown_vm + 'Unit Tests': + executor: + name: amphtml-large-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Unit Tests' + command: node build-system/pr-check/unit-tests.js + - store_test_output + - fail_fast + - teardown_vm + 'Unminified Tests': + executor: + name: amphtml-large-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Unminified Tests' + command: node build-system/pr-check/unminified-tests.js + - store_test_output + - fail_fast + - teardown_vm + 'Nomodule Tests': + executor: + name: amphtml-large-executor + parameters: + config: + description: 'Which config file to use' + type: enum + enum: ['prod', 'canary'] + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Nomodule Tests (<< parameters.config >>)' + command: node build-system/pr-check/nomodule-tests.js --config=<< parameters.config >> + - store_test_output + - fail_fast + - teardown_vm + 'Module Tests': + executor: + name: amphtml-large-executor + parameters: + config: + description: 'Which config file to use' + type: enum + enum: ['prod', 'canary'] + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Module Tests (<< parameters.config >>)' + command: node build-system/pr-check/module-tests.js --config=<< parameters.config >> + - store_test_output + - fail_fast + - teardown_vm + 'End-to-End Tests': + executor: + name: amphtml-large-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'End-to-End Tests' + command: node build-system/pr-check/e2e-tests.js + - store_test_output + - fail_fast + - teardown_vm + 'Performance Tests': + executor: + name: amphtml-xlarge-executor + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Performance Tests' + command: node build-system/pr-check/performance-tests.js + - store_test_output + - fail_fast + - teardown_vm + 'Experiment Build': + executor: + name: amphtml-xlarge-executor + parameters: + exp: + description: 'Which of the three (A/B/C) experiments to use' + type: enum + enum: ['A', 'B', 'C'] + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - run: + name: 'Experiment << parameters.exp >> Build' + command: node build-system/pr-check/experiment-build.js --experiment=experiment<< parameters.exp >> + - fail_fast + - teardown_vm + 'Experiment Tests': + executor: + name: amphtml-large-executor + parameters: + exp: + description: 'Which of the three (A/B/C) experiments to use' + type: enum + enum: ['A', 'B', 'C'] + steps: + - restore_workspace + - maybe_gracefully_halt + - setup_vm + - install_chrome + - run: + name: 'Experiment << parameters.exp >> Tests' + command: node build-system/pr-check/experiment-tests.js --experiment=experiment<< parameters.exp >> + - store_test_output + - fail_fast + - teardown_vm + +workflows: + 'CircleCI': + jobs: + - 'Compute Merge Commit': + <<: *push_and_pr_builds + - 'Checks': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Unminified Build': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Nomodule Build': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Module Build': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Bundle Size': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Validator Tests': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Visual Diff Tests': + <<: *push_and_pr_builds + requires: + - 'Nomodule Build' + - 'Unit Tests': + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Unminified Tests': + <<: *push_and_pr_builds + requires: + - 'Unminified Build' + - 'Nomodule Tests': + name: 'Nomodule Tests (<< matrix.config >>)' + matrix: + parameters: + config: ['prod', 'canary'] + <<: *push_and_pr_builds + requires: + - 'Nomodule Build' + - 'Module Tests': + name: 'Module Tests (<< matrix.config >>)' + matrix: + parameters: + config: ['prod', 'canary'] + <<: *push_and_pr_builds + requires: + - 'Nomodule Build' + - 'Module Build' + - 'End-to-End Tests': + <<: *push_and_pr_builds + requires: + - 'Nomodule Build' + - 'Experiment Build': + name: 'Experiment << matrix.exp >> Build' + matrix: + parameters: + exp: ['A', 'B', 'C'] + <<: *push_and_pr_builds + requires: + - 'Compute Merge Commit' + - 'Experiment Tests': + name: 'Experiment << matrix.exp >> Tests' + matrix: + parameters: + exp: ['A', 'B', 'C'] + <<: *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' diff --git a/.circleci/fail_fast.sh b/.circleci/fail_fast.sh new file mode 100755 index 0000000000000..710e7e009dca8 --- /dev/null +++ b/.circleci/fail_fast.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# This script early-exits CircleCI PR builds when one of its jobs fails. +# Reference: https://support.circleci.com/hc/en-us/articles/360052058811-Exit-build-early-if-any-test-fails +# In case of the `main` or a release branch, it emails the build/release-on-duty +# team using Twilio SendGrid: https://sendgrid.com/ + +set -e + +RED() { echo -e "\n\033[0;31m$1\033[0m"; } +YELLOW() { echo -e "\n\033[0;33m$1\033[0m"; } + +send_email() { + curl --request POST \ + --url https://api.sendgrid.com/v3/mail/send \ + --header "Authorization: Bearer ${SENDGRID_API_KEY}" \ + --header "Content-Type: application/json" \ + --data '{ + "from": { + "email": "bot+noreply@amp.dev" + }, + "template_id": "d-5a6b574506534ab3aad1da13a78cdeb4", + "personalizations": [ + { + "to": [ + { + "email": "'"${1}"'", + "name": "'"${2}"'" + } + ], + "dynamic_template_data": { + "branch": "'"${CIRCLE_BRANCH}"'", + "name": "'"${2}"'", + "build_url": "'"${CIRCLE_BUILD_URL}"'" + } + } + ] + }' +} + +# For push builds, continue in spite of failures so that other jobs like +# bundle-size and visual-diff can establish their baselines for this commit. +# Without this, our custom bots will not be able to function correctly. +if [[ "$CIRCLE_BRANCH" == "main" ]]; then + echo $(YELLOW "Not canceling build in spite of failures because ${CIRCLE_BRANCH} is not a PR branch.") + echo $(YELLOW "This main branch build failed, notifying @ampproject/build-on-duty.") + send_email "amp-build-on-duty@grotations.appspotmail.com" "AMP Build On-Duty" + exit 0 +elif [[ "$CIRCLE_BRANCH" =~ ^amp-release-* ]]; then + echo $(YELLOW "Not canceling build in spite of failures because ${CIRCLE_BRANCH} is not a PR branch.") + echo $(YELLOW "This release branch build failed, notifying @ampproject/release-on-duty.") + send_email "amp-release-on-duty@grotations.appspotmail.com" "AMP Release On-Duty" + exit 0 +fi + +# For PR builds, cancel when the first job fails. +echo $(RED "Canceling PR build because a job failed.") +curl -X POST \ +--header "Content-Type: application/json" \ +"https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel?circle-token=${CIRCLE_TOKEN}" diff --git a/.circleci/fetch_merge_commit.sh b/.circleci/fetch_merge_commit.sh new file mode 100755 index 0000000000000..7921eab05b388 --- /dev/null +++ b/.circleci/fetch_merge_commit.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# This script fetches the merge commit of a PR branch with the main branch to +# make sure PRs are tested against all the latest changes on CircleCI. + +set -e +err=0 + +GREEN() { echo -e "\n\033[0;32m$1\033[0m"; } +RED() { echo -e "\n\033[0;31m$1\033[0m"; } + +# Try to determine the PR number. +./.circleci/get_pr_number.sh +if [[ -f "$BASH_ENV" ]]; then + source $BASH_ENV +fi + +# If PR_NUMBER doesn't exist, there is nothing more to do. +if [[ -z "$PR_NUMBER" ]]; then + exit 0 +fi + +# If PR_NUMBER exists, but the merge commit file doesn't exist, the PR was +# created after the first stage of CI was run. There is nothing more to do. +if [[ ! -f /tmp/restored-workspace/.CIRCLECI_MERGE_COMMIT ]]; then + exit 0 +fi + +# Extract the merge commit for this workflow and make it visible to other steps. +CIRCLECI_MERGE_COMMIT="$(cat /tmp/restored-workspace/.CIRCLECI_MERGE_COMMIT)" +echo "export CIRCLECI_MERGE_COMMIT=$CIRCLECI_MERGE_COMMIT" >> $BASH_ENV + +# Fetch the merge commit. This ensures that all CI stages use the same commit. +echo $(GREEN "Fetching merge commit $CIRCLECI_MERGE_COMMIT...") +(set -x && git pull --ff-only origin "$CIRCLECI_MERGE_COMMIT") || err=$? + +# If a clean merge is not possible, do not proceed with the build. GitHub's UI +# will show an error indicating there was a merge conflict. +if [[ "$err" -ne "0" ]]; then + echo $(RED "Detected a merge conflict between $CIRCLE_BRANCH and the main branch.") + echo $(RED "Please rebase your PR branch.") + exit $err +fi + +echo $(GREEN "Successfully fetched merge commit of $CIRCLE_BRANCH with the main branch.") diff --git a/.circleci/get_pr_number.sh b/.circleci/get_pr_number.sh new file mode 100755 index 0000000000000..31cd5ec8de408 --- /dev/null +++ b/.circleci/get_pr_number.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# This script extracts the PR number (if there is one) for a CircleCI build. +# Reference: https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables + +set -e +err=0 + +GREEN() { echo -e "\n\033[0;32m$1\033[0m"; } +YELLOW() { echo -e "\n\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 + 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 + echo $(YELLOW "WARNING: Build is incorrectly linked to a PR outside ampproject/amphtml:") + echo $(YELLOW "$CIRCLE_PULL_REQUEST") + fi + exit 0 +fi + +# CIRCLE_PR_NUMBER is present for PRs originating from forks, but absent for PRs +# originating from a branch on the main repo. In such cases, extract the PR +# number from CIRCLE_PULL_REQUEST. +if [[ "$CIRCLE_PR_NUMBER" ]]; then + PR_NUMBER=$CIRCLE_PR_NUMBER +else + PR_NUMBER=${CIRCLE_PULL_REQUEST#"https://github.com/ampproject/amphtml/pull/"} +fi + +# If neither CIRCLE_PR_NUMBER nor CIRCLE_PULL_REQUEST are available, it's +# possible this is a PR branch that is yet to be associated with a PR. Exit +# early becaue there is no merge commit to fetch. +if [[ -z "$PR_NUMBER" ]]; then + echo $(GREEN "Nothing to do because $CIRCLE_BRANCH is not yet linked to a PR.") + exit 0 +fi + +echo "export PR_NUMBER=$PR_NUMBER" >> $BASH_ENV +echo $(GREEN "This is a PR build for #$PR_NUMBER.") diff --git a/.circleci/install_dependencies.sh b/.circleci/install_dependencies.sh new file mode 100755 index 0000000000000..5f46966fce2ab --- /dev/null +++ b/.circleci/install_dependencies.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# Script used by AMP's CI builds to install project dependencies on CircleCI. + +set -e + +GREEN() { echo -e "\033[0;32m$1\033[0m"; } + +echo $(GREEN "Installing NVM...") +curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash + +echo $(GREEN "Setting up NVM environment...") +export NVM_DIR="/opt/circleci/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + +echo $(GREEN "Installing Node LTS...") +nvm install 'lts/*' + +echo $(GREEN "Installing dependencies...") +npm ci + +echo $(GREEN "Setting up environment...") +NPM_BIN_DIR="`npm config get prefix`/bin" +(set -x && echo "export PATH=$NPM_BIN_DIR:$PATH" >> $BASH_ENV) + +echo $(GREEN "Successfully installed all project dependencies.") diff --git a/.circleci/install_validator_dependencies.sh b/.circleci/install_validator_dependencies.sh new file mode 100755 index 0000000000000..360c573f1d9d3 --- /dev/null +++ b/.circleci/install_validator_dependencies.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# Script used by AMP's CI builds to install AMP Validator dependencies on +# CircleCI. + +set -e + +GREEN() { echo -e "\033[0;32m$1\033[0m"; } + +echo $(GREEN "Adding Bazel repo...") +curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg +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 "Installing Bazel...") +sudo apt update +sudo apt install bazel + +echo $(GREEN "Installing Clang...") +sudo apt install clang + +echo $(GREEN "Installing Protobuf...") +pip3 install --user protobuf diff --git a/.circleci/maybe_gracefully_halt.sh b/.circleci/maybe_gracefully_halt.sh new file mode 100755 index 0000000000000..cb674c5a89f1b --- /dev/null +++ b/.circleci/maybe_gracefully_halt.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# Script used by AMP's CI builds to gracefully halt unnecessary jobs, before +# executing time-consuming steps such as checking out the code or installing +# dependencies. + +set -e + +GREEN() { echo -e "\n\033[0;32m$1\033[0m"; } +YELLOW() { echo -e "\n\033[0;33m$1\033[0m"; } + +if ls /tmp/restored-workspace/.CI_GRACEFULLY_HALT_* 1>/dev/null 2>&1; then + echo $(GREEN "Gracefully halting this job.") + circleci-agent step halt + exit 0 +fi + +if [[ $CIRCLE_JOB == Experiment* ]]; then + # Extract the experiment name from the job name in `config.yml`. + EXP=$(echo $CIRCLE_JOB | awk '{print $2}') + + # 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)" + else + COMMIT_SHA="${CIRCLE_SHA1}" + 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}") + 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 $(GREEN "Gracefully halting this job") + circleci-agent step halt + exit 0 + fi + + # Do not proceed if the experiment is expired (config date is in the past). + 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 $(GREEN "Gracefully halting this job") + circleci-agent step halt + exit 0 + fi +fi diff --git a/.circleci/restore_build_output.sh b/.circleci/restore_build_output.sh new file mode 100755 index 0000000000000..c37d065576bcd --- /dev/null +++ b/.circleci/restore_build_output.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# Script used by AMP's CI builds to restore any build outputs from previous jobs +# in the workflow. + +set -e + +GREEN() { echo -e "\033[0;32m$1\033[0m"; } +YELLOW() { echo -e "\033[0;33m$1\033[0m"; } +CYAN() { echo -e "\033[0;36m$1\033[0m"; } + +MERGABLE_OUTPUT_DIRS="build dist dist.3p dist.tools" + +# TODO(danielrozenberg): remove conditional after #33708 is merged. +WORKSPACE_DIR=$(if [[ -d /tmp/restored-workspace ]]; then echo "/tmp/restored-workspace"; else echo "/tmp/workspace"; fi) + +if [[ -d "${WORKSPACE_DIR}/builds" ]]; then + echo $(GREEN "Restoring build output from workspace") + for CONTAINER_DIR in ${WORKSPACE_DIR}/builds/*; do + for OUTPUT_DIR in ${MERGABLE_OUTPUT_DIRS}; do + RESTORED_DIR="${CONTAINER_DIR}/${OUTPUT_DIR}" + if [[ -d "${RESTORED_DIR}" ]]; then + echo "*" $(GREEN "Merging") $(CYAN "${RESTORED_DIR}") $(GREEN "into") $(CYAN "./${OUTPUT_DIR}") + rsync -a "${RESTORED_DIR}/" "./${OUTPUT_DIR}" + fi + done + done +else + echo $(YELLOW "Workspace does not contain any build outputs to restore") +fi diff --git a/.codecov.yml b/.codecov.yml index 95023199dd738..24c5fc928f729 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,8 +2,6 @@ # See https://docs.codecov.io/docs/codecovyml-reference codecov: bot: 'amp-coverage-bot' - ci: - - 'travis.org' max_report_age: 24 require_ci_to_pass: no notify: @@ -13,20 +11,14 @@ codecov: # See https://docs.codecov.io/docs/pull-request-comments comment: false -# Separate PR statuses for project-level and patch-level coverage +# PR status for patch-level coverage # See https://docs.codecov.io/docs/commit-status coverage: precision: 2 round: down range: '75...100' status: - project: - default: - base: auto - if_not_found: success - only_pulls: true - target: 75% - threshold: 1% + project: off patch: default: base: auto diff --git a/.eslintignore b/.eslintignore index 905a1ff9ff317..2b32663eb6a7d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,19 +1,39 @@ +# Local cache directories +# Keep this list in sync with .gitignore, .prettierignore, and build-system/tasks/clean.js +.babel-cache +.css-cache +.pre-closure-cache + # Output directories -node_modules -build/** -dist/** -dist.3p/** -dist.tools/** -firebase/** -out/** -test/coverage/** +# Keep this list in sync with .gitignore, .prettierignore, and build-system/tasks/clean.js +.amp-dep-check +build +build-system/dist +build-system/server/new-server/transforms/dist +build-system/tasks/performance/cache +build-system/tasks/performance/results.json +build-system/global-configs/custom-config.json +dist +dist.3p +dist.tools +export +examples/storybook +extensions/**/dist +/release +result-reports +src/purifier/dist +test/coverage +test/coverage-e2e +validator/**/dist +validator/export -# Code directories -build-system/tasks/visual-diff/snippets/*.js +# Files and directories explicitly ignored by eslint build-system/babel-plugins/**/fixtures/**/*.js build-system/babel-plugins/**/fixtures/**/*.mjs -build-system/server/app-index/test/*.js -examples/** +build-system/tasks/make-extension/template/**/* +build-system/tasks/visual-diff/snippets/*.js +examples/amp-script/todomvc.ssr.js +examples/amp-script/vue-todomvc.js extensions/amp-a4a/0.1/test/testdata/** src/purifier/noop.js testing/local-amp-chrome-extension/** diff --git a/.eslintrc.js b/.eslintrc.js index 1e928f4b5d950..02b361af84480 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,6 +15,10 @@ */ const fs = require('fs'); +const { + forbiddenTermsGlobal, + forbiddenTermsSrcInclusive, +} = require('./build-system/test-configs/forbidden-terms'); /** * Dynamically extracts experiment globals from the config file. @@ -34,16 +38,18 @@ function getExperimentGlobals() { module.exports = { 'root': true, - 'parser': 'babel-eslint', + 'parser': '@babel/eslint-parser', 'plugins': [ 'chai-expect', 'google-camelcase', + 'import', 'jsdoc', 'local', 'notice', 'prettier', 'react', 'react-hooks', + 'sort-destructure-keys', 'sort-imports-es6-autofix', 'sort-requires', ], @@ -59,6 +65,7 @@ module.exports = { 'globals': { ...getExperimentGlobals(), 'IS_ESM': 'readonly', + 'IS_SXG': 'readonly', 'AMP': 'readonly', 'context': 'readonly', 'global': 'readonly', @@ -79,6 +86,7 @@ module.exports = { 'pragma': 'Preact', }, }, + 'reportUnusedDisableDirectives': true, 'rules': { 'chai-expect/missing-assertion': 2, 'chai-expect/no-inner-compare': 2, @@ -96,7 +104,6 @@ module.exports = { 'export', 'final', 'nocollapse', - 'noinline', 'package', 'record', 'restricted', @@ -127,6 +134,7 @@ module.exports = { 'local/html-template': 2, 'local/is-experiment-on': 2, 'local/json-configuration': 2, + 'local/jss-animation-name': 2, 'local/no-array-destructuring': 2, 'local/no-arrow-on-register-functions': 2, 'local/no-bigint': 2, @@ -136,7 +144,11 @@ module.exports = { 'local/no-dynamic-import': 2, 'local/no-es2015-number-props': 2, 'local/no-export-side-effect': 2, - 'local/no-for-of-statement': 2, + 'local/no-forbidden-terms': [ + 2, + forbiddenTermsGlobal, + forbiddenTermsSrcInclusive, + ], 'local/no-function-async': 2, 'local/no-function-generator': 2, 'local/no-global': 0, @@ -144,17 +156,19 @@ module.exports = { 'local/no-import': 2, 'local/no-import-meta': 2, 'local/no-import-rename': 2, - 'local/no-is-amp-alt': 2, + 'local/no-invalid-this': 2, 'local/no-log-array': 2, 'local/no-mixed-interpolation': 2, 'local/no-mixed-operators': 2, 'local/no-module-exports': 2, 'local/no-rest': 2, 'local/no-spread': 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, 'local/no-unload-listener': 2, + 'local/objstr-literal': 2, 'local/preact': 2, 'local/prefer-deferred-promise': 0, 'local/prefer-destructuring': 2, @@ -185,7 +199,7 @@ module.exports = { 'no-lone-blocks': 2, 'no-native-reassign': 2, 'no-redeclare': 2, - 'no-restricted-globals': [2, 'error', 'event'], + 'no-restricted-globals': [2, 'error', 'event', 'Animation'], 'no-script-url': 2, 'no-self-compare': 2, 'no-sequences': 2, @@ -202,13 +216,6 @@ module.exports = { 'no-useless-concat': 2, 'no-undef': 2, 'no-var': 2, - 'no-warning-comments': [ - 2, - { - 'terms': ['do not submit'], - 'location': 'anywhere', - }, - ], 'notice/notice': [ 2, { @@ -245,6 +252,7 @@ module.exports = { }, }, ], + 'sort-destructure-keys/sort-destructure-keys': 2, 'sort-imports-es6-autofix/sort-imports-es6': [ 2, { @@ -263,6 +271,7 @@ module.exports = { 'extensions/**/test-e2e/*.js', 'ads/**/test/**/*.js', 'testing/**/*.js', + 'build-system/**/test/*.js', ], 'rules': { 'require-jsdoc': 0, @@ -272,6 +281,7 @@ module.exports = { 'local/no-function-async': 0, 'local/no-function-generator': 0, 'local/no-import-meta': 0, + 'local/no-invalid-this': 0, 'jsdoc/check-param-names': 0, 'jsdoc/check-tag-names': 0, 'jsdoc/check-types': 0, @@ -300,18 +310,37 @@ module.exports = { }, }, { - 'files': ['babel.config.js', '**/.eslintrc.js'], + 'files': ['**/test-*', '**/*_test.js', '**/testing/**'], + 'rules': { + 'local/no-forbidden-terms': [2, forbiddenTermsGlobal], + }, + }, + { + 'files': ['**/storybook/*.js'], + 'rules': { + 'require-jsdoc': 0, + 'local/no-forbidden-terms': [2, forbiddenTermsGlobal], + }, + }, + { + 'files': [ + '**/.eslintrc.js', + 'amp.js', + 'babel.config.js', + 'package-scripts.js', + ], 'globals': { 'module': false, 'process': false, 'require': false, }, 'rules': { + 'local/no-forbidden-terms': 0, 'local/no-module-exports': 0, }, }, { - 'files': ['**/*.extern.js'], + 'files': ['**/*.extern.js', '**/*.type.js'], 'rules': { 'no-var': 0, 'no-undef': 0, diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000..eda3c8538421c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Force line endings to LF. +# Specially useful for contributors on Windows machines. +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index a8e26832e299e..0000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,32 +0,0 @@ -**Please only file bugs/feature requests for AMP here.** - -- If you have questions about how to use AMP or other general questions about AMP please ask them on Stack Overflow under the AMP HTML tag instead of filing an issue here: http://stackoverflow.com/questions/tagged/amp-html -- If you have questions/issues related to Google Search please ask them in Google's AMP forum instead of filing an issue here: https://goo.gl/utQ1KZ - -If you have a bug or feature request for AMP please fill in the following template. Delete everything except the headers (including this text). - -## What's the issue? - -Briefly describe the bug/feature request. - -## How do we reproduce the issue? - -If this is a bug please provide a public URL and ideally a reduced test case (e.g. on jsbin.com, codepen.io, or glitch.com) that exhibits only your issue and nothing else. Then provide step-by-step instructions for reproducing the issue: - -1. Step 1 to reproduce -2. Step 2 to reproduce -3. … - -If this is a feature request you can use this section to point to a prototype/mockup that will help us understand the request. - -### Tips on Rendering Bugs - -If you're reporting a bug that's the result of rendering data from an endpoint (e.g. with `` or ``), it's helpful to include sample JSON data from your endpoint. [JSONPlaceholder](https://jsonplaceholder.typicode.com/) is great for providing publicly accessible sample endpoint as well as dummy json endpoints. - -## What browsers are affected? - -All browsers? Some specific browser? What device type? - -## Which AMP version is affected? - -Is this a new issue? Or was it always broken? Paste your AMP version. You can find it in the browser dev tools. diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 4b390fb2ea0af..0000000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug report -about: Used to report bugs in AMP. -title: '' -labels: 'Type: Bug' -assignees: '' - ---- - -**Please only file reports about bugs in AMP here.** - -- If you have questions about how to use AMP or other general questions about AMP please ask them on Stack Overflow under the AMP HTML tag instead of filing an issue here: http://stackoverflow.com/questions/tagged/amp-html -- If you have questions/issues related to Google Search please ask them in Google's AMP forum instead of filing an issue here: https://goo.gl/utQ1KZ - -If you have a bug for AMP please fill in the following template. Delete everything except the headers (including this text). - -## What's the issue? - -Briefly describe the bug. - -## How do we reproduce the issue? - -Please provide a public URL and ideally a reduced test case (e.g. on jsbin.com) that exhibits only your issue and nothing else. Provide step-by-step instructions for reproducing the issue: - -1. Step 1 to reproduce -2. Step 2 to reproduce -3. … - -## What browsers are affected? - -All browsers? Some specific browser? What device type? - -## Which AMP version is affected? - -Is this a new issue? Or was it always broken? Paste the version of AMP where you saw this issue. (You can find the version printed in your browser's console.) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000000..0501127e131ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,67 @@ +--- +name: Bug Report +description: Report a bug in AMP. +labels: 'Type: Bug' +body: + - type: markdown + id: header + attributes: + value: | + Thanks for filling out this bug report. + - 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: 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/cherry-pick-request.yml b/.github/ISSUE_TEMPLATE/cherry-pick-request.yml new file mode 100644 index 0000000000000..ba71d5a81911e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/cherry-pick-request.yml @@ -0,0 +1,134 @@ +--- +name: Cherry-Pick Request +description: Request a cherry-pick to an AMP release. +labels: + [ + 'Type: Release', + 'Cherry-pick: Beta', + 'Cherry-pick: Experimental', + 'Cherry-pick: LTS', + 'Cherry-pick: Stable', + ] +title: "\U0001F338 Cherry-pick request for #ISSUE into #RELEASE" +body: + - type: markdown + id: header + attributes: + value: | + Thanks for filling out this cherry-pick request. + - See AMP's [release schedule](https://github.com/ampproject/amphtml/blob/main/docs/release-schedule.md) to learn about how releases work. + - See AMP's [release calendar](https://amp-release-calendar.appspot.com) for information about past releases (e.g. versions, dates, submission times, notes). + - See AMP's [code contribution docs](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md#Cherry-picks) for an overview of the cherry-pick process. + - type: input + id: issue + attributes: + label: Issue (P0 Bug) + description: The P0 bug that necessitates this cherry-pick. Remember to update the issue title with this info. + placeholder: 'e.g. #11111' + validations: + required: true + - type: input + id: pull_request + attributes: + label: Pull Request(s) + description: The PR(s) that fix the bug. Make sure they are merged and have passing [CI builds](https://app.circleci.com/pipelines/github/ampproject/amphtml?branch=main). + placeholder: 'e.g. #22222, #33333' + validations: + required: true + - type: input + id: release_tracker + 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' + validations: + required: true + - type: dropdown + id: channels + attributes: + label: Channels + description: The [release channels](https://github.com/ampproject/amphtml/blob/main/docs/release-schedule.md#release-channels) to which the cherry-pick will be applied. Remember to update the issue labels with this info. + multiple: true + options: + - Beta / Experimental + - Stable + - LTS + validations: + required: true + - type: textarea + id: justification + attributes: + label: Justification + description: Why you believe this issue meets the [cherry-pick criteria](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md#Cherry-picks). + placeholder: Provide a specific rationale for why this fix cannot wait until the next Stable release. + validations: + required: true + - type: textarea + id: verification_steps + attributes: + label: Verification Steps + description: How the fix can be verified once the cherry-pick has been performed. + placeholder: Provide detailed steps to manually verify the changes in this cherry-pick. They will be run by a developer or a QA expert to ensure that the bug was indeed fixed. + validations: + required: true + - type: markdown + id: mini_postmortem + attributes: + value: | + ## Mini Postmortem + For cherry-picks to Stable or LTS, the following sections serve as a postmortem, and are meant to document things like the bug summary, its root causes, user impact, and action items. Fill in these sections to the best of your knowledge so that similar issues can be prevented in future. For cherry-picks to only Beta / Experimental, these sections can be ignored. + - type: textarea + id: summary + attributes: + label: Summary + description: A summary of the problem and its root cause(s). + placeholder: | + For Stable or LTS cherry-picks, provide answers to questions like: + - What is the bug being fixed? + - What are its root cause(s)? + - Could it have been detected by error reports, error / performance graphs, or CI checks? + If details are as yet unknown, put down "TODO" in this section and remember to fill it in later. + - type: textarea + id: impact + attributes: + label: Impact + description: How users were impacted. + placeholder: | + For Stable or LTS cherry-picks, provide answers to questions like: + - Which users were affected? E.g. Users of Firefox version XXX. + - Roughly how many users? E.g. O(YYY) users. + - How were they affected? E.g. ZZZ feature did not work. + If details are as yet unknown, put down "TODO" in this section and remember to fill it in later. + - type: textarea + id: action_items + attributes: + label: Action Items + description: What can be done to prevent this in future. + placeholder: | + For Stable or LTS cherry-picks, provide answers to questions like: + - How can this class of bugs be prevented in the future? + - How do we detect them sooner and mitigate their impact? + - How could we make the investigation of these issues easier? + If details are as yet unknown, put down "TODO" in this section and remember to fill it in later. + - type: textarea + id: cherry_pick_progress + attributes: + label: Cherry-Pick Progress + description: Progress details for this cherry-pick. + value: | + + + To be updated by @ampproject/release-on-duty as each stage is completed. + - [x] Cherry-pick request created + - [ ] Cherry-pick request approved + - [ ] New version released to Beta / Experimental channels + - [ ] New version released to Stable channel + - [ ] New version released to LTS channel + - [ ] Release tracker updated + - [ ] Cherry-pick completed + - type: textarea + id: notifications + attributes: + label: Notifications + description: Add working groups or individuals you want to notify about this cherry-pick. + value: /cc @ampproject/release-on-duty @ampproject/wg-approvers @ampproject/cherry-pick-approvers diff --git a/.github/ISSUE_TEMPLATE/cherry_pick_template.md b/.github/ISSUE_TEMPLATE/cherry_pick_template.md deleted file mode 100644 index 75e9f7b1d4cd6..0000000000000 --- a/.github/ISSUE_TEMPLATE/cherry_pick_template.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -name: Cherry-pick request -about: Used to request a cherry-pick. See go.amp.dev/cherry-picks -title: "\U0001F338 Cherry-pick request for # into # (Pending)" -labels: - 'Cherry-pick: Beta, Cherry-pick: Experimental, Cherry-pick: LTS, Cherry-pick: Stable, Type: - Release' -assignees: '' ---- - - - -# Cherry-pick request - - - -| Issue | PR | Beta / Experimental? | Stable? | LTS? | [Release issue](https://github.com/ampproject/amphtml/labels/Type%3A%20Release) | -| :---------------: | :------------: | :------------------: | :----------: | :----------: | ------------------------------------------------------------------------------- | -| #<_ISSUE_NUMBER_> | #<_PR_NUMBER_> | **** | **** | **** | #<_RELEASE_ISSUE_> | - -## Why does this issue meet the [cherry-pick criteria](https://github.com/ampproject/amphtml/blob/master/contributing/contributing-code.md#Cherry-picks)? - - - -<_YOUR_REASONS_> - - - -## Why is a Beta / Experimental cherry-pick not needed? - -<_YOUR_REASONS_> - - - -## Why is an LTS cherry-pick needed? - -<_YOUR_REASONS_> - - - -# Mini-postmortem - -> **TODO:** This postmortem will be written after the cherry-pick deployment and before this issue is closed. Delete this TODO when the postmortem is ready. - -## Summary - -<_1-2 sentences summarizing the problem and root causes._> - -## Impact - -- <_Which users were affected? Roughly how many?_> -- <_How were users affected? E.g. partial or complete loss of functionality?_> - -## Action Items - - - -- #<_ISSUE_NUMBER_>: <_Add unit/integration/end-to-end test_> -- #<_ISSUE_NUMBER_>: <_Add monitoring for edge case via error logging_> -- #<_ISSUE_NUMBER_>: <_Refactor an easily misused API_> - ---- - -/cc @ampproject/release-on-duty @ampproject/wg-approvers @ampproject/cherry-pick-approvers diff --git a/.github/ISSUE_TEMPLATE/error-report.md b/.github/ISSUE_TEMPLATE/error-report.md deleted file mode 100644 index be0b4c5e8e3d9..0000000000000 --- a/.github/ISSUE_TEMPLATE/error-report.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Error report -about: Used to report production errors seen in AMP Error Reporting. -title: "\U0001F6A8 Error: [error message]" -labels: 'Type: Error Report' -assignees: '' - ---- - - - -Details ---- - - -**Error report:** `[link](go/ampe/)` -**First seen:** -**Frequency:** ~ /day - - -Stacktrace ---- -``` - -``` - - - - - -/cc @ampproject/release-on-duty diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000000..5b2bc44c97c45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,36 @@ +--- +name: Feature Request +description: Request a new feature in AMP. +labels: 'Type: Feature Request' +body: + - type: markdown + id: header + attributes: + value: | + Thanks for filling out this feature request. + - Feature requests related to the [AMP](https://amp.dev) format and cache can be reported using the form below. + - Feature requests 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 feature request. + placeholder: Provide a clear and concise description of the new feature or change to an existing feature you'd like to see. + validations: + required: true + - type: textarea + id: alternatives_considered + attributes: + label: Alternatives Considered + description: Alternatives to this feature request. + placeholder: Provide details around any alternative solutions or features you've considered. + validations: + required: true + - type: textarea + id: additional_context + attributes: + label: Additional Context + description: Other relevant context. + placeholder: Add any other context about your feature request here. E.g. paste a screenshot, or provide a link. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 62d84276f6efa..0000000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Used to report a feature request for AMP. -title: '' -labels: 'Type: Feature Request' -assignees: '' - ---- - -## Describe the new feature or change to an existing feature you'd like to see - -A clear and concise description of what you want to happen. - -## Describe alternatives you've considered - -Provide a clear and concise description of any alternative solutions or features you've considered. - -## Additional context - -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/intent-to-deprecate--i2d-.md b/.github/ISSUE_TEMPLATE/intent-to-deprecate--i2d-.md deleted file mode 100644 index 133f5d21822c9..0000000000000 --- a/.github/ISSUE_TEMPLATE/intent-to-deprecate--i2d-.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Intent-to-deprecate (I2D) -about: Proposes deprecating an existing AMP feature. -title: 'Intent-to-Deprecate: ' -labels: INTENT TO DEPRECATE -assignees: '' - ---- - - - -## Summary - - - -## Motivation - - - -## Impact on existing users - - - -## Alternative implementation suggestion for developers using AMP - - - -## Additional context - - - - - -/cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-deprecate.yml b/.github/ISSUE_TEMPLATE/intent-to-deprecate.yml new file mode 100644 index 0000000000000..997b566a61f31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/intent-to-deprecate.yml @@ -0,0 +1,57 @@ +--- +name: Intent-to-Deprecate (I2D) +description: Propose that an existing AMP feature be deprecated. +labels: INTENT TO DEPRECATE +body: + - type: markdown + id: header + attributes: + value: | + Thanks for creating this Intent-to-Deprecate (I2D) issue. + - See AMP's [versioning and deprecations policy](https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-versioning-policy.md) for instructions on how to fill out this I2D template and how to get help. + - If the feature can be removed immediately after deprecation, add the "INTENT TO REMOVE" label to this issue. + - Otherwise, file a separate Intent-to-Remove (I2R) when the feature is ready for removal. + - type: textarea + id: summary + attributes: + label: Summary + description: A brief description of the feature to be deprecated. + placeholder: Provide the detailed removal plan if the feature is ready for immediate removal after deprecation. Otherwise provide an initial plan for removing the deprecated feature and file a separate Intent-to-Remove (I2R) issue after this issue is approved. + validations: + required: true + - type: textarea + id: motivation + attributes: + label: Motivation + description: The rationale behind this deprecation. + placeholder: Explain why this feature needs to be deprecated and eventually removed. + validations: + required: true + - type: textarea + id: impact + attributes: + label: Impact on Existing Users + description: How this will affect existing users. + placeholder: Explain how the removal of this feature will affect sites that currently use AMP. If available, provide the estimated usage of this feature. + validations: + required: true + - type: textarea + id: alternative_implementation + attributes: + label: Alternative Implementation + description: Alternative implementation suggestions for developers using AMP. + placeholder: Explain how developers using AMP can achieve similar functionality after the feature you are deprecating is removed. + validations: + required: true + - type: textarea + id: additional_context + attributes: + label: Additional Context + description: Any other relevant context. + placeholder: Add any other context that may help people understand your I2D. E.g. paste a screenshot, or provide a link. + - type: textarea + id: notifications + attributes: + label: Notifications + description: Add working groups or individuals you want to notify about this I2D. + value: /cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-implement--i2i-.md b/.github/ISSUE_TEMPLATE/intent-to-implement--i2i-.md deleted file mode 100644 index 556581676751f..0000000000000 --- a/.github/ISSUE_TEMPLATE/intent-to-implement--i2i-.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Intent-to-implement (I2I) -about: Proposes a significant change/update to AMP. See https://github.com/ampproject/amphtml/blob/master/contributing/contributing-code.md. -title: 'I2I: ' -labels: INTENT TO IMPLEMENT -assignees: '' - ---- - - - -## Summary - - - -## Design document - - - -## Motivation - - - -A clear and concise description of any alternative solutions or features you've considered. - -## Additional context - - - -## Launch tracker - - - - - -/cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-implement.yml b/.github/ISSUE_TEMPLATE/intent-to-implement.yml new file mode 100644 index 0000000000000..a202ef107c6a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/intent-to-implement.yml @@ -0,0 +1,56 @@ +--- +name: Intent-to-Implement (I2I) +description: Propose a significant feature or change to AMP. +labels: INTENT TO IMPLEMENT +body: + - type: markdown + id: header + attributes: + value: | + Thanks for creating this Intent-to-Implement (I2I) issue. + - See AMP's [code contribution guide](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md) for instructions on how to fill out this I2I form and how to get help. + - Note that if you are implementing a minor change or fix, you likely do not need to file an I2I. + - If you haven't already done so, sign AMP's [Contributor License Agreement (CLA)](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md#contributor-license-agreement). + - A signed CLA is not required to submit this I2I or to create a PR, but is required before code can be merged. + - type: textarea + id: summary + attributes: + label: Summary + description: A brief description of the feature being implemented. + placeholder: Provide a brief description of the feature you are planning on implementing. + validations: + required: true + - type: textarea + id: design_doc + attributes: + label: Design Document + description: Document that describes the feature's design. + placeholder: Provide a link to your design document once you have one. You do not need a design document to file this I2I. + - type: textarea + id: motivation + attributes: + label: Motivation + description: The rationale behind this feature. + placeholder: Explain why AMP needs this change. It may be useful to describe what AMP developers/users are forced to do without it. When possible, include links to back up your claims. + validations: + required: true + - type: textarea + id: alternative_solutions + attributes: + label: Alternative Solutions + description: Alternative solutions for developers using AMP. + placeholder: Provide a clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + - type: textarea + id: launch_tracker + attributes: + label: Launch Tracker + description: A tracker for the project's status. + placeholder: Provide a link to the launch tracker for this work here if applicable. A template with instructions can be found at bit.ly/amp-launch-tracker. + - type: textarea + id: notifications + attributes: + label: Notifications + description: Add working groups or individuals you want to notify about this I2I, including a code reviewer once you have found one. See [here](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md) for help. + value: /cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-remove--i2r-.md b/.github/ISSUE_TEMPLATE/intent-to-remove--i2r-.md deleted file mode 100644 index 1fe02fc81e692..0000000000000 --- a/.github/ISSUE_TEMPLATE/intent-to-remove--i2r-.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Intent-to-remove (I2R) -about: Rollout plan for removal of a deprecated feature that was described in an I2D. -title: 'Intent-to-Remove: ' -labels: INTENT TO REMOVE -assignees: '' - ---- - - - -## Summary - - - -## Rollout plan - - - -## Alternative implementation suggestion for developers using AMP - - - -## Additional context - - - - - -/cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-remove.yml b/.github/ISSUE_TEMPLATE/intent-to-remove.yml new file mode 100644 index 0000000000000..17ba6a11864f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/intent-to-remove.yml @@ -0,0 +1,55 @@ +--- +name: Intent-to-Remove (I2R) +description: Propose a rollout plan for the removal of a deprecated feature. +labels: INTENT TO REMOVE +body: + - type: markdown + id: header + attributes: + value: | + Thanks for creating this Intent-to-Remove (I2R) issue. + - See AMP's [versioning and deprecations policy](https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-versioning-policy.md) for instructions on how to fill out this I2R template and how to get help. + - This I2R issue should be created after the Intent-to-Deprecate (I2D) issue for the feature was approved. + - type: textarea + id: summary + attributes: + label: Summary + description: A brief description of the feature being removed. + placeholder: Provide a brief description of the feature you are planning on removing. + validations: + required: true + - type: textarea + id: i2d_issue + attributes: + label: Intent-to-Deprecate (I2D) Issue + description: A link to the I2D issue. + placeholder: Provide a link to the I2D issue you filed to deprecate this feature. + validations: + required: true + - type: textarea + id: rollout_plan + attributes: + label: Rollout Plan + description: Steps to remove the feature. + placeholder: Provide details for the steps you will use to remove this previously deprecated feature. + validations: + required: true + - type: textarea + id: alternative_implementation + attributes: + label: Alternative Implementation + description: Alternative implementation suggestions for developers using AMP. + placeholder: Explain how developers using AMP can achieve similar functionality after the feature you are deprecating is removed. + validations: + required: true + - type: textarea + id: additional_context + attributes: + label: Additional Context + description: Any other relevant context. + - type: textarea + id: notifications + attributes: + label: Notifications + description: Add working groups or individuals you want to notify about this I2I. + value: /cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-ship--i2s-.md b/.github/ISSUE_TEMPLATE/intent-to-ship--i2s-.md deleted file mode 100644 index 14f8b0fcdf579..0000000000000 --- a/.github/ISSUE_TEMPLATE/intent-to-ship--i2s-.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Intent-to-ship (I2S) -about: Proposes launching a significant change/update to AMP that has already been - implemented. https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md -title: 'I2S: ' -labels: INTENT TO SHIP -assignees: '' - ---- - - - -## Summary - - - -## Intent-to-implement (I2I) issue - - - -## Experiment(s) to enable - - - -## Required release version - - - -## Demo instructions - - - -## Additional context - - - - - -/cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/intent-to-ship.yml b/.github/ISSUE_TEMPLATE/intent-to-ship.yml new file mode 100644 index 0000000000000..d42b6d31ca5ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/intent-to-ship.yml @@ -0,0 +1,65 @@ +--- +name: Intent-to-Ship (I2S) +description: Propose the launch of an already implemented significant feature or change to AMP. +labels: INTENT TO SHIP +body: + - type: markdown + id: header + attributes: + value: | + Thanks for creating this Intent-to-Ship (I2S) issue. + - See AMP's [code contribution guide](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md) for instructions on how to fill out this I2I form and how to get help. + - Note that if you are shipping a minor feature or change, you likely do not need to file an I2I. + - Typically, issues that require an I2S issue also required an Intent-to-implement (I2I) issue. + - type: textarea + id: summary + attributes: + label: Summary + description: A brief description of the feature being shipped. + placeholder: Provide a brief description of the feature you have implemented and are planning on shipping. + validations: + required: true + - type: textarea + id: i2i_issue + attributes: + label: Intent-to-Implement (I2I) Issue + description: A link to the I2I issue. + placeholder: Provide a link to the I2I issue you filed for this feature or change. + validations: + required: true + - type: textarea + id: experiments + attributes: + label: Experiment(s) to Enable + description: A list of experiments to enable. + placeholder: Provide a list of the experiment(s) that should be enabled to launch your feature. + validations: + required: true + - type: textarea + id: required_release + attributes: + label: Release Tracking Issue + description: AMP release containing the implementation of this feature. + placeholder: 'Provide a link to the "Type: Release" issue for the release that contains all of the changes necessary for your launch.' + validations: + required: true + - type: textarea + id: demo_instructions + attributes: + label: Demo Instructions + description: How to demo the feature. + placeholder: Provide instructions for how to demonstrate the feature. E.g. a link to an example page that uses the feature. + validations: + required: true + - type: textarea + id: additional_context + attributes: + label: Additional Context + description: Other relevant context. + placeholder: Add any other information that may be relevant in determining if your feature can ship. + - type: textarea + id: notifications + attributes: + label: Notifications + description: Add working groups or individuals you want to notify about this I2S, including the code reviewer you worked with on the I2I. + value: /cc @ampproject/wg-approvers diff --git a/.github/ISSUE_TEMPLATE/release-tracker.yml b/.github/ISSUE_TEMPLATE/release-tracker.yml new file mode 100644 index 0000000000000..c13b67afd6349 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-tracker.yml @@ -0,0 +1,104 @@ +--- +name: Release Tracker +description: Track a new AMP release. +labels: 'Type: Release' +title: "\U0001F684 Tracking issue for release VERSION" +body: + - type: markdown + id: header + attributes: + value: | + This is AMP's release tracker form, meant to be used by on-duty engineers. + - See AMP's [release schedule](https://github.com/ampproject/amphtml/blob/main/docs/release-schedule.md) to learn about how releases work. + - See AMP's [release calendar](https://amp-release-calendar.appspot.com) for information about past releases (e.g. versions, dates, submission times, notes). + - See AMP's [pre-release documentation](https://github.com/ampproject/amphtml/blob/main/docs/release-schedule.md#beta-and-experimental-channels) to learn how to test changes in the Experimental channel. + - If you find a bug in this release, file a [bug report](https://github.com/ampproject/amphtml/issues/new?assignees=&labels=Type%3A+Bug&template=bug-report.yml). + - If you believe a bug should be fixed as part of this release, request a [cherry-pick](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md#Cherry-picks). + - For updates that may be of interest to the community (e.g. bug fixes, delayed releases), post a comment to this issue. + - type: input + id: release_version + attributes: + label: Release Version + description: The version of the release being tracked, in the format YYMMDDHHMMXXX. + placeholder: e.g. 2105150310000 + validations: + required: true + - type: input + id: previous_release_version + attributes: + label: Previous Release Version + description: The version of the previous stable channel release, in the format YYMMDDHHMMXXX. Copy it from [here](https://github.com/ampproject/amphtml/releases/latest). + placeholder: e.g. 2105072136000 + validations: + required: true + - type: textarea + id: release_progress + attributes: + label: Release Progress + description: Progress details for this release. + value: | + + + + + This issue tracks release [VERSION](https://github.com/ampproject/amphtml/releases/tag/VERSION). See [new commits](https://github.com/ampproject/amphtml/compare/PREVIOUS_VERSION...VERSION) since the previous Stable channel release. + + - [ ] Release VERSION promoted to Experimental and Beta (opt-in) channels (CL_SUBMIT_TIME) + - [ ] Release VERSION promoted to Experimental and Beta (1% traffic) channels (CL_SUBMIT_TIME) + - [ ] Release VERSION promoted to Stable channel (CL_SUBMIT_TIME) + validations: + required: true + - type: textarea + id: lts_release + attributes: + label: LTS Release + description: Details for the LTS promotion if necessary. + value: | + _To be updated if an LTS promotion is necessary._ + + + + - [ ] Release VERSION promoted to LTS channel (CL_SUBMIT_TIME) + - type: markdown + id: lts_release_instructions + attributes: + value: | + On the second Monday of each month, the current Stable channel version will be promoted to the LTS channel. + - Releases promoted to Stable channel on the first Tuesday of a given month are promoted to LTS channel on the second Monday of the same month. + - Releases promoted to Stable channel on the second, third, fourth, or fifth Tuesday of a given month are not LTS release candidates. + + Based on the above, if this release must be promoted to LTS, fill out the "LTS Release" section. + - type: input + id: cherry_pick_version + attributes: + label: Cherry-Pick Release Version + description: The updated cherry-pick release version if necessary, in the format YYMMDDHHMMXXX. + placeholder: e.g. 2105150310001 + - type: markdown + id: cherry_pick_instructions + attributes: + value: | + Sometimes, a bug in a release necessitates that a fix be cherry-picked before the release can progress. If this is the case, follow the instructions in the [cherry-picks documentation](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md#Cherry-picks) and fill out this section. + - type: textarea + id: cherry_pick_progress + attributes: + label: Cherry-Pick Release Progress + description: Progress details for a cherry-pick release if necessary. + value: | + _To be updated if a cherry-pick release is necessary._ + + + + + A cherry-pick release [CP_VERSION](https://github.com/ampproject/amphtml/releases/tag/CP_VERSION) was created with cherry-pick(s) #CP_ISSUE. + + - [ ] Release CP_VERSION promoted to Experimental and Beta (opt-in) channels (CL_SUBMIT_TIME) + - [ ] Release CP_VERSION promoted to Experimental and Beta (1% traffic) channels (CL_SUBMIT_TIME) + - [ ] Release CP_VERSION promoted to Stable channel (CL_SUBMIT_TIME) + - [ ] Release CP_VERSION promoted to LTS channel (CL_SUBMIT_TIME) + - type: textarea + id: notifications + attributes: + label: Notifications + description: Add working groups or individuals you want to notify about this release. + value: /cc @ampproject/release-on-duty diff --git a/.github/ISSUE_TEMPLATE/release-tracking-issue.md b/.github/ISSUE_TEMPLATE/release-tracking-issue.md deleted file mode 100644 index 5010e6050d2bd..0000000000000 --- a/.github/ISSUE_TEMPLATE/release-tracking-issue.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Release tracking issue -about: - Create a tracking issue for a new AMP release (to be used by release on-duty - engineers) -title: "\U0001F684 Release tracking issue for release " -labels: 'Type: Release' -assignees: '' ---- - -# Release tracking issue - - - -This issue tracks release `[](https://github.com/ampproject/amphtml/releases/tag/)` - -- [ ] Release promoted to Experimental and Beta (opt-in) channels () -- [ ] Release promoted to Experimental and Beta (1% traffic) channels () -- [ ] Release promoted to Stable channel () - - - -See the [release documentation](https://github.com/ampproject/amphtml/blob/master/contributing/release-schedule.md) for more information on the release process, including how to test changes in the Experimental channel. - -If you find a bug in this build, please file an [issue](https://github.com/ampproject/amphtml/issues/new). If you believe the bug should be fixed in this build, follow the instructions in the [cherry picks documentation](https://go.amp.dev/cherry-picks). - -/cc @ampproject/release-on-duty diff --git a/.github/OWNERS b/.github/OWNERS index 643f230b57d4e..a9f24fddf8a7f 100644 --- a/.github/OWNERS +++ b/.github/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index d8aea7c45d38e..0000000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000000..83b0aed09b5fd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ + diff --git a/.github/workflows/create-design-review-issue.js b/.github/workflows/create-design-review-issue.js new file mode 100644 index 0000000000000..9906b461c2d8e --- /dev/null +++ b/.github/workflows/create-design-review-issue.js @@ -0,0 +1,256 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * Creates a Github issue for an upcoming design review. + * + * https://go.amp.dev/design-reviews + * + * A Github Action runs this once a week. See create-design-review-issues.yml + */ + +/* + * ⚠️ Only use standard node modules. + * + * This file runs by itself. It cannot depend on `npm install` nor file + * structure since the Github Action downloads this file only. + */ +const https = require('https'); + +const dayOfWeek = /* wednesday */ 3; // sunday = 0, monday = 1, ... + +const sessionDurationHours = 1; + +// Times in this rotation are adjusted according to Daylight Savings +const timeRotationUtc = [ + ['Americas', '21:00'], + ['Asia/Oceania', '01:00'], + ['Africa/Europe/western Asia', '16:30'], +]; + +const timeRotationStartYyyyMmDd = '2021-03-31'; + +// All previous weeks have already been handled. +const generateWeeksFromNow = 3; + +const labels = ['Type: Design Review']; + +const createTitle = ({yyyyMmDd, timeUtc, region}) => + `Design Review ${yyyyMmDd} ${timeUtc} UTC (${region})`; + +const vcUrl = 'https://bit.ly/amp-dr'; +const calendarEventTitle = 'AMP Project Design Review'; +const calendarEventDetails = vcUrl; + +const createBody = ({timeUtc, timeUrl, calendarUrl}) => + ` +Time: [${timeUtc} UTC](${timeUrl}) ([add to Google Calendar](${calendarUrl})) +Location: [Video conference via Google Meet](${vcUrl}) + +The AMP community holds weekly engineering [design reviews](https://github.com/ampproject/amphtml/blob/main/docs/design-reviews.md). **We encourage everyone in the community to participate in these design reviews.** + +If you are interested in bringing your design to design review, read the [design review documentation](https://github.com/ampproject/amphtml/blob/main/docs/design-reviews.md) and add a link to your design doc or issue by the Monday before your design review. + +When attending a design review please read through the designs _before_ the design review starts. This allows us to spend more time on discussion of the design. + +We rotate our design review between times that work better for different parts of the world as described in our [design review documentation](https://github.com/ampproject/amphtml/blob/main/docs/design-reviews.md), but you are welcome to attend any design review. If you cannot make any of the design reviews but have a design to discuss please let mrjoro@ know on [Slack](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md#discussion-channels) and we will find a time that works for you. +`.trim(); + +function leadingZero(number) { + return number.toString().padStart(2, '0'); +} + +function isDaylightSavingsUsa(date) { + // [start, end] ranges of Daylight Savings in (most of) the USA + // https://www.timeanddate.com/time/dst/2021.html + const ranges = [ + ['2021/3/14', '2021/11/7'], + ['2022/3/13', '2022/11/6'], + ['2023/3/12', '2023/11/5'], + ['2024/3/10', '2024/11/3'], + ['2025/3/9', '2025/11/2'], + ['2026/3/8', '2026/11/1'], + ['2027/3/14', '2027/11/7'], + ['2028/3/12', '2028/11/5'], + ['2029/3/11', '2029/11/4'], + ]; + return ranges.some(([start, end]) => { + const time = date.getTime(); + return ( + time > parseYyyyMmDd(start, /* hours */ 2, /* minutes */ 0).getTime() && + time < parseYyyyMmDd(end, /* hours */ 2, /* minutes */ 0).getTime() + ); + }); +} + +function parseYyyyMmDd(yyyyMmDd, hours = 0, minutes = 0) { + const [yyyy, mm, dd] = yyyyMmDd.split('/', 3).map(Number); + return new Date(yyyy, mm - 1, dd, hours, minutes); +} + +function httpsRequest(url, options, data) { + return new Promise((resolve, reject) => { + const req = https.request(url, options, (res) => { + const chunks = []; + res.on('data', (chunk) => { + chunks.push(Buffer.from(chunk)); + }); + res.on('close', () => { + const body = Buffer.concat(chunks).toString('utf-8'); + resolve({res, body}); + }); + }); + req.on('error', (error) => { + reject(error); + }); + req.write(data); + req.end(); + }); +} + +async function postGithub(token, url, data) { + const {res, body} = await httpsRequest( + url, + { + method: 'POST', + headers: { + 'Authorization': `token ${token}`, + 'Content-Type': 'application/json', + 'User-Agent': 'amphtml', + 'Accept': 'application/vnd.github.v3+json', + }, + }, + JSON.stringify(data) + ); + + if (res.statusCode < 200 || res.statusCode > 299) { + console./*OK*/ error(body); + throw new Error(res.statusCode); + } + + return JSON.parse(body); +} + +function postGithubIssue(token, repo, data) { + const url = `https://api.github.com/repos/${repo}/issues`; + return postGithub(token, url, data); +} + +function getNextDayOfWeek(date, dayOfWeek, weeks = 1) { + const resultDate = new Date(date.getTime()); + resultDate.setDate( + resultDate.getDate() + + (weeks - 1) * 7 + + ((7 + dayOfWeek - date.getDay()) % 7) + ); + return resultDate; +} + +function getRotation(date, startYyyyMmDd) { + const [year, month, day] = startYyyyMmDd.split('-'); + const start = new Date(year, month - 1, day); + const dateBeginningOfDay = new Date( + date.getFullYear(), + date.getMonth(), + date.getDate() + ); + const weeks = Math.round( + (dateBeginningOfDay - start) / (7 * 24 * 60 * 60 * 1000) + ); + return timeRotationUtc[weeks % timeRotationUtc.length]; +} + +const timeZ = (yyyy, mm, dd, hours, minutes) => + `${yyyy + mm + dd}T${leadingZero(hours) + leadingZero(minutes)}Z`; + +function getNextIssueData() { + const today = new Date(); + + // if we run on the same day of week, we need to skip one day to calculate + // properly + today.setDate(today.getDate() + 1); + + const nextDay = getNextDayOfWeek(today, dayOfWeek, generateWeeksFromNow); + const [region, timeUtcNoDst] = getRotation( + nextDay, + timeRotationStartYyyyMmDd + ); + + let [hours, minutes] = timeUtcNoDst.split(':').map(Number); + if (isDaylightSavingsUsa(nextDay)) { + hours -= 1; + } + + const yyyy = nextDay.getFullYear(); + const mm = leadingZero(nextDay.getMonth() + 1); + const dd = leadingZero(nextDay.getDate()); + + const timeUtc = `${leadingZero(hours)}:${leadingZero(minutes)}`; + + const timeUrl = `https://www.timeanddate.com/worldclock/meeting.html?year=${yyyy}&month=${mm}&day=${dd}&iv=0`; + + const startZ = timeZ(yyyy, mm, dd, hours, minutes); + const endZ = timeZ(yyyy, mm, dd, hours + sessionDurationHours, minutes); + + const calendarUrl = `http://www.google.com/calendar/event?action=TEMPLATE&text=${encodeURIComponent( + calendarEventTitle + )}&dates=${startZ}/${endZ}&details=${encodeURIComponent( + calendarEventDetails + )}`; + + const templateData = { + yyyyMmDd: `${yyyy}-${mm}-${dd}`, + timeUtc, + region, + timeUrl, + calendarUrl, + }; + + const title = createTitle(templateData); + const body = createBody(templateData); + + return {title, labels, body}; +} + +function env(key) { + if (!(key in process.env)) { + throw new Error(`Missing env variable: ${key}`); + } + return process.env[key]; +} + +async function createDesignReviewIssue() { + const nextIssueData = getNextIssueData(); + + if (process.argv.includes('--dry-run')) { + console./*OK*/ log(nextIssueData); + return; + } + + const {title, 'html_url': htmlUrl} = await postGithubIssue( + env('GITHUB_TOKEN'), + env('GITHUB_REPOSITORY'), + nextIssueData + ); + console./*OK*/ log(title); + console./*OK*/ log(htmlUrl); +} + +createDesignReviewIssue().catch((e) => { + console./*OK*/ error(e); + process.exit(1); +}); diff --git a/.github/workflows/create-design-review-issue.yml b/.github/workflows/create-design-review-issue.yml new file mode 100644 index 0000000000000..92bc07ac12ed7 --- /dev/null +++ b/.github/workflows/create-design-review-issue.yml @@ -0,0 +1,18 @@ +name: Create Design Review Issue + +on: + schedule: + # Every Wednesday at 00:00:00 + - cron: '0 0 * * 3' + +jobs: + create-design-review-issue: + if: github.repository == 'ampproject/amphtml' + name: Create Design Review Issue + runs-on: ubuntu-latest + steps: + - name: Create Design Review Issue + run: | + wget -q -O - "https://raw.githubusercontent.com/ampproject/amphtml/main/.github/workflows/create-design-review-issue.js" | node + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cross-browser-tests.yml b/.github/workflows/cross-browser-tests.yml new file mode 100644 index 0000000000000..18d6a66bd4d50 --- /dev/null +++ b/.github/workflows/cross-browser-tests.yml @@ -0,0 +1,25 @@ +name: GitHub Actions +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + if: github.repository == 'ampproject/amphtml' + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install Dependencies + run: bash ./.github/workflows/install_dependencies.sh + - name: Build and Test + run: node build-system/pr-check/cross-browser-tests.js diff --git a/.github/workflows/install_dependencies.sh b/.github/workflows/install_dependencies.sh new file mode 100755 index 0000000000000..a76800a27f368 --- /dev/null +++ b/.github/workflows/install_dependencies.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Copyright 2021 The AMP HTML Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the license. + +# Script used by AMP's CI builds to install project dependencies on GH Actions. + +set -e + +GREEN() { echo -e "\033[0;32m$1\033[0m"; } + +if [[ "$OSTYPE" == "linux-gnu"* || "$OSTYPE" == "darwin"* ]]; then + echo $(GREEN "Updating npm prefix...") + npm config set prefix "$HOME/.npm" + + echo $(GREEN "Updating PATH...") + echo "export PATH=$HOME/.npm/bin:$PATH" >> $GITHUB_ENV && source $GITHUB_ENV # For now + echo "$HOME/.npm/bin" >> $GITHUB_PATH # For later +fi + +echo $(GREEN "Enabling log coloring...") +echo "FORCE_COLOR=1" >> $GITHUB_ENV + +echo $(GREEN "Installing dependencies...") +npm ci + +echo $(GREEN "Successfully installed all project dependencies.") diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml new file mode 100644 index 0000000000000..f72a1e3b2a539 --- /dev/null +++ b/.github/workflows/publish-npm-packages.yml @@ -0,0 +1,56 @@ +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 +jobs: + setup: + runs-on: ubuntu-latest + outputs: + extensions: ${{ steps.get-extensions.outputs.extensions }} + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.events.inputs.ampversion }} + - name: Get extensions to publish + id: get-extensions + run: | + EXTENSIONS=$(node ./build-system/npm-publish/get-extensions.js) + echo "::set-output name=extensions::{\"include\":${EXTENSIONS}}" + publish: + if: github.repository == 'ampproject/amphtml' + environment: NPM_TOKEN #TODO: change environment name in repo settings + 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: + check-latest: true + registry-url: https://registry.npmjs.org + - name: Build package + run: | + npm install + node ./build-system/npm-publish/build-npm-binaries.js ${{ matrix.extension }} + node ./build-system/npm-publish/write-package-files.js ${{ matrix.extension }} ${{ github.event.inputs.ampversion }} + - name: Publish v1 + run: npm publish ./extensions/${{ matrix.extension }}/1.0 --access public --tag ${{ github.event.inputs.tag }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish v2 if it exists + run: | + if $(test -d ${{ github.workspace }}/extensions/${{ matrix.extension }}/2.0); then + npm publish ./extensions/${{ matrix.extension }}/2.0 --access public --tag ${{ github.event.inputs.tag }} + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/sweep-experiments.yml b/.github/workflows/sweep-experiments.yml new file mode 100644 index 0000000000000..b9b7395df38bd --- /dev/null +++ b/.github/workflows/sweep-experiments.yml @@ -0,0 +1,77 @@ +# 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.1.5 + + - 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/.gitignore b/.gitignore index 6516b78463468..c4eecdcc02d53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,44 @@ -.DS_Store -.g4ignore -build/ -.karma-cache -.amp-build -c -/dist +# Local cache directories +# Keep this list in sync with .eslintignore, .prettierignore, and build-system/tasks/clean.js +.babel-cache +.css-cache +.pre-closure-cache + +# Output directories +# Keep this list in sync with .eslintignore, .prettierignore, and build-system/tasks/clean.js +.amp-dep-check +build +build-system/dist +build-system/server/new-server/transforms/dist +build-system/tasks/performance/cache +build-system/tasks/performance/results.json +build-system/global-configs/custom-config.json +dist dist.3p dist.tools -examples.min -node_modules -npm-debug.log -.idea -.tm_properties -.settings -typings -typings.json -build-system/runner/dist -/test/manual/amp-ad.adtech.html +export +examples/storybook +extensions/**/dist +/release +result-reports +src/purifier/dist test/coverage -./package-lock.json +test/coverage-e2e +validator/**/dist +validator/export + +# OS level files +.DS_Store +.g4ignore *.swp *.swo -yarn-error.log -sc-*-linux* -sc-*-osx* -sauce_connect_* -deps.txt -firebase + +# Development environment files +**/node_modules +npm-debug.log + +# Firebase directories .firebaserc +.firebase +firebase firebase.json -.firebase/ -src/purifier/dist -build-system/server/new-server/transforms/dist -build-system/tasks/performance/cache -build-system/tasks/performance/results.json -build-system/global-configs/custom-config.json -export -validator/export -validator/java/.classpath -validator/java/.project diff --git a/.lando.yml b/.lando.yml index 190d9aefbf62a..8987a551405a4 100644 --- a/.lando.yml +++ b/.lando.yml @@ -1,6 +1,6 @@ # This is a configuration file for a Lando-based local dev environment for amphtml. # It is an alternative to the existing local development environment to what is described in the Getting Started -# End-to-end guide . +# End-to-end guide . # # This environment allows you to encapsulate the amphtml development environment inside of a Docker container. # Instead of accessing , this environment can be accessed via . @@ -15,8 +15,8 @@ # 5. You should then be able to access in your browser. # 6. If using the Local AMP Chrome Extension, open the popup and configure the Base URL to be https://amphtml.lando.site/ # -# Now instead of running `gulp` commands directly, you instead run `lando gulp`. Additionally, some of the common gulp -# commands have shortcuts, so you can run `lando watch` instead of `lando gulp build --watch`. For a full list of the +# Now instead of running `amp` commands directly, you instead run `lando amp`. Additionally, some of the common amp +# commands have shortcuts, so you can run `lando watch` instead of `lando amp build --watch`. For a full list of the # shortcuts run `lando help`. # # For details on the file format, see . @@ -28,53 +28,49 @@ services: appserver: type: node install_dependencies_as_root: - - curl --compressed -o- -L https://yarnpkg.com/install.sh | bash - apt-get update && apt-get install -y openjdk-8-jdk && apt-get install -y ant && apt-get clean - apt-get install -y openjdk-7-jre protobuf-compiler python2.7 python-protobuf build: - - yarn global add gulp-cli - - yarn + - npm install run_as_root: - echo "127.0.0.1 amphtml.lndo.site" >> /etc/hosts - command: cd /app && yarn gulp serve --port=80 --host=0.0.0.0 + command: cd /app && amp serve --port=80 --host=0.0.0.0 tooling: - yarn: + amp: service: appserver - gulp: - service: appserver - cmd: 'yarn gulp' + cmd: 'amp' watch: service: appserver - cmd: 'yarn gulp build --watch' + cmd: 'amp build --watch' description: 'Builds unminified code, watches for changes in files, re-builds when detected' watch-minified: service: appserver - cmd: 'yarn gulp dist --watch' + cmd: 'amp dist --watch' description: 'Builds minified code, watches for changes in files, re-builds when detected' lint: service: appserver - cmd: 'yarn gulp lint' + cmd: 'amp lint' description: 'Validates against Google Closure Linter' check-types: service: appserver - cmd: 'yarn gulp check-types' + cmd: 'amp check-types' description: 'Check source code for JS type errors' dist: service: appserver - cmd: 'yarn gulp dist' + cmd: 'amp dist' description: 'Build production binaries' unit: service: appserver - cmd: 'yarn gulp unit' + cmd: 'amp unit' description: 'Run unit tests' integration: service: appserver - cmd: 'yarn gulp integration' + cmd: 'amp integration' description: 'Run integration tests' e2e: service: appserver - cmd: 'yarn gulp e2e' + cmd: 'amp e2e' description: 'Runs e2e tests' proxy: diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000..2e3285ff656e3 --- /dev/null +++ b/.npmrc @@ -0,0 +1,6 @@ +# npm configuration for amphtml + +audit=false # Speed up install by not running a full audit +fund=false # Don't print the trailing funding message +loglevel=error # Suppress verbose logging during installation +save-exact=true # Use exact semver numbers diff --git a/.prettierignore b/.prettierignore index ec6d3cdd7f5b0..0fe74095da2dd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,32 @@ -package.json +# Local cache directories +# Keep this list in sync with .gitignore, .eslintignore, and build-system/tasks/clean.js +.babel-cache +.css-cache +.pre-closure-cache + +# Output directories +# Keep this list in sync with .gitignore, .eslintignore, and build-system/tasks/clean.js +.amp-dep-check +build +build-system/dist +build-system/server/new-server/transforms/dist +build-system/tasks/performance/cache +build-system/tasks/performance/results.json +build-system/global-configs/custom-config.json +dist +dist.3p +dist.tools +export +examples/storybook +extensions/**/dist +/release +result-reports +src/purifier/dist +test/coverage +test/coverage-e2e +validator/**/dist +validator/export + +# Files and directories explicitly ignored by prettier +**/package*.json +**/node_modules/** diff --git a/.prettierrc b/.prettierrc index d5a6bee8de619..ae6bf44db9904 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,17 +8,23 @@ "files": "OWNERS", "options": {"parser": "json5"} }, + { + "files": "build-system/tasks/make-extension/template/**", + "options": { + "requirePragma": true + } + }, { "files": "*.md", - "options": {"parser": "markdown"} + "options": { + "parser": "markdown", + "embeddedLanguageFormatting": "off", + "tabWidth": 4 + } }, { "files": [".prettierrc", ".renovaterc.json", "*.json"], "options": {"parser": "json"} - }, - { - "files": [".codecov.yml", ".lando.yml", ".lgtm.yml", ".travis.yml"], - "options": {"parser": "yaml"} } ] } diff --git a/.renovaterc.json b/.renovaterc.json index 1689316875165..f2ec776c0601a 100644 --- a/.renovaterc.json +++ b/.renovaterc.json @@ -1,109 +1,112 @@ { "extends": ["config:base"], "node": { - "supportPolicy": ["lts_active"] + "supportPolicy": ["lts_latest"] }, - "statusCheckVerify": true, - "ignorePaths": [], - + "ignoreDeps": ["ubuntu-2004"], "commitMessagePrefix": "📦", "timezone": "America/Los_Angeles", "schedule": "after 12am every weekday", - - "masterIssue": true, + "dependencyDashboard": true, "prBodyColumns": ["Package", "Update", "Type", "Change", "Package file"], - "separateMinorPatch": true, + "prBodyNotes": [ + "
", + "How to resolve breaking changes", + "This PR may introduce breaking changes that require manual intervention. In such cases, you will need to check out this branch, fix the cause of the breakage, and commit the fix to ensure a green CI build. To check out and update this PR, follow the steps below:", + "```sh\n# Check out the PR branch (these steps are from GitHub)\ngit checkout -b renovate-bot-{{{branchName}}} main\ngit pull https://github.com/renovate-bot/amphtml.git {{{branchName}}}\n\n# Directly make fixes and commit them\namp lint --fix # For lint errors in JS files\namp prettify --fix # For prettier errors in non-JS files\n# Edit source code in case of new compiler warnings / errors\n\n# Push the changes to the branch\ngit push git@github.com:renovate-bot/amphtml.git renovate-bot-{{{branchName}}}:{{{branchName}}}\n```", + "
" + ], "packageRules": [ { - "paths": ["**/*"], "groupName": "subpackage devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "subpackage-" - } + "matchPaths": ["**/*"], + "major": {"automerge": false, "assignAutomerge": false}, + "automerge": true, + "assignAutomerge": true }, { - "paths": ["src/**"], - "labels": ["WG: runtime"], - - "groupName": "runtime devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "runtime-" - } - }, - - { - "paths": ["build-system/**"], + "groupName": "build-system devDependencies", + "matchPaths": ["build-system/**"], + "major": {"automerge": false, "assignAutomerge": false}, "labels": ["WG: infra"], - - "groupName": "build system devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "build-system-" - } + "automerge": true, + "assignAutomerge": true }, - { - "paths": ["validator/**"], - "labels": ["WG: caching"], - "groupName": "validator devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "validator-" - } + "matchPaths": ["validator/**"], + "major": {"automerge": false, "assignAutomerge": false}, + "labels": ["WG: caching"], + "automerge": true, + "assignAutomerge": true + }, + { + "groupName": "validator webui", + "matchPaths": ["validator/js/webui/**"], + "enabled": false }, - { - "paths": ["+(package.json)"], - "groupName": "core devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "core-" - } + "matchFiles": ["package.json"], + "major": {"automerge": false, "assignAutomerge": false}, + "labels": ["WG: infra"], + "automerge": true, + "assignAutomerge": true }, - { - "packagePatterns": ["\\b(prettier|eslint)\\b"], - "prBodyNotes": [ - "This PR upgrades one or more packages used to ensure code formatting. In case there are new errors, you will need to check out this branch, make sure `gulp lint` and `gulp prettify` pass (try using `--fix`), and commit the fixes to ensure a green Travis build." - ], - "groupName": "linting devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "linting-" - } + "matchFiles": ["package.json"], + "matchPackagePatterns": ["\\b(prettier|eslint)\\b"], + "major": {"automerge": false, "assignAutomerge": false}, + "labels": ["WG: infra"], + "automerge": true, + "assignAutomerge": true }, - { - "excludePackagePatterns": ["^@ampproject/"], - "depTypeList": ["dependencies"], - "enabled": false + "groupName": "babel devDependencies", + "matchFiles": ["package.json"], + "matchPackagePatterns": ["\\bbabel"], + "major": {"automerge": false, "assignAutomerge": false}, + "labels": ["WG: infra", "WG: performance"], + "automerge": true, + "assignAutomerge": true + }, + { + "groupName": "esbuild devDependencies", + "matchFiles": ["package.json"], + "matchPackagePatterns": ["\\besbuild\\b"], + "major": {"automerge": false, "assignAutomerge": false}, + "labels": ["WG: infra", "WG: performance"], + "automerge": true, + "assignAutomerge": true }, - { - "packagePatterns": ["^@ampproject/"], - "depTypeList": ["devDependencies"], - "groupName": "ampproject devDependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "ampproject-" - } + "matchFiles": ["package.json"], + "matchPackagePatterns": ["^@ampproject/"], + "matchDepTypes": ["devDependencies"], + "major": {"automerge": false, "assignAutomerge": false}, + "labels": ["WG: bento", "WG: components", "WG: performance"], + "automerge": true, + "assignAutomerge": true }, - { - "packagePatterns": ["^@ampproject/"], - "depTypeList": ["dependencies"], - "groupName": "ampproject dependencies", - "major": { - "groupName": null, - "managerBranchPrefix": "ampproject-" - } + "matchFiles": ["package.json"], + "matchPackagePatterns": ["^@ampproject/"], + "matchDepTypes": ["dependencies"], + "labels": ["WG: bento", "WG: components", "WG: performance"], + "automerge": false, + "assignAutomerge": false + }, + { + "groupName": "core dependencies", + "matchFiles": ["package.json"], + "excludePackagePatterns": ["^@ampproject/"], + "matchDepTypes": ["dependencies"], + "labels": ["WG: bento", "WG: components", "WG: performance"], + "automerge": false, + "assignAutomerge": false } ] } diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 42b8d861a0898..0000000000000 --- a/.travis.yml +++ /dev/null @@ -1,145 +0,0 @@ -language: node_js -dist: xenial -node_js: - - '12' -notifications: - email: - recipients: - - amp-build-cop@grotations.appspotmail.com - on_success: change - on_failure: change -before_install: - # Install the latest version of Yarn (Xenial's built-in version v1.15.2 is outdated) - - curl -o- -L https://yarnpkg.com/install.sh | bash - - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" - # Override Xenial's default Java version (github.com/travis-ci/travis-ci/issues/10290) - - export PATH=$(echo "$PATH" | sed -e 's/:\/usr\/local\/lib\/jvm\/openjdk11\/bin//') - - export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64 - # Ensure python v2 and v3 are available, and "python" defaults to v3 (docs.travis-ci.com/user/reference/xenial/#python-support) - - pyenv global 3.6.7 2.7.15 - - python3 --version - - python2 --version - - python --version - # Xenial's version of python-protobuf is outdated (github.com/ampproject/amphtml/pull/22528) - - pip3 install --user protobuf -branches: - only: - - master - - /^amp-release-.*$/ -addons: - apt: - packages: - # Use unbuffer (from expect-dev) for log coloring (github.com/travis-ci/travis-ci/issues/7967) - - expect-dev - - google-cloud-sdk - - openssl - - protobuf-compiler - chrome: stable - hosts: - - ads.localhost - - iframe.localhost - # CURLs amp subdomain for the amp-recaptcha-input integration test. The hash - # is the CURLS subdomain for localhost:9876 - - jgla3zmib2ggq5buc4hwi5taloh6jlvzukddfr4zltz3vay5s5rq.recaptcha.localhost - # Requested by some tests because they need a valid font host, - # but should not resolve in tests. - - fonts.googleapis.com -env: - jobs: - - CACHENAME=AMPHTMLJOB -stages: - - name: build - - name: test - - name: experiment - if: type = push -jobs: - include: - - stage: build - name: 'Build' - script: - - unbuffer node build-system/pr-check/build.js - - stage: build - name: 'Checks' - script: - - unbuffer node build-system/pr-check/checks.js - - stage: build - name: 'Validator Tests' - script: - - unbuffer node build-system/pr-check/validator-tests.js - - stage: build - name: 'Dist, Bundle Size' - script: - - unbuffer node build-system/pr-check/dist-bundle-size.js - - stage: build - name: 'Module Dist, Bundle Size' - script: - - unbuffer node build-system/pr-check/module-dist-bundle-size.js - - stage: test - name: 'Visual Diff Tests' - script: - - unbuffer node build-system/pr-check/visual-diff-tests.js - env: - - CACHE_NAME=VISUALDIFFJOB - - stage: test - name: 'Local Tests' - script: - - unbuffer node build-system/pr-check/local-tests.js - - stage: test - name: 'Dist Tests' - script: - - unbuffer node build-system/pr-check/dist-tests.js - - stage: test - name: 'Remote (Sauce Labs) Tests' - script: - - unbuffer node build-system/pr-check/remote-tests.js - after_script: - - build-system/sauce_connect/stop_sauce_connect.sh - - ps -ef - - stage: test - name: 'End to End Tests' - script: - - unbuffer node build-system/pr-check/e2e-tests.js - env: - - CACHE_NAME=E2EJOB - - stage: experiment - name: 'Experiment A Tests' - script: - - unbuffer node build-system/pr-check/experiment-tests.js --experiment=experimentA - cache: false - - stage: experiment - name: 'Experiment B Tests' - script: - - unbuffer node build-system/pr-check/experiment-tests.js --experiment=experimentB - cache: false - - stage: experiment - name: 'Experiment C Tests' - script: - - unbuffer node build-system/pr-check/experiment-tests.js --experiment=experimentC - cache: false - - stage: experiment - name: 'Performance Tests' - script: - - unbuffer node build-system/pr-check/performance-tests.js - env: - - CACHE_NAME=PERFORMANCEJOB - allow_failures: - - script: unbuffer node build-system/pr-check/performance-tests.js # See #28148 - - script: unbuffer node build-system/pr-check/remote-tests.js # See #28343 - fast_finish: true -before_cache: - # do not store cache for pr builds or experiment stage builds - - if [[ $TRAVIS_EVENT_TYPE == pull_request ]] || [[ $TRAVIS_BUILD_STAGE_NAME == experiment ]]; then exit $TRAVIS_TEST_RESULT ; fi -cache: - directories: - - node_modules - - build-system/tasks/e2e/node_modules - - build-system/tasks/performance/node_modules - - build-system/tasks/visual-diff/node_modules - - sauce_connect - - validator/node_modules - - validator/nodejs/node_modules - - validator/webui/node_modules - - validator/java/bazel-installer - - $HOME/.m2 - - .karma-cache - yarn: true diff --git a/.vscode/OWNERS b/.vscode/OWNERS index 8425845cae3ee..002922bb9cc93 100644 --- a/.vscode/OWNERS +++ b/.vscode/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/.vscode/settings.json b/.vscode/settings.json index 1e5ee029ced85..e65a2f0e79e10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,13 +7,7 @@ // Enable JSON auto-formatting for these files. ".prettierrc": "json", - ".renovaterc.json": "json", - - // Enable YAML auto-formatting for these files. - ".codecov.yml": "yaml", - ".lando.yml": "yaml", - ".lgtm.yml": "yaml", - ".travis.yml": "yaml" + ".renovaterc.json": "json" }, // Auto-fix JS files with ESLint using amphtml's custom settings. Needs diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index 1333f4e0284ce..0000000000000 --- a/.yarnrc +++ /dev/null @@ -1 +0,0 @@ ---add.exact true diff --git a/3p/3d-gltf/index.js b/3p/3d-gltf/index.js index 623ba1b83c0f6..b2fcec99da9f0 100644 --- a/3p/3d-gltf/index.js +++ b/3p/3d-gltf/index.js @@ -14,10 +14,10 @@ * limitations under the License. */ -import {dict} from '../../src/utils/object'; +import {dict} from '../../src/core/types/object'; import {listenParent, nonSensitiveDataPostMessage} from '../messaging'; import {loadScript} from '../3p'; -import {parseJson} from '../../src/json'; +import {parseJson} from '../../src/core/types/object/json'; import {user} from '../../src/log'; import GltfViewer from './viewer'; diff --git a/3p/3d-gltf/viewer.js b/3p/3d-gltf/viewer.js index 1ee91bde01f97..b1c89772b10d5 100644 --- a/3p/3d-gltf/viewer.js +++ b/3p/3d-gltf/viewer.js @@ -58,8 +58,7 @@ export default class GltfViewer { this.ampPlay_ = true; /** @private */ - this.ampInViewport_ = - options['initialIntersection']['intersectionRatio'] > 0; + this.ampInViewport_ = false; /** @private */ this.setSize_ = this.setupSize_(); diff --git a/3p/3p.js b/3p/3p.js index 0611bbd1099a7..5e4132744b9e0 100644 --- a/3p/3p.js +++ b/3p/3p.js @@ -21,9 +21,10 @@ // Note: loaded by 3p system. Cannot rely on babel polyfills. -import {devAssert, rethrowAsync, userAssert} from '../src/log'; -import {hasOwn, map} from '../src/utils/object'; -import {isArray} from '../src/types'; +import {devAssert, userAssert} from '../src/log'; +import {hasOwn, map} from '../src/core/types/object'; +import {isArray} from '../src/core/types'; +import {rethrowAsync} from '../src/core/error'; /** @typedef {function(!Window, !Object)} */ let ThirdPartyFunctionDef; diff --git a/3p/OWNERS b/3p/OWNERS index a8918c00443a9..07a473cf02aee 100644 --- a/3p/OWNERS +++ b/3p/OWNERS @@ -1,12 +1,12 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [ {name: 'ampproject/wg-ads-reviewers'}, - {name: 'ampproject/wg-ui-and-a11y'}, + {name: 'ampproject/wg-components'}, ], }, ], diff --git a/3p/README.md b/3p/README.md index c61ccb4d7d3fb..56047f6fe7041 100644 --- a/3p/README.md +++ b/3p/README.md @@ -1,6 +1,6 @@ # Inclusion of third party software, embeds, and services into AMP -In general all inclusions are subject to [CONTRIBUTING.md](../CONTRIBUTING.md). This files outlines specific rules for certain types of external embed and other software inclusions. +In general all inclusions are subject to [docs/contributing.md](../docs/contributing.md). This files outlines specific rules for certain types of external embed and other software inclusions. In order to qualify for inclusion, an extended component that integrates a third-party service must generally meet the notability requirements of the English Wikipedia, and is in common use in Internet publishing. As a rough rule of thumb, it should be used or requested by 5% of the top 10,000 websites as noted on builtwith.com, or already integrated into [oEmbed](http://oembed.com/). @@ -10,42 +10,42 @@ We highly prefer integrations that do not use iframes. JSONP cannot be used for Examples: Youtube, Vimeo videos; Tweets, Instagrams; comment systems; polls; quizzes; document viewers -- Our intent is to provide first class support for all embeds that fulfill the notability guidelines laid out in [CONTRIBUTING.md](../CONTRIBUTING.md). -- Consider whether a iframe-with-placeholder solution fits your use case where iframe generation is not done immediately (can be done before user action for instant loading of iframe). -- Consider whether all that is needed is some documentation for how to use the embed with `amp-iframe`. -- Iframes and all sub resources must be served from HTTPS. -- Avoid client side rendering of iframe content. -- If your use of iframes is for style isolation, consider that AMP might provide an iframe-free alternative. -- If you can make it not-iframe-based that is much better. (See e.g. the pinterest embed). We will always ask to do this first. E.g. adding a CORS endpoint to your server might make this possible. -- Must play well within [AMP's sizing framework](https://github.com/ampproject/amphtml/blob/master/spec/amp-html-layout.md). -- All JS on container page must be open source and bundled with AMP. -- JavaScript loaded into iframe should be reasonable with respect to functionality. -- Use the `sandbox` attribute on iframe if possible. -- Provide unit and [integration tests](#adding-proper-integration-tests). -- Embeds that require browser plugins, such as Flash, Java, ActiveX, Silverlight, etc. are disallowed unless necessary. Special review required. We cannot currently see a reason why these should be allowed. +- Our intent is to provide first class support for all embeds that fulfill the notability guidelines laid out in [docs/contributing.md](../docs/contributing.md). +- Consider whether a iframe-with-placeholder solution fits your use case where iframe generation is not done immediately (can be done before user action for instant loading of iframe). +- Consider whether all that is needed is some documentation for how to use the embed with `amp-iframe`. +- Iframes and all sub resources must be served from HTTPS. +- Avoid client side rendering of iframe content. +- If your use of iframes is for style isolation, consider that AMP might provide an iframe-free alternative. +- If you can make it not-iframe-based that is much better. (See e.g. the pinterest embed). We will always ask to do this first. E.g. adding a CORS endpoint to your server might make this possible. +- Must play well within [AMP's sizing framework](https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-html-layout.md). +- All JS on container page must be open source and bundled with AMP. +- JavaScript loaded into iframe should be reasonable with respect to functionality. +- Use the `sandbox` attribute on iframe if possible. +- Provide unit and [integration tests](#adding-proper-integration-tests). +- Embeds that require browser plugins, such as Flash, Java, ActiveX, Silverlight, etc. are disallowed unless necessary. Special review required. We cannot currently see a reason why these should be allowed. ## Ads -- We welcome pull requests by all ad networks for inclusion into AMP. -- All ads and all sub resources must be served from HTTPS. -- Must play well within [AMP's sizing framework](https://github.com/ampproject/amphtml/blob/master/spec/amp-html-layout.md). -- For display ads support, always implement amp-ad and instruct your client to use your amp-ad implementation instead of using amp-iframe. Althought amp-iframe will render the ad, ad clicks will break and viewability information is not available. -- Providing an optional image only zero-iframe embed is appreciated. -- Support viewability and other metrics/instrumentation as supplied by AMP (via postMessage API) -- Try to keep overall iframe count at one per ad. Explain why more are needed. -- Share JS between iframes on the same page. -- Provide unit and [integration tests](#adding-proper-integration-tests). -- Provide test accounts for inclusion in our open source repository for integration tests. +- We welcome pull requests by all ad networks for inclusion into AMP. +- All ads and all sub resources must be served from HTTPS. +- Must play well within [AMP's sizing framework](https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-html-layout.md). +- For display ads support, always implement amp-ad and instruct your client to use your amp-ad implementation instead of using amp-iframe. Althought amp-iframe will render the ad, ad clicks will break and viewability information is not available. +- Providing an optional image only zero-iframe embed is appreciated. +- Support viewability and other metrics/instrumentation as supplied by AMP (via postMessage API) +- Try to keep overall iframe count at one per ad. Explain why more are needed. +- Share JS between iframes on the same page. +- Provide unit and [integration tests](#adding-proper-integration-tests). +- Provide test accounts for inclusion in our open source repository for integration tests. The following aren't hard requirements, but are performance optimizations we should strive to incorporate. Please provide a timeline as to when you expect to follow these guidelines: -- Support pause and play APIs to turn animations on and off. Ideally also to interrupt loading. -- Never block the UI thread for longer than 50ms, so that user action is never blocked for longer than that. - - Keep individual JS files small enough, so they compile in under 50ms on a 2013 Android phone. - - Split up other expensive work, so it can be interrupted at 50ms boundary. -- Creatives should only use animations drive by CSS animation frame. -- Creatives should only animate using CSS transforms and opacity. -- When creatives animate they should not use more than 2ms time per frame to allow for other animations to have sufficient time for concurrent animations. +- Support pause and play APIs to turn animations on and off. Ideally also to interrupt loading. +- Never block the UI thread for longer than 50ms, so that user action is never blocked for longer than that. + - Keep individual JS files small enough, so they compile in under 50ms on a 2013 Android phone. + - Split up other expensive work, so it can be interrupted at 50ms boundary. +- Creatives should only use animations drive by CSS animation frame. +- Creatives should only animate using CSS transforms and opacity. +- When creatives animate they should not use more than 2ms time per frame to allow for other animations to have sufficient time for concurrent animations. The better an ad network does on the above requirements, the earlier ads from it will be loaded into AMP. In other words: _The slower the ad loads and the more it interferes with the page, the later AMP will load it._ @@ -55,16 +55,16 @@ Review the [ads/README](../ads/README.md) for further details on ad integration. ## Fonts -- AMP allows inclusion of fonts via the `@font-face` directive. -- JavaScript can not be involved with the initiation of font loading. -- Font loading gets controlled (but not initiated) by [``](https://github.com/ampproject/amphtml/issues/648). -- AMP by default does not allow inclusion of external stylesheets, but it is happy to allow URL prefixes of font providers for font inclusion via link tags. These link tags and their fonts must be served via HTTPS. -- If a font provider does referrer based "security" it needs to allow the AMP proxy origins before being included in the link tag allowlist. AMP proxy sends the appropriate referrer header such as `https://cdn.ampproject.org`. +- AMP allows inclusion of fonts via the `@font-face` directive. +- JavaScript can not be involved with the initiation of font loading. +- Font loading gets controlled (but not initiated) by [``](https://github.com/ampproject/amphtml/issues/648). +- AMP by default does not allow inclusion of external stylesheets, but it is happy to allow URL prefixes of font providers for font inclusion via link tags. These link tags and their fonts must be served via HTTPS. +- If a font provider does referrer based "security" it needs to allow the AMP proxy origins before being included in the link tag allowlist. AMP proxy sends the appropriate referrer header such as `https://cdn.ampproject.org`. # Adding proper integration tests You should ensure there are integration tests for your extension. These should be added to the AMP repo where it makes sense. In some cases this won't be possible because it relies on bringing up third-party infrastructure. In these cases you should maintain testing for the extension on your -infrastructure against both production AMP and [canary](https://github.com/ampproject/amphtml/blob/master/contributing/release-schedule.md#amp-experimental-and-beta-channels). -Upon any monitored failures, an escalation can be raised in [regular AMP communication channel](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#discussion-channels). +infrastructure against both production AMP and [canary](https://github.com/ampproject/amphtml/blob/main/docs/release-schedule.md#amp-experimental-and-beta-channels). +Upon any monitored failures, an escalation can be raised in [regular AMP communication channel](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md#discussion-channels). diff --git a/3p/amp-script-proxy-iframe.js b/3p/amp-script-proxy-iframe.js new file mode 100644 index 0000000000000..0bf32fead838a --- /dev/null +++ b/3p/amp-script-proxy-iframe.js @@ -0,0 +1,93 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * See IframeWorker within `worker-dom` for the iframe proxy contract. + */ + +/** + * @enum {string} + */ +const MESSAGE_TYPE = { + iframeReady: 'iframe-ready', + workerReady: 'worker-ready', + init: 'init-worker', + onmessage: 'onmessage', + onerror: 'onerror', + onmessageerror: 'onmessageerror', + postMessage: 'postMessage', +}; + +let parentOrigin = '*'; + +/** + * @param {MessageType} type + * @param {*} message + */ +function send(type, message) { + if (type !== MESSAGE_TYPE.iframeReady && parentOrigin === '*') { + throw new Error('Broadcast banned except for iframe-ready message.'); + } + parent./*OK*/ postMessage({type, message}, parentOrigin); +} + +/** + * + * @param {MessageType} type + * @param {*} handler + */ +function listen(type, handler) { + window.addEventListener('message', (event) => { + if (event.source !== parent) { + return; + } + parentOrigin = event.origin; + + if (event.data.type === type) { + handler(event.data); + } + }); +} + +// Send initialization. +send(MESSAGE_TYPE.iframeReady); + +let worker = null; +// Listen for Worker Init. +listen(MESSAGE_TYPE.init, ({code}) => { + if (worker) { + return; + } + worker = new Worker(URL.createObjectURL(new Blob([code]))); + + // Proxy messages Worker to parent Window. + worker.onmessage = (e) => send(MESSAGE_TYPE.onmessage, e.data); + worker.onmessageerror = (e) => send(MESSAGE_TYPE.onmessageerror, e.data); + worker.onerror = (e) => + send(MESSAGE_TYPE.onerror, { + lineno: e.lineno, + colno: e.colno, + message: e.message, + filename: e.filename, + }); + + // Proxy message from parent Window to Worker. + listen(MESSAGE_TYPE./*OK*/ postMessage, ({message}) => + worker./*OK*/ postMessage(message) + ); + + send(MESSAGE_TYPE.workerReady); +}); diff --git a/3p/ampcontext-integration.js b/3p/ampcontext-integration.js index e71a05e86c5ff..c84253b652141 100644 --- a/3p/ampcontext-integration.js +++ b/3p/ampcontext-integration.js @@ -14,10 +14,9 @@ * limitations under the License. */ import {AbstractAmpContext} from './ampcontext'; -import {adConfig} from '../ads/_config'; import {computeInMasterFrame} from './3p'; import {dev, user, userAssert} from '../src/log'; -import {dict} from '../src/utils/object'; +import {dict} from '../src/core/types/object'; /** * Returns the "master frame" for all widgets of a given type. @@ -30,10 +29,8 @@ import {dict} from '../src/utils/object'; */ export function masterSelection(win, type) { type = type.toLowerCase(); - const configType = - adConfig[type] && adConfig[type]['masterFrameAccessibleType']; // The master has a special name. - const masterName = 'frame_' + (configType || type) + '_master'; + const masterName = 'frame_' + type + '_master'; let master; try { // Try to get the master from the parent. If it does not diff --git a/3p/ampcontext-lib.js b/3p/ampcontext-lib.js index 84a8437f86a18..f2ba0e446ced0 100644 --- a/3p/ampcontext-lib.js +++ b/3p/ampcontext-lib.js @@ -15,7 +15,7 @@ */ // src/polyfills.js must be the first import. -import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 +import './polyfills'; import {AmpContext} from './ampcontext.js'; import {initLogConstructor, setReportError} from '../src/log'; diff --git a/3p/ampcontext.js b/3p/ampcontext.js index 9a87fe612bcb1..990d2e2768b19 100644 --- a/3p/ampcontext.js +++ b/3p/ampcontext.js @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {AmpEvents} from '../src/amp-events'; +import {AmpEvents} from '../src/core/constants/amp-events'; +import {Deferred} from '../src/core/data-structures/promise'; import {IframeMessagingClient} from './iframe-messaging-client'; import {MessageType} from '../src/3p-frame-messaging'; import {dev, devAssert} from '../src/log'; -import {dict} from '../src/utils/object'; -import {isObject} from '../src/types'; +import {dict, map} from '../src/core/types/object'; +import {isObject} from '../src/core/types'; import {parseUrlDeprecated} from '../src/url'; -import {tryParseJson} from '../src/json'; +import {tryParseJson} from '../src/core/types/object/json'; export class AbstractAmpContext { /** @@ -77,6 +78,9 @@ export class AbstractAmpContext { /** @type {?string} */ this.initialConsentValue = null; + /** @type {?Object} */ + this.initialConsentMetadata = null; + /** @type {?Object} */ this.initialLayoutRect = null; @@ -107,6 +111,12 @@ export class AbstractAmpContext { /** @type {?string} */ this.tagName = null; + /** @type {!Object} */ + this.resizeIdToDeferred_ = map(); + + /** @type {number} */ + this.nextResizeRequestId_ = 0; + this.findAndSetMetadata_(); /** @protected {!IframeMessagingClient} */ @@ -114,6 +124,7 @@ export class AbstractAmpContext { this.client_.setSentinel(devAssert(this.sentinel)); this.listenForPageVisibility_(); + this.listenToResizeResponse_(); } /** @@ -207,19 +218,60 @@ export class AbstractAmpContext { /** * Send message to runtime requesting to resize ad to height and width. * This is not guaranteed to succeed. All this does is make the request. - * @param {number} width The new width for the ad we are requesting. - * @param {number} height The new height for the ad we are requesting. + * @param {number|undefined} width The new width for the ad we are requesting. + * @param {number|undefined} height The new height for the ad we are requesting. * @param {boolean=} hasOverflow Whether the ad handles its own overflow ele + * @return {Promise} Signify the success/failure of the request. */ requestResize(width, height, hasOverflow) { + const requestId = this.nextResizeRequestId_++; this.client_.sendMessage( MessageType.EMBED_SIZE, dict({ + 'id': requestId, 'width': width, 'height': height, 'hasOverflow': hasOverflow, }) ); + const deferred = new Deferred(); + this.resizeIdToDeferred_[requestId] = deferred; + return deferred.promise; + } + + /** + * 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.EMBED_SIZE_DENIED, (data) => { + const id = data['id']; + if (id !== undefined) { + this.resizeIdToDeferred_[id].reject('Resizing is denied'); + delete this.resizeIdToDeferred_[id]; + } + }); + } + + /** + * @param {string} endpoint Method being called + * @private + */ + sendDeprecationNotice_(endpoint) { + this.client_.sendMessage( + MessageType.USER_ERROR_IN_IFRAME, + dict({ + 'message': `${endpoint} is deprecated`, + 'expected': true, + }) + ); } /** @@ -233,6 +285,7 @@ export class AbstractAmpContext { this.client_.registerCallback(MessageType.EMBED_SIZE_CHANGED, (obj) => { callback(obj['requestedHeight'], obj['requestedWidth']); }); + this.sendDeprecationNotice_('onResizeSuccess'); } /** @@ -246,6 +299,14 @@ export class AbstractAmpContext { this.client_.registerCallback(MessageType.EMBED_SIZE_DENIED, (obj) => { callback(obj['requestedHeight'], obj['requestedWidth']); }); + this.sendDeprecationNotice_('onResizeDenied'); + } + + /** + * Make the ad interactive. + */ + signalInteractive() { + this.client_.sendMessage(MessageType.SIGNAL_INTERACTIVE); } /** @@ -297,6 +358,7 @@ export class AbstractAmpContext { this.hidden = context.hidden; this.initialConsentState = context.initialConsentState; this.initialConsentValue = context.initialConsentValue; + this.initialConsentMetadata = context.initialConsentMetadata; this.initialLayoutRect = context.initialLayoutRect; this.initialIntersection = context.initialIntersection; this.location = parseUrlDeprecated(context.location.href); diff --git a/3p/beopinion.js b/3p/beopinion.js index 83da5b7144ae3..c5b255dc205fd 100644 --- a/3p/beopinion.js +++ b/3p/beopinion.js @@ -88,9 +88,6 @@ function createContainer(global, data) { function getBeOpinionAsyncInit(global, accountId) { const {context} = global; return function () { - context.onResizeDenied(function (requestedHeight, requestedWidth) { - context.requestResize(requestedWidth, requestedHeight); - }); global.BeOpinionSDK.init({ account: accountId, onContentReceive: function (hasContent) { @@ -103,10 +100,14 @@ function getBeOpinionAsyncInit(global, accountId) { onHeightChange: function (newHeight) { const c = global.document.getElementById('c'); const boundingClientRect = c./*REVIEW*/ getBoundingClientRect(); - context.requestResize(boundingClientRect.width, newHeight); + context + .requestResize(boundingClientRect.width, newHeight) + .catch(function () { + context.requestResize(boundingClientRect.width, newHeight); + }); }, }); - global.BeOpinionSDK['watch'](); // global.BeOpinionSDK.watch() fails 'gulp check-types' validation + global.BeOpinionSDK['watch'](); // global.BeOpinionSDK.watch() fails 'amp check-types' validation }; } diff --git a/3p/bodymovinanimation.js b/3p/bodymovinanimation.js index 21e3e538fadda..bd6e95b012b23 100644 --- a/3p/bodymovinanimation.js +++ b/3p/bodymovinanimation.js @@ -14,12 +14,21 @@ * limitations under the License. */ -import {dict} from '../src/utils/object'; +import {dict} from '../src/core/types/object'; import {getData} from '../src/event-helper'; import {loadScript} from './3p'; -import {parseJson} from '../src/json'; +import {parseJson} from '../src/core/types/object/json'; import {setStyles} from '../src/style'; +const libSourceUrl = dict({ + '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. * @param {!Window} global @@ -34,10 +43,7 @@ let animationHandler; * @param {!Function} cb */ function getBodymovinAnimationSdk(global, renderer, cb) { - const scriptToLoad = - renderer === 'svg' - ? 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin_light.min.js' - : 'https://cdnjs.cloudflare.com/ajax/libs/bodymovin/4.13.0/bodymovin.min.js'; + const scriptToLoad = libSourceUrl[renderer] ?? libSourceUrl['svg']; loadScript(global, scriptToLoad, function () { cb(global.bodymovin); }); diff --git a/3p/embedly.js b/3p/embedly.js index 9824d601793db..757cabf0da024 100644 --- a/3p/embedly.js +++ b/3p/embedly.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {hasOwn} from '../src/utils/object'; +import {hasOwn} from '../src/core/types/object'; import {loadScript} from './3p'; import {setStyle} from '../src/style'; diff --git a/3p/facebook.js b/3p/facebook.js index 834c16a011ab1..b60d30e3f5215 100644 --- a/3p/facebook.js +++ b/3p/facebook.js @@ -14,11 +14,26 @@ * limitations under the License. */ -import {dashToUnderline} from '../src/string'; -import {dict} from '../src/utils/object'; +import {dashToUnderline} from '../src/core/types/string'; +import {devAssert} from '../src/log'; +import {dict} from '../src/core/types/object'; +import {isEnumValue} from '../src/core/types/enum'; import {loadScript} from './3p'; import {setStyle} from '../src/style'; -import {userAssert} from '../src/log'; + +/** @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', + LIKE: 'like', + PAGE: 'page', + POST: 'post', + VIDEO: 'video', +}; /** * Produces the Facebook SDK object for the passed in callback. @@ -120,32 +135,6 @@ function getDefaultEmbedAs(href) { return href.match(/\/videos\/\d+\/?$/) ? 'video' : 'post'; } -/** - * Create DOM element for the Facebook embedded content plugin. - * @param {!Window} global - * @param {!Object} data The element data - * @return {!Element} div - */ -function getEmbedContainer(global, data) { - const embedAs = data.embedAs || getDefaultEmbedAs(data.href); - - userAssert( - ['post', 'video', 'comment'].indexOf(embedAs) !== -1, - 'Attribute data-embed-as for value is wrong, should be' + - ' "post", "video" or "comment" but was: %s', - embedAs - ); - - switch (embedAs) { - case 'comment': - return getCommentContainer(global, data); - case 'video': - return getVideoContainer(global, data); - default: - return getPostContainer(global, data); - } -} - /** * Create DOM element for the Facebook embedded page plugin. * Reference: https://developers.facebook.com/docs/plugins/page-plugin @@ -207,23 +196,40 @@ function getLikeContainer(global, data) { } /** + * Create DOM element for the Facebook embedded content plugin. * @param {!Window} global - * @param {!Object} data + * @param {!Object} data The element data + * @param {string} embedAs + * @return {!Element} div */ -export function facebook(global, data) { - const extension = global.context.tagName; - let container; - - if (extension === 'AMP-FACEBOOK-PAGE') { - container = getPageContainer(global, data); - } else if (extension === 'AMP-FACEBOOK-LIKE') { - container = getLikeContainer(global, data); - } else if (extension === 'AMP-FACEBOOK-COMMENTS') { - container = getCommentsContainer(global, data); - } /*AMP-FACEBOOK */ else { - container = getEmbedContainer(global, data); +function getEmbedContainer(global, data, embedAs) { + devAssert(isEnumValue(FacebookEmbedType, embedAs)); + switch (embedAs) { + case FacebookEmbedType.PAGE: + return getPageContainer(global, data); + case FacebookEmbedType.LIKE: + 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: + return getPostContainer(global, data); } +} +/** + * @param {!Window} global + * @param {!Object} data + */ +export function facebook(global, data) { + const container = getEmbedContainer( + global, + data, + data.embedAs || getDefaultEmbedAs(data.href) + ); global.document.getElementById('c').appendChild(container); getFacebookSdk( diff --git a/3p/frame-metadata.js b/3p/frame-metadata.js index c1ba8732b3c75..7b6f0a62cf66d 100644 --- a/3p/frame-metadata.js +++ b/3p/frame-metadata.js @@ -15,10 +15,10 @@ */ import {dev} from '../src/log'; -import {dict} from '../src/utils/object.js'; +import {dict} from '../src/core/types/object'; import {getMode} from '../src/mode'; -import {once} from '../src/utils/function.js'; -import {parseJson} from '../src/json'; +import {once} from '../src/core/types/function'; +import {parseJson} from '../src/core/types/object/json'; import {parseUrlDeprecated} from '../src/url'; /** diff --git a/3p/frame.max.html b/3p/frame.max.html index 19d77478edf5d..85783273e917b 100644 --- a/3p/frame.max.html +++ b/3p/frame.max.html @@ -2,7 +2,13 @@ - +
diff --git a/3p/iframe-messaging-client.js b/3p/iframe-messaging-client.js index d81aa23b59767..89db922e8e0ac 100644 --- a/3p/iframe-messaging-client.js +++ b/3p/iframe-messaging-client.js @@ -19,9 +19,9 @@ import { listen, serializeMessage, } from '../src/3p-frame-messaging'; -import {Observable} from '../src/observable'; +import {Observable} from '../src/core/data-structures/observable'; import {dev} from '../src/log'; -import {dict, map} from '../src/utils/object'; +import {dict, map} from '../src/core/types/object'; import {getData} from '../src/event-helper'; import {getMode} from '../src/mode'; diff --git a/3p/iframe-transport-client-lib.js b/3p/iframe-transport-client-lib.js index 7a635e4a717b9..c43c4d7f8f5e5 100644 --- a/3p/iframe-transport-client-lib.js +++ b/3p/iframe-transport-client-lib.js @@ -15,7 +15,7 @@ */ // src/polyfills.js must be the first import. -import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 +import './polyfills'; import {IframeTransportClient} from './iframe-transport-client.js'; import {initLogConstructor, setReportError} from '../src/log'; diff --git a/3p/iframe-transport-client.js b/3p/iframe-transport-client.js index c45b9d588b21e..c36f64615bbca 100644 --- a/3p/iframe-transport-client.js +++ b/3p/iframe-transport-client.js @@ -17,7 +17,7 @@ import {IframeMessagingClient} from './iframe-messaging-client'; import {MessageType} from '../src/3p-frame-messaging'; import {dev, devAssert, user, userAssert} from '../src/log'; -import {tryParseJson} from '../src/json'; +import {tryParseJson} from '../src/core/types/object/json'; /** @private @const {string} */ const TAG_ = 'iframe-transport-client'; diff --git a/3p/integration-lib.js b/3p/integration-lib.js new file mode 100644 index 0000000000000..9f5aac71463cd --- /dev/null +++ b/3p/integration-lib.js @@ -0,0 +1,369 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {IntegrationAmpContext} from './ampcontext-integration'; +import {dict} from '../src/core/types/object'; +import {endsWith} from '../src/core/types/string'; +import {getAmpConfig, getEmbedType, getLocation} from './frame-metadata'; +import {getSourceUrl, isProxyOrigin, parseUrlDeprecated} from '../src/url'; +import { + initLogConstructor, + isUserErrorMessage, + setReportError, + userAssert, +} from '../src/log'; +import {installEmbedStateListener, manageWin} from './environment'; +import {internalRuntimeVersion} from '../src/internal-version'; +import {parseJson} from '../src/core/types/object/json'; +import {run, setExperimentToggles} from './3p'; +import {urls} from '../src/config'; + +/** + * Whether the embed type may be used with amp-embed tag. + * @const {!Object} + */ +const AMP_EMBED_ALLOWED = { + _ping_: true, + '1wo': true, + '24smi': true, + adsloom: true, + adstyle: true, + bringhub: true, + dable: true, + engageya: true, + epeex: true, + firstimpression: true, + forkmedia: true, + glomex: true, + idealmedia: true, + insticator: true, + jubna: true, + kuadio: true, + 'mantis-recommend': true, + mediaad: true, + mgid: true, + miximedia: true, + mywidget: true, + nativery: true, + lentainform: true, + opinary: true, + outbrain: true, + plista: true, + postquare: true, + ppstudio: true, + pubexchange: true, + pulse: true, + rbinfox: true, + rcmwidget: true, + readmo: true, + recreativ: true, + runative: true, + smartclip: true, + smi2: true, + speakol: true, + strossle: true, + svknative: true, + taboola: true, + temedya: true, + vlyby: true, + whopainfeed: true, + yahoofedads: true, + yahoonativeads: true, + yektanet: true, + zen: true, + zergnet: true, +}; + +// For backward compat, we always allow these types without the iframe +// opting in. +const defaultAllowedTypesInCustomFrame = [ + // Entries must be reasonably safe and not allow overriding the injected + // JS URL. + // Each custom iframe can override this through the second argument to + // draw3p. See amp-ad docs. + 'facebook', + 'twitter', + 'doubleclick', + 'yieldbot', + '_ping_', +]; + +/** + * Initialize 3p frame. + * @param {!Window} win + */ +export function init(win) { + initLogConstructor(); + const config = getAmpConfig(); + + // Overriding to short-circuit src/mode#getMode() + win.__AMP_MODE = config.mode; + + setReportError(console.error.bind(console)); + + setExperimentToggles(config.experimentToggles); +} + +/** + * Visible for testing. + * Draws a 3p embed to the window. Expects the data to include the 3p type. + * @param {!Window} win + * @param {!Object} data + * @param {function(!Object, function(!Object))|undefined} configCallback + * Optional callback that allows user code to manipulate the incoming + * configuration. See + * https://github.com/ampproject/amphtml/issues/1210 for some context + * on this. + */ +export function draw3pInternal(win, data, configCallback) { + const type = data['type']; + + userAssert( + isTagNameAllowed(type, win.context.tagName), + 'Embed type %s not allowed with tag %s', + type, + win.context.tagName + ); + if (configCallback) { + configCallback(data, (data) => { + userAssert(data, 'Expected configuration to be passed as first argument'); + run(type, win, data); + }); + } else { + run(type, win, data); + } +} + +/** + * Draws an embed, optionally synchronously, to the DOM. + * @param {function(!Object, function(!Object))} opt_configCallback If provided + * will be invoked with two arguments: + * 1. The configuration parameters supplied to this embed. + * 2. A callback that MUST be called for rendering to proceed. It takes + * no arguments. Configuration is expected to be modified in-place. + * @param {!Array=} opt_allowed3pTypes List of advertising network + * types you expect. + * @param {!Array=} opt_allowedEmbeddingOrigins List of domain suffixes + * that are allowed to embed this frame. + */ +export function draw3p( + opt_configCallback, + opt_allowed3pTypes, + opt_allowedEmbeddingOrigins +) { + try { + const location = getLocation(); + + ensureFramed(window); + validateParentOrigin(window, location); + validateAllowedTypes(window, getEmbedType(), opt_allowed3pTypes); + if (opt_allowedEmbeddingOrigins) { + validateAllowedEmbeddingOrigins(window, opt_allowedEmbeddingOrigins); + } + window.context = new IntegrationAmpContext(window); + manageWin(window); + installEmbedStateListener(); + + // Ugly type annotation is due to Event.prototype.data being denylisted + // and the compiler not being able to discern otherwise + // TODO(alanorozco): Do this more elegantly once old impl is cleaned up. + draw3pInternal( + window, + /** @type {!IntegrationAmpContext} */ (window.context).data || {}, + opt_configCallback + ); + + window.context.bootstrapLoaded(); + } catch (e) { + if (window.context && window.context.report3pError) { + // window.context has initiated yet + if (e.message && isUserErrorMessage(e.message)) { + // report user error to parent window + window.context.report3pError(e); + } + } + + const c = window.context || {mode: {test: false}}; + if (!c.mode.test) { + lightweightErrorReport(e, c.canary); + throw e; + } + } +} + +/** + * Throws if the current frame's parent origin is not equal to + * the claimed origin. + * Only check for browsers that support ancestorOrigins + * @param {!Window} window + * @param {!Location} parentLocation + * @visibleForTesting + */ +export function validateParentOrigin(window, parentLocation) { + const ancestors = window.location.ancestorOrigins; + // Currently only webkit and blink based browsers support + // ancestorOrigins. In that case we proceed but mark the origin + // as non-validated. + if (!ancestors || !ancestors.length) { + return; + } + userAssert( + ancestors[0] == parentLocation.origin, + 'Parent origin mismatch: %s, %s', + ancestors[0], + parentLocation.origin + ); +} + +/** + * Check that this iframe intended this particular ad type to run. + * @param {!Window} window + * @param {string} type 3p type + * @param {!Array|undefined} allowedTypes May be undefined. + * @visibleForTesting + */ +export function validateAllowedTypes(window, type, allowedTypes) { + const thirdPartyHost = parseUrlDeprecated(urls.thirdParty).hostname; + + // Everything allowed in default iframe. + if (window.location.hostname == thirdPartyHost) { + return; + } + if (urls.thirdPartyFrameRegex.test(window.location.hostname)) { + return; + } + if (window.location.hostname == 'ads.localhost') { + return; + } + if (defaultAllowedTypesInCustomFrame.indexOf(type) != -1) { + return; + } + userAssert( + allowedTypes && allowedTypes.indexOf(type) != -1, + '3p type for custom iframe not allowed: %s', + type + ); +} + +/** + * Check that parent host name was allowed. + * @param {!Window} window + * @param {!Array} allowedHostnames Suffixes of allowed host names. + * @visibleForTesting + */ +export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { + if (!window.document.referrer) { + throw new Error('Referrer expected: ' + window.location.href); + } + const ancestors = window.location.ancestorOrigins; + // We prefer the unforgable ancestorOrigins, but referrer is better than + // nothing. + const ancestor = ancestors ? ancestors[0] : window.document.referrer; + let {hostname} = parseUrlDeprecated(ancestor); + if (isProxyOrigin(ancestor)) { + // If we are on the cache domain, parse the source hostname from + // the referrer. The referrer is used because it should be + // trustable. + hostname = parseUrlDeprecated( + getSourceUrl(window.document.referrer) + ).hostname; + } + for (let i = 0; i < allowedHostnames.length; i++) { + // Either the hostname is allowed + if (allowedHostnames[i] == hostname) { + return; + } + // Or it ends in .$hostname (aka is a sub domain of an allowed domain. + if (endsWith(hostname, '.' + allowedHostnames[i])) { + return; + } + } + throw new Error( + 'Invalid embedding hostname: ' + hostname + ' not in ' + allowedHostnames + ); +} + +/** + * Throws if this window is a top level window. + * @param {!Window} window + * @visibleForTesting + */ +export function ensureFramed(window) { + if (window == window.parent) { + throw new Error('Must be framed: ' + window.location.href); + } +} + +/** + * Expects the fragment to contain JSON. + * @param {string} fragment Value of location.fragment + * @return {?JsonObject} + * @visibleForTesting + */ +export function parseFragment(fragment) { + try { + let json = fragment.substr(1); + // Some browser, notably Firefox produce an encoded version of the fragment + // while most don't. Since we know how the string should start, this is easy + // to detect. + if (json.startsWith('{%22')) { + json = decodeURIComponent(json); + } + return /** @type {!JsonObject} */ (json ? parseJson(json) : dict()); + } catch (err) { + return null; + } +} + +/** + * Not all types of embeds are allowed to be used with all tag names on the + * AMP side. This function checks whether the current usage is permissible. + * @param {string} type + * @param {string|undefined} tagName The tagName that was used to embed this + * 3p-frame. + * @return {boolean} + */ +export function isTagNameAllowed(type, tagName) { + if (tagName == 'AMP-EMBED') { + return !!AMP_EMBED_ALLOWED[type]; + } + return true; +} + +/** + * Reports an error to the server. Must only be called once per page. + * Not for use in event handlers. + * + * We don't use the default error in error.js handler because it has + * too many deps for this small JS binary. + * + * @param {!Error} e + * @param {boolean} isCanary + */ +function lightweightErrorReport(e, isCanary) { + new Image().src = + urls.errorReporting + + '?3p=1&v=' + + encodeURIComponent(internalRuntimeVersion()) + + '&m=' + + encodeURIComponent(e.message) + + '&ca=' + + (isCanary ? 1 : 0) + + '&r=' + + encodeURIComponent(document.referrer) + + '&s=' + + encodeURIComponent(e.stack || ''); +} diff --git a/3p/integration.js b/3p/integration.js index f295db37a3237..2cd949dfd4486 100644 --- a/3p/integration.js +++ b/3p/integration.js @@ -23,26 +23,11 @@ */ // src/polyfills.js must be the first import. -import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 +import './polyfills'; -import {IntegrationAmpContext} from './ampcontext-integration'; -import {dict} from '../src/utils/object.js'; -import {endsWith} from '../src/string'; -import {getAmpConfig, getEmbedType, getLocation} from './frame-metadata'; +import {draw3p, init} from './integration-lib'; import {getMode} from '../src/mode'; -import {getSourceUrl, isProxyOrigin, parseUrlDeprecated} from '../src/url'; -import { - initLogConstructor, - isUserErrorMessage, - setReportError, - userAssert, -} from '../src/log'; -import {installEmbedStateListener, manageWin} from './environment'; -import {internalRuntimeVersion} from '../src/internal-version'; -import {parseJson} from '../src/json'; -import {register, run, setExperimentToggles} from './3p'; -import {startsWith} from '../src/string.js'; -import {urls} from '../src/config'; +import {register} from './3p'; // 3P - please keep in alphabetic order import {beopinion} from './beopinion'; @@ -57,301 +42,276 @@ import {twitter} from './twitter'; import {viqeoplayer} from './viqeoplayer'; import {yotpo} from './yotpo'; -import {_ping_} from '../ads/_ping_'; +import {_ping_} from '../ads/vendors/_ping_'; // 3P Ad Networks - please keep in alphabetic order -import {_1wo} from '../ads/1wo'; -import {_24smi} from '../ads/24smi'; -import {a8} from '../ads/a8'; -import {a9} from '../ads/a9'; -import {accesstrade} from '../ads/accesstrade'; -import {adagio} from '../ads/adagio'; -import {adblade, industrybrains} from '../ads/adblade'; -import {adbutler} from '../ads/adbutler'; -import {adform} from '../ads/adform'; -import {adfox} from '../ads/adfox'; -import {adgeneration} from '../ads/adgeneration'; -import {adglare} from '../ads/adglare'; -import {adhese} from '../ads/adhese'; -import {adincube} from '../ads/adincube'; -import {adition} from '../ads/adition'; -import {adman} from '../ads/adman'; -import {admanmedia} from '../ads/admanmedia'; -import {admixer} from '../ads/admixer'; -import {adocean} from '../ads/adocean'; -import {adop} from '../ads/adop'; -import {adpicker} from '../ads/adpicker'; -import {adplugg} from '../ads/adplugg'; -import {adpon} from '../ads/adpon'; -import {adreactor} from '../ads/adreactor'; -import {adsensor} from '../ads/adsensor'; -import {adservsolutions} from '../ads/adservsolutions'; -import {adsloom} from '../ads/adsloom'; -import {adsnative} from '../ads/adsnative'; -import {adspeed} from '../ads/adspeed'; -import {adspirit} from '../ads/adspirit'; -import {adstir} from '../ads/adstir'; -import {adstyle} from '../ads/adstyle'; -import {adtech} from '../ads/adtech'; -import {adthrive} from '../ads/adthrive'; -import {adunity} from '../ads/adunity'; -import {aduptech} from '../ads/aduptech'; -import {adventive} from '../ads/adventive'; -import {adverline} from '../ads/adverline'; -import {adverticum} from '../ads/adverticum'; -import {advertserve} from '../ads/advertserve'; -import {adyoulike} from '../ads/adyoulike'; -import {affiliateb} from '../ads/affiliateb'; -import {aja} from '../ads/aja'; -import {amoad} from '../ads/amoad'; -import {aniview} from '../ads/aniview'; -import {anyclip} from '../ads/anyclip'; -import {appnexus} from '../ads/appnexus'; -import {appvador} from '../ads/appvador'; -import {atomx} from '../ads/atomx'; -import {baidu} from '../ads/baidu'; -import {beaverads} from '../ads/beaverads'; -import {bidtellect} from '../ads/bidtellect'; -import {blade} from '../ads/blade'; -import {brainy} from '../ads/brainy'; -import {bringhub} from '../ads/bringhub'; -import {broadstreetads} from '../ads/broadstreetads'; -import {byplay} from '../ads/byplay'; -import {caajainfeed} from '../ads/caajainfeed'; -import {capirs} from '../ads/capirs'; -import {caprofitx} from '../ads/caprofitx'; -import {cedato} from '../ads/cedato'; -import {chargeads, nws} from '../ads/nws'; -import {colombia} from '../ads/colombia'; -import {conative} from '../ads/conative'; -import {connatix} from '../ads/connatix'; -import {contentad} from '../ads/contentad'; -import {criteo} from '../ads/criteo'; -import {csa} from '../ads/google/csa'; -import {dable} from '../ads/dable'; -import {directadvert} from '../ads/directadvert'; -import {distroscale} from '../ads/distroscale'; -import {dotandads} from '../ads/dotandads'; -import {dynad} from '../ads/dynad'; -import {eadv} from '../ads/eadv'; -import {eas} from '../ads/eas'; -import {empower} from '../ads/empower'; -import {engageya} from '../ads/engageya'; -import {epeex} from '../ads/epeex'; -import {eplanning} from '../ads/eplanning'; -import {ezoic} from '../ads/ezoic'; -import {f1e} from '../ads/f1e'; -import {f1h} from '../ads/f1h'; -import {felmat} from '../ads/felmat'; -import {flite} from '../ads/flite'; -import {fluct} from '../ads/fluct'; -import {forkmedia} from '../ads/forkmedia'; -import {freewheel} from '../ads/freewheel'; -import {fusion} from '../ads/fusion'; -import {genieessp} from '../ads/genieessp'; -import {giraff} from '../ads/giraff'; -import {gmossp} from '../ads/gmossp'; -import {gumgum} from '../ads/gumgum'; -import {holder} from '../ads/holder'; -import {ibillboard} from '../ads/ibillboard'; -import {idealmedia} from '../ads/idealmedia'; -import {imaVideo} from '../ads/google/imaVideo'; -import {imedia} from '../ads/imedia'; -import {imobile} from '../ads/imobile'; -import {imonomy} from '../ads/imonomy'; -import {improvedigital} from '../ads/improvedigital'; -import {inmobi} from '../ads/inmobi'; -import {innity} from '../ads/innity'; -import {insticator} from '../ads/insticator'; -import {invibes} from '../ads/invibes'; -import {iprom} from '../ads/iprom'; -import {ix} from '../ads/ix'; -import {jubna} from '../ads/jubna'; -import {kargo} from '../ads/kargo'; -import {kiosked} from '../ads/kiosked'; -import {kixer} from '../ads/kixer'; -import {kuadio} from '../ads/kuadio'; -import {lentainform} from '../ads/lentainform'; -import {ligatus} from '../ads/ligatus'; -import {lockerdome} from '../ads/lockerdome'; -import {logly} from '../ads/logly'; -import {loka} from '../ads/loka'; -import {mads} from '../ads/mads'; -import {mantisDisplay, mantisRecommend} from '../ads/mantis'; -import {marfeel} from '../ads/marfeel'; -import {mediaad} from '../ads/mediaad'; -import {medianet} from '../ads/medianet'; -import {mediavine} from '../ads/mediavine'; -import {medyanet} from '../ads/medyanet'; -import {meg} from '../ads/meg'; -import {mgid} from '../ads/mgid'; -import {microad} from '../ads/microad'; -import {miximedia} from '../ads/miximedia'; -import {mixpo} from '../ads/mixpo'; -import {monetizer101} from '../ads/monetizer101'; -import {mox} from '../ads/mox'; -import {my6sense} from '../ads/my6sense'; -import {mytarget} from '../ads/mytarget'; -import {mywidget} from '../ads/mywidget'; -import {nativeroll} from '../ads/nativeroll'; -import {nativery} from '../ads/nativery'; -import {nativo} from '../ads/nativo'; -import {navegg} from '../ads/navegg'; -import {nend} from '../ads/nend'; -import {netletix} from '../ads/netletix'; -import {noddus} from '../ads/noddus'; -import {nokta} from '../ads/nokta'; -import {onead} from '../ads/onead'; -import {onnetwork} from '../ads/onnetwork'; -import {openadstream} from '../ads/openadstream'; -import {openx} from '../ads/openx'; -import {opinary} from '../ads/opinary'; -import {outbrain} from '../ads/outbrain'; -import {pixels} from '../ads/pixels'; -import {plista} from '../ads/plista'; -import {polymorphicads} from '../ads/polymorphicads'; -import {popin} from '../ads/popin'; -import {postquare} from '../ads/postquare'; -import {ppstudio} from '../ads/ppstudio'; -import {pressboard} from '../ads/pressboard'; -import {promoteiq} from '../ads/promoteiq'; -import {pubexchange} from '../ads/pubexchange'; -import {pubguru} from '../ads/pubguru'; -import {pubmatic} from '../ads/pubmatic'; -import {pubmine} from '../ads/pubmine'; -import {puffnetwork} from '../ads/puffnetwork'; -import {pulsepoint} from '../ads/pulsepoint'; -import {purch} from '../ads/purch'; -import {quoraad} from '../ads/quoraad'; -import {rakutenunifiedads} from '../ads/rakutenunifiedads'; -import {rbinfox} from '../ads/rbinfox'; -import {readmo} from '../ads/readmo'; -import {realclick} from '../ads/realclick'; -import {recomad} from '../ads/recomad'; -import {relap} from '../ads/relap'; -import {relappro} from '../ads/relappro'; -import {revcontent} from '../ads/revcontent'; -import {revjet} from '../ads/revjet'; -import {rfp} from '../ads/rfp'; -import {rnetplus} from '../ads/rnetplus'; -import {rubicon} from '../ads/rubicon'; -import {runative} from '../ads/runative'; -import {sas} from '../ads/sas'; -import {seedingalliance} from '../ads/seedingalliance'; -import {sekindo} from '../ads/sekindo'; -import {sharethrough} from '../ads/sharethrough'; -import {shemedia} from '../ads/shemedia'; -import {sklik} from '../ads/sklik'; -import {slimcutmedia} from '../ads/slimcutmedia'; -import {smartadserver} from '../ads/smartadserver'; -import {smartclip} from '../ads/smartclip'; -import {smi2} from '../ads/smi2'; -import {smilewanted} from '../ads/smilewanted'; -import {sogouad} from '../ads/sogouad'; -import {sortable} from '../ads/sortable'; -import {sovrn} from '../ads/sovrn'; -import {speakol} from '../ads/speakol'; -import {spotx} from '../ads/spotx'; -import {springAds} from '../ads/springAds'; -import {ssp} from '../ads/ssp'; -import {strossle} from '../ads/strossle'; -import {sulvo} from '../ads/sulvo'; -import {sunmedia} from '../ads/sunmedia'; -import {svknative} from '../ads/svknative'; -import {swoop} from '../ads/swoop'; -import {taboola} from '../ads/taboola'; -import {tcsemotion} from '../ads/tcsemotion'; -import {teads} from '../ads/teads'; -import {temedya} from '../ads/temedya'; -import {torimochi} from '../ads/torimochi'; -import {tracdelight} from '../ads/tracdelight'; -import {triplelift} from '../ads/triplelift'; -import {trugaze} from '../ads/trugaze'; -import {uas} from '../ads/uas'; -import {ucfunnel} from '../ads/ucfunnel'; -import {unruly} from '../ads/unruly'; -import {uzou} from '../ads/uzou'; -import {valuecommerce} from '../ads/valuecommerce'; -import {vdoai} from '../ads/vdoai'; -import {videointelligence} from '../ads/videointelligence'; -import {videonow} from '../ads/videonow'; -import {viralize} from '../ads/viralize'; -import {vmfive} from '../ads/vmfive'; -import {webediads} from '../ads/webediads'; -import {weboramaDisplay} from '../ads/weborama'; -import {whopainfeed} from '../ads/whopainfeed'; -import {widespace} from '../ads/widespace'; -import {wisteria} from '../ads/wisteria'; -import {wpmedia} from '../ads/wpmedia'; -import {xlift} from '../ads/xlift'; -import {yahoo} from '../ads/yahoo'; -import {yahoofedads} from '../ads/yahoofedads'; -import {yahoojp} from '../ads/yahoojp'; -import {yahoonativeads} from '../ads/yahoonativeads'; -import {yandex} from '../ads/yandex'; -import {yengo} from '../ads/yengo'; -import {yieldbot} from '../ads/yieldbot'; -import {yieldmo} from '../ads/yieldmo'; -import {yieldone} from '../ads/yieldone'; -import {yieldpro} from '../ads/yieldpro'; -import {zedo} from '../ads/zedo'; -import {zen} from '../ads/zen'; -import {zergnet} from '../ads/zergnet'; -import {zucks} from '../ads/zucks'; - -/** - * Whether the embed type may be used with amp-embed tag. - * @const {!Object} - */ -const AMP_EMBED_ALLOWED = { - _ping_: true, - '1wo': true, - '24smi': true, - adsloom: true, - adstyle: true, - bringhub: true, - dable: true, - engageya: true, - epeex: true, - forkmedia: true, - idealmedia: true, - insticator: true, - jubna: true, - kuadio: true, - 'mantis-recommend': true, - mediaad: true, - mgid: true, - miximedia: true, - mywidget: true, - nativery: true, - lentainform: true, - opinary: true, - outbrain: true, - plista: true, - postquare: true, - ppstudio: true, - pubexchange: true, - rbinfox: true, - readmo: true, - runative: true, - smartclip: true, - smi2: true, - speakol: true, - strossle: true, - svknative: true, - taboola: true, - temedya: true, - whopainfeed: true, - yahoofedads: true, - yahoonativeads: true, - zen: true, - zergnet: true, -}; +import {_1wo} from '../ads/vendors/1wo'; +import {_24smi} from '../ads/vendors/24smi'; +import {a8} from '../ads/vendors/a8'; +import {a9} from '../ads/vendors/a9'; +import {accesstrade} from '../ads/vendors/accesstrade'; +import {adagio} from '../ads/vendors/adagio'; +import {adblade, industrybrains} from '../ads/vendors/adblade'; +import {adbutler} from '../ads/vendors/adbutler'; +import {adform} from '../ads/vendors/adform'; +import {adfox} from '../ads/vendors/adfox'; +import {adgeneration} from '../ads/vendors/adgeneration'; +import {adglare} from '../ads/vendors/adglare'; +import {adhese} from '../ads/vendors/adhese'; +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 {admixer} from '../ads/vendors/admixer'; +import {adnuntius} from '../ads/vendors/adnuntius'; +import {adocean} from '../ads/vendors/adocean'; +import {adop} from '../ads/vendors/adop'; +import {adpicker} from '../ads/vendors/adpicker'; +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 {adsensor} from '../ads/vendors/adsensor'; +import {adservsolutions} from '../ads/vendors/adservsolutions'; +import {adsloom} from '../ads/vendors/adsloom'; +import {adsnative} from '../ads/vendors/adsnative'; +import {adspeed} from '../ads/vendors/adspeed'; +import {adspirit} from '../ads/vendors/adspirit'; +import {adstir} from '../ads/vendors/adstir'; +import {adstyle} from '../ads/vendors/adstyle'; +import {adtech} from '../ads/vendors/adtech'; +import {adtelligent} from '../ads/vendors/adtelligent'; +import {adthrive} from '../ads/vendors/adthrive'; +import {adunity} from '../ads/vendors/adunity'; +import {aduptech} from '../ads/vendors/aduptech'; +import {adventive} from '../ads/vendors/adventive'; +import {adverline} from '../ads/vendors/adverline'; +import {adverticum} from '../ads/vendors/adverticum'; +import {advertserve} from '../ads/vendors/advertserve'; +import {adyoulike} from '../ads/vendors/adyoulike'; +import {affiliateb} from '../ads/vendors/affiliateb'; +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 {appnexus} from '../ads/vendors/appnexus'; +import {appvador} from '../ads/vendors/appvador'; +import {atomx} from '../ads/vendors/atomx'; +import {baidu} from '../ads/vendors/baidu'; +import {beaverads} from '../ads/vendors/beaverads'; +import {bidtellect} from '../ads/vendors/bidtellect'; +import {blade} from '../ads/vendors/blade'; +import {brainy} from '../ads/vendors/brainy'; +import {bringhub} from '../ads/vendors/bringhub'; +import {broadstreetads} from '../ads/vendors/broadstreetads'; +import {byplay} from '../ads/vendors/byplay'; +import {caajainfeed} from '../ads/vendors/caajainfeed'; +import {capirs} from '../ads/vendors/capirs'; +import {caprofitx} from '../ads/vendors/caprofitx'; +import {cedato} from '../ads/vendors/cedato'; +import {chargeads, nws} from '../ads/vendors/nws'; +import {colombia} from '../ads/vendors/colombia'; +import {conative} from '../ads/vendors/conative'; +import {connatix} from '../ads/vendors/connatix'; +import {contentad} from '../ads/vendors/contentad'; +import {criteo} from '../ads/vendors/criteo'; +import {csa} from '../ads/vendors/csa'; +import {dable} from '../ads/vendors/dable'; +import {digiteka} from '../ads/vendors/digiteka'; +import {directadvert} from '../ads/vendors/directadvert'; +import {distroscale} from '../ads/vendors/distroscale'; +import {dotandads} from '../ads/vendors/dotandads'; +import {dynad} from '../ads/vendors/dynad'; +import {eadv} from '../ads/vendors/eadv'; +import {empower} from '../ads/vendors/empower'; +import {engageya} from '../ads/vendors/engageya'; +import {epeex} from '../ads/vendors/epeex'; +import {eplanning} from '../ads/vendors/eplanning'; +import {ezoic} from '../ads/vendors/ezoic'; +import {f1e} from '../ads/vendors/f1e'; +import {f1h} from '../ads/vendors/f1h'; +import {fakeDelayed} from '../ads/vendors/_fakedelayed_'; +import {feedad} from '../ads/vendors/feedad'; +import {felmat} from '../ads/vendors/felmat'; +import {finative} from '../ads/vendors/finative'; +import {firstimpression} from '../ads/vendors/firstimpression'; +import {flite} from '../ads/vendors/flite'; +import {fluct} from '../ads/vendors/fluct'; +import {forkmedia} from '../ads/vendors/forkmedia'; +import {freewheel} from '../ads/vendors/freewheel'; +import {fusion} from '../ads/vendors/fusion'; +import {genieessp} from '../ads/vendors/genieessp'; +import {giraff} from '../ads/vendors/giraff'; +import {glomex} from '../ads/vendors/glomex'; +import {gmossp} from '../ads/vendors/gmossp'; +import {gumgum} from '../ads/vendors/gumgum'; +import {holder} from '../ads/vendors/holder'; +import {ibillboard} from '../ads/vendors/ibillboard'; +import {idealmedia} from '../ads/vendors/idealmedia'; +import {imaVideo} from '../ads/google/ima/ima-video'; +import {imedia} from '../ads/vendors/imedia'; +import {imobile} from '../ads/vendors/imobile'; +import {imonomy} from '../ads/vendors/imonomy'; +import {improvedigital} from '../ads/vendors/improvedigital'; +import {inmobi} from '../ads/vendors/inmobi'; +import {innity} from '../ads/vendors/innity'; +import {insticator} from '../ads/vendors/insticator'; +import {invibes} from '../ads/vendors/invibes'; +import {iprom} from '../ads/vendors/iprom'; +import {ix} from '../ads/vendors/ix'; +import {jubna} from '../ads/vendors/jubna'; +import {kargo} from '../ads/vendors/kargo'; +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'; +import {loka} from '../ads/vendors/loka'; +import {luckyads} from '../ads/vendors/luckyads'; +import {macaw} from '../ads/vendors/macaw'; +import {mads} from '../ads/vendors/mads'; +import {mantisDisplay, mantisRecommend} from '../ads/vendors/mantis'; +import {marfeel} from '../ads/vendors/marfeel'; +import {mediaad} from '../ads/vendors/mediaad'; +import {medianet} from '../ads/vendors/medianet'; +import {mediavine} from '../ads/vendors/mediavine'; +import {medyanet} from '../ads/vendors/medyanet'; +import {meg} from '../ads/vendors/meg'; +import {mgid} from '../ads/vendors/mgid'; +import {microad} from '../ads/vendors/microad'; +import {miximedia} from '../ads/vendors/miximedia'; +import {mixpo} from '../ads/vendors/mixpo'; +import {monetizer101} from '../ads/vendors/monetizer101'; +import {mox} from '../ads/vendors/mox'; +import {my6sense} from '../ads/vendors/my6sense'; +import {myfinance} from '../ads/vendors/myfinance'; +import {myoffrz} from '../ads/vendors/myoffrz'; +import {mytarget} from '../ads/vendors/mytarget'; +import {mywidget} from '../ads/vendors/mywidget'; +import {nativeroll} from '../ads/vendors/nativeroll'; +import {nativery} from '../ads/vendors/nativery'; +import {nativo} from '../ads/vendors/nativo'; +import {navegg} from '../ads/vendors/navegg'; +import {nend} from '../ads/vendors/nend'; +import {netletix} from '../ads/vendors/netletix'; +import {noddus} from '../ads/vendors/noddus'; +import {nokta} from '../ads/vendors/nokta'; +import {oblivki} from '../ads/vendors/oblivki'; +import {onead} from '../ads/vendors/onead'; +import {onnetwork} from '../ads/vendors/onnetwork'; +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 {pixels} from '../ads/vendors/pixels'; +import {playstream} from '../ads/vendors/playstream'; +import {plista} from '../ads/vendors/plista'; +import {polymorphicads} from '../ads/vendors/polymorphicads'; +import {popin} from '../ads/vendors/popin'; +import {postquare} from '../ads/vendors/postquare'; +import {ppstudio} from '../ads/vendors/ppstudio'; +import {pressboard} from '../ads/vendors/pressboard'; +import {promoteiq} from '../ads/vendors/promoteiq'; +import {pubexchange} from '../ads/vendors/pubexchange'; +import {pubguru} from '../ads/vendors/pubguru'; +import {pubmatic} from '../ads/vendors/pubmatic'; +import {pubmine} from '../ads/vendors/pubmine'; +import {puffnetwork} from '../ads/vendors/puffnetwork'; +import {pulse} from '../ads/vendors/pulse'; +import {pulsepoint} from '../ads/vendors/pulsepoint'; +import {purch} from '../ads/vendors/purch'; +import {quoraad} from '../ads/vendors/quoraad'; +import {rakutenunifiedads} from '../ads/vendors/rakutenunifiedads'; +import {rbinfox} from '../ads/vendors/rbinfox'; +import {rcmwidget} from '../ads/vendors/rcmwidget'; +import {readmo} from '../ads/vendors/readmo'; +import {realclick} from '../ads/vendors/realclick'; +import {recomad} from '../ads/vendors/recomad'; +import {recreativ} from '../ads/vendors/recreativ'; +import {relap} from '../ads/vendors/relap'; +import {relappro} from '../ads/vendors/relappro'; +import {remixd} from '../ads/vendors/remixd'; +import {revcontent} from '../ads/vendors/revcontent'; +import {revjet} from '../ads/vendors/revjet'; +import {rfp} from '../ads/vendors/rfp'; +import {rnetplus} from '../ads/vendors/rnetplus'; +import {rubicon} from '../ads/vendors/rubicon'; +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 {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'; +import {sogouad} from '../ads/vendors/sogouad'; +import {sona} from '../ads/vendors/sona'; +import {sortable} from '../ads/vendors/sortable'; +import {sovrn} from '../ads/vendors/sovrn'; +import {speakol} from '../ads/vendors/speakol'; +import {spotx} from '../ads/vendors/spotx'; +import {springAds} from '../ads/vendors/springAds'; +import {ssp} from '../ads/vendors/ssp'; +import {strossle} from '../ads/vendors/strossle'; +import {sulvo} from '../ads/vendors/sulvo'; +import {sunmedia} from '../ads/vendors/sunmedia'; +import {svknative} from '../ads/vendors/svknative'; +import {swoop} from '../ads/vendors/swoop'; +import {taboola} from '../ads/vendors/taboola'; +import {tcsemotion} from '../ads/vendors/tcsemotion'; +import {teads} from '../ads/vendors/teads'; +import {temedya} from '../ads/vendors/temedya'; +import {torimochi} from '../ads/vendors/torimochi'; +import {tracdelight} from '../ads/vendors/tracdelight'; +import {triplelift} from '../ads/vendors/triplelift'; +import {trugaze} from '../ads/vendors/trugaze'; +import {uas} from '../ads/vendors/uas'; +import {ucfunnel} from '../ads/vendors/ucfunnel'; +import {unruly} from '../ads/vendors/unruly'; +import {uzou} from '../ads/vendors/uzou'; +import {valuecommerce} from '../ads/vendors/valuecommerce'; +import {vdoai} from '../ads/vendors/vdoai'; +import {verizonmedia} from '../ads/vendors/verizonmedia'; +import {videointelligence} from '../ads/vendors/videointelligence'; +import {videonow} from '../ads/vendors/videonow'; +import {viralize} from '../ads/vendors/viralize'; +import {vlyby} from '../ads/vendors/vlyby'; +import {vmfive} from '../ads/vendors/vmfive'; +import {webediads} from '../ads/vendors/webediads'; +import {weboramaDisplay} from '../ads/vendors/weborama'; +import {whopainfeed} from '../ads/vendors/whopainfeed'; +import {widespace} from '../ads/vendors/widespace'; +import {wisteria} from '../ads/vendors/wisteria'; +import {wpmedia} from '../ads/vendors/wpmedia'; +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'; +import {yandex} from '../ads/vendors/yandex'; +import {yektanet} from '../ads/vendors/yektanet'; +import {yengo} from '../ads/vendors/yengo'; +import {yieldbot} from '../ads/vendors/yieldbot'; +import {yieldmo} from '../ads/vendors/yieldmo'; +import {yieldone} from '../ads/vendors/yieldone'; +import {yieldpro} from '../ads/vendors/yieldpro'; +import {zedo} from '../ads/vendors/zedo'; +import {zen} from '../ads/vendors/zen'; +import {zergnet} from '../ads/vendors/zergnet'; +import {zucks} from '../ads/vendors/zucks'; init(window); if (getMode().test || getMode().localDev) { register('_ping_', _ping_); + register('fake-delayed', fakeDelayed); } // Keep the list in alphabetic order @@ -374,11 +334,13 @@ register('adition', adition); register('adman', adman); register('admanmedia', admanmedia); register('admixer', admixer); +register('adnuntius', adnuntius); register('adocean', adocean); register('adop', adop); register('adpicker', adpicker); register('adplugg', adplugg); register('adpon', adpon); +register('adpushup', adpushup); register('adreactor', adreactor); register('adsensor', adsensor); register('adservsolutions', adservsolutions); @@ -389,6 +351,7 @@ register('adspirit', adspirit); register('adstir', adstir); register('adstyle', adstyle); register('adtech', adtech); +register('adtelligent', adtelligent); register('adthrive', adthrive); register('adunity', adunity); register('aduptech', aduptech); @@ -427,12 +390,12 @@ register('contentad', contentad); register('criteo', criteo); register('csa', csa); register('dable', dable); +register('digiteka', digiteka); register('directadvert', directadvert); register('distroscale', distroscale); register('dotandads', dotandads); register('dynad', dynad); register('eadv', eadv); -register('eas', eas); register('embedly', embedly); register('empower', empower); register('engageya', engageya); @@ -442,7 +405,10 @@ register('ezoic', ezoic); register('f1e', f1e); register('f1h', f1h); register('facebook', facebook); +register('feedad', feedad); register('felmat', felmat); +register('finative', finative); +register('firstimpression', firstimpression); register('flite', flite); register('fluct', fluct); register('forkmedia', forkmedia); @@ -451,6 +417,7 @@ register('fusion', fusion); register('genieessp', genieessp); register('giraff', giraff); register('github', github); +register('glomex', glomex); register('gmossp', gmossp); register('gumgum', gumgum); register('holder', holder); @@ -470,6 +437,7 @@ register('iprom', iprom); register('ix', ix); register('jubna', jubna); register('kargo', kargo); +register('ketshwa', ketshwa); register('kiosked', kiosked); register('kixer', kixer); register('kuadio', kuadio); @@ -478,6 +446,8 @@ register('ligatus', ligatus); register('lockerdome', lockerdome); register('logly', logly); register('loka', loka); +register('luckyads', luckyads); +register('macaw', macaw); register('mads', mads); register('mantis-display', mantisDisplay); register('mantis-recommend', mantisRecommend); @@ -495,6 +465,8 @@ register('mixpo', mixpo); register('monetizer101', monetizer101); register('mox', mox); register('my6sense', my6sense); +register('myfinance', myfinance); +register('myoffrz', myoffrz); register('mytarget', mytarget); register('mywidget', mywidget); register('nativeroll', nativeroll); @@ -506,6 +478,7 @@ register('netletix', netletix); register('noddus', noddus); register('nokta', nokta); register('nws', nws); +register('oblivki', oblivki); register('onead', onead); register('onnetwork', onnetwork); register('openadstream', openadstream); @@ -513,6 +486,7 @@ register('openx', openx); register('opinary', opinary); register('outbrain', outbrain); register('pixels', pixels); +register('playstream', playstream); register('plista', plista); register('polymorphicads', polymorphicads); register('popin', popin); @@ -525,17 +499,21 @@ register('pubguru', pubguru); register('pubmatic', pubmatic); register('pubmine', pubmine); register('puffnetwork', puffnetwork); +register('pulse', pulse); register('pulsepoint', pulsepoint); register('purch', purch); register('quoraad', quoraad); register('rakutenunifiedads', rakutenunifiedads); register('rbinfox', rbinfox); +register('rcmwidget', rcmwidget); register('readmo', readmo); register('realclick', realclick); register('reddit', reddit); register('recomad', recomad); +register('recreativ', recreativ); register('relap', relap); register('relappro', relappro); +register('remixd', remixd); register('revcontent', revcontent); register('revjet', revjet); register('rfp', rfp); @@ -555,8 +533,10 @@ register('smartclip', smartclip); register('smi2', smi2); register('smilewanted', smilewanted); register('sogouad', sogouad); +register('sona', sona); register('sortable', sortable); register('sovrn', sovrn); +register('speakol', speakol); register('spotx', spotx); register('springAds', springAds); register('strossle', strossle); @@ -579,10 +559,12 @@ register('unruly', unruly); register('uzou', uzou); register('valuecommerce', valuecommerce); register('vdoai', vdoai); +register('verizonmedia', verizonmedia); register('videointelligence', videointelligence); register('videonow', videonow); register('viqeoplayer', viqeoplayer); register('viralize', viralize); +register('vlyby', vlyby); register('vmfive', vmfive); register('webediads', webediads); register('weborama-display', weboramaDisplay); @@ -596,6 +578,7 @@ register('yahoofedads', yahoofedads); register('yahoojp', yahoojp); register('yahoonativeads', yahoonativeads); register('yandex', yandex); +register('yektanet', yektanet); register('yengo', yengo); register('yieldbot', yieldbot); register('yieldmo', yieldmo); @@ -606,284 +589,5 @@ register('zedo', zedo); register('zen', zen); register('zergnet', zergnet); register('zucks', zucks); -register('speakol', speakol); - -// For backward compat, we always allow these types without the iframe -// opting in. -const defaultAllowedTypesInCustomFrame = [ - // Entries must be reasonably safe and not allow overriding the injected - // JS URL. - // Each custom iframe can override this through the second argument to - // draw3p. See amp-ad docs. - 'facebook', - 'twitter', - 'doubleclick', - 'yieldbot', - '_ping_', -]; - -/** - * Initialize 3p frame. - * @param {!Window} win - */ -function init(win) { - initLogConstructor(); - const config = getAmpConfig(); - - // Overriding to short-circuit src/mode#getMode() - win.__AMP_MODE = config.mode; - - setReportError(console.error.bind(console)); - - setExperimentToggles(config.experimentToggles); -} - -/** - * Visible for testing. - * Draws a 3p embed to the window. Expects the data to include the 3p type. - * @param {!Window} win - * @param {!Object} data - * @param {function(!Object, function(!Object))|undefined} configCallback - * Optional callback that allows user code to manipulate the incoming - * configuration. See - * https://github.com/ampproject/amphtml/issues/1210 for some context - * on this. - */ -export function draw3p(win, data, configCallback) { - const type = data['type']; - - userAssert( - isTagNameAllowed(type, win.context.tagName), - 'Embed type %s not allowed with tag %s', - type, - win.context.tagName - ); - if (configCallback) { - configCallback(data, (data) => { - userAssert(data, 'Expected configuration to be passed as first argument'); - run(type, win, data); - }); - } else { - run(type, win, data); - } -} - -/** - * Draws an embed, optionally synchronously, to the DOM. - * @param {function(!Object, function(!Object))} opt_configCallback If provided - * will be invoked with two arguments: - * 1. The configuration parameters supplied to this embed. - * 2. A callback that MUST be called for rendering to proceed. It takes - * no arguments. Configuration is expected to be modified in-place. - * @param {!Array=} opt_allowed3pTypes List of advertising network - * types you expect. - * @param {!Array=} opt_allowedEmbeddingOrigins List of domain suffixes - * that are allowed to embed this frame. - */ -window.draw3p = function ( - opt_configCallback, - opt_allowed3pTypes, - opt_allowedEmbeddingOrigins -) { - try { - const location = getLocation(); - ensureFramed(window); - validateParentOrigin(window, location); - validateAllowedTypes(window, getEmbedType(), opt_allowed3pTypes); - if (opt_allowedEmbeddingOrigins) { - validateAllowedEmbeddingOrigins(window, opt_allowedEmbeddingOrigins); - } - window.context = new IntegrationAmpContext(window); - manageWin(window); - installEmbedStateListener(); - - // Ugly type annotation is due to Event.prototype.data being denylisted - // and the compiler not being able to discern otherwise - // TODO(alanorozco): Do this more elegantly once old impl is cleaned up. - draw3p( - window, - /** @type {!IntegrationAmpContext} */ (window.context).data || {}, - opt_configCallback - ); - - window.context.bootstrapLoaded(); - } catch (e) { - if (window.context && window.context.report3pError) { - // window.context has initiated yet - if (e.message && isUserErrorMessage(e.message)) { - // report user error to parent window - window.context.report3pError(e); - } - } - - const c = window.context || {mode: {test: false}}; - if (!c.mode.test) { - lightweightErrorReport(e, c.canary); - throw e; - } - } -}; - -/** - * Throws if the current frame's parent origin is not equal to - * the claimed origin. - * Only check for browsers that support ancestorOrigins - * @param {!Window} window - * @param {!Location} parentLocation - * @visibleForTesting - */ -export function validateParentOrigin(window, parentLocation) { - const ancestors = window.location.ancestorOrigins; - // Currently only webkit and blink based browsers support - // ancestorOrigins. In that case we proceed but mark the origin - // as non-validated. - if (!ancestors || !ancestors.length) { - return; - } - userAssert( - ancestors[0] == parentLocation.origin, - 'Parent origin mismatch: %s, %s', - ancestors[0], - parentLocation.origin - ); -} - -/** - * Check that this iframe intended this particular ad type to run. - * @param {!Window} window - * @param {string} type 3p type - * @param {!Array|undefined} allowedTypes May be undefined. - * @visibleForTesting - */ -export function validateAllowedTypes(window, type, allowedTypes) { - const thirdPartyHost = parseUrlDeprecated(urls.thirdParty).hostname; - - // Everything allowed in default iframe. - if (window.location.hostname == thirdPartyHost) { - return; - } - if (urls.thirdPartyFrameRegex.test(window.location.hostname)) { - return; - } - if (window.location.hostname == 'ads.localhost') { - return; - } - if (defaultAllowedTypesInCustomFrame.indexOf(type) != -1) { - return; - } - userAssert( - allowedTypes && allowedTypes.indexOf(type) != -1, - '3p type for custom iframe not allowed: %s', - type - ); -} - -/** - * Check that parent host name was allowed. - * @param {!Window} window - * @param {!Array} allowedHostnames Suffixes of allowed host names. - * @visibleForTesting - */ -export function validateAllowedEmbeddingOrigins(window, allowedHostnames) { - if (!window.document.referrer) { - throw new Error('Referrer expected: ' + window.location.href); - } - const ancestors = window.location.ancestorOrigins; - // We prefer the unforgable ancestorOrigins, but referrer is better than - // nothing. - const ancestor = ancestors ? ancestors[0] : window.document.referrer; - let {hostname} = parseUrlDeprecated(ancestor); - if (isProxyOrigin(ancestor)) { - // If we are on the cache domain, parse the source hostname from - // the referrer. The referrer is used because it should be - // trustable. - hostname = parseUrlDeprecated(getSourceUrl(window.document.referrer)) - .hostname; - } - for (let i = 0; i < allowedHostnames.length; i++) { - // Either the hostname is allowed - if (allowedHostnames[i] == hostname) { - return; - } - // Or it ends in .$hostname (aka is a sub domain of an allowed domain. - if (endsWith(hostname, '.' + allowedHostnames[i])) { - return; - } - } - throw new Error( - 'Invalid embedding hostname: ' + hostname + ' not in ' + allowedHostnames - ); -} - -/** - * Throws if this window is a top level window. - * @param {!Window} window - * @visibleForTesting - */ -export function ensureFramed(window) { - if (window == window.parent) { - throw new Error('Must be framed: ' + window.location.href); - } -} - -/** - * Expects the fragment to contain JSON. - * @param {string} fragment Value of location.fragment - * @return {?JsonObject} - * @visibleForTesting - */ -export function parseFragment(fragment) { - try { - let json = fragment.substr(1); - // Some browser, notably Firefox produce an encoded version of the fragment - // while most don't. Since we know how the string should start, this is easy - // to detect. - if (startsWith(json, '{%22')) { - json = decodeURIComponent(json); - } - return /** @type {!JsonObject} */ (json ? parseJson(json) : dict()); - } catch (err) { - return null; - } -} - -/** - * Not all types of embeds are allowed to be used with all tag names on the - * AMP side. This function checks whether the current usage is permissible. - * @param {string} type - * @param {string|undefined} tagName The tagName that was used to embed this - * 3p-frame. - * @return {boolean} - */ -export function isTagNameAllowed(type, tagName) { - if (tagName == 'AMP-EMBED') { - return !!AMP_EMBED_ALLOWED[type]; - } - return true; -} - -/** - * Reports an error to the server. Must only be called once per page. - * Not for use in event handlers. - * - * We don't use the default error in error.js handler because it has - * too many deps for this small JS binary. - * - * @param {!Error} e - * @param {boolean} isCanary - */ -function lightweightErrorReport(e, isCanary) { - new Image().src = - urls.errorReporting + - '?3p=1&v=' + - encodeURIComponent(internalRuntimeVersion()) + - '&m=' + - encodeURIComponent(e.message) + - '&ca=' + - (isCanary ? 1 : 0) + - '&r=' + - encodeURIComponent(document.referrer) + - '&s=' + - encodeURIComponent(e.stack || ''); -} +window.draw3p = draw3p; diff --git a/3p/mathml.js b/3p/mathml.js index 25e1c590cec75..8231a28e2a368 100644 --- a/3p/mathml.js +++ b/3p/mathml.js @@ -59,6 +59,12 @@ export function mathml(global, data) { global.document.body.appendChild(div); mathjax.Hub.Config({ showMathMenu: false, + // (#26082): From a11y perspective, user should not be able to tab to + // the math formula which has no functionality to interact with. This + // configuration removes the formula from the tab-index. + menuSettings: { + inTabOrder: false, + }, }); mathjax.Hub.Queue(function () { const rendered = document.getElementById('MathJax-Element-1-Frame'); diff --git a/3p/messaging.js b/3p/messaging.js index bf732de87b6ba..7f7f08a9334a6 100644 --- a/3p/messaging.js +++ b/3p/messaging.js @@ -15,7 +15,7 @@ */ import {getData} from '../src/event-helper'; -import {parseJson} from '../src/json'; +import {parseJson} from '../src/core/types/object/json'; /** * Send messages to parent frame. These should not contain user data. @@ -84,9 +84,9 @@ function startListening(win) { return; } // Parse JSON only once per message. - const data = /** @type {!JsonObject} */ (parseJson( - /**@type {string} */ (getData(event)).substr(4) - )); + const data = /** @type {!JsonObject} */ ( + parseJson(/**@type {string} */ (getData(event)).substr(4)) + ); if (win.context.sentinel && data['sentinel'] != win.context.sentinel) { return; } diff --git a/3p/polyfills.js b/3p/polyfills.js index 9d2545379b7af..7c24b359df219 100644 --- a/3p/polyfills.js +++ b/3p/polyfills.js @@ -22,7 +22,13 @@ import {install as installMathSign} from '../src/polyfills/math-sign'; import {install as installObjectAssign} from '../src/polyfills/object-assign'; import {install as installObjectValues} from '../src/polyfills/object-values'; +import {install as installPromise} from '../src/polyfills/promise'; +import {install as installStringStartsWith} from '../src/polyfills/string-starts-with'; -installMathSign(self); -installObjectAssign(self); -installObjectValues(self); +if (!IS_ESM) { + installMathSign(self); + installObjectAssign(self); + installObjectValues(self); + installPromise(self); + installStringStartsWith(self); +} diff --git a/3p/recaptcha.js b/3p/recaptcha.js index f172280336fee..41b77048db28b 100644 --- a/3p/recaptcha.js +++ b/3p/recaptcha.js @@ -15,7 +15,7 @@ */ // src/polyfills.js must be the first import. -import './polyfills'; // eslint-disable-line sort-imports-es6-autofix/sort-imports-es6 +import './polyfills'; import ampToolboxCacheUrl from '../third_party/amp-toolbox-cache-url/dist/amp-toolbox-cache-url.esm'; @@ -27,10 +27,10 @@ import { setReportError, user, } from '../src/log'; -import {dict, hasOwn} from '../src/utils/object'; +import {dict, hasOwn} from '../src/core/types/object'; import {isProxyOrigin, parseUrlDeprecated} from '../src/url'; import {loadScript} from './3p'; -import {parseJson} from '../src/json'; +import {parseJson} from '../src/core/types/object/json'; /** * @fileoverview diff --git a/3p/twitter.js b/3p/twitter.js index 7bc39f784e602..55eff6e4a2555 100644 --- a/3p/twitter.js +++ b/3p/twitter.js @@ -18,7 +18,6 @@ import {loadScript} from './3p'; import {setStyles} from '../src/style'; -import {startsWith} from '../src/string'; /** * Produces the Twitter API object for the passed in callback. If the current @@ -75,7 +74,7 @@ export function twitter(global, data) { } else if (data.timelineSourceType) { // Extract properties starting with 'timeline'. const timelineData = Object.keys(data) - .filter((prop) => startsWith(prop, 'timeline')) + .filter((prop) => prop.startsWith('timeline')) .reduce((newData, prop) => { newData[stripPrefixCamelCase(prop, 'timeline')] = data[prop]; return newData; diff --git a/3p/vendors/1wo.js b/3p/vendors/1wo.js new file mode 100644 index 0000000000000..927c9fd02cdb8 --- /dev/null +++ b/3p/vendors/1wo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {_1wo} from '../../ads/vendors/1wo'; + +init(window); +register('1wo', _1wo); + +window.draw3p = draw3p; diff --git a/3p/vendors/24smi.js b/3p/vendors/24smi.js new file mode 100644 index 0000000000000..714a83ee5ca3b --- /dev/null +++ b/3p/vendors/24smi.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {_24smi} from '../../ads/vendors/24smi'; + +init(window); +register('24smi', _24smi); + +window.draw3p = draw3p; diff --git a/3p/vendors/3d-gltf.js b/3p/vendors/3d-gltf.js new file mode 100644 index 0000000000000..248bf1fdd2998 --- /dev/null +++ b/3p/vendors/3d-gltf.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {gltfViewer} from '../3d-gltf/index'; + +init(window); +register('3d-gltf', gltfViewer); + +window.draw3p = draw3p; diff --git a/3p/vendors/_ping_.js b/3p/vendors/_ping_.js new file mode 100644 index 0000000000000..84c4af7c58a4a --- /dev/null +++ b/3p/vendors/_ping_.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {_ping_} from '../../ads/vendors/_ping_'; + +init(window); +register('_ping_', _ping_); + +window.draw3p = draw3p; diff --git a/3p/vendors/a8.js b/3p/vendors/a8.js new file mode 100644 index 0000000000000..725407dde89fe --- /dev/null +++ b/3p/vendors/a8.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {a8} from '../../ads/vendors/a8'; + +init(window); +register('a8', a8); + +window.draw3p = draw3p; diff --git a/3p/vendors/a9.js b/3p/vendors/a9.js new file mode 100644 index 0000000000000..f298ec03bb886 --- /dev/null +++ b/3p/vendors/a9.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {a9} from '../../ads/vendors/a9'; + +init(window); +register('a9', a9); + +window.draw3p = draw3p; diff --git a/3p/vendors/accesstrade.js b/3p/vendors/accesstrade.js new file mode 100644 index 0000000000000..38e7ea7305970 --- /dev/null +++ b/3p/vendors/accesstrade.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {accesstrade} from '../../ads/vendors/accesstrade'; + +init(window); +register('accesstrade', accesstrade); + +window.draw3p = draw3p; diff --git a/3p/vendors/adagio.js b/3p/vendors/adagio.js new file mode 100644 index 0000000000000..fcf020e40c4af --- /dev/null +++ b/3p/vendors/adagio.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adagio} from '../../ads/vendors/adagio'; + +init(window); +register('adagio', adagio); + +window.draw3p = draw3p; diff --git a/3p/vendors/adblade.js b/3p/vendors/adblade.js new file mode 100644 index 0000000000000..1e492b34abe82 --- /dev/null +++ b/3p/vendors/adblade.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adblade} from '../../ads/vendors/adblade'; + +init(window); +register('adblade', adblade); + +window.draw3p = draw3p; diff --git a/3p/vendors/adbutler.js b/3p/vendors/adbutler.js new file mode 100644 index 0000000000000..bcb46a88f845d --- /dev/null +++ b/3p/vendors/adbutler.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adbutler} from '../../ads/vendors/adbutler'; + +init(window); +register('adbutler', adbutler); + +window.draw3p = draw3p; diff --git a/3p/vendors/adform.js b/3p/vendors/adform.js new file mode 100644 index 0000000000000..f220df8819efa --- /dev/null +++ b/3p/vendors/adform.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adform} from '../../ads/vendors/adform'; + +init(window); +register('adform', adform); + +window.draw3p = draw3p; diff --git a/3p/vendors/adfox.js b/3p/vendors/adfox.js new file mode 100644 index 0000000000000..a0c918d9d899e --- /dev/null +++ b/3p/vendors/adfox.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adfox} from '../../ads/vendors/adfox'; + +init(window); +register('adfox', adfox); + +window.draw3p = draw3p; diff --git a/3p/vendors/adgeneration.js b/3p/vendors/adgeneration.js new file mode 100644 index 0000000000000..dfec4cfc1bc1e --- /dev/null +++ b/3p/vendors/adgeneration.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adgeneration} from '../../ads/vendors/adgeneration'; + +init(window); +register('adgeneration', adgeneration); + +window.draw3p = draw3p; diff --git a/3p/vendors/adglare.js b/3p/vendors/adglare.js new file mode 100644 index 0000000000000..1ffe44e528450 --- /dev/null +++ b/3p/vendors/adglare.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adglare} from '../../ads/vendors/adglare'; + +init(window); +register('adglare', adglare); + +window.draw3p = draw3p; diff --git a/3p/vendors/adhese.js b/3p/vendors/adhese.js new file mode 100644 index 0000000000000..261441a94f2ab --- /dev/null +++ b/3p/vendors/adhese.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adhese} from '../../ads/vendors/adhese'; + +init(window); +register('adhese', adhese); + +window.draw3p = draw3p; diff --git a/3p/vendors/adincube.js b/3p/vendors/adincube.js new file mode 100644 index 0000000000000..d40b15836fd3f --- /dev/null +++ b/3p/vendors/adincube.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adincube} from '../../ads/vendors/adincube'; + +init(window); +register('adincube', adincube); + +window.draw3p = draw3p; diff --git a/3p/vendors/adition.js b/3p/vendors/adition.js new file mode 100644 index 0000000000000..a7ad44eaeb10f --- /dev/null +++ b/3p/vendors/adition.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adition} from '../../ads/vendors/adition'; + +init(window); +register('adition', adition); + +window.draw3p = draw3p; diff --git a/3p/vendors/adman.js b/3p/vendors/adman.js new file mode 100644 index 0000000000000..43aa1567c0b0d --- /dev/null +++ b/3p/vendors/adman.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adman} from '../../ads/vendors/adman'; + +init(window); +register('adman', adman); + +window.draw3p = draw3p; diff --git a/3p/vendors/admanmedia.js b/3p/vendors/admanmedia.js new file mode 100644 index 0000000000000..dd02b1c1b4b1f --- /dev/null +++ b/3p/vendors/admanmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {admanmedia} from '../../ads/vendors/admanmedia'; + +init(window); +register('admanmedia', admanmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/admixer.js b/3p/vendors/admixer.js new file mode 100644 index 0000000000000..dc2ddf8ca827e --- /dev/null +++ b/3p/vendors/admixer.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {admixer} from '../../ads/vendors/admixer'; + +init(window); +register('admixer', admixer); + +window.draw3p = draw3p; diff --git a/3p/vendors/adnuntius.js b/3p/vendors/adnuntius.js new file mode 100644 index 0000000000000..f030d96fe8a86 --- /dev/null +++ b/3p/vendors/adnuntius.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adnuntius} from '../../ads/vendors/adnuntius'; + +init(window); +register('adnuntius', adnuntius); + +window.draw3p = draw3p; diff --git a/3p/vendors/adocean.js b/3p/vendors/adocean.js new file mode 100644 index 0000000000000..5109db2e3973f --- /dev/null +++ b/3p/vendors/adocean.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adocean} from '../../ads/vendors/adocean'; + +init(window); +register('adocean', adocean); + +window.draw3p = draw3p; diff --git a/3p/vendors/adop.js b/3p/vendors/adop.js new file mode 100644 index 0000000000000..203ff0cb70799 --- /dev/null +++ b/3p/vendors/adop.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adop} from '../../ads/vendors/adop'; + +init(window); +register('adop', adop); + +window.draw3p = draw3p; diff --git a/3p/vendors/adpicker.js b/3p/vendors/adpicker.js new file mode 100644 index 0000000000000..1acba0b228cc5 --- /dev/null +++ b/3p/vendors/adpicker.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adpicker} from '../../ads/vendors/adpicker'; + +init(window); +register('adpicker', adpicker); + +window.draw3p = draw3p; diff --git a/3p/vendors/adplugg.js b/3p/vendors/adplugg.js new file mode 100644 index 0000000000000..6255fc468dedf --- /dev/null +++ b/3p/vendors/adplugg.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adplugg} from '../../ads/vendors/adplugg'; + +init(window); +register('adplugg', adplugg); + +window.draw3p = draw3p; diff --git a/3p/vendors/adpon.js b/3p/vendors/adpon.js new file mode 100644 index 0000000000000..f85596c99f4eb --- /dev/null +++ b/3p/vendors/adpon.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adpon} from '../../ads/vendors/adpon'; + +init(window); +register('adpon', adpon); + +window.draw3p = draw3p; diff --git a/3p/vendors/adpushup.js b/3p/vendors/adpushup.js new file mode 100644 index 0000000000000..99bb2945011d6 --- /dev/null +++ b/3p/vendors/adpushup.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adpushup} from '../../ads/vendors/adpushup'; + +init(window); +register('adpushup', adpushup); + +window.draw3p = draw3p; diff --git a/3p/vendors/adreactor.js b/3p/vendors/adreactor.js new file mode 100644 index 0000000000000..5fb9a982eacab --- /dev/null +++ b/3p/vendors/adreactor.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adreactor} from '../../ads/vendors/adreactor'; + +init(window); +register('adreactor', adreactor); + +window.draw3p = draw3p; diff --git a/3p/vendors/adsensor.js b/3p/vendors/adsensor.js new file mode 100644 index 0000000000000..d717f3e2d35ff --- /dev/null +++ b/3p/vendors/adsensor.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adsensor} from '../../ads/vendors/adsensor'; + +init(window); +register('adsensor', adsensor); + +window.draw3p = draw3p; diff --git a/3p/vendors/adservsolutions.js b/3p/vendors/adservsolutions.js new file mode 100644 index 0000000000000..8563b1e0911f4 --- /dev/null +++ b/3p/vendors/adservsolutions.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adservsolutions} from '../../ads/vendors/adservsolutions'; + +init(window); +register('adservsolutions', adservsolutions); + +window.draw3p = draw3p; diff --git a/3p/vendors/adsloom.js b/3p/vendors/adsloom.js new file mode 100644 index 0000000000000..5ee7320636266 --- /dev/null +++ b/3p/vendors/adsloom.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adsloom} from '../../ads/vendors/adsloom'; + +init(window); +register('adsloom', adsloom); + +window.draw3p = draw3p; diff --git a/3p/vendors/adsnative.js b/3p/vendors/adsnative.js new file mode 100644 index 0000000000000..19e6b2724baf0 --- /dev/null +++ b/3p/vendors/adsnative.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adsnative} from '../../ads/vendors/adsnative'; + +init(window); +register('adsnative', adsnative); + +window.draw3p = draw3p; diff --git a/3p/vendors/adspeed.js b/3p/vendors/adspeed.js new file mode 100644 index 0000000000000..e289f9ff076c5 --- /dev/null +++ b/3p/vendors/adspeed.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adspeed} from '../../ads/vendors/adspeed'; + +init(window); +register('adspeed', adspeed); + +window.draw3p = draw3p; diff --git a/3p/vendors/adspirit.js b/3p/vendors/adspirit.js new file mode 100644 index 0000000000000..53037132ddb54 --- /dev/null +++ b/3p/vendors/adspirit.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adspirit} from '../../ads/vendors/adspirit'; + +init(window); +register('adspirit', adspirit); + +window.draw3p = draw3p; diff --git a/3p/vendors/adstir.js b/3p/vendors/adstir.js new file mode 100644 index 0000000000000..ec6cc67fb8365 --- /dev/null +++ b/3p/vendors/adstir.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adstir} from '../../ads/vendors/adstir'; + +init(window); +register('adstir', adstir); + +window.draw3p = draw3p; diff --git a/3p/vendors/adstyle.js b/3p/vendors/adstyle.js new file mode 100644 index 0000000000000..bb1e9c455eaa6 --- /dev/null +++ b/3p/vendors/adstyle.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adstyle} from '../../ads/vendors/adstyle'; + +init(window); +register('adstyle', adstyle); + +window.draw3p = draw3p; diff --git a/3p/vendors/adtech.js b/3p/vendors/adtech.js new file mode 100644 index 0000000000000..6cd78f318d6c7 --- /dev/null +++ b/3p/vendors/adtech.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adtech} from '../../ads/vendors/adtech'; + +init(window); +register('adtech', adtech); + +window.draw3p = draw3p; diff --git a/3p/vendors/adtelligent.js b/3p/vendors/adtelligent.js new file mode 100644 index 0000000000000..dd3b3eeb29cce --- /dev/null +++ b/3p/vendors/adtelligent.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adtelligent} from '../../ads/vendors/adtelligent'; + +init(window); +register('adtelligent', adtelligent); + +window.draw3p = draw3p; diff --git a/3p/vendors/adthrive.js b/3p/vendors/adthrive.js new file mode 100644 index 0000000000000..b6244d97f4595 --- /dev/null +++ b/3p/vendors/adthrive.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adthrive} from '../../ads/vendors/adthrive'; + +init(window); +register('adthrive', adthrive); + +window.draw3p = draw3p; diff --git a/3p/vendors/adunity.js b/3p/vendors/adunity.js new file mode 100644 index 0000000000000..a93c420abc77c --- /dev/null +++ b/3p/vendors/adunity.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adunity} from '../../ads/vendors/adunity'; + +init(window); +register('adunity', adunity); + +window.draw3p = draw3p; diff --git a/3p/vendors/aduptech.js b/3p/vendors/aduptech.js new file mode 100644 index 0000000000000..ed0919338a053 --- /dev/null +++ b/3p/vendors/aduptech.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {aduptech} from '../../ads/vendors/aduptech'; + +init(window); +register('aduptech', aduptech); + +window.draw3p = draw3p; diff --git a/3p/vendors/adventive.js b/3p/vendors/adventive.js new file mode 100644 index 0000000000000..96dec8dc3f7fe --- /dev/null +++ b/3p/vendors/adventive.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adventive} from '../../ads/vendors/adventive'; + +init(window); +register('adventive', adventive); + +window.draw3p = draw3p; diff --git a/3p/vendors/adverline.js b/3p/vendors/adverline.js new file mode 100644 index 0000000000000..427983bc3b9f7 --- /dev/null +++ b/3p/vendors/adverline.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adverline} from '../../ads/vendors/adverline'; + +init(window); +register('adverline', adverline); + +window.draw3p = draw3p; diff --git a/3p/vendors/adverticum.js b/3p/vendors/adverticum.js new file mode 100644 index 0000000000000..b47f8e1ce26e7 --- /dev/null +++ b/3p/vendors/adverticum.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adverticum} from '../../ads/vendors/adverticum'; + +init(window); +register('adverticum', adverticum); + +window.draw3p = draw3p; diff --git a/3p/vendors/advertserve.js b/3p/vendors/advertserve.js new file mode 100644 index 0000000000000..29094382d7999 --- /dev/null +++ b/3p/vendors/advertserve.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {advertserve} from '../../ads/vendors/advertserve'; + +init(window); +register('advertserve', advertserve); + +window.draw3p = draw3p; diff --git a/3p/vendors/adyoulike.js b/3p/vendors/adyoulike.js new file mode 100644 index 0000000000000..120951cb9948c --- /dev/null +++ b/3p/vendors/adyoulike.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {adyoulike} from '../../ads/vendors/adyoulike'; + +init(window); +register('adyoulike', adyoulike); + +window.draw3p = draw3p; diff --git a/3p/vendors/affiliateb.js b/3p/vendors/affiliateb.js new file mode 100644 index 0000000000000..80272857c4040 --- /dev/null +++ b/3p/vendors/affiliateb.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {affiliateb} from '../../ads/vendors/affiliateb'; + +init(window); +register('affiliateb', affiliateb); + +window.draw3p = draw3p; diff --git a/3p/vendors/aja.js b/3p/vendors/aja.js new file mode 100644 index 0000000000000..3c37e52fc8b09 --- /dev/null +++ b/3p/vendors/aja.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {aja} from '../../ads/vendors/aja'; + +init(window); +register('aja', aja); + +window.draw3p = draw3p; diff --git a/3p/vendors/amoad.js b/3p/vendors/amoad.js new file mode 100644 index 0000000000000..a1c58d4b823bc --- /dev/null +++ b/3p/vendors/amoad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {amoad} from '../../ads/vendors/amoad'; + +init(window); +register('amoad', amoad); + +window.draw3p = draw3p; diff --git a/3p/vendors/aniview.js b/3p/vendors/aniview.js new file mode 100644 index 0000000000000..5bbe2810a1e9e --- /dev/null +++ b/3p/vendors/aniview.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {aniview} from '../../ads/vendors/aniview'; + +init(window); +register('aniview', aniview); + +window.draw3p = draw3p; diff --git a/3p/vendors/anyclip.js b/3p/vendors/anyclip.js new file mode 100644 index 0000000000000..729d3b3c328bb --- /dev/null +++ b/3p/vendors/anyclip.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {anyclip} from '../../ads/vendors/anyclip'; + +init(window); +register('anyclip', anyclip); + +window.draw3p = draw3p; diff --git a/3p/vendors/appnexus.js b/3p/vendors/appnexus.js new file mode 100644 index 0000000000000..15f8f288c4b7a --- /dev/null +++ b/3p/vendors/appnexus.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {appnexus} from '../../ads/vendors/appnexus'; + +init(window); +register('appnexus', appnexus); + +window.draw3p = draw3p; diff --git a/3p/vendors/appvador.js b/3p/vendors/appvador.js new file mode 100644 index 0000000000000..65ef84e0606d2 --- /dev/null +++ b/3p/vendors/appvador.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {appvador} from '../../ads/vendors/appvador'; + +init(window); +register('appvador', appvador); + +window.draw3p = draw3p; diff --git a/3p/vendors/atomx.js b/3p/vendors/atomx.js new file mode 100644 index 0000000000000..5444e0a7dd47a --- /dev/null +++ b/3p/vendors/atomx.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {atomx} from '../../ads/vendors/atomx'; + +init(window); +register('atomx', atomx); + +window.draw3p = draw3p; diff --git a/3p/vendors/baidu.js b/3p/vendors/baidu.js new file mode 100644 index 0000000000000..11b13d3788bf4 --- /dev/null +++ b/3p/vendors/baidu.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {baidu} from '../../ads/vendors/baidu'; + +init(window); +register('baidu', baidu); + +window.draw3p = draw3p; diff --git a/3p/vendors/beaverads.js b/3p/vendors/beaverads.js new file mode 100644 index 0000000000000..8fc1adaec6ebf --- /dev/null +++ b/3p/vendors/beaverads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {beaverads} from '../../ads/vendors/beaverads'; + +init(window); +register('beaverads', beaverads); + +window.draw3p = draw3p; diff --git a/3p/vendors/beopinion.js b/3p/vendors/beopinion.js new file mode 100644 index 0000000000000..93a53519d1709 --- /dev/null +++ b/3p/vendors/beopinion.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {beopinion} from '../beopinion'; + +init(window); +register('beopinion', beopinion); + +window.draw3p = draw3p; diff --git a/3p/vendors/bidtellect.js b/3p/vendors/bidtellect.js new file mode 100644 index 0000000000000..9f6a111e29a9a --- /dev/null +++ b/3p/vendors/bidtellect.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {bidtellect} from '../../ads/vendors/bidtellect'; + +init(window); +register('bidtellect', bidtellect); + +window.draw3p = draw3p; diff --git a/3p/vendors/blade.js b/3p/vendors/blade.js new file mode 100644 index 0000000000000..6cf64ebd4c67b --- /dev/null +++ b/3p/vendors/blade.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {blade} from '../../ads/vendors/blade'; + +init(window); +register('blade', blade); + +window.draw3p = draw3p; diff --git a/3p/vendors/bodymovinanimation.js b/3p/vendors/bodymovinanimation.js new file mode 100644 index 0000000000000..cf29c82e8a229 --- /dev/null +++ b/3p/vendors/bodymovinanimation.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {bodymovinanimation} from '../bodymovinanimation'; + +init(window); +register('bodymovinanimation', bodymovinanimation); + +window.draw3p = draw3p; diff --git a/3p/vendors/brainy.js b/3p/vendors/brainy.js new file mode 100644 index 0000000000000..e538d511e8921 --- /dev/null +++ b/3p/vendors/brainy.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {brainy} from '../../ads/vendors/brainy'; + +init(window); +register('brainy', brainy); + +window.draw3p = draw3p; diff --git a/3p/vendors/bringhub.js b/3p/vendors/bringhub.js new file mode 100644 index 0000000000000..adc0b6c2f9378 --- /dev/null +++ b/3p/vendors/bringhub.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {bringhub} from '../../ads/vendors/bringhub'; + +init(window); +register('bringhub', bringhub); + +window.draw3p = draw3p; diff --git a/3p/vendors/broadstreetads.js b/3p/vendors/broadstreetads.js new file mode 100644 index 0000000000000..baaff4958b816 --- /dev/null +++ b/3p/vendors/broadstreetads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {broadstreetads} from '../../ads/vendors/broadstreetads'; + +init(window); +register('broadstreetads', broadstreetads); + +window.draw3p = draw3p; diff --git a/3p/vendors/byplay.js b/3p/vendors/byplay.js new file mode 100644 index 0000000000000..fb1d40d94d7d0 --- /dev/null +++ b/3p/vendors/byplay.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {byplay} from '../../ads/vendors/byplay'; + +init(window); +register('byplay', byplay); + +window.draw3p = draw3p; diff --git a/3p/vendors/caajainfeed.js b/3p/vendors/caajainfeed.js new file mode 100644 index 0000000000000..2cf5c0ead9079 --- /dev/null +++ b/3p/vendors/caajainfeed.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {caajainfeed} from '../../ads/vendors/caajainfeed'; + +init(window); +register('caajainfeed', caajainfeed); + +window.draw3p = draw3p; diff --git a/3p/vendors/capirs.js b/3p/vendors/capirs.js new file mode 100644 index 0000000000000..391e5706e6fb9 --- /dev/null +++ b/3p/vendors/capirs.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {capirs} from '../../ads/vendors/capirs'; + +init(window); +register('capirs', capirs); + +window.draw3p = draw3p; diff --git a/3p/vendors/caprofitx.js b/3p/vendors/caprofitx.js new file mode 100644 index 0000000000000..0063aff1db6d9 --- /dev/null +++ b/3p/vendors/caprofitx.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {caprofitx} from '../../ads/vendors/caprofitx'; + +init(window); +register('caprofitx', caprofitx); + +window.draw3p = draw3p; diff --git a/3p/vendors/cedato.js b/3p/vendors/cedato.js new file mode 100644 index 0000000000000..b0f3d7e43ef0e --- /dev/null +++ b/3p/vendors/cedato.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {cedato} from '../../ads/vendors/cedato'; + +init(window); +register('cedato', cedato); + +window.draw3p = draw3p; diff --git a/3p/vendors/chargeads.js b/3p/vendors/chargeads.js new file mode 100644 index 0000000000000..e7339dd7b5b62 --- /dev/null +++ b/3p/vendors/chargeads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {chargeads} from '../../ads/vendors/nws'; + +init(window); +register('chargeads', chargeads); + +window.draw3p = draw3p; diff --git a/3p/vendors/colombia.js b/3p/vendors/colombia.js new file mode 100644 index 0000000000000..d666480a70b8a --- /dev/null +++ b/3p/vendors/colombia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {colombia} from '../../ads/vendors/colombia'; + +init(window); +register('colombia', colombia); + +window.draw3p = draw3p; diff --git a/3p/vendors/conative.js b/3p/vendors/conative.js new file mode 100644 index 0000000000000..07f8b5625c834 --- /dev/null +++ b/3p/vendors/conative.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {conative} from '../../ads/vendors/conative'; + +init(window); +register('conative', conative); + +window.draw3p = draw3p; diff --git a/3p/vendors/connatix.js b/3p/vendors/connatix.js new file mode 100644 index 0000000000000..09330ea98dd61 --- /dev/null +++ b/3p/vendors/connatix.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {connatix} from '../../ads/vendors/connatix'; + +init(window); +register('connatix', connatix); + +window.draw3p = draw3p; diff --git a/3p/vendors/contentad.js b/3p/vendors/contentad.js new file mode 100644 index 0000000000000..1af7ca3f5e1dd --- /dev/null +++ b/3p/vendors/contentad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {contentad} from '../../ads/vendors/contentad'; + +init(window); +register('contentad', contentad); + +window.draw3p = draw3p; diff --git a/3p/vendors/criteo.js b/3p/vendors/criteo.js new file mode 100644 index 0000000000000..b1257b7b27f67 --- /dev/null +++ b/3p/vendors/criteo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {criteo} from '../../ads/vendors/criteo'; + +init(window); +register('criteo', criteo); + +window.draw3p = draw3p; diff --git a/3p/vendors/csa.js b/3p/vendors/csa.js new file mode 100644 index 0000000000000..c031755eff805 --- /dev/null +++ b/3p/vendors/csa.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {csa} from '../../ads/vendors/csa'; + +init(window); +register('csa', csa); + +window.draw3p = draw3p; diff --git a/3p/vendors/dable.js b/3p/vendors/dable.js new file mode 100644 index 0000000000000..fb68892181cc6 --- /dev/null +++ b/3p/vendors/dable.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {dable} from '../../ads/vendors/dable'; + +init(window); +register('dable', dable); + +window.draw3p = draw3p; diff --git a/3p/vendors/digiteka.js b/3p/vendors/digiteka.js new file mode 100644 index 0000000000000..6909c62fc9762 --- /dev/null +++ b/3p/vendors/digiteka.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {digiteka} from '../../ads/vendors/digiteka'; + +init(window); +register('digiteka', digiteka); + +window.draw3p = draw3p; diff --git a/3p/vendors/directadvert.js b/3p/vendors/directadvert.js new file mode 100644 index 0000000000000..d494b52f103c6 --- /dev/null +++ b/3p/vendors/directadvert.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {directadvert} from '../../ads/vendors/directadvert'; + +init(window); +register('directadvert', directadvert); + +window.draw3p = draw3p; diff --git a/3p/vendors/distroscale.js b/3p/vendors/distroscale.js new file mode 100644 index 0000000000000..1cb3b4e5222e5 --- /dev/null +++ b/3p/vendors/distroscale.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {distroscale} from '../../ads/vendors/distroscale'; + +init(window); +register('distroscale', distroscale); + +window.draw3p = draw3p; diff --git a/3p/vendors/dotandads.js b/3p/vendors/dotandads.js new file mode 100644 index 0000000000000..64a8dcb7c333e --- /dev/null +++ b/3p/vendors/dotandads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {dotandads} from '../../ads/vendors/dotandads'; + +init(window); +register('dotandads', dotandads); + +window.draw3p = draw3p; diff --git a/3p/vendors/dynad.js b/3p/vendors/dynad.js new file mode 100644 index 0000000000000..dfd040c48fa84 --- /dev/null +++ b/3p/vendors/dynad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {dynad} from '../../ads/vendors/dynad'; + +init(window); +register('dynad', dynad); + +window.draw3p = draw3p; diff --git a/3p/vendors/eadv.js b/3p/vendors/eadv.js new file mode 100644 index 0000000000000..b131023a80f9b --- /dev/null +++ b/3p/vendors/eadv.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {eadv} from '../../ads/vendors/eadv'; + +init(window); +register('eadv', eadv); + +window.draw3p = draw3p; diff --git a/3p/vendors/embedly.js b/3p/vendors/embedly.js new file mode 100644 index 0000000000000..95b1666d00c79 --- /dev/null +++ b/3p/vendors/embedly.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {embedly} from '../embedly'; + +init(window); +register('embedly', embedly); + +window.draw3p = draw3p; diff --git a/3p/vendors/empower.js b/3p/vendors/empower.js new file mode 100644 index 0000000000000..8110e2ba76893 --- /dev/null +++ b/3p/vendors/empower.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {empower} from '../../ads/vendors/empower'; + +init(window); +register('empower', empower); + +window.draw3p = draw3p; diff --git a/3p/vendors/engageya.js b/3p/vendors/engageya.js new file mode 100644 index 0000000000000..45f80094b834b --- /dev/null +++ b/3p/vendors/engageya.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {engageya} from '../../ads/vendors/engageya'; + +init(window); +register('engageya', engageya); + +window.draw3p = draw3p; diff --git a/3p/vendors/epeex.js b/3p/vendors/epeex.js new file mode 100644 index 0000000000000..3a1716cf270da --- /dev/null +++ b/3p/vendors/epeex.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {epeex} from '../../ads/vendors/epeex'; + +init(window); +register('epeex', epeex); + +window.draw3p = draw3p; diff --git a/3p/vendors/eplanning.js b/3p/vendors/eplanning.js new file mode 100644 index 0000000000000..bd3a03ee63704 --- /dev/null +++ b/3p/vendors/eplanning.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {eplanning} from '../../ads/vendors/eplanning'; + +init(window); +register('eplanning', eplanning); + +window.draw3p = draw3p; diff --git a/3p/vendors/ezoic.js b/3p/vendors/ezoic.js new file mode 100644 index 0000000000000..7efb4f4d1e101 --- /dev/null +++ b/3p/vendors/ezoic.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ezoic} from '../../ads/vendors/ezoic'; + +init(window); +register('ezoic', ezoic); + +window.draw3p = draw3p; diff --git a/3p/vendors/f1e.js b/3p/vendors/f1e.js new file mode 100644 index 0000000000000..54ec88c80517d --- /dev/null +++ b/3p/vendors/f1e.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {f1e} from '../../ads/vendors/f1e'; + +init(window); +register('f1e', f1e); + +window.draw3p = draw3p; diff --git a/3p/vendors/f1h.js b/3p/vendors/f1h.js new file mode 100644 index 0000000000000..498abf2c4373c --- /dev/null +++ b/3p/vendors/f1h.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {f1h} from '../../ads/vendors/f1h'; + +init(window); +register('f1h', f1h); + +window.draw3p = draw3p; diff --git a/3p/vendors/facebook.js b/3p/vendors/facebook.js new file mode 100644 index 0000000000000..0ba50372d2a48 --- /dev/null +++ b/3p/vendors/facebook.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {facebook} from '../facebook'; + +init(window); +register('facebook', facebook); + +window.draw3p = draw3p; diff --git a/3p/vendors/fake-delayed.js b/3p/vendors/fake-delayed.js new file mode 100644 index 0000000000000..e217bcb26ca3e --- /dev/null +++ b/3p/vendors/fake-delayed.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {fakeDelayed} from '../../ads/vendors/_fakedelayed_'; + +init(window); +register('fake-delayed', fakeDelayed); + +window.draw3p = draw3p; diff --git a/3p/vendors/feedad.js b/3p/vendors/feedad.js new file mode 100644 index 0000000000000..9c7b353c870f4 --- /dev/null +++ b/3p/vendors/feedad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {feedad} from '../../ads/vendors/feedad'; + +init(window); +register('feedad', feedad); + +window.draw3p = draw3p; diff --git a/3p/vendors/felmat.js b/3p/vendors/felmat.js new file mode 100644 index 0000000000000..522c90a6db249 --- /dev/null +++ b/3p/vendors/felmat.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {felmat} from '../../ads/vendors/felmat'; + +init(window); +register('felmat', felmat); + +window.draw3p = draw3p; diff --git a/3p/vendors/finative.js b/3p/vendors/finative.js new file mode 100644 index 0000000000000..3f71967a419bd --- /dev/null +++ b/3p/vendors/finative.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {finative} from '../../ads/vendors/finative'; + +init(window); +register('finative', finative); + +window.draw3p = draw3p; diff --git a/3p/vendors/firstimpression.js b/3p/vendors/firstimpression.js new file mode 100644 index 0000000000000..c4c93bc67eedb --- /dev/null +++ b/3p/vendors/firstimpression.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {firstimpression} from '../../ads/vendors/firstimpression'; + +init(window); +register('firstimpression', firstimpression); + +window.draw3p = draw3p; diff --git a/3p/vendors/flite.js b/3p/vendors/flite.js new file mode 100644 index 0000000000000..ba3949914a18a --- /dev/null +++ b/3p/vendors/flite.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {flite} from '../../ads/vendors/flite'; + +init(window); +register('flite', flite); + +window.draw3p = draw3p; diff --git a/3p/vendors/fluct.js b/3p/vendors/fluct.js new file mode 100644 index 0000000000000..9bca87e5162b3 --- /dev/null +++ b/3p/vendors/fluct.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {fluct} from '../../ads/vendors/fluct'; + +init(window); +register('fluct', fluct); + +window.draw3p = draw3p; diff --git a/3p/vendors/forkmedia.js b/3p/vendors/forkmedia.js new file mode 100644 index 0000000000000..63f29c91bd66b --- /dev/null +++ b/3p/vendors/forkmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {forkmedia} from '../../ads/vendors/forkmedia'; + +init(window); +register('forkmedia', forkmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/freewheel.js b/3p/vendors/freewheel.js new file mode 100644 index 0000000000000..13ca03c5863ae --- /dev/null +++ b/3p/vendors/freewheel.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {freewheel} from '../../ads/vendors/freewheel'; + +init(window); +register('freewheel', freewheel); + +window.draw3p = draw3p; diff --git a/3p/vendors/fusion.js b/3p/vendors/fusion.js new file mode 100644 index 0000000000000..186bf73d91ca9 --- /dev/null +++ b/3p/vendors/fusion.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {fusion} from '../../ads/vendors/fusion'; + +init(window); +register('fusion', fusion); + +window.draw3p = draw3p; diff --git a/3p/vendors/genieessp.js b/3p/vendors/genieessp.js new file mode 100644 index 0000000000000..1faac44f70fc4 --- /dev/null +++ b/3p/vendors/genieessp.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {genieessp} from '../../ads/vendors/genieessp'; + +init(window); +register('genieessp', genieessp); + +window.draw3p = draw3p; diff --git a/3p/vendors/giraff.js b/3p/vendors/giraff.js new file mode 100644 index 0000000000000..ee247afc0472d --- /dev/null +++ b/3p/vendors/giraff.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {giraff} from '../../ads/vendors/giraff'; + +init(window); +register('giraff', giraff); + +window.draw3p = draw3p; diff --git a/3p/vendors/github.js b/3p/vendors/github.js new file mode 100644 index 0000000000000..d017c72b8211c --- /dev/null +++ b/3p/vendors/github.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {github} from '../github'; + +init(window); +register('github', github); + +window.draw3p = draw3p; diff --git a/3p/vendors/glomex.js b/3p/vendors/glomex.js new file mode 100644 index 0000000000000..913f0750cf99f --- /dev/null +++ b/3p/vendors/glomex.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {glomex} from '../../ads/vendors/glomex'; + +init(window); +register('glomex', glomex); + +window.draw3p = draw3p; diff --git a/3p/vendors/gmossp.js b/3p/vendors/gmossp.js new file mode 100644 index 0000000000000..1f0cb14094bbd --- /dev/null +++ b/3p/vendors/gmossp.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {gmossp} from '../../ads/vendors/gmossp'; + +init(window); +register('gmossp', gmossp); + +window.draw3p = draw3p; diff --git a/3p/vendors/gumgum.js b/3p/vendors/gumgum.js new file mode 100644 index 0000000000000..496d198ecb1f3 --- /dev/null +++ b/3p/vendors/gumgum.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {gumgum} from '../../ads/vendors/gumgum'; + +init(window); +register('gumgum', gumgum); + +window.draw3p = draw3p; diff --git a/3p/vendors/holder.js b/3p/vendors/holder.js new file mode 100644 index 0000000000000..a4aac0ef085d7 --- /dev/null +++ b/3p/vendors/holder.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {holder} from '../../ads/vendors/holder'; + +init(window); +register('holder', holder); + +window.draw3p = draw3p; diff --git a/3p/vendors/ibillboard.js b/3p/vendors/ibillboard.js new file mode 100644 index 0000000000000..c20e8813d5a6b --- /dev/null +++ b/3p/vendors/ibillboard.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ibillboard} from '../../ads/vendors/ibillboard'; + +init(window); +register('ibillboard', ibillboard); + +window.draw3p = draw3p; diff --git a/3p/vendors/idealmedia.js b/3p/vendors/idealmedia.js new file mode 100644 index 0000000000000..cacbd9bfd3f7c --- /dev/null +++ b/3p/vendors/idealmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {idealmedia} from '../../ads/vendors/idealmedia'; + +init(window); +register('idealmedia', idealmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/ima-video.js b/3p/vendors/ima-video.js new file mode 100644 index 0000000000000..035493f8025b4 --- /dev/null +++ b/3p/vendors/ima-video.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {imaVideo} from '../../ads/google/ima/ima-video'; + +init(window); +register('ima-video', imaVideo); + +window.draw3p = draw3p; diff --git a/3p/vendors/imedia.js b/3p/vendors/imedia.js new file mode 100644 index 0000000000000..e41c220506600 --- /dev/null +++ b/3p/vendors/imedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {imedia} from '../../ads/vendors/imedia'; + +init(window); +register('imedia', imedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/imobile.js b/3p/vendors/imobile.js new file mode 100644 index 0000000000000..6ee9420c2db8b --- /dev/null +++ b/3p/vendors/imobile.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {imobile} from '../../ads/vendors/imobile'; + +init(window); +register('imobile', imobile); + +window.draw3p = draw3p; diff --git a/3p/vendors/imonomy.js b/3p/vendors/imonomy.js new file mode 100644 index 0000000000000..dcd8731d8679b --- /dev/null +++ b/3p/vendors/imonomy.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {imonomy} from '../../ads/vendors/imonomy'; + +init(window); +register('imonomy', imonomy); + +window.draw3p = draw3p; diff --git a/3p/vendors/improvedigital.js b/3p/vendors/improvedigital.js new file mode 100644 index 0000000000000..b7991c3e3f82a --- /dev/null +++ b/3p/vendors/improvedigital.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {improvedigital} from '../../ads/vendors/improvedigital'; + +init(window); +register('improvedigital', improvedigital); + +window.draw3p = draw3p; diff --git a/3p/vendors/industrybrains.js b/3p/vendors/industrybrains.js new file mode 100644 index 0000000000000..30b4c16e87a69 --- /dev/null +++ b/3p/vendors/industrybrains.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {industrybrains} from '../../ads/vendors/adblade'; + +init(window); +register('industrybrains', industrybrains); + +window.draw3p = draw3p; diff --git a/3p/vendors/inmobi.js b/3p/vendors/inmobi.js new file mode 100644 index 0000000000000..196ac8788039b --- /dev/null +++ b/3p/vendors/inmobi.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {inmobi} from '../../ads/vendors/inmobi'; + +init(window); +register('inmobi', inmobi); + +window.draw3p = draw3p; diff --git a/3p/vendors/innity.js b/3p/vendors/innity.js new file mode 100644 index 0000000000000..70e3941842c64 --- /dev/null +++ b/3p/vendors/innity.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {innity} from '../../ads/vendors/innity'; + +init(window); +register('innity', innity); + +window.draw3p = draw3p; diff --git a/3p/vendors/insticator.js b/3p/vendors/insticator.js new file mode 100644 index 0000000000000..99a095fcf1670 --- /dev/null +++ b/3p/vendors/insticator.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {insticator} from '../../ads/vendors/insticator'; + +init(window); +register('insticator', insticator); + +window.draw3p = draw3p; diff --git a/3p/vendors/invibes.js b/3p/vendors/invibes.js new file mode 100644 index 0000000000000..92dbdbb5752ab --- /dev/null +++ b/3p/vendors/invibes.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {invibes} from '../../ads/vendors/invibes'; + +init(window); +register('invibes', invibes); + +window.draw3p = draw3p; diff --git a/3p/vendors/iprom.js b/3p/vendors/iprom.js new file mode 100644 index 0000000000000..8b2c314c6c1d5 --- /dev/null +++ b/3p/vendors/iprom.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {iprom} from '../../ads/vendors/iprom'; + +init(window); +register('iprom', iprom); + +window.draw3p = draw3p; diff --git a/3p/vendors/ix.js b/3p/vendors/ix.js new file mode 100644 index 0000000000000..ccfa1b2503d07 --- /dev/null +++ b/3p/vendors/ix.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ix} from '../../ads/vendors/ix'; + +init(window); +register('ix', ix); + +window.draw3p = draw3p; diff --git a/3p/vendors/jubna.js b/3p/vendors/jubna.js new file mode 100644 index 0000000000000..32dc1199793a4 --- /dev/null +++ b/3p/vendors/jubna.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {jubna} from '../../ads/vendors/jubna'; + +init(window); +register('jubna', jubna); + +window.draw3p = draw3p; diff --git a/3p/vendors/kargo.js b/3p/vendors/kargo.js new file mode 100644 index 0000000000000..eadb31aea9647 --- /dev/null +++ b/3p/vendors/kargo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {kargo} from '../../ads/vendors/kargo'; + +init(window); +register('kargo', kargo); + +window.draw3p = draw3p; diff --git a/3p/vendors/ketshwa.js b/3p/vendors/ketshwa.js new file mode 100644 index 0000000000000..6ee96f4455235 --- /dev/null +++ b/3p/vendors/ketshwa.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ketshwa} from '../../ads/vendors/ketshwa'; + +init(window); +register('ketshwa', ketshwa); + +window.draw3p = draw3p; diff --git a/3p/vendors/kiosked.js b/3p/vendors/kiosked.js new file mode 100644 index 0000000000000..a09af6b923b2b --- /dev/null +++ b/3p/vendors/kiosked.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {kiosked} from '../../ads/vendors/kiosked'; + +init(window); +register('kiosked', kiosked); + +window.draw3p = draw3p; diff --git a/3p/vendors/kixer.js b/3p/vendors/kixer.js new file mode 100644 index 0000000000000..8f906f383666d --- /dev/null +++ b/3p/vendors/kixer.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {kixer} from '../../ads/vendors/kixer'; + +init(window); +register('kixer', kixer); + +window.draw3p = draw3p; diff --git a/3p/vendors/kuadio.js b/3p/vendors/kuadio.js new file mode 100644 index 0000000000000..b27f3c1d4fcb3 --- /dev/null +++ b/3p/vendors/kuadio.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {kuadio} from '../../ads/vendors/kuadio'; + +init(window); +register('kuadio', kuadio); + +window.draw3p = draw3p; diff --git a/3p/vendors/lentainform.js b/3p/vendors/lentainform.js new file mode 100644 index 0000000000000..4cd2d968b671c --- /dev/null +++ b/3p/vendors/lentainform.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {lentainform} from '../../ads/vendors/lentainform'; + +init(window); +register('lentainform', lentainform); + +window.draw3p = draw3p; diff --git a/3p/vendors/ligatus.js b/3p/vendors/ligatus.js new file mode 100644 index 0000000000000..42ce103e78d40 --- /dev/null +++ b/3p/vendors/ligatus.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ligatus} from '../../ads/vendors/ligatus'; + +init(window); +register('ligatus', ligatus); + +window.draw3p = draw3p; diff --git a/3p/vendors/lockerdome.js b/3p/vendors/lockerdome.js new file mode 100644 index 0000000000000..e559ef4fe1431 --- /dev/null +++ b/3p/vendors/lockerdome.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {lockerdome} from '../../ads/vendors/lockerdome'; + +init(window); +register('lockerdome', lockerdome); + +window.draw3p = draw3p; diff --git a/3p/vendors/logly.js b/3p/vendors/logly.js new file mode 100644 index 0000000000000..8615969f4cb91 --- /dev/null +++ b/3p/vendors/logly.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {logly} from '../../ads/vendors/logly'; + +init(window); +register('logly', logly); + +window.draw3p = draw3p; diff --git a/3p/vendors/loka.js b/3p/vendors/loka.js new file mode 100644 index 0000000000000..8eb1be7da160f --- /dev/null +++ b/3p/vendors/loka.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {loka} from '../../ads/vendors/loka'; + +init(window); +register('loka', loka); + +window.draw3p = draw3p; diff --git a/3p/vendors/luckyads.js b/3p/vendors/luckyads.js new file mode 100644 index 0000000000000..26e3785654119 --- /dev/null +++ b/3p/vendors/luckyads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {luckyads} from '../../ads/vendors/luckyads'; + +init(window); +register('luckyads', luckyads); + +window.draw3p = draw3p; diff --git a/3p/vendors/macaw.js b/3p/vendors/macaw.js new file mode 100644 index 0000000000000..2cc9e4ed19787 --- /dev/null +++ b/3p/vendors/macaw.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {macaw} from '../../ads/vendors/macaw'; + +init(window); +register('macaw', macaw); + +window.draw3p = draw3p; diff --git a/3p/vendors/mads.js b/3p/vendors/mads.js new file mode 100644 index 0000000000000..2f252e48e3843 --- /dev/null +++ b/3p/vendors/mads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mads} from '../../ads/vendors/mads'; + +init(window); +register('mads', mads); + +window.draw3p = draw3p; diff --git a/3p/vendors/mantis-display.js b/3p/vendors/mantis-display.js new file mode 100644 index 0000000000000..2d9ee2bde8777 --- /dev/null +++ b/3p/vendors/mantis-display.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mantisDisplay} from '../../ads/vendors/mantis'; + +init(window); +register('mantis-display', mantisDisplay); + +window.draw3p = draw3p; diff --git a/3p/vendors/mantis-recommend.js b/3p/vendors/mantis-recommend.js new file mode 100644 index 0000000000000..8f34f1448c61a --- /dev/null +++ b/3p/vendors/mantis-recommend.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mantisRecommend} from '../../ads/vendors/mantis'; + +init(window); +register('mantis-recommend', mantisRecommend); + +window.draw3p = draw3p; diff --git a/3p/vendors/marfeel.js b/3p/vendors/marfeel.js new file mode 100644 index 0000000000000..41693dd9707d9 --- /dev/null +++ b/3p/vendors/marfeel.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {marfeel} from '../../ads/vendors/marfeel'; + +init(window); +register('marfeel', marfeel); + +window.draw3p = draw3p; diff --git a/3p/vendors/mathml.js b/3p/vendors/mathml.js new file mode 100644 index 0000000000000..fe84fc1231956 --- /dev/null +++ b/3p/vendors/mathml.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mathml} from '../mathml'; + +init(window); +register('mathml', mathml); + +window.draw3p = draw3p; diff --git a/3p/vendors/mediaad.js b/3p/vendors/mediaad.js new file mode 100644 index 0000000000000..e1461f6805a2d --- /dev/null +++ b/3p/vendors/mediaad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mediaad} from '../../ads/vendors/mediaad'; + +init(window); +register('mediaad', mediaad); + +window.draw3p = draw3p; diff --git a/3p/vendors/medianet.js b/3p/vendors/medianet.js new file mode 100644 index 0000000000000..1dd922a8d2816 --- /dev/null +++ b/3p/vendors/medianet.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {medianet} from '../../ads/vendors/medianet'; + +init(window); +register('medianet', medianet); + +window.draw3p = draw3p; diff --git a/3p/vendors/mediavine.js b/3p/vendors/mediavine.js new file mode 100644 index 0000000000000..0be7231b2cf95 --- /dev/null +++ b/3p/vendors/mediavine.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mediavine} from '../../ads/vendors/mediavine'; + +init(window); +register('mediavine', mediavine); + +window.draw3p = draw3p; diff --git a/3p/vendors/medyanet.js b/3p/vendors/medyanet.js new file mode 100644 index 0000000000000..a2874e4643dfd --- /dev/null +++ b/3p/vendors/medyanet.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {medyanet} from '../../ads/vendors/medyanet'; + +init(window); +register('medyanet', medyanet); + +window.draw3p = draw3p; diff --git a/3p/vendors/meg.js b/3p/vendors/meg.js new file mode 100644 index 0000000000000..de68c9cef0757 --- /dev/null +++ b/3p/vendors/meg.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {meg} from '../../ads/vendors/meg'; + +init(window); +register('meg', meg); + +window.draw3p = draw3p; diff --git a/3p/vendors/mgid.js b/3p/vendors/mgid.js new file mode 100644 index 0000000000000..bbf9cc8f7f579 --- /dev/null +++ b/3p/vendors/mgid.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mgid} from '../../ads/vendors/mgid'; + +init(window); +register('mgid', mgid); + +window.draw3p = draw3p; diff --git a/3p/vendors/microad.js b/3p/vendors/microad.js new file mode 100644 index 0000000000000..c2a916b917c1b --- /dev/null +++ b/3p/vendors/microad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {microad} from '../../ads/vendors/microad'; + +init(window); +register('microad', microad); + +window.draw3p = draw3p; diff --git a/3p/vendors/miximedia.js b/3p/vendors/miximedia.js new file mode 100644 index 0000000000000..f7b30603283b6 --- /dev/null +++ b/3p/vendors/miximedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {miximedia} from '../../ads/vendors/miximedia'; + +init(window); +register('miximedia', miximedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/mixpo.js b/3p/vendors/mixpo.js new file mode 100644 index 0000000000000..5c1dfee431c47 --- /dev/null +++ b/3p/vendors/mixpo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mixpo} from '../../ads/vendors/mixpo'; + +init(window); +register('mixpo', mixpo); + +window.draw3p = draw3p; diff --git a/3p/vendors/monetizer101.js b/3p/vendors/monetizer101.js new file mode 100644 index 0000000000000..1be86a2dcfef2 --- /dev/null +++ b/3p/vendors/monetizer101.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {monetizer101} from '../../ads/vendors/monetizer101'; + +init(window); +register('monetizer101', monetizer101); + +window.draw3p = draw3p; diff --git a/3p/vendors/mox.js b/3p/vendors/mox.js new file mode 100644 index 0000000000000..40ccaeb3798f7 --- /dev/null +++ b/3p/vendors/mox.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mox} from '../../ads/vendors/mox'; + +init(window); +register('mox', mox); + +window.draw3p = draw3p; diff --git a/3p/vendors/my6sense.js b/3p/vendors/my6sense.js new file mode 100644 index 0000000000000..9a6ca61df5cf0 --- /dev/null +++ b/3p/vendors/my6sense.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {my6sense} from '../../ads/vendors/my6sense'; + +init(window); +register('my6sense', my6sense); + +window.draw3p = draw3p; diff --git a/3p/vendors/myfinance.js b/3p/vendors/myfinance.js new file mode 100644 index 0000000000000..a0246c05c2f7e --- /dev/null +++ b/3p/vendors/myfinance.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {myfinance} from '../../ads/vendors/myfinance'; + +init(window); +register('myfinance', myfinance); + +window.draw3p = draw3p; diff --git a/3p/vendors/myoffrz.js b/3p/vendors/myoffrz.js new file mode 100644 index 0000000000000..7377b14cb4353 --- /dev/null +++ b/3p/vendors/myoffrz.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {myoffrz} from '../../ads/vendors/myoffrz'; + +init(window); +register('myoffrz', myoffrz); + +window.draw3p = draw3p; diff --git a/3p/vendors/mytarget.js b/3p/vendors/mytarget.js new file mode 100644 index 0000000000000..9c03b62f85a41 --- /dev/null +++ b/3p/vendors/mytarget.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mytarget} from '../../ads/vendors/mytarget'; + +init(window); +register('mytarget', mytarget); + +window.draw3p = draw3p; diff --git a/3p/vendors/mywidget.js b/3p/vendors/mywidget.js new file mode 100644 index 0000000000000..4d75f288874b8 --- /dev/null +++ b/3p/vendors/mywidget.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {mywidget} from '../../ads/vendors/mywidget'; + +init(window); +register('mywidget', mywidget); + +window.draw3p = draw3p; diff --git a/3p/vendors/nativeroll.js b/3p/vendors/nativeroll.js new file mode 100644 index 0000000000000..5ffde83d29cb1 --- /dev/null +++ b/3p/vendors/nativeroll.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {nativeroll} from '../../ads/vendors/nativeroll'; + +init(window); +register('nativeroll', nativeroll); + +window.draw3p = draw3p; diff --git a/3p/vendors/nativery.js b/3p/vendors/nativery.js new file mode 100644 index 0000000000000..c5da72800b97f --- /dev/null +++ b/3p/vendors/nativery.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {nativery} from '../../ads/vendors/nativery'; + +init(window); +register('nativery', nativery); + +window.draw3p = draw3p; diff --git a/3p/vendors/nativo.js b/3p/vendors/nativo.js new file mode 100644 index 0000000000000..1d20444c076a9 --- /dev/null +++ b/3p/vendors/nativo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {nativo} from '../../ads/vendors/nativo'; + +init(window); +register('nativo', nativo); + +window.draw3p = draw3p; diff --git a/3p/vendors/navegg.js b/3p/vendors/navegg.js new file mode 100644 index 0000000000000..d734a339efb15 --- /dev/null +++ b/3p/vendors/navegg.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {navegg} from '../../ads/vendors/navegg'; + +init(window); +register('navegg', navegg); + +window.draw3p = draw3p; diff --git a/3p/vendors/nend.js b/3p/vendors/nend.js new file mode 100644 index 0000000000000..180c4836fea03 --- /dev/null +++ b/3p/vendors/nend.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {nend} from '../../ads/vendors/nend'; + +init(window); +register('nend', nend); + +window.draw3p = draw3p; diff --git a/3p/vendors/netletix.js b/3p/vendors/netletix.js new file mode 100644 index 0000000000000..f5830b0de4e04 --- /dev/null +++ b/3p/vendors/netletix.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {netletix} from '../../ads/vendors/netletix'; + +init(window); +register('netletix', netletix); + +window.draw3p = draw3p; diff --git a/3p/vendors/noddus.js b/3p/vendors/noddus.js new file mode 100644 index 0000000000000..fd5bf079ce8f8 --- /dev/null +++ b/3p/vendors/noddus.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {noddus} from '../../ads/vendors/noddus'; + +init(window); +register('noddus', noddus); + +window.draw3p = draw3p; diff --git a/3p/vendors/nokta.js b/3p/vendors/nokta.js new file mode 100644 index 0000000000000..d45245d49d701 --- /dev/null +++ b/3p/vendors/nokta.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {nokta} from '../../ads/vendors/nokta'; + +init(window); +register('nokta', nokta); + +window.draw3p = draw3p; diff --git a/3p/vendors/nws.js b/3p/vendors/nws.js new file mode 100644 index 0000000000000..bf069b83449f0 --- /dev/null +++ b/3p/vendors/nws.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {nws} from '../../ads/vendors/nws'; + +init(window); +register('nws', nws); + +window.draw3p = draw3p; diff --git a/3p/vendors/oblivki.js b/3p/vendors/oblivki.js new file mode 100644 index 0000000000000..35d987d3e911a --- /dev/null +++ b/3p/vendors/oblivki.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {oblivki} from '../../ads/vendors/oblivki'; + +init(window); +register('oblivki', oblivki); + +window.draw3p = draw3p; diff --git a/3p/vendors/onead.js b/3p/vendors/onead.js new file mode 100644 index 0000000000000..59a180198ae20 --- /dev/null +++ b/3p/vendors/onead.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {onead} from '../../ads/vendors/onead'; + +init(window); +register('onead', onead); + +window.draw3p = draw3p; diff --git a/3p/vendors/onnetwork.js b/3p/vendors/onnetwork.js new file mode 100644 index 0000000000000..c25d31cb8a101 --- /dev/null +++ b/3p/vendors/onnetwork.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {onnetwork} from '../../ads/vendors/onnetwork'; + +init(window); +register('onnetwork', onnetwork); + +window.draw3p = draw3p; diff --git a/3p/vendors/openadstream.js b/3p/vendors/openadstream.js new file mode 100644 index 0000000000000..035f3b177d073 --- /dev/null +++ b/3p/vendors/openadstream.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {openadstream} from '../../ads/vendors/openadstream'; + +init(window); +register('openadstream', openadstream); + +window.draw3p = draw3p; diff --git a/3p/vendors/openx.js b/3p/vendors/openx.js new file mode 100644 index 0000000000000..8037534df17dd --- /dev/null +++ b/3p/vendors/openx.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {openx} from '../../ads/vendors/openx'; + +init(window); +register('openx', openx); + +window.draw3p = draw3p; diff --git a/3p/vendors/opinary.js b/3p/vendors/opinary.js new file mode 100644 index 0000000000000..7a461bc1cafda --- /dev/null +++ b/3p/vendors/opinary.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {opinary} from '../../ads/vendors/opinary'; + +init(window); +register('opinary', opinary); + +window.draw3p = draw3p; diff --git a/3p/vendors/outbrain.js b/3p/vendors/outbrain.js new file mode 100644 index 0000000000000..e7f99c7e83b5e --- /dev/null +++ b/3p/vendors/outbrain.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {outbrain} from '../../ads/vendors/outbrain'; + +init(window); +register('outbrain', outbrain); + +window.draw3p = draw3p; diff --git a/3p/vendors/pixels.js b/3p/vendors/pixels.js new file mode 100644 index 0000000000000..dfa7c6263f4e3 --- /dev/null +++ b/3p/vendors/pixels.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pixels} from '../../ads/vendors/pixels'; + +init(window); +register('pixels', pixels); + +window.draw3p = draw3p; diff --git a/3p/vendors/playstream.js b/3p/vendors/playstream.js new file mode 100644 index 0000000000000..af6bc99019d3c --- /dev/null +++ b/3p/vendors/playstream.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {playstream} from '../../ads/vendors/playstream'; + +init(window); +register('playstream', playstream); + +window.draw3p = draw3p; diff --git a/3p/vendors/plista.js b/3p/vendors/plista.js new file mode 100644 index 0000000000000..5d287daeda142 --- /dev/null +++ b/3p/vendors/plista.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {plista} from '../../ads/vendors/plista'; + +init(window); +register('plista', plista); + +window.draw3p = draw3p; diff --git a/3p/vendors/polymorphicads.js b/3p/vendors/polymorphicads.js new file mode 100644 index 0000000000000..21b768bc528a1 --- /dev/null +++ b/3p/vendors/polymorphicads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {polymorphicads} from '../../ads/vendors/polymorphicads'; + +init(window); +register('polymorphicads', polymorphicads); + +window.draw3p = draw3p; diff --git a/3p/vendors/popin.js b/3p/vendors/popin.js new file mode 100644 index 0000000000000..a862f602fb907 --- /dev/null +++ b/3p/vendors/popin.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {popin} from '../../ads/vendors/popin'; + +init(window); +register('popin', popin); + +window.draw3p = draw3p; diff --git a/3p/vendors/postquare.js b/3p/vendors/postquare.js new file mode 100644 index 0000000000000..be6e8b60c21a2 --- /dev/null +++ b/3p/vendors/postquare.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {postquare} from '../../ads/vendors/postquare'; + +init(window); +register('postquare', postquare); + +window.draw3p = draw3p; diff --git a/3p/vendors/ppstudio.js b/3p/vendors/ppstudio.js new file mode 100644 index 0000000000000..c2b966a53b660 --- /dev/null +++ b/3p/vendors/ppstudio.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ppstudio} from '../../ads/vendors/ppstudio'; + +init(window); +register('ppstudio', ppstudio); + +window.draw3p = draw3p; diff --git a/3p/vendors/pressboard.js b/3p/vendors/pressboard.js new file mode 100644 index 0000000000000..b9ed1f55cacbc --- /dev/null +++ b/3p/vendors/pressboard.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pressboard} from '../../ads/vendors/pressboard'; + +init(window); +register('pressboard', pressboard); + +window.draw3p = draw3p; diff --git a/3p/vendors/promoteiq.js b/3p/vendors/promoteiq.js new file mode 100644 index 0000000000000..93235230eaef9 --- /dev/null +++ b/3p/vendors/promoteiq.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {promoteiq} from '../../ads/vendors/promoteiq'; + +init(window); +register('promoteiq', promoteiq); + +window.draw3p = draw3p; diff --git a/3p/vendors/pubexchange.js b/3p/vendors/pubexchange.js new file mode 100644 index 0000000000000..90b3477a1f6e0 --- /dev/null +++ b/3p/vendors/pubexchange.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pubexchange} from '../../ads/vendors/pubexchange'; + +init(window); +register('pubexchange', pubexchange); + +window.draw3p = draw3p; diff --git a/3p/vendors/pubguru.js b/3p/vendors/pubguru.js new file mode 100644 index 0000000000000..17ede73370b08 --- /dev/null +++ b/3p/vendors/pubguru.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pubguru} from '../../ads/vendors/pubguru'; + +init(window); +register('pubguru', pubguru); + +window.draw3p = draw3p; diff --git a/3p/vendors/pubmatic.js b/3p/vendors/pubmatic.js new file mode 100644 index 0000000000000..ccbd2a2fea7a7 --- /dev/null +++ b/3p/vendors/pubmatic.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pubmatic} from '../../ads/vendors/pubmatic'; + +init(window); +register('pubmatic', pubmatic); + +window.draw3p = draw3p; diff --git a/3p/vendors/pubmine.js b/3p/vendors/pubmine.js new file mode 100644 index 0000000000000..28c20b01397f1 --- /dev/null +++ b/3p/vendors/pubmine.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pubmine} from '../../ads/vendors/pubmine'; + +init(window); +register('pubmine', pubmine); + +window.draw3p = draw3p; diff --git a/3p/vendors/puffnetwork.js b/3p/vendors/puffnetwork.js new file mode 100644 index 0000000000000..cc92561cab091 --- /dev/null +++ b/3p/vendors/puffnetwork.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {puffnetwork} from '../../ads/vendors/puffnetwork'; + +init(window); +register('puffnetwork', puffnetwork); + +window.draw3p = draw3p; diff --git a/3p/vendors/pulse.js b/3p/vendors/pulse.js new file mode 100644 index 0000000000000..08147e373c944 --- /dev/null +++ b/3p/vendors/pulse.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pulse} from '../../ads/vendors/pulse'; + +init(window); +register('pulse', pulse); + +window.draw3p = draw3p; diff --git a/3p/vendors/pulsepoint.js b/3p/vendors/pulsepoint.js new file mode 100644 index 0000000000000..cf562f4d92c17 --- /dev/null +++ b/3p/vendors/pulsepoint.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {pulsepoint} from '../../ads/vendors/pulsepoint'; + +init(window); +register('pulsepoint', pulsepoint); + +window.draw3p = draw3p; diff --git a/3p/vendors/purch.js b/3p/vendors/purch.js new file mode 100644 index 0000000000000..ce257786b4715 --- /dev/null +++ b/3p/vendors/purch.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {purch} from '../../ads/vendors/purch'; + +init(window); +register('purch', purch); + +window.draw3p = draw3p; diff --git a/3p/vendors/quoraad.js b/3p/vendors/quoraad.js new file mode 100644 index 0000000000000..35496944aecbe --- /dev/null +++ b/3p/vendors/quoraad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {quoraad} from '../../ads/vendors/quoraad'; + +init(window); +register('quoraad', quoraad); + +window.draw3p = draw3p; diff --git a/3p/vendors/rakutenunifiedads.js b/3p/vendors/rakutenunifiedads.js new file mode 100644 index 0000000000000..bbc47b04fbb2c --- /dev/null +++ b/3p/vendors/rakutenunifiedads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {rakutenunifiedads} from '../../ads/vendors/rakutenunifiedads'; + +init(window); +register('rakutenunifiedads', rakutenunifiedads); + +window.draw3p = draw3p; diff --git a/3p/vendors/rbinfox.js b/3p/vendors/rbinfox.js new file mode 100644 index 0000000000000..962a1bbbbcd40 --- /dev/null +++ b/3p/vendors/rbinfox.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {rbinfox} from '../../ads/vendors/rbinfox'; + +init(window); +register('rbinfox', rbinfox); + +window.draw3p = draw3p; diff --git a/3p/vendors/rcmwidget.js b/3p/vendors/rcmwidget.js new file mode 100644 index 0000000000000..bd995c8cb2d78 --- /dev/null +++ b/3p/vendors/rcmwidget.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {rcmwidget} from '../../ads/vendors/rcmwidget'; + +init(window); +register('rcmwidget', rcmwidget); + +window.draw3p = draw3p; diff --git a/3p/vendors/readmo.js b/3p/vendors/readmo.js new file mode 100644 index 0000000000000..2514201dbd391 --- /dev/null +++ b/3p/vendors/readmo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {readmo} from '../../ads/vendors/readmo'; + +init(window); +register('readmo', readmo); + +window.draw3p = draw3p; diff --git a/3p/vendors/realclick.js b/3p/vendors/realclick.js new file mode 100644 index 0000000000000..be10ff227adab --- /dev/null +++ b/3p/vendors/realclick.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {realclick} from '../../ads/vendors/realclick'; + +init(window); +register('realclick', realclick); + +window.draw3p = draw3p; diff --git a/3p/vendors/recomad.js b/3p/vendors/recomad.js new file mode 100644 index 0000000000000..41548a9560fb8 --- /dev/null +++ b/3p/vendors/recomad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {recomad} from '../../ads/vendors/recomad'; + +init(window); +register('recomad', recomad); + +window.draw3p = draw3p; diff --git a/3p/vendors/recreativ.js b/3p/vendors/recreativ.js new file mode 100644 index 0000000000000..fd0e81f92dd0f --- /dev/null +++ b/3p/vendors/recreativ.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {recreativ} from '../../ads/vendors/recreativ'; + +init(window); +register('recreativ', recreativ); + +window.draw3p = draw3p; diff --git a/3p/vendors/reddit.js b/3p/vendors/reddit.js new file mode 100644 index 0000000000000..61147f80c4162 --- /dev/null +++ b/3p/vendors/reddit.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {reddit} from '../reddit'; + +init(window); +register('reddit', reddit); + +window.draw3p = draw3p; diff --git a/3p/vendors/relap.js b/3p/vendors/relap.js new file mode 100644 index 0000000000000..02f80094c8a85 --- /dev/null +++ b/3p/vendors/relap.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {relap} from '../../ads/vendors/relap'; + +init(window); +register('relap', relap); + +window.draw3p = draw3p; diff --git a/3p/vendors/relappro.js b/3p/vendors/relappro.js new file mode 100644 index 0000000000000..7163c7b92f00e --- /dev/null +++ b/3p/vendors/relappro.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {relappro} from '../../ads/vendors/relappro'; + +init(window); +register('relappro', relappro); + +window.draw3p = draw3p; diff --git a/3p/vendors/remixd.js b/3p/vendors/remixd.js new file mode 100644 index 0000000000000..36595eb552e57 --- /dev/null +++ b/3p/vendors/remixd.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {remixd} from '../../ads/vendors/remixd'; + +init(window); +register('remixd', remixd); + +window.draw3p = draw3p; diff --git a/3p/vendors/revcontent.js b/3p/vendors/revcontent.js new file mode 100644 index 0000000000000..03d56b2ec0245 --- /dev/null +++ b/3p/vendors/revcontent.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {revcontent} from '../../ads/vendors/revcontent'; + +init(window); +register('revcontent', revcontent); + +window.draw3p = draw3p; diff --git a/3p/vendors/revjet.js b/3p/vendors/revjet.js new file mode 100644 index 0000000000000..36b1a3248e203 --- /dev/null +++ b/3p/vendors/revjet.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {revjet} from '../../ads/vendors/revjet'; + +init(window); +register('revjet', revjet); + +window.draw3p = draw3p; diff --git a/3p/vendors/rfp.js b/3p/vendors/rfp.js new file mode 100644 index 0000000000000..f22ac4cb5c6c5 --- /dev/null +++ b/3p/vendors/rfp.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {rfp} from '../../ads/vendors/rfp'; + +init(window); +register('rfp', rfp); + +window.draw3p = draw3p; diff --git a/3p/vendors/rnetplus.js b/3p/vendors/rnetplus.js new file mode 100644 index 0000000000000..818cc88901165 --- /dev/null +++ b/3p/vendors/rnetplus.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {rnetplus} from '../../ads/vendors/rnetplus'; + +init(window); +register('rnetplus', rnetplus); + +window.draw3p = draw3p; diff --git a/3p/vendors/rubicon.js b/3p/vendors/rubicon.js new file mode 100644 index 0000000000000..ce51714271cca --- /dev/null +++ b/3p/vendors/rubicon.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {rubicon} from '../../ads/vendors/rubicon'; + +init(window); +register('rubicon', rubicon); + +window.draw3p = draw3p; diff --git a/3p/vendors/runative.js b/3p/vendors/runative.js new file mode 100644 index 0000000000000..cef21d5c6aa2e --- /dev/null +++ b/3p/vendors/runative.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {runative} from '../../ads/vendors/runative'; + +init(window); +register('runative', runative); + +window.draw3p = draw3p; diff --git a/3p/vendors/sas.js b/3p/vendors/sas.js new file mode 100644 index 0000000000000..025fee4c1a3bf --- /dev/null +++ b/3p/vendors/sas.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sas} from '../../ads/vendors/sas'; + +init(window); +register('sas', sas); + +window.draw3p = draw3p; diff --git a/3p/vendors/seedingalliance.js b/3p/vendors/seedingalliance.js new file mode 100644 index 0000000000000..b3ff421eb09e5 --- /dev/null +++ b/3p/vendors/seedingalliance.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {seedingalliance} from '../../ads/vendors/seedingalliance'; + +init(window); +register('seedingalliance', seedingalliance); + +window.draw3p = draw3p; diff --git a/3p/vendors/sekindo.js b/3p/vendors/sekindo.js new file mode 100644 index 0000000000000..5d17dcb110eb8 --- /dev/null +++ b/3p/vendors/sekindo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sekindo} from '../../ads/vendors/sekindo'; + +init(window); +register('sekindo', sekindo); + +window.draw3p = draw3p; diff --git a/3p/vendors/sharethrough.js b/3p/vendors/sharethrough.js new file mode 100644 index 0000000000000..e40c44872f689 --- /dev/null +++ b/3p/vendors/sharethrough.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sharethrough} from '../../ads/vendors/sharethrough'; + +init(window); +register('sharethrough', sharethrough); + +window.draw3p = draw3p; diff --git a/3p/vendors/shemedia.js b/3p/vendors/shemedia.js new file mode 100644 index 0000000000000..fd18582d80857 --- /dev/null +++ b/3p/vendors/shemedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {shemedia} from '../../ads/vendors/shemedia'; + +init(window); +register('shemedia', shemedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/sklik.js b/3p/vendors/sklik.js new file mode 100644 index 0000000000000..ae2e145bd3096 --- /dev/null +++ b/3p/vendors/sklik.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sklik} from '../../ads/vendors/sklik'; + +init(window); +register('sklik', sklik); + +window.draw3p = draw3p; diff --git a/3p/vendors/slimcutmedia.js b/3p/vendors/slimcutmedia.js new file mode 100644 index 0000000000000..2f240a4052469 --- /dev/null +++ b/3p/vendors/slimcutmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {slimcutmedia} from '../../ads/vendors/slimcutmedia'; + +init(window); +register('slimcutmedia', slimcutmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/smartadserver.js b/3p/vendors/smartadserver.js new file mode 100644 index 0000000000000..0a3aadb3bbaee --- /dev/null +++ b/3p/vendors/smartadserver.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {smartadserver} from '../../ads/vendors/smartadserver'; + +init(window); +register('smartadserver', smartadserver); + +window.draw3p = draw3p; diff --git a/3p/vendors/smartclip.js b/3p/vendors/smartclip.js new file mode 100644 index 0000000000000..6061a51cb1aad --- /dev/null +++ b/3p/vendors/smartclip.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {smartclip} from '../../ads/vendors/smartclip'; + +init(window); +register('smartclip', smartclip); + +window.draw3p = draw3p; diff --git a/3p/vendors/smi2.js b/3p/vendors/smi2.js new file mode 100644 index 0000000000000..ba741fc2622f1 --- /dev/null +++ b/3p/vendors/smi2.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {smi2} from '../../ads/vendors/smi2'; + +init(window); +register('smi2', smi2); + +window.draw3p = draw3p; diff --git a/3p/vendors/smilewanted.js b/3p/vendors/smilewanted.js new file mode 100644 index 0000000000000..94c6b8a65e05a --- /dev/null +++ b/3p/vendors/smilewanted.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {smilewanted} from '../../ads/vendors/smilewanted'; + +init(window); +register('smilewanted', smilewanted); + +window.draw3p = draw3p; diff --git a/3p/vendors/sogouad.js b/3p/vendors/sogouad.js new file mode 100644 index 0000000000000..b4445181d901b --- /dev/null +++ b/3p/vendors/sogouad.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sogouad} from '../../ads/vendors/sogouad'; + +init(window); +register('sogouad', sogouad); + +window.draw3p = draw3p; diff --git a/3p/vendors/sona.js b/3p/vendors/sona.js new file mode 100644 index 0000000000000..d227994fc5b35 --- /dev/null +++ b/3p/vendors/sona.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sona} from '../../ads/vendors/sona'; + +init(window); +register('sona', sona); + +window.draw3p = draw3p; diff --git a/3p/vendors/sortable.js b/3p/vendors/sortable.js new file mode 100644 index 0000000000000..e4c8078843962 --- /dev/null +++ b/3p/vendors/sortable.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sortable} from '../../ads/vendors/sortable'; + +init(window); +register('sortable', sortable); + +window.draw3p = draw3p; diff --git a/3p/vendors/sovrn.js b/3p/vendors/sovrn.js new file mode 100644 index 0000000000000..360e488b78dd1 --- /dev/null +++ b/3p/vendors/sovrn.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sovrn} from '../../ads/vendors/sovrn'; + +init(window); +register('sovrn', sovrn); + +window.draw3p = draw3p; diff --git a/3p/vendors/speakol.js b/3p/vendors/speakol.js new file mode 100644 index 0000000000000..c7f81f92432ba --- /dev/null +++ b/3p/vendors/speakol.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {speakol} from '../../ads/vendors/speakol'; + +init(window); +register('speakol', speakol); + +window.draw3p = draw3p; diff --git a/3p/vendors/spotx.js b/3p/vendors/spotx.js new file mode 100644 index 0000000000000..d3b4b187274d4 --- /dev/null +++ b/3p/vendors/spotx.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {spotx} from '../../ads/vendors/spotx'; + +init(window); +register('spotx', spotx); + +window.draw3p = draw3p; diff --git a/3p/vendors/springAds.js b/3p/vendors/springAds.js new file mode 100644 index 0000000000000..e154e7aaf5a19 --- /dev/null +++ b/3p/vendors/springAds.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {springAds} from '../../ads/vendors/springAds'; + +init(window); +register('springAds', springAds); + +window.draw3p = draw3p; diff --git a/3p/vendors/ssp.js b/3p/vendors/ssp.js new file mode 100644 index 0000000000000..fe2609fd44ab0 --- /dev/null +++ b/3p/vendors/ssp.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ssp} from '../../ads/vendors/ssp'; + +init(window); +register('ssp', ssp); + +window.draw3p = draw3p; diff --git a/3p/vendors/strossle.js b/3p/vendors/strossle.js new file mode 100644 index 0000000000000..7234c6655f097 --- /dev/null +++ b/3p/vendors/strossle.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {strossle} from '../../ads/vendors/strossle'; + +init(window); +register('strossle', strossle); + +window.draw3p = draw3p; diff --git a/3p/vendors/sulvo.js b/3p/vendors/sulvo.js new file mode 100644 index 0000000000000..7c53e41fdf32d --- /dev/null +++ b/3p/vendors/sulvo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sulvo} from '../../ads/vendors/sulvo'; + +init(window); +register('sulvo', sulvo); + +window.draw3p = draw3p; diff --git a/3p/vendors/sunmedia.js b/3p/vendors/sunmedia.js new file mode 100644 index 0000000000000..2940dac15f987 --- /dev/null +++ b/3p/vendors/sunmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {sunmedia} from '../../ads/vendors/sunmedia'; + +init(window); +register('sunmedia', sunmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/svknative.js b/3p/vendors/svknative.js new file mode 100644 index 0000000000000..21a6c49d09696 --- /dev/null +++ b/3p/vendors/svknative.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {svknative} from '../../ads/vendors/svknative'; + +init(window); +register('svknative', svknative); + +window.draw3p = draw3p; diff --git a/3p/vendors/swoop.js b/3p/vendors/swoop.js new file mode 100644 index 0000000000000..1ce656a495c7b --- /dev/null +++ b/3p/vendors/swoop.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {swoop} from '../../ads/vendors/swoop'; + +init(window); +register('swoop', swoop); + +window.draw3p = draw3p; diff --git a/3p/vendors/taboola.js b/3p/vendors/taboola.js new file mode 100644 index 0000000000000..ed40c7c7e4852 --- /dev/null +++ b/3p/vendors/taboola.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {taboola} from '../../ads/vendors/taboola'; + +init(window); +register('taboola', taboola); + +window.draw3p = draw3p; diff --git a/3p/vendors/tcsemotion.js b/3p/vendors/tcsemotion.js new file mode 100644 index 0000000000000..4e0a8f6d8378a --- /dev/null +++ b/3p/vendors/tcsemotion.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {tcsemotion} from '../../ads/vendors/tcsemotion'; + +init(window); +register('tcsemotion', tcsemotion); + +window.draw3p = draw3p; diff --git a/3p/vendors/teads.js b/3p/vendors/teads.js new file mode 100644 index 0000000000000..1534c09977600 --- /dev/null +++ b/3p/vendors/teads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {teads} from '../../ads/vendors/teads'; + +init(window); +register('teads', teads); + +window.draw3p = draw3p; diff --git a/3p/vendors/temedya.js b/3p/vendors/temedya.js new file mode 100644 index 0000000000000..e5fa647b4780b --- /dev/null +++ b/3p/vendors/temedya.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {temedya} from '../../ads/vendors/temedya'; + +init(window); +register('temedya', temedya); + +window.draw3p = draw3p; diff --git a/3p/vendors/torimochi.js b/3p/vendors/torimochi.js new file mode 100644 index 0000000000000..02e3bfcda8541 --- /dev/null +++ b/3p/vendors/torimochi.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {torimochi} from '../../ads/vendors/torimochi'; + +init(window); +register('torimochi', torimochi); + +window.draw3p = draw3p; diff --git a/3p/vendors/tracdelight.js b/3p/vendors/tracdelight.js new file mode 100644 index 0000000000000..75ce426dc2f20 --- /dev/null +++ b/3p/vendors/tracdelight.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {tracdelight} from '../../ads/vendors/tracdelight'; + +init(window); +register('tracdelight', tracdelight); + +window.draw3p = draw3p; diff --git a/3p/vendors/triplelift.js b/3p/vendors/triplelift.js new file mode 100644 index 0000000000000..4a914bf479296 --- /dev/null +++ b/3p/vendors/triplelift.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {triplelift} from '../../ads/vendors/triplelift'; + +init(window); +register('triplelift', triplelift); + +window.draw3p = draw3p; diff --git a/3p/vendors/trugaze.js b/3p/vendors/trugaze.js new file mode 100644 index 0000000000000..c95a1e3db542b --- /dev/null +++ b/3p/vendors/trugaze.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {trugaze} from '../../ads/vendors/trugaze'; + +init(window); +register('trugaze', trugaze); + +window.draw3p = draw3p; diff --git a/3p/vendors/twitter.js b/3p/vendors/twitter.js new file mode 100644 index 0000000000000..259b797829531 --- /dev/null +++ b/3p/vendors/twitter.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {twitter} from '../twitter'; + +init(window); +register('twitter', twitter); + +window.draw3p = draw3p; diff --git a/3p/vendors/uas.js b/3p/vendors/uas.js new file mode 100644 index 0000000000000..b76bbd684dd2c --- /dev/null +++ b/3p/vendors/uas.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {uas} from '../../ads/vendors/uas'; + +init(window); +register('uas', uas); + +window.draw3p = draw3p; diff --git a/3p/vendors/ucfunnel.js b/3p/vendors/ucfunnel.js new file mode 100644 index 0000000000000..380958966c72f --- /dev/null +++ b/3p/vendors/ucfunnel.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {ucfunnel} from '../../ads/vendors/ucfunnel'; + +init(window); +register('ucfunnel', ucfunnel); + +window.draw3p = draw3p; diff --git a/3p/vendors/unruly.js b/3p/vendors/unruly.js new file mode 100644 index 0000000000000..a2ce910657a45 --- /dev/null +++ b/3p/vendors/unruly.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {unruly} from '../../ads/vendors/unruly'; + +init(window); +register('unruly', unruly); + +window.draw3p = draw3p; diff --git a/3p/vendors/uzou.js b/3p/vendors/uzou.js new file mode 100644 index 0000000000000..d23c3d73387fb --- /dev/null +++ b/3p/vendors/uzou.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {uzou} from '../../ads/vendors/uzou'; + +init(window); +register('uzou', uzou); + +window.draw3p = draw3p; diff --git a/3p/vendors/valuecommerce.js b/3p/vendors/valuecommerce.js new file mode 100644 index 0000000000000..ad199f888964c --- /dev/null +++ b/3p/vendors/valuecommerce.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {valuecommerce} from '../../ads/vendors/valuecommerce'; + +init(window); +register('valuecommerce', valuecommerce); + +window.draw3p = draw3p; diff --git a/3p/vendors/vdoai.js b/3p/vendors/vdoai.js new file mode 100644 index 0000000000000..80f8c42c41aff --- /dev/null +++ b/3p/vendors/vdoai.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {vdoai} from '../../ads/vendors/vdoai'; + +init(window); +register('vdoai', vdoai); + +window.draw3p = draw3p; diff --git a/3p/vendors/verizonmedia.js b/3p/vendors/verizonmedia.js new file mode 100644 index 0000000000000..eb1a0db843d5a --- /dev/null +++ b/3p/vendors/verizonmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {verizonmedia} from '../../ads/vendors/verizonmedia'; + +init(window); +register('verizonmedia', verizonmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/videointelligence.js b/3p/vendors/videointelligence.js new file mode 100644 index 0000000000000..89eee85da10ff --- /dev/null +++ b/3p/vendors/videointelligence.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {videointelligence} from '../../ads/vendors/videointelligence'; + +init(window); +register('videointelligence', videointelligence); + +window.draw3p = draw3p; diff --git a/3p/vendors/videonow.js b/3p/vendors/videonow.js new file mode 100644 index 0000000000000..d50ef18a61c90 --- /dev/null +++ b/3p/vendors/videonow.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {videonow} from '../../ads/vendors/videonow'; + +init(window); +register('videonow', videonow); + +window.draw3p = draw3p; diff --git a/3p/vendors/viqeoplayer.js b/3p/vendors/viqeoplayer.js new file mode 100644 index 0000000000000..da93e2154465f --- /dev/null +++ b/3p/vendors/viqeoplayer.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {viqeoplayer} from '../viqeoplayer'; + +init(window); +register('viqeoplayer', viqeoplayer); + +window.draw3p = draw3p; diff --git a/3p/vendors/viralize.js b/3p/vendors/viralize.js new file mode 100644 index 0000000000000..1f9254bfad31f --- /dev/null +++ b/3p/vendors/viralize.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {viralize} from '../../ads/vendors/viralize'; + +init(window); +register('viralize', viralize); + +window.draw3p = draw3p; diff --git a/3p/vendors/vlyby.js b/3p/vendors/vlyby.js new file mode 100644 index 0000000000000..11dcb3bd08eaa --- /dev/null +++ b/3p/vendors/vlyby.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {vlyby} from '../../ads/vendors/vlyby'; + +init(window); +register('vlyby', vlyby); + +window.draw3p = draw3p; diff --git a/3p/vendors/vmfive.js b/3p/vendors/vmfive.js new file mode 100644 index 0000000000000..0870dd1875809 --- /dev/null +++ b/3p/vendors/vmfive.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {vmfive} from '../../ads/vendors/vmfive'; + +init(window); +register('vmfive', vmfive); + +window.draw3p = draw3p; diff --git a/3p/vendors/webediads.js b/3p/vendors/webediads.js new file mode 100644 index 0000000000000..bfb065c8dd169 --- /dev/null +++ b/3p/vendors/webediads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {webediads} from '../../ads/vendors/webediads'; + +init(window); +register('webediads', webediads); + +window.draw3p = draw3p; diff --git a/3p/vendors/weborama-display.js b/3p/vendors/weborama-display.js new file mode 100644 index 0000000000000..98222ead7e6a7 --- /dev/null +++ b/3p/vendors/weborama-display.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {weboramaDisplay} from '../../ads/vendors/weborama'; + +init(window); +register('weborama-display', weboramaDisplay); + +window.draw3p = draw3p; diff --git a/3p/vendors/whopainfeed.js b/3p/vendors/whopainfeed.js new file mode 100644 index 0000000000000..8a61ae9ae7f47 --- /dev/null +++ b/3p/vendors/whopainfeed.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {whopainfeed} from '../../ads/vendors/whopainfeed'; + +init(window); +register('whopainfeed', whopainfeed); + +window.draw3p = draw3p; diff --git a/3p/vendors/widespace.js b/3p/vendors/widespace.js new file mode 100644 index 0000000000000..ec67960a2e853 --- /dev/null +++ b/3p/vendors/widespace.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {widespace} from '../../ads/vendors/widespace'; + +init(window); +register('widespace', widespace); + +window.draw3p = draw3p; diff --git a/3p/vendors/wisteria.js b/3p/vendors/wisteria.js new file mode 100644 index 0000000000000..2f737de20553d --- /dev/null +++ b/3p/vendors/wisteria.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {wisteria} from '../../ads/vendors/wisteria'; + +init(window); +register('wisteria', wisteria); + +window.draw3p = draw3p; diff --git a/3p/vendors/wpmedia.js b/3p/vendors/wpmedia.js new file mode 100644 index 0000000000000..a4bd1607bd0e4 --- /dev/null +++ b/3p/vendors/wpmedia.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {wpmedia} from '../../ads/vendors/wpmedia'; + +init(window); +register('wpmedia', wpmedia); + +window.draw3p = draw3p; diff --git a/3p/vendors/xlift.js b/3p/vendors/xlift.js new file mode 100644 index 0000000000000..3bcd2d72845ed --- /dev/null +++ b/3p/vendors/xlift.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {xlift} from '../../ads/vendors/xlift'; + +init(window); +register('xlift', xlift); + +window.draw3p = draw3p; diff --git a/3p/vendors/yahoo.js b/3p/vendors/yahoo.js new file mode 100644 index 0000000000000..a7e0ab13df0a9 --- /dev/null +++ b/3p/vendors/yahoo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yahoo} from '../../ads/vendors/yahoo'; + +init(window); +register('yahoo', yahoo); + +window.draw3p = draw3p; diff --git a/3p/vendors/yahoofedads.js b/3p/vendors/yahoofedads.js new file mode 100644 index 0000000000000..d00ec92604291 --- /dev/null +++ b/3p/vendors/yahoofedads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yahoofedads} from '../../ads/vendors/yahoofedads'; + +init(window); +register('yahoofedads', yahoofedads); + +window.draw3p = draw3p; diff --git a/3p/vendors/yahoojp.js b/3p/vendors/yahoojp.js new file mode 100644 index 0000000000000..6a560b0d365ef --- /dev/null +++ b/3p/vendors/yahoojp.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yahoojp} from '../../ads/vendors/yahoojp'; + +init(window); +register('yahoojp', yahoojp); + +window.draw3p = draw3p; diff --git a/3p/vendors/yahoonativeads.js b/3p/vendors/yahoonativeads.js new file mode 100644 index 0000000000000..5cfdc5f376db8 --- /dev/null +++ b/3p/vendors/yahoonativeads.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yahoonativeads} from '../../ads/vendors/yahoonativeads'; + +init(window); +register('yahoonativeads', yahoonativeads); + +window.draw3p = draw3p; diff --git a/3p/vendors/yandex.js b/3p/vendors/yandex.js new file mode 100644 index 0000000000000..86800fa8febc8 --- /dev/null +++ b/3p/vendors/yandex.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yandex} from '../../ads/vendors/yandex'; + +init(window); +register('yandex', yandex); + +window.draw3p = draw3p; diff --git a/3p/vendors/yektanet.js b/3p/vendors/yektanet.js new file mode 100644 index 0000000000000..f7055840ff3d3 --- /dev/null +++ b/3p/vendors/yektanet.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yektanet} from '../../ads/vendors/yektanet'; + +init(window); +register('yektanet', yektanet); + +window.draw3p = draw3p; diff --git a/3p/vendors/yengo.js b/3p/vendors/yengo.js new file mode 100644 index 0000000000000..df79bf89515cf --- /dev/null +++ b/3p/vendors/yengo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yengo} from '../../ads/vendors/yengo'; + +init(window); +register('yengo', yengo); + +window.draw3p = draw3p; diff --git a/3p/vendors/yieldbot.js b/3p/vendors/yieldbot.js new file mode 100644 index 0000000000000..f6552a8db50a5 --- /dev/null +++ b/3p/vendors/yieldbot.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yieldbot} from '../../ads/vendors/yieldbot'; + +init(window); +register('yieldbot', yieldbot); + +window.draw3p = draw3p; diff --git a/3p/vendors/yieldmo.js b/3p/vendors/yieldmo.js new file mode 100644 index 0000000000000..961f882d77eca --- /dev/null +++ b/3p/vendors/yieldmo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yieldmo} from '../../ads/vendors/yieldmo'; + +init(window); +register('yieldmo', yieldmo); + +window.draw3p = draw3p; diff --git a/3p/vendors/yieldone.js b/3p/vendors/yieldone.js new file mode 100644 index 0000000000000..bdaa477555261 --- /dev/null +++ b/3p/vendors/yieldone.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yieldone} from '../../ads/vendors/yieldone'; + +init(window); +register('yieldone', yieldone); + +window.draw3p = draw3p; diff --git a/3p/vendors/yieldpro.js b/3p/vendors/yieldpro.js new file mode 100644 index 0000000000000..aa67b6283f663 --- /dev/null +++ b/3p/vendors/yieldpro.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yieldpro} from '../../ads/vendors/yieldpro'; + +init(window); +register('yieldpro', yieldpro); + +window.draw3p = draw3p; diff --git a/3p/vendors/yotpo.js b/3p/vendors/yotpo.js new file mode 100644 index 0000000000000..39c3c5deb84f3 --- /dev/null +++ b/3p/vendors/yotpo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {yotpo} from '../yotpo'; + +init(window); +register('yotpo', yotpo); + +window.draw3p = draw3p; diff --git a/3p/vendors/zedo.js b/3p/vendors/zedo.js new file mode 100644 index 0000000000000..2cbf478ec951a --- /dev/null +++ b/3p/vendors/zedo.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {zedo} from '../../ads/vendors/zedo'; + +init(window); +register('zedo', zedo); + +window.draw3p = draw3p; diff --git a/3p/vendors/zen.js b/3p/vendors/zen.js new file mode 100644 index 0000000000000..76d285fe636e4 --- /dev/null +++ b/3p/vendors/zen.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {zen} from '../../ads/vendors/zen'; + +init(window); +register('zen', zen); + +window.draw3p = draw3p; diff --git a/3p/vendors/zergnet.js b/3p/vendors/zergnet.js new file mode 100644 index 0000000000000..3f70d5b453e44 --- /dev/null +++ b/3p/vendors/zergnet.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {zergnet} from '../../ads/vendors/zergnet'; + +init(window); +register('zergnet', zergnet); + +window.draw3p = draw3p; diff --git a/3p/vendors/zucks.js b/3p/vendors/zucks.js new file mode 100644 index 0000000000000..2f570a1eb57bc --- /dev/null +++ b/3p/vendors/zucks.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// src/polyfills.js must be the first import. +import '../polyfills'; + +import {draw3p, init} from '../integration-lib'; +import {register} from '../3p'; + +import {zucks} from '../../ads/vendors/zucks'; + +init(window); +register('zucks', zucks); + +window.draw3p = draw3p; diff --git a/3p/viqeoplayer.js b/3p/viqeoplayer.js index a04f8a1cbed28..a37336278cf4b 100644 --- a/3p/viqeoplayer.js +++ b/3p/viqeoplayer.js @@ -15,7 +15,7 @@ */ import {getData} from '../src/event-helper'; import {loadScript} from './3p'; -import {tryDecodeUriComponent} from '../src/url'; +import {tryDecodeUriComponent} from '../src/core/types/string/url'; /** * @param {Window} global diff --git a/AMP_MODULE_BUILD.md b/AMP_MODULE_BUILD.md deleted file mode 100644 index db65d64d3a1d7..0000000000000 --- a/AMP_MODULE_BUILD.md +++ /dev/null @@ -1,78 +0,0 @@ -# AMP Module Build - -## Explainer - -ES Modules is an official, standardized module system for JavaScript. However, on the AMP Project and for many in the wider web community, they are also an inflection point for supported JavaScript features from user-agents that is declarative and well known. When a browser supports ES Modules we can infer it also supports other modern features that were implemented before ES Modules were introduced. These include classes, promises, async/await, css variables, custom elements, window.fetch, let/const, arrow functions, and Shadow DOM. Like others, we're using this signal to remove polyfills and leverage modern JavaScript syntax more closely mirroring our authored source. Even better, this output is significantly terser than our previous output (which was compiled to ~ECMAScript 5) and helps improve performance for all AMP Documents. - -The [module/nomodule pattern](https://philipwalton.com/articles/deploying-es2015-code-in-production-today/) is a way to load the modern "module" build of the AMP runtime in a way were there would still be a fallback "nomodule" build if the browser does not support ES Modules. - -The HTML markup would look like: - -```html - - -``` - -## Recommended Browser Support for Module build - -There are some idiosyncrasies to how the module/nomodule pattern is loaded in -different browsers so we must take care in selecting the browsers we apply the -module/nomodule pattern. - -Some AMP environments such as a normal web page, or an AMP story page may be -OK with "double fetching" where the browser will request both the module and -nomodule build, but environments like AMP for Ads are more sensitive to -additional request and might not be OK with the double fetching. - -The Original documentation/source for the table and summary below can be found [here](https://gist.github.com/jakub-g/5fc11af85a061ca29cc84892f1059fec). - -| IE/Edge | Firefox | Chrome | Safari | fetches module | fetches nomodule | executes | | -| :-----: | :-----: | :----: | :-----: | :------------: | :--------------: | :------: | ---- | -| 15- | 59- | 55- | 10.0- | v | v | nomodule | ❌ | -| 16 | | | 10.1/3 | v | v | module | ❌ | -| 17-18 | | | | double! | v | module | ❌❌ | -| | | 56-60 | | | v | nomodule | ✅ | -| | 60+ | 61+ | 11.0+\* | v | | module | ✅ | - -Summary: - -- ✅ no browser does double execution (provided [the Safari hack](https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc)) -- ✅ modern Chrome and Firefox never fetch more than necessary -- ⚠ Safari <11 may or may not double fetch (even with the hack); it does not on small test pages, but in real complex pages it does (it seems deterministic, but not clear what's the exact trigger) -- ⚠ Safari 11+ may still double fetch in some cases (see https://bugs.webkit.org/show_bug.cgi?id=194337) -- ❌ pre-2018 browsers do double fetches -- ❌❌ latest Edge does triple fetch (2x module + 1x nomodule) - -The AMP Project recommends applying the module/nomodule pattern for Google Chrome, Microsoft Edge >= 79, Safari >= 11, and Firefox >= 60. - -For other user-agents, we recommend sticking with the traditional single script loading model as part of our standard boilerplate. - -Specifically, this will help avoid the double or triple fetching issues (denoted by an ❌in the table above). - -## High Level Detailed Difference in Output - -We use the [babel preset-env plugin](https://babeljs.io/blog/2020/03/16/7.9.0) with `{bugfixes: true}` enabled to support module builds. - -At a high level we now avoid compiling the following in the AMP "module" output: - -- classes -- arrow functions -- async/await -- Object and Array spreads -- Array destructuring - -We Also remove the following polyfills: - -- document.contains -- DOMTokenList -- window.fetch -- Math.sign -- Object.assign -- Object.values -- Promises -- Array.includes -- CSS.escape() - -## Report a bug - -[File an issue](https://github.com/ampproject/amphtml/issues/new?assignees=&labels=Type%3A+Bug&template=bug-report.md&title=) if you find a bug in AMP. Provide a detailed description and steps for reproducing the bug; screenshots and working examples that demonstrate the problem are appreciated! diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b74de28e04b78..bdc2810b80918 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1 +1 @@ -See https://github.com/ampproject/meta/blob/master/CODE_OF_CONDUCT.md +See https://github.com/ampproject/meta/blob/main/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 78883b3ab4b9d..0000000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,71 +0,0 @@ -# Contributing to AMP HTML - -The AMP HTML project strongly encourages your participation and [contributions](https://www.ampproject.org/contribute/)! - -We hope you'll become an ongoing participant in our open source community but we also welcome one-off contributions for the issues you're particularly passionate about. - -How would you like to help? - -- [Report a bug](#report-a-bug) -- [Make a suggestion](#make-a-suggestion) -- [Contribute code/features](#contribute-codefeatures) -- [Get started with open source](#get-started-with-open-source) -- [Participate in ongoing discussions](#ongoing-participation) - -If you have questions about _using_ AMP or are _encountering problems using AMP_ on your site please visit our [support page](SUPPORT.md) for help. - -## Report a bug - -[File an issue](https://github.com/ampproject/amphtml/issues/new?assignees=&labels=Type%3A+Bug&template=bug-report.md&title=) if you find a bug in AMP. Provide a detailed description and steps for reproducing the bug; screenshots and working examples that demonstrate the problem are appreciated! - -The community regularly monitoring issues and will try to fix open bugs quickly according to our [prioritization guidelines](./contributing/issue-priorities.md). - -## Make a suggestion - -[File a "feature request" issue](https://github.com/ampproject/amphtml/issues/new?assignees=&labels=Type%3A+Feature+Request&template=feature_request.md&title=) if you have a suggestion for a way to improve AMP or a request for a new AMP feature. - -If you are suggesting a feature that you are intending to implement, please see the [Contributing code/features](#contribute-code-features) section below for next steps. - -## Contribute code/features - -We'd love to have your help contributing code and features to AMP! - -See the [Contributing code and features](contributing/contributing-code.md) document for details on the process you can use to add code/features. - -## Get started with open source - -If you are new to contributing to an open source project, Git/GitHub, etc. welcome! We are glad you're interested in contributing to AMP and we want to help make your open source experience a success. - -The [Getting Started End-to-End Guide](./contributing/getting-started-e2e.md) provides step-by-step instructions for everything from creating a GitHub account to getting your code reviewed and merged. Even if you've never contributed to an open source project before you'll soon be building AMP, making improvements and seeing your code live across the web. - -The community has created a list of [Good First Issues](https://github.com/ampproject/amphtml/labels/good%20first%20issue) specifically for new contributors to the project. Feel free to find one of the [unclaimed Good First Issues](https://github.com/ampproject/amphtml/issues?utf8=%E2%9C%93&q=is%3Aopen%20label%3A%22good%20first%20issue%22%20-label%3A%22GFI%20Claimed!%22) that interests you, claim it by adding a comment to it and jump in! - -If you're interested in helping out but can't find a Good First Issue that matches your skills/interests, [sign up for our Slack](https://bit.ly/amp-slack-signup) and then reach out in the [#welcome-contributors channel](https://amphtml.slack.com/messages/welcome-contributors/) or send a Direct Message to [mrjoro](https://amphtml.slack.com/team/mrjoro/). - -If you run into any problems we have plenty of people who are willing to help; see the [How to get help](./contributing/getting-started-e2e.md#how-to-get-help) section of the Getting Started guide. - -## Ongoing participation - -We actively encourage ongoing participation by community members. - -### Discussion channels - -Technical issues, designs, etc. are discussed using several different channels: - -- [GitHub issues](https://github.com/ampproject/amphtml/issues) and [pull requests](https://github.com/ampproject/amphtml/pulls) -- [Slack](https://amphtml.slack.com) ([signup](https://bit.ly/amp-slack-signup)) - - the [#contributing](https://amphtml.slack.com/messages/C9HRJ1GPN/details/) channel is the main channel for you to discuss/ask questions about _contributing_ to the open source project - - if you're _new to contributing_ to AMP stop by [#welcome-contributors](https://amphtml.slack.com/messages/C432AFMFE/details/) to say hi! - - **NOTE: if you have a question about _using AMP on your site_, use [Stack Overflow](https://stackoverflow.com/questions/tagged/amp-html) rather than Slack** as Stack Overflow is more actively monitored for these types of questions - - there are many other Slack channels for more specific topics; after you join our Slack click on the "Channels" header to find other channels you want to participate in -- the [amphtml-discuss Google Group](https://groups.google.com/forum/#!forum/amphtml-discuss) - -### Working groups - -Most of the day-to-day work in building AMP happens in AMP's [Working Groups (WGs)](https://github.com/ampproject/meta/tree/master/working-groups). Getting involved in a WG is a great way to contribute to AMP. - -Most working groups post approximately bi-weekly [Status Update GitHub issues](https://github.com/search?q=org%3Aampproject+label%3A%22Type%3A+Status+Update%22&type=Issues). - -### Weekly design reviews - -The community holds weekly engineering [design reviews](./contributing/design-reviews.md) via video conference. We encourage everyone in the community to participate in these discussions and to bring their designs for new features and significant bug fixes to these reviews. diff --git a/Dockerfile b/Dockerfile index 923878fd7a083..22182552a6862 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,14 @@ -FROM node:12 +FROM node:14 MAINTAINER Format team -RUN yarn global add gulp-cli - ADD . /var/www WORKDIR /var/www -RUN yarn +RUN npm i gulp +RUN amp build -RUN gulp build EXPOSE 8000 -CMD gulp serve --host=0.0.0.0 +CMD amp serve diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index 87eac8cfb3bef..0000000000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1 +0,0 @@ -See https://github.com/ampproject/meta/blob/master/GOVERNANCE.md diff --git a/METRICS.md b/METRICS.md deleted file mode 100644 index 198a9296cc651..0000000000000 --- a/METRICS.md +++ /dev/null @@ -1,37 +0,0 @@ -# Project Metrics - -## Absolute Code Coverage - -Test coverage for the repository as computed by CodeCov - -![Absolute Code Coverage](https://amp-project-metrics.appspot.com/api/plot/AbsoluteCoverageMetric.png) - -## Presubmit Latency - -Average Travis build time over the last 90 days - -![Presubmit Latency](https://amp-project-metrics.appspot.com/api/plot/PresubmitLatencyMetric.png) - -## Cherrypick Issue Count - -Number of cherry-pick issues in the last 90 days - -![Cherrypick Issue Count](https://amp-project-metrics.appspot.com/api/plot/CherrypickIssueCountMetric.png) - -## Release Granularity - -Average commits per release over the last 90 days - -![Release Granularity](https://amp-project-metrics.appspot.com/api/plot/ReleaseGranularityMetric.png) - -## Travis Greenness - -Percentage of green Travis builds over the last 90 days - -![Travis Greenness](https://amp-project-metrics.appspot.com/api/plot/TravisGreennessMetric.png) - -## Travis Flakiness - -Percentage of flaky Travis builds over the last 90 days - -![Travis Flakiness](https://amp-project-metrics.appspot.com/api/plot/TravisFlakinessMetric.png) diff --git a/OWNERS b/OWNERS index 7d5ce46475c02..28362bb9c6c7a 100644 --- a/OWNERS +++ b/OWNERS @@ -1,14 +1,15 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { - reviewerTeam: 'ampproject/reviewers-amphtml', + reviewerPool: ['ampproject/reviewers-amphtml', 'renovate-approve[bot]'], rules: [ { owners: [ {name: 'cramforce', requestReviews: false}, {name: 'dvoytenko', requestReviews: false}, {name: 'jridgewell', requestReviews: false}, + {name: 'kristoferbaxter', requestReviews: false}, ], }, { @@ -17,20 +18,30 @@ }, { pattern: '**/*.md', - owners: [{name: 'mrjoro'}, {name: 'ampproject/wg-outreach'}], + owners: [{name: '*'}], }, { - pattern: '{.*,gulpfile.js}', + pattern: '{.*,amp.js}', owners: [{name: 'ampproject/wg-infra'}], }, { - pattern: '{babel.config.js,package.json,yarn.lock}', + pattern: '{babel.config.js,package-scripts.js}', owners: [ {name: 'ampproject/wg-infra'}, - {name: 'ampproject/wg-runtime'}, {name: 'ampproject/wg-performance'}, ], }, + { + pattern: '{package.json,package-lock.json}', + owners: [ + {name: 'ampproject/wg-infra', requestReviews: false}, + {name: 'ampproject/wg-performance', requestReviews: false}, + ], + }, + { + pattern: '**/package{,-lock}.json', + owners: [{name: 'renovate-bot'}], + }, { pattern: '**/OWNERS', owners: [{name: 'mrjoro', requestReviews: false}], diff --git a/README.md b/README.md index 3bf2174029e40..e5d29ea34c5a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # AMP ⚡ -[![Build Status](https://img.shields.io/travis/ampproject/amphtml/master.svg?logo=Travis%20CI&logoColor=white&style=flat-square 'Build Status')](https://travis-ci.org/ampproject/amphtml/builds) +⚡⚡⚡ + +[![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)](#) @@ -12,11 +14,8 @@ 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/) -[![Presubmit Latency](https://img.shields.io/endpoint.svg?logo=Travis%20CI&logoColor=white&style=flat-square&url=https%3A%2F%2Famp-project-metrics.appspot.com%2Fapi%2Fbadge%2FPresubmitLatencyMetric 'Average Travis build time over the last 90 days')](https://travis-ci.org/ampproject/amphtml/builds) [![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) -[![Travis Greenness](https://img.shields.io/endpoint.svg?logo=Travis%20CI&logoColor=white&style=flat-square&url=https%3A%2F%2Famp-project-metrics.appspot.com%2Fapi%2Fbadge%2FTravisGreennessMetric 'Percentage of green Travis builds over the last 90 days')](https://travis-ci.org/ampproject/amphtml/builds) -[![Travis Flakiness](https://img.shields.io/endpoint.svg?logo=Travis%20CI&logoColor=white&style=flat-square&url=https%3A%2F%2Famp-project-metrics.appspot.com%2Fapi%2Fbadge%2FTravisFlakinessMetric 'Percentage of flaky Travis builds over the last 90 days')](https://travis-ci.org/ampproject/amphtml/builds) @@ -40,31 +39,31 @@ AMP is an open source project, and we'd love your help making it better! ## Want to know more about AMP? -- [amp.dev](https://amp.dev) is the best place to learn more about AMP--and of course the site is made using AMP! -- For developers using AMP, amp.dev includes - - [guides and tutorials](https://amp.dev/documentation/guides-and-tutorials/) - - [examples](https://amp.dev/documentation/examples/) - - [reference docs](https://amp.dev/documentation/components/?format=websites) - - [example templates](https://amp.dev/documentation/templates/) - - [tools to make using AMP easier](https://amp.dev/documentation/tools) +- [amp.dev](https://amp.dev) is the best place to learn more about AMP--and of course the site is made using AMP! +- For developers using AMP, amp.dev includes + - [guides and tutorials](https://amp.dev/documentation/guides-and-tutorials/) + - [examples](https://amp.dev/documentation/examples/) + - [reference docs](https://amp.dev/documentation/components/?format=websites) + - [example templates](https://amp.dev/documentation/templates/) + - [tools to make using AMP easier](https://amp.dev/documentation/tools) ## Having a problem using AMP? -- The [amp.dev Support page](https://amp.dev/support/) has resources for getting help. -- Use [Stack Overflow](http://stackoverflow.com/questions/tagged/amp-html) to ask questions about using AMP and find answers to questions others have asked. -- [Let us know about bugs](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#report-a-bug), and [file feature requests](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#make-a-suggestion) to suggest improvements. -- AMP accepts responsible security disclosures through the [Google Application Security program](https://www.google.com/about/appsecurity/). +- The [amp.dev Support page](https://amp.dev/support/) has resources for getting help. +- Use [Stack Overflow](http://stackoverflow.com/questions/tagged/amp-html) to ask questions about using AMP and find answers to questions others have asked. +- [Let us know about bugs](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md#report-a-bug), and [file feature requests](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md#make-a-suggestion) to suggest improvements. +- AMP accepts responsible security disclosures through the [Google Application Security program](https://www.google.com/about/appsecurity/). ## Want to help make AMP better? -- [CONTRIBUTING.md](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md) has information on how you can help improve AMP, including [ongoing participation](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#ongoing-participation) through Slack, weekly design reviews, etc. -- We strongly encourage [code contributions](https://github.com/ampproject/amphtml/blob/master/contributing/contributing-code.md)! -- **We enthusiastically welcome new contributors to AMP _even if you have no experience being part of an open source project_**. We've made it easy to [get started contributing](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#get-started-with-open-source). -- Consider joining one of AMP's [Working Groups](https://github.com/ampproject/meta/tree/master/working-groups), where most of the day-to-day work in AMP gets done. +- [docs/contributing.md](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md) has information on how you can help improve AMP, including [ongoing participation](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md#ongoing-participation) through Slack, weekly design reviews, etc. +- We strongly encourage [code contributions](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md)! +- **We enthusiastically welcome new contributors to AMP _even if you have no experience being part of an open source project_**. We've made it easy to [get started contributing](https://github.com/ampproject/amphtml/blob/main/docs/contributing.md#get-started-with-open-source). +- Consider joining one of AMP's [Working Groups](https://github.com/ampproject/meta/tree/main/working-groups), where most of the day-to-day work in AMP gets done. ## Other useful information -- [AMP's release documentation](contributing/release-schedule.md) provides details on how and when AMP releases new versions, including [how to know when a change is live in a release](https://github.com/ampproject/amphtml/blob/master/contributing/release-schedule.md#determining-if-your-change-is-in-a-release). -- [AMP's roadmap](https://amp.dev/community/roadmap) provides details on some of the significant projects we are working on. -- The [AMP meta repository](https://github.com/ampproject/meta) has information _about_ the AMP open source project, including AMP's [governance](https://github.com/ampproject/meta/blob/master/GOVERNANCE.md). -- [AMP's code of conduct](https://github.com/ampproject/meta/blob/master/CODE_OF_CONDUCT.md) documents how all members, committers and volunteers in the community are required to act. AMP strives for a positive and growing project community that provides a safe environment for everyone. +- [AMP's release documentation](docs/release-schedule.md) provides details on how and when AMP releases new versions, including [how to know when a change is live in a release](https://github.com/ampproject/amphtml/blob/main/docs/release-schedule.md#determining-if-your-change-is-in-a-release). +- [AMP's roadmap](https://amp.dev/community/roadmap) provides details on some of the significant projects we are working on. +- The [AMP meta repository](https://github.com/ampproject/meta) has information _about_ the AMP open source project, including AMP's [governance](https://github.com/ampproject/meta/blob/main/docs/governance.md). +- [AMP's code of conduct](https://github.com/ampproject/meta/blob/main/CODE_OF_CONDUCT.md) documents how all members, committers and volunteers in the community are required to act. AMP strives for a positive and growing project community that provides a safe environment for everyone. diff --git a/SUPPORT.md b/SUPPORT.md deleted file mode 100644 index dc432985105ec..0000000000000 --- a/SUPPORT.md +++ /dev/null @@ -1,26 +0,0 @@ - - -# Getting Support - -There are many ways to get help for questions and issues related to AMP: - -**Need help with AMP?** - -If you are looking for help to get started using AMP on your site or you are having issues using AMP, consult these resources: - -- [Official documentation](https://amp.dev/documentation/guides-and-tutorials/) with guides and tutorials to help you learn about AMP. -- [Hands-on samples and demos](https://amp.dev/documentation/examples/) for using AMP components. -- [Pre-styled templates and components](https://amp.dev/documentation/templates/) that you can use to create styled AMP sites from scratch. -- [AMP tools](https://amp.dev/documentation/tools) helping you get started with building AMP pages. -- [AMP supported browsers](https://amp.dev/support/faq/supported-browsers) to learn which browsers support AMP. -- [Stack Overflow](http://stackoverflow.com/questions/tagged/amp-html) is our recommended way to find answers to questions about AMP; since members of the AMP Project community regularly monitor Stack Overflow you will probably receive the fastest response to your questions there. -- For AMP on Google Search questions or issues, please use [Google's AMP forum](https://goo.gl/utQ1KZ). -- To check the status of AMP serving and its related services, see the [AMP Status](https://status.ampproject.org/) page. - -**Found a bug? Suggest a feature?** - -If you encounter a bug in AMP or have a feature request for AMP, see [Reporting issues with AMP](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#reporting-issues-with-amp) for information on filing an issue or requesting features. - -**Contributing to AMP?** - -[Contributing to AMP HTML](https://github.com/ampproject/amphtml/blob/master/CONTRIBUTING.md#ongoing-participation) is a great place to find out how you can make contributions to the AMP open source project and how you can get help if you run into questions when contributing to AMP. diff --git a/ads/1wo.js b/ads/1wo.js deleted file mode 100644 index 754569f2012bb..0000000000000 --- a/ads/1wo.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function _1wo(global, data) { - validateData(data, ['src', 'owoType', 'owoCode', 'owoMode']); - 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-owo-type', data['owoType']); - d.setAttribute('data-owo-code', data['owoCode']); - d.setAttribute('data-owo-mode', data['owoMode']); - global.document.getElementById('c').appendChild(d); -} diff --git a/ads/24smi.js b/ads/24smi.js deleted file mode 100644 index 6e5d5dbc65842..0000000000000 --- a/ads/24smi.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData, validateSrcPrefix} from '../3p/3p'; - -const jsnPrefix = 'https://jsn.24smi.net/'; -const smiJs = `${jsnPrefix}smi.js`; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function _24smi(global, data) { - validateData(data, [['blockid', 'src']]); - const {src} = data; - let blockId = data['blockid']; - - if (!blockId) { - validateSrcPrefix(jsnPrefix, src); - blockId = getBlockId(src); - } - - const element = createContainer(global); - (global.smiq = global.smiq || []).push({ - element, - blockId, - }); - loadScript(global, smiJs); -} - -/** - * @param {!Window} global - * @return {Element} - */ -function createContainer(global) { - const d = global.document.createElement('div'); - global.document.getElementById('c').appendChild(d); - return d; -} - -/** - * @param {string} src - * @return {string} - */ -function getBlockId(src) { - const parts = src.split('/'); - return parts[parts.length - 1].split('.')[0]; -} diff --git a/ads/OWNERS b/ads/OWNERS index 94b65ebe76929..1b8c3b93f6d99 100644 --- a/ads/OWNERS +++ b/ads/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/ads/README.md b/ads/README.md index 9e443f7c0f6c6..e6597dd4c67f8 100644 --- a/ads/README.md +++ b/ads/README.md @@ -4,24 +4,35 @@ This guide provides details for ad networks to create an `amp-ad` integration fo **Table of contents** -- [Overview](#overview) -- [Constraints](#constraints) -- [The iframe sandbox](#the-iframe-sandbox) - - [Available information](#available-information) - - [Available APIs](#available-apis) - - [Exceptions to available APIs and information](#exceptions-to-available-apis-and-information) - - [Ad viewability](#ad-viewability) - - [Ad resizing](#ad-resizing) - - [Support for multi-size ad requests](#support-for-multi-size-ad-requests) - - [Optimizing ad performance](#optimizing-ad-performance) - - [Ad markup](#ad-markup) - - [1st party cookies](#1st-party-cookies) -- [Developer guidelines for a pull request](#developer-guidelines-for-a-pull-request) - - [Files to change](#files-to-change) - - [Verify your examples](#verify-your-examples) - - [Tests](#tests) - - [Other tips](#other-tips) -- [Developer announcements for ads related API changes ](#developer-announcements-for-ads-related-API-changes) + + + + +- [Overview](#overview) +- [Constraints](#constraints) +- [The iframe sandbox](#the-iframe-sandbox) + - [Available information to the ad](#available-information-to-the-ad) + - [Available APIs](#available-apis) + - [Exceptions to available APIs and information](#exceptions-to-available-apis-and-information) + - [Ad viewability](#ad-viewability) + - [Ad resizing](#ad-resizing) + - [Support for multi-size ad requests](#support-for-multi-size-ad-requests) + - [amp-consent integration](#amp-consent-integration) + - [Optimizing ad performance](#optimizing-ad-performance) + - [Ad markup](#ad-markup) + - [1st party cookies](#1st-party-cookies) +- [Developer guidelines for a pull request](#developer-guidelines-for-a-pull-request) + - [Files to change](#files-to-change) + - [Verify your examples](#verify-your-examples) + - [Tests](#tests) + - [Lint and type-check](#lint-and-type-check) + - [Other tips](#other-tips) +- [Developer announcements for ads related API changes](#developer-announcements-for-ads-related-api-changes) ## Overview @@ -33,15 +44,15 @@ If you are an ad technology provider looking to integrate with AMP HTML, please Below is a summary of constraints placed on external resources, such as ads in AMP HTML: -- Because AMP pages are served on HTTPS and ads cannot be proxied, ads must be served over HTTPS. -- The size of an ad unit must be static. It must be knowable without fetching the ad and it cannot change at runtime except through [iframe resizing](#ad-resizing). -- If placing the ad requires running JavaScript (assumed to be true for 100% of ads served through networks), the ad must be placed on an origin different from the AMP document itself. Reasons include: - - Improved security. - - Takes synchronous HTTP requests made by the ad out of the critical rendering path of the primary page. - - Allows browsers to run the ad in a different process from the primary page (even better security and prevents JS inside the ad to block the main page UI thread). - - Prevents ads doing less than optimal things to measure user behavior and other interference with the primary page. -- The AMP Runtime may at any moment decide that there are too many iframes on a page and that memory is low. In that case, the AMP Runtime unloads ads that were previously loaded and are no longer visible. It may later load new ads in the same slot if the user scrolls them back into view. -- The AMP Runtime may decide to set an ad that is currently not visible to `display: none` to reduce browser layout and compositing cost. +- Because AMP pages are served on HTTPS and ads cannot be proxied, ads must be served over HTTPS. +- The size of an ad unit must be static. It must be knowable without fetching the ad and it cannot change at runtime except through [iframe resizing](#ad-resizing). +- If placing the ad requires running JavaScript (assumed to be true for 100% of ads served through networks), the ad must be placed on an origin different from the AMP document itself. Reasons include: + - Improved security. + - Takes synchronous HTTP requests made by the ad out of the critical rendering path of the primary page. + - Allows browsers to run the ad in a different process from the primary page (even better security and prevents JS inside the ad to block the main page UI thread). + - Prevents ads doing less than optimal things to measure user behavior and other interference with the primary page. +- The AMP Runtime may at any moment decide that there are too many iframes on a page and that memory is low. In that case, the AMP Runtime unloads ads that were previously loaded and are no longer visible. It may later load new ads in the same slot if the user scrolls them back into view. +- The AMP Runtime may decide to set an ad that is currently not visible to `display: none` to reduce browser layout and compositing cost. ## The iframe sandbox @@ -78,7 +89,7 @@ The AMP runtime provides the following information to the ad:
window.context.sourceUrl
-
Contains the source URL of the original AMP document. See details here.
+
Contains the source URL of the original AMP document. See details here.
window.context.startTime
Contains the time at which processing of the amp-ad element started.
@@ -106,7 +117,7 @@ Depending on the ad server / provider, some methods of rendering ads involve a s #### Position in viewport -Ads can call the special `window.context.observeIntersection(changesCallback)`API to receive IntersectionObserver style [change records](https://github.com/w3c/IntersectionObserver/blob/master/explainer.md) of the ad's intersection with the parent viewport. +Ads can call the special `window.context.observeIntersection(changesCallback)`API to receive IntersectionObserver style [change records](https://github.com/w3c/IntersectionObserver/blob/main/explainer.md) of the ad's intersection with the parent viewport. The API allows you to specify a callback that fires with change records when AMP observes that an ad becomes visible and then while it is visible, changes are reported as they happen. @@ -162,38 +173,33 @@ Once the request is processed the AMP runtime will try to accommodate this reque possible, but it will take into account where the reader is currently reading, whether the scrolling is ongoing and any other UX or performance factors. -Ads can observe whether resize request were successful using the `window.context.onResizeSuccess` and `window.context.onResizeDenied` methods. +The API will return a promise and the ads can observe whether resize request were successful by checking whether that promise resolves or rejects. `window.context.onResizeSuccess` and `window.context.onResizeDenied` methods are to be deprecated. The `opt_hasOverflow` is an optional boolean value, ads can specify `opt_hasOverflow` to `true` to let AMP runtime know that the ad context can handle overflow when attempt to resize is denied, and not to throw warning in such cases. _Example:_ ```javascript -var unlisten = window.context.onResizeSuccess(function ( - requestedHeight, - requestedWidth -) { - // Hide any overflow elements that were shown. - // The requestedHeight and requestedWidth arguments may be used to - // check which size change the request corresponds to. -}); - -var unlisten = window.context.onResizeDenied(function ( - requestedHeight, - requestedWidth -) { - // Show the overflow element and send a window.context.requestResize(width, height) - // when the overflow element is clicked. - // You may use the requestedHeight and requestedWidth to check which - // size change the request corresponds to. -}); +window.context + .requestResize(requestedWidth, requestedHeight) + .then(function () { + // Hide any overflow elements that were shown. + // The requestedHeight and requestedWidth arguments may be used to + // check which size change the request corresponds to. + }) + .catch(function () { + // Show the overflow element and send a window.context.requestResize(width, height) + // when the overflow element is clicked. + // You may use the requestedHeight and requestedWidth to check which + // size change the request corresponds to. + }); ``` Here are some factors that affect whether the resize will be executed: -- Whether the resize is triggered by the user action; -- Whether the resize is requested for a currently active ad; -- Whether the resize is requested for an ad below the viewport or above the viewport. +- Whether the resize is triggered by the user action; +- Whether the resize is requested for a currently active ad; +- Whether the resize is requested for an ad below the viewport or above the viewport. #### Specifying an overflow element @@ -227,7 +233,7 @@ Note that if the creative needs to resize on user interaction, the creative can ### amp-consent integration -If [amp-consent](https://github.com/ampproject/amphtml/blob/master/extensions/amp-consent/amp-consent.md) extension is used on the page, `data-block-on-consent` attribute +If [amp-consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md) extension is used on the page, `data-block-on-consent` attribute can be added to `amp-ad` element to respect the corresponding `amp-consent` policy. In that case, the `amp-ad` element will be blocked from loading until the consent accepted. Individual ad network can override this default consent handling by putting a `consentHandlingOverride: true` in `ads/_config.js`. @@ -239,8 +245,8 @@ AMP runtime provides the following `window.context` APIs for ad network to acces
window.context.initialConsentState
Provides the initial consent state when the ad is unblocked. - The states are integers defined here - (code). + The states are integers defined here + (code).
window.context.getConsentState(callback)
@@ -250,12 +256,22 @@ AMP runtime provides the following `window.context` APIs for ad network to acces
window.context.consentSharedData
Provides additional user privacy related data retrieved from publishers. - See here for details. + See here for details. +
+
window.context.initialConsentState
+
+ Provides the initial consent string when the ad is unblocked. + See here for details. +
+
window.context.initialConsentMetadata
+
+ Provides initial consent metadata when the ad is unblocked. + See here for details.
After overriding the default consent handling behavior, don't forget to update your publisher facing -documentation with the new behaviors on user's consent choices. You can refer to our documentation example [here](https://github.com/ampproject/amphtml/blob/master/ads/_ping_.md#user-consent-integration). +documentation with the new behaviors on user's consent choices. You can refer to our documentation example [here](https://github.com/ampproject/amphtml/blob/main/ads/vendors/_ping_.md#user-consent-integration). ### Optimizing ad performance @@ -263,7 +279,7 @@ documentation with the new behaviors on user's consent choices. You can refer to To allow ads to bundle HTTP requests across multiple ad units on the same page the object `window.context.master` will contain the window object of the iframe being elected master iframe for the current page. The `window.context.isMaster` property is `true` when the current frame is the master frame. -The `computeInMasterFrame` function is designed to make it easy to perform a task only in the master frame and provide the result to all frames. It is also available to custom ad iframes as `window.context.computeInMasterFrame`. See [3p.js](https://github.com/ampproject/amphtml/blob/master/3p/3p.js) for function signature. +The `computeInMasterFrame` function is designed to make it easy to perform a task only in the master frame and provide the result to all frames. It is also available to custom ad iframes as `window.context.computeInMasterFrame`. See [3p.js](https://github.com/ampproject/amphtml/blob/main/3p/3p.js) for function signature. #### Preconnect and prefetch @@ -273,7 +289,7 @@ This triggers prefetch/preconnect when the ad is first seen, so that loads are f ### Ad markup -Ads are loaded using the `` tag containing the specified `type` for the ad netowkr, and name value pairs of configuration. +Ads are loaded using the `` tag containing the specified `type` for the ad network, and name value pairs of configuration. This is an example for the A9 network: @@ -307,7 +323,7 @@ For ad networks that support loading via a single script tag, this form is suppo ``` -Note, that the network still needs to be white-listed and provide a prefix to valid URLs. AMP may add similar support for ad networks that support loading via an iframe tag. +Note, that the network still needs to be allow-listed and provide a prefix to valid URLs. AMP may add similar support for ad networks that support loading via an iframe tag. Technically, the `` tag loads an iframe to a generic bootstrap URL that knows how to render the ad given the parameters to the tag. @@ -319,24 +335,24 @@ If the publisher would like to add custom JavaScript in the `remote.html` file t ## Developer guidelines for a pull request -Please read through [DEVELOPING.md](../contributing/DEVELOPING.md) before contributing to this code repository. +Please read through [developing.md](../docs/developing.md) before contributing to this code repository. ### Files to change If you're adding support for a new third-party ad service, changes to the following files are expected: -- `/ads/yournetwork.js`: Implement the main logic here. This is the code that's invoked in the third-party iframe once loaded. -- `/ads/yournetwork.md`: Documentation detailing yourr ad service for publishers to read. -- `/ads/_config.js`: Add service specific configuration here. -- `/3p/integration.js`: Register your service here. -- `/extensions/amp-ad/amp-ad.md`: Add a link that points to your publisher doc. -- `/examples/ads.amp.html`: Add publisher examples here. Since a real ad isn't guaranteed to fill, a consistently displayed fake ad is highly recommended here to help AMP developers confidently identify new bugs. +- `/ads/yournetwork.js`: Implement the main logic here. This is the code that's invoked in the third-party iframe once loaded. +- `/ads/yournetwork.md`: Documentation detailing yourr ad service for publishers to read. +- `/ads/_config.js`: Add service specific configuration here. +- `/3p/integration.js`: Register your service here. +- `/extensions/amp-ad/amp-ad.md`: Add a link that points to your publisher doc. +- `/examples/ads.amp.html`: Add publisher examples here. Since a real ad isn't guaranteed to fill, a consistently displayed fake ad is highly recommended here to help AMP developers confidently identify new bugs. ### Verify your examples To verify the examples that you have put in `/examples/ads.amp.html`: -1. Start a local gulp web server by running command `gulp`. +1. Start a local amp web server by running command `amp`. 2. Visit `http://localhost:8000/examples/ads.amp.html?type=yournetwork` in your browser to make sure the examples load ads. Please consider having the example consistently load a fake ad (with ad targeting disabled). Not only will it be a more confident example for publishers to follow, but also allows the AMP team to catch any regression bug during AMP releases. @@ -350,23 +366,23 @@ Please verify your ad is fully functioning, for example, by clicking on an ad. W Please make sure your changes pass the tests: ```sh -gulp unit --watch --nobuild --files=test/unit/{test-ads-config.js,test-integration.js} +amp unit --watch --nobuild --files=test/unit/{test-ads-config.js,test-integration.js} ``` If you have non-trivial logic in `/ads/yournetwork.js`, adding a unit test at `/test/unit/ads/test-yournetwork.js` is highly recommended. ### Lint and type-check -To speed up the review process, please run `gulp lint` and `gulp check-types`, then fix errors, if any, before sending out the PR. +To speed up the review process, please run `amp lint` and `amp check-types`, then fix errors, if any, before sending out the PR. ### Other tips -- Add **cc ampproject/wg-ads** in all pull request's descriptions. -- It's highly recommended to maintain [an integration test outside AMP repo](../3p/README.md#adding-proper-integration-tests). -- Please consider implementing the `render-start` and `no-content-available` APIs (see [Available APIs](#available-apis)), which helps AMP to provide user a much better ad loading experience. -- [CLA](../CONTRIBUTING.md#contributing-code): for anyone who has trouble to pass the automatic CLA check in a pull request, try to follow the guidelines provided by the CLA Bot. Common mistakes are: - 1. Using a different email address in the git commit. - 2. Not providing the exact company name in the PR thread. +- Add **cc ampproject/wg-monetization** in all pull request's descriptions. +- It's highly recommended to maintain [an integration test outside AMP repo](../3p/README.md#adding-proper-integration-tests). +- Please consider implementing the `render-start` and `no-content-available` APIs (see [Available APIs](#available-apis)), which helps AMP to provide user a much better ad loading experience. +- [CLA](../docs/contributing.md#contributing-code): for anyone who has trouble to pass the automatic CLA check in a pull request, try to follow the guidelines provided by the CLA Bot. Common mistakes are: + 1. Using a different email address in the git commit. + 2. Not providing the exact company name in the PR thread. ## Developer announcements for ads related API changes diff --git a/ads/_a4a-config.js b/ads/_a4a-config.js index 6622b3ff7397f..75d47cbff9383 100644 --- a/ads/_a4a-config.js +++ b/ads/_a4a-config.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {map} from '../src/utils/object'; +import {map} from '../src/core/types/object'; /** * Registry for A4A (AMP Ads for AMPHTML pages) "is supported" predicates. @@ -42,6 +42,8 @@ export function getA4ARegistry() { 'adzerk': () => true, 'doubleclick': () => true, 'fake': () => true, + 'nws': () => 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, // you'll probably also need to add an exception to diff --git a/ads/_config.js b/ads/_config.js index cf6052c5ec69a..33cd80589c242 100755 --- a/ads/_config.js +++ b/ads/_config.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {jsonConfiguration} from '../src/json'; +import {jsonConfiguration} from '../src/core/types/object/json'; /** * @typedef {{ @@ -24,7 +24,6 @@ import {jsonConfiguration} from '../src/json'; * clientIdScope: (string|undefined), * clientIdCookieName: (string|undefined), * consentHandlingOverride: (boolean|undefined), - * remoteHTMLDisabled: (boolean|undefined), * fullWidthHeightRatio: (number|undefined), * }} */ @@ -150,6 +149,11 @@ const adConfig = jsonConfiguration({ preconnect: ['https://inv-nets.admixer.net', 'https://cdn.admixer.net'], }, + 'adnuntius': { + prefetch: 'https://cdn.adnuntius.com/adn.js', + renderStartImplemented: true, + }, + 'adocean': { consentHandlingOverride: true, }, @@ -170,6 +174,11 @@ const adConfig = jsonConfiguration({ clientIdScope: 'AMP_ECID_ADPON', }, + 'adpushup': { + prefetch: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + preconnect: 'https://cdn.adpushup.com', + }, + 'adreactor': {}, 'adsensor': { @@ -210,6 +219,11 @@ const adConfig = jsonConfiguration({ preconnect: ['https://mads.at.atwola.com', 'https://aka-cdn.adtechus.com'], }, + 'adtelligent': { + preconnect: ['https://s.adtelligent.com'], + renderStartImplemented: true, + }, + 'adthrive': { prefetch: ['https://www.googletagservices.com/tag/js/gpt.js'], preconnect: [ @@ -227,8 +241,13 @@ const adConfig = jsonConfiguration({ 'aduptech': { prefetch: 'https://s.d.adup-tech.com/jsapi', - preconnect: ['https://d.adup-tech.com', 'https://m.adup-tech.com'], + preconnect: [ + 'https://d.adup-tech.com', + 'https://m.adup-tech.com', + 'https://v.adup-tech.com', + ], renderStartImplemented: true, + consentHandlingOverride: true, }, 'adventive': { @@ -344,6 +363,7 @@ const adConfig = jsonConfiguration({ 'broadstreetads': { prefetch: 'https://cdn.broadstreetads.com/init-2.min.js', + renderStartImplemented: true, }, 'byplay': {}, @@ -403,6 +423,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'digiteka': { + renderStartImplemented: true, + }, + 'directadvert': { renderStartImplemented: true, }, @@ -432,11 +456,6 @@ const adConfig = jsonConfiguration({ ], }, - 'eas': { - prefetch: 'https://amp.emediate.eu/amp.v0.js', - renderStartImplemented: true, - }, - 'empower': { prefetch: 'https://cdn.empower.net/sdk/amp-ad.min.js', renderStartImplemented: true, @@ -470,17 +489,39 @@ const adConfig = jsonConfiguration({ 'fake': {}, + 'fake-delayed': { + renderStartImplemented: true, + }, + + 'feedad': { + clientIdScope: '__fa_amp', + prefetch: 'https://web.feedad.com/sdk/feedad-async.js', + renderStartImplemented: true, + fullWidthHeightRatio: 16 / 9, + consentHandlingOverride: true, + }, + 'felmat': { prefetch: 'https://t.felmat.net/js/fmamp.js', renderStartImplemented: true, }, + 'finative': {}, + + 'firstimpression': { + prefetch: 'https://ecdn.firstimpression.io/static/js/fiamp.js', + preconnect: 'https://cdn.firstimpression.io', + renderStartImplemented: true, + consentHandlingOverride: true, + }, + 'flite': {}, 'fluct': { + prefetch: ['https://pdn.adingo.jp/p.js'], preconnect: [ 'https://cdn-fluct.sh.adingo.jp', - 'https://s.sh.adingo.jp', + 'https://sh.adingo.jp', 'https://i.adingo.jp', ], }, @@ -506,6 +547,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'glomex': { + prefetch: 'https://player.glomex.com/integration/1/amp-embed.js', + }, + 'gmossp': { prefetch: 'https://cdn.gmossp-sp.jp/ads/amp.js', }, @@ -591,6 +636,8 @@ const adConfig = jsonConfiguration({ 'kargo': {}, + 'ketshwa': {}, + 'kiosked': { renderStartImplemented: true, }, @@ -635,6 +682,14 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'luckyads': { + renderStartImplemented: true, + }, + + 'macaw': { + renderStartImplemented: true, + }, + 'mads': { prefetch: 'https://eu2.madsone.com/js/tags.js', }, @@ -649,8 +704,20 @@ const adConfig = jsonConfiguration({ }, 'marfeel': { - prefetch: 'https://www.googletagservices.com/tag/js/gpt.js', - preconnect: 'https://live.mrf.io', + prefetch: 'https://securepubads.g.doubleclick.net/tag/js/gpt.js', + preconnect: [ + 'https://live.mrf.io', + 'https://tpc.googlesyndication.com', + 'https://fastlane.rubiconproject.com', + 'https://htlb.casalemedia.com', + 'https://prg.smartadserver.com', + 'https://ib.adnxs.com', + 'https://bidder.criteo.com', + 'https://marfeel-d.openx.net', + 'https://ice.360yield.com', + 'https://mbid.marfeelrev.com', + 'https://adservice.google.com', + ], consentHandlingOverride: true, }, @@ -733,6 +800,20 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'myfinance': { + preconnect: [ + 'https://a.myfidevs.io', + 'https://static.myfinance.com', + 'https://www.myfinance.com', + ], + renderStartImplemented: true, + clientIdScope: 'AMP_ECID_GOOGLE', + }, + + 'myoffrz': { + renderStartImplemented: true, + }, + 'mytarget': { prefetch: 'https://ad.mail.ru/static/ads-async.js', renderStartImplemented: true, @@ -782,7 +863,9 @@ const adConfig = jsonConfiguration({ 'nws': {}, - 'oblivki': {}, + 'oblivki': { + renderStartImplemented: true, + }, 'onead': { prefetch: 'https://ad-specs.guoshipartners.com/static/js/onead-amp.min.js', @@ -820,6 +903,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'playstream': { + prefetch: 'https://app.playstream.media/js/amp.js', + renderStartImplemented: true, + }, + 'plista': {}, 'polymorphicads': { @@ -868,6 +956,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'pulse': { + prefetch: 'https://static.pulse.mail.ru/pulse-widget-amp.js', + renderStartImplemented: true, + }, + 'pulsepoint': { prefetch: 'https://ads.contextweb.com/TagPublish/getjs.static.js', preconnect: 'https://tag.contextweb.com', @@ -893,6 +986,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'rcmwidget': { + prefetch: 'https://rcmjs.rambler.ru/static/rcmw/rcmw-amp.js', + renderStartImplemented: true, + }, + 'readmo': { renderStartImplemented: true, }, @@ -905,6 +1003,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'recreativ': { + prefetch: 'https://go.rcvlink.com/static/amp.js', + renderStartImplemented: true, + }, + 'relap': { renderStartImplemented: true, }, @@ -915,6 +1018,11 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'remixd': { + preconnect: 'https://tags.remixd.com', + renderStartImplemented: true, + }, + 'revcontent': { prefetch: 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js', @@ -986,6 +1094,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'smartads': { + prefetch: 'https://smart-ads.biz/amp.js', + }, + 'smartadserver': { prefetch: 'https://ec-ns.sascdn.com/diff/js/amp.v0.js', preconnect: 'https://static.sascdn.com', @@ -1024,6 +1136,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'sona': { + renderStartImplemented: true, + }, + 'sovrn': { prefetch: 'https://ap.lijit.com/www/sovrn_amp/sovrn_ads.js', }, @@ -1038,7 +1154,6 @@ const adConfig = jsonConfiguration({ }, 'springAds': { - prefetch: 'https://www.asadcdn.com/adlib/adlib_seq.js', preconnect: ['https://ib.adnxs.com'], renderStartImplemented: true, }, @@ -1145,6 +1260,12 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'verizonmedia': { + prefetch: 'https://jac.yahoosandbox.com/amp/jac.js', + preconnect: ['https://jill.fc.yahoo.com'], + renderStartImplemented: true, + }, + 'videointelligence': { preconnect: 'https://s.vi-serve.com', renderStartImplemented: true, @@ -1158,6 +1279,10 @@ const adConfig = jsonConfiguration({ renderStartImplemented: true, }, + 'vlyby': { + prefetch: 'https://cdn.vlyby.com/amp/qad/qad-outer2.js', + }, + 'vmfive': { prefetch: 'https://man.vm5apis.com/dist/adn-web-sdk.js', preconnect: ['https://vawpro.vm5apis.com', 'https://vahfront.vm5apis.com'], @@ -1220,7 +1345,22 @@ const adConfig = jsonConfiguration({ }, 'yandex': { - prefetch: 'https://yastatic.net/partner-code/loaders/context_amp.js', + prefetch: 'https://an.yandex.ru/system/context_amp.js', + renderStartImplemented: true, + }, + + 'yektanet': { + preconnect: [ + 'https://cdn.yektanet.com', + 'https://cg-sc.yektanet.com', + 'https://native.yektanet.com', + 'https://nfetch.yektanet.net', + 'https://rfetch.yektanet.net', + 'https://scrapper.yektanet.com', + 'https://ua.yektanet.com', + 'https://bfetch.yektanet.com', + 'https://mostatil.cdn.yektanet.com', + ], renderStartImplemented: true, }, diff --git a/ads/_integration-guide.md b/ads/_integration-guide.md index a58a9031e0fd2..37fcf48ebbc92 100644 --- a/ads/_integration-guide.md +++ b/ads/_integration-guide.md @@ -1,7 +1,7 @@ # Guidelines for Integrating with AMP If you are an ad technology provider looking to integrate with AMP HTML, please see the guidelines below. -To ensure minimum latency and quality, please follow the instructions listed [here](../3p/README.md#ads) before submitting a pull request to the AMP open-source project. For general guidance on how to get started with contributing to AMP, please see [CONTRIBUTING.md](../CONTRIBUTING.md). +To ensure minimum latency and quality, please follow the instructions listed [here](../3p/README.md#ads) before submitting a pull request to the AMP open-source project. For general guidance on how to get started with contributing to AMP, please see [docs/contributing.md](../docs/contributing.md). ## Ad Server @@ -27,7 +27,7 @@ For example : Amazon A9 server can be invoked by using following syntax: Note that each of the attributes that follow `type` are dependent on the parameters that the Amazon’s A9 server expects in order to deliver an ad. The [a9.js](./a9.js) file shows you how the parameters are mapped to making a JavaScript call which invokes the A9 server via the `https://c.amazon-adsystem.com/aax2/assoc.js` URL. The corresponding parameters passed by the AMP ad tag are appended to the URL to return an ad. -For details on how to integrate your ad network with AMP, see [Integrating ad networks into AMP](https://github.com/ampproject/amphtml/blob/master/ads/README.md). +For details on how to integrate your ad network with AMP, see [Integrating ad networks into AMP](https://github.com/ampproject/amphtml/blob/main/ads/README.md). ## Supply Side Platform (SSP) or an Ad Exchange @@ -51,7 +51,7 @@ Some ad formats are not fully supported at the moment and we recommend testing t _Examples : Brightcove, Ooyala_ A video player that works in regular HTML pages will not work in AMP and therefore a specific tag must be created that allows the AMP Runtime to load your player. -Brightcove has created a custom [amp-brightcove](https://github.com/ampproject/amphtml/blob/master/extensions/amp-brightcove/amp-brightcove.md) tag that allows media and ads to be played in AMP pages. +Brightcove has created a custom [amp-brightcove](https://github.com/ampproject/amphtml/blob/main/extensions/amp-brightcove/amp-brightcove.md) tag that allows media and ads to be played in AMP pages. A Brightcove player can be invoked by the following: @@ -75,8 +75,8 @@ _Examples : Tremor, Brightroll_ If you are a video ad network, please work with your publisher to ensure that: -- All video assets are served over HTTPS -- The publisher’s video player has AMP support +- All video assets are served over HTTPS +- The publisher’s video player has AMP support ## Data Management Platform (DMP) @@ -94,10 +94,10 @@ Viewability providers typically integrate with publishers via the ad server’s For e.g. for MOAT, make sure `http://js.moatads.com` is switched to `https://z.moatads.com` -Also, see the approach to using the [intersection observer pattern](https://github.com/ampproject/amphtml/blob/master/ads/README.md#ad-viewability). +Also, see the approach to using the [intersection observer pattern](https://github.com/ampproject/amphtml/blob/main/ads/README.md#ad-viewability). ## Content-Recommendation Platform _Examples : Taboola, Outbrain_ -Useful if you have some piece of JavaScript embeded on the publisher website today but the approach will not work in AMP pages. If you would like to recommend content on an AMP page, we suggest that you use the [`amp-embed` extension](https://amp.dev/documentation/components/amp-ad) to request the content details. Please see the [Taboola](https://github.com/ampproject/amphtml/blob/master/ads/taboola.md) example. +Useful if you have some piece of JavaScript embeded on the publisher website today but the approach will not work in AMP pages. If you would like to recommend content on an AMP page, we suggest that you use the [`amp-embed` extension](https://amp.dev/documentation/components/amp-ad) to request the content details. Please see the [Taboola](https://github.com/ampproject/amphtml/blob/main/ads/taboola.md) example. diff --git a/ads/_ping_.js b/ads/_ping_.js deleted file mode 100644 index d48b2e475694e..0000000000000 --- a/ads/_ping_.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {dev, devAssert, userAssert} from '../src/log'; -import {validateData} from '../3p/3p'; - -/** - * A fake ad network integration that is mainly used for testing - * and demo purposes. This implementation gets stripped out in compiled - * production code. - * @param {!Window} global - * @param {!Object} data - */ -export function _ping_(global, data) { - // for testing only. see #10628 - global.networkIntegrationDataParamForTesting = data; - - validateData(data, ['url'], ['valid', 'adHeight', 'adWidth', 'enableIo']); - userAssert(!data['error'], 'Fake user error!'); - global.document.getElementById('c').textContent = data.ping; - global.ping = Object.create(null); - - global.context.onResizeSuccess(() => { - global.ping.resizeSuccess = true; - }); - - global.context.onResizeDenied(() => { - global.ping.resizeSuccess = false; - }); - - if (data.ad_container) { - devAssert(global.context.container == data.ad_container, 'wrong container'); - } - if (data.valid == 'false') { - // Immediately send no-content for visual diff test - global.context.noContentAvailable(); - } - if (data.valid && data.valid == 'true') { - const img = document.createElement('img'); - if (data.url) { - img.setAttribute('src', data.url); - img.setAttribute('width', data.width); - img.setAttribute('height', data.height); - } - let width, height; - if (data.adHeight) { - img.setAttribute('height', data.adHeight); - height = Number(data.adHeight); - } - if (data.adWidth) { - img.setAttribute('width', data.adWidth); - width = Number(data.adWidth); - } - document.body.appendChild(img); - if (width || height) { - global.context.renderStart({width, height}); - } else { - global.context.renderStart(); - } - if (data.enableIo) { - global.context.observeIntersection(function (changes) { - /** @type {!Array} */ (changes).forEach(function (c) { - dev().info( - 'AMP-AD', - 'Intersection: (WxH)' + - `${c.intersectionRect.width}x${c.intersectionRect.height}` - ); - }); - // store changes to global.lastIO for testing purpose - global.ping.lastIO = changes[changes.length - 1]; - }); - } - global.context.getHtml('a', ['href'], function (html) { - dev().info('GET-HTML', html); - }); - global.context.getConsentState(function (consentState) { - dev().info('GET-CONSENT-STATE', consentState); - }); - if (global.context.consentSharedData) { - const TAG = 'consentSharedData'; - dev().info(TAG, global.context.consentSharedData); - } - if (global.context.initialConsentValue) { - const TAG = 'consentStringValue'; - dev().info(TAG, global.context.initialConsentValue); - } - } else { - global.setTimeout(() => { - global.context.noContentAvailable(); - }, 1000); - } -} diff --git a/ads/_ping_.md b/ads/_ping_.md deleted file mode 100644 index 323b328fb1aac..0000000000000 --- a/ads/_ping_.md +++ /dev/null @@ -1,57 +0,0 @@ - - -# \_PING\_ - -A fake ad type that is only used for local development. - -## Example - -```html - - -``` - -## Configuration - -For details on the configuration semantics, please contact the [ad network](#configuration) or refer to their [documentation](#ping). - -### Required parameters - -- `data-url` : Image ad with the image. - -### Optional parameters - -- `data-valid` : Set to false to return a no fill ad. -- `data-ad-height` : Ad image size. -- `data-ad-width` : Ad image width. -- `data-enable-io` : Enable logging IntersectionObserver entry. - -## User Consent Integration - -When [user consent](https://github.com/ampproject/amphtml/blob/master/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required. \_Ping\_ ad approaches user consent in the following ways: - -- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. -- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN`: Will not serve an ad to the user. diff --git a/ads/a8.js b/ads/a8.js deleted file mode 100644 index 6c9ac98ddd98b..0000000000000 --- a/ads/a8.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function a8(global, data) { - validateData(data, ['aid'], ['wid', 'eno', 'mid', 'mat', 'type']); - global.a8Param = data; - writeScript(global, 'https://statics.a8.net/amp/ad.js'); -} diff --git a/ads/a9.js b/ads/a9.js deleted file mode 100644 index caeb9ff5cc610..0000000000000 --- a/ads/a9.js +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {loadScript, validateData, writeScript} from '../3p/3p'; -import {parseJson} from '../src/json'; - -const mandatoryParams = [], - optionalParams = [ - 'ad_mode', - 'placement', - 'tracking_id', - 'ad_type', - 'marketplace', - 'region', - 'title', - 'default_search_phrase', - 'default_category', - 'linkid', - 'search_bar', - 'search_bar_position', - 'rows', - 'design', - 'asins', - 'debug', - 'aax_src_id', - 'header_style', - 'link_style', - 'link_hover_style', - 'text_style', - 'random_permute', - 'render_full_page', - 'axf_exp_name', - 'axf_treatment', - 'disable_borders', - 'attributes', - 'carousel', - 'feedback_enable', - 'max_ads_in_a_row', - 'list_price', - 'prime', - 'prime_position', - 'widget_padding', - 'strike_text_style', - 'brand_text_link', - 'brand_position', - 'large_rating', - 'rating_position', - 'max_title_height', - 'enable_swipe_on_mobile', - 'overrides', - 'ead', - 'force_win_bid', - 'fallback_mode', - 'url', - 'regionurl', - 'divid', - 'recomtype', - 'adinstanceid', - ]; -const prefix = 'amzn_assoc_'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function a9(global, data) { - let i; - for (i = 0; i < 46; i++) { - optionalParams[i] = prefix + optionalParams[i]; - } - - validateData(data, mandatoryParams, optionalParams); - - const publisherUrl = global.context.canonicalUrl || global.context.sourceUrl; - - if (data.amzn_assoc_ad_mode) { - if (data.amzn_assoc_ad_mode === 'auto') { - if (data.adinstanceid && data.adinstanceid !== '') { - loadRecTag(global, data, publisherUrl); - } else { - loadSearTag(global, data, publisherUrl); - } - } else if ( - data.amzn_assoc_ad_mode === 'search' || - data.amzn_assoc_ad_mode === 'manual' - ) { - loadSearTag(global, data, publisherUrl); - } - } else { - loadSearTag(global, data, publisherUrl); - } -} - -/** - * @param {!Object} data - * @return {string} - */ -function getURL(data) { - let url = 'https://z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US'; - if (data.regionurl && data.regionurl !== '') { - url = data.regionurl; - } - - return url; -} - -/** - * @param {!Window} global - * @param {!Object} data - * @param {string} publisherUrl - */ -function loadRecTag(global, data, publisherUrl) { - let url = getURL(data); - url += '&adInstanceId=' + data['adinstanceid']; - global['amzn_assoc_URL'] = publisherUrl; - - if (data['recomtype'] === 'sync') { - writeScript(global, url); - } else if (data['recomtype'] === 'async') { - const d = global.document.createElement('div'); - d.setAttribute('id', data['divid']); - global.document.getElementById('c').appendChild(d); - loadScript(global, url); - } -} - -/** - * @param {!Window} global - * @param {!Object} data - * @param {string} publisherUrl - */ -function loadSearTag(global, data, publisherUrl) { - /** - * Sets macro type. - * @param {string} type - */ - function setMacro(type) { - if (!type) { - return; - } - - if (hasOwn(data, type)) { - global[type] = data[type]; - } - } - - /** - * Sets the params. - */ - function setParams() { - const url = getURL(data); - let i; - - for (i = 0; i < 44; i++) { - setMacro(optionalParams[i]); - } - - if (data.amzn_assoc_fallback_mode) { - const str = data.amzn_assoc_fallback_mode.split(','); - let types = str[0].split(':'); - let typev = str[1].split(':'); - types = parseJson(types[1]); - typev = parseJson(typev[1]); - global['amzn_assoc_fallback_mode'] = { - 'type': types, - 'value': typev, - }; - } - if (data.amzn_assoc_url) { - global['amzn_assoc_URL'] = data['amzn_assoc_url']; - } else { - global['amzn_assoc_URL'] = publisherUrl; - } - - writeScript(global, url); - } - - /** - * Initializer. - */ - function init() { - setParams(); - } - - init(); -} diff --git a/ads/accesstrade.js b/ads/accesstrade.js deleted file mode 100644 index 8d4bf0239fdc5..0000000000000 --- a/ads/accesstrade.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function accesstrade(global, data) { - validateData(data, ['atops', 'atrotid']); - global.atParams = data; - writeScript(global, 'https://h.accesstrade.net/js/amp/amp.js'); -} diff --git a/ads/adagio.js b/ads/adagio.js deleted file mode 100755 index eb072bef42135..0000000000000 --- a/ads/adagio.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adagio(global, data) { - validateData(data, ['sid', 'loc']); - - const $neodata = global; - - $neodata._adagio = {}; - $neodata._adagio.amp = data; - - loadScript($neodata, 'https://js-ssl.neodatagroup.com/adagio_amp.js'); -} diff --git a/ads/adblade.js b/ads/adblade.js deleted file mode 100644 index 495c63624d72e..0000000000000 --- a/ads/adblade.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -const adbladeFields = ['width', 'height', 'cid']; -const adbladeHostname = 'web.adblade.com'; -const industrybrainsHostname = 'web.industrybrains.com'; - -/** - * @param {string} hostname - * @param {!Window} global - * @param {!Object} data - */ -function addAdiantUnit(hostname, global, data) { - validateData(data, adbladeFields, []); - - // create a data element so our script knows what to do - const ins = global.document.createElement('ins'); - ins.setAttribute('class', 'adbladeads'); - ins.setAttribute('data-width', data.width); - ins.setAttribute('data-height', data.height); - ins.setAttribute('data-cid', data.cid); - ins.setAttribute('data-host', hostname); - ins.setAttribute('data-protocol', 'https'); - ins.setAttribute('data-tag-type', 1); - global.document.getElementById('c').appendChild(ins); - - ins.parentNode.addEventListener( - 'eventAdbladeRenderStart', - global.context.renderStart() - ); - - // run our JavaScript code to display the ad unit - writeScript(global, 'https://' + hostname + '/js/ads/async/show.js'); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adblade(global, data) { - addAdiantUnit(adbladeHostname, global, data); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function industrybrains(global, data) { - addAdiantUnit(industrybrainsHostname, global, data); -} diff --git a/ads/adbutler.js b/ads/adbutler.js deleted file mode 100644 index 51efddaff521e..0000000000000 --- a/ads/adbutler.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adbutler(global, data) { - validateData( - data, - ['account', 'zone', 'width', 'height'], - ['keyword', 'place'] - ); - - data['place'] = data['place'] || 0; - - const placeholderID = 'placement_' + data['zone'] + '_' + data['place']; - - // placeholder div - const d = global.document.createElement('div'); - d.setAttribute('id', placeholderID); - global.document.getElementById('c').appendChild(d); - - global.AdButler = global.AdButler || {}; - global.AdButler.ads = global.AdButler.ads || []; - - global.AdButler.ads.push({ - handler(opt) { - global.AdButler.register( - data['account'], - data['zone'], - [data['width'], data['height']], - placeholderID, - opt - ); - }, - opt: { - place: data['place'], - pageKey: global.context.pageViewId, - keywords: data['keyword'], - domain: 'servedbyadbutler.com', - click: 'CLICK_MACRO_PLACEHOLDER', - }, - }); - loadScript(global, 'https://servedbyadbutler.com/app.js'); -} diff --git a/ads/adform.js b/ads/adform.js deleted file mode 100644 index 7617b2d089b6a..0000000000000 --- a/ads/adform.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, validateSrcPrefix, writeScript} from '../3p/3p'; - -// Valid adform ad source hosts -const hosts = { - track: 'https://track.adform.net', - adx: 'https://adx.adform.net', - a2: 'https://a2.adform.net', - adx2: 'https://adx2.adform.net', - asia: 'https://asia.adform.net', - adx3: 'https://adx3.adform.net', -}; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adform(global, data) { - validateData(data, [['src', 'bn', 'mid']]); - global.Adform = {ampData: data}; - const {src, bn, mid} = data; - let url; - - // Custom ad url using "data-src" attribute - if (src) { - validateSrcPrefix( - Object.keys(hosts).map((type) => hosts[type]), - src - ); - url = src; - } - // Ad tag using "data-bn" attribute - else if (bn) { - url = hosts.track + '/adfscript/?bn=' + encodeURIComponent(bn) + ';msrc=1'; - } - // Ad placement using "data-mid" attribute - else if (mid) { - url = hosts.adx + '/adx/?mid=' + encodeURIComponent(mid); - } - - writeScript(global, url); -} diff --git a/ads/adfox.js b/ads/adfox.js deleted file mode 100644 index 4027484825719..0000000000000 --- a/ads/adfox.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {yandex} from './yandex'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adfox(global, data) { - validateData(data, ['adfoxParams', 'ownerId']); - loadScript(global, 'https://yastatic.net/pcode/adfox/loader.js', () => - initAdFox(global, data) - ); -} - -/** - * @param {!Window} global - * @param {Object} data - */ -function initAdFox(global, data) { - const params = JSON.parse(data.adfoxParams); - - createContainer(global, 'adfox_container'); - - global.Ya.adfoxCode.create({ - ownerId: data.ownerId, - containerId: 'adfox_container', - params, - onLoad: (data, onRender, onError) => { - checkLoading(global, data, onRender, onError); - }, - onRender: () => global.context.renderStart(), - onError: () => global.context.noContentAvailable(), - onStub: () => global.context.noContentAvailable(), - }); -} - -/** - * @param {!Window} global - * @param {!Object} data - * @param {!Object} onRender - * @param {!Object} onError - * @return {boolean} - */ -function checkLoading(global, data, onRender, onError) { - if (data.bundleName === 'banner.direct') { - const dblParams = { - blockId: data.bundleParams.blockId, - data: data.bundleParams.data, - onRender, - onError, - }; - - yandex(global, dblParams); - return false; - } - return true; -} - -/** - * @param {!Window} global - * @param {string} id - */ -function createContainer(global, id) { - const container = global.document.createElement('div'); - container.setAttribute('id', id); - global.document.getElementById('c').appendChild(container); -} diff --git a/ads/adgeneration.js b/ads/adgeneration.js deleted file mode 100644 index ef30d56be4657..0000000000000 --- a/ads/adgeneration.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adgeneration(global, data) { - validateData(data, ['id'], ['targetid', 'displayid', 'adtype', 'option']); - - // URL encoding - const option = data.option ? encodeQueryValue(data.option) : null; - - const url = - 'https://i.socdm.com/sdk/js/adg-script-loader.js?' + - 'id=' + - encodeURIComponent(data.id) + - '&width=' + - encodeURIComponent(data.width) + - '&height=' + - encodeURIComponent(data.height) + - '&async=true' + - '&adType=' + - validateAdType(data.adType) + - '&displayid=' + - (data.displayid ? encodeURIComponent(data.displayid) : '1') + - '&tagver=2.0.0' + - (data.targetid ? '&targetID=' + encodeURIComponent(data.targetid) : '') + - (option ? '&' + option : ''); - writeScript(global, url); -} - -/** - * URL encoding of query string - * @param {string} str - * @return {string} - */ -function encodeQueryValue(str) { - return str - .split('&') - .map((v) => { - const key = v.split('=')[0], - val = v.split('=')[1]; - return encodeURIComponent(key) + '=' + encodeURIComponent(val); - }) - .join('&'); -} -/** - * If adtype is "RECTANGLE", replace it with "RECT" - * @param {string} str - * @return {string} - */ -function validateAdType(str) { - if (str != null) { - const upperStr = encodeURIComponent(str.toUpperCase()); - if (upperStr === 'RECTANGLE') { - return 'RECT'; - } else { - return upperStr; - } - } - return 'FREE'; -} diff --git a/ads/adglare.js b/ads/adglare.js deleted file mode 100644 index cd534ee4d6297..0000000000000 --- a/ads/adglare.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adglare(global, data) { - validateData(data, ['host', 'zid'], ['keywords']); - - const adglareSpan = global.document.createElement('span'); - adglareSpan.id = 'zone' + data.zid; - global.document.getElementById('c').appendChild(adglareSpan); - - let url = - 'https://' + - data.host + - '.engine.adglare.net/?' + - data.zid + - '&ad&rnd=' + - Date.now() + - Math.random(); - if (data.keywords) { - url = url + '&keywords=' + data.keywords; - } - - writeScript(global, url); -} diff --git a/ads/adhese.js b/ads/adhese.js deleted file mode 100644 index e7c74246434d7..0000000000000 --- a/ads/adhese.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adhese(global, data) { - validateData(data, ['location', 'format', 'account', 'requestType']); - let targetParam = ''; - const gctx = global.context; - - if (data['targeting']) { - const targetList = data['targeting']; - for (const category in targetList) { - targetParam += encodeURIComponent(category); - const targets = targetList[category]; - for (let x = 0; x < targets.length; x++) { - targetParam += - encodeURIComponent(targets[x]) + (targets.length - 1 > x ? ';' : ''); - } - targetParam += '/'; - } - } - - if (gctx.consentSharedData) { - if ( - gctx.consentSharedData.consentStateValue && - gctx.consentSharedData.consentStateValue == 'accepted' - ) { - targetParam += 'tlall/'; - } - if ( - gctx.consentSharedData.consentString && - gctx.consentSharedData.consentString !== '' - ) { - targetParam += 'xt' + gctx.consentSharedData.consentString + '/'; - } - } - - targetParam += '?t=' + Date.now(); - writeScript( - window, - 'https://ads-' + - encodeURIComponent(data['account']) + - '.adhese.com/' + - encodeURIComponent(data['requestType']) + - '/sl' + - encodeURIComponent(data['location']) + - encodeURIComponent(data['position']) + - '-' + - encodeURIComponent(data['format']) + - '/' + - targetParam - ); - - const co = global.document.querySelector('#c'); - co.width = data['width']; - co.height = data['height']; - co.addEventListener('adhLoaded', getAdInfo.bind(null, global), false); -} - -/** - * @param {!Window} global - * @param {!Object} e - */ -function getAdInfo(global, e) { - if ( - e.detail.isReady && - e.detail.width == e.target.width && - e.detail.height == e.target.height - ) { - global.context.renderStart(); - } else if ( - e.detail.isReady && - (e.detail.width != e.target.width || e.detail.height != e.target.height) - ) { - global.context.renderStart({ - width: e.detail.width, - height: e.detail.height, - }); - } else { - global.context.noContentAvailable(); - } -} diff --git a/ads/adincube.js b/ads/adincube.js deleted file mode 100644 index a82b0d135736a..0000000000000 --- a/ads/adincube.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adincube(global, data) { - validateData(data, ['adType', 'siteKey'], ['params']); - - let url = global.context.location.protocol; - url += '//tag.adincube.com/tag/1.0/next?'; - url += 'ad_type=' + encodeURIComponent(String(data['adType']).toUpperCase()); - url += '&ad_subtype=' + encodeURIComponent(data['width']); - url += 'x' + encodeURIComponent(data['height']); - url += '&site_key=' + encodeURIComponent(data['siteKey']); - url += '&r=' + encodeURIComponent(global.context.referrer); - url += '&h=' + encodeURIComponent(global.context.location.href); - url += '&c=amp'; - url += '&t=' + Date.now(); - - if (data['params']) { - url += parseParams(data['params']); - } - - loadScript(global, url); -} - -/** - * @param {string} data - * @return {string} - */ -function parseParams(data) { - try { - const params = JSON.parse(data); - let queryParams = ''; - for (const p in params) { - if (hasOwn(params, p)) { - queryParams += '&' + p + '=' + encodeURIComponent(params[p]); - } - } - return queryParams; - } catch (e) { - return ''; - } -} diff --git a/ads/adition.js b/ads/adition.js deleted file mode 100644 index 92a258ccde886..0000000000000 --- a/ads/adition.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adition(global, data) { - validateData(data, ['version']); - global.data = data; - writeScript( - global, - 'https://imagesrv.adition.com/js/amp/v' + - encodeURIComponent(data['version']) + - '.js' - ); -} diff --git a/ads/adman.js b/ads/adman.js deleted file mode 100644 index d52e3a40738cc..0000000000000 --- a/ads/adman.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adman(global, data) { - validateData(data, ['ws', 'host', 's'], []); - - 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); -} diff --git a/ads/admanmedia.js b/ads/admanmedia.js deleted file mode 100644 index 4a875550c0ea1..0000000000000 --- a/ads/admanmedia.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function admanmedia(global, data) { - validateData(data, ['id']); - - const encodedId = encodeURIComponent(data.id); - loadScript( - global, - `https://mona.admanmedia.com/go?id=${encodedId}`, - () => { - const pattern = `script[src$="id=${encodedId}"]`; - const scriptTag = global.document.querySelector(pattern); - scriptTag.setAttribute('id', `hybs-${encodedId}`); - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/admixer.js b/ads/admixer.js deleted file mode 100644 index 34a47bc6e20e4..0000000000000 --- a/ads/admixer.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {tryParseJson} from '../src/json'; -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function admixer(global, data) { - validateData(data, ['zone'], ['sizes']); - /** - * @type {Object} - */ - const payload = { - imps: [], - referrer: window.context.referrer, - }; - const imp = { - params: { - zone: data.zone, - }, - }; - if (data.sizes) { - imp.sizes = tryParseJson(data.sizes); - } - payload.imps.push(imp); - const json = JSON.stringify(/** @type {JsonObject} */ (payload)); - writeScript(global, 'https://inv-nets.admixer.net/ampsrc.js?data=' + json); -} diff --git a/ads/adocean.js b/ads/adocean.js deleted file mode 100644 index 7b3e87071952b..0000000000000 --- a/ads/adocean.js +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {CONSENT_POLICY_STATE} from '../src/consent-state'; -import {computeInMasterFrame, validateData, writeScript} from '../3p/3p'; -import {parseJson} from '../src/json'; - -/** - * @const {Object} - */ -const ADO_JS_PATHS = { - 'sync': '/files/js/ado.js', - 'buffered': '/files/js/ado.FIF.test.js', -}; - -/** - * @param {string} str - * @return {boolean} - */ -function isFalseString(str) { - return /^(?:false|off)?$/i.test(str); -} - -/** - * @param {string} mode - * @param {!Window} global - * @param {boolean} consent - */ -function setupAdoConfig(mode, global, consent) { - if (global['ado']) { - const config = { - mode: mode == 'sync' ? 'old' : 'new', - protocol: 'https:', - fif: { - enabled: mode != 'sync', - }, - consent, - }; - - global['ado']['config'](config); - } -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function setupPreview(global, data) { - if (global.ado && data.aoPreview && !isFalseString(data.aoPreview)) { - global.ado.preview({ - enabled: true, - }); - } -} - -/** - * @param {string} str - * @return {Object|undefined} - * @throws {SyntaxError} - */ -function parseJSONObj(str) { - return str.match(/^\s*{/) ? parseJson(str) : undefined; -} - -/** - * @param {string} keys - * @return {string|undefined} - */ -function buildKeys(keys) { - return keys || undefined; -} - -/** - * @param {string} vars - * @return {Object|string|undefined} - */ -function buildVars(vars) { - try { - return parseJSONObj(vars); - } catch (e) { - return vars || undefined; - } -} - -/** - * @param {string} clusters - * @return {Object|undefined} - */ -function buildClusters(clusters) { - try { - return parseJSONObj(clusters); - } catch (e) { - // return undefined - } -} - -/** @type {number} */ -let runSyncCount = 0; - -/** - * @param {!Window} global - * @param {function()} cb - */ -function runSync(global, cb) { - global['__aoPrivFnct' + ++runSyncCount] = cb; - global.document.write( - // eslint-disable-next-line no-useless-concat - '<' + 'script>__aoPrivFnct' + runSyncCount + '();<' + '/script>' - ); -} - -/** - * @param {string} mode - * @param {!Window} global - * @param {!Object} data - */ -function appendPlacement(mode, global, data) { - const doc = global.document; - const placement = doc.createElement('div'); - placement.id = data['aoId']; - - const dom = doc.getElementById('c'); - dom.appendChild(placement); - - const config = { - id: data['aoId'], - server: data['aoEmitter'], - keys: buildKeys(data['aoKeys']), - vars: buildVars(data['aoVars']), - clusters: buildClusters(data['aoClusters']), - }; - - if (global.ado) { - if (mode == 'sync') { - runSync(global, function () { - global['ado']['placement'](config); - }); - - runSync(global, function () { - if (!config['_hasAd']) { - window.context.noContentAvailable(); - } - }); - } else { - global['ado']['onAd'](data['aoId'], function (isAd) { - if (!isAd) { - window.context.noContentAvailable(); - } - }); - global['ado']['placement'](config); - } - } -} - -/** - * @param {string} masterId - * @param {!Object} data - * @param {!Window} global - * @param {Function} callback - */ -function executeMaster(masterId, data, global, callback) { - const config = { - id: masterId, - server: data['aoEmitter'], - keys: buildKeys(data['aoKeys']), - vars: buildVars(data['aoVars']), - clusters: buildClusters(data['aoClusters']), - }; - - if (global['ado']) { - global['ado']['onEmit']((masterId, instanceId, codes) => { - callback(codes); - }); - - global['ado']['master'](config); - } -} - -/** - * - * @param {string} masterId - * @param {!Object} data - * @param {!Window} global - * @param {Function} callback - */ -function requestCodes(masterId, data, global, callback) { - const slaveId = data['aoId']; - - computeInMasterFrame( - global, - 'ao-master-exec', - (done) => { - executeMaster(masterId, data, global, (codes) => done(codes)); - }, - (codes) => { - const creative = codes[slaveId]; - if (codes[slaveId + '_second_phase']) { - creative['code'] += '\n' + codes[slaveId + '_second_phase']['code']; - } - callback(creative); - } - ); -} - -class AdoBuffer { - /** - * - * @param {Object} container - * @param {!Window} global - */ - constructor(container, global) { - this.container = container; - this.global = global; - this.callback = null; - } - - /** - * - * @param {Function} callback - */ - render(callback) { - this.callback = callback; - - if (this.global.document.readyState === 'loading') { - this.global.document.addEventListener( - 'DOMContentLoaded', - this._init.bind(this) - ); - } else { - this._init(); - } - } - - /** - */ - _init() { - const ado = this.global['ado']; - const gao = this.global['gao']; - - if (ado['busy'] || (typeof gao !== 'undefined' && gao['busy'])) { - ado['queue'].unshift(this._execute.bind(this)); - } else { - this._execute(); - } - } - - /** - */ - _execute() { - const adoElement = new this.global['AdoElement']({ - 'id': this.container.id, - 'orgId': this.container.id, - 'clearId': this.container.id, - '_isBuffer': true, - }); - this.global['AdoElems'] = this.global['AdoElems'] || []; - this.global['AdoElems'].push(adoElement); - adoElement['getDOMElement'](); - adoElement['initBuffor'](); - this.global['ado']['elems'][this.container.id] = adoElement; - - this.callback(adoElement); - - adoElement['rewriteBuffor'](); - adoElement['dispatch'](); - } -} - -/** - * - * @param {string} slaveId - * @param {!Object} config - * @param {!Window} global - */ -function executeSlave(slaveId, config, global) { - const doc = global.document; - const placement = doc.createElement('div'); - placement['id'] = slaveId; - - const dom = doc.getElementById('c'); - dom.appendChild(placement); - - if (global['ado']) { - if (!config || config['isEmpty']) { - global.context.noContentAvailable(); - } else { - const buffer = new AdoBuffer(placement, global); - buffer.render(() => { - new Function(config['sendHitsDef'] + config['code'])(); - }); - } - } -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adocean(global, data) { - validateData( - data, - ['aoEmitter', 'aoId'], - ['aoMode', 'aoPreview', 'aoKeys', 'aoVars', 'aoClusters', 'aoMaster'] - ); - - const masterId = data['aoMaster']; - const mode = data['aoMode'] !== 'sync' || masterId ? 'buffered' : 'sync'; - 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; - - writeScript(global, adoUrl, () => { - setupAdoConfig(mode, global, consent); - setupPreview(global, data); - - if (masterId) { - const ado = global['ado']; - if (ado && ado['features'] && ado['features']['passback']) { - ado['features']['passback'] = false; - } - - requestCodes(masterId, data, global, (codes) => { - executeSlave(data['aoId'], codes, global); - }); - } else { - appendPlacement(mode, global, data); - } - }); -} diff --git a/ads/adocean.md b/ads/adocean.md deleted file mode 100644 index e640cc0b3a402..0000000000000 --- a/ads/adocean.md +++ /dev/null @@ -1,90 +0,0 @@ - - -# AdOcean - -## Example - -##### Single placement: - -```html - - -``` - -##### Master-Slave: - -```html - - - - - -``` - -Do not define different values for slaves within one master for paramerters: data-ao-keys, data-ao-vars and data-block-on-consent. Otherwise, the behavior will be non-deterministic. - -## Configuration - -For details on the configuration semantics, see [AdOcean documentation](http://www.adocean-global.com). - -### Required parameters - -- `data-ao-id` - Ad unit unique id -- `data-ao-emitter` - Ad server hostname - -### Optional parameters - -- `data-ao-mode` - sync|buffered - processing mode -- `data-ao-preview` - true|false - whether livepreview is enabled -- `data-ao-keys` - additional configuration, see adserver documentation, do not define different values for slaves within one master -- `data-ao-vars` - additional configuration, see adserver documentation, do not define different values for slaves within one master -- `data-ao-clusters` - additional configuration, see adserver documentation -- `data-ao-master` - unique id of master placement - -## User Consent Integration - -When [user consent](https://github.com/ampproject/amphtml/blob/master/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required. AdOcean ad approaches user consent in the following ways: - -- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. -- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN`: Serve a non-personalized ad to the user. diff --git a/ads/adop.js b/ads/adop.js deleted file mode 100755 index 7d90112377196..0000000000000 --- a/ads/adop.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adop(global, data) { - validateData(data, ['z']); - global.adop = data; - writeScript(global, 'https://compass.adop.cc/assets/js/adop/amp.js'); -} diff --git a/ads/adpicker.js b/ads/adpicker.js deleted file mode 100644 index fd2e703db4b55..0000000000000 --- a/ads/adpicker.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adpicker(global, data) { - validateData(data, ['ph']); - const url = - 'https://cdn.adpicker.net' + - '/ads/main.js?et=amp' + - '&ph=' + - encodeURIComponent(data.ph) + - '&cb=' + - Math.floor(89999999 * Math.random() + 10000000); - writeScript(global, url); -} diff --git a/ads/adplugg.js b/ads/adplugg.js deleted file mode 100644 index 2db95918f3815..0000000000000 --- a/ads/adplugg.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {loadScript, validateData} from '../3p/3p'; - -/** - * Make an AdPlugg iframe. - * @param {!Window} global - * @param {!Object} data - */ -export function adplugg(global, data) { - // Load ad.js - loadScript(global, 'https://www.adplugg.com/serve/js/ad.js'); - - // Validate the amp-ad attributes. - validateData( - data, - ['accessCode', 'width', 'height'], //required - ['zone'] //optional - ); - - // Get the amp wrapper element. - const ampwrapper = global.document.getElementById('c'); - - // Build and append the ad tag. - const adTag = global.document.createElement('div'); - adTag.setAttribute('class', 'adplugg-tag'); - adTag.setAttribute('data-adplugg-access-code', data['accessCode']); - if (data['zone']) { - adTag.setAttribute('data-adplugg-zone', data['zone']); - } - ampwrapper.appendChild(adTag); - - // Get a handle on the AdPlugg SDK. - global.AdPlugg = global.AdPlugg || []; - const {AdPlugg} = global; - - // Register event listeners (via async wrapper). - AdPlugg.push(function () { - const {AdPlugg} = global; - // Register the renderStart event listener. - AdPlugg.on(adTag, 'adplugg:renderStart', function (event) { - // Create the opt_data object. - const optData = {}; - if (hasOwn(event, 'width')) { - optData.width = event.width; - } - if (hasOwn(event, 'height')) { - optData.height = event.height; - } - global.context.renderStart(optData); - }); - - // Register the noContentAvailable event listener. - AdPlugg.on(adTag, 'adplugg:noContentAvailable', function () { - global.context.noContentAvailable(); - }); - }); - - // Fill the tag. - AdPlugg.push({'command': 'run'}); -} diff --git a/ads/adpon.js b/ads/adpon.js deleted file mode 100644 index bddc65896ae96..0000000000000 --- a/ads/adpon.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adpon(global, data) { - validateData(data, ['fid'], ['debugScript']); - - global._adpon = {fid: data['fid']}; - - writeScript(global, data['debugScript'] || 'https://ad.adpon.jp/amp.js'); -} diff --git a/ads/adreactor.js b/ads/adreactor.js deleted file mode 100644 index 91c9844576ec5..0000000000000 --- a/ads/adreactor.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adreactor(global, data) { - // TODO: check mandatory fields - validateData(data, [], ['zid', 'pid', 'custom3']); - const url = - 'https://adserver.adreactor.com' + - '/servlet/view/banner/javascript/zone?' + - 'zid=' + - encodeURIComponent(data.zid) + - '&pid=' + - encodeURIComponent(data.pid) + - '&custom3=' + - encodeURIComponent(data.custom3) + - '&random=' + - Math.floor(89999999 * Math.random() + 10000000) + - '&millis=' + - Date.now(); - writeScript(global, url); -} diff --git a/ads/ads.extern.js b/ads/ads.extern.js index 81cf0e51384bf..2f9b79b3f90b9 100644 --- a/ads/ads.extern.js +++ b/ads/ads.extern.js @@ -221,6 +221,10 @@ var google; google.ima; google.ima.Ad; google.ima.Ad.getSkipTimeOffset; +google.ima.Ad.getAdPodInfo; +google.ima.AdPodInfo; +google.ima.AdPodInfo.getAdPosition; +google.ima.AdPodInfo.getTotalAds; google.ima.AdDisplayContainer; google.ima.AdDisplayContainer.initialize; google.ima.ImaSdkSettings; @@ -252,6 +256,8 @@ google.ima.AdEvent.Type.AD_PROGRESS; google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED; google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED; google.ima.AdEvent.Type.LOADED; +google.ima.AdEvent.Type.PAUSED; +google.ima.AdEvent.Type.RESUMED; google.ima.AdEvent.Type.ALL_ADS_COMPLETED; google.ima.AdsManager; google.ima.AdsManager.getRemainingTime; @@ -303,6 +309,19 @@ data.s; // adpicker.js data.ph; +// adpushup.js +window.adpushup = {}; +window.adpushup.initAmp = function ( + global, + width, + height, + siteid, + slotpath, + totalampslots, + jsontargeting, + extras +) {}; + // adreactor.js data.zid; data.pid; @@ -338,7 +357,15 @@ data.siteId; // aduptech.js window.uAd = {}; window.uAd.embed; +data.amp; data.responsive; +data.placementkey; +data.mincpc; +data.query; +data.pageurl; +data.gdpr; +data.gdpr_consent; +data.adtest; data.onAds; data.onNoAds; @@ -659,6 +686,9 @@ data.slot.setVisibility; data.slot.setTargeting; data.slot.setExtraParameters; +// verizonmedia.js +window.jacData; + // webediads.js var wads; wads.init; diff --git a/ads/adsensor.js b/ads/adsensor.js deleted file mode 100644 index c15462c6e0b57..0000000000000 --- a/ads/adsensor.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - */ -export function adsensor(global) { - writeScript( - global, - 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js' - ); -} diff --git a/ads/adservsolutions.js b/ads/adservsolutions.js deleted file mode 100644 index 2b504e9f435a0..0000000000000 --- a/ads/adservsolutions.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adservsolutions(global, data) { - validateData(data, [], ['client', 'zid']); - global['ABNS'] = - global['ABNS'] || - function () { - (global['ABNSl'] = global['ABNSl'] || []).push(arguments); - }; - global['ABNSh'] = data.client; - writeScript(global, 'https://cdn.' + global['ABNSh'] + '/libs/b.js'); - global['ABNS']('c', {id: data.zid + '&o=a'}); -} diff --git a/ads/adsloom.js b/ads/adsloom.js deleted file mode 100644 index 70be06f90ce5a..0000000000000 --- a/ads/adsloom.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adsloom(global, data) { - validateData(data, ['widgetId']); - global._adsLoom = global._adsLoom || { - widgetId: data['widgetId'], - clientId: global.context.clientId, - sourceUrl: global.context.sourceUrl, - }; - loadScript( - global, - 'https://adsloomwebservices.adsloom.com/scripts/amp-loader.js' - ); -} diff --git a/ads/adsnative.js b/ads/adsnative.js deleted file mode 100644 index 5dd5b98928794..0000000000000 --- a/ads/adsnative.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adsnative(global, data) { - try { - validateData(data, ['anapiid'], ['ankv', 'ancat', 'antid']); - } catch (e) { - validateData(data, ['annid', 'anwid'], ['ankv', 'ancat', 'antid']); - } - - // convert string to object - let actualkv = undefined; - if (data.ankv) { - actualkv = {}; - const arraykv = data.ankv.split(','); - for (const k in arraykv) { - const kv = arraykv[k].split(':'); - actualkv[kv.pop()] = kv.pop(); - } - } - - // convert string to array - const actualcat = data.ancat ? data.ancat.split(',') : undefined; - - // populate settings - global._AdsNativeOpts = { - apiKey: data.anapiid, - networkKey: data.annid, - nativeAdElementId: 'adsnative_ampad', - currentPageUrl: global.context.location.href, - widgetId: data.anwid, - templateKey: data.antid, - categories: actualcat, - keyValues: actualkv, - amp: true, - }; - - // drop ad placeholder div - const ad = global.document.createElement('div'); - const ampwrapper = global.document.getElementById('c'); - ad.id = global._AdsNativeOpts.nativeAdElementId; - ampwrapper.appendChild(ad); - - // load renderjs - writeScript(global, 'https://static.adsnative.com/static/js/render.v1.js'); -} diff --git a/ads/adsnative.md b/ads/adsnative.md deleted file mode 100644 index 7a0949578af17..0000000000000 --- a/ads/adsnative.md +++ /dev/null @@ -1,44 +0,0 @@ - - -# AdsNative - -## Example - -```html - - -``` - -## Configuration - -For configuration details, see [AdsNative's documentation](http://dev.adsnative.com). - -### Required parameters - -- `width`: required by amp -- `height`: required by amp -- `data-anapiid`: the api id may be used instead of network and widget id -- `data-annid`: the network id must be paired with widget id -- `data-anwid`: the widget id must be paired with network id - -### Optional parameters - -- `data-anapiid`: the api id -- `data-anwid`: the widget id -- `data-antid`: the template id -- `data-ancat`: a comma separated list of categories -- `data-ankv`: a list of key value pairs in the format `"key1:value1, key2:value2"`. diff --git a/ads/adspeed.js b/ads/adspeed.js deleted file mode 100644 index 91b5f7e9be05a..0000000000000 --- a/ads/adspeed.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adspeed(global, data) { - validateData(data, ['zone', 'client']); - - const url = - 'https://g.adspeed.net/ad.php?do=amphtml&zid=' + - data.zone + - '&oid=' + - data.client + - '&cb=' + - Math.random(); - - writeScript(global, url); -} diff --git a/ads/adspirit.js b/ads/adspirit.js deleted file mode 100644 index 9aa21cfa12ef9..0000000000000 --- a/ads/adspirit.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {setStyles} from '../src/style'; -import {validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adspirit(global, data) { - // TODO: check mandatory fields - validateData(data, [], ['asmParams', 'asmHost']); - const i = global.document.createElement('ins'); - i.setAttribute('data-asm-params', data['asmParams']); - i.setAttribute('data-asm-host', data['asmHost']); - i.setAttribute('class', 'asm_async_creative'); - setStyles(i, { - display: 'inline-block', - 'text-align': 'left', - }); - global.document.getElementById('c').appendChild(i); - const s = global.document.createElement('script'); - s.src = 'https://' + data['asmHost'] + '/adasync.js'; - global.document.body.appendChild(s); -} diff --git a/ads/adstir.js b/ads/adstir.js deleted file mode 100644 index 248210dad982e..0000000000000 --- a/ads/adstir.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adstir(global, data) { - // TODO: check mandatory fields - validateData(data, [], ['appId', 'adSpot']); - - const v = '4.0'; - - const d = global.document.createElement('div'); - d.setAttribute('class', 'adstir-ad-async'); - d.setAttribute('data-ver', v); - d.setAttribute('data-app-id', data['appId']); - d.setAttribute('data-ad-spot', data['adSpot']); - d.setAttribute('data-amp', true); - d.setAttribute('data-origin', global.context.location.href); - global.document.getElementById('c').appendChild(d); - - loadScript(global, 'https://js.ad-stir.com/js/adstir_async.js'); -} diff --git a/ads/adstyle.js b/ads/adstyle.js deleted file mode 100644 index 80c7eea066b33..0000000000000 --- a/ads/adstyle.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adstyle(global, data) { - validateData(data, ['widget']); - - global._adstyle = global._adstyle || { - viewId: global.context.pageViewId, - widgetIds: [], - referrer: global.context.referrer, - url: global.context.canonicalUrl, - source: global.context.sourceUrl, - }; - - global._adstyle.widgetIds.push(data.widget); - - const url = 'https://widgets.ad.style/amp.js'; - - window.context.observeIntersection(function (changes) { - /** @type {!Array} */ (changes).forEach(function (c) { - window['intersectionRect' + data.widget] = c.intersectionRect; - window['boundingClientRect' + data.widget] = c.boundingClientRect; - }); - }); - - loadScript(global, url); -} diff --git a/ads/adtech.js b/ads/adtech.js deleted file mode 100644 index 8df0e89190ee4..0000000000000 --- a/ads/adtech.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import { - validateData, - validateSrcContains, - validateSrcPrefix, - writeScript, -} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adtech(global, data) { - const adsrc = data.src; - if (typeof adsrc != 'undefined') { - validateSrcPrefix('https:', adsrc); - validateSrcContains('/addyn/', adsrc); - writeScript(global, adsrc); - } else { - validateData( - data, - ['atwmn', 'atwdiv'], - [ - 'atwco', - 'atwheight', - 'atwhtnmat', - 'atwmoat', - 'atwnetid', - 'atwothat', - 'atwplid', - 'atwpolar', - 'atwsizes', - 'atwwidth', - ] - ); - global.atwco = data.atwco; - global.atwdiv = data.atwdiv; - global.atwheight = data.atwheight; - global.atwhtnmat = data.atwhtnmat; - global.atwmn = data.atwmn; - global.atwmoat = data.atwmoat; - global.atwnetid = data.atwnetid; - global.atwothat = data.atwothat; - global.atwplid = data.atwplid; - global.atwpolar = data.atwpolar; - global.atwsizes = data.atwsizes; - global.atwwidth = data.atwwidth; - writeScript(global, 'https://s.aolcdn.com/os/ads/adsWrapper3.js'); - } -} diff --git a/ads/adtech.md b/ads/adtech.md deleted file mode 100644 index 2bc0f648aab4a..0000000000000 --- a/ads/adtech.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# AdTech - -## Example - -```html - - -``` - -## Configuration - -For details on the configuration semantics, please contact the ad network or refer to their documentation. - -### Required parameters: - -- `data-atwMN` - magic number for the ad spot -- `data-atwDiv` - div name of the ad spot; can be class or id - -### Optional parameters: - -- `data-atwPlId` - placement ID (instead of Magic Number) -- `data-atwOthAT` - generic var to set key/value pairs to send with the ad call; accepts multiple values in a semi-colon delimited list -- `data-atwCo` - override default country code -- `data-atwHtNmAT` - override ad host name -- `data-atwNetId` - network ID -- `data-atwWidth` - ad width (use with atwHeight only if the ad is not 300x250) -- `data-atwHeight`- ad height (use with atwWidth only if the ad is not 300x250) -- `data-atwSizes` - this overrides atwWidth/atwHeight; use this to create a comma-separated list of possible ad sizes -- 'data-atwPolar' - set to "1" to enable Polar.me ad in the ad spot - -### Direct URL call: - -- `src` - Value must start with `https:` and contain `/addyn/`. This should only be used in cases where a direct ad call is being used rather than a magic number (MN). diff --git a/ads/adthrive.js b/ads/adthrive.js deleted file mode 100644 index ad2c047a29bce..0000000000000 --- a/ads/adthrive.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adthrive(global, data) { - validateData(data, ['siteId', 'adUnit'], ['sizes']); - loadScript( - global, - 'https://ads.adthrive.com/sites/' + - encodeURIComponent(data.siteId) + - '/amp.min.js' - ); -} diff --git a/ads/adunity.js b/ads/adunity.js deleted file mode 100644 index facccf4444a0c..0000000000000 --- a/ads/adunity.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {startsWith} from '../src/string'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adunity(global, data) { - const doc = global.document; - - validateData( - data, - ['auAccount', 'auSite'], - [ - 'auSection', - 'auZone', - 'auDemo', - 'auIsdemo', - 'auAd', - 'auOrder', - 'auSegment', - 'auOptions', - 'auSources', - 'auAds', - 'auTriggerFn', - 'auTriggerVal', - 'auCallbackVal', - 'auCallbackFn', - 'auPassbackFn', - 'auPassbackVal', - 'auClick', - 'auDual', - 'auImpression', - 'auVideo', - ] - ); - - //prepare tag structure - const tag = doc.createElement('div'); - tag.classList.add('au-tag'); - tag.setAttribute('data-au-width', data['width']); - tag.setAttribute('data-au-height', data['height']); - - if (data != null) { - for (const key in data) { - //skip not valid attributes - if (!hasOwnProperty.call(data, key)) { - continue; - } - - //skip if attribute is type or ampSlotIndex - if (startsWith(key, 'type') || startsWith(key, 'ampSlotIndex')) { - continue; - } - - if (startsWith(key, 'au')) { - if (key == 'auVideo') { - tag.setAttribute('class', 'au-video'); - } else { - const auKey = key.substring(2).toLowerCase(); - tag.setAttribute('data-au-' + auKey, data[key]); - } - } - } - } - - //make sure is executed only once - let libAd = false; - - //execute tag only if in view - const inViewCb = global.context.observeIntersection(function (changes) { - /** @type {!Array} */ (changes).forEach(function (c) { - if (!libAd && c.intersectionRect.height > data['height'] / 2) { - libAd = true; - inViewCb(); - renderTags(global, data); - } - }); - }); - const tagPh = doc.getElementById('c'); - tagPh.appendChild(tag); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function renderTags(global, data) { - if (data == null) { - return; - } - - global.context.renderStart({ - width: data.width, - height: data.height, - }); - loadScript(global, 'https://content.adunity.com/aulib.js'); -} diff --git a/ads/aduptech.js b/ads/aduptech.js deleted file mode 100644 index b69e9d606ce2d..0000000000000 --- a/ads/aduptech.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function aduptech(global, data) { - const elementId = 'aduptech'; - - validateData(data, ['placementkey'], ['query', 'mincpc', 'adtest']); - - // add id attriubte to given container (required) - global.document.getElementById('c').setAttribute('id', elementId); - - // load aduptech js api - loadScript(global, 'https://s.d.adup-tech.com/jsapi', () => { - // force responsive ads for amp - data.responsive = true; - - // ads callback => render start - // - // NOTE: Not using "data.onAds = global.context.renderStart;" - // because the "onAds()" callback returns our API object - // as first parameter which will cause errors - data.onAds = () => { - global.context.renderStart(); - }; - - // no ads callback => noContentAvailable - data.onNoAds = global.context.noContentAvailable; - - // embed iframe - global.uAd.embed(elementId, data); - }); -} diff --git a/ads/aduptech.md b/ads/aduptech.md deleted file mode 100644 index 0de7971cb1288..0000000000000 --- a/ads/aduptech.md +++ /dev/null @@ -1,112 +0,0 @@ - - -# Ad Up Technology - -Please visit [www.adup-tech.com](http://www.adup-tech.com) for more information -on how to get required ad tag or placement keys. - -## Examples - -### Fixed size - -Uses fixed size by the given `width` and `height`. - -```html - - -``` - -### Filled size - -Uses available space of parent html container. - -```html - -
- - -
-``` - -### Fixed height - -Uses available width and the given `height`. - -```html - - -``` - -### Responsive - -Uses available space but respecting aspect ratio by given `width` and `height` (for example 10:3). - -```html - - -``` - -## Configuration - -### Required parameters - -- `data-placementkey` - -### Optional parameters - -- `data-query` -- `data-mincpc` -- `data-adtest` - -## Design/Layout - -Please visit [www.adup-tech.com](http://www.adup-tech.com) and sign up as publisher to create your own placement. diff --git a/ads/adventive.js b/ads/adventive.js deleted file mode 100644 index 471069c87f5b0..0000000000000 --- a/ads/adventive.js +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {addParamsToUrl} from '../src/url.js'; -import {dict, hasOwn} from '../src/utils/object'; -import {endsWith, startsWith} from '../src/string'; -import {loadScript, validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adventive(global, data) { - if (hasOwn(data, 'isDev')) { - adventive_(global, data); - } else { - validateData(data, ['src'], ['isDev']); - writeScript(global, `${data.src}&isAmp=1`); - } -} - -const adv = { - addInstance: () => {}, - args: {}, - isLibLoaded: false, - mode: { - dev: false, - live: false, - localDev: false, - preview: false, - prod: false, - testing: false, - }, - }, - requiredData = ['pid'], - optionalData = ['click', 'async', 'isDev'], - sld = {true: 'adventivedev', false: 'adventive'}, - thld = {true: 'amp', false: 'ads'}, - cacheTime = 5; - -/** - * Future data: - * - async - * - click - * - height - * - isDev - * - width - * - pid - * - * Considerations: - * - Recipe reuse for multi-placed Ads. - * - Reduce request to only what is needed - * - Mitigate risk of data corruption - * - * @todo implement multi-size handling for multi-slotted ads. @see doubleclick - * - * @param {!Window} global - * @param {!Object} data - */ -function adventive_(global, data) { - validateData(data, requiredData, optionalData); - - if (!hasOwn(global, 'adventive')) { - global.adventive = adv; - } - const ns = global.adventive; - if (!hasOwn(ns, 'context')) { - ns.context = global.context; - } - - if (!Object.isFrozen(ns.mode)) { - updateMode(ns.mode, global.context.location.hostname); - } - - const cb = callback.bind(this, data.pid, ns), - url = getUrl(global.context, data, ns); - url - ? (hasOwn(data, 'async') ? loadScript : writeScript)(global, url, cb) - : cb(); -} - -/** - * @param {!Object} mode - * @param {string} hostname - */ -function updateMode(mode, hostname) { - mode.localDev = hostname === 'localhost'; - mode.dev = !mode.localDev && endsWith(hostname, `${sld[false]}.com`); - mode.prod = !mode.localDev && endsWith(hostname, `${sld[true]}.com`); - mode.preview = (mode.dev || mode.prod) && startsWith(hostname, '/ad'); - mode.testing = (mode.dev || mode.prod) && startsWith(hostname, '/testing'); - mode.live = (mode.testing || !mode.preview) && !mode.localDev; - - Object.freeze(mode); -} - -/** - * @param {string} id - * @param {!Object} ns - */ -function callback(id, ns) { - ns.addInstance(id); -} - -/** - * @param {!Object} context - * @param {!Object} data - * @param {!Object} ns - * @return {string|boolean} if a search query is generated, a full url is - * provided, otherwise false - */ -function getUrl(context, data, ns) { - const {mode} = ns, - isDev = hasOwn(data, 'isDev'), - sld_ = sld[!mode.dev], - thld_ = thld[isDev && !mode.live], - search = reduceSearch(ns, data.pid, data.click, context.referrer), - url = search - ? addParamsToUrl(`https://${thld_}.${sld_}.com/ad`, search) - : false; - return url; -} - -/** - * @todo determine if we can reduce the request to nothing & return false - * @todo usage of RTC may be applicable here for macros - * @todo check if click-macros can be offloaded to amp-analytics (i.e recipe) - * - * @param {!Object} ns - * @param {string} placementId - * @param {string} click - * @param {string} referrer - * @return {JsonObject} if no more data is needed, false, otherwise JSON - * representation of the url search query. - */ -function reduceSearch(ns, placementId, click, referrer) { - const isRecipeLoaded = hasOwn(ns.args, 'placementId'); - let isRecipeStale = true; - if (isRecipeLoaded) { - const info = ns.args[placementId]; - isRecipeStale = Date.now() - info['requestTime'] > 60 * cacheTime; - } - const needsRequest = !isRecipeLoaded || isRecipeStale; - - return !needsRequest - ? null - : dict({ - 'click': click, - 'referrer': referrer, - 'isAmp': '1', - 'lib': !ns.isLibLoaded ? '1' : '', // may be prefetchable via _config - 'pid': needsRequest ? placementId : '', - }); -} diff --git a/ads/adverline.js b/ads/adverline.js deleted file mode 100644 index fa5dfc0f01eb1..0000000000000 --- a/ads/adverline.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adverline(global, data) { - validateData(data, ['id', 'plc'], ['s', 'section']); - - writeScript(global, 'https://ads.adverline.com/richmedias/amp.js'); -} diff --git a/ads/adverticum.js b/ads/adverticum.js deleted file mode 100644 index 04f20a28683d3..0000000000000 --- a/ads/adverticum.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {setStyle} from '../src/style'; -import {validateData, writeScript} from '../3p/3p'; -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adverticum(global, data) { - validateData(data, ['goa3zone'], ['costumetargetstring']); - const zoneid = 'zone' + data['goa3zone']; - const d = global.document.createElement('div'); - - d.id = zoneid; - d.classList.add('goAdverticum'); - - document.getElementById('c').appendChild(d); - if (data['costumetargetstring']) { - const s = global.document.createTextNode(data['costumetargetstring']); - const v = global.document.createElement('var'); - v.setAttribute('id', 'cT'); - v.setAttribute('class', 'customtarget'); - setStyle(v, 'display', 'none'); - v.appendChild(s); - document.getElementById(zoneid).appendChild(v); - } - writeScript(global, '//ad.adverticum.net/g3.js'); -} diff --git a/ads/advertserve.js b/ads/advertserve.js deleted file mode 100644 index 54d2f3f3ade5a..0000000000000 --- a/ads/advertserve.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function advertserve(global, data) { - validateData(data, [], ['zid', 'pid', 'client']); - - const url = - 'https://' + - data.client + - '.advertserve.com' + - '/servlet/view/banner/javascript/zone?amp=true' + - '&zid=' + - encodeURIComponent(data.zid) + - '&pid=' + - encodeURIComponent(data.pid) + - '&random=' + - Math.floor(89999999 * Math.random() + 10000000) + - '&millis=' + - Date.now(); - - writeScript(global, url); -} diff --git a/ads/advertserve.md b/ads/advertserve.md deleted file mode 100644 index 0cd306cefc93a..0000000000000 --- a/ads/advertserve.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# AdvertServe - -## Example - -```html - -
Loading ad.
-
Ad could not be loaded.
-
-``` - -## Configuration - -For details on the configuration semantics, please contact the ad network or refer to their documentation. - -Supported parameters: - -- `data-client`: client id -- `data-pid`: publisher id -- `data-zid`: zone id diff --git a/ads/adyoulike.js b/ads/adyoulike.js deleted file mode 100644 index dc9afccf115f4..0000000000000 --- a/ads/adyoulike.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function adyoulike(global, data) { - validateData(data, ['placement'], ['dc', 'campaign']); - global.adyoulikeParams = data; - - writeScript(global, 'https://fo-static.omnitagjs.com/amp.js'); -} diff --git a/ads/affiliateb.js b/ads/affiliateb.js deleted file mode 100644 index 5fbdf81dfe9f7..0000000000000 --- a/ads/affiliateb.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function affiliateb(global, data) { - validateData(data, ['afb_a', 'afb_p', 'afb_t']); - global.afbParam = data; - writeScript(global, 'https://track.affiliate-b.com/amp/a.js'); -} diff --git a/ads/aja.js b/ads/aja.js deleted file mode 100644 index d28d599c6a4f2..0000000000000 --- a/ads/aja.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function aja(global, data) { - validateData(data, ['asi']); - - const {document} = global; - const asi = data['asi']; - - const d = document.createElement('div'); - d.dataset['ajaAd'] = ''; - d.dataset['ajaAsi'] = asi; - document.getElementById('c').appendChild(d); - - loadScript(global, 'https://cdn.as.amanad.adtdp.com/sdk/asot-amp.js'); -} diff --git a/ads/alp/handler.js b/ads/alp/handler.js index 6989dfa7426ae..f27654ca1ff51 100644 --- a/ads/alp/handler.js +++ b/ads/alp/handler.js @@ -18,13 +18,12 @@ import { addParamToUrl, isLocalhostOrigin, isProxyOrigin, - parseQueryString, parseUrlDeprecated, } from '../../src/url'; import {closest, openWindowDialog} from '../../src/dom'; import {dev} from '../../src/log'; -import {dict} from '../../src/utils/object'; -import {startsWith} from '../../src/string'; +import {dict} from '../../src/core/types/object'; +import {parseQueryString} from '../../src/core/types/string/url'; import {urls} from '../../src/config'; /** @@ -140,7 +139,7 @@ function getEventualUrl(a) { } if ( !isProxyOrigin(eventualUrl) || - !startsWith(parseUrlDeprecated(eventualUrl).pathname, '/c/') + !parseUrlDeprecated(eventualUrl).pathname.startsWith('/c/') ) { return; } diff --git a/ads/alp/install-alp.js b/ads/alp/install-alp.js index 993d9d9712a93..7bed2cd9144c9 100644 --- a/ads/alp/install-alp.js +++ b/ads/alp/install-alp.js @@ -18,7 +18,7 @@ import {initLogConstructor, setReportError} from '../../src/log'; import {installAlpClickHandler, warmupStatic} from './handler'; -import {reportError} from '../../src/error'; +import {reportError} from '../../src/error-reporting'; initLogConstructor(); setReportError(reportError); diff --git a/ads/amoad.js b/ads/amoad.js deleted file mode 100644 index d0447724c15fa..0000000000000 --- a/ads/amoad.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function amoad(global, data) { - validateData(data, ['sid'], ['adType']); - - let script; - const attrs = {}; - if (data['adType'] === 'native') { - script = 'https://j.amoad.com/js/n.js'; - attrs['class'] = 'amoad_native'; - attrs['data-sid'] = data.sid; - } else { - script = 'https://j.amoad.com/js/a.js'; - attrs['class'] = `amoad_frame sid_${data.sid} container_div sp`; - } - global.amoadOption = {ampData: data}; - - const d = global.document.createElement('div'); - Object.keys(attrs).forEach((k) => { - d.setAttribute(k, attrs[k]); - }); - global.document.getElementById('c').appendChild(d); - - loadScript(global, script); -} diff --git a/ads/aniview.js b/ads/aniview.js deleted file mode 100644 index fe19f0766f708..0000000000000 --- a/ads/aniview.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function aniview(global, data) { - const requiredParams = ['publisherid', 'channelid']; - validateData(data, requiredParams); - global.avampdata = data; - const scpdomain = data.scriptdomain || 'player.aniview.com'; - const scpurl = 'https://' + scpdomain + '/script/6.1/ampaniview.js'; - writeScript(global, scpurl); -} diff --git a/ads/aniview.md b/ads/aniview.md deleted file mode 100644 index 22c413aded9f3..0000000000000 --- a/ads/aniview.md +++ /dev/null @@ -1,56 +0,0 @@ - - -# Aniview - -## Example - -```html - - -``` - -## Configuration - -For additional details and support contact support@aniview.com. - -### Required parameters - -- `data-publisherid`: the publisher or network id -- `data-channelid`: the channel id - -### Optional parameters - -- `data-ref1`: ref1 extra AV\_ parameters -- `data-loop`: set false to disable loop. Default is true -- `data-vastretry`: vastretry configuration -- `data-errorlimit`: errorlimit configuration -- `data-maximp`: Max number of impressions. -- `data-maxrun`: Max number of waterfall runs. -- `data-preloader`: String of the preloader json object. -- `data-customcss`: Custom css string. -- `data-customlogo`: String of the customlogo json object. -- `data-passbackurl`: passbackurl. -- `data-av_gdpr`: '1' or '0'. -- `data-av_consent`: Consent string. -- `data-av_url`: Current url. -- `data-av_subid`: Subid string. diff --git a/ads/anyclip.js b/ads/anyclip.js deleted file mode 100644 index 31d3608ac3b5d..0000000000000 --- a/ads/anyclip.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -const requiredParams = ['pubname', 'widgetname']; - -const scriptHost = 'player.anyclip.com'; -const scriptPath = 'anyclip-widget/lre-widget/prod/v1/src'; -const scriptName = 'aclre-amp-loader.js'; -const scriptUrl = `https://${scriptHost}/${scriptPath}/${scriptName}`; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function anyclip(global, data) { - validateData(data, requiredParams); - - global.addEventListener('message', () => { - global.context.renderStart(); - }); - - loadScript(global, scriptUrl, () => { - global.anyclip = global.anyclip || {}; - global.anyclip.getWidget = global.anyclip.getWidget || function () {}; - }); -} diff --git a/ads/anyclip.md b/ads/anyclip.md deleted file mode 100644 index 31a377d7e682c..0000000000000 --- a/ads/anyclip.md +++ /dev/null @@ -1,51 +0,0 @@ - - -# AnyClip - -## Example - -```html - - -``` - -## Configuration - -For additional details and support contact support@anyclip.com. - -### Required parameters - -- `data-pub-name`: the publisher name -- `data-widget-name`: the widget id - -### Optional parameters - -- `data-widget-url`: the widget source url -- `data-lre-body-bgc`: color of background of the widget -- `data-ac-embed-mode`: the widget embed mode -- `data-ourl`: override url -- `data-plid`: playlist ID -- `data-ar`: aspect ratio -- `data-sid`: given session ID -- `data-tm-*`: misc passthrough params diff --git a/ads/appnexus.js b/ads/appnexus.js deleted file mode 100644 index 6abede2b18ece..0000000000000 --- a/ads/appnexus.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData, writeScript} from '../3p/3p'; -import {setStyles} from '../src/style'; - -const APPNEXUS_AST_URL = 'https://acdn.adnxs.com/ast/ast.js'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function appnexus(global, data) { - const args = []; - args.push('size=' + data.width + 'x' + data.height); - if (data.tagid) { - validateData(data, ['tagid']); - args.push('id=' + encodeURIComponent(data.tagid)); - writeScript(global, constructTtj(args)); - return; - } else if (data.member && data.code) { - validateData(data, ['member', 'code']); - args.push('member=' + encodeURIComponent(data.member)); - args.push('inv_code=' + encodeURIComponent(data.code)); - writeScript(global, constructTtj(args)); - return; - } - - /** - * Construct the TTJ URL. - * Note params should be properly encoded first (use encodeURIComponent); - * @param {!Array} args query string params to add to the base URL. - * @return {string} Formated TTJ URL. - */ - function constructTtj(args) { - let url = 'https://ib.adnxs.com/ttj?'; - for (let i = 0; i < args.length; i++) { - //append arg to query. Please encode arg first. - url += args[i] + '&'; - } - - return url; - } - - appnexusAst(global, data); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function appnexusAst(global, data) { - validateData(data, ['adUnits']); - let apntag; - if (context.isMaster) { - // in case we are in the master iframe, we load AST - context.master.apntag = context.master.apntag || {}; - context.master.apntag.anq = context.master.apntag.anq || []; - apntag = context.master.apntag; - - context.master.adUnitTargetIds = context.master.adUnitTargetIds || []; - - context.master.adUnitTargetIds = data.adUnits.map( - (adUnit) => adUnit.targetId - ); - - apntag.anq.push(() => { - if (data.pageOpts) { - apntag.anq.push(() => { - //output console information - apntag.debug = data.debug || false; - apntag.setPageOpts(data.pageOpts); - }); - } - - /** @type {!Array} */ (data.adUnits).forEach((adUnit) => { - apntag.defineTag(adUnit); - }); - }); - loadScript(global, APPNEXUS_AST_URL, () => { - apntag.anq.push(() => { - apntag.loadTags(); - }); - }); - } - - const div = global.document.createElement('div'); - div.setAttribute('id', data.target); - const divContainer = global.document.getElementById('c'); - if (divContainer) { - divContainer.appendChild(div); - setStyles(divContainer, { - top: '50%', - left: '50%', - bottom: '', - right: '', - transform: 'translate(-50%, -50%)', - }); - } - - if (!apntag) { - apntag = context.master.apntag; - //preserve a global reference - /** @type {{showTag: function(string, Object)}} global.apntag */ - global.apntag = context.master.apntag; - } - - if (!context.isMaster && data.adUnits) { - const newAddUnits = data.adUnits.filter((adUnit) => { - return context.master.adUnitTargetIds.indexOf(adUnit.targetId) === -1; - }); - if (newAddUnits.length) { - apntag.anq.push(() => { - /** @type {!Array} */ (newAddUnits).forEach((adUnit) => { - apntag.defineTag(adUnit); - context.master.adUnitTargetIds.push(adUnit.targetId); - }); - apntag.loadTags(); - }); - } - } - - // check for ad responses received for a slot but before listeners are - // registered, for example when an above-the-fold ad is scrolled into view - apntag.anq.push(() => { - if (typeof apntag.checkAdAvailable === 'function') { - const getAd = apntag.checkAdAvailable(data.target); - getAd({resolve: isAdAvailable, reject: context.noContentAvailable}); - } - }); - - apntag.anq.push(() => { - apntag.onEvent('adAvailable', data.target, isAdAvailable); - apntag.onEvent('adNoBid', data.target, context.noContentAvailable); - }); - - /** - * resolve getAd with an available ad object - * - * @param {{targetId: string}} adObj - */ - function isAdAvailable(adObj) { - global.context.renderStart({width: adObj.width, height: adObj.height}); - global.apntag.showTag(adObj.targetId, global.window); - } -} diff --git a/ads/appvador.js b/ads/appvador.js deleted file mode 100644 index 50c2dab325a6b..0000000000000 --- a/ads/appvador.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function appvador(global, data) { - validateData(data, ['id'], ['options', 'jsType', 'customScriptSrc']); - - const container = global.document.getElementById('c'); - const apvDiv = global.document.createElement('div'); - apvDiv.setAttribute('id', 'apvad-' + data.id); - container.appendChild(apvDiv); - - const scriptUrl = data.customScriptSrc - ? data.customScriptSrc - : 'https://cdn.apvdr.com/js/' + - (data.jsType ? encodeURIComponent(data.jsType) : 'VastAdUnit') + - '.min.js'; - const apvScript = - 'new APV.' + - (data.jsType ? data.jsType : 'VASTAdUnit') + - '({s:"' + - data.id + - '",isAmpAd:true' + - (data.options ? ',' + data.options : '') + - '}).load();'; - - const cb = function () { - const apvLoadScript = global.document.createElement('script'); - apvLoadScript.text = apvScript; - container.appendChild(apvLoadScript); - }; - - writeScript(global, scriptUrl, cb); -} diff --git a/ads/atomx.js b/ads/atomx.js deleted file mode 100644 index b44185aa8f41a..0000000000000 --- a/ads/atomx.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function atomx(global, data) { - const optionals = ['click', 'uv1', 'uv2', 'uv3', 'context']; - - validateData(data, ['id'], optionals); - - const args = [ - 'size=' + data.width + 'x' + data.height, - 'id=' + encodeURIComponent(data.id), - ]; - - for (let i = 0; i < optionals.length; i++) { - const optional = optionals[i]; - if (optional in data) { - args.push(optional + '=' + encodeURIComponent(data[optional])); - } - } - - writeScript(global, 'https://s.ato.mx/p.js#' + args.join('&')); -} diff --git a/ads/atomx.md b/ads/atomx.md deleted file mode 100644 index d52d5122f1e23..0000000000000 --- a/ads/atomx.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# Atomx - -## Example - -```html - -``` - -## Configuration - -For configuration information, see [atomx documentation](https://wiki.atomx.com/tags). - -### Required Parameters - -- `data-id` - placement ID - -### Optional parameters - -- `data-click` - URL to pre-pend to the click URL to enable tracking. -- `data-uv1`, `data-uv2`, `data-uv3` - User value to pass in to the tag. Can be used to track & report on custom values. Needs to be a whole number between 1 and 4,294,967,295. -- `data-context` - Conversion Callback Context diff --git a/ads/baidu.js b/ads/baidu.js deleted file mode 100644 index 09cddba9cac9f..0000000000000 --- a/ads/baidu.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function baidu(global, data) { - validateData(data, ['cproid']); - - const id = '_' + Math.random().toString(36).slice(2); - const container = global.document.createElement('div'); - container.id = id; - global.document.getElementById('c').appendChild(container); - - global.slotbydup = global.slotbydup || []; - global.slotbydup.push({ - id: data['cproid'], - container: id, - display: 'inlay-fix', - async: true, - }); - - global.addEventListener('message', () => { - global.context.renderStart(); - }); - - loadScript( - global, - 'https://dup.baidustatic.com/js/dm.js', - () => {}, - () => { - // noContentAvailable should be called, - // if parent iframe receives no message. - // setTimeout can work, but it's not that reliable. - // So, only the faliure of JS loading is dealed with for now. - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/beaverads.js b/ads/beaverads.js deleted file mode 100644 index dcd0aa99d0fb1..0000000000000 --- a/ads/beaverads.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function beaverads(global, data) { - validateData(data, ['blockId']); - - const url = - 'https://code.beaverads.com/data/' + - encodeURIComponent(data['blockId']) + - '.js?async=1&div=c'; - - loadScript( - global, - url, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/bidtellect.js b/ads/bidtellect.js deleted file mode 100644 index 959d23f0d52ac..0000000000000 --- a/ads/bidtellect.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function bidtellect(global, data) { - const requiredParams = ['t', 'pid', 'sid']; - const optionalParams = [ - 'sname', - 'pubid', - 'pubname', - 'renderid', - 'bestrender', - 'autoplay', - 'playbutton', - 'videotypeid', - 'videocloseicon', - 'targetid', - 'bustframe', - ]; - validateData(data, requiredParams, optionalParams); - let params = '?t=' + encodeURIComponent(data.t); - params += '&pid=' + encodeURIComponent(data.pid); - params += '&sid=' + encodeURIComponent(data.sid); - if (data.width) { - params += '&w=' + encodeURIComponent(data.width); - } - if (data.height) { - params += '&h=' + encodeURIComponent(data.height); - } - optionalParams.forEach(function (param) { - if (data[param]) { - params += '&' + param + '=' + encodeURIComponent(data[param]); - } - }); - const url = 'https://cdn.bttrack.com/js/infeed/2.0/infeed.min.js' + params; - writeScript(global, url); -} diff --git a/ads/bidtellect.md b/ads/bidtellect.md deleted file mode 100644 index e02042261ca68..0000000000000 --- a/ads/bidtellect.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# Bidtellect - -## Example - -```html - - -``` - -## Configuration - -For details on the configuration semantics, contact [Bidtellect](mailto:technology@bidtellect.com). - -### Required parameters - -- `data-t`: Parent publisher security token. -- `data-pid`: The unique identifier for your placement. -- `data-sid`: Unique identifier for the site. - -### Optional Parameters: - -- `data-sname`: Name of site that corresponds to the Site ID. -- `data-pubid`: Unique identifier for the publisher. -- `data-pubname`: Name of publisher that corresponds to the Publisher ID. -- `data-renderid`: Unique identifier of the placement widget. -- `data-bestrender`: Provides the best size and cropping for the placement. -- `data-autoplay`: Enables autoplay for video placements. -- `data-playbutton`: Onscreen play button for video placements. -- `data-videotypeid`: Defines how it will be rendered the video player. -- `data-videocloseicon`: Enable close button on the video player. -- `data-targetid`: Allows the placement to render inside a target HTML element. -- `data-bustframe`: Allows the placement to bust out of nested iframes recursively. diff --git a/ads/blade.js b/ads/blade.js deleted file mode 100644 index 5286a352f53b0..0000000000000 --- a/ads/blade.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {tryParseJson} from '../src/json'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function blade(global, data) { - // ensure mandatory fields - validateData(data, [ - 'width', - 'height', - 'blade_api_key', - 'blade_player_id', - 'blade_player_type', - ]); - - const marcosObj = tryParseJson(data['blade_macros']) || {}; - marcosObj['rand'] = Math.random().toString(); - marcosObj['page_url'] = marcosObj['page_url'] || global.context.canonicalUrl; - const macros = {...marcosObj}; - macros.width = data.width; - macros.height = data.height; - - const containerId = `player-${data['blade_api_key']}-${data['blade_player_id']}`; - createContainer(containerId); - - const bladeConfig = `_bladeConfig-${containerId}`; - global[bladeConfig] = { - playerId: data['blade_player_id'], - apiKey: data['blade_api_key'], - version: '1.0', - macros, - }; - const ctx = global.context; - - const bladeOnLoad = `_bladeOnLoad-${containerId}`; - global[bladeOnLoad] = function (error, player) { - if (error) { - global.context.noContentAvailable(); - return; - } - ctx.reportRenderedEntityIdentifier(containerId); - ctx.renderStart({ - width: player.width, - height: player.height, - }); - }; - - const servingDomain = data.servingDomain - ? encodeURIComponent(data.servingDomain) - : 'ssr.streamrail.net'; - - loadScript( - global, - `https://${servingDomain}/js/${data['blade_api_key']}/${data['blade_player_id']}/player.js?t=${data['blade_player_type']}&callback=${bladeOnLoad}&config=${bladeConfig}&c=${containerId}`, - undefined, - () => { - global.context.noContentAvailable(); - } - ); - /** - * @param {string} elemId - */ - function createContainer(elemId) { - const d = global.document.createElement('div'); - d.id = elemId; - d.classList.add('blade'); - global.document.getElementById('c').appendChild(d); - } -} diff --git a/ads/brainy.js b/ads/brainy.js deleted file mode 100644 index 7647906f77b09..0000000000000 --- a/ads/brainy.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function brainy(global, data) { - validateData(data, [], ['aid', 'slotId']); - - const url = - 'https://proparm.jp/ssp/p/js1' + - '?_aid=' + - encodeURIComponent(data['aid']) + - '&_slot=' + - encodeURIComponent(data['slotId']); - - writeScript(global, url); -} diff --git a/ads/bringhub.js b/ads/bringhub.js deleted file mode 100644 index 0284f51e40a28..0000000000000 --- a/ads/bringhub.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function bringhub(global, data) { - global._bringhub = global._bringhub || { - viewId: global.context.pageViewId, - htmlURL: data['htmlurl'] || global.context.canonicalUrl, - ampURL: data['ampurl'] || global.context.sourceUrl, - referrer: data['referrer'] || global.context.referrer, - }; - - writeScript( - global, - `https://static.bh-cdn.com/msf/amp-loader.js?v=${Date.now()}`, - function () { - loadScript( - global, - `https://static.bh-cdn.com/msf/amp-widget.js?v=${global._bringhub.hash}` - ); - } - ); -} diff --git a/ads/bringhub.md b/ads/bringhub.md deleted file mode 100644 index 2702db458528b..0000000000000 --- a/ads/bringhub.md +++ /dev/null @@ -1,40 +0,0 @@ - - -# Bringhub - -## Example installation of the Bringhub Mini-Storefront - -### Basic - -```html - - -``` - -## Configuration - -### Optional parameters - -- `htmlURL`: The URL of the standard html version of the page. Defaults to `global.context.canonicalURL`. -- `ampURL`: The URL of the AMP version of the page. Defaults to `global.context.sourceUrl`. -- `articleSelector`: The CSS Selector of the article body on the page. Contact your Bringhub Account Manager for requirements. diff --git a/ads/broadstreetads.js b/ads/broadstreetads.js deleted file mode 100644 index 43942742cd08f..0000000000000 --- a/ads/broadstreetads.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function broadstreetads(global, data) { - validateData( - data, - ['network', 'zone', 'width', 'height'], - ['keywords', 'place'] - ); - - data.place = data.place || 0; - - const placeholderID = 'placement_' + data.zone + '_' + data.place; - - // placeholder div - const d = global.document.createElement('div'); - d.setAttribute('id', placeholderID); - global.document.getElementById('c').appendChild(d); - - global.broadstreet = global.broadstreet || {}; - global.broadstreet.loadZone = global.broadstreet.loadZone || (() => ({})); - global.broadstreet.run = global.broadstreet.run || []; - global.broadstreet.run.push(() => { - global.broadstreet.loadZone(d, { - amp: true, - height: data.height, - keywords: data.keywords, - networkId: data.network, - place: data.place, - softKeywords: true, - width: data.width, - zoneId: data.zone, - }); - }); - loadScript(global, 'https://cdn.broadstreetads.com/init-2.min.js'); -} diff --git a/ads/byplay.js b/ads/byplay.js deleted file mode 100644 index a70bde7501a4d..0000000000000 --- a/ads/byplay.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2019 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. - */ -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function byplay(global, data) { - validateData(data, ['vastUrl']); - - global.BYPLAY_VAST_URL = data['vastUrl']; - - loadScript(global, 'https://cdn.byplay.net/amp-byplay-v2.js'); -} diff --git a/ads/caajainfeed.js b/ads/caajainfeed.js deleted file mode 100644 index ae07567aca7fc..0000000000000 --- a/ads/caajainfeed.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function caajainfeed(global, data) { - validateData( - data, - [], - [ - 'adSpot', - 'format', - 'test', - 'optout', - 'offset', - 'ipv4', - 'ipv6', - 'networkReachability', - 'osName', - 'osVersion', - 'osLang', - 'osTimezone', - 'deviceVersion', - 'appId', - 'appVersion', - 'kv', - 'uids', - 'template', - 'protocol', - 'fields', - ] - ); - - global.caAjaInfeedConfig = data; - loadScript(global, 'https://cdn.amanad.adtdp.com/sdk/ajaamp.js'); -} diff --git a/ads/caajainfeed.md b/ads/caajainfeed.md deleted file mode 100644 index 70195c2ba86b7..0000000000000 --- a/ads/caajainfeed.md +++ /dev/null @@ -1,60 +0,0 @@ - - -# CA A.J.A. Infeed - -## Example - -```html - - -``` - -## Configuration - -For configuration details, please email amb-nad@cyberagent.co.jp. - -### Required parameters - -- `data-ad-spot` - -### Optional parameters - -- `data-format` -- `data-test` -- `data-optout` -- `data-offset` -- `data-ipv4` -- `data-ipv6` -- `data-network-reachability` -- `data-os-name` -- `data-os-version` -- `data-os-lang` -- `data-os-timezone` -- `data-device-version` -- `data-app-id` -- `data-app-version` -- `data-kv` -- `data-uids` -- `data-template` -- `data-protocol` -- `data-fields` diff --git a/ads/capirs.js b/ads/capirs.js deleted file mode 100644 index 864f5703f6daa..0000000000000 --- a/ads/capirs.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function capirs(global, data) { - validateData(data, ['begunAutoPad', 'begunBlockId']); - - if (data['customCss']) { - const style = global.document.createElement('style'); - - if (style.styleSheet) { - style.styleSheet.cssText = data['customCss']; - } else { - style.appendChild(global.document.createTextNode(data['customCss'])); - } - - global.document.getElementById('c').appendChild(style); - } - - global['begun_callbacks'] = { - lib: { - init: () => { - const block = global.document.createElement('div'); - block.id = 'x-' + Math.round(Math.random() * 1e8).toString(36); - - global.document.getElementById('c').appendChild(block); - - global['Adf']['banner']['ssp'](block.id, data['params'], { - 'begun-auto-pad': data['begunAutoPad'], - 'begun-block-id': data['begunBlockId'], - }); - }, - }, - block: { - draw: (feed) => { - const banner = feed['banners']['graph'][0]; - - global.context.renderStart({ - width: getWidth(global, banner), - height: banner.height, - }); - - const reportId = 'capirs-' + banner['banner_id']; - global.context.reportRenderedEntityIdentifier(reportId); - }, - unexist: function () { - global.context.noContentAvailable(); - }, - }, - }; - - loadScript(global, '//ssp.rambler.ru/capirs_async.js'); -} - -/** - * @param {!Window} global - * @param {!Object} banner - * @return {*} TODO(#23582): Specify return type - */ -function getWidth(global, banner) { - let width; - - if (isResponsiveAd(banner)) { - width = Math.max( - global.document.documentElement./*OK*/ clientWidth, - global.window./*OK*/ innerWidth || 0 - ); - } else { - width = banner.width; - } - - return width; -} - -/** - * @param {!Object} banner - * @return {boolean} - */ -function isResponsiveAd(banner) { - return banner.width.indexOf('%') !== -1; -} diff --git a/ads/caprofitx.js b/ads/caprofitx.js deleted file mode 100644 index 759b28836622f..0000000000000 --- a/ads/caprofitx.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function caprofitx(global, data) { - global.caprofitxConfig = data; - loadScript(global, 'https://cdn.caprofitx.com/tags/amp/profitx_amp.js'); -} diff --git a/ads/cedato.js b/ads/cedato.js deleted file mode 100644 index eff6ffc11f1d4..0000000000000 --- a/ads/cedato.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {parseUrlDeprecated} from '../src/url'; -import {setStyles} from '../src/style'; -import {validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function cedato(global, data) { - const requiredParams = ['id']; - const optionalParams = [ - 'domain', - 'servingDomain', - 'subid', - 'version', - 'extraParams', - ]; - validateData(data, requiredParams, optionalParams); - - if (!data || !data.id) { - global.context.noContentAvailable(); - return; - } - - const cb = Math.floor(Math.random() * 10000); - const domain = - data.domain || parseUrlDeprecated(global.context.sourceUrl).origin; - - /* Create div for ad to target */ - const playerDiv = global.document.createElement('div'); - playerDiv.id = 'video' + data.id + cb; - setStyles(playerDiv, { - width: '100%', - height: '100%', - }); - const playerScript = global.document.createElement('script'); - const servingDomain = data.servingDomain - ? encodeURIComponent(data.servingDomain) - : 'algovid.com'; - const srcParams = [ - 'https://p.' + servingDomain + '/player/player.js', - '?p=' + encodeURIComponent(data.id), - '&cb=' + cb, - '&w=' + encodeURIComponent(data.width), - '&h=' + encodeURIComponent(data.height), - data.version ? '&pv=' + encodeURIComponent(data.version) : '', - data.subid ? '&subid=' + encodeURIComponent(data.subid) : '', - domain ? '&d=' + encodeURIComponent(domain) : '', - data.extraParams || '', // already encoded url query string - ]; - - playerScript.onload = () => { - global.context.renderStart(); - }; - - playerScript.src = srcParams.join(''); - playerDiv.appendChild(playerScript); - global.document.getElementById('c').appendChild(playerDiv); -} diff --git a/ads/cedato.md b/ads/cedato.md deleted file mode 100644 index ecafdf1fc3380..0000000000000 --- a/ads/cedato.md +++ /dev/null @@ -1,46 +0,0 @@ - - -# Cedato - -## Example - -```html - - -``` - -## Configuration - -For additional details and support contact support@cedato.com. - -### Required parameters - -- `data-id`: the id of the player - supply ID - -### Optional parameters - -- `data-domain`: page domain reported to the player -- `data-serving-domain`: the domain from which the player is served -- `data-subid`: player subid -- `data-version`: version of the player that is being used -- `data-extra-params`: additional player tag parameters can be set in the 'extra-params' query string, all parts have to be encoded. diff --git a/ads/colombia.js b/ads/colombia.js deleted file mode 100644 index 4a9900fcb9dd2..0000000000000 --- a/ads/colombia.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function colombia(global, data) { - validateData(data, [ - 'clmb_slot', - 'clmb_position', - 'clmb_section', - 'clmb_divid', - 'loadingStrategy', - ]); - // push the two object into the '_colombia' global - (global._colombia = global._colombia || []).push({ - clmbslot: data.clmb_slot, - clmbposition: data.clmb_position, - clmbsection: data.clmb_section, - clmbdivid: data.clmb_divid, - }); - // install observation on entering/leaving the view - global.context.observeIntersection(function (newrequest) { - /** @type {!Array} */ (newrequest).forEach(function (d) { - if (d.intersectionRect.height > 0) { - global._colombia.push({ - visible: true, - rect: d, - }); - } - }); - }); - loadScript( - global, - 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js' - ); -} diff --git a/ads/conative.js b/ads/conative.js deleted file mode 100644 index 801dac02166c0..0000000000000 --- a/ads/conative.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function conative(global, data) { - validateData(data, ['domain', 'adslot', 'height'], ['preview']); - - data.domain = data.domain || null; - data.adslot = data.adslot || null; - data.preview = data.preview || null; - - window.dmConativeData = window.dmConativeData || {}; - window.dmConativeData.domain = window.dmConativeData.domain || data.domain; - window.dmConativeData.adslot = window.dmConativeData.adslot || data.adslot; - window.dmConativeData.preview = window.dmConativeData.preview || data.preview; - window.dmConativeData.visibility = window.dmConativeData.visibility || 0; - - window.context.observeIntersection(function (changes) { - /** @type {!Array} */ (changes).forEach(function (c) { - window.dmConativeData.visibility = parseInt( - (c.intersectionRect.height / c.boundingClientRect.height) * 100, - 10 - ); - }); - }); - - if (data.domain) { - writeScript( - global, - '//s3-eu-west-1.amazonaws.com/ccc-adscript/serve/domain/' + - data.domain + - '/config.js' - ); - } -} diff --git a/ads/connatix.js b/ads/connatix.js deleted file mode 100644 index 6d9fbb453b8bc..0000000000000 --- a/ads/connatix.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {hasOwn} from '../src/utils/object.js'; -import {tryParseJson} from '../src/json.js'; -import {validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function connatix(global, data) { - validateData(data, ['connatix']); - - // Because 3p's loadScript does not allow for data attributes, - // we will write the JS tag ourselves. - const script = global.document.createElement('script'); - const cnxData = Object.assign(Object(tryParseJson(data['connatix']))); - global.cnxAmpAd = true; - for (const key in cnxData) { - if (hasOwn(cnxData, key)) { - script.setAttribute(key, cnxData[key]); - } - } - - window.addEventListener( - 'connatix_no_content', - function () { - window.context.noContentAvailable(); - }, - false - ); - - script.onload = () => { - global.context.renderStart(); - }; - - script.src = 'https://cdn.connatix.com/min/connatix.renderer.infeed.min.js'; - global.document.getElementById('c').appendChild(script); -} diff --git a/ads/contentad.js b/ads/contentad.js deleted file mode 100644 index 356906fca5dd1..0000000000000 --- a/ads/contentad.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {parseUrlDeprecated} from '../src/url'; -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function contentad(global, data) { - validateData(data, [], ['id', 'd', 'wid', 'url']); - global.id = data.id; - global.d = data.d; - global.wid = data.wid; - global.url = data.url; - - /* Create div for ad to target */ - const cadDiv = window.document.createElement('div'); - cadDiv.id = 'contentad' + global.wid; - window.document.body.appendChild(cadDiv); - - /* Pass Source URL */ - let {sourceUrl} = window.context; - if (data.url) { - const domain = data.url || window.atob(data.d); - sourceUrl = sourceUrl.replace(parseUrlDeprecated(sourceUrl).host, domain); - } - - /* Build API URL */ - const cadApi = - 'https://api.content-ad.net/Scripts/widget2.aspx' + - '?id=' + - encodeURIComponent(global.id) + - '&d=' + - encodeURIComponent(global.d) + - '&wid=' + - global.wid + - '&url=' + - encodeURIComponent(sourceUrl) + - '&cb=' + - Date.now(); - - /* Call Content.ad Widget */ - writeScript(global, cadApi); -} diff --git a/ads/criteo.js b/ads/criteo.js deleted file mode 100644 index 07442661bfa42..0000000000000 --- a/ads/criteo.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {dev} from '../src/log'; -import {loadScript} from '../3p/3p'; - -/* global Criteo: false */ - -/** @const {string} */ -const TAG = 'CRITEO'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function criteo(global, data) { - loadScript(global, 'https://static.criteo.net/js/ld/publishertag.js', () => { - if (!data.tagtype || data.tagtype === 'passback') { - Criteo.DisplayAd({ - zoneid: data.zone, - containerid: 'c', - integrationmode: 'amp', - }); - } else if (data.tagtype === 'rta' || data.tagtype === 'standalone') { - dev().error( - TAG, - 'You are using a deprecated Criteo integration', - data.tagtype - ); - } else { - dev().error( - TAG, - 'You are using an unknown Criteo integration', - data.tagtype - ); - } - }); -} diff --git a/ads/dable.js b/ads/dable.js deleted file mode 100644 index 764e68c9e6b39..0000000000000 --- a/ads/dable.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function dable(global, data) { - // check required props - validateData(data, ['widgetId']); - - global.dable = - global.dable || - function () { - (global.dable.q = global.dable.q || []).push(arguments); - }; - global.dable( - 'setService', - data['serviceName'] || global.window.context.location.hostname - ); - global.dable('setURL', global.window.context.sourceUrl); - global.dable('setRef', global.window.context.referrer); - - const slot = global.document.createElement('div'); - slot.id = '_dbl_' + Math.floor(Math.random() * 100000); - slot.setAttribute('data-widget_id', data['widgetId']); - - const divContainer = global.document.getElementById('c'); - if (divContainer) { - divContainer.appendChild(slot); - } - - const itemId = data['itemId'] || ''; - const opts = {}; - - if (itemId) { - global.dable('sendLog', 'view', {id: itemId}); - } else { - opts.ignoreItems = true; - } - - // call render widget - global.dable('renderWidget', slot.id, itemId, opts, function (hasAd) { - if (hasAd) { - global.context.renderStart(); - } else { - global.context.noContentAvailable(); - } - }); - - // load the Dable script asynchronously - loadScript(global, 'https://static.dable.io/dist/plugin.min.js'); -} diff --git a/ads/directadvert.js b/ads/directadvert.js deleted file mode 100644 index a27f8969380f2..0000000000000 --- a/ads/directadvert.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {serializeQueryString} from '../src/url'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function directadvert(global, data) { - validateData(data, ['blockId']); - - const params = /** @type {!JsonObject} */ ({ - 'async': 1, - 'div': 'c', - }); - - if (global.context.referrer) { - params['amp_rref'] = encodeURIComponent(global.context.referrer); - } - - if (global.context.canonicalUrl) { - params['amp_rurl'] = encodeURIComponent(global.context.canonicalUrl); - } - - const serverName = data['serverName'] || 'code.directadvert.ru'; - - const url = - '//' + - encodeURIComponent(serverName) + - '/data/' + - encodeURIComponent(data['blockId']) + - '.js?' + - serializeQueryString(params); - - loadScript( - global, - url, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/distroscale.js b/ads/distroscale.js deleted file mode 100644 index 3ad830f2f4b09..0000000000000 --- a/ads/distroscale.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function distroscale(global, data) { - validateData(data, ['pid'], ['zid', 'tid']); - let src = '//c.jsrdn.com/s/cs.js?p=' + encodeURIComponent(data.pid); - - if (data.zid) { - src += '&z=' + encodeURIComponent(data.zid); - } else { - src += '&z=amp'; - } - - if (data.tid) { - src += '&t=' + encodeURIComponent(data.tid); - } - - let srcUrl = global.context.sourceUrl; - - srcUrl = srcUrl.replace(/#.+/, '').replace(/\?.+/, ''); - - src += '&f=' + encodeURIComponent(srcUrl); - - global.dsAMPCallbacks = { - renderStart: global.context.renderStart, - noContentAvailable: global.context.noContentAvailable, - }; - loadScript( - global, - src, - () => {}, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/dotandads.js b/ads/dotandads.js deleted file mode 100644 index 8c2e82f51eb95..0000000000000 --- a/ads/dotandads.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function dotandads(global, data) { - global.data = data; - writeScript(global, 'https://amp.ad.dotandad.com/dotandadsAmp.js'); -} diff --git a/ads/dynad.js b/ads/dynad.js deleted file mode 100644 index d30b8d1116b6e..0000000000000 --- a/ads/dynad.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import { - validateData, - validateSrcContains, - validateSrcPrefix, - writeScript, -} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function dynad(global, data) { - validateData(data, ['src'], []); - validateSrcPrefix('https:', data.src); - validateSrcContains('/t.dynad.net/', data.src); - writeScript(global, data.src); -} diff --git a/ads/eadv.js b/ads/eadv.js deleted file mode 100644 index b61884465308a..0000000000000 --- a/ads/eadv.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function eadv(global, data) { - validateData(data, ['x', 'u'], []); - writeScript( - global, - 'https://www.eadv.it/track/?x=' + data.x + '&u=' + data.u - ); -} diff --git a/ads/eas.js b/ads/eas.js deleted file mode 100644 index 8a689a0eb77f3..0000000000000 --- a/ads/eas.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function eas(global, data) { - validateData(data, ['easDomain']); - global.easAmpParams = data; - writeScript(global, 'https://amp.emediate.eu/amp.v0.js'); -} diff --git a/ads/eas.md b/ads/eas.md deleted file mode 100644 index f27481bdc7485..0000000000000 --- a/ads/eas.md +++ /dev/null @@ -1,59 +0,0 @@ - - -# CxenseDisplay - -## Example - -### Basic call - -Corresponds to `https://eas4.emediate.eu/eas?cu=12345` - -```html - - -``` - -### With targeting parameters - -```html - - -``` - -## Configuration - -### Required parameters - -- `data-eas-domain`: Specify your ad-server domain (e.g., `eas3.emediate.se`); If you're using a custom domain-name (like, `eas.somesite.com`) you should NOT use that one unless you already have an SSL-certificate installed on our ad servers. - -### Optional parameters - -- `data-eas-[parameter]`: Any ad-request parameter, like 'cu'. diff --git a/ads/empower.js b/ads/empower.js deleted file mode 100644 index 6e4ef2d04971a..0000000000000 --- a/ads/empower.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function empower(global, data) { - validateData(data, ['site', 'zone'], ['category']); - global.category = data.category || 'general'; - global.site = data.site + ':general'; - global.zone = data.zone; - global.iwidth = data.width; - global.iheight = data.height; - writeScript(global, 'https://cdn.empower.net/sdk/amp-ad.min.js'); -} diff --git a/ads/engageya.js b/ads/engageya.js deleted file mode 100644 index 8a8864b86695d..0000000000000 --- a/ads/engageya.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function engageya(global, data) { - validateData(data, ['widgetids']); - - global._engageya = global._engageya || { - viewId: global.context.pageViewId, - widgetIds: data['widgetids'], - websiteId: data['websiteid'], - publisherId: data['publisherid'], - url: data['url'] || global.context.canonicalUrl, - ampURL: data['ampurl'] || global.context.sourceUrl, - mode: data['mode'] || 1, - style: data['stylecss'] || '', - referrer: global.context.referrer, - }; - - loadScript(global, 'https://widget.engageya.com/engageya_amp_loader.js'); -} diff --git a/ads/epeex.js b/ads/epeex.js deleted file mode 100644 index 160d3023d88f0..0000000000000 --- a/ads/epeex.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function epeex(global, data) { - global._epeex = global._epeex || { - account: data['account'] || 'demoepeex', - channel: data['channel'] || '1', - htmlURL: data['htmlurl'] || encodeURIComponent(global.context.canonicalUrl), - ampURL: data['ampurl'] || encodeURIComponent(global.context.sourceUrl), - testMode: data['testmode'] || 'false', - }; - - // load the epeex AMP remote js file - loadScript(global, 'https://epeex.com/related/service/widget/amp/remote.js'); -} diff --git a/ads/eplanning.js b/ads/eplanning.js deleted file mode 100644 index 7c50740779e13..0000000000000 --- a/ads/eplanning.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function eplanning(global, data) { - validateData(data, [ - 'epl_si', - 'epl_isv', - 'epl_sv', - 'epl_sec', - 'epl_kvs', - 'epl_e', - ]); - // push the two object into the '_eplanning' global - (global._eplanning = global._eplanning || []).push({ - sI: data.epl_si, - isV: data.epl_isv, - sV: data.epl_sv, - sec: data.epl_sec, - kVs: data.epl_kvs, - e: data.epl_e, - }); - loadScript(global, 'https://us.img.e-planning.net/layers/epl-amp.js'); -} diff --git a/ads/ezoic.js b/ads/ezoic.js deleted file mode 100644 index f783c5777917a..0000000000000 --- a/ads/ezoic.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function ezoic(global, data) { - // TODO: check mandatory fields - validateData(data, [], ['slot', 'targeting', 'extras']); - loadScript(global, 'https://g.ezoic.net/ezoic/ampad.js', () => { - loadScript( - global, - 'https://www.googletagservices.com/tag/js/gpt.js', - () => { - global.googletag.cmd.push(() => { - new window.EzoicAmpAd(global, data).createAd(); - }); - } - ); - }); -} diff --git a/ads/ezoic.md b/ads/ezoic.md deleted file mode 100644 index 5f47b7d7fe27a..0000000000000 --- a/ads/ezoic.md +++ /dev/null @@ -1,56 +0,0 @@ - - -# Ezoic - -## Example - -```html - - -``` - -## Ad size - -The ad size is the size of the ad that should be displayed. Make sure the `width` and `height` attributes of the `amp-ad` tag match the available ad size. - -## Configuration - -To generate tags, please visit https://svc.ezoic.com/publisher.php?login - -Supported parameters: - -- `data-slot`: the slot name corresponding to the ad position - -Supported via `json` attribute: - -- `targeting` -- `extras` - -## Consent Support - -Ezoic amp-ad adhere to a user's consent in the following ways: - -- `CONSENT_POLICY_STATE.SUFFICIENT`: Ezoic amp-ad will display a personalized ad to the user. -- `CONSENT_POLICY_STATE.INSUFFICIENT`: Ezoic amp-ad will display a non-personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Ezoic amp-ad will display a personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN`: Ezoic amp-ad will not display an ad. diff --git a/ads/f1e.js b/ads/f1e.js deleted file mode 100644 index 74fe9db6983d1..0000000000000 --- a/ads/f1e.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function f1e(global, data) { - validateData(data, ['url', 'target'], []); - global.f1eData = data; - writeScript(global, 'https://img.ak.impact-ad.jp/util/f1e_amp.min.js'); -} diff --git a/ads/f1h.js b/ads/f1h.js deleted file mode 100644 index 942219b53faa8..0000000000000 --- a/ads/f1h.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function f1h(global, data) { - validateData(data, ['sectionId', 'slot']); - - const scriptUrl = - data['debugsrc'] || 'https://img.ak.impact-ad.jp/fh/f1h_amp.js'; - - global.f1hData = data; - loadScript(global, scriptUrl); -} diff --git a/ads/felmat.js b/ads/felmat.js deleted file mode 100644 index 2bce7bf1ee79b..0000000000000 --- a/ads/felmat.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function felmat(global, data) { - validateData(data, ['host', 'fmt', 'fmk', 'fmp']); - global.fmParam = data; - writeScript( - global, - 'https://t.' + encodeURIComponent(data.host) + '/js/fmamp.js' - ); -} diff --git a/ads/flite.js b/ads/flite.js deleted file mode 100644 index 95fbfbd91e4ee..0000000000000 --- a/ads/flite.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function flite(global, data) { - // TODO: check mandatory fields - validateData(data, [], ['guid', 'mixins']); - const {guid} = data, - o = global, - e = encodeURIComponent, - x = 0; - let r = '', - dep = ''; - o.FLITE = o.FLITE || {}; - o.FLITE.config = o.FLITE.config || {}; - o.FLITE.config[guid] = o.FLITE.config[guid] || {}; - o.FLITE.config[guid].cb = Math.random(); - o.FLITE.config[guid].ts = +Number(new Date()); - r = global.context.location.href; - const m = r.match(new RegExp('[A-Za-z]+:[/][/][A-Za-z0-9.-]+')); - dep = data.mixins ? '&dep=' + data.mixins : ''; - const url = [ - 'https://r.flite.com/syndication/uscript.js?i=', - e(guid), - '&v=3', - dep, - '&x=us', - x, - '&cb=', - o.FLITE.config[guid].cb, - '&d=', - e((m && m[0]) || r), - '&tz=', - new Date().getTimezoneOffset(), - ].join(''); - loadScript(o, url); -} diff --git a/ads/fluct.js b/ads/fluct.js deleted file mode 100644 index 5eaa306f679f7..0000000000000 --- a/ads/fluct.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/* global adingoFluct: false */ - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function fluct(global, data) { - validateData(data, ['g', 'u']); - writeScript( - global, - `https://cdn-fluct.sh.adingo.jp/f.js?G=${encodeURIComponent(data['g'])}`, - function () { - adingoFluct.showAd(data['u']); - } - ); -} diff --git a/ads/forkmedia.js b/ads/forkmedia.js deleted file mode 100644 index fa2ec2de793eb..0000000000000 --- a/ads/forkmedia.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function forkmedia(global, data) { - let src = null; - if (data.product === 'inread') { - src = 'https://delivery.forkcdn.com/rappio/inread/v1.1/amp/inread.js'; - } else if (data.product === 'vibe') { - src = 'https://vibecdn.forkcdn.com/Inarticle/amp/iav.js'; - } else { - src = 'https://delivery.forkcdn.com/amp/default.js'; - } - - loadScript( - global, - src, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/freewheel.js b/ads/freewheel.js deleted file mode 100644 index b0e0757ab56a9..0000000000000 --- a/ads/freewheel.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function freewheel(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - global._freewheel_amp = { - data, - }; - - validateData( - data, - ['zone'], - [ - 'zone', - 'gdpr', - 'gdpr_consent', - 'useCMP', - 'zIndex', - 'blurDisplay', - 'timeline', - 'soundButton', - 'defaultMute', - 'onOver', - 'closeAction', - 'errorAction', - 'pauseRatio', - 'label', - 'vastUrlParams', - ] - ); - - loadScript(global, 'https://cdn.stickyadstv.com/prime-time/fw-amp.min.js'); -} diff --git a/ads/freewheel.md b/ads/freewheel.md deleted file mode 100644 index 66759d16684be..0000000000000 --- a/ads/freewheel.md +++ /dev/null @@ -1,62 +0,0 @@ - - -# FreeWheel - -## Example - -### Expand-banner - -```html - - -``` - -### FloorAd - -```html - - - -``` - -## Configuration - -For details on the configuration semantics, please contact the FreeWheel support team : clientsidesdk@freewheel.tv - -Supported parameters: -All parameters are optional, unless otherwise stated - -- `data-zone` [required] -- `data-blurDisplay` -- `data-timeline` -- `data-soundButton` -- `data-defaultMute` -- `data-onOver` -- `data-closeAction` -- `data-errorAction` -- `data-pauseRatio` -- `data-label` -- `data-vastUrlParams` -- `data-gdpr` -- `data-gdpr_consent` -- `data-useCMP` diff --git a/ads/fusion.js b/ads/fusion.js deleted file mode 100644 index e4b06476ba689..0000000000000 --- a/ads/fusion.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {string=} input - * @return {JsonObject|undefined} - */ -function queryParametersToObject(input) { - if (!input) { - return undefined; - } - return input - .split('&') - .filter((_) => _) - .reduce((obj, val) => { - const kv = val.split('='); - return Object.assign(obj, {[kv[0]]: kv[1] || true}); - }, {}); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function fusion(global, data) { - validateData( - data, - [], - ['mediaZone', 'layout', 'adServer', 'space', 'parameters'] - ); - - const container = global.document.getElementById('c'); - const ad = global.document.createElement('div'); - ad.setAttribute('data-fusion-space', data.space); - container.appendChild(ad); - const parameters = queryParametersToObject(data.parameters); - - writeScript( - global, - 'https://assets.adtomafusion.net/fusion/latest/fusion-amp.min.js', - () => { - global.Fusion.apply(container, global.Fusion.loadAds(data, parameters)); - - global.Fusion.on.warning.run((ev) => { - if (ev.msg === 'Space not present in response.') { - global.context.noContentAvailable(); - } - }); - } - ); -} diff --git a/ads/genieessp.js b/ads/genieessp.js deleted file mode 100644 index 0eb920ac1fd63..0000000000000 --- a/ads/genieessp.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function genieessp(global, data) { - validateData(data, ['vid', 'zid']); - - global.data = data; - writeScript(global, 'https://js.gsspcln.jp/l/amp.js'); -} diff --git a/ads/giraff.js b/ads/giraff.js deleted file mode 100644 index 4f317f3ffaa2a..0000000000000 --- a/ads/giraff.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function giraff(global, data) { - validateData(data, ['blockName']); - - const serverName = data['serverName'] || 'code.giraff.io'; - const url = - '//' + - encodeURIComponent(serverName) + - '/data/widget-' + - encodeURIComponent(data['blockName']) + - '.js'; - - loadScript( - global, - url, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); - - const anchorEl = global.document.createElement('div'); - const widgetId = data['widgetId'] ? '_' + data['widgetId'] : ''; - anchorEl.id = 'grf_' + data['blockName'] + widgetId; - global.document.getElementById('c').appendChild(anchorEl); -} diff --git a/ads/gmossp.js b/ads/gmossp.js deleted file mode 100644 index 9b8ce85062e62..0000000000000 --- a/ads/gmossp.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -const gmosspFields = ['id']; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function gmossp(global, data) { - validateData(data, gmosspFields, []); - - global.gmosspParam = data; - writeScript(global, 'https://cdn.gmossp-sp.jp/ads/amp.js'); -} diff --git a/ads/google/OWNERS b/ads/google/OWNERS index 94b65ebe76929..1b8c3b93f6d99 100644 --- a/ads/google/OWNERS +++ b/ads/google/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/ads/google/a4a/docs/Network-Impl-Guide.md b/ads/google/a4a/docs/Network-Impl-Guide.md index 71597d2b7aae7..16f7d351e0d40 100644 --- a/ads/google/a4a/docs/Network-Impl-Guide.md +++ b/ads/google/a4a/docs/Network-Impl-Guide.md @@ -4,26 +4,31 @@ This guide outlines the requirements and steps for ad networks to implement Fast Fetch for early ad request and support for AMP ads returned by the ad network to be given preferential rendering. -- _Status: Draft_ -- _Authors: [kjwright@google.com](mailto:kjwright@google.com), - [bradfrizzell@google.com](mailto:bradfrizzell@google.com)_ -- _Last Updated: 1-27-2016_ - -## Contents - -- [Background](#background) -- [Overview](#overview) -- [Detailed design](#detailed-design) - - [Ad server requirements](#ad-server-requirements) - - [SSL](#ssl) - - [AMPHTML ad creative signature](#amphtml-ad-creative-signature) - - [Ad response headers](#ad-response-headers) - - [Creating an AMPHTML ad extension implementation](#creating-an-amphtml-ad-extension-implementation) - - [Create the implementation script](#create-the-implementation-script) - - [Create the configuration file](#create-the-configuration-file) - - [Create documentation](#create-documentation) - - [Create tests](#create-tests) -- [Checklist for ad network implementation](#checklist-for-ad-network-implementation) +- _Status: Draft_ +- _Authors: [kjwright@google.com](mailto:kjwright@google.com), + [bradfrizzell@google.com](mailto:bradfrizzell@google.com)_ +- _Last Updated: 1-27-2016_ + + + +- [Background](#background) +- [Overview](#overview) +- [Detailed design](#detailed-design) + - [Ad server requirements](#ad-server-requirements) + - [SSL](#ssl) + - [AMPHTML ad creative signature](#amphtml-ad-creative-signature) + - [Ad response headers](#ad-response-headers) + - [Creating an AMPHTML ad extension implementation](#creating-an-amphtml-ad-extension-implementation) + - [Create the implementation script](#create-the-implementation-script) + - [Create the configuration file](#create-the-configuration-file) + - [Create documentation](#create-documentation) + - [Create tests](#create-tests) +- [Checklist for ad network implementation](#checklist-for-ad-network-implementation) ## Background @@ -31,7 +36,7 @@ If you haven’t already, please read the [AMPHTML ads readme](./a4a-readme.md) learn about why all networks should implement Fast Fetch. Relevant design documents: [AMPHTML ads readme](./a4a-readme.md), -[AMPHTML ads spec](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/amp-a4a-format.md) +[AMPHTML ads spec](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/amp-a4a-format.md) & [intent to implement](https://github.com/ampproject/amphtml/issues/3133). ## Overview @@ -49,8 +54,8 @@ To support Fast Fetch, ad networks are required to implement the following: 1. An [XHR CORS](https://www.w3.org/TR/cors/) for the ad request. 2. The JavaScript to build the ad request, which must be located within the AMP HTML GitHub repository (example implementations: - [AdSense](https://github.com/ampproject/amphtml/tree/master/extensions/amp-ad-network-adsense-impl) - & [Google Ad Manager](https://github.com/ampproject/amphtml/tree/master/extensions/amp-ad-network-doubleclick-impl)). + [AdSense](https://github.com/ampproject/amphtml/tree/main/extensions/amp-ad-network-adsense-impl) + & [Google Ad Manager](https://github.com/ampproject/amphtml/tree/main/extensions/amp-ad-network-doubleclick-impl)). ## Detailed design @@ -71,11 +76,11 @@ All network communication via the AMP HTML runtime (resources or XHR) require SS #### AMPHTML ad creative signature -For the AMP runtime to know that a creative is valid [AMP](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/amp-a4a-format.md), +For the AMP runtime to know that a creative is valid [AMP](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/amp-a4a-format.md), and thus receive preferential ad rendering, it must pass a client-side, validation check. The creative must be sent by the ad network to a validation service which verifies that the creative conforms to the -[AMPHTML ad specification](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/amp-a4a-format.md). +[AMPHTML ad specification](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/amp-a4a-format.md). If the ad conforms, the creative is rewritten by the validation service and the rewritten creative and a cryptographic signature are returned to the ad network. The rewritten creative and signature must be included in the response to the AMP @@ -112,7 +117,7 @@ is allowed by including the following headers in the response: custom headers are not included, they will be dropped by the browser. -For details on CORS verification in AMP context, see [Verify CORS requests](https://github.com/ampproject/amphtml/blob/master/spec/amp-cors-requests.md#verify-cors-header). +For details on CORS verification in AMP context, see [Verify CORS requests](https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-cors-requests.md#verify-cors-header). ### Creating an AMPHTML ad extension implementation @@ -133,7 +138,7 @@ ad network: To create an ad network implementation, you must perform the following: 1. Create a new extension in the `extensions` directory of the AMP HTML Github - [repository](https://github.com/ampproject/amphtml/tree/master/extensions) + [repository](https://github.com/ampproject/amphtml/tree/main/extensions) whose path and name match the `type` attribute given for the amp-ad element as follows: @@ -151,7 +156,7 @@ To create an ad network implementation, you must perform the following: replaced by their own network. Files must implement all requirements as specified below. Anything not specified, i.e. helper functions etc are at the discretion of the ad network, but must be approved using the [process required - for all AMP contributions](https://github.com/ampproject/amphtml/blob/master/contributing/contributing-code.md). + for all AMP contributions](https://github.com/ampproject/amphtml/blob/main/docs/contributing-code.md). #### Create the implementation script @@ -159,7 +164,7 @@ _For reference, see [Figure 1 Parts B and D](#detailed-design)._ 1. Create a file named `amp-ad-network--impl.js`, which implement the `AmpAdNetworkImpl` class. -2. This class must extend [AmpA4A](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/0.1/amp-a4a.js). +2. This class must extend [AmpA4A](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/0.1/amp-a4a.js). 3. This class must overwrite the super class method **getAdUrl()**. ```javascript @@ -167,9 +172,9 @@ _For reference, see [Figure 1 Parts B and D](#detailed-design)._ // @return {string} - the ad url ``` -Examples of network implementations can be seen for [Google Ad Manager](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js) and [AdSense](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js). +Examples of network implementations can be seen for [Google Ad Manager](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js) and [AdSense](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js). Usage of `getAdUrl` can be seen within the `this.adPromise_ promise` chain in -[amp-a4a.js](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/0.1/amp-a4a.js). +[amp-a4a.js](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/0.1/amp-a4a.js). #### Create the configuration file @@ -185,7 +190,7 @@ _For reference, see [Figure 1: Part A](#figure-1-fast-fetch-rendering-flow)_. // @return (boolean) Whether or not A4A should be used in this context. ``` -2. Once this file is implemented, you must also update [amphtml/ads/\_a4a-config.js](https://github.com/ampproject/amphtml/blob/master/ads/_a4a-config.js). +2. Once this file is implemented, you must also update [amphtml/ads/\_a4a-config.js](https://github.com/ampproject/amphtml/blob/main/ads/_a4a-config.js). Specifically, `IsA4AEnabled()` must be imported, and it must be mapped to the ad network type in the a4aRegistry mapping. @@ -203,16 +208,16 @@ _For reference, see [Figure 1: Part A](#figure-1-fast-fetch-rendering-flow)_. }); ``` -Example configs: [AdSense](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config.js#L68). -Usage of Google Ad Manager and AdSense configs can be seen in [\_a4a-config.js](https://github.com/ampproject/amphtml/blob/master/ads/_a4a-config.js). +Example configs: [AdSense](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-adsense-impl/0.1/adsense-a4a-config.js#L68). +Usage of Google Ad Manager and AdSense configs can be seen in [\_a4a-config.js](https://github.com/ampproject/amphtml/blob/main/ads/_a4a-config.js). #### Create documentation Create a file named `amp-ad-network--impl-internal.md`, and within this file provide thorough documentation for the use of your implementation. -Examples: See [Google Ad Manager](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-doubleclick-impl/amp-ad-network-doubleclick-impl-internal.md) -and [AdSense](https://github.com/ampproject/amphtml/blob/master/extensions/amp-ad-network-adsense-impl/amp-ad-network-adsense-impl-internal.md). +Examples: See [Google Ad Manager](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-doubleclick-impl/amp-ad-network-doubleclick-impl-internal.md) +and [AdSense](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-adsense-impl/amp-ad-network-adsense-impl-internal.md). #### Create tests @@ -221,14 +226,14 @@ for your AMP ad network implementation. ## Checklist for ad network implementation -- [ ] All Server-AMP communication done with SSL -- [ ] AMP ads sent to validation server -- [ ] Validated AMP ads sent from network to AMP with signature -- [ ] Validated AMP ads sent from network to AMP with appropriate headers -- [ ] File hierarchy created within amphtml/extensions -- [ ] Custom `amp-ad-network--impl.js` overwrites `getAdUrl()` -- [ ] `-a4a-config.js` implements `IsA4AEnabled()` -- [ ] Mapping added for ad network to a4aRegistry map within `_a4a-config.js` -- [ ] Documentation written in `amp-ad-network--impl-internal.md` -- [ ] Tests written in `test-amp-ad-network--impl.js` -- [ ] Pull request merged to master +- [ ] All Server-AMP communication done with SSL +- [ ] AMP ads sent to validation server +- [ ] Validated AMP ads sent from network to AMP with signature +- [ ] Validated AMP ads sent from network to AMP with appropriate headers +- [ ] File hierarchy created within amphtml/extensions +- [ ] Custom `amp-ad-network--impl.js` overwrites `getAdUrl()` +- [ ] `-a4a-config.js` implements `IsA4AEnabled()` +- [ ] Mapping added for ad network to a4aRegistry map within `_a4a-config.js` +- [ ] Documentation written in `amp-ad-network--impl-internal.md` +- [ ] Tests written in `test-amp-ad-network--impl.js` +- [ ] Pull request merged to the repo diff --git a/ads/google/a4a/docs/RTBExchangeGuide.md b/ads/google/a4a/docs/RTBExchangeGuide.md index 79583abe66323..014f93f509a8b 100644 --- a/ads/google/a4a/docs/RTBExchangeGuide.md +++ b/ads/google/a4a/docs/RTBExchangeGuide.md @@ -20,9 +20,9 @@ Exchanges will need to indicate in the RTB bid request whether a page is built i A new field is added to the `Site` object of the OpenRTB standard to indicate whether a webpage is built on AMP. In OpenRTB 2.5, this is section 3.2.13. -| Field | Scope | Type | Default | Description | -| ----- | -------- | ------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `amp` | optional | integer | - | Whether the request is for an Accelerated Mobile Page. 0 = page is non-AMP, 1 = page is built with AMP HTML. AMP status unknown if omitted. | +| Field | Scope | Type | Default | Description | +| ----- | -------- | ------- | :-----: | -------------------------------------------------------------------------------------------------------------------------------- | +| `amp` | optional | integer | - | Whether the request is for an AMP Document. 0 = page is non-AMP, 1 = page is built with AMP HTML. AMP status unknown if omitted. | **`Imp` Object additional field: `ampad`** @@ -55,29 +55,29 @@ SSPs will need to provide a new field in the bid response to allow bidders to re ### Verification of valid AMP -- For AMPHTML ads to be rendered early, the exchange is required to verify and sign in real time that the ad is written in amp4ads `` creative format. -- See "[Proposed Design](https://github.com/ampproject/amphtml/issues/3133)" for signing. -- Ads that are valid AMPHTML ads will be allowed to render early by AMP pages. Ads that are not verified as valid AMPHTML ads will render at the same speed as non-AMPHTML ads. -- Only AMPHTML ads should be returned in the `ampadurl`. +- For AMPHTML ads to be rendered early, the exchange is required to verify and sign in real time that the ad is written in amp4ads `` creative format. +- See "[Proposed Design](https://github.com/ampproject/amphtml/issues/3133)" for signing. +- Ads that are valid AMPHTML ads will be allowed to render early by AMP pages. Ads that are not verified as valid AMPHTML ads will render at the same speed as non-AMPHTML ads. +- Only AMPHTML ads should be returned in the `ampadurl`. ### Server-side fetch -- For AMPHTML ads to be rendered early, AMPHTML ad content must be fetched with 0 additional "hops" from the client. This is designed to avoid poor user experiences due to ad latency and extra client-side calls. -- The exchange's servers (not the client browser) will request the AMPHTML ad content located at the URL provided in `ampadurl` after a bidder wins the auction. -- Creative servers must respond and return content within some reasonable SLA, recommended at 150ms. -- The AMPHTML ad will be injected into the adslot and subsequently rendered. Note that since a valid AMPHTML ad cannot contain an iframe or another ad tag, the server-side fetch must retrieve the actual HTML of the creative. +- For AMPHTML ads to be rendered early, AMPHTML ad content must be fetched with 0 additional "hops" from the client. This is designed to avoid poor user experiences due to ad latency and extra client-side calls. +- The exchange's servers (not the client browser) will request the AMPHTML ad content located at the URL provided in `ampadurl` after a bidder wins the auction. +- Creative servers must respond and return content within some reasonable SLA, recommended at 150ms. +- The AMPHTML ad will be injected into the adslot and subsequently rendered. Note that since a valid AMPHTML ad cannot contain an iframe or another ad tag, the server-side fetch must retrieve the actual HTML of the creative. ### Impression Tracking and Billing URLs -- RTB buyers often include impression trackers as a structured field in the bid response (for example `Bid.burl`, the "billing notice URL" in OpenRTB 2.5). -- It is up to the exchange or publisher ad server to determine how these URLs are fired, but <[amp-pixel](https://amp.dev/documentation/components/amp-pixel)> and <[amp-analytics](https://amp.dev/documentation/components/amp-analytics)> can handle most impression tracking and analytics use cases. +- RTB buyers often include impression trackers as a structured field in the bid response (for example `Bid.burl`, the "billing notice URL" in OpenRTB 2.5). +- It is up to the exchange or publisher ad server to determine how these URLs are fired, but <[amp-pixel](https://amp.dev/documentation/components/amp-pixel)> and <[amp-analytics](https://amp.dev/documentation/components/amp-analytics)> can handle most impression tracking and analytics use cases. ## Background Docs -- [AMPHTML Ads for AMP Pages (Github)](https://github.com/ampproject/amphtml/issues/3133) -- [AMPHTML Ad Creative Format Spec (Github)](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/amp-a4a-format.md) -- [AMPHTML Ads Overview (Github)](https://github.com/ampproject/amphtml/blob/master/ads/google/a4a/docs/a4a-readme.md) -- [AMPHTML Ads Website from amp.dev](https://amp.dev/community/platform-and-vendor-partners) -- [Example AMPHTML Ads](https://amp.dev/documentation/examples/) -- [Speed comparison](https://amp.dev/documentation/examples/advertising-analytics/amphtml_ads_vs_non_amp_ads/?format=websites): see how fast an AMP Ad loads in comparison to a regular ad. Best viewed on a 3G connection. -- [Discussion in OpenRTB Dev Forum](https://groups.google.com/forum/#!topic/openrtb-dev/0wyPsF5D07Q): RTB Specific Proposal +- [AMPHTML Ads for AMP Pages (Github)](https://github.com/ampproject/amphtml/issues/3133) +- [AMPHTML Ad Creative Format Spec (Github)](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/amp-a4a-format.md) +- [AMPHTML Ads Overview (Github)](https://github.com/ampproject/amphtml/blob/main/ads/google/a4a/docs/a4a-readme.md) +- [AMPHTML Ads Website from amp.dev](https://amp.dev/community/platform-and-vendor-partners) +- [Example AMPHTML Ads](https://amp.dev/documentation/examples/) +- [Speed comparison](https://amp.dev/documentation/examples/advertising-analytics/amphtml_ads_vs_non_amp_ads/?format=websites): see how fast an AMP Ad loads in comparison to a regular ad. Best viewed on a 3G connection. +- [Discussion in OpenRTB Dev Forum](https://groups.google.com/forum/#!topic/openrtb-dev/0wyPsF5D07Q): RTB Specific Proposal diff --git a/ads/google/a4a/docs/a4a-readme.md b/ads/google/a4a/docs/a4a-readme.md index fe47824ee5ac8..031566246785b 100644 --- a/ads/google/a4a/docs/a4a-readme.md +++ b/ads/google/a4a/docs/a4a-readme.md @@ -2,4 +2,4 @@ For details, please update your link to point to the latest doc available at: https://amp.dev/documentation/guides-and-tutorials/learn/intro-to-amphtml-ads. -_Need to make changes to the doc_? The doc is now hosted in the [docs repo](https://github.com/ampproject/docs/blob/master/content/docs/ads/amphtml_ads.md). +_Need to make changes to the doc_? The doc is now hosted in the [amp.dev repo](https://github.com/ampproject/amp.dev/blob/future/pages/content/amp-dev/documentation/guides-and-tutorials/learn/intro-to-amphtml-ads.md). diff --git a/ads/google/a4a/experiment-utils.js b/ads/google/a4a/experiment-utils.js index c912da5c0e1fc..02fe75e2855cb 100644 --- a/ads/google/a4a/experiment-utils.js +++ b/ads/google/a4a/experiment-utils.js @@ -63,12 +63,13 @@ export class ExperimentUtils { * @return {?string} */ maybeSelectExperiment(win, element, selectionBranches, experimentName) { - const experimentInfoMap = /** @type {!Object} */ ({}); - experimentInfoMap[experimentName] = { + const experimentInfoList = /** @type {!Array} */ ([]); + experimentInfoList.push({ + experimentId: experimentName, isTrafficEligible: () => true, branches: selectionBranches, - }; - randomlySelectUnsetExperiments(win, experimentInfoMap); + }); + randomlySelectUnsetExperiments(win, experimentInfoList); return getExperimentBranch(win, experimentName); } } diff --git a/ads/google/a4a/line-delimited-response-handler.js b/ads/google/a4a/line-delimited-response-handler.js index a7d2938a1c366..27d329624e1e0 100644 --- a/ads/google/a4a/line-delimited-response-handler.js +++ b/ads/google/a4a/line-delimited-response-handler.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {tryParseJson} from '../../../src/json'; +import {tryParseJson} from '../../../src/core/types/object/json'; /** * Handles an XHR response by calling lineCallback for each line delineation. @@ -49,7 +49,9 @@ export function lineDelimitedStreamer(win, response, lineCallback) { } const decoder = new TextDecoder('utf-8'); - const reader = /** @type {!ReadableStreamDefaultReader} */ (response.body.getReader()); + const reader = /** @type {!ReadableStreamDefaultReader} */ ( + response.body.getReader() + ); reader.read().then(function chunk(result) { if (result.value) { streamer( @@ -76,8 +78,9 @@ export function metaJsonCreativeGrouper(callback) { let first; return function (line, done) { if (first) { - const metadata = /** @type {!Object} */ (tryParseJson(first) || - {}); + const metadata = /** @type {!Object} */ ( + tryParseJson(first) || {} + ); const lowerCasedMetadata = Object.keys(metadata).reduce((newObj, key) => { newObj[key.toLowerCase()] = metadata[key]; return newObj; diff --git a/ads/google/a4a/shared/content-recommendation.js b/ads/google/a4a/shared/content-recommendation.js index 943ba505ba108..b4eafb69312f7 100644 --- a/ads/google/a4a/shared/content-recommendation.js +++ b/ads/google/a4a/shared/content-recommendation.js @@ -143,7 +143,6 @@ const PUB_CONTROL_EXAMPLE = * @record */ export class CoReConfig { - // eslint-disable-line no-unused-vars /** see comment on class */ constructor() { /** @const {number} */ @@ -242,7 +241,7 @@ export function getAutoConfig(availableWidth, isMobile) { * layoutType: (string|undefined), * }} */ -export let RawPublisherControlParams; // eslint-disable-line no-unused-vars +export let RawPublisherControlParams; /** * Get CoRe Pub Control UI Sizes. @@ -343,9 +342,8 @@ function validateAndParsePubControlParams(params) { const /** !Array */ layoutTypes = params.layoutType.split(','); const /** !Array */ numberOfRows = params.numberOfRows.split(','); - const /** !Array */ numberOfColumns = params.numberOfColumns.split( - ',' - ); + const /** !Array */ numberOfColumns = + params.numberOfColumns.split(','); // Check all params have same length. if ( diff --git a/ads/google/a4a/shared/test/test-content-recommendation.js b/ads/google/a4a/shared/test/test-content-recommendation.js index 6058e6d7f2686..ca5e4f4bf2837 100644 --- a/ads/google/a4a/shared/test/test-content-recommendation.js +++ b/ads/google/a4a/shared/test/test-content-recommendation.js @@ -20,7 +20,7 @@ import { getPubControlConfig, } from '../content-recommendation.js'; -describe('getAutoConfig', function () { +describes.sandboxed('getAutoConfig', {}, function () { it('should use image_stacked on wide slots', function () { const runTest = (availableWidth, expectedWidth, expectedHeight) => { expect( @@ -164,7 +164,7 @@ describe('getAutoConfig', function () { }); }); -describe('getPubControlConfig', function () { +describes.sandboxed('getPubControlConfig', {}, function () { it('should use setting when only one provided', function () { const rawPubControlParams = { numberOfColumns: '4', diff --git a/ads/google/a4a/shared/test/test-url-builder.js b/ads/google/a4a/shared/test/test-url-builder.js index 6cfd67fd6806e..f95ef55b61798 100644 --- a/ads/google/a4a/shared/test/test-url-builder.js +++ b/ads/google/a4a/shared/test/test-url-builder.js @@ -16,7 +16,7 @@ import {buildUrl} from '../url-builder'; -describe('buildUrl', () => { +describes.sandboxed('buildUrl', {}, () => { it('should build a simple URL', () => { expect( buildUrl('https://example.test', {'key': 'value'}, Infinity) 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 9327181a8d3a3..6758989a66861 100644 --- a/ads/google/a4a/test/test-line-delimited-response-handler.js +++ b/ads/google/a4a/test/test-line-delimited-response-handler.js @@ -19,7 +19,7 @@ import { metaJsonCreativeGrouper, } from '../line-delimited-response-handler'; -describe('#line-delimited-response-handler', () => { +describes.sandboxed('#line-delimited-response-handler', {}, (env) => { let chunkHandlerStub; let slotData; let win; @@ -92,7 +92,7 @@ describe('#line-delimited-response-handler', () => { } beforeEach(() => { - chunkHandlerStub = window.sandbox.stub(); + chunkHandlerStub = env.sandbox.stub(); }); describe('stream not supported', () => { @@ -154,7 +154,7 @@ describe('#line-delimited-response-handler', () => { } beforeEach(() => { - readStub = window.sandbox.stub(); + readStub = env.sandbox.stub(); response = { text: () => Promise.resolve(), body: { diff --git a/ads/google/a4a/test/test-traffic-experiments.js b/ads/google/a4a/test/test-traffic-experiments.js index 3c09a1ffdbe4f..e7a93bc93189c 100644 --- a/ads/google/a4a/test/test-traffic-experiments.js +++ b/ads/google/a4a/test/test-traffic-experiments.js @@ -14,14 +14,15 @@ * limitations under the License. */ -import {EXPERIMENT_ATTRIBUTE} from '../utils'; +import {AMP_EXPERIMENT_ATTRIBUTE, EXPERIMENT_ATTRIBUTE} from '../utils'; import { + addAmpExperimentIdToElement, addExperimentIdToElement, isInExperiment, validateExperimentIds, } from '../traffic-experiments'; -describe('all-traffic-experiments-tests', () => { +describes.sandboxed('all-traffic-experiments-tests', {}, () => { describe('#validateExperimentIds', () => { it('should return true for empty list', () => { expect(validateExperimentIds([])).to.be.true; @@ -84,6 +85,45 @@ describe('all-traffic-experiments-tests', () => { }); }); + describe('#addAmpExperimentIdToElement', () => { + it('should add attribute when there is none present to begin with', () => { + const element = document.createElement('div'); + expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.not.be.ok; + addAmpExperimentIdToElement('3', element); + expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('3'); + }); + + it('should append experiment to already valid single experiment', () => { + const element = document.createElement('div'); + element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99'); + addAmpExperimentIdToElement('3', element); + expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('99,3'); + }); + + it('should do nothing to already valid single experiment', () => { + const element = document.createElement('div'); + element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99'); + addAmpExperimentIdToElement(undefined, element); + expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('99'); + }); + + it('should append experiment to already valid multiple experiments', () => { + const element = document.createElement('div'); + element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99,77,11,0122345'); + addAmpExperimentIdToElement('3', element); + expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal( + '99,77,11,0122345,3' + ); + }); + + it('should should replace existing invalid experiments', () => { + const element = document.createElement('div'); + element.setAttribute(AMP_EXPERIMENT_ATTRIBUTE, '99,14,873,k,44'); + addAmpExperimentIdToElement('3', element); + expect(element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE)).to.equal('3'); + }); + }); + describe('#isInExperiment', () => { it('should return false for empty element and any query', () => { const element = document.createElement('div'); diff --git a/ads/google/a4a/test/test-utils.js b/ads/google/a4a/test/test-utils.js index 3fe90f7578080..800f6965a72c3 100644 --- a/ads/google/a4a/test/test-utils.js +++ b/ads/google/a4a/test/test-utils.js @@ -17,8 +17,8 @@ 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 {CONSENT_POLICY_STATE} from '../../../../src/consent-state'; import { + AMP_EXPERIMENT_ATTRIBUTE, EXPERIMENT_ATTRIBUTE, TRUNCATION_PARAM, ValidAdContainerTypes, @@ -32,11 +32,14 @@ import { getEnclosingContainerTypes, getIdentityToken, getIdentityTokenRequestUrl, + getServeNpaPromise, googleAdUrl, groupAmpAdsByType, maybeAppendErrorParameter, mergeExperimentIds, } from '../utils'; +import {CONSENT_POLICY_STATE} from '../../../../src/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 '../../../../src/services'; import {buildUrl} from '../shared/url-builder'; @@ -46,6 +49,7 @@ import {installDocService} from '../../../../src/service/ampdoc-impl'; import {installExtensionsService} from '../../../../src/service/extensions-impl'; import {installXhrService} from '../../../../src/service/xhr-impl'; import {toggleExperiment} from '../../../../src/experiments'; +import {user} from '../../../../src/log'; function setupForAdTesting(fixture) { installDocService(fixture.win, /* isSingleDoc */ true); @@ -68,23 +72,25 @@ function noopMethods( ampdoc, sandbox, pageLayoutBox = { - top: 11, - left: 12, - right: 0, - bottom: 0, + top: 11.1, + left: 12.1, width: 0, height: 0, } ) { const noop = () => {}; - impl.element.build = noop; + impl.element.buildInternal = noop; impl.element.getPlaceholder = noop; impl.element.createPlaceholder = noop; sandbox.stub(impl, 'getAmpDoc').returns(ampdoc); - sandbox.stub(impl, 'getPageLayoutBox').returns(pageLayoutBox); + sandbox.stub(impl.element, 'offsetParent').value(null); + sandbox.stub(impl.element, 'offsetTop').value(pageLayoutBox.top); + sandbox.stub(impl.element, 'offsetLeft').value(pageLayoutBox.left); + sandbox.stub(impl.element, 'offsetWidth').value(pageLayoutBox.width); + sandbox.stub(impl.element, 'offsetHeight').value(pageLayoutBox.height); } -describe('Google A4A utils', () => { +describes.sandboxed('Google A4A utils', {}, (env) => { //TODO: Add tests for other utils functions. describe('#additionalDimensions', () => { @@ -132,14 +138,59 @@ describe('Google A4A utils', () => { }, }; + const btrConfig = { + transport: {beacon: false, xhrpost: false}, + requests: { + btr1: 'https://example.test?id=1', + btr2: 'https://example.test?id=2', + }, + triggers: { + beginToRender: { + on: 'ini-load', + request: ['btr1', 'btr2'], + selector: 'amp-ad', + selectionMethod: 'closest', + }, + }, + }; + + const fullConfig = { + transport: {beacon: false, xhrpost: false}, + requests: { + visibility1: 'https://foo.com?hello=world', + visibility2: 'https://bar.com?a=b', + btr1: 'https://example.test?id=1', + btr2: 'https://example.test?id=2', + }, + triggers: { + continuousVisible: { + on: 'visible', + request: ['visibility1', 'visibility2'], + visibilitySpec: { + selector: 'amp-ad', + selectionMethod: 'closest', + visiblePercentageMin: 50, + continuousTimeMin: 1000, + }, + }, + beginToRender: { + on: 'ini-load', + request: ['btr1', 'btr2'], + selector: 'amp-ad', + selectionMethod: 'closest', + }, + }, + }; + it('should extract correct config from header', () => { return createIframePromise().then((fixture) => { setupForAdTesting(fixture); let url; + let btrUrl; const headers = { get(name) { if (name == 'X-AmpAnalytics') { - return JSON.stringify({url}); + return JSON.stringify({url, btrUrl}); } if (name == 'X-QQID') { return 'qqid_string'; @@ -158,7 +209,7 @@ describe('Google A4A utils', () => { 'width': '200', 'height': '50', 'type': 'adsense', - 'data-experiment-id': '00000001,0000002', + [EXPERIMENT_ATTRIBUTE]: '00000001,0000002', }); const a4a = new MockA4AImpl(element); url = 'not an array'; @@ -168,13 +219,27 @@ describe('Google A4A utils', () => { allowConsoleError( () => expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null ); + url = []; + btrUrl = []; expect(extractAmpAnalyticsConfig(a4a, headers)).to.not.be.ok; expect(extractAmpAnalyticsConfig(a4a, headers)).to.be.null; url = ['https://foo.com?hello=world', 'https://bar.com?a=b']; - const config = extractAmpAnalyticsConfig(a4a, headers); + btrUrl = []; + let config = extractAmpAnalyticsConfig(a4a, headers); expect(config).to.deep.equal(builtConfig); + + url = []; + btrUrl = ['https://example.test?id=1', 'https://example.test?id=2']; + config = extractAmpAnalyticsConfig(a4a, headers); + expect(config).to.deep.equal(btrConfig); + + url = ['https://foo.com?hello=world', 'https://bar.com?a=b']; + btrUrl = ['https://example.test?id=1', 'https://example.test?id=2']; + config = extractAmpAnalyticsConfig(a4a, headers); + expect(config).to.deep.equal(fullConfig); + headers.has = function (name) { expect(name).to.equal('X-AmpAnalytics'); return false; @@ -184,7 +249,7 @@ describe('Google A4A utils', () => { }); it('should add the correct CSI signals', () => { - window.sandbox + env.sandbox .stub(Services, 'documentInfoForDoc') .returns({pageViewId: 777}); const mockElement = { @@ -192,6 +257,8 @@ describe('Google A4A utils', () => { switch (name) { case EXPERIMENT_ATTRIBUTE: return '00000001,00000002'; + case AMP_EXPERIMENT_ATTRIBUTE: + return '103,204'; case 'type': return 'fake-type'; case 'data-amp-slot-index': @@ -226,6 +293,7 @@ describe('Google A4A utils', () => { new RegExp(`(\\?|&)met\\.a4a\\.0=${metricName}\\.-?[0-9]+(&|$)`), /(\?|&)dt=-?[0-9]+(&|$)/, /(\?|&)e\.0=00000001%2C00000002(&|$)/, + /(\?|&)aexp=103!204(&|$)/, /(\?|&)rls=\$internalRuntimeVersion\$(&|$)/, /(\?|&)adt.0=fake-type(&|$)/, ]; @@ -320,13 +388,13 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); - return fixture.addElement(elem).then(() => { - return googleAdUrl(impl, '', 0, [], []).then((url1) => { + noopMethods(impl, fixture.ampdoc, env.sandbox); + return fixture.addElement(elem).then(() => + googleAdUrl(impl, '', 0, [], []).then((url1) => { expect(url1).to.match(/ady=11/); expect(url1).to.match(/adx=12/); - }); - }); + }) + ); }); }); @@ -341,21 +409,19 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); + noopMethods(impl, fixture.ampdoc, env.sandbox); const getRect = () => { return {'width': 100, 'height': 200}; }; const getSize = () => { return {'width': 100, 'height': 200}; }; - const getScrollLeft = () => 12; - const getScrollTop = () => 34; - const viewportStub = window.sandbox.stub(Services, 'viewportForDoc'); + const getScrollLeft = () => 12.1; + const getScrollTop = () => 34.2; + const viewportStub = env.sandbox.stub(Services, 'viewportForDoc'); viewportStub.returns({getRect, getSize, getScrollTop, getScrollLeft}); - return fixture.addElement(elem).then(() => { - return googleAdUrl(impl, '', 0, {}, []).then((url1) => { - expect(url1).to.match(/scr_x=12&scr_y=34/); - }); + return googleAdUrl(impl, '', 0, {}, []).then((url1) => { + expect(url1).to.match(/scr_x=12&scr_y=34/); }); }); }); @@ -371,13 +437,15 @@ describe('Google A4A utils', () => { 'type': 'adsense', 'width': '320', 'height': '50', - 'data-experiment-id': '123,456', + [EXPERIMENT_ATTRIBUTE]: '123,456', + [AMP_EXPERIMENT_ATTRIBUTE]: '111,222', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); + noopMethods(impl, fixture.ampdoc, env.sandbox); return fixture.addElement(elem).then(() => { return googleAdUrl(impl, '', 0, {}, ['789', '098']).then((url1) => { expect(url1).to.match(/eid=123%2C456%2C789%2C098/); + expect(url1).to.match(/aexp=111!222/); }); }); }); @@ -394,7 +462,7 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); + noopMethods(impl, fixture.ampdoc, env.sandbox); impl.win.AMP_CONFIG = {type: 'production'}; impl.win.location.hash = 'foo,deid=123456,654321,bar'; return fixture.addElement(elem).then(() => { @@ -416,7 +484,7 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); + noopMethods(impl, fixture.ampdoc, env.sandbox); impl.win.gaGlobal = {cid: 'foo', hid: 'bar'}; return fixture.addElement(elem).then(() => { return googleAdUrl(impl, '', 0, [], []).then((url) => { @@ -438,8 +506,8 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); - const createElementStub = window.sandbox.stub( + noopMethods(impl, fixture.ampdoc, env.sandbox); + const createElementStub = env.sandbox.stub( impl.win.document, 'createElement' ); @@ -467,8 +535,8 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); - const createElementStub = window.sandbox.stub( + noopMethods(impl, fixture.ampdoc, env.sandbox); + const createElementStub = env.sandbox.stub( impl.win.document, 'createElement' ); @@ -494,9 +562,9 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); + noopMethods(impl, fixture.ampdoc, env.sandbox); impl.win.SVGElement = undefined; - const createElementStub = window.sandbox.stub( + const createElementStub = env.sandbox.stub( impl.win.document, 'createElement' ); @@ -524,11 +592,11 @@ describe('Google A4A utils', () => { 'height': '50', }); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); - window.sandbox + noopMethods(impl, fixture.ampdoc, env.sandbox); + env.sandbox .stub(Services.viewerForDoc(impl.getAmpDoc()), 'getReferrerUrl') .returns(new Promise(() => {})); - const createElementStub = window.sandbox.stub( + const createElementStub = env.sandbox.stub( impl.win.document, 'createElement' ); @@ -553,7 +621,7 @@ describe('Google A4A utils', () => { doc.win = fixture.win; const elem = createElementWithAttributes(doc, 'amp-a4a', {}); const impl = new MockA4AImpl(elem); - noopMethods(impl, fixture.ampdoc, window.sandbox); + noopMethods(impl, fixture.ampdoc, env.sandbox); return fixture.addElement(elem).then(() => { return googleAdUrl(impl, '', Date.now(), [], []).then((url) => { expect(url).to.match(/[&?]bdt=[1-9][0-9]*[&$]/); @@ -921,7 +989,7 @@ describe('Google A4A utils', () => { }); it('should include viewer lastVisibleTime', () => { - window.sandbox.stub(ampdoc, 'getLastVisibleTime').returns(300); + env.sandbox.stub(ampdoc, 'getLastVisibleTime').returns(300); const vars = getCsiAmpAnalyticsVariables('trigger', a4a, null); expect(vars['viewerLastVisibleTime']).to.be.a('number'); @@ -985,6 +1053,71 @@ describe('Google A4A utils', () => { expect(correlator).to.be.above(0); }); }); + + describes.realWin('#getServeNpaPromise', {}, (env) => { + let win, doc, element, geoService; + + beforeEach(() => { + win = env.win; + doc = win.document; + element = doc.createElement('amp-ad'); + geoService = { + isInCountryGroup(country) { + switch (country) { + case 'usca': + return GEO_IN_GROUP.IN; + case 'gdpr': + return GEO_IN_GROUP.NOT_IN; + default: + return GEO_IN_GROUP.NOT_DEFINED; + } + }, + }; + }); + + it('should return false if no attribute found', async () => { + expect(await getServeNpaPromise(element)).to.false; + }); + + it('should return true, regardless of geo location if empty string', async () => { + element.setAttribute('always-serve-npa', ''); + expect(await getServeNpaPromise(element)).to.true; + }); + + it('should return if doc is served from a defined geo group', async () => { + env.sandbox + .stub(Services, 'geoForDocOrNull') + .returns(Promise.resolve(geoService)); + element.setAttribute('always-serve-npa', 'gdpr,usca'); + expect(await getServeNpaPromise(element)).to.true; + }); + + it('should return false when doc is in an undefined group or not in', async () => { + const warnSpy = env.sandbox.stub(user(), 'warn'); + env.sandbox + .stub(Services, 'geoForDocOrNull') + .returns(Promise.resolve(geoService)); + + // Undefined group + element.setAttribute('always-serve-npa', 'tx'); + expect(await getServeNpaPromise(element)).to.false; + expect(warnSpy.args[0][0]).to.match(/AMP-AD/); + expect(warnSpy.args[0][1]).to.match(/Geo group "tx" was not defined./); + expect(warnSpy).to.have.been.calledOnce; + // Not in + element.setAttribute('always-serve-npa', 'gdpr'); + expect(await getServeNpaPromise(element)).to.false; + }); + + it('should return true when geoService is null', async () => { + geoService = null; + env.sandbox + .stub(Services, 'geoForDocOrNull') + .returns(Promise.resolve(geoService)); + element.setAttribute('always-serve-npa', 'gdpr'); + expect(await getServeNpaPromise(element)).to.true; + }); + }); }); describes.realWin('#groupAmpAdsByType', {amp: true}, (env) => { @@ -1080,14 +1213,13 @@ describes.realWin('#groupAmpAdsByType', {amp: true}, (env) => { expect(result['bar'].length).to.equal(2); expect(result['hello']).to.be.ok; expect(result['hello'].length).to.equal(1); - return Promise.all( - result['bar'].concat(result['hello']) - ).then((baseElements) => - baseElements.forEach((baseElement) => - expect(baseElement.element.getAttribute('type')).to.equal( - 'doubleclick' + return Promise.all(result['bar'].concat(result['hello'])).then( + (baseElements) => + baseElements.forEach((baseElement) => + expect(baseElement.element.getAttribute('type')).to.equal( + 'doubleclick' + ) ) - ) ); }); }); diff --git a/ads/google/a4a/traffic-experiments.js b/ads/google/a4a/traffic-experiments.js index 93e94c24fd0f1..94982fdfdb420 100644 --- a/ads/google/a4a/traffic-experiments.js +++ b/ads/google/a4a/traffic-experiments.js @@ -22,12 +22,13 @@ * impacts on click-throughs. */ -import {EXPERIMENT_ATTRIBUTE, mergeExperimentIds} from './utils'; import { - ExperimentInfo, // eslint-disable-line no-unused-vars -} from '../../../src/experiments'; + AMP_EXPERIMENT_ATTRIBUTE, + EXPERIMENT_ATTRIBUTE, + mergeExperimentIds, +} from './utils'; import {Services} from '../../../src/services'; -import {parseQueryString} from '../../../src/url'; +import {parseQueryString} from '../../../src/core/types/string/url'; /** @typedef {{ * control: string, @@ -141,18 +142,42 @@ export function validateExperimentIds(idList) { * * @param {string|undefined} experimentId ID to add to the element. * @param {Element} element to add the experiment ID to. + * @param {string} attr the attribute name that holds the experiments */ -export function addExperimentIdToElement(experimentId, element) { +function addExpIdToElement(experimentId, element, attr) { if (!experimentId) { return; } - const currentEids = element.getAttribute(EXPERIMENT_ATTRIBUTE); + const currentEids = element.getAttribute(attr); if (currentEids && validateExperimentIds(parseExperimentIds(currentEids))) { - element.setAttribute( - EXPERIMENT_ATTRIBUTE, - mergeExperimentIds([experimentId], currentEids) - ); + element.setAttribute(attr, mergeExperimentIds([experimentId], currentEids)); } else { - element.setAttribute(EXPERIMENT_ATTRIBUTE, experimentId); + element.setAttribute(attr, experimentId); } } + +/** + * Adds a single experimentID to an element iff it's a valid experiment ID. + * No-ops if the experimentId is undefined. + * + * @param {string|undefined} experimentId ID to add to the element. + * @param {Element} element to add the experiment ID to. + */ +export function addExperimentIdToElement(experimentId, element) { + addExpIdToElement(experimentId, element, EXPERIMENT_ATTRIBUTE); +} + +/** + * Adds a single AMP experimentID to an element iff it's a valid experiment ID. + * No-ops if the experimentId is undefined. + * + * Note that AMP experiment brances do not have their own unique IDs. Instead, + * we generate a pseudo ID for them by concatenating the id with the + * experiment's value. + * + * @param {string|undefined} experimentId ID to add to the element. + * @param {Element} element to add the experiment ID to. + */ +export function addAmpExperimentIdToElement(experimentId, element) { + addExpIdToElement(experimentId, element, AMP_EXPERIMENT_ATTRIBUTE); +} diff --git a/ads/google/a4a/utils.js b/ads/google/a4a/utils.js index 47ac02268f0ea..03444de5da966 100644 --- a/ads/google/a4a/utils.js +++ b/ads/google/a4a/utils.js @@ -14,12 +14,13 @@ * limitations under the License. */ -import {CONSENT_POLICY_STATE} from '../../../src/consent-state'; -import {DomFingerprint} from '../../../src/utils/dom-fingerprint'; +import {CONSENT_POLICY_STATE} from '../../../src/core/constants/consent-state'; +import {DomFingerprint} from '../../../src/core/dom/fingerprint'; +import {GEO_IN_GROUP} from '../../../extensions/amp-geo/0.1/amp-geo-in-group'; import {Services} from '../../../src/services'; import {buildUrl} from './shared/url-builder'; -import {dev, devAssert} from '../../../src/log'; -import {dict} from '../../../src/utils/object'; +import {dev, devAssert, user} from '../../../src/log'; +import {dict} from '../../../src/core/types/object'; import { getBinaryType, isExperimentOn, @@ -29,9 +30,10 @@ import {getConsentPolicyState} from '../../../src/consent'; import {getMeasuredResources} from '../../../src/ini-load'; import {getMode} from '../../../src/mode'; import {getOrCreateAdCid} from '../../../src/ad-cid'; +import {getPageLayoutBoxBlocking} from '../../../src/utils/page-layout-box'; import {getTimingDataSync} from '../../../src/service/variable-source'; import {internalRuntimeVersion} from '../../../src/internal-version'; -import {parseJson} from '../../../src/json'; +import {parseJson} from '../../../src/core/types/object/json'; import {whenUpgradedToCustomElement} from '../../../src/dom'; /** @type {string} */ @@ -47,20 +49,6 @@ const AmpAdImplementation = { AMP_AD_IFRAME_GET: '5', }; -/** @const {!{id: string, control: string, experiment: string}} */ -export const AMP_AD_NO_CENTER_CSS_EXP = { - id: 'amp-ad-no-center-css', - control: '21065897', - experiment: '21065898', -}; - -/** @const {!{id: string, control: string, experiment: string}} */ -export const RENDER_ON_IDLE_FIX_EXP = { - id: 'render-on-idle-fix', - control: '21066311', - experiment: '21066312', -}; - /** @const {!Object} */ export const ValidAdContainerTypes = { 'AMP-CAROUSEL': 'ac', @@ -87,7 +75,7 @@ export const QQID_HEADER = 'X-QQID'; export const SANDBOX_HEADER = 'amp-ff-sandbox'; /** - * Element attribute that stores experiment IDs. + * Element attribute that stores Google ads experiment IDs. * * Note: This attribute should be used only for tracking experimental * implementations of AMP tags, e.g., by AMPHTML implementors. It should not be @@ -98,6 +86,18 @@ export const SANDBOX_HEADER = 'amp-ff-sandbox'; */ export const EXPERIMENT_ATTRIBUTE = 'data-experiment-id'; +/** + * Element attribute that stores AMP experiment IDs. + * + * Note: This attribute should be used only for tracking experimental + * implementations of AMP tags, e.g., by AMPHTML implementors. It should not be + * added by a publisher page. + * + * @const {string} + * @visibleForTesting + */ +export const AMP_EXPERIMENT_ATTRIBUTE = 'data-amp-experiment-id'; + /** @typedef {{urls: !Array}} */ export let AmpAnalyticsConfigDef; @@ -114,7 +114,8 @@ export let NameframeExperimentConfig; export const TRUNCATION_PARAM = {name: 'trunc', value: '1'}; /** @const {Object} */ -const CDN_PROXY_REGEXP = /^https:\/\/([a-zA-Z0-9_-]+\.)?cdn\.ampproject\.org((\/.*)|($))+/; +const CDN_PROXY_REGEXP = + /^https:\/\/([a-zA-Z0-9_-]+\.)?cdn\.ampproject\.org((\/.*)|($))+/; /** * Returns the value of some navigation timing parameter. @@ -198,21 +199,24 @@ export function isReportingEnabled(ampElement) { */ export function googleBlockParameters(a4a, opt_experimentIds) { const {element: adElement, win} = a4a; - const slotRect = a4a.getPageLayoutBox(); + const slotRect = getPageLayoutBoxBlocking(adElement); const iframeDepth = iframeNestingDepth(win); const enclosingContainers = getEnclosingContainerTypes(adElement); - let eids = adElement.getAttribute('data-experiment-id'); + let eids = adElement.getAttribute(EXPERIMENT_ATTRIBUTE); if (opt_experimentIds) { eids = mergeExperimentIds(opt_experimentIds, eids); } + const aexp = adElement.getAttribute(AMP_EXPERIMENT_ATTRIBUTE); return { 'adf': DomFingerprint.generate(adElement), 'nhd': iframeDepth, 'eid': eids, - 'adx': slotRect.left, - 'ady': slotRect.top, + 'adx': Math.round(slotRect.left), + 'ady': Math.round(slotRect.top), 'oid': '2', 'act': enclosingContainers.length ? enclosingContainers.join() : null, + // aexp URL param is separated by `!`, not `,`. + 'aexp': aexp ? aexp.replace(/,/g, '!') : null, }; } @@ -286,14 +290,18 @@ export function googlePageParameters(a4a, startTime) { dev().expectedError('AMP-A4A', 'Referrer timeout!'); return ''; }); - const domLoading = getNavigationTiming(win, 'domLoading'); + // Set dom loading time to first visible if page started in prerender state + // determined by truthy value for visibilityState param. + const domLoading = a4a.getAmpDoc().getParam('visibilityState') + ? a4a.getAmpDoc().getLastVisibleTime() + : getNavigationTiming(win, 'domLoading'); return Promise.all([ getOrCreateAdCid(ampDoc, 'AMP_ECID_GOOGLE', '_ga'), referrerPromise, ]).then((promiseResults) => { const clientId = promiseResults[0]; const referrer = promiseResults[1]; - const {pageViewId, canonicalUrl} = Services.documentInfoForDoc(ampDoc); + const {canonicalUrl, pageViewId} = Services.documentInfoForDoc(ampDoc); // Read by GPT for GA/GPT integration. win.gaGlobal = win.gaGlobal || {cid: clientId, hid: pageViewId}; const {screen} = win; @@ -324,8 +332,8 @@ export function googlePageParameters(a4a, startTime) { 'ish': win != win.top ? viewportSize.height : null, 'art': getAmpRuntimeTypeParameter(win), 'vis': visibilityStateCodes[visibilityState] || '0', - 'scr_x': viewport.getScrollLeft(), - 'scr_y': viewport.getScrollTop(), + 'scr_x': Math.round(viewport.getScrollLeft()), + 'scr_y': Math.round(viewport.getScrollTop()), 'bc': getBrowserCapabilitiesBitmap(win) || null, 'debug_experiment_id': (/(?:#|,)deid=([\d,]+)/i.exec(win.location.hash) || [])[1] || null, @@ -516,7 +524,7 @@ function makeCorrelator(pageViewId, opt_clientId) { /** * Collect additional dimensions for the brdim parameter. * @param {!Window} win The window for which we read the browser dimensions. - * @param {{width: number, height: number}|null} viewportSize + * @param {?{width: number, height: number}} viewportSize * @return {string} * @visibleForTesting */ @@ -643,7 +651,8 @@ export function getCsiAmpAnalyticsVariables(analyticsTrigger, a4a, qqid) { } /** - * Extracts configuration used to build amp-analytics element for active view. + * Extracts configuration used to build amp-analytics element for active view + * and begin to render. * * @param {!../../../extensions/amp-a4a/0.1/amp-a4a.AmpA4A} a4a * @param {!Headers} responseHeaders @@ -659,36 +668,35 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { const analyticsConfig = parseJson( responseHeaders.get(AMP_ANALYTICS_HEADER) ); - devAssert(Array.isArray(analyticsConfig['url'])); - const urls = analyticsConfig['url']; - if (!urls.length) { + + const acUrls = analyticsConfig['url']; + const btrUrls = analyticsConfig['btrUrl']; + if ( + (acUrls && !Array.isArray(acUrls)) || + (btrUrls && !Array.isArray(btrUrls)) + ) { + dev().error( + 'AMP-A4A', + 'Invalid analytics', + responseHeaders.get(AMP_ANALYTICS_HEADER) + ); + } + const hasActiveViewRequests = Array.isArray(acUrls) && acUrls.length; + const hasBeginToRenderRequests = Array.isArray(btrUrls) && btrUrls.length; + if (!hasActiveViewRequests && !hasBeginToRenderRequests) { return null; } - - const config = /** @type {JsonObject}*/ ({ + const config = dict({ 'transport': {'beacon': false, 'xhrpost': false}, - 'triggers': { - 'continuousVisible': { - 'on': 'visible', - 'visibilitySpec': { - 'selector': 'amp-ad', - 'selectionMethod': 'closest', - 'visiblePercentageMin': 50, - 'continuousTimeMin': 1000, - }, - }, - }, + 'requests': {}, + 'triggers': {}, }); - - // Discover and build visibility endpoints. - const requests = dict(); - for (let idx = 1; idx <= urls.length; idx++) { - // TODO: Ensure url is valid and not freeform JS? - requests[`visibility${idx}`] = `${urls[idx - 1]}`; + if (hasActiveViewRequests) { + generateActiveViewRequest(config, acUrls); + } + if (hasBeginToRenderRequests) { + generateBeginToRenderRequest(config, btrUrls); } - // Security review needed here. - config['requests'] = requests; - config['triggers']['continuousVisible']['request'] = Object.keys(requests); return config; } catch (err) { dev().error( @@ -701,6 +709,49 @@ export function extractAmpAnalyticsConfig(a4a, responseHeaders) { return null; } +/** + * @param {!JsonObject} config + * @param {!Array} urls + */ +function generateActiveViewRequest(config, urls) { + config['triggers']['continuousVisible'] = dict({ + 'request': [], + 'on': 'visible', + 'visibilitySpec': { + 'selector': 'amp-ad', + 'selectionMethod': 'closest', + '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]}`; + config['triggers']['continuousVisible']['request'].push( + `visibility${idx + 1}` + ); + } +} + +/** + * @param {!JsonObject} config + * @param {!Array} urls + */ +function generateBeginToRenderRequest(config, urls) { + config['triggers']['beginToRender'] = dict({ + '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? + config['requests'][`btr${idx + 1}`] = `${urls[idx]}`; + config['triggers']['beginToRender']['request'].push(`btr${idx + 1}`); + } +} + /** * Add new experiment IDs to a (possibly empty) existing set of experiment IDs. * The {@code currentIdString} may be {@code null} or {@code ''}, but if it is @@ -746,6 +797,11 @@ export function addCsiSignalsToAmpAnalyticsConfig( const correlator = getCorrelator(win, element); const slotId = Number(element.getAttribute('data-amp-slot-index')); const eids = encodeURIComponent(element.getAttribute(EXPERIMENT_ATTRIBUTE)); + let aexp = element.getAttribute(AMP_EXPERIMENT_ATTRIBUTE); + if (aexp) { + // aexp URL param is separated by `!`, not `,`. + aexp = aexp.replace(/,/g, '!'); + } const adType = element.getAttribute('type'); const initTime = Number( getTimingDataSync(win, 'navigationStart') || Date.now() @@ -760,6 +816,7 @@ export function addCsiSignalsToAmpAnalyticsConfig( `&c=${correlator}&slotId=${slotId}&qqid.${slotId}=${qqid}` + `&dt=${initTime}` + (eids != 'null' ? `&e.${slotId}=${eids}` : '') + + (aexp ? `&aexp=${aexp}` : '') + `&rls=${internalRuntimeVersion()}&adt.${slotId}=${adType}`; const isAmpSuffix = isVerifiedAmpCreative ? 'Friendly' : 'CrossDomain'; config['triggers']['continuousVisibleIniLoad'] = { @@ -846,6 +903,8 @@ export function getBinaryTypeNumericalCode(type) { 'control': '1', 'experimental': '2', 'rc': '3', + 'nightly': '4', + 'nightly-control': '5', 'experimentA': '10', 'experimentB': '11', 'experimentC': '12', @@ -1051,3 +1110,37 @@ export function getAmpRuntimeTypeParameter(win) { const art = getBinaryTypeNumericalCode(getBinaryType(win)); return isCdnProxy(win) && art != '0' ? art : null; } + +/** + * Checks if the `always-serve-npa` attribute is present and valid + * based on the geolocation. + * @param {!Element} element + * @return {!Promise} + * @visibleForTesting + */ +export function getServeNpaPromise(element) { + if (!element.hasAttribute('always-serve-npa')) { + return Promise.resolve(false); + } + const npaSignal = element.getAttribute('always-serve-npa'); + if (npaSignal == '') { + return Promise.resolve(true); + } + return Services.geoForDocOrNull(element).then((geoService) => { + if (!geoService) { + // Err on safe side and signal for NPA. + return true; + } + const locations = npaSignal.split(','); + for (let i = 0; i < locations.length; i++) { + const geoGroup = geoService.isInCountryGroup(locations[i]); + if (geoGroup === GEO_IN_GROUP.IN) { + return true; + } else if (geoGroup === GEO_IN_GROUP.NOT_DEFINED) { + user().warn('AMP-AD', `Geo group "${locations[i]}" was not defined.`); + } + } + // Not in any of the defined geo groups. + return false; + }); +} diff --git a/ads/google/adsense.md b/ads/google/adsense.md index bbd997992676c..a823bced88992 100644 --- a/ads/google/adsense.md +++ b/ads/google/adsense.md @@ -34,20 +34,20 @@ limitations under the License. ## Configuration -For semantics of configuration, please see [ad network documentation](https://support.google.com/adsense/answer/7183212?hl=en). For AdSense for Search and AdSense for Shopping, please see the [CSA AMP ad type](https://github.com/ampproject/amphtml/blob/master/ads/google/csa.md). +For semantics of configuration, please see [ad network documentation](https://support.google.com/adsense/answer/7183212?hl=en). For AdSense for Search and AdSense for Shopping, please see the [CSA AMP ad type](https://github.com/ampproject/amphtml/blob/main/ads/vendors/csa.md). Supported parameters: -- data-ad-channel -- data-ad-client -- data-ad-slot -- data-ad-host -- data-adtest -- data-auto-format -- data-full-width -- data-tag-origin -- data-language -- data-matched-content-ui-type -- data-matched-content-rows-num -- data-matched-content-columns-num -- data-npa-on-unknown-consent +- data-ad-channel +- data-ad-client +- data-ad-slot +- data-ad-host +- data-adtest +- data-auto-format +- data-full-width +- data-tag-origin +- data-language +- data-matched-content-ui-type +- data-matched-content-rows-num +- data-matched-content-columns-num +- data-npa-on-unknown-consent diff --git a/ads/google/csa.js b/ads/google/csa.js deleted file mode 100644 index 0eea294220f38..0000000000000 --- a/ads/google/csa.js +++ /dev/null @@ -1,388 +0,0 @@ -/** - * Copyright 2015 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. - */ -import {getStyle, setStyle, setStyles} from '../../src/style'; -import {loadScript, validateData} from '../../3p/3p'; -import {tryParseJson} from '../../src/json.js'; - -// Keep track of current height of AMP iframe -let currentAmpHeight = null; - -// Height of overflow element -const overflowHeight = 40; - -/** - * Enum for different AdSense Products - * @enum {number} - * @visibleForTesting - */ -export const AD_TYPE = { - /** Value if we can't determine which product to request */ - UNSUPPORTED: 0, - /** AdSense for Search */ - AFS: 1, - /** AdSense for Shopping */ - AFSH: 2, - /** AdSense for Shopping, backfilled with AdSense for Search */ - AFSH_BACKFILL: 3, -}; - -/** - * Request Custom Search Ads (Adsense for Search or AdSense for Shopping). - * @param {!Window} global The window object of the iframe - * @param {!Object} data - */ -export function csa(global, data) { - // Get parent width in case we want to override - const width = global.document.body./*OK*/ clientWidth; - - validateData( - data, - [], - [ - 'afshPageOptions', - 'afshAdblockOptions', - 'afsPageOptions', - 'afsAdblockOptions', - 'ampSlotIndex', - ] - ); - - // Add the ad container to the document - const containerDiv = global.document.createElement('div'); - const containerId = 'csacontainer'; - containerDiv.id = containerId; - global.document.getElementById('c').appendChild(containerDiv); - - const pageOptions = {source: 'amp', referer: global.context.referrer}; - const adblockOptions = {container: containerId}; - - // Parse all the options - const afshPage = Object.assign( - Object(tryParseJson(data['afshPageOptions'])), - pageOptions - ); - const afsPage = Object.assign( - Object(tryParseJson(data['afsPageOptions'])), - pageOptions - ); - const afshAd = Object.assign( - Object(tryParseJson(data['afshAdblockOptions'])), - adblockOptions - ); - const afsAd = Object.assign( - Object(tryParseJson(data['afsAdblockOptions'])), - adblockOptions - ); - - // Special case for AFSh when "auto" is the requested width - if (afshAd['width'] == 'auto') { - afshAd['width'] = width; - } - - // Event listener needed for iOS9 bug - global.addEventListener( - 'orientationchange', - orientationChangeHandler.bind(null, global, containerDiv) - ); - - // Register resize callbacks - global.context.onResizeSuccess( - resizeSuccessHandler.bind(null, global, containerDiv) - ); - global.context.onResizeDenied( - resizeDeniedHandler.bind(null, global, containerDiv) - ); - - // Only call for ads once the script has loaded - loadScript( - global, - 'https://www.google.com/adsense/search/ads.js', - requestCsaAds.bind(null, global, data, afsPage, afsAd, afshPage, afshAd) - ); -} - -/** - * Resize the AMP iframe if the CSA container changes in size upon rotation. - * This is needed for an iOS bug found in versions 10.0.1 and below that - * doesn't properly reflow the iframe upon orientation change. - * @param {!Window} global The window object of the iframe - * @param {!Element} containerDiv The CSA container - */ -function orientationChangeHandler(global, containerDiv) { - // Save the height of the container before the event listener triggers - const oldHeight = getStyle(containerDiv, 'height'); - global.setTimeout(() => { - // Force DOM reflow and repaint. - // eslint-disable-next-line no-unused-vars - const ignore = global.document.body./*OK*/ offsetHeight; - // Capture new height. - let newHeight = getStyle(containerDiv, 'height'); - // In older versions of iOS, this height will be different because the - // container height is resized. - // In Chrome and iOS 10.0.2 the height is the same because - // the container isn't resized. - if (oldHeight != newHeight && newHeight != currentAmpHeight) { - // style.height returns "60px" (for example), so turn this into an int - newHeight = parseInt(newHeight, 10); - // Also update the onclick function to resize to the right height. - const overflow = global.document.getElementById('overflow'); - if (overflow) { - overflow.onclick = () => - global.context.requestResize(undefined, newHeight); - } - // Resize the container to the correct height. - global.context.requestResize(undefined, newHeight); - } - }, 250); /* 250 is time in ms to wait before executing orientation */ -} - -/** - * Hanlder for when a resize request succeeds - * Hide the overflow and resize the container - * @param {!Window} global The window object of the iframe - * @param {!Element} container The CSA container - * @param {number} requestedHeight The height of the resize request - * @visibleForTesting - */ -export function resizeSuccessHandler(global, container, requestedHeight) { - currentAmpHeight = requestedHeight; - const overflow = global.document.getElementById('overflow'); - if (overflow) { - setStyle(overflow, 'display', 'none'); - resizeCsa(container, requestedHeight); - } -} - -/** - * Hanlder for When a resize request is denied - * If the container is larger than the AMP container and an overflow already - * exists, show the overflow and resize the container to fit inside the AMP - * container. If an overflow doesn't exist, create one. - * @param {!Window} global The window object of the iframe - * @param {!Element} container The CSA container - * @param {number} requestedHeight The height of the resize request - * @visibleForTesting - */ -export function resizeDeniedHandler(global, container, requestedHeight) { - const overflow = global.document.getElementById('overflow'); - const containerHeight = parseInt(getStyle(container, 'height'), 10); - if (containerHeight > currentAmpHeight) { - if (overflow) { - setStyle(overflow, 'display', ''); - resizeCsa(container, currentAmpHeight - overflowHeight); - } else { - createOverflow(global, container, requestedHeight); - } - } -} - -/** - * Make a request for either AFS or AFSh - * @param {!Window} global The window object of the iframe - * @param {!Object} data The data passed in by the partner - * @param {!Object} afsP The parsed AFS page options object - * @param {!Object} afsA The parsed AFS adblock options object - * @param {!Object} afshP The parsed AFSh page options object - * @param {!Object} afshA The parsed AFSh adblock options object - */ -function requestCsaAds(global, data, afsP, afsA, afshP, afshA) { - const type = getAdType(data); - const callback = callbackWithNoBackfill.bind(null, global); - const callbackBackfill = callbackWithBackfill.bind(null, global, afsP, afsA); - - switch (type) { - case AD_TYPE.AFS: - /** Do not backfill, request AFS */ - afsA['adLoadedCallback'] = callback; - global._googCsa('ads', afsP, afsA); - break; - case AD_TYPE.AFSH: - /** Do not backfill, request AFSh */ - afshA['adLoadedCallback'] = callback; - global._googCsa('plas', afshP, afshA); - break; - case AD_TYPE.AFSH_BACKFILL: - /** Backfill with AFS, request AFSh */ - afshA['adLoadedCallback'] = callbackBackfill; - global._googCsa('plas', afshP, afshA); - break; - } -} - -/** - * Helper function to determine which product to request - * @param {!Object} data The data passed in by the partner - * @return {number} Enum of ad type - */ -function getAdType(data) { - if (data['afsPageOptions'] != null && data['afshPageOptions'] == null) { - return AD_TYPE.AFS; - } - if (data['afsPageOptions'] == null && data['afshPageOptions'] != null) { - return AD_TYPE.AFSH; - } - if (data['afsPageOptions'] != null && data['afshPageOptions'] != null) { - return AD_TYPE.AFSH_BACKFILL; - } else { - return AD_TYPE.UNSUPPORTED; - } -} - -/** - * The adsLoadedCallback for requests without a backfill. If ads were returned, - * resize the iframe. If ads weren't returned, tell AMP we don't have ads. - * @param {!Window} global The window object of the iframe - * @param {string} containerName The name of the CSA container - * @param {boolean} hasAd Whether or not CSA returned an ad - * @visibleForTesting - */ -export function callbackWithNoBackfill(global, containerName, hasAd) { - if (hasAd) { - resizeIframe(global, containerName); - } else { - global.context.noContentAvailable(); - } -} - -/** - * The adsLoadedCallback for requests with a backfill. If ads were returned, - * resize the iframe. If ads weren't returned, backfill the ads. - * @param {!Window} global The window object of the iframe - * @param {!Object} page The parsed AFS page options to backfill the unit with - * @param {!Object} ad The parsed AFS page options to backfill the unit with - * @param {string} containerName The name of the CSA container - * @param {boolean} hasAd Whether or not CSA returned an ad - * @visibleForTesting - */ -export function callbackWithBackfill(global, page, ad, containerName, hasAd) { - if (hasAd) { - resizeIframe(global, containerName); - } else { - ad['adLoadedCallback'] = callbackWithNoBackfill.bind(null, global); - global['_googCsa']('ads', page, ad); - } -} - -/** - * CSA callback function to resize the iframe when ads were returned - * @param {!Window} global - * @param {string} containerName Name of the container ('csacontainer') - * @visibleForTesting - */ -export function resizeIframe(global, containerName) { - // Get actual height of container - const container = global.document.getElementById(containerName); - const height = container./*OK*/ offsetHeight; - // Set initial AMP height - currentAmpHeight = - global.context.initialIntersection.boundingClientRect.height; - - // If the height of the container is larger than the height of the - // initially requested AMP tag, add the overflow element - if (height > currentAmpHeight) { - createOverflow(global, container, height); - } - // Attempt to resize to actual CSA container height - global.context.requestResize(undefined, height); -} - -/** - * Helper function to create an overflow element - * @param {!Window} global The window object of the iframe - * @param {!Element} container HTML element of the CSA container - * @param {number} height The full height the CSA container should be when the - * overflow element is clicked. - */ -function createOverflow(global, container, height) { - const overflow = getOverflowElement(global); - // When overflow is clicked, resize to full height - overflow.onclick = () => global.context.requestResize(undefined, height); - global.document.getElementById('c').appendChild(overflow); - // Resize the CSA container to not conflict with overflow - resizeCsa(container, currentAmpHeight - overflowHeight); -} - -/** - * Helper function to create the base overflow element - * @param {!Window} global The window object of the iframe - * @return {!Element} - */ -function getOverflowElement(global) { - const overflow = global.document.createElement('div'); - overflow.id = 'overflow'; - setStyles(overflow, { - position: 'absolute', - height: overflowHeight + 'px', - width: '100%', - }); - overflow.appendChild(getOverflowLine(global)); - overflow.appendChild(getOverflowChevron(global)); - return overflow; -} - -/** - * Helper function to create a line element for the overflow element - * @param {!Window} global The window object of the iframe - * @return {!Element} - */ -function getOverflowLine(global) { - const line = global.document.createElement('div'); - setStyles(line, { - background: 'rgba(0,0,0,.16)', - height: '1px', - }); - return line; -} - -/** - * Helper function to create a chevron element for the overflow element - * @param {!Window} global The window object of the iframe - * @return {!Element} - */ -function getOverflowChevron(global) { - const svg = - '' + - ' '; - - const chevron = global.document.createElement('div'); - setStyles(chevron, { - width: '36px', - height: '36px', - marginLeft: 'auto', - marginRight: 'auto', - display: 'block', - }); - chevron./*OK*/ innerHTML = svg; - return chevron; -} - -/** - * Helper function to resize the height of a CSA container and its child iframe - * @param {!Element} container HTML element of the CSA container - * @param {number} height Height to resize, in pixels - */ -function resizeCsa(container, height) { - const iframe = container.firstElementChild; - if (iframe) { - setStyles(iframe, { - height: height + 'px', - width: '100%', - }); - } - setStyle(container, 'height', height + 'px'); -} diff --git a/ads/google/doubleclick.md b/ads/google/doubleclick.md index d6cd6df532cc0..6d2dcf60e2ffc 100644 --- a/ads/google/doubleclick.md +++ b/ads/google/doubleclick.md @@ -16,4 +16,4 @@ limitations under the License. # Google Ad Manager -Support for Google Ad Manager Delayed Fetch has been deprecated as of March 29, 2018. Please upgrade to Fast Fetch. Visit the Google Ad Manager Fast Fetch documentation page for more details. +Support for Google Ad Manager Delayed Fetch has been deprecated as of March 29, 2018. Please upgrade to Fast Fetch. Visit the Google Ad Manager Fast Fetch documentation page for more details. diff --git a/ads/google/ima/OWNERS b/ads/google/ima/OWNERS new file mode 100644 index 0000000000000..cae983b4cc00d --- /dev/null +++ b/ads/google/ima/OWNERS @@ -0,0 +1,14 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [ + {name: 'ampproject/wg-bento'}, + {name: 'ampproject/wg-components'}, + {name: 'ampproject/wg-monetization'}, + ], + }, + ], +} diff --git a/ads/google/ima-player-data.js b/ads/google/ima/ima-player-data.js similarity index 100% rename from ads/google/ima-player-data.js rename to ads/google/ima/ima-player-data.js diff --git a/ads/google/ima/ima-video.js b/ads/google/ima/ima-video.js new file mode 100644 index 0000000000000..495bdab26fa22 --- /dev/null +++ b/ads/google/ima/ima-video.js @@ -0,0 +1,1728 @@ +/** + * Copyright 2017 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. + */ + +import {CONSENT_POLICY_STATE} from '../../../src/core/constants/consent-state'; +import {ImaPlayerData} from './ima-player-data'; +import {camelCaseToTitleCase, setStyle, toggle} from '../../../src/style'; +import {getData} from '../../../src/event-helper'; +import {htmlFor, htmlRefs, svgFor} from '../../../src/static-template'; +import {isArray, isObject} from '../../../src/core/types'; +import {loadScript} from '../../../3p/3p'; +import {throttle} from '../../../src/core/types/function'; +import {tryParseJson} from '../../../src/core/types/object/json'; +// Source for this constant is css/amp-ima-video-iframe.css +import {cssText} from '../../../build/amp-ima-video-iframe.css'; + +/** + * Possible player states. + * @enum {number} + * @private + */ +const PlayerStates = { + PLAYING: 1, + PAUSED: 2, +}; + +/** + * Icons from Google Material Icons + * https://material.io/tools/icons + */ +const icons = { + play: (svg) => svg` + + + + `, + pause: (svg) => svg` + + + + + `, + fullscreen: (svg) => svg` + + + + + `, + muted: (svg) => svg` + + + + + `, + volumeMax: (svg) => svg` + + + + + `, +}; + +// References to rendered elements. See renderElements(). +let elements; + +// Event indicating user interaction. +let interactEvent; + +// Event for mouse down. +let mouseDownEvent; + +// Event for mouse move. +let mouseMoveEvent; + +// Event for mouse up. +let mouseUpEvent; + +// Percent of the way through the video the user has seeked. Used for seek +// events. +let seekPercent; + +// Flag tracking whether or not content has played to completion. +let contentComplete; + +// Flag tracking whether or not all ads have been played and been completed. +let allAdsCompleted; + +// Flag tracking if an ad request has failed. +let adRequestFailed; + +// IMA SDK Ad object +let currentAd; + +// IMA SDK AdDisplayContainer object. +let adDisplayContainer; + +// IMA SDK AdsRequest object. +let adsRequest; + +// IMA SDK AdsLoader object. +let adsLoader; + +// IMA SDK AdsManager object; +let adsManager; + +// Timer for UI updates. +let uiTicker; + +// Tracks the current state of the player. +let playerState; + +// Flag for whether or not we are currently in fullscreen mode. +let fullscreen; + +// Width the player should be in fullscreen mode. +let fullscreenWidth; + +// Height the player should be in fullscreen mode. +let fullscreenHeight; + +// "Ad" label used in ad controls. +let adLabel; + +// Flag tracking if ads are currently active. +let adsActive; + +// Flag tracking if playback has started. +let playbackStarted; + +// Flag for video's controls first being shown. +let showControlsFirstCalled; + +// Flag to indicate that showControls() should +// not take immediate effect: i.e. the case when +// hideControls() is called before controls are +// visible. +let hideControlsQueued; + +// Boolean tracking if controls are hidden or shown +let controlsVisible; + +// Timer used to hide controls after user action. +let hideControlsTimeout; + +// Flag tracking if we need to mute the ads manager once it loads. Used for +// autoplay. +let muteAdsManagerOnLoaded; + +// Flag tracking if we are in native fullscreen mode. Used for iPhone. +let nativeFullscreen; + +// Flag tracking if the IMA library was allowed to load. Will be set to false +// when e.g. a user is using an ad blocker. +let imaLoadAllowed; + +// Used if the adsManager needs to be resized on load. +let adsManagerWidthOnLoad, adsManagerHeightOnLoad; + +// Initial video dimensions. +let videoWidth, videoHeight; + +// IMASettings provided via -``` - -See [AMP documentation](https://amp.dev/documentation/components/amp-sticky-ad) for more information regarding `` component. - -## Configuration - -For details on the configuration semantics, please contact Outbrain’s Account Management Team.\ -These configurations are relevant for both `` and ``. - -### Required parameters - -- `data-widgetIds`: Widget Id/s Provided by Account Manager. - -### Optional parameters - -- `data-htmlURL`: The URL of the standard html version of the page. -- `data-ampURL`: The URL of the AMP version of the page. -- `data-styleFile`: Provide publisher an option to pass CSS file in order to inherit the design for the AMP displayed widget. **Consult with Account Manager regarding CSS options**. - -### User Consent - -The widget will check for user consent to decide whether personalized or non-personalized recommendations should be displayed. - -The following rules will be applied: - -- CONSENT_POLICY_STATE.SUFFICIENT - Show personalized recommendations -- CONSENT_POLICY_STATE.INSUFFICIENT - Show non-personalized recommendations only -- CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED - Show personalized recommendations -- CONSENT_POLICY_STATE.UNKNOWN - Show non-personalized recommendations only - -## Troubleshooting - -### Widget is cut off - -According to the AMP API, "resizes are honored when the resize will not adjust the content the user is currently reading. That is, if the ad is above the viewport's contents, it'll resize. Same if it's below. If it's in the viewport, it ignores it." - -**Resolution** - -You can set an initial height of what the widget height is supposed to be. That is, instead of `height="100"`, if the widget's final height is 600px, then set `height="600"`. Setting the initial height **_will not_** finalize the widget height if it's different from the actual. The widget will resize to it's true dimensions after the widget leaves the viewport. diff --git a/ads/pixels.js b/ads/pixels.js deleted file mode 100644 index afe513a47f481..0000000000000 --- a/ads/pixels.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pixels(global, data) { - validateData(data, ['origin', 'sid', 'tag'], ['clickTracker', 'viewability']); - data.tag = data.tag.toString().toLowerCase(); - global._pixelsParam = data; - if (data.tag === 'sync') { - writeScript( - global, - 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', - () => { - const pixelsAMPAd = global.pixelsAd; - const pixelsAMPTag = new pixelsAMPAd(data); - pixelsAMPTag.renderAmp(global.context); - global.context.renderStart(); - } - ); - } else { - global.context.noContentAvailable(); - } -} diff --git a/ads/pixels.md b/ads/pixels.md deleted file mode 100644 index 0f0194c213a15..0000000000000 --- a/ads/pixels.md +++ /dev/null @@ -1,48 +0,0 @@ - - -# Pixels - -## Example - -```html - - -``` - -## Configuration - -For additional details and support contact techteam@pixels.asia - -Required parameters: - -- data-origin - Specify which ad server group to handle the ad request. -- data-sid - Unique ad tag identifier. -- data-tag - Specify whether this tag is a sync tag. - -Optional parameters: - -- data-click-tracker - Specify whether there is a third party click-tracker. -- data-viewability - Specify whether the tag should record viewability statistics. diff --git a/ads/plista.js b/ads/plista.js deleted file mode 100644 index b7a8390fa53a5..0000000000000 --- a/ads/plista.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function plista(global, data) { - // TODO: check mandatory fields - validateData( - data, - [], - [ - 'publickey', - 'widgetname', - 'urlprefix', - 'item', - 'geo', - 'categories', - 'countrycode', - ] - ); - const div = global.document.createElement('div'); - div.setAttribute('data-display', 'plista_widget_' + data.widgetname); - // container with id "c" is provided by amphtml - global.document.getElementById('c').appendChild(div); - window.PLISTA = { - publickey: data.publickey, - widgets: [ - { - name: data.widgetname, - pre: data.urlprefix, - }, - ], - item: data.item, - geo: data.geo, - categories: data.categories, - noCache: true, - useDocumentReady: false, - dataMode: 'data-display', - }; - - // load the plista modules asynchronously - loadScript( - global, - 'https://static' + - (data.countrycode ? '-' + encodeURIComponent(data.countrycode) : '') + - '.plista.com/async.js' - ); -} diff --git a/ads/polymorphicads.js b/ads/polymorphicads.js deleted file mode 100644 index a4021f3c3c5f4..0000000000000 --- a/ads/polymorphicads.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function polymorphicads(global, data) { - validateData(data, ['adunit', 'params']); - global.polyParam = data; - writeScript(global, 'https://www.polymorphicads.jp/js/amp.js'); -} diff --git a/ads/popin.js b/ads/popin.js deleted file mode 100644 index b16f4fc5bf979..0000000000000 --- a/ads/popin.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function popin(global, data) { - validateData(data, ['mediaid']); - - const d = global.document.createElement('div'); - d.id = '_popIn_amp_recommend'; - global.document.getElementById('c').appendChild(d); - - const url = - 'https://api.popin.cc/searchbox/' + - encodeURIComponent(data['mediaid']) + - '.js'; - - loadScript(global, url); -} diff --git a/ads/postquare.js b/ads/postquare.js deleted file mode 100644 index c4e50182fbf7a..0000000000000 --- a/ads/postquare.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function postquare(global, data) { - validateData(data, ['widgetids']); - - global._postquare = global._postquare || { - viewId: global.context.pageViewId, - widgetIds: data['widgetids'], - websiteId: data['websiteid'], - publisherId: data['publisherid'], - url: data['url'] || global.context.canonicalUrl, - ampURL: data['ampurl'] || global.context.sourceUrl, - mode: data['mode'] || 1, - style: data['stylecss'] || '', - referrer: global.context.referrer, - }; - - if (data['mode'] == 100) { - loadScript(global, 'https://widget.engageya.com/pos_amp_loader.js'); - } else { - loadScript(global, 'https://widget.postquare.com/postquare_amp_loader.js'); - } -} diff --git a/ads/ppstudio.js b/ads/ppstudio.js deleted file mode 100644 index b385000e9bd4a..0000000000000 --- a/ads/ppstudio.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function ppstudio(global, data) { - validateData(data, ['crid', 'width', 'height', 'holderScript'], []); - - global._ppstudio = { - crid: data.crid, - width: data.width, - height: data.height, - holderScript: data.holderScript, - }; - - const e = global.document.createElement('script'); - e.id = 'pps-script-' + data.crid; - e.setAttribute('data-width', data.width); - e.setAttribute('data-height', data.height); - e.setAttribute('data-click-url', ''); - e.src = data.holderScript; - global.document.getElementById('c').appendChild(e); - - const i = global.document.createElement('ins'); - i.classList.add('ppstudio'); - i.setAttribute('data-pps-target-id', 'cr-' + data.crid); - global.document.getElementById('c').appendChild(i); - - loadScript(global, 'https://ads-cdn.tenmax.io/code/ppstudio.js', () => { - global.context.renderStart(); - }); -} diff --git a/ads/pressboard.js b/ads/pressboard.js deleted file mode 100644 index a68bc8fc969f4..0000000000000 --- a/ads/pressboard.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pressboard(global, data) { - validateData(data, ['media']); - data.baseUrl = 'https://adserver.pressboard.ca'; - global.pbParams = data; - loadScript( - global, - data.baseUrl + '/js/amp-ad.js', - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/promoteiq.js b/ads/promoteiq.js deleted file mode 100644 index e31c646d24442..0000000000000 --- a/ads/promoteiq.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {parseJson} from '../src/json'; -import {user} from '../src/log'; - -const TAG = 'PROMOTEIQ'; -const mandatoryDataFields = ['src', 'params', 'sfcallback']; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function promoteiq(global, data) { - validateData(data, mandatoryDataFields, []); - const sfInputs = parseJson(data['params']); - - loadScript(global, data['src'], () => { - if (!!global['TagDeliveryContent']) { - const sfCallback = new Function('response', data['sfcallback']); - global['TagDeliveryContent']['request'](sfInputs, sfCallback); - } else { - user().error(TAG, 'TagDeliveryContent object not loaded on page'); - } - }); -} diff --git a/ads/pubexchange.js b/ads/pubexchange.js deleted file mode 100644 index a002281e00248..0000000000000 --- a/ads/pubexchange.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pubexchange(global, data) { - // ensure we have valid widgetIds value - validateData(data, ['publication', 'moduleId', 'moduleNum'], ['test']); - - global.PUBX = global.PUBX || { - pub: data['publication'], - modNum: data['moduleNum'], - modId: data['moduleId'], - test: data['test'], - }; - - // load the Outbrain AMP JS file - loadScript(global, 'https://main.pubexchange.com/loader-amp.min.js'); -} diff --git a/ads/pubguru.js b/ads/pubguru.js deleted file mode 100644 index 6d6c2fdb36386..0000000000000 --- a/ads/pubguru.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pubguru(global, data) { - validateData(data, ['publisher', 'slot']); - - global.$pubguru = data; - - const el = global.document.createElement('div'); - el.setAttribute('id', 'the-ad-unit'); - - global.document.getElementById('c').appendChild(el); - loadScript( - global, - 'https://amp.pubguru.org/amp.' + - encodeURIComponent(data.publisher) + - '.min.js' - ); -} diff --git a/ads/pubmatic.js b/ads/pubmatic.js deleted file mode 100644 index 251619f2624fa..0000000000000 --- a/ads/pubmatic.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript} from '../3p/3p'; - -/* global PubMatic: false */ - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pubmatic(global, data) { - loadScript(global, 'https://ads.pubmatic.com/AdServer/js/amp.js', () => { - data.kadpageurl = global.context.sourceUrl || global.context.location.href; - PubMatic.showAd(data); - }); -} diff --git a/ads/pubmine.js b/ads/pubmine.js deleted file mode 100644 index c5096d6ef937d..0000000000000 --- a/ads/pubmine.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {CONSENT_POLICY_STATE} from '../src/consent-state'; -import {loadScript, validateData} from '../3p/3p'; - -const pubmineOptional = ['section', 'pt', 'ht', 'npaOnUnknownConsent'], - pubmineRequired = ['siteid'], - pubmineURL = 'https://s.pubmine.com/head.js'; - -/** - * @param {!Object} data - * @param {!Window} global - */ -function initMasterFrame(data, global) { - /* - * INSUFFICIENT and UNKNOWN should be treated as INSUFFICIENT - * unless state is UNKNOWN and `data-npa-on-unknown-consent=false` - */ - const paUnknown = - data['npaOnUnknownConsent'] !== undefined && - 'false' == data['npaOnUnknownConsent']; - const ctxt = global.context; - const consent = - ctxt.initialConsentState === null || - ctxt.initialConsentState === CONSENT_POLICY_STATE.SUFFICIENT || - ctxt.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED || - (ctxt.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN && paUnknown); - - global['__ATA_PP'] = { - pt: data['pt'] || 1, - ht: data['ht'] || 1, - tn: 'amp', - amp: true, - consent: consent ? 1 : 0, - siteid: Number(data['siteid']) || undefined, - }; - global['__ATA'] = global['__ATA'] || {}; - global['__ATA']['cmd'] = global['__ATA']['cmd'] || []; - loadScript(global, pubmineURL); -} - -/** - * @param {string} slotId - * @param {!Window} global - */ -function createSlot(slotId, global) { - const containerEl = global.document.getElementById('c'); - const adSlot = global.document.createElement('div'); - adSlot.setAttribute('id', slotId); - containerEl.appendChild(adSlot); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pubmine(global, data) { - validateData(data, pubmineRequired, pubmineOptional); - - const sectionId = data['siteid'] + (data['section'] || '1'); - - const slotConfig = { - sectionId, - height: data.height == 250 ? 250 : data.height - 15, - width: data.width, - window: global, - }; - - const slotId = `atatags-${sectionId}`; - - createSlot(slotId, global); - const {isMaster} = global.context; - if (isMaster) { - initMasterFrame(data, global); - } - const master = isMaster ? global : global.context.master; - master['__ATA']['cmd']['push'](function () { - master['__ATA']['insertStyles'](global); - master['__ATA']['initSlot'](slotId, slotConfig); - }); -} diff --git a/ads/pubmine.md b/ads/pubmine.md deleted file mode 100644 index d9539a70dfc17..0000000000000 --- a/ads/pubmine.md +++ /dev/null @@ -1,69 +0,0 @@ - - -# Pubmine - -## Example - -### Basic - -```html - - -``` - -### With all attributes - -```html - - -``` - -## Configuration - -For further configuration information, please [contact Pubmine](https://wordpress.com/help/contact). - -Please note that the height parameter should be 15 greater than your ad size to ensure there is enough room for the "Report this ad" link. - -### Required parameters - -- `data-siteid`: Pubmine publisher site number. - -### Optional parameters - -- `data-section`: Pubmine slot identifier -- `data-pt`: Enum value for page type -- `data-ht`: Enum value for hosting type -- `data-npa-on-unknown-consent`: Flag for allowing/prohibiting non-personalized-ads on unknown consent. - -## Consent Support - -Pubmine's amp-ad adheres to a user's consent in the following ways: - -- No `data-block-on-consent` attribute: Pubmine amp-ad will display a personalized ad to the user. -- `CONSENT_POLICY_STATE.SUFFICIENT`: Pubmine amp-ad will display a personalized ad to the user. -- `CONSENT_POLICY_STATE.INSUFFICIENT`: Pubmine amp-ad will display a non-personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Pubmine amp-ad will display a personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN`: Pubmine amp-ad will display a non-personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN` and `data-npa-on-unknown-consent=false`: Pubmine amp-ad will display a personalized ad to the user. diff --git a/ads/puffnetwork.js b/ads/puffnetwork.js deleted file mode 100644 index c04f1a9f00638..0000000000000 --- a/ads/puffnetwork.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function puffnetwork(global, data) { - validateData(data, ['chid']); - global.pn = data; - writeScript(global, 'https://static.puffnetwork.com/amp_ad.js'); -} diff --git a/ads/pulsepoint.js b/ads/pulsepoint.js deleted file mode 100644 index 586756e329d9b..0000000000000 --- a/ads/pulsepoint.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {doubleclick} from '../ads/google/doubleclick'; -import {loadScript, validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function pulsepoint(global, data) { - // TODO: check mandatory fields - validateData(data, [], ['pid', 'tagid', 'tagtype', 'slot', 'timeout']); - if (data.tagtype === 'hb') { - headerBidding(global, data); - } else { - tag(global, data); - } -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function tag(global, data) { - writeScript( - global, - 'https://tag.contextweb.com/getjs.aspx?action=VIEWAD' + - '&cwpid=' + - encodeURIComponent(data.pid) + - '&cwtagid=' + - encodeURIComponent(data.tagid) + - '&cwadformat=' + - encodeURIComponent(data.width + 'X' + data.height) - ); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function headerBidding(global, data) { - loadScript(global, 'https://ads.contextweb.com/ht.js', () => { - const hbConfig = { - timeout: data.timeout || 1000, - slots: [ - { - cp: data.pid, - ct: data.tagid, - cf: data.width + 'x' + data.height, - placement: data.slot, - elementId: 'c', - }, - ], - done(targeting) { - doubleclick(global, { - width: data.width, - height: data.height, - slot: data.slot, - targeting: targeting[data.slot], - }); - }, - }; - new window.PulsePointHeaderTag(hbConfig).init(); - }); -} diff --git a/ads/purch.js b/ads/purch.js deleted file mode 100644 index 64ed76c97ca5e..0000000000000 --- a/ads/purch.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, validateSrcPrefix, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function purch(global, data) { - validateData(data, [], ['pid', 'divid', 'config']); - global.data = data; - - const adsrc = 'https://ramp.purch.com/serve/creative_amp.js'; - validateSrcPrefix('https:', adsrc); - writeScript(global, adsrc); -} diff --git a/ads/quoraad.js b/ads/quoraad.js deleted file mode 100644 index d7fd0f7891aad..0000000000000 --- a/ads/quoraad.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function quoraad(global, data) { - validateData(data, ['adid']); - global.ampAdParam = data; - writeScript(global, 'https://a.quora.com/amp_ad.js'); -} diff --git a/ads/rakutenunifiedads.js b/ads/rakutenunifiedads.js deleted file mode 100644 index 4d04beaf76ba4..0000000000000 --- a/ads/rakutenunifiedads.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function rakutenunifiedads(global, data) { - validateData(data, ['id']); - if (hasOwn(data, 'env')) { - data.env = `${data.env}-`; - } else { - data.env = ''; - } - global.runa = data; - writeScript(global, `https://${data.env}s-cdn.rmp.rakuten.co.jp/js/amp.js`); -} diff --git a/ads/rakutenunifiedads.md b/ads/rakutenunifiedads.md deleted file mode 100644 index 8561b8b679f3a..0000000000000 --- a/ads/rakutenunifiedads.md +++ /dev/null @@ -1,68 +0,0 @@ - - -# Rakuten Unified Ads - -## Example - -```html - - -``` - -```html - - -``` - -## Configuration - -### Required parameters - -- `data-id` : Your adspot id -- `type` : fixed value `rakutenunifiedads` - -### Optional parameters - -- `data-env` : Environment of server for Not production. e.g. `dev`, `stg`, `tst` -- `data-genre` : Genre object -- `data-ifa` : IFA string -- `data-targeting` : Targeting object - -### How to handle responsive design - -Please refer to [Create responsive AMP pages](https://amp.dev/documentation/guides-and-tutorials/develop/style_and_layout/responsive_design/) - -```html - - -``` diff --git a/ads/rbinfox.js b/ads/rbinfox.js deleted file mode 100644 index e0fb1a66aeb89..0000000000000 --- a/ads/rbinfox.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2017 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. - */ -import {loadScript, validateData, validateSrcPrefix} from '../3p/3p'; - -const jsnPrefix = 'https://rb.infox.sg/'; -const n = 'infoxContextAsyncCallbacks'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function rbinfox(global, data) { - validateData(data, ['src']); - const {src} = data; - validateSrcPrefix(jsnPrefix, src); - - addToQueue(global, src); - loadScript(global, src); -} - -/** - * @param {!Window} global - * @param {string} renderTo - */ -function createContainer(global, renderTo) { - const d = global.document.createElement('div'); - d.id = renderTo; - global.document.getElementById('c').appendChild(d); -} - -/** - * @param {string} src - * @return {string} - */ -function getBlockId(src) { - const parts = src.split('/'); - return parts[parts.length - 1]; -} - -/** - * @param {!Window} global - * @param {string} src - */ -function addToQueue(global, src) { - const blockId = getBlockId(src); - const ctx = n + blockId; - global[ctx] = global[ctx] || []; - global[ctx].push(() => { - const renderTo = 'infox_' + blockId; - // Create container - createContainer(global, renderTo); - global['INFOX' + blockId].renderTo(renderTo); - }); -} diff --git a/ads/readmo.js b/ads/readmo.js deleted file mode 100644 index 91c1694f2ea93..0000000000000 --- a/ads/readmo.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function readmo(global, data) { - validateData(data, ['section']); - - const config = { - container: '#c', - amp: true, - }; - - if (data.url) { - global.publisherUrl = data.url; - } - - Object.keys(data).forEach((property) => { - config[property] = data[property]; - }); - - (global.readmo = global.readmo || []).push(config); - - loadScript(global, 'https://s.yimg.com/dy/ads/readmo.js', () => - global.context.renderStart() - ); -} diff --git a/ads/readmo.md b/ads/readmo.md deleted file mode 100644 index 0a4c3dc7d5794..0000000000000 --- a/ads/readmo.md +++ /dev/null @@ -1,48 +0,0 @@ - - -# ReadMo - -## Example - -ReadMo only requires a section code to run. Please work with your account manager to properly configure your AMP section. - -### Basic - -```html - - -``` - -### Required parameters - -- `data-section` : A unique identifier that represents your site and placement - -### Optional parameters - -- `data-module` : Defines the type of module to render (`end-of-article`, `smart-feed`, `smart-feed-video`, `side-rail`) -- `data-infinite` : If true, enables infinite feed for your module -- `data-title` : The title that appears above the module (defaults to "You May Like") -- `data-sponsored-by-label` : Text override to the default "Sponsored by" label that appears next to the sponsors name -- `data-url` : Publisher url override -- `json` : Use this to pass additional configuration properties (ex: `json='{ "contentId": 1234 }'`) diff --git a/ads/realclick.js b/ads/realclick.js deleted file mode 100644 index 24fb1d5f2cd4b..0000000000000 --- a/ads/realclick.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function realclick(global, data) { - validateData(data, ['mcode']); - global.rcParams = data; - loadScript(global, 'https://ssp.realclick.co.kr/amp/ad.js'); -} diff --git a/ads/recomad.js b/ads/recomad.js deleted file mode 100644 index 911544fc65f0d..0000000000000 --- a/ads/recomad.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * Add a container for the recomAD widget, - * which will be discovered by the script automatically. - * - * @param {Element} container - * @param {string} appId - * @param {string} widgetId - * @param {string} searchTerm - * @param {string} origin - * @param {string} baseUrl - * @param {string} puid - */ -function createWidgetContainer( - container, - appId, - widgetId, - searchTerm, - origin, - baseUrl, - puid -) { - container.className = 's24widget'; - - container.setAttribute('data-app-id', appId); - container.setAttribute('data-widget-id', widgetId); - searchTerm && container.setAttribute('data-search-term', searchTerm); - origin && container.setAttribute('data-origin', origin); - baseUrl && container.setAttribute('data-base-url', baseUrl); - puid && container.setAttribute('data-puid', puid); - - window.document.body.appendChild(container); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function recomad(global, data) { - validateData(data, ['appId', 'widgetId', ['searchTerm', 'origin']]); - - createWidgetContainer( - window.document.createElement('div'), - data['appId'], - data['widgetId'], - data['searchTerm'] || '', - data['origin'] || '', - data['baseUrl'] || '', - data['puid'] || '' - ); - - loadScript(window, 'https://widget.s24.com/js/s24widget.min.js'); -} diff --git a/ads/relap.js b/ads/relap.js deleted file mode 100644 index 8fa8a5d60cccf..0000000000000 --- a/ads/relap.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function relap(global, data) { - validateData(data, [], ['token', 'url', 'anchorid', 'version']); - - 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'], - anchorEl: global.document.getElementById('c'), - position: 'append', - events: { - onReady: function () { - window.context.renderStart(); - }, - onNoContent: function () { - window.context.noContentAvailable(); - }, - }, - }); - }; - - loadScript(global, 'https://relap.io/v7/relap.js'); - } else { - window.relapV6WidgetReady = function () { - window.context.renderStart(); - }; - - window.relapV6WidgetNoSimilarPages = function () { - window.context.noContentAvailable(); - }; - - const anchorEl = global.document.createElement('div'); - anchorEl.id = data['anchorid']; - global.document.getElementById('c').appendChild(anchorEl); - - const url = `https://relap.io/api/v6/head.js?token=${encodeURIComponent( - data['token'] - )}&url=${encodeURIComponent(urlParam)}`; - loadScript(global, url); - } -} diff --git a/ads/relappro.js b/ads/relappro.js deleted file mode 100644 index 624867b55377b..0000000000000 --- a/ads/relappro.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function relappro(global, data) { - validateData(data, [], ['slotId', 'nameAdUnit', 'requirements']); - global.params = data; - - loadScript( - global, - 'https://cdn.relappro.com/adservices/amp/relappro.amp.min.js' - ); -} diff --git a/ads/revcontent.js b/ads/revcontent.js deleted file mode 100644 index ebe88e30b0ecb..0000000000000 --- a/ads/revcontent.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function revcontent(global, data) { - let endpoint = - 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js'; - - 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'; - } - } - - 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', - ]; - - data.endpoint = data.endpoint ? data.endpoint : 'trends.revcontent.com'; - - validateData(data, required, optional); - global.data = data; - if (data.loadscript) { - loadScript(window, endpoint); - } else { - writeScript(window, endpoint); - } -} diff --git a/ads/revjet.js b/ads/revjet.js deleted file mode 100644 index 50af0213fc017..0000000000000 --- a/ads/revjet.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function revjet(global, data) { - validateData(data, ['tag', 'key'], ['plc', 'opts', 'params']); - - global._revjetData = {...data}; - - loadScript( - global, - 'https://cdn.revjet.com/~cdn/JS/03/amp.js', - /* opt_cb */ undefined, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/rfp.js b/ads/rfp.js deleted file mode 100644 index 624b36f769028..0000000000000 --- a/ads/rfp.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function rfp(global, data) { - validateData(data, ['adspotId'], ['stylesheetUrl', 'country']); - global.rfpData = data; - writeScript(global, 'https://js.rfp.fout.jp/rfp-amp.js'); -} diff --git a/ads/rnetplus.js b/ads/rnetplus.js deleted file mode 100644 index bf54c0dedef8e..0000000000000 --- a/ads/rnetplus.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData, validateSrcPrefix} from '../3p/3p'; - -const jsnPrefix = 'https://api.rnet.plus/'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function rnetplus(global, data) { - validateData(data, ['src']); - const {src} = data; - validateSrcPrefix(jsnPrefix, src); - createContainer(global, 'rnetplus_' + getBlockId(src)); - loadScript(global, src); -} - -/** - * @param {!Window} global - * @param {string} renderTo - */ -function createContainer(global, renderTo) { - const d = global.document.createElement('div'); - d.id = renderTo; - global.document.getElementById('c').appendChild(d); -} - -/** - * @param {string} src - * @return {string} - */ -function getBlockId(src) { - const parts = src.split('?'); - const vars = parts[1].split('&'); - for (let j = 0; j < vars.length; ++j) { - const pair = vars[j].split('='); - if (pair[0] == 'blockId') { - return pair[1]; - } - } - return '660'; -} diff --git a/ads/rubicon.js b/ads/rubicon.js deleted file mode 100644 index 2e331cbd35360..0000000000000 --- a/ads/rubicon.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function rubicon(global, data) { - // TODO: check mandatory fields - validateData( - data, - [], - [ - 'account', - 'site', - 'zone', - 'size', - 'kw', - 'visitor', - 'inventory', - 'method', - 'callback', - ] - ); - - if (data.method === 'smartTag') { - smartTag(global, data); - } -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function smartTag(global, data) { - /* eslint-disable */ - global.rp_account = data.account; - global.rp_site = data.site; - global.rp_zonesize = data.zone + '-' + data.size; - global.rp_adtype = 'js'; - global.rp_page = context.sourceUrl; - global.rp_kw = data.kw; - global.rp_visitor = data.visitor; - global.rp_inventory = data.inventory; - global.rp_amp = 'st'; - global.rp_callback = data.callback; - /* eslint-enable */ - writeScript( - global, - 'https://ads.rubiconproject.com/ad/' + - encodeURIComponent(data.account) + - '.js' - ); -} diff --git a/ads/runative.js b/ads/runative.js deleted file mode 100644 index 69e783a073f21..0000000000000 --- a/ads/runative.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {parseJson} from '../src/json'; - -const requiredParams = ['spot']; -const optionsParams = [ - 'keywords', - 'adType', - 'param1', - 'param2', - 'param3', - 'subid', - 'cols', - 'rows', - 'title', - 'titlePosition', - 'adsByPosition', -]; -const adContainerId = 'runative_id'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function runative(global, data) { - // ensure we have valid widgetIds value - validateData(data, requiredParams, optionsParams); - - const adContainer = global.document.getElementById('c'); - const adNativeContainer = getAdContainer(global); - const initScript = getInitAdScript(global, data); - - adContainer.appendChild(adNativeContainer); - - // load the RUNative AMP JS file - loadScript(global, '//cdn.run-syndicate.com/sdk/v1/n.js', () => { - global.document.body.appendChild(initScript); - }); -} - -/** - * @param {!Object} data - * @return {JsonObject} - */ -function getInitData(data) { - const initKeys = requiredParams.concat(optionsParams); - const initParams = {}; - - initKeys.forEach((key) => { - if (key in data) { - const initKey = key === 'adType' ? 'type' : key; - - initParams[initKey] = data[key]; - } - }); - - initParams['element_id'] = adContainerId; - - return parseJson(initParams); -} - -/** - * @param {!Window} global - * @return {?Node} - */ -function getAdContainer(global) { - const container = global.document.createElement('div'); - - container['id'] = adContainerId; - - return container; -} - -/** - * @param {!Window} global - * @param {!Object} data - * @return {?Node} - */ -function getInitAdScript(global, data) { - const scriptElement = global.document.createElement('script'); - const initData = getInitData(data); - const initScript = global.document.createTextNode( - `NativeAd(${JSON.stringify(initData)});` - ); - - scriptElement.appendChild(initScript); - - return scriptElement; -} diff --git a/ads/runative.md b/ads/runative.md deleted file mode 100644 index 7c090de74052a..0000000000000 --- a/ads/runative.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# RUNative - -Serves ads from the [RUNative](https://www.runative.com/). - -## Example - -```html - - -``` - -## Configuration - -For details on the configuration semantics, please contact the ad network or refer to their documentation. - -### Required parameters - -- `data-spot` - code spot - -### Optional parameters - -- `data-ad-type` - types of ads: `img-left`, `img-right`, `label-over`, `label-under` -- `data-keywords` - title of ad -- `data-title` - title of ad -- `data-cols` - number of cols 1 till 6 -- `data-rows` - number of rows 1 till 6 -- `data-title-position` - position of ad title (`left` or `right`) -- `data-ads-by-position` - position of runative logo (`left` or `right`) diff --git a/ads/sas.js b/ads/sas.js deleted file mode 100644 index 87e039207dd50..0000000000000 --- a/ads/sas.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {parseJson} from '../src/json'; -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sas(global, data) { - let url, adHost; - const fields = ['site', 'size', 'area']; - validateData( - data, - ['customerName'], - ['adHost', 'site', 'size', 'area', 'mid', 'tags'] - ); - - if (typeof data.adHost === 'undefined') { - adHost = encodeURIComponent(data['customerName']) + '-ads.aimatch.com'; - } else { - adHost = encodeURIComponent(data['adHost']); - } - - url = '//' + adHost + '/' + data['customerName'] + '/jserver'; - - for (let idx = 0; idx < fields.length; idx++) { - if (data[fields[idx]]) { - if (typeof data[fields[idx]] !== 'undefined') { - url += '/' + fields[idx] + '=' + encodeURIComponent(data[fields[idx]]); - } - } - } - - if (typeof data.tags !== 'undefined') { - const tags = parseJson(data.tags); - for (const tag in tags) { - url += '/' + tag + '=' + encodeURIComponent(tags[tag]); - } - } - writeScript(global, url, () => { - global.context.renderStart(); - }); -} diff --git a/ads/seedingalliance.js b/ads/seedingalliance.js deleted file mode 100644 index 41861ecc527f3..0000000000000 --- a/ads/seedingalliance.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function seedingalliance(global, data) { - writeScript( - global, - 'https://d.nativendo.de/cds/delivery/init?url=' + - encodeURIComponent(data.url) - ); -} diff --git a/ads/sekindo.js b/ads/sekindo.js deleted file mode 100644 index cd49c1df1e471..0000000000000 --- a/ads/sekindo.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sekindo(global, data) { - validateData(data, ['spaceid']); - const pubUrl = encodeURIComponent(global.context.sourceUrl); - const excludesSet = {ampSlotIndex: 1, type: 1}; - const customParamMap = {spaceid: 's', width: 'x', height: 'y'}; - let query = - 'isAmpProject=1&pubUrl=' + - pubUrl + - '&cbuster=' + - global.context.startTime + - '&'; - let getParam = ''; - for (const key in data) { - if (hasOwn(data, key)) { - if (typeof excludesSet[key] == 'undefined') { - getParam = - typeof customParamMap[key] == 'undefined' ? key : customParamMap[key]; - query += getParam + '=' + encodeURIComponent(data[key]) + '&'; - } - } - } - loadScript( - global, - 'https://live.sekindo.com/live/liveView.php?' + query, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/sharethrough.js b/ads/sharethrough.js deleted file mode 100644 index baa152d1bef93..0000000000000 --- a/ads/sharethrough.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sharethrough(global, data) { - validateData(data, ['pkey'], []); - global.pkey = data.pkey; - writeScript(global, 'https://native.sharethrough.com/iframe/amp.js'); -} diff --git a/ads/shemedia.js b/ads/shemedia.js deleted file mode 100644 index 3a9f0c53bb32c..0000000000000 --- a/ads/shemedia.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function shemedia(global, data) { - validateData(data, ['slotType', 'boomerangPath']); - - loadScript(global, 'https://ads.shemedia.com/static/amp.js'); -} diff --git a/ads/sklik.js b/ads/sklik.js deleted file mode 100644 index d4fe68ff4a8c2..0000000000000 --- a/ads/sklik.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript} from '../3p/3p'; - -/* global sklikProvider: false */ - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sklik(global, data) { - loadScript(global, 'https://c.imedia.cz/js/amp.js', () => { - const parentId = 'sklik_parent'; - - const parentElement = document.createElement('div'); - parentElement.id = parentId; - window.document.body.appendChild(parentElement); - - data.elm = parentId; - data.url = global.context.canonicalUrl; - - sklikProvider.show(data); - }); -} diff --git a/ads/slimcutmedia.js b/ads/slimcutmedia.js deleted file mode 100644 index 7622a77f8e102..0000000000000 --- a/ads/slimcutmedia.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function slimcutmedia(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - global._scm_amp = { - allowed_data: ['pid', 'ffc'], - mandatory_data: ['pid'], - data, - }; - - validateData( - data, - global._scm_amp.mandatory_data, - global._scm_amp.allowed_data - ); - - loadScript( - global, - 'https://static.freeskreen.com/publisher/' + - encodeURIComponent(data.pid) + - '/freeskreen.min.js' - ); -} diff --git a/ads/smartadserver.js b/ads/smartadserver.js deleted file mode 100644 index 03983799f1990..0000000000000 --- a/ads/smartadserver.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2015 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. - */ - -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/smartclip.js b/ads/smartclip.js deleted file mode 100644 index e5a5594e24fb8..0000000000000 --- a/ads/smartclip.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function smartclip(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - global._smartclip_amp = { - allowed_data: ['extra'], - mandatory_data: ['plc', 'sz'], - data, - }; - - validateData( - data, - global._smartclip_amp.mandatory_data, - global._smartclip_amp.allowed_data - ); - - const rand = Math.round(Math.random() * 100000000); - - loadScript( - global, - 'https://des.smartclip.net/ads?type=dyn&plc=' + - encodeURIComponent(data.plc) + - '&sz=' + - encodeURIComponent(data.sz) + - (data.extra ? '&' + encodeURI(data.extra) : '') + - '&rnd=' + - rand - ); -} diff --git a/ads/smi2.js b/ads/smi2.js deleted file mode 100644 index 443321cf2e574..0000000000000 --- a/ads/smi2.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function smi2(global, data) { - validateData(data, ['blockid']); - global._smi2 = global._smi2 || { - viewId: global.context.pageViewId, - blockId: data['blockid'], - htmlURL: data['canonical'] || global.context.canonicalUrl, - ampURL: data['ampurl'] || global.context.sourceUrl, - testMode: data['testmode'] || 'false', - referrer: data['referrer'] || global.context.referrer, - hostname: global.window.context.location.hostname, - clientId: window.context.clientId, - domFingerprint: window.context.domFingerprint, - location: window.context.location, - startTime: window.context.startTime, - }; - global._smi2.AMPCallbacks = { - renderStart: global.context.renderStart, - noContentAvailable: global.context.noContentAvailable, - }; - // load the smi2 AMP JS file script asynchronously - const rand = Math.round(Math.random() * 100000000); - loadScript( - global, - 'https://amp.smi2.ru/ampclient/ampfecth.js?rand=' + rand, - () => {}, - global.context.noContentAvailable - ); -} diff --git a/ads/smilewanted.js b/ads/smilewanted.js deleted file mode 100644 index ef15c13fd8653..0000000000000 --- a/ads/smilewanted.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function smilewanted(global, data) { - // For more flexibility, we construct the call to SmileWanted's URL in the external loader, based on the data received from the AMP tag. - global.smilewantedConfig = data; - loadScript(global, 'https://prebid.smilewanted.com/amp/amp.js'); -} diff --git a/ads/sogouad.js b/ads/sogouad.js deleted file mode 100644 index f9d1058c0d4c1..0000000000000 --- a/ads/sogouad.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sogouad(global, data) { - validateData(data, ['slot', 'w', 'h'], ['responsive']); - const slot = global.document.getElementById('c'); - const ad = global.document.createElement('div'); - const sogouUn = 'sogou_un'; - global[sogouUn] = window[sogouUn] || []; - if (data.w === '100%') { - global[sogouUn].push({ - id: data.slot, - ele: ad, - }); - } else { - global[sogouUn].push({ - id: data.slot, - ele: ad, - w: data.w, - h: data.h, - }); - } - slot.appendChild(ad); - loadScript(global, 'https://theta.sogoucdn.com/wap/js/aw.js'); -} diff --git a/ads/sortable.js b/ads/sortable.js deleted file mode 100644 index 35b7ec85b6d66..0000000000000 --- a/ads/sortable.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sortable(global, data) { - validateData(data, ['site', 'name'], ['responsive']); - - const slot = global.document.getElementById('c'); - const ad = global.document.createElement('div'); - const size = - data.responsive === 'true' ? 'auto' : data.width + 'x' + data.height; - ad.className = 'ad-tag'; - ad.setAttribute('data-ad-name', data.name); - ad.setAttribute('data-ad-size', size); - slot.appendChild(ad); - loadScript( - global, - 'https://tags-cdn.deployads.com/a/' + encodeURIComponent(data.site) + '.js' - ); -} diff --git a/ads/sovrn.js b/ads/sovrn.js deleted file mode 100644 index 183ad0c0951f2..0000000000000 --- a/ads/sovrn.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2016 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. - */ -/* - ********* - * Existing sovrn customers feel free to contact amp-implementations@sovrn.com - * for assistance with setting up your amp-ad tagid New customers please see - * www.sovrn.com to sign up and get started! - ********* - */ -import {writeScript} from '../3p/3p'; -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sovrn(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - global.width = data.width; - global.height = data.height; - global.domain = data.domain; - global.u = data.u; - global.iid = data.iid; - global.aid = data.aid; - global.z = data.z; - global.tf = data.tf; - writeScript(global, 'https://ap.lijit.com/www/sovrn_amp/sovrn_ads.js'); -} diff --git a/ads/speakol.js b/ads/speakol.js deleted file mode 100644 index dd757920e0f6a..0000000000000 --- a/ads/speakol.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable indent */ -/* eslint-disable require-jsdoc */ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ - -export function speakol(global, data) { - validateData(data, ['widgetid']); - - (global.spksdk = global.spksdk || []).push({ - // eslint-disable-next-line google-camelcase/google-camelcase - widget_id: `wi-${data['widgetid']}`, - element: `wi-${data['widgetid']}`, - }); - const d = global.document.createElement('div'); - d.classList.add('speakol-widget'); - d.id = 'wi-' + data['widgetid']; - - global.document.getElementById('c').appendChild(d); - - loadScript(global, 'https://cdn.speakol.com/widget/js/speakol-widget-v2.js'); -} diff --git a/ads/spotx.js b/ads/spotx.js deleted file mode 100644 index d56ef90d6dd81..0000000000000 --- a/ads/spotx.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {startsWith} from '../src/string'; -import {validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function spotx(global, data) { - // ensure we have valid channel id - validateData(data, ['spotx_channel_id', 'width', 'height']); - - // Because 3p's loadScript does not allow for data attributes, - // we will write the JS tag ourselves. - const script = global.document.createElement('script'); - - data['spotx_content_width'] = data.spotx_content_width || data.width; - data['spotx_content_height'] = data.spotx_content_height || data.height; - data['spotx_content_page_url'] = - global.context.location.href || global.context.sourceUrl; - - // Add data-* attribute for each data value passed in. - for (const key in data) { - if (hasOwn(data, key) && startsWith(key, 'spotx_')) { - script.setAttribute(`data-${key}`, data[key]); - } - } - - global['spotx_ad_done_function'] = function (spotxAdFound) { - if (!spotxAdFound) { - global.context.noContentAvailable(); - } - }; - - // TODO(KenneyE): Implement AdLoaded callback in script to accurately trigger - // renderStart() - script.onload = global.context.renderStart; - - script.src = `//js.spotx.tv/easi/v1/${data['spotx_channel_id']}.js`; - global.document.body.appendChild(script); -} diff --git a/ads/springAds.js b/ads/springAds.js deleted file mode 100644 index 069e1a9548e31..0000000000000 --- a/ads/springAds.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {computeInMasterFrame, loadScript} from '../3p/3p'; -import {parseJson} from '../src/json'; - -/** - * @param context - */ - -const initSlotList = function (context) { - context.master.availableSlots = context.master.availableSlots || {}; -}; - -const registerSlot = function (slot) { - context.master.availableSlots[slot.slotName] = slot; -}; - -// eslint-disable-next-line require-jsdoc -export function springAds(global, data) { - computeInMasterFrame( - global, - 'springAds', - function () { - initSlotList(context); - }, - () => {} - ); - if (data.adssetup) { - const adSSetup = parseJson(data.adssetup); - adSSetup['isAMP'] = !0; - adSSetup['availableSlots'] = context.master.availableSlots; - context.master.adSSetup = global.adSSetup = adSSetup; - const sitename = adSSetup['publisher'].match(/(.*)\..*/)[1]; - loadScript( - global, - 'https://www.asadcdn.com/adlib/pages/' + sitename + '_amp.js' - ); - } else { - computeInMasterFrame( - global, - 'springAds', - function () { - registerSlot({ - window, - document, - context, - slotName: data['adslot'], - }); - if (context.master.ASCDP) { - context.master.ASCDP.adS.renderAd(data.adslot); - } - }, - () => {} - ); - } -} diff --git a/ads/springAds.md b/ads/springAds.md deleted file mode 100644 index d84bb10885713..0000000000000 --- a/ads/springAds.md +++ /dev/null @@ -1,62 +0,0 @@ - - -# Spring AdTechnology AmpAd Integration - -## Example - -```html - - - - -``` - -## Configuration - -for further information regarding this implementation, please contact adtechnology@axelspringer.de - -Supported parameters: - -- `data-site`: siteid given by mediaimpact -- `data-page`: pageName given by mediaimpact - -## Optional features - -- Loading placeholder for ads, see [Placeholders in amp-ad](https://amp.dev/documentation/components/amp-ad#placeholder). -- No ad fallback for ads, see [No ad in amp-ad](https://amp.dev/documentation/components/amp-ad#no-ad-available). diff --git a/ads/ssp.js b/ads/ssp.js deleted file mode 100644 index b94e7dde9ba51..0000000000000 --- a/ads/ssp.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; -import {parseJson} from '../src/json'; - -/* - * How to develop: - * https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-e2e.md - */ - -/** - * @param {!Array.} array - * @param {!Function} iteratee - * - * @return {Object} - */ -function keyBy(array, iteratee) { - return array.reduce( - (itemById, item) => Object.assign(itemById, {[iteratee(item)]: item}), - {} - ); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function ssp(global, data) { - // validate AMP input data- attributes - validateData(data, ['position'], ['site']); - - let position = {id: -1}; - - try { - position = parseJson(data.position); - - if (position['id'] === undefined) { - position = {id: -1}; - } - } catch (error) {} - - if (position['id'] === -1) { - global.context.noContentAvailable(); - - return; - } - - // This is super important. Without this any variables on context are not shared - const mW = global.context.isMaster ? global : global.context.master; - - // create parent element - const parentElement = document.createElement('div'); - - parentElement.id = position['id']; - - // https://github.com/ampproject/amphtml/tree/master/ads#the-iframe-sandbox - global.document.getElementById('c').appendChild(parentElement); - - // https://github.com/ampproject/amphtml/blob/master/3p/3p.js#L186 - computeInMasterFrame( - global, - 'ssp-load', - (done) => { - loadScript(global, 'https://ssp.imedia.cz/static/js/ssp.js', () => { - // This callback is run just once for amp-ad with same type - // Script will inject "sssp" object on Window - if (!global['sssp']) { - done(false); - - return; - } - - /** @type {{config: Function, getAds: Function, writeAd: Function}} */ - const ssp = global['sssp']; - - ssp.config({ - site: data.site || global.context.canonicalUrl, - }); - - mW.ssp = ssp; - - done(true); - }); - }, - (loaded) => { - if (!loaded) { - global.context.noContentAvailable(); - - return; - } - - mW.ssp.getAds([position], { - requestErrorCallback: () => global.context.noContentAvailable(), - AMPcallback: (ads) => { - /** @suppress {checkTypes} */ - const adById = keyBy(ads, (item) => item.id); - const ad = adById[position['id']]; - - if (!ad || ['error', 'empty'].includes(ad.type)) { - global.context.noContentAvailable(); - - return; - } - - // SSP need parentElement as value in "position.id" - mW.ssp.writeAd(ad, {...position, id: parentElement}); - - parentElement.setAttribute( - 'style', - [ - 'position: absolute', - 'top: 50%', - 'left: 50%', - 'transform: translate(-50%, -50%)', - '-ms-transform: translate(-50%, -50%)', - ].join('; ') - ); - - const {width, height} = ad; - - global.context.renderStart({width, height}); - }, - }); - } - ); -} diff --git a/ads/strossle.js b/ads/strossle.js deleted file mode 100644 index e24a75fac4c78..0000000000000 --- a/ads/strossle.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -const renderTo = 'strossle-widget'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function strossle(global, data) { - validateData(data, ['widgetid']); - global._strossle = global._strossle || { - widgetId: data['widgetid'], - }; - - createContainer(global, data); - loadScript( - global, - 'https://widgets.sprinklecontent.com/v2/sprinkle.js', - () => {} - ); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function createContainer(global, data) { - const d = global.document.createElement('div'); - d.className = renderTo; - d.setAttribute('data-spklw-widget', data['widgetid']); - global.document.getElementById('c').appendChild(d); -} diff --git a/ads/sulvo.js b/ads/sulvo.js deleted file mode 100644 index 7827f47ed7a61..0000000000000 --- a/ads/sulvo.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2019 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. - */ -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sulvo(global, data) { - global.sulvoAmpAdData = data; - loadScript(global, 'https://live.demand.supply/up.amp.js'); -} diff --git a/ads/sunmedia.js b/ads/sunmedia.js deleted file mode 100644 index 4bf2c62f4d92f..0000000000000 --- a/ads/sunmedia.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function sunmedia(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - global._sunmedia_amp = { - allowed_data: ['cskp', 'crst', 'cdb', 'cid'], - mandatory_data: ['cid'], - data, - }; - - validateData( - data, - global._sunmedia_amp.mandatory_data, - global._sunmedia_amp.allowed_data - ); - - loadScript( - global, - 'https://vod.addevweb.com/sunmedia/amp/ads/SMIntextAMP.js' - ); -} diff --git a/ads/svknative.js b/ads/svknative.js deleted file mode 100644 index 62e50afb49509..0000000000000 --- a/ads/svknative.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function svknative(global, data) { - // ensure we have valid widgetid value - validateData(data, ['widgetid']); - - const s = global.document.createElement('script'); - const scriptKey = - 'svknativeampwidget_' + Math.floor(Math.random() * 10000000); - - s.setAttribute('data-key', scriptKey); - global.document.getElementById('c').appendChild(s); - - (function (w, a) { - (w[a] = w[a] || []).push({ - 'script_key': scriptKey, - 'settings': { - 'w': data['widgetid'], - 'amp': true, - }, - }); - })(global, '_svk_n_widgets'); - - // load the SVK Native AMP JS file - loadScript(global, 'https://widget.svk-native.ru/js/embed.js'); -} diff --git a/ads/swoop.js b/ads/swoop.js deleted file mode 100644 index 849eed02011ef..0000000000000 --- a/ads/swoop.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function swoop(global, data) { - // Required properties - validateData(data, ['layout', 'placement', 'publisher', 'slot']); - - computeInMasterFrame( - global, - 'swoop-load', - (done) => { - global.swoopIabConfig = data; - - loadScript(global, 'https://www.swoop-amp.com/amp.js', () => - done(global.Swoop != null) - ); - }, - (success) => { - if (success) { - if (!global.context.isMaster) { - global.context.master.Swoop.announcePlace(global, data); - } - } else { - global.context.noContentAvailable(); - throw new Error('Swoop failed to load'); - } - } - ); -} diff --git a/ads/taboola.js b/ads/taboola.js deleted file mode 100644 index 4fe48edca40d6..0000000000000 --- a/ads/taboola.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function taboola(global, data) { - // do not copy the following attributes from the 'data' object - // to _tablloa global object - const denylist = ['height', 'type', 'width', 'placement', 'mode']; - - // ensure we have vlid publisher, placement and mode - // and exactly one page-type - validateData(data, [ - 'publisher', - 'placement', - 'mode', - ['article', 'video', 'photo', 'search', 'category', 'homepage', 'other'], - ]); - - // setup default values for referrer and url - const params = { - referrer: data.referrer || global.context.referrer, - url: data.url || global.context.canonicalUrl, - }; - - // copy none denylisted attribute to the 'params' map - Object.keys(data).forEach((k) => { - if (denylist.indexOf(k) === -1) { - params[k] = data[k]; - } - }); - - // push the two object into the '_taboola' global - (global._taboola = global._taboola || []).push([ - { - viewId: global.context.pageViewId, - publisher: data.publisher, - placement: data.placement, - mode: data.mode, - framework: 'amp', - container: 'c', - }, - params, - {flush: true}, - ]); - - // install observation on entering/leaving the view - global.context.observeIntersection(function (changes) { - /** @type {!Array} */ (changes).forEach(function (c) { - if (c.intersectionRect.height) { - global._taboola.push({ - visible: true, - rects: c, - placement: data.placement, - }); - } - }); - }); - - // load the taboola loader asynchronously - loadScript( - global, - `https://cdn.taboola.com/libtrc/${encodeURIComponent( - data.publisher - )}/loader.js` - ); -} diff --git a/ads/tcsemotion.js b/ads/tcsemotion.js deleted file mode 100644 index 98d7c866f01be..0000000000000 --- a/ads/tcsemotion.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2019 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. - */ -import {validateData, writeScript} from '../3p/3p'; -/** - * @param {!Window} global - * @param {!Object} d - */ -export function tcsemotion(global, d) { - validateData(d, ['zone', 'delhost']); - global.djaxData = d; - if (d.hb && d.hb == 'true') { - global.djaxData.hb = true; - } else { - global.djaxData.hb = false; - } - writeScript(global, 'https://ads.tcsemotion.com/www/delivery/amphb.js'); -} diff --git a/ads/tcsemotion.md b/ads/tcsemotion.md deleted file mode 100644 index e7975b8dcd3ea..0000000000000 --- a/ads/tcsemotion.md +++ /dev/null @@ -1,29 +0,0 @@ -# TcsEmotion - -## Example - -```html - - -``` - -## Configuration - -Please refer to [Tcsemotion](https://tcsemotion.com/) for more -information on how to get zone id and other details - -### Required parameters - -- `data-zone`: the id of the zone provided by adserver -- `data-delhost`: target domain url - -### Optional parameters - -- `data-hb`: enable this field to true when you are using header bidding diff --git a/ads/teads.js b/ads/teads.js deleted file mode 100644 index dcbca82831779..0000000000000 --- a/ads/teads.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function teads(global, data) { - /*eslint "google-camelcase/google-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 - ); - - const QueryString = function () { - // This function is anonymous, is executed immediately and - // the return value is assigned to QueryString! - const query_string = {} - const a = document.createElement('a') - a.href = global.context.sourceUrl - const query = a.search.substring(1) - const vars = query.split("&") - for (var i = 0; i < vars.length; i++) { - const pair = vars[i].split("=") - // If first entry with this name - if (typeof query_string[pair[0]] === "undefined") { - query_string[pair[0]] = decodeURIComponent(pair[1]) - // If second entry with this name - } else if (typeof query_string[pair[0]] === "string") { - const arr = [query_string[pair[0]], decodeURIComponent(pair[1])] - query_string[pair[0]] = arr - // If third or later entry with this name - } else { - query_string[pair[0]].push(decodeURIComponent(pair[1])) - } - } - return query_string - }() - - if ( - QueryString.js || - QueryString.second_asset_url || - QueryString.pid || - QueryString.page || - QueryString.content - ) { - - if (QueryString.tracking) { - (global.teads || (global.teads = {})).TRACKING_URL = QueryString.tracking - } - - // FOR-3052: Split teads-format into 2 assets - if (QueryString.second_asset_url) { - (global.teads || (global.teads = {})).FORMAT_WITH_PLAYER_URL = QueryString.second_asset_url - } - - const ttag = () => { - global.teads - .ad(QueryString.pid || 47405, { - type: 'VastUrl', - content: QueryString.content || 'https://a.teads.tv/vast/preview/50221', - settings: { - values: { - pageId: QueryString.page || 42266, - placementId: QueryString.pid || 47405, - adType: QueryString.adtype || 'video' - }, - components: { - progressBar: QueryString.pb || false - } - }, - headerBiddingProvider: 'debugFormat' - }) - .page(QueryString.page || 42266) - .placement(QueryString.pid || 47405, { format: 'inread', slot: { selector: 'body' } }) - .serve() - } - - loadScript(global, `//${QueryString.js || 's8t.teads.tv/media/format/v3/teads-format.min.js'}`, ttag) - } else 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/teads.md b/ads/teads.md deleted file mode 100644 index c59461585f7f6..0000000000000 --- a/ads/teads.md +++ /dev/null @@ -1,47 +0,0 @@ - - -# Teads - -## Example - -```html - - -``` - -## Configuration - -For configuration semantics, please contact [Teads](http://teads.tv/fr/contact/). - -Supported parameters: - -- `data-pid` - -## User Consent Integration - -When [user consent](https://github.com/ampproject/amphtml/blob/master/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required, Teads ad approaches user consent in the following ways: - -- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. -- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a personalized ad to the user. -- `CONSENT_POLICY_STATE.UNKNOWN`: Serve a non-personalized ad to the user.. diff --git a/ads/temedya.js b/ads/temedya.js deleted file mode 100644 index f239a9dedb3ea..0000000000000 --- a/ads/temedya.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function temedya(global, data) { - validateData(data, ['widgetid']); - global._temedya = global._temedya || { - widgetId: data['widgetid'], - }; - global._temedya.AMPCallbacks = { - renderStart: global.context.renderStart, - noContentAvailable: global.context.noContentAvailable, - }; - // load the temedya AMP JS file script asynchronously - loadScript( - global, - 'https://widget.cdn.vidyome.com/builds/loader-amp.js', - () => {}, - global.context.noContentAvailable - ); -} diff --git a/ads/torimochi.js b/ads/torimochi.js deleted file mode 100644 index 2e628a5a54a6e..0000000000000 --- a/ads/torimochi.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {parseJson} from '../src/json'; -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function torimochi(global, data) { - validateData(data, ['area', 'adtype']); - - if (data.width < global.width) { - global.width = data.width; - } - global.height = data.height; - global.area = data['area']; - global.adtype = data['adtype']; - global.tcid = data['tcid']; - global.wid = data['wid']; - global.extra = parseJson(data['extra'] || '{}'); - global.context.renderStart({width: global.width, height: global.height}); - - const url = - 'https://asset.torimochi-ad.net/js/torimochi_ad_amp.min.js?v=' + Date.now(); - - writeScript(global, url); -} diff --git a/ads/tracdelight.js b/ads/tracdelight.js deleted file mode 100644 index 0bc7568465ffa..0000000000000 --- a/ads/tracdelight.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function tracdelight(global, data) { - const mandatoryFields = ['widget_id', 'access_key']; - const optionalFields = ['mode']; - - validateData(data, mandatoryFields, optionalFields); - - global.tdData = data; - writeScript(global, 'https://scripts.tracdelight.io/amp.js'); -} diff --git a/ads/triplelift.js b/ads/triplelift.js deleted file mode 100644 index cddd6af052a1a..0000000000000 --- a/ads/triplelift.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateSrcPrefix} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function triplelift(global, data) { - const {src} = data; - validateSrcPrefix('https://ib.3lift.com/', src); - loadScript(global, src); -} diff --git a/ads/trugaze.js b/ads/trugaze.js deleted file mode 100644 index c2d7683e18e7e..0000000000000 --- a/ads/trugaze.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - */ -export function trugaze(global) { - // For simplicity and flexibility, all validations are performed in the - // Trugaze's URL based on the data received - loadScript(global, 'https://cdn.trugaze.io/amp-init-v1.js'); -} diff --git a/ads/uas.js b/ads/uas.js deleted file mode 100644 index 81400f4fec4b5..0000000000000 --- a/ads/uas.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {hasOwn} from '../src/utils/object'; -import {loadScript, validateData} from '../3p/3p'; -import {setStyles} from '../src/style'; - -/** - * @param {!Object} theObject - * @param {!Function} callback - */ -function forEachOnObject(theObject, callback) { - if (typeof theObject === 'object' && theObject !== null) { - if (typeof callback === 'function') { - for (const key in theObject) { - if (hasOwn(theObject, key)) { - callback(key, theObject[key]); - } - } - } - } -} - -/** - * @param {!Window} global - */ -function centerAd(global) { - const e = global.document.getElementById('c'); - if (e) { - setStyles(e, { - top: '50%', - left: '50%', - bottom: '', - right: '', - transform: 'translate(-50%, -50%)', - }); - } -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function uas(global, data) { - validateData( - data, - ['accId', 'adUnit', 'sizes'], - [ - 'locLat', - 'locLon', - 'locSrc', - 'pageURL', - 'targetings', - 'extraParams', - 'visibility', - ] - ); - global.Phoenix = {EQ: []}; - const uasDivId = 'uas-amp-slot'; - global.document.write('
'); - loadScript(global, 'https://ads.pubmatic.com/AdServer/js/phoenix.js', () => { - global.Phoenix.EQ.push(function () { - global.Phoenix.enableSingleRequestCallMode(); - global.Phoenix.setInfo('AMP', 1); // Need to set the AMP flag - global.Phoenix.setInfo('ACCID', data.accId); - // Reading PAGEURL from sourceUrl or location.href - global.Phoenix.setInfo( - 'PAGEURL', - global.context.sourceUrl || global.context.location.href - ); // eslint-disable-line max-len - data.pageURL && global.Phoenix.setInfo('PAGEURL', data.pageURL); - data.locLat && global.Phoenix.setInfo('LAT', data.locLat); - data.locLon && global.Phoenix.setInfo('LON', data.locLon); - data.locSrc && global.Phoenix.setInfo('LOC_SRC', data.locSrc); - const slot = global.Phoenix.defineAdSlot( - data.adUnit, - data.sizes, - uasDivId - ); - slot.setVisibility(1); - forEachOnObject(data.targetings, function (key, value) { - slot.setTargeting(key, value); - }); - forEachOnObject(data.extraParams, function (key, value) { - slot.setExtraParameters(key, value); - }); - global.Phoenix.display(uasDivId); - }); - }); - centerAd(global); -} diff --git a/ads/ucfunnel.js b/ads/ucfunnel.js deleted file mode 100644 index 20df072c10f33..0000000000000 --- a/ads/ucfunnel.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function ucfunnel(global, data) { - validateData(data, ['siteId']); - loadScript(window, 'https://ads.aralego.com/ampsdk'); - window.context.renderStart(); -} diff --git a/ads/unruly.js b/ads/unruly.js deleted file mode 100644 index f75916aa8da6c..0000000000000 --- a/ads/unruly.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - * @param {!Function} [scriptLoader=loadScript] - */ -export function unruly(global, data, scriptLoader = loadScript) { - validateData(data, ['siteId']); - - global.unruly = global.unruly || {}; - global.unruly.native = { - siteId: data.siteId, - }; - - scriptLoader(global, 'https://video.unrulymedia.com/native/native-loader.js'); -} diff --git a/ads/uzou.js b/ads/uzou.js deleted file mode 100644 index 3086c8008147c..0000000000000 --- a/ads/uzou.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {parseJson} from '../src/json'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function uzou(global, data) { - validateData(data, ['widgetParams'], []); - - const prefixMap = { - test: 'dev-', - development: 'dev-', - staging: 'staging-', - production: '', - }; - - const widgetParams = parseJson(data['widgetParams']); - const akamaiHost = widgetParams['akamaiHost'] || 'speee-ad.akamaized.net'; - const placementCode = widgetParams['placementCode']; - const mode = widgetParams['mode'] || 'production'; - const entryPoint = `https://${prefixMap[mode]}${akamaiHost}/tag/${placementCode}/js/outer-frame.min.js`; - - const d = global.document.createElement('div'); - d.className = `uz-${placementCode} uz-ny`; - - const container = global.document.getElementById('c'); - container.appendChild(d); - - const uzouInjector = { - url: fixedEncodeURIComponent( - widgetParams['url'] || - global.context.canonicalUrl || - global.context.sourceUrl - ), - referer: widgetParams['referer'] || global.context.referrer, - }; - ['adServerHost', 'akamaiHost', 'iframeSrcPath'].forEach(function (elem) { - if (widgetParams[elem]) { - uzouInjector[elem] = widgetParams[elem]; - } - }); - global.UzouInjector = uzouInjector; - - loadScript(global, entryPoint, () => { - global.context.renderStart(); - }); -} - -/** - * encode URI based on RFC 3986 - * @param {string} str url string - * @return {*} TODO(#23582): Specify return type - */ -function fixedEncodeURIComponent(str) { - return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { - return '%' + c.charCodeAt(0).toString(16); - }); -} diff --git a/ads/valuecommerce.js b/ads/valuecommerce.js deleted file mode 100644 index 6bcf751b385a1..0000000000000 --- a/ads/valuecommerce.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function valuecommerce(global, data) { - validateData(data, ['pid'], ['sid', 'vcptn', 'om']); - global.vcParam = data; - writeScript(global, 'https://amp.valuecommerce.com/amp_bridge.js'); -} diff --git a/ads/vdoai.js b/ads/vdoai.js deleted file mode 100644 index 3535b62e879f2..0000000000000 --- a/ads/vdoai.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript} from '../3p/3p'; -/** - * @param {!Window} global - * @param {!Object} data - */ -export function vdoai(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - global.vdo_ai_ = { - unitData: data['unitid'], - unitTagname: data['tagname'], - }; - loadScript(global, 'https://a.vdo.ai/core/dependencies_amp/remote.js'); -} diff --git a/ads/vendors/1wo.js b/ads/vendors/1wo.js new file mode 100644 index 0000000000000..726c80178bcd1 --- /dev/null +++ b/ads/vendors/1wo.js @@ -0,0 +1,40 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function _1wo(global, data) { + validateData(data, ['src', 'owoType', 'owoCode', 'owoMode']); + 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-owo-type', data['owoType']); + d.setAttribute('data-owo-code', data['owoCode']); + d.setAttribute('data-owo-mode', data['owoMode']); + global.document.getElementById('c').appendChild(d); +} diff --git a/ads/1wo.md b/ads/vendors/1wo.md similarity index 93% rename from ads/1wo.md rename to ads/vendors/1wo.md index 4897a5b0c97f7..6d9168553352d 100644 --- a/ads/1wo.md +++ b/ads/vendors/1wo.md @@ -20,10 +20,10 @@ Provides support for [1wo](https://welcome.1worldonline.com/) widgets. ### Required parameters -- `src` -- `data-owo-code` -- `data-owo-type` -- `data-owo-mode` +- `src` +- `data-owo-code` +- `data-owo-type` +- `data-owo-mode` ## Example diff --git a/ads/vendors/24smi.js b/ads/vendors/24smi.js new file mode 100644 index 0000000000000..f42eabc583e62 --- /dev/null +++ b/ads/vendors/24smi.js @@ -0,0 +1,61 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData, validateSrcPrefix} from '../../3p/3p'; + +const jsnPrefix = 'https://jsn.24smi.net/'; +const smiJs = `${jsnPrefix}smi.js`; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function _24smi(global, data) { + validateData(data, [['blockid', 'src']]); + const {src} = data; + let blockId = data['blockid']; + + if (!blockId) { + validateSrcPrefix(jsnPrefix, src); + blockId = getBlockId(src); + } + + const element = createContainer(global); + (global.smiq = global.smiq || []).push({ + element, + blockId, + }); + loadScript(global, smiJs); +} + +/** + * @param {!Window} global + * @return {Element} + */ +function createContainer(global) { + const d = global.document.createElement('div'); + global.document.getElementById('c').appendChild(d); + return d; +} + +/** + * @param {string} src + * @return {string} + */ +function getBlockId(src) { + const parts = src.split('/'); + return parts[parts.length - 1].split('.')[0]; +} diff --git a/ads/24smi.md b/ads/vendors/24smi.md similarity index 98% rename from ads/24smi.md rename to ads/vendors/24smi.md index 2a755dc04e3a5..5e5ea60b8c93f 100644 --- a/ads/24smi.md +++ b/ads/vendors/24smi.md @@ -35,4 +35,4 @@ For details on the configuration semantics, please contact [24smi](https://partn ### Required parameters -- `src` +- `src` diff --git a/ads/vendors/_fakedelayed_.js b/ads/vendors/_fakedelayed_.js new file mode 100644 index 0000000000000..44461c5a00606 --- /dev/null +++ b/ads/vendors/_fakedelayed_.js @@ -0,0 +1,43 @@ +/** + * Copyright 2020 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. + */ + +import {setStyles} from '../../src/style'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function fakeDelayed(global, data) { + validateData(data, ['src', 'bootstrapScript']); + + const iframe = global.document.createElement('iframe'); + iframe.setAttribute('id', 'creative'); + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('marginheight', '0'); + iframe.setAttribute('marginwidth', '0'); + iframe.setAttribute('scrolling', 'no'); + iframe.setAttribute('src', data['src']); + setStyles(iframe, { + border: '0 none transparent', + position: 'relative', + height: '100%', + width: '100%', + }); + global.document.getElementById('c').appendChild(iframe); + + writeScript(global, data['bootstrapScript']); +} diff --git a/ads/vendors/_fakedelayed_.md b/ads/vendors/_fakedelayed_.md new file mode 100644 index 0000000000000..9f96f17f49aee --- /dev/null +++ b/ads/vendors/_fakedelayed_.md @@ -0,0 +1,39 @@ + + +# fake-delayed + +A fake ad type that is used for local development and testing. + +## Example + +```html + + +``` + +## Configuration + +### Required parameters + +- `src` : The URL of the target creative to be displayed. +- `data-bootstrap-script` : The URL of a bootstraping JS code to be loaded in iframe s. diff --git a/ads/vendors/_ping_.js b/ads/vendors/_ping_.js new file mode 100644 index 0000000000000..b959624fe023a --- /dev/null +++ b/ads/vendors/_ping_.js @@ -0,0 +1,101 @@ +/** + * Copyright 2016 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. + */ + +import {dev, devAssert, userAssert} from '../../src/log'; +import {validateData} from '../../3p/3p'; + +/** + * A fake ad network integration that is mainly used for testing + * and demo purposes. This implementation gets stripped out in compiled + * production code. + * @param {!Window} global + * @param {!Object} data + */ +export function _ping_(global, data) { + // for testing only. see #10628 + global.networkIntegrationDataParamForTesting = data; + + validateData(data, ['url'], ['valid', 'adHeight', 'adWidth', 'enableIo']); + userAssert(!data['error'], 'Fake user error!'); + global.document.getElementById('c').textContent = data.ping; + global.ping = Object.create(null); + + if (data.ad_container) { + devAssert(global.context.container == data.ad_container, 'wrong container'); + } + if (data.valid == 'false') { + // Immediately send no-content for visual diff test + global.context.noContentAvailable(); + } + if (data.valid && data.valid == 'true') { + const img = document.createElement('img'); + if (data.url) { + img.setAttribute('src', data.url); + img.setAttribute('width', data.width); + img.setAttribute('height', data.height); + } + let width, height; + if (data.adHeight) { + img.setAttribute('height', data.adHeight); + height = Number(data.adHeight); + } + if (data.adWidth) { + img.setAttribute('width', data.adWidth); + width = Number(data.adWidth); + } + document.body.appendChild(img); + if (width || height) { + global.context.renderStart({width, height}); + } else { + global.context.renderStart(); + } + if (data.enableIo) { + global.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + dev().info( + 'AMP-AD', + 'Intersection: (WxH)' + + `${c.intersectionRect.width}x${c.intersectionRect.height}` + ); + }); + // store changes to global.lastIO for testing purpose + global.ping.lastIO = changes[changes.length - 1]; + }); + } + global.context.getHtml('a', ['href'], function (html) { + dev().info('GET-HTML', html); + }); + global.context.getConsentState(function (consentState) { + dev().info('GET-CONSENT-STATE', consentState); + }); + if (global.context.consentSharedData) { + const TAG = 'consentSharedData'; + dev().info(TAG, global.context.consentSharedData); + } + if (global.context.initialConsentValue) { + const TAG = 'consentStringValue'; + dev().info(TAG, global.context.initialConsentValue); + } + if (global.context.initialConsentMetadata) { + const TAG = 'consentMetadata'; + dev().info(TAG, global.context.initialConsentMetadata); + } + } else { + global.setTimeout(() => { + global.context.noContentAvailable(); + }, 1000); + } +} diff --git a/ads/vendors/_ping_.md b/ads/vendors/_ping_.md new file mode 100644 index 0000000000000..6e823d3a73724 --- /dev/null +++ b/ads/vendors/_ping_.md @@ -0,0 +1,57 @@ + + +# \_PING\_ + +A fake ad type that is only used for local development. + +## Example + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the [ad network](#configuration) or refer to their [documentation](#ping). + +### Required parameters + +- `data-url` : Image ad with the image. + +### Optional parameters + +- `data-valid` : Set to false to return a no fill ad. +- `data-ad-height` : Ad image size. +- `data-ad-width` : Ad image width. +- `data-enable-io` : Enable logging IntersectionObserver entry. + +## User Consent Integration + +When [user consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required. \_Ping\_ ad approaches user consent in the following ways: + +- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN`: Will not serve an ad to the user. diff --git a/ads/vendors/a8.js b/ads/vendors/a8.js new file mode 100644 index 0000000000000..ac0b72d12cf54 --- /dev/null +++ b/ads/vendors/a8.js @@ -0,0 +1,27 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function a8(global, data) { + validateData(data, ['aid'], ['wid', 'eno', 'mid', 'mat', 'type']); + global.a8Param = data; + writeScript(global, 'https://statics.a8.net/amp/ad.js'); +} diff --git a/ads/a8.md b/ads/vendors/a8.md similarity index 92% rename from ads/a8.md rename to ads/vendors/a8.md index 9c3840ed3cce2..e62312d4d879c 100644 --- a/ads/a8.md +++ b/ads/vendors/a8.md @@ -43,12 +43,12 @@ For configuration details and to generate your tags, please contact https://supp ### Required parameters -- `data-aid` +- `data-aid` ### Optional parameters -- `data-wid` -- `data-eno` -- `data-mid` -- `data-mat` -- `data-type` +- `data-wid` +- `data-eno` +- `data-mid` +- `data-mat` +- `data-type` diff --git a/ads/vendors/a9.js b/ads/vendors/a9.js new file mode 100644 index 0000000000000..a69833c4a9b50 --- /dev/null +++ b/ads/vendors/a9.js @@ -0,0 +1,200 @@ +/** + * Copyright 2015 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, validateData, writeScript} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +const mandatoryParams = [], + optionalParams = [ + 'ad_mode', + 'placement', + 'tracking_id', + 'ad_type', + 'marketplace', + 'region', + 'title', + 'default_search_phrase', + 'default_category', + 'linkid', + 'search_bar', + 'search_bar_position', + 'rows', + 'design', + 'asins', + 'debug', + 'aax_src_id', + 'header_style', + 'link_style', + 'link_hover_style', + 'text_style', + 'random_permute', + 'render_full_page', + 'axf_exp_name', + 'axf_treatment', + 'disable_borders', + 'attributes', + 'carousel', + 'feedback_enable', + 'max_ads_in_a_row', + 'list_price', + 'prime', + 'prime_position', + 'widget_padding', + 'strike_text_style', + 'brand_text_link', + 'brand_position', + 'large_rating', + 'rating_position', + 'max_title_height', + 'enable_swipe_on_mobile', + 'overrides', + 'ead', + 'force_win_bid', + 'fallback_mode', + 'url', + 'regionurl', + 'divid', + 'recomtype', + 'adinstanceid', + ]; +const prefix = 'amzn_assoc_'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function a9(global, data) { + let i; + for (i = 0; i < 46; i++) { + optionalParams[i] = prefix + optionalParams[i]; + } + + validateData(data, mandatoryParams, optionalParams); + + const publisherUrl = global.context.canonicalUrl || global.context.sourceUrl; + + if (data.amzn_assoc_ad_mode) { + if (data.amzn_assoc_ad_mode === 'auto') { + if (data.adinstanceid && data.adinstanceid !== '') { + loadRecTag(global, data, publisherUrl); + } else { + loadSearTag(global, data, publisherUrl); + } + } else if ( + data.amzn_assoc_ad_mode === 'search' || + data.amzn_assoc_ad_mode === 'manual' + ) { + loadSearTag(global, data, publisherUrl); + } + } else { + loadSearTag(global, data, publisherUrl); + } +} + +/** + * @param {!Object} data + * @return {string} + */ +function getURL(data) { + let url = 'https://z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US'; + if (data.regionurl && data.regionurl !== '') { + url = data.regionurl; + } + + return url; +} + +/** + * @param {!Window} global + * @param {!Object} data + * @param {string} publisherUrl + */ +function loadRecTag(global, data, publisherUrl) { + let url = getURL(data); + url += '&adInstanceId=' + data['adinstanceid']; + global['amzn_assoc_URL'] = publisherUrl; + + if (data['recomtype'] === 'sync') { + writeScript(global, url); + } else if (data['recomtype'] === 'async') { + const d = global.document.createElement('div'); + d.setAttribute('id', data['divid']); + global.document.getElementById('c').appendChild(d); + loadScript(global, url); + } +} + +/** + * @param {!Window} global + * @param {!Object} data + * @param {string} publisherUrl + */ +function loadSearTag(global, data, publisherUrl) { + /** + * Sets macro type. + * @param {string} type + */ + function setMacro(type) { + if (!type) { + return; + } + + if (hasOwn(data, type)) { + global[type] = data[type]; + } + } + + /** + * Sets the params. + */ + function setParams() { + const url = getURL(data); + let i; + + for (i = 0; i < 44; i++) { + setMacro(optionalParams[i]); + } + + if (data.amzn_assoc_fallback_mode) { + const str = data.amzn_assoc_fallback_mode.split(','); + let types = str[0].split(':'); + let typev = str[1].split(':'); + types = parseJson(types[1]); + typev = parseJson(typev[1]); + global['amzn_assoc_fallback_mode'] = { + 'type': types, + 'value': typev, + }; + } + if (data.amzn_assoc_url) { + global['amzn_assoc_URL'] = data['amzn_assoc_url']; + } else { + global['amzn_assoc_URL'] = publisherUrl; + } + + writeScript(global, url); + } + + /** + * Initializer. + */ + function init() { + setParams(); + } + + init(); +} diff --git a/ads/a9.md b/ads/vendors/a9.md similarity index 97% rename from ads/a9.md rename to ads/vendors/a9.md index 620cadce9c7d4..74a8a33fcdc2d 100644 --- a/ads/a9.md +++ b/ads/vendors/a9.md @@ -102,7 +102,7 @@ The A9 amp ad unit supports the Search, Recommendation and Custom ad widgets of ## Required parameters -- `data-amzn_assoc_ad_mode` - specify the ad type as search, manual or auto (for recommendation ads) +- `data-amzn_assoc_ad_mode` - specify the ad type as search, manual or auto (for recommendation ads) ## Optional parameters diff --git a/ads/vendors/accesstrade.js b/ads/vendors/accesstrade.js new file mode 100644 index 0000000000000..c2bdfc053eab0 --- /dev/null +++ b/ads/vendors/accesstrade.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function accesstrade(global, data) { + validateData(data, ['atops', 'atrotid']); + global.atParams = data; + writeScript(global, 'https://h.accesstrade.net/js/amp/amp.js'); +} diff --git a/ads/accesstrade.md b/ads/vendors/accesstrade.md similarity index 96% rename from ads/accesstrade.md rename to ads/vendors/accesstrade.md index 49b14adcafdd3..c6356f47fdbfa 100644 --- a/ads/accesstrade.md +++ b/ads/vendors/accesstrade.md @@ -35,5 +35,5 @@ For configuration details and to generate your tags, please contact https://memb Supported parameters: -- `data-atops` -- `data-atrotid` +- `data-atops` +- `data-atrotid` diff --git a/ads/vendors/adagio.js b/ads/vendors/adagio.js new file mode 100755 index 0000000000000..6a2ba7d270b34 --- /dev/null +++ b/ads/vendors/adagio.js @@ -0,0 +1,32 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adagio(global, data) { + validateData(data, ['sid', 'loc']); + + const $neodata = global; + + $neodata._adagio = {}; + $neodata._adagio.amp = data; + + loadScript($neodata, 'https://js-ssl.neodatagroup.com/adagio_amp.js'); +} diff --git a/ads/adagio.md b/ads/vendors/adagio.md similarity index 93% rename from ads/adagio.md rename to ads/vendors/adagio.md index 46a90b589a656..360d1ce4e9ba6 100755 --- a/ads/adagio.md +++ b/ads/vendors/adagio.md @@ -37,7 +37,7 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-sid` -- `data-loc` -- `data-keywords` -- `data-uservars` +- `data-sid` +- `data-loc` +- `data-keywords` +- `data-uservars` diff --git a/ads/vendors/adblade.js b/ads/vendors/adblade.js new file mode 100644 index 0000000000000..60c10ffe56d92 --- /dev/null +++ b/ads/vendors/adblade.js @@ -0,0 +1,65 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +const adbladeFields = ['width', 'height', 'cid']; +const adbladeHostname = 'web.adblade.com'; +const industrybrainsHostname = 'web.industrybrains.com'; + +/** + * @param {string} hostname + * @param {!Window} global + * @param {!Object} data + */ +function addAdiantUnit(hostname, global, data) { + validateData(data, adbladeFields, []); + + // create a data element so our script knows what to do + const ins = global.document.createElement('ins'); + ins.setAttribute('class', 'adbladeads'); + ins.setAttribute('data-width', data.width); + ins.setAttribute('data-height', data.height); + ins.setAttribute('data-cid', data.cid); + ins.setAttribute('data-host', hostname); + ins.setAttribute('data-protocol', 'https'); + ins.setAttribute('data-tag-type', 1); + global.document.getElementById('c').appendChild(ins); + + ins.parentNode.addEventListener( + 'eventAdbladeRenderStart', + global.context.renderStart() + ); + + // run our JavaScript code to display the ad unit + writeScript(global, 'https://' + hostname + '/js/ads/async/show.js'); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adblade(global, data) { + addAdiantUnit(adbladeHostname, global, data); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function industrybrains(global, data) { + addAdiantUnit(industrybrainsHostname, global, data); +} diff --git a/ads/adblade.md b/ads/vendors/adblade.md similarity index 94% rename from ads/adblade.md rename to ads/vendors/adblade.md index 093cb27c4be31..620f791e9780b 100644 --- a/ads/adblade.md +++ b/ads/vendors/adblade.md @@ -36,6 +36,6 @@ For semantics of configuration, see [Adblade's documentation](https://www.adblad Supported parameters: -- `data-cid` -- `data-width` -- `data-height` +- `data-cid` +- `data-width` +- `data-height` diff --git a/ads/vendors/adbutler.js b/ads/vendors/adbutler.js new file mode 100644 index 0000000000000..1c260a9e78d05 --- /dev/null +++ b/ads/vendors/adbutler.js @@ -0,0 +1,61 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adbutler(global, data) { + validateData( + data, + ['account', 'zone', 'width', 'height'], + ['keyword', 'place'] + ); + + data['place'] = data['place'] || 0; + + const placeholderID = 'placement_' + data['zone'] + '_' + data['place']; + + // placeholder div + const d = global.document.createElement('div'); + d.setAttribute('id', placeholderID); + global.document.getElementById('c').appendChild(d); + + global.AdButler = global.AdButler || {}; + global.AdButler.ads = global.AdButler.ads || []; + + global.AdButler.ads.push({ + handler(opt) { + global.AdButler.register( + data['account'], + data['zone'], + [data['width'], data['height']], + placeholderID, + opt + ); + }, + opt: { + place: data['place'], + pageKey: global.context.pageViewId, + keywords: data['keyword'], + domain: 'servedbyadbutler.com', + click: 'CLICK_MACRO_PLACEHOLDER', + }, + }); + loadScript(global, 'https://servedbyadbutler.com/app.js'); +} diff --git a/ads/adbutler.md b/ads/vendors/adbutler.md similarity index 91% rename from ads/adbutler.md rename to ads/vendors/adbutler.md index 889c1fcc388bb..777e20b2d9f57 100644 --- a/ads/adbutler.md +++ b/ads/vendors/adbutler.md @@ -37,12 +37,12 @@ For details on the configuration semantics, please see [AdButler's documentation ### Required parameters -- `width` -- `height` -- `data-account` -- `data-zone` +- `width` +- `height` +- `data-account` +- `data-zone` ### Optional parameters -- `data-place` -- `data-keyword` +- `data-place` +- `data-keyword` diff --git a/ads/vendors/adform.js b/ads/vendors/adform.js new file mode 100644 index 0000000000000..93c7be9452e06 --- /dev/null +++ b/ads/vendors/adform.js @@ -0,0 +1,57 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, validateSrcPrefix, writeScript} from '../../3p/3p'; + +// Valid adform ad source hosts +const hosts = { + track: 'https://track.adform.net', + adx: 'https://adx.adform.net', + a2: 'https://a2.adform.net', + adx2: 'https://adx2.adform.net', + asia: 'https://asia.adform.net', + adx3: 'https://adx3.adform.net', +}; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adform(global, data) { + validateData(data, [['src', 'bn', 'mid']]); + global.Adform = {ampData: data}; + const {bn, mid, src} = data; + let url; + + // Custom ad url using "data-src" attribute + if (src) { + validateSrcPrefix( + Object.keys(hosts).map((type) => hosts[type]), + src + ); + url = src; + } + // Ad tag using "data-bn" attribute + else if (bn) { + url = hosts.track + '/adfscript/?bn=' + encodeURIComponent(bn) + ';msrc=1'; + } + // Ad placement using "data-mid" attribute + else if (mid) { + url = hosts.adx + '/adx/?mid=' + encodeURIComponent(mid); + } + + writeScript(global, url); +} diff --git a/ads/adform.md b/ads/vendors/adform.md similarity index 91% rename from ads/adform.md rename to ads/vendors/adform.md index 503c2fe76f602..70d82a8a1e183 100644 --- a/ads/adform.md +++ b/ads/vendors/adform.md @@ -51,7 +51,7 @@ information on how to get required ad tag or placement IDs. Only one of the mentioned parameters should be used at the same time. -- `data-bn` -- `data-mid` -- `src`: must use https protocol and must be from one of the - allowed Adform hosts. +- `data-bn` +- `data-mid` +- `src`: must use https protocol and must be from one of the + allowed Adform hosts. diff --git a/ads/vendors/adfox.js b/ads/vendors/adfox.js new file mode 100644 index 0000000000000..ec5758f39cb68 --- /dev/null +++ b/ads/vendors/adfox.js @@ -0,0 +1,83 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {yandex} from './yandex'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adfox(global, data) { + validateData(data, ['adfoxParams', 'ownerId']); + loadScript(global, 'https://yastatic.net/pcode/adfox/loader.js', () => + initAdFox(global, data) + ); +} + +/** + * @param {!Window} global + * @param {Object} data + */ +function initAdFox(global, data) { + const params = JSON.parse(data.adfoxParams); + + createContainer(global, 'adfox_container'); + + global.Ya.adfoxCode.create({ + ownerId: data.ownerId, + containerId: 'adfox_container', + params, + onLoad: (data, onRender, onError) => { + checkLoading(global, data, onRender, onError); + }, + onRender: () => global.context.renderStart(), + onError: () => global.context.noContentAvailable(), + onStub: () => global.context.noContentAvailable(), + }); +} + +/** + * @param {!Window} global + * @param {!Object} data + * @param {!Object} onRender + * @param {!Object} onError + * @return {boolean} + */ +function checkLoading(global, data, onRender, onError) { + if (data.bundleName === 'banner.direct') { + const dblParams = { + blockId: data.bundleParams.blockId, + data: data.bundleParams.data, + onRender, + onError, + }; + + yandex(global, dblParams); + return false; + } + return true; +} + +/** + * @param {!Window} global + * @param {string} id + */ +function createContainer(global, id) { + const container = global.document.createElement('div'); + container.setAttribute('id', id); + global.document.getElementById('c').appendChild(container); +} diff --git a/ads/adfox.md b/ads/vendors/adfox.md similarity index 95% rename from ads/adfox.md rename to ads/vendors/adfox.md index 52c279952173c..46397e1f70e20 100644 --- a/ads/adfox.md +++ b/ads/vendors/adfox.md @@ -35,5 +35,5 @@ For details on the configuration semantics, please see [AdFox's documentation](h ### Required parameters -- `data-owner-id` -- `data-adfox-params` +- `data-owner-id` +- `data-adfox-params` diff --git a/ads/vendors/adgeneration.js b/ads/vendors/adgeneration.js new file mode 100644 index 0000000000000..6d53191c45e52 --- /dev/null +++ b/ads/vendors/adgeneration.js @@ -0,0 +1,78 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adgeneration(global, data) { + validateData(data, ['id'], ['targetid', 'displayid', 'adtype', 'option']); + + // URL encoding + const option = data.option ? encodeQueryValue(data.option) : null; + + const url = + 'https://i.socdm.com/sdk/js/adg-script-loader.js?' + + 'id=' + + encodeURIComponent(data.id) + + '&width=' + + encodeURIComponent(data.width) + + '&height=' + + encodeURIComponent(data.height) + + '&async=true' + + '&adType=' + + validateAdType(data.adType) + + '&displayid=' + + (data.displayid ? encodeURIComponent(data.displayid) : '1') + + '&tagver=2.0.0' + + (data.targetid ? '&targetID=' + encodeURIComponent(data.targetid) : '') + + (option ? '&' + option : ''); + writeScript(global, url); +} + +/** + * URL encoding of query string + * @param {string} str + * @return {string} + */ +function encodeQueryValue(str) { + return str + .split('&') + .map((v) => { + const key = v.split('=')[0], + val = v.split('=')[1]; + return encodeURIComponent(key) + '=' + encodeURIComponent(val); + }) + .join('&'); +} +/** + * If adtype is "RECTANGLE", replace it with "RECT" + * @param {string} str + * @return {string} + */ +function validateAdType(str) { + if (str != null) { + const upperStr = encodeURIComponent(str.toUpperCase()); + if (upperStr === 'RECTANGLE') { + return 'RECT'; + } else { + return upperStr; + } + } + return 'FREE'; +} diff --git a/ads/adgeneration.md b/ads/vendors/adgeneration.md similarity index 90% rename from ads/adgeneration.md rename to ads/vendors/adgeneration.md index 1c23a724f6e93..be67717dbec1e 100644 --- a/ads/adgeneration.md +++ b/ads/vendors/adgeneration.md @@ -28,8 +28,8 @@ For details on the configuration semantics, please see [Ad Generation's document Supported parameters: -- `data-id` -- `data-targetid` -- `data-displayid` -- `data-adtype` -- `data-option` +- `data-id` +- `data-targetid` +- `data-displayid` +- `data-adtype` +- `data-option` diff --git a/ads/vendors/adglare.js b/ads/vendors/adglare.js new file mode 100644 index 0000000000000..5cf57c22268d1 --- /dev/null +++ b/ads/vendors/adglare.js @@ -0,0 +1,43 @@ +/** + * Copyright 2019 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adglare(global, data) { + validateData(data, ['host', 'zid'], ['keywords']); + + const adglareSpan = global.document.createElement('span'); + adglareSpan.id = 'zone' + data.zid; + global.document.getElementById('c').appendChild(adglareSpan); + + let url = + 'https://' + + data.host + + '.engine.adglare.net/?' + + data.zid + + '&ad&rnd=' + + Date.now() + + Math.random(); + if (data.keywords) { + url = url + '&keywords=' + data.keywords; + } + + writeScript(global, url); +} diff --git a/ads/adglare.md b/ads/vendors/adglare.md similarity index 95% rename from ads/adglare.md rename to ads/vendors/adglare.md index 00c0adc62f97e..1ad848ac4f6f6 100644 --- a/ads/adglare.md +++ b/ads/vendors/adglare.md @@ -37,9 +37,9 @@ For more information, read AdGlare's blog article about [AMP Ad Tags](https://ww ### Required parameters -- `data-host` -- `data-zid` +- `data-host` +- `data-zid` ### Optional parameters -- `data-keywords` +- `data-keywords` diff --git a/ads/vendors/adhese.js b/ads/vendors/adhese.js new file mode 100644 index 0000000000000..9cdff96194494 --- /dev/null +++ b/ads/vendors/adhese.js @@ -0,0 +1,100 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adhese(global, data) { + validateData(data, ['location', 'format', 'account', 'requestType']); + let targetParam = ''; + const gctx = global.context; + + if (data['targeting']) { + const targetList = data['targeting']; + for (const category in targetList) { + targetParam += encodeURIComponent(category); + const targets = targetList[category]; + for (let x = 0; x < targets.length; x++) { + targetParam += + encodeURIComponent(targets[x]) + (targets.length - 1 > x ? ';' : ''); + } + targetParam += '/'; + } + } + + if (gctx.consentSharedData) { + if ( + gctx.consentSharedData.consentStateValue && + gctx.consentSharedData.consentStateValue == 'accepted' + ) { + targetParam += 'tlall/'; + } + if ( + gctx.consentSharedData.consentString && + gctx.consentSharedData.consentString !== '' + ) { + targetParam += 'xt' + gctx.consentSharedData.consentString + '/'; + } + } + + targetParam += '?t=' + Date.now(); + writeScript( + window, + 'https://ads-' + + encodeURIComponent(data['account']) + + '.adhese.com/' + + encodeURIComponent(data['requestType']) + + '/sl' + + encodeURIComponent(data['location']) + + encodeURIComponent(data['position']) + + '-' + + encodeURIComponent(data['format']) + + '/' + + targetParam + ); + + const co = global.document.querySelector('#c'); + co.width = data['width']; + co.height = data['height']; + co.addEventListener('adhLoaded', getAdInfo.bind(null, global), false); +} + +/** + * @param {!Window} global + * @param {!Object} e + */ +function getAdInfo(global, e) { + if ( + e.detail.isReady && + e.detail.width == e.target.width && + e.detail.height == e.target.height + ) { + global.context.renderStart(); + } else if ( + e.detail.isReady && + (e.detail.width != e.target.width || e.detail.height != e.target.height) + ) { + global.context.renderStart({ + width: e.detail.width, + height: e.detail.height, + }); + } else { + global.context.noContentAvailable(); + } +} diff --git a/ads/adhese.md b/ads/vendors/adhese.md similarity index 88% rename from ads/adhese.md rename to ads/vendors/adhese.md index 5844ce2ecb79d..d59fcafaa45e9 100644 --- a/ads/adhese.md +++ b/ads/vendors/adhese.md @@ -59,24 +59,24 @@ For details on the configuration semantics, see the [Adhese website](https://www ### Required parameters -- `data-account` -- `data-request_type` -- `data-location` -- `data-position` -- `data-format` +- `data-account` +- `data-request_type` +- `data-location` +- `data-position` +- `data-format` ### Optional parameter The following optional parameter is supported via the 'json' attribute: -- `targeting` +- `targeting` ## User Consent Integration Adhese consent is linked to the window.context.consentSharedData object: -- consentStateValue which contains the consent state -- consentString which contains the IAB consent string +- consentStateValue which contains the consent state +- consentString which contains the IAB consent string If the consentStateValue is set to 'accepted', our consent parameter is set to 'all'. When avaiable, the consentString will always be send to the adserver. diff --git a/ads/vendors/adincube.js b/ads/vendors/adincube.js new file mode 100644 index 0000000000000..45641961dc8a6 --- /dev/null +++ b/ads/vendors/adincube.js @@ -0,0 +1,62 @@ +/** + * Copyright 2018 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adincube(global, data) { + validateData(data, ['adType', 'siteKey'], ['params']); + + let url = global.context.location.protocol; + url += '//tag.adincube.com/tag/1.0/next?'; + url += 'ad_type=' + encodeURIComponent(String(data['adType']).toUpperCase()); + url += '&ad_subtype=' + encodeURIComponent(data['width']); + url += 'x' + encodeURIComponent(data['height']); + url += '&site_key=' + encodeURIComponent(data['siteKey']); + url += '&r=' + encodeURIComponent(global.context.referrer); + url += '&h=' + encodeURIComponent(global.context.location.href); + url += '&c=amp'; + url += '&t=' + Date.now(); + + if (data['params']) { + url += parseParams(data['params']); + } + + loadScript(global, url); +} + +/** + * @param {string} data + * @return {string} + */ +function parseParams(data) { + try { + const params = JSON.parse(data); + let queryParams = ''; + for (const p in params) { + if (hasOwn(params, p)) { + queryParams += '&' + p + '=' + encodeURIComponent(params[p]); + } + } + return queryParams; + } catch (e) { + return ''; + } +} diff --git a/ads/adincube.md b/ads/vendors/adincube.md similarity index 91% rename from ads/adincube.md rename to ads/vendors/adincube.md index bb68deb2b32ca..23b7f2976d13f 100644 --- a/ads/adincube.md +++ b/ads/vendors/adincube.md @@ -60,9 +60,9 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-ad-type` - type of the ad -- `data-site-key` - unique key attached to a website +- `data-ad-type` - type of the ad +- `data-site-key` - unique key attached to a website ### Optional parameters -- `data-params` - additional config parameters +- `data-params` - additional config parameters diff --git a/ads/vendors/adition.js b/ads/vendors/adition.js new file mode 100644 index 0000000000000..ff71263c26b67 --- /dev/null +++ b/ads/vendors/adition.js @@ -0,0 +1,32 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adition(global, data) { + validateData(data, ['version']); + global.data = data; + writeScript( + global, + 'https://imagesrv.adition.com/js/amp/v' + + encodeURIComponent(data['version']) + + '.js' + ); +} diff --git a/ads/adition.md b/ads/vendors/adition.md similarity index 97% rename from ads/adition.md rename to ads/vendors/adition.md index 35c512d42c9c0..c2dcdd3535342 100644 --- a/ads/adition.md +++ b/ads/vendors/adition.md @@ -39,5 +39,5 @@ For semantics of configuration and examples, sign-in and see the [ADITION docume Supported parameters: -- `data-version` -- `data-wp_id` +- `data-version` +- `data-wp_id` diff --git a/ads/vendors/adman.js b/ads/vendors/adman.js new file mode 100644 index 0000000000000..8148a232c85fd --- /dev/null +++ b/ads/vendors/adman.js @@ -0,0 +1,35 @@ +/** + * Copyright 2016 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. + */ + +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adman(global, data) { + validateData(data, ['ws', 'host', 's'], []); + + 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); +} diff --git a/ads/adman.md b/ads/vendors/adman.md similarity index 88% rename from ads/adman.md rename to ads/vendors/adman.md index 8c10d8dd2f44c..29cd5f818ea0d 100644 --- a/ads/adman.md +++ b/ads/vendors/adman.md @@ -36,6 +36,6 @@ For details on the configuration semantics, please see [Adman documentation](htt ### Required parameters -- `data-ws` - Ad unit unique id -- `data-s` - Ad unit size -- `data-host` - SSL enabled Adman service domain +- `data-ws` - Ad unit unique id +- `data-s` - Ad unit size +- `data-host` - SSL enabled Adman service domain diff --git a/ads/vendors/admanmedia.js b/ads/vendors/admanmedia.js new file mode 100644 index 0000000000000..b2827f9415553 --- /dev/null +++ b/ads/vendors/admanmedia.js @@ -0,0 +1,37 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function admanmedia(global, data) { + validateData(data, ['id']); + + const encodedId = encodeURIComponent(data.id); + loadScript( + global, + `https://pub.admanmedia.com/go?id=${encodedId}`, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/admanmedia.md b/ads/vendors/admanmedia.md similarity index 88% rename from ads/admanmedia.md rename to ads/vendors/admanmedia.md index d26f501172a14..903b6ce181961 100644 --- a/ads/admanmedia.md +++ b/ads/vendors/admanmedia.md @@ -21,7 +21,7 @@ Please visit [www.admanmedia.com](http://www.admanmedia.com) for more details. ## Example ```html - + ``` ## Configuration @@ -30,4 +30,4 @@ For details on the configuration semantics, see [Admanmedia documentation](http: ### Required parameters -- `data-id` - Ad unit unique id +- `data-id` - Ad unit unique id diff --git a/ads/vendors/admixer.js b/ads/vendors/admixer.js new file mode 100644 index 0000000000000..66f3c3b74aed0 --- /dev/null +++ b/ads/vendors/admixer.js @@ -0,0 +1,44 @@ +/** + * Copyright 2018 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. + */ + +import {tryParseJson} from '../../src/core/types/object/json'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function admixer(global, data) { + validateData(data, ['zone'], ['sizes']); + /** + * @type {Object} + */ + const payload = { + imps: [], + referrer: window.context.referrer, + }; + const imp = { + params: { + zone: data.zone, + }, + }; + if (data.sizes) { + imp.sizes = tryParseJson(data.sizes); + } + payload.imps.push(imp); + const json = JSON.stringify(/** @type {JsonObject} */ (payload)); + writeScript(global, 'https://inv-nets.admixer.net/ampsrc.js?data=' + json); +} diff --git a/ads/admixer.md b/ads/vendors/admixer.md similarity index 96% rename from ads/admixer.md rename to ads/vendors/admixer.md index 33a845761a5a8..0211a527ba425 100644 --- a/ads/admixer.md +++ b/ads/vendors/admixer.md @@ -35,8 +35,8 @@ For configuration semantics, see [Admixer documentation](http://docs.admixer.net ### Required parameters -- `data-zone` +- `data-zone` ### Optional parameters -- `data-sizes` +- `data-sizes` diff --git a/ads/vendors/adnuntius.js b/ads/vendors/adnuntius.js new file mode 100644 index 0000000000000..23104f86c931b --- /dev/null +++ b/ads/vendors/adnuntius.js @@ -0,0 +1,35 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adnuntius(global, data) { + validateData(data, ['auId']); + + loadScript(global, 'https://cdn.adnuntius.com/adn.js', () => { + global.adn = global.adn || {}; + global.adn.calls = global.adn.calls || []; + global.adn.calls.push(() => { + global.adn.request({ + amp: {data}, + }); + }); + }); +} diff --git a/ads/vendors/adnuntius.md b/ads/vendors/adnuntius.md new file mode 100644 index 0000000000000..b5e57a2dd8374 --- /dev/null +++ b/ads/vendors/adnuntius.md @@ -0,0 +1,63 @@ + + +# Adnuntius + +Adnuntius provides a wide array of features alongside their AMP integration. + +For configuration details and to generate your tags, please refer to +[the Adnuntius documentation](https://docs.adnuntius.com) or [your account](https://admin.adnuntius.com). + +## Simplest Call + +```html + + +``` + +## Call with All Options Enabled + +```html + + +``` + +## Configuration + +### Required parameters + +- `width`: Width of the ad unit that will be filled +- `height`: Height of the ad unit that will be filled +- `type`: Ensures the ad request goes via Adnuntius +- `data-au-id`: Specify your ad unit ID + +### Optional parameters + +For the remaining optional parameters listed above, refer to the +[Adnuntius documentation](https://docs.adnuntius.com/adnuntius-advertising/requesting-ads/intro/adn-request) for the +relevant information. diff --git a/ads/vendors/adocean.js b/ads/vendors/adocean.js new file mode 100644 index 0000000000000..787ed70fb64f3 --- /dev/null +++ b/ads/vendors/adocean.js @@ -0,0 +1,350 @@ +/** + * Copyright 2015 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. + */ + +import {CONSENT_POLICY_STATE} from '../../src/core/constants/consent-state'; +import {computeInMasterFrame, validateData, writeScript} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +/** + * @const {Object} + */ +const ADO_JS_PATHS = { + 'sync': '/files/js/ado.js', + 'buffered': '/files/js/ado.FIF.test.js', +}; + +/** + * @param {string} str + * @return {boolean} + */ +function isFalseString(str) { + return /^(?:false|off)?$/i.test(str); +} + +/** + * @param {string} mode + * @param {!Window} global + * @param {boolean} consent + */ +function setupAdoConfig(mode, global, consent) { + if (global['ado']) { + const config = { + mode: mode == 'sync' ? 'old' : 'new', + protocol: 'https:', + fif: { + enabled: mode != 'sync', + }, + consent, + }; + + global['ado']['config'](config); + } +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function setupPreview(global, data) { + if (global.ado && data.aoPreview && !isFalseString(data.aoPreview)) { + global.ado.preview({ + enabled: true, + }); + } +} + +/** + * @param {string} str + * @return {Object|undefined} + * @throws {SyntaxError} + */ +function parseJSONObj(str) { + return str.match(/^\s*{/) ? parseJson(str) : undefined; +} + +/** + * @param {string} keys + * @return {string|undefined} + */ +function buildKeys(keys) { + return keys || undefined; +} + +/** + * @param {string} vars + * @return {Object|string|undefined} + */ +function buildVars(vars) { + try { + return parseJSONObj(vars); + } catch (e) { + return vars || undefined; + } +} + +/** + * @param {string} clusters + * @return {Object|undefined} + */ +function buildClusters(clusters) { + try { + return parseJSONObj(clusters); + } catch (e) { + // return undefined + } +} + +/** @type {number} */ +let runSyncCount = 0; + +/** + * @param {!Window} global + * @param {function()} cb + */ +function runSync(global, cb) { + global['__aoPrivFnct' + ++runSyncCount] = cb; + global.document.write( + // eslint-disable-next-line no-useless-concat + '<' + 'script>__aoPrivFnct' + runSyncCount + '();<' + '/script>' + ); +} + +/** + * @param {string} mode + * @param {!Window} global + * @param {!Object} data + */ +function appendPlacement(mode, global, data) { + const doc = global.document; + const placement = doc.createElement('div'); + placement.id = data['aoId']; + + const dom = doc.getElementById('c'); + dom.appendChild(placement); + + const config = { + id: data['aoId'], + server: data['aoEmitter'], + keys: buildKeys(data['aoKeys']), + vars: buildVars(data['aoVars']), + clusters: buildClusters(data['aoClusters']), + }; + + if (global.ado) { + if (mode == 'sync') { + runSync(global, function () { + global['ado']['placement'](config); + }); + + runSync(global, function () { + if (!config['_hasAd']) { + window.context.noContentAvailable(); + } + }); + } else { + global['ado']['onAd'](data['aoId'], function (isAd) { + if (!isAd) { + window.context.noContentAvailable(); + } + }); + global['ado']['placement'](config); + } + } +} + +/** + * @param {string} masterId + * @param {!Object} data + * @param {!Window} global + * @param {Function} callback + */ +function executeMaster(masterId, data, global, callback) { + const config = { + id: masterId, + server: data['aoEmitter'], + keys: buildKeys(data['aoKeys']), + vars: buildVars(data['aoVars']), + clusters: buildClusters(data['aoClusters']), + }; + + if (global['ado']) { + global['ado']['onEmit']((masterId, instanceId, codes) => { + callback(codes); + }); + + global['ado']['master'](config); + } +} + +/** + * + * @param {string} masterId + * @param {!Object} data + * @param {!Window} global + * @param {Function} callback + */ +function requestCodes(masterId, data, global, callback) { + const slaveId = data['aoId']; + + computeInMasterFrame( + global, + 'ao-master-exec', + (done) => { + executeMaster(masterId, data, global, (codes) => done(codes)); + }, + (codes) => { + const creative = codes[slaveId]; + if (codes[slaveId + '_second_phase']) { + creative['code'] += '\n' + codes[slaveId + '_second_phase']['code']; + } + callback(creative); + } + ); +} + +class AdoBuffer { + /** + * + * @param {Object} container + * @param {!Window} global + */ + constructor(container, global) { + this.container = container; + this.global = global; + this.callback = null; + } + + /** + * + * @param {Function} callback + */ + render(callback) { + this.callback = callback; + + if (this.global.document.readyState === 'loading') { + this.global.document.addEventListener( + 'DOMContentLoaded', + this._init.bind(this) + ); + } else { + this._init(); + } + } + + /** + */ + _init() { + const ado = this.global['ado']; + const gao = this.global['gao']; + + if (ado['busy'] || (typeof gao !== 'undefined' && gao['busy'])) { + ado['queue'].unshift(this._execute.bind(this)); + } else { + this._execute(); + } + } + + /** + */ + _execute() { + const adoElement = new this.global['AdoElement']({ + 'id': this.container.id, + 'orgId': this.container.id, + 'clearId': this.container.id, + '_isBuffer': true, + }); + this.global['AdoElems'] = this.global['AdoElems'] || []; + this.global['AdoElems'].push(adoElement); + adoElement['getDOMElement'](); + adoElement['initBuffor'](); + this.global['ado']['elems'][this.container.id] = adoElement; + + this.callback(adoElement); + + adoElement['rewriteBuffor'](); + adoElement['dispatch'](); + } +} + +/** + * + * @param {string} slaveId + * @param {!Object} config + * @param {!Window} global + */ +function executeSlave(slaveId, config, global) { + const doc = global.document; + const placement = doc.createElement('div'); + placement['id'] = slaveId; + + const dom = doc.getElementById('c'); + dom.appendChild(placement); + + if (global['ado']) { + if (!config || config['isEmpty']) { + global.context.noContentAvailable(); + } else { + const buffer = new AdoBuffer(placement, global); + buffer.render(() => { + new Function(config['sendHitsDef'] + config['code'])(); + }); + } + } +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adocean(global, data) { + validateData( + data, + ['aoEmitter', 'aoId'], + ['aoMode', 'aoPreview', 'aoKeys', 'aoVars', 'aoClusters', 'aoMaster'] + ); + + const masterId = data['aoMaster']; + const mode = data['aoMode'] !== 'sync' || masterId ? 'buffered' : 'sync'; + 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; + + writeScript(global, adoUrl, () => { + setupAdoConfig(mode, global, consent); + setupPreview(global, data); + + if (masterId) { + const ado = global['ado']; + if (ado && ado['features'] && ado['features']['passback']) { + ado['features']['passback'] = false; + } + + requestCodes(masterId, data, global, (codes) => { + executeSlave(data['aoId'], codes, global); + }); + } else { + appendPlacement(mode, global, data); + } + }); +} diff --git a/ads/vendors/adocean.md b/ads/vendors/adocean.md new file mode 100644 index 0000000000000..cde6a61ff77d7 --- /dev/null +++ b/ads/vendors/adocean.md @@ -0,0 +1,90 @@ + + +# AdOcean + +## Example + +##### Single placement: + +```html + + +``` + +##### Master-Slave: + +```html + + + + + +``` + +Do not define different values for slaves within one master for paramerters: data-ao-keys, data-ao-vars and data-block-on-consent. Otherwise, the behavior will be non-deterministic. + +## Configuration + +For details on the configuration semantics, see [AdOcean documentation](http://www.adocean-global.com). + +### Required parameters + +- `data-ao-id` - Ad unit unique id +- `data-ao-emitter` - Ad server hostname + +### Optional parameters + +- `data-ao-mode` - sync|buffered - processing mode +- `data-ao-preview` - true|false - whether livepreview is enabled +- `data-ao-keys` - additional configuration, see adserver documentation, do not define different values for slaves within one master +- `data-ao-vars` - additional configuration, see adserver documentation, do not define different values for slaves within one master +- `data-ao-clusters` - additional configuration, see adserver documentation +- `data-ao-master` - unique id of master placement + +## User Consent Integration + +When [user consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required. AdOcean ad approaches user consent in the following ways: + +- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN`: Serve a non-personalized ad to the user. diff --git a/ads/vendors/adop.js b/ads/vendors/adop.js new file mode 100755 index 0000000000000..ca1c7e63d678b --- /dev/null +++ b/ads/vendors/adop.js @@ -0,0 +1,27 @@ +/** + * Copyright 2019 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adop(global, data) { + validateData(data, ['z']); + global.adop = data; + writeScript(global, 'https://compass.adop.cc/assets/js/adop/amp.js'); +} diff --git a/ads/adop.md b/ads/vendors/adop.md similarity index 98% rename from ads/adop.md rename to ads/vendors/adop.md index 15bfac291b9d0..5f5b81b3fe630 100755 --- a/ads/adop.md +++ b/ads/vendors/adop.md @@ -36,4 +36,4 @@ For configuration details and to generate your tags, please contact http://adop. ### Required parameters -- `data-z` +- `data-z` diff --git a/ads/vendors/adpicker.js b/ads/vendors/adpicker.js new file mode 100644 index 0000000000000..4faf2c90a8a63 --- /dev/null +++ b/ads/vendors/adpicker.js @@ -0,0 +1,33 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adpicker(global, data) { + validateData(data, ['ph']); + const url = + 'https://cdn.adpicker.net' + + '/ads/main.js?et=amp' + + '&ph=' + + encodeURIComponent(data.ph) + + '&cb=' + + Math.floor(89999999 * Math.random() + 10000000); + writeScript(global, url); +} diff --git a/ads/adpicker.md b/ads/vendors/adpicker.md similarity index 96% rename from ads/adpicker.md rename to ads/vendors/adpicker.md index affb6376affae..56aaada84dbaf 100644 --- a/ads/adpicker.md +++ b/ads/vendors/adpicker.md @@ -29,4 +29,4 @@ For details on the configuration semantics, visit [adpicker.net](https://adpicke ### Required parameters -- `data-ph`: the placement id +- `data-ph`: the placement id diff --git a/ads/vendors/adplugg.js b/ads/vendors/adplugg.js new file mode 100644 index 0000000000000..15609b130da93 --- /dev/null +++ b/ads/vendors/adplugg.js @@ -0,0 +1,76 @@ +/** + * Copyright 2018 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, validateData} from '../../3p/3p'; + +/** + * Make an AdPlugg iframe. + * @param {!Window} global + * @param {!Object} data + */ +export function adplugg(global, data) { + // Load ad.js + loadScript(global, 'https://www.adplugg.com/serve/js/ad.js'); + + // Validate the amp-ad attributes. + validateData( + data, + ['accessCode', 'width', 'height'], //required + ['zone'] //optional + ); + + // Get the amp wrapper element. + const ampwrapper = global.document.getElementById('c'); + + // Build and append the ad tag. + const adTag = global.document.createElement('div'); + adTag.setAttribute('class', 'adplugg-tag'); + adTag.setAttribute('data-adplugg-access-code', data['accessCode']); + if (data['zone']) { + adTag.setAttribute('data-adplugg-zone', data['zone']); + } + ampwrapper.appendChild(adTag); + + // Get a handle on the AdPlugg SDK. + global.AdPlugg = global.AdPlugg || []; + const {AdPlugg} = global; + + // Register event listeners (via async wrapper). + AdPlugg.push(function () { + const {AdPlugg} = global; + // Register the renderStart event listener. + AdPlugg.on(adTag, 'adplugg:renderStart', function (event) { + // Create the opt_data object. + const optData = {}; + if (hasOwn(event, 'width')) { + optData.width = event.width; + } + if (hasOwn(event, 'height')) { + optData.height = event.height; + } + global.context.renderStart(optData); + }); + + // Register the noContentAvailable event listener. + AdPlugg.on(adTag, 'adplugg:noContentAvailable', function () { + global.context.noContentAvailable(); + }); + }); + + // Fill the tag. + AdPlugg.push({'command': 'run'}); +} diff --git a/ads/adplugg.md b/ads/vendors/adplugg.md similarity index 89% rename from ads/adplugg.md rename to ads/vendors/adplugg.md index ea46980d0d632..f23e937c46f26 100644 --- a/ads/adplugg.md +++ b/ads/vendors/adplugg.md @@ -35,8 +35,8 @@ For additional info, see AdPlugg's [AMP Ad Help](https://www.adplugg.com/support ### Required parameters -- `data-access-code`: your AdPlugg access code. +- `data-access-code`: your AdPlugg access code. ### Optional parameters -- `data-zone`: your AdPlugg zone machine name. Recommended. +- `data-zone`: your AdPlugg zone machine name. Recommended. diff --git a/ads/vendors/adpon.js b/ads/vendors/adpon.js new file mode 100644 index 0000000000000..ea8a401914905 --- /dev/null +++ b/ads/vendors/adpon.js @@ -0,0 +1,29 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adpon(global, data) { + validateData(data, ['fid'], ['debugScript']); + + global._adpon = {fid: data['fid']}; + + writeScript(global, data['debugScript'] || 'https://ad.adpon.jp/amp.js'); +} diff --git a/ads/adpon.md b/ads/vendors/adpon.md similarity index 98% rename from ads/adpon.md rename to ads/vendors/adpon.md index f6eb815a69ec8..35089bde34c3f 100644 --- a/ads/adpon.md +++ b/ads/vendors/adpon.md @@ -26,4 +26,4 @@ limitations under the License. Supported parameters: -- `data-fid` +- `data-fid` diff --git a/ads/vendors/adpushup.js b/ads/vendors/adpushup.js new file mode 100644 index 0000000000000..fd4cf3a556113 --- /dev/null +++ b/ads/vendors/adpushup.js @@ -0,0 +1,51 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adpushup(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://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 + ); + } + ); + } + ); +} diff --git a/ads/vendors/adpushup.md b/ads/vendors/adpushup.md new file mode 100644 index 0000000000000..9cf8216742466 --- /dev/null +++ b/ads/vendors/adpushup.md @@ -0,0 +1,34 @@ + + +# Adpushup + +## Example + +```html + + +``` + +## Configuration + +- `data-slot`: The GAM ad unit path corresponding to the ad position +- `siteid`: Adpushup Site ID +- `totalAmpSlots`: Total adpushup amp-ad slots on the page diff --git a/ads/vendors/adreactor.js b/ads/vendors/adreactor.js new file mode 100644 index 0000000000000..1a1857ad8ee0b --- /dev/null +++ b/ads/vendors/adreactor.js @@ -0,0 +1,40 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adreactor(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['zid', 'pid', 'custom3']); + const url = + 'https://adserver.adreactor.com' + + '/servlet/view/banner/javascript/zone?' + + 'zid=' + + encodeURIComponent(data.zid) + + '&pid=' + + encodeURIComponent(data.pid) + + '&custom3=' + + encodeURIComponent(data.custom3) + + '&random=' + + Math.floor(89999999 * Math.random() + 10000000) + + '&millis=' + + Date.now(); + writeScript(global, url); +} diff --git a/ads/adreactor.md b/ads/vendors/adreactor.md similarity index 94% rename from ads/adreactor.md rename to ads/vendors/adreactor.md index a8626f0971d81..54ba981a68ad6 100644 --- a/ads/adreactor.md +++ b/ads/vendors/adreactor.md @@ -36,6 +36,6 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-pid` -- `data-zid` -- `data-custom3` +- `data-pid` +- `data-zid` +- `data-custom3` diff --git a/ads/vendors/adsensor.js b/ads/vendors/adsensor.js new file mode 100644 index 0000000000000..cefcfdc413ae1 --- /dev/null +++ b/ads/vendors/adsensor.js @@ -0,0 +1,27 @@ +/** + * Copyright 2019 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. + */ + +import {writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + */ +export function adsensor(global) { + writeScript( + global, + 'https://wfpscripts.webspectator.com/amp/adsensor-amp.js' + ); +} diff --git a/ads/adsensor.md b/ads/vendors/adsensor.md similarity index 100% rename from ads/adsensor.md rename to ads/vendors/adsensor.md diff --git a/ads/vendors/adservsolutions.js b/ads/vendors/adservsolutions.js new file mode 100644 index 0000000000000..c54f751774372 --- /dev/null +++ b/ads/vendors/adservsolutions.js @@ -0,0 +1,33 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adservsolutions(global, data) { + validateData(data, [], ['client', 'zid']); + global['ABNS'] = + global['ABNS'] || + function () { + (global['ABNSl'] = global['ABNSl'] || []).push(arguments); + }; + global['ABNSh'] = data.client; + writeScript(global, 'https://cdn.' + global['ABNSh'] + '/libs/b.js'); + global['ABNS']('c', {id: data.zid + '&o=a'}); +} diff --git a/ads/adservsolutions.md b/ads/vendors/adservsolutions.md similarity index 93% rename from ads/adservsolutions.md rename to ads/vendors/adservsolutions.md index 44ca351e89fd8..0fc2459a4794c 100644 --- a/ads/adservsolutions.md +++ b/ads/vendors/adservsolutions.md @@ -35,5 +35,5 @@ For details on the configuration semantics, please contact the ad network. Supported parameters: -- `data-client`: ad server client -- `data-zid`: ad zone id +- `data-client`: ad server client +- `data-zid`: ad zone id diff --git a/ads/vendors/adsloom.js b/ads/vendors/adsloom.js new file mode 100644 index 0000000000000..b490a8dc0ace7 --- /dev/null +++ b/ads/vendors/adsloom.js @@ -0,0 +1,34 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adsloom(global, data) { + validateData(data, ['widgetId']); + global._adsLoom = global._adsLoom || { + widgetId: data['widgetId'], + clientId: global.context.clientId, + sourceUrl: global.context.sourceUrl, + }; + loadScript( + global, + 'https://adsloomwebservices.adsloom.com/scripts/amp-loader.js' + ); +} diff --git a/ads/adsloom.md b/ads/vendors/adsloom.md similarity index 97% rename from ads/adsloom.md rename to ads/vendors/adsloom.md index 623d01e562bbe..81461bcd12417 100644 --- a/ads/adsloom.md +++ b/ads/vendors/adsloom.md @@ -35,4 +35,4 @@ For details on the configuration, please contact AdsLoom. ### Required parameters -- `widget-id`: Widget ID +- `widget-id`: Widget ID diff --git a/ads/vendors/adsnative.js b/ads/vendors/adsnative.js new file mode 100644 index 0000000000000..6dafdffba343c --- /dev/null +++ b/ads/vendors/adsnative.js @@ -0,0 +1,65 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adsnative(global, data) { + try { + validateData(data, ['anapiid'], ['ankv', 'ancat', 'antid']); + } catch (e) { + validateData(data, ['annid', 'anwid'], ['ankv', 'ancat', 'antid']); + } + + // convert string to object + let actualkv = undefined; + if (data.ankv) { + actualkv = {}; + const arraykv = data.ankv.split(','); + for (const k in arraykv) { + const kv = arraykv[k].split(':'); + actualkv[kv.pop()] = kv.pop(); + } + } + + // convert string to array + const actualcat = data.ancat ? data.ancat.split(',') : undefined; + + // populate settings + global._AdsNativeOpts = { + apiKey: data.anapiid, + networkKey: data.annid, + nativeAdElementId: 'adsnative_ampad', + currentPageUrl: global.context.location.href, + widgetId: data.anwid, + templateKey: data.antid, + categories: actualcat, + keyValues: actualkv, + amp: true, + }; + + // drop ad placeholder div + const ad = global.document.createElement('div'); + const ampwrapper = global.document.getElementById('c'); + ad.id = global._AdsNativeOpts.nativeAdElementId; + ampwrapper.appendChild(ad); + + // load renderjs + writeScript(global, 'https://static.adsnative.com/static/js/render.v1.js'); +} diff --git a/ads/vendors/adsnative.md b/ads/vendors/adsnative.md new file mode 100644 index 0000000000000..164d02e19a91c --- /dev/null +++ b/ads/vendors/adsnative.md @@ -0,0 +1,44 @@ + + +# AdsNative + +## Example + +```html + + +``` + +## Configuration + +For configuration details, see [AdsNative's documentation](http://dev.adsnative.com). + +### Required parameters + +- `width`: required by amp +- `height`: required by amp +- `data-anapiid`: the api id may be used instead of network and widget id +- `data-annid`: the network id must be paired with widget id +- `data-anwid`: the widget id must be paired with network id + +### Optional parameters + +- `data-anapiid`: the api id +- `data-anwid`: the widget id +- `data-antid`: the template id +- `data-ancat`: a comma separated list of categories +- `data-ankv`: a list of key value pairs in the format `"key1:value1, key2:value2"`. diff --git a/ads/vendors/adspeed.js b/ads/vendors/adspeed.js new file mode 100644 index 0000000000000..faf5abe56eff3 --- /dev/null +++ b/ads/vendors/adspeed.js @@ -0,0 +1,35 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adspeed(global, data) { + validateData(data, ['zone', 'client']); + + const url = + 'https://g.adspeed.net/ad.php?do=amphtml&zid=' + + data.zone + + '&oid=' + + data.client + + '&cb=' + + Math.random(); + + writeScript(global, url); +} diff --git a/ads/adspeed.md b/ads/vendors/adspeed.md similarity index 94% rename from ads/adspeed.md rename to ads/vendors/adspeed.md index ae893273a6e33..c3c72ee6f4ac4 100644 --- a/ads/adspeed.md +++ b/ads/vendors/adspeed.md @@ -37,5 +37,5 @@ For configuration semantics, please see [AdSpeed documentation](https://www.adsp Supported parameters: -- `data-zone`: the zone ID -- `data-client`: the publisher ID +- `data-zone`: the zone ID +- `data-client`: the publisher ID diff --git a/ads/vendors/adspirit.js b/ads/vendors/adspirit.js new file mode 100644 index 0000000000000..a5379ae1b51db --- /dev/null +++ b/ads/vendors/adspirit.js @@ -0,0 +1,39 @@ +/** + * Copyright 2015 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. + */ + +import {setStyles} from '../../src/style'; +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adspirit(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['asmParams', 'asmHost']); + const i = global.document.createElement('ins'); + i.setAttribute('data-asm-params', data['asmParams']); + i.setAttribute('data-asm-host', data['asmHost']); + i.setAttribute('class', 'asm_async_creative'); + setStyles(i, { + display: 'inline-block', + 'text-align': 'left', + }); + global.document.getElementById('c').appendChild(i); + const s = global.document.createElement('script'); + s.src = 'https://' + data['asmHost'] + '/adasync.js'; + global.document.body.appendChild(s); +} diff --git a/ads/adspirit.md b/ads/vendors/adspirit.md similarity index 95% rename from ads/adspirit.md rename to ads/vendors/adspirit.md index 823f9ee9817ea..9eedea6b642d4 100644 --- a/ads/adspirit.md +++ b/ads/vendors/adspirit.md @@ -35,5 +35,5 @@ For details on the configuration semantics, please see [AdSpirit's documentation Supported parameters: -- `data-asm-params` -- `data-asm-host` +- `data-asm-params` +- `data-asm-host` diff --git a/ads/vendors/adstir.js b/ads/vendors/adstir.js new file mode 100644 index 0000000000000..a147567d8e4b0 --- /dev/null +++ b/ads/vendors/adstir.js @@ -0,0 +1,39 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adstir(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['appId', 'adSpot']); + + const v = '4.0'; + + const d = global.document.createElement('div'); + d.setAttribute('class', 'adstir-ad-async'); + d.setAttribute('data-ver', v); + d.setAttribute('data-app-id', data['appId']); + d.setAttribute('data-ad-spot', data['adSpot']); + d.setAttribute('data-amp', true); + d.setAttribute('data-origin', global.context.location.href); + global.document.getElementById('c').appendChild(d); + + loadScript(global, 'https://js.ad-stir.com/js/adstir_async.js'); +} diff --git a/ads/adstir.md b/ads/vendors/adstir.md similarity index 96% rename from ads/adstir.md rename to ads/vendors/adstir.md index ada9514c2df91..7695ea32bf458 100644 --- a/ads/adstir.md +++ b/ads/vendors/adstir.md @@ -35,5 +35,5 @@ For configuration details and to generate your tags, please refer to [your publi Supported parameters: -- `data-app-id` -- `data-ad-spot` +- `data-app-id` +- `data-ad-spot` diff --git a/ads/vendors/adstyle.js b/ads/vendors/adstyle.js new file mode 100644 index 0000000000000..4dddb230c2c80 --- /dev/null +++ b/ads/vendors/adstyle.js @@ -0,0 +1,46 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adstyle(global, data) { + validateData(data, ['widget']); + + global._adstyle = global._adstyle || { + viewId: global.context.pageViewId, + widgetIds: [], + referrer: global.context.referrer, + url: global.context.canonicalUrl, + source: global.context.sourceUrl, + }; + + global._adstyle.widgetIds.push(data.widget); + + const url = 'https://widgets.ad.style/amp.js'; + + window.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + window['intersectionRect' + data.widget] = c.intersectionRect; + window['boundingClientRect' + data.widget] = c.boundingClientRect; + }); + }); + + loadScript(global, url); +} diff --git a/ads/adstyle.md b/ads/vendors/adstyle.md similarity index 98% rename from ads/adstyle.md rename to ads/vendors/adstyle.md index 0639ab4b3898a..20b2c23dd1872 100644 --- a/ads/adstyle.md +++ b/ads/vendors/adstyle.md @@ -50,4 +50,4 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-widget` +- `data-widget` diff --git a/ads/vendors/adtech.js b/ads/vendors/adtech.js new file mode 100644 index 0000000000000..7d9ffc6bf49d7 --- /dev/null +++ b/ads/vendors/adtech.js @@ -0,0 +1,65 @@ +/** + * Copyright 2015 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. + */ + +import { + validateData, + validateSrcContains, + validateSrcPrefix, + writeScript, +} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adtech(global, data) { + const adsrc = data.src; + if (typeof adsrc != 'undefined') { + validateSrcPrefix('https:', adsrc); + validateSrcContains('/addyn/', adsrc); + writeScript(global, adsrc); + } else { + validateData( + data, + ['atwmn', 'atwdiv'], + [ + 'atwco', + 'atwheight', + 'atwhtnmat', + 'atwmoat', + 'atwnetid', + 'atwothat', + 'atwplid', + 'atwpolar', + 'atwsizes', + 'atwwidth', + ] + ); + global.atwco = data.atwco; + global.atwdiv = data.atwdiv; + global.atwheight = data.atwheight; + global.atwhtnmat = data.atwhtnmat; + global.atwmn = data.atwmn; + global.atwmoat = data.atwmoat; + global.atwnetid = data.atwnetid; + global.atwothat = data.atwothat; + global.atwplid = data.atwplid; + global.atwpolar = data.atwpolar; + global.atwsizes = data.atwsizes; + global.atwwidth = data.atwwidth; + writeScript(global, 'https://s.aolcdn.com/os/ads/adsWrapper3.js'); + } +} diff --git a/ads/vendors/adtech.md b/ads/vendors/adtech.md new file mode 100644 index 0000000000000..c081bd43e4f32 --- /dev/null +++ b/ads/vendors/adtech.md @@ -0,0 +1,55 @@ + + +# AdTech + +## Example + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +### Required parameters: + +- `data-atwMN` - magic number for the ad spot +- `data-atwDiv` - div name of the ad spot; can be class or id + +### Optional parameters: + +- `data-atwPlId` - placement ID (instead of Magic Number) +- `data-atwOthAT` - generic var to set key/value pairs to send with the ad call; accepts multiple values in a semi-colon delimited list +- `data-atwCo` - override default country code +- `data-atwHtNmAT` - override ad host name +- `data-atwNetId` - network ID +- `data-atwWidth` - ad width (use with atwHeight only if the ad is not 300x250) +- `data-atwHeight`- ad height (use with atwWidth only if the ad is not 300x250) +- `data-atwSizes` - this overrides atwWidth/atwHeight; use this to create a comma-separated list of possible ad sizes +- 'data-atwPolar' - set to "1" to enable Polar.me ad in the ad spot + +### Direct URL call: + +- `src` - Value must start with `https:` and contain `/addyn/`. This should only be used in cases where a direct ad call is being used rather than a magic number (MN). diff --git a/ads/vendors/adtelligent.js b/ads/vendors/adtelligent.js new file mode 100644 index 0000000000000..9f1efe21e6623 --- /dev/null +++ b/ads/vendors/adtelligent.js @@ -0,0 +1,79 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adtelligent(global, data) { + validateData( + data, + [], + ['source', 'floor', 'hbmpPubId', 'hbmpSiteId', 'hbmpUnitId'] + ); + + const doc = global.document; + const container = doc.createElement('div'); + const ctx = window.context; + doc.body.appendChild(container); + if (data.source) { + const url = + `https://s.adtelligent.com/?floor=${data.floor || 0}` + + `&content_page_url=${encodeURIComponent(ctx.location)}` + + `&width=${data.width}` + + `&height=${data.height}` + + `&cb=${Date.now()}` + + `&aid=${data.source}`; + container.id = 'PDS' + data.source; + loadScript(global, url, () => { + ctx.renderStart({ + width: data.width, + height: data.height, + }); + }); + } else { + const HTML_ELEMENT_ID = 'adt-placement'; + const vpbSrc = `//player.adtelligent.com/prebid/wrapper_hb_${data['hbmpPubId']}_${data['hbmpSiteId']}.js`; + const pbSrc = vpbSrc.replace('wrapper_hb', 'hb'); + container.id = HTML_ELEMENT_ID; + global.vpb = window.vpb || { + cmd: [], + fastLoad: true, + amp: true, + startAuction: 1, + }; + + loadScript(global, vpbSrc); + loadScript(global, pbSrc); + + global.vpb.cmd.push(function () { + global.vpb.startAuction({ + code: HTML_ELEMENT_ID, + adUnitId: parseInt(data['hbmpUnitId'], 10), + sizes: [[data.width, data.height]], + render: true, + onEnd(winner) { + ctx.renderStart({ + width: winner.width, + height: winner.height, + }); + }, + }); + }); + } +} diff --git a/ads/vendors/adtelligent.md b/ads/vendors/adtelligent.md new file mode 100644 index 0000000000000..8ecae7dca1868 --- /dev/null +++ b/ads/vendors/adtelligent.md @@ -0,0 +1,59 @@ + + +# Adtelligent + +## Examples + +### For DSP/SSP Clients + +``` + + +``` + +### For HBMP Clients + +``` + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +### Required parameters + +For DSP/SSP clients + +- `data-source` + +For HBMP clients + +- `data-hbmp-site-id` +- `data-hbmp-pub-id` +- `data-hbmp-unit-id` + +### Optional parameters + +- `data-floor` diff --git a/ads/vendors/adthrive.js b/ads/vendors/adthrive.js new file mode 100644 index 0000000000000..e25c61023c35e --- /dev/null +++ b/ads/vendors/adthrive.js @@ -0,0 +1,31 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adthrive(global, data) { + validateData(data, ['siteId', 'adUnit'], ['sizes']); + loadScript( + global, + 'https://ads.adthrive.com/sites/' + + encodeURIComponent(data.siteId) + + '/amp.min.js' + ); +} diff --git a/ads/adthrive.md b/ads/vendors/adthrive.md similarity index 94% rename from ads/adthrive.md rename to ads/vendors/adthrive.md index d0fb751a8ad26..b43d2e116b87a 100644 --- a/ads/adthrive.md +++ b/ads/vendors/adthrive.md @@ -65,8 +65,8 @@ Your site must be approved and active with [AdThrive](http://www.adthrive.com) p ### Required parameters -- `data-site-id` - Your AdThrive site id. -- `data-ad-unit` - AdThrive provided ad unit. +- `data-site-id` - Your AdThrive site id. +- `data-ad-unit` - AdThrive provided ad unit. ### Optional parameters diff --git a/ads/vendors/adunity.js b/ads/vendors/adunity.js new file mode 100644 index 0000000000000..6776afba1d261 --- /dev/null +++ b/ads/vendors/adunity.js @@ -0,0 +1,113 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adunity(global, data) { + const doc = global.document; + + validateData( + data, + ['auAccount', 'auSite'], + [ + 'auSection', + 'auZone', + 'auDemo', + 'auIsdemo', + 'auAd', + 'auOrder', + 'auSegment', + 'auOptions', + 'auSources', + 'auAds', + 'auTriggerFn', + 'auTriggerVal', + 'auCallbackVal', + 'auCallbackFn', + 'auPassbackFn', + 'auPassbackVal', + 'auClick', + 'auDual', + 'auImpression', + 'auVideo', + ] + ); + + //prepare tag structure + const tag = doc.createElement('div'); + tag.classList.add('au-tag'); + tag.setAttribute('data-au-width', data['width']); + tag.setAttribute('data-au-height', data['height']); + + if (data != null) { + for (const key in data) { + //skip not valid attributes + if (!hasOwnProperty.call(data, key)) { + continue; + } + + //skip if attribute is type or ampSlotIndex + if (key.startsWith('type') || key.startsWith('ampSlotIndex')) { + continue; + } + + if (key.startsWith('au')) { + if (key == 'auVideo') { + tag.setAttribute('class', 'au-video'); + } else { + const auKey = key.substring(2).toLowerCase(); + tag.setAttribute('data-au-' + auKey, data[key]); + } + } + } + } + + //make sure is executed only once + let libAd = false; + + //execute tag only if in view + const inViewCb = global.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + if (!libAd && c.intersectionRect.height > data['height'] / 2) { + libAd = true; + inViewCb(); + renderTags(global, data); + } + }); + }); + const tagPh = doc.getElementById('c'); + tagPh.appendChild(tag); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function renderTags(global, data) { + if (data == null) { + return; + } + + global.context.renderStart({ + width: data.width, + height: data.height, + }); + loadScript(global, 'https://content.adunity.com/aulib.js'); +} diff --git a/ads/adunity.md b/ads/vendors/adunity.md similarity index 100% rename from ads/adunity.md rename to ads/vendors/adunity.md diff --git a/ads/vendors/aduptech.js b/ads/vendors/aduptech.js new file mode 100644 index 0000000000000..1878ccd0450d9 --- /dev/null +++ b/ads/vendors/aduptech.js @@ -0,0 +1,103 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * ID for the html container + * + * @const {string} + */ +export const ADUPTECH_ELEMENT_ID = 'aduptech'; + +/** + * URL to the AdUp Technology javascript api + * + * @const {string} + */ +export const ADUPTECH_API_URL = 'https://s.d.adup-tech.com/jsapi'; + +/** + * Logic for the AdUp Technology amp-ad tag + * + * @param {!Window} global + * @param {!Object} data + */ +export function aduptech(global, data) { + const {context, document} = global; + + validateData( + data, + ['placementkey'], + ['mincpc', 'query', 'pageurl', 'gdpr', 'gdpr_consent', 'adtest'] + ); + + // add id attriubte to container + document.getElementById('c').setAttribute('id', ADUPTECH_ELEMENT_ID); + + // init api options + const options = { + amp: true, + responsive: true, + placementkey: data.placementkey, + onAds: () => context.renderStart(), + onNoAds: () => context.noContentAvailable(), + }; + + if ('mincpc' in data) { + options.mincpc = data.mincpc; + } + + if ('query' in data) { + options.query = data.query; + } + + if ('adtest' in data) { + options.adtest = data.adtest; + } + + if ('pageurl' in data) { + options.pageurl = data.pageurl; + } else if (context.sourceUrl) { + options.pageurl = context.sourceUrl; + } else if (context.location && context.location.href) { + options.pageurl = context.location.href; + } + + if ('gdpr' in data) { + options.gdpr = data.gdpr; + } + + // prefer consent string from consentSharedData (if defined) + // otherwise use consent string from optional tag attribute + if ( + context.consentSharedData !== null && + typeof context.consentSharedData === 'object' && + context.consentSharedData.consentString && + context.consentSharedData.consentString !== '' + ) { + // eslint-disable-next-line google-camelcase/google-camelcase + options.gdpr_consent = context.consentSharedData.consentString; + } else if ('gdpr_consent' in data) { + // eslint-disable-next-line google-camelcase/google-camelcase + options.gdpr_consent = data.gdpr_consent; + } + + // load api and embed ads iframe + loadScript(global, ADUPTECH_API_URL, () => + global.uAd.embed(ADUPTECH_ELEMENT_ID, options) + ); +} diff --git a/ads/vendors/aduptech.md b/ads/vendors/aduptech.md new file mode 100644 index 0000000000000..4ca6bce6e1d8c --- /dev/null +++ b/ads/vendors/aduptech.md @@ -0,0 +1,120 @@ + + +# AdUp Technology + +Please visit [www.adup-tech.com](http://www.adup-tech.com) for more information and sign up as publisher to create your placement. + +## Examples + +### Fixed size + +Uses fixed size by the given `width` and `height`. + +```html + + +``` + +### Filled size + +Uses available space of parent html container. + +```html + +
+ + +
+``` + +### Fixed height + +Uses available width and the given `height`. + +```html + + +``` + +### Responsive + +Uses available space but respecting aspect ratio by given `width` and `height` (for example 10:3). + +```html + + +``` + +## Configuration + +| Attribute | Optional | Description | +| ------------------- | :------: | --------------------------------------------------------------------------------------------- | +| `data-placementkey` | | The unique placement key | +| `data-mincpc` | X | The mininum price per click in € | +| `data-query` | X | Additional query keywords separated by semicolon | +| `data-pageurl` | X | The page url (if different from current url) | +| `data-gdpr` | X | `1` = GDPR applies / `0` = GDPR does not apply / omit = unknown wether GDPR applies (default) | +| `data-gdpr_consent` | X | The base64url-encoded IAB consent string | +| `data-adtest` | X | `1` = testing mode enabled / `0` = testing mode disabled (default) | + +## User Consent Integration + +If avaiable, the following consent data will always be send to our adserver: + +- `window.context.consentSharedData.consentString` as IAB consent string + +Otherwise following (optional) tag attributes will be send to our adserver: + +- `data-gdpr` as "GDPR applies" state +- `data-gdpr_consent` as IAB consent string + +If none of above values are given, we try to fetch users consent data via TCF API (if avaiable). diff --git a/ads/vendors/adventive.js b/ads/vendors/adventive.js new file mode 100644 index 0000000000000..e83389c49fa58 --- /dev/null +++ b/ads/vendors/adventive.js @@ -0,0 +1,167 @@ +/** + * Copyright 2015 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. + */ + +import {addParamsToUrl} from '../../src/url'; +import {dict, hasOwn} from '../../src/core/types/object'; +import {endsWith} from '../../src/core/types/string'; +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adventive(global, data) { + if (hasOwn(data, 'isDev')) { + adventive_(global, data); + } else { + validateData(data, ['src'], ['isDev']); + writeScript(global, `${data.src}&isAmp=1`); + } +} + +const adv = { + addInstance: () => {}, + args: {}, + isLibLoaded: false, + mode: { + dev: false, + live: false, + localDev: false, + preview: false, + prod: false, + testing: false, + }, + }, + requiredData = ['pid'], + optionalData = ['click', 'async', 'isDev'], + sld = {true: 'adventivedev', false: 'adventive'}, + thld = {true: 'amp', false: 'ads'}, + cacheTime = 5; + +/** + * Future data: + * - async + * - click + * - height + * - isDev + * - width + * - pid + * + * Considerations: + * - Recipe reuse for multi-placed Ads. + * - Reduce request to only what is needed + * - Mitigate risk of data corruption + * + * @todo implement multi-size handling for multi-slotted ads. @see doubleclick + * + * @param {!Window} global + * @param {!Object} data + */ +function adventive_(global, data) { + validateData(data, requiredData, optionalData); + + if (!hasOwn(global, 'adventive')) { + global.adventive = adv; + } + const ns = global.adventive; + if (!hasOwn(ns, 'context')) { + ns.context = global.context; + } + + if (!Object.isFrozen(ns.mode)) { + updateMode(ns.mode, global.context.location.hostname); + } + + const cb = callback.bind(this, data.pid, ns), + url = getUrl(global.context, data, ns); + url + ? (hasOwn(data, 'async') ? loadScript : writeScript)(global, url, cb) + : cb(); +} + +/** + * @param {!Object} mode + * @param {string} hostname + */ +function updateMode(mode, hostname) { + mode.localDev = hostname === 'localhost'; + mode.dev = !mode.localDev && endsWith(hostname, `${sld[false]}.com`); + mode.prod = !mode.localDev && endsWith(hostname, `${sld[true]}.com`); + mode.preview = (mode.dev || mode.prod) && hostname.startsWith('/ad'); + mode.testing = (mode.dev || mode.prod) && hostname.startsWith('/testing'); + mode.live = (mode.testing || !mode.preview) && !mode.localDev; + + Object.freeze(mode); +} + +/** + * @param {string} id + * @param {!Object} ns + */ +function callback(id, ns) { + ns.addInstance(id); +} + +/** + * @param {!Object} context + * @param {!Object} data + * @param {!Object} ns + * @return {string|boolean} if a search query is generated, a full url is + * provided, otherwise false + */ +function getUrl(context, data, ns) { + const {mode} = ns, + isDev = hasOwn(data, 'isDev'), + sld_ = sld[!mode.dev], + thld_ = thld[isDev && !mode.live], + search = reduceSearch(ns, data.pid, data.click, context.referrer), + url = search + ? addParamsToUrl(`https://${thld_}.${sld_}.com/ad`, search) + : false; + return url; +} + +/** + * @todo determine if we can reduce the request to nothing & return false + * @todo usage of RTC may be applicable here for macros + * @todo check if click-macros can be offloaded to amp-analytics (i.e recipe) + * + * @param {!Object} ns + * @param {string} placementId + * @param {string} click + * @param {string} referrer + * @return {JsonObject} if no more data is needed, false, otherwise JSON + * representation of the url search query. + */ +function reduceSearch(ns, placementId, click, referrer) { + const isRecipeLoaded = hasOwn(ns.args, 'placementId'); + let isRecipeStale = true; + if (isRecipeLoaded) { + const info = ns.args[placementId]; + isRecipeStale = Date.now() - info['requestTime'] > 60 * cacheTime; + } + const needsRequest = !isRecipeLoaded || isRecipeStale; + + return !needsRequest + ? null + : dict({ + 'click': click, + 'referrer': referrer, + 'isAmp': '1', + 'lib': !ns.isLibLoaded ? '1' : '', // may be prefetchable via _config + 'pid': needsRequest ? placementId : '', + }); +} diff --git a/ads/adventive.md b/ads/vendors/adventive.md similarity index 96% rename from ads/adventive.md rename to ads/vendors/adventive.md index e9f91a2991e82..8ea2b265a114e 100644 --- a/ads/adventive.md +++ b/ads/vendors/adventive.md @@ -36,4 +36,4 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-src`: provided ad url +- `data-src`: provided ad url diff --git a/ads/vendors/adverline.js b/ads/vendors/adverline.js new file mode 100644 index 0000000000000..631d753daa9fc --- /dev/null +++ b/ads/vendors/adverline.js @@ -0,0 +1,27 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adverline(global, data) { + validateData(data, ['id', 'plc'], ['s', 'section']); + + writeScript(global, 'https://ads.adverline.com/richmedias/amp.js'); +} diff --git a/ads/adverline.md b/ads/vendors/adverline.md similarity index 83% rename from ads/adverline.md rename to ads/vendors/adverline.md index efaea0c03a43c..c6862f6951257 100644 --- a/ads/adverline.md +++ b/ads/vendors/adverline.md @@ -38,10 +38,10 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-id`: site ID -- `data-plc`: format ID (unique per page) +- `data-id`: site ID +- `data-plc`: format ID (unique per page) ### Optional parameters -- `data-section`: tag list, separated by commas -- `data-s`: dynamic sizing, allowed values: fixed, all, small (default), big +- `data-section`: tag list, separated by commas +- `data-s`: dynamic sizing, allowed values: fixed, all, small (default), big diff --git a/ads/vendors/adverticum.js b/ads/vendors/adverticum.js new file mode 100644 index 0000000000000..8621f1292092b --- /dev/null +++ b/ads/vendors/adverticum.js @@ -0,0 +1,42 @@ +/** + * Copyright 2016 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. + */ + +import {setStyle} from '../../src/style'; +import {validateData, writeScript} from '../../3p/3p'; +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adverticum(global, data) { + validateData(data, ['goa3zone'], ['costumetargetstring']); + const zoneid = 'zone' + data['goa3zone']; + const d = global.document.createElement('div'); + + d.id = zoneid; + d.classList.add('goAdverticum'); + + document.getElementById('c').appendChild(d); + if (data['costumetargetstring']) { + const s = global.document.createTextNode(data['costumetargetstring']); + const v = global.document.createElement('var'); + v.setAttribute('id', 'cT'); + v.setAttribute('class', 'customtarget'); + setStyle(v, 'display', 'none'); + v.appendChild(s); + document.getElementById(zoneid).appendChild(v); + } + writeScript(global, '//ad.adverticum.net/g3.js'); +} diff --git a/ads/adverticum.md b/ads/vendors/adverticum.md similarity index 88% rename from ads/adverticum.md rename to ads/vendors/adverticum.md index 1236e82f40a39..6d795b56d3b1e 100644 --- a/ads/adverticum.md +++ b/ads/vendors/adverticum.md @@ -35,11 +35,11 @@ For details on the configuration semantics, please contact the Adverticum suppor ### Required parameters -- `data-goa3zone`: The zoneID, which can be found at the Adverticum AdServer. +- `data-goa3zone`: The zoneID, which can be found at the Adverticum AdServer. ### Optional parameters -- `data-costumetargetstring:`: The value must be Base64Encoded. +- `data-costumetargetstring:`: The value must be Base64Encoded. ## Support and contact diff --git a/ads/vendors/advertserve.js b/ads/vendors/advertserve.js new file mode 100644 index 0000000000000..e707c28cde18f --- /dev/null +++ b/ads/vendors/advertserve.js @@ -0,0 +1,68 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function advertserve(global, data) { + validateData( + data, + ['zid', 'pid', 'client'], + [ + 'custom1', + 'custom2', + 'custom3', + 'custom4', + 'custom5', + 'custom6', + 'custom7', + 'custom8', + 'custom9', + 'custom10', + ] + ); + + const customFields = (function () { + let params = ''; + for (let i = 1; i <= 10; i++) { + const fieldName = 'custom' + i; + if (data[fieldName] !== undefined) { + params += '&' + fieldName + '=' + encodeURIComponent(data[fieldName]); + } + } + return params; + })(); + + const url = + 'https://' + + data.client + + '.advertserve.com' + + '/servlet/view/banner/javascript/zone?amp=true' + + '&zid=' + + encodeURIComponent(data.zid) + + '&pid=' + + encodeURIComponent(data.pid) + + customFields + + '&random=' + + Math.floor(89999999 * Math.random() + 10000000) + + '&millis=' + + Date.now(); + + writeScript(global, url); +} diff --git a/ads/vendors/advertserve.md b/ads/vendors/advertserve.md new file mode 100644 index 0000000000000..87520f638380c --- /dev/null +++ b/ads/vendors/advertserve.md @@ -0,0 +1,57 @@ + + +# AdvertServe + +## Example + +```html + +
Loading ad.
+
Ad could not be loaded.
+
+``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +Required parameters: + +- `data-client`: client id +- `data-pid`: publisher id +- `data-zid`: zone id + +Optional parameters: + +- `data-custom1`: custom field 1 +- `data-custom2`: custom field 2 +- `data-custom3`: custom field 3 +- `data-custom4`: custom field 4 +- `data-custom5`: custom field 5 +- `data-custom6`: custom field 6 +- `data-custom7`: custom field 7 +- `data-custom8`: custom field 8 +- `data-custom9`: custom field 9 +- `data-custom10`: custom field 10 diff --git a/ads/vendors/adyoulike.js b/ads/vendors/adyoulike.js new file mode 100644 index 0000000000000..fdb59992ba51b --- /dev/null +++ b/ads/vendors/adyoulike.js @@ -0,0 +1,28 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function adyoulike(global, data) { + validateData(data, ['placement'], ['dc', 'campaign']); + global.adyoulikeParams = data; + + writeScript(global, 'https://fo-static.omnitagjs.com/amp.js'); +} diff --git a/ads/adyoulike.md b/ads/vendors/adyoulike.md similarity index 94% rename from ads/adyoulike.md rename to ads/vendors/adyoulike.md index 52564630219a8..2967a7abcfc76 100644 --- a/ads/adyoulike.md +++ b/ads/vendors/adyoulike.md @@ -38,9 +38,9 @@ For configuration details and to generate your tags, please contact [Adyoulike]( ### Required parameters -- `data-placement` +- `data-placement` ### Optional parameters -- `data-dc` -- `data-campaign` +- `data-dc` +- `data-campaign` diff --git a/ads/vendors/affiliateb.js b/ads/vendors/affiliateb.js new file mode 100644 index 0000000000000..1dea3ca176136 --- /dev/null +++ b/ads/vendors/affiliateb.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function affiliateb(global, data) { + validateData(data, ['afb_a', 'afb_p', 'afb_t']); + global.afbParam = data; + writeScript(global, 'https://track.affiliate-b.com/amp/a.js'); +} diff --git a/ads/affiliateb.md b/ads/vendors/affiliateb.md similarity index 97% rename from ads/affiliateb.md rename to ads/vendors/affiliateb.md index dbb449b6ed2c2..c435ceeaab27b 100644 --- a/ads/affiliateb.md +++ b/ads/vendors/affiliateb.md @@ -36,4 +36,4 @@ For configuration details and to generate your tags, please contact [AffiliateB] Supported parameters: -- `data-nend_params` +- `data-nend_params` diff --git a/ads/vendors/aja.js b/ads/vendors/aja.js new file mode 100644 index 0000000000000..32f2a93938a08 --- /dev/null +++ b/ads/vendors/aja.js @@ -0,0 +1,35 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function aja(global, data) { + validateData(data, ['asi']); + + const {document} = global; + const asi = data['asi']; + + const d = document.createElement('div'); + d.dataset['ajaAd'] = ''; + d.dataset['ajaAsi'] = asi; + document.getElementById('c').appendChild(d); + + loadScript(global, 'https://cdn.as.amanad.adtdp.com/sdk/asot-amp.js'); +} diff --git a/ads/aja.md b/ads/vendors/aja.md similarity index 97% rename from ads/aja.md rename to ads/vendors/aja.md index 101f99ac261ec..0806b7e36c50c 100644 --- a/ads/aja.md +++ b/ads/vendors/aja.md @@ -38,5 +38,5 @@ You have to contact our account manager to create ad spot at first. Required parameters: -- asi - - `data-asi` +- asi + - `data-asi` diff --git a/ads/vendors/amoad.js b/ads/vendors/amoad.js new file mode 100644 index 0000000000000..6df54d826c77d --- /dev/null +++ b/ads/vendors/amoad.js @@ -0,0 +1,45 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function amoad(global, data) { + validateData(data, ['sid'], ['adType']); + + let script; + const attrs = {}; + if (data['adType'] === 'native') { + script = 'https://j.amoad.com/js/n.js'; + attrs['class'] = 'amoad_native'; + attrs['data-sid'] = data.sid; + } else { + script = 'https://j.amoad.com/js/a.js'; + attrs['class'] = `amoad_frame sid_${data.sid} container_div sp`; + } + global.amoadOption = {ampData: data}; + + const d = global.document.createElement('div'); + Object.keys(attrs).forEach((k) => { + d.setAttribute(k, attrs[k]); + }); + global.document.getElementById('c').appendChild(d); + + loadScript(global, script); +} diff --git a/ads/amoad.md b/ads/vendors/amoad.md similarity index 95% rename from ads/amoad.md rename to ads/vendors/amoad.md index b16219a1345f4..7364f29e0b1f8 100644 --- a/ads/amoad.md +++ b/ads/vendors/amoad.md @@ -50,7 +50,7 @@ For configuration details and to generate your tags, please contact [AMoAd](http Supported parameters: -- `width` -- `height` -- `data-sid` -- `data-ad-type` +- `width` +- `height` +- `data-sid` +- `data-ad-type` diff --git a/ads/vendors/aniview.js b/ads/vendors/aniview.js new file mode 100644 index 0000000000000..136f557ae01f0 --- /dev/null +++ b/ads/vendors/aniview.js @@ -0,0 +1,30 @@ +/** + * Copyright 2019 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function aniview(global, data) { + const requiredParams = ['publisherid', 'channelid']; + validateData(data, requiredParams); + global.avampdata = data; + const scpdomain = data.scriptdomain || 'player.aniview.com'; + const scpurl = 'https://' + scpdomain + '/script/6.1/ampaniview.js'; + writeScript(global, scpurl); +} diff --git a/ads/vendors/aniview.md b/ads/vendors/aniview.md new file mode 100644 index 0000000000000..f50f2c009dd8c --- /dev/null +++ b/ads/vendors/aniview.md @@ -0,0 +1,56 @@ + + +# Aniview + +## Example + +```html + + +``` + +## Configuration + +For additional details and support contact support@aniview.com. + +### Required parameters + +- `data-publisherid`: the publisher or network id +- `data-channelid`: the channel id + +### Optional parameters + +- `data-ref1`: ref1 extra AV\_ parameters +- `data-loop`: set false to disable loop. Default is true +- `data-vastretry`: vastretry configuration +- `data-errorlimit`: errorlimit configuration +- `data-maximp`: Max number of impressions. +- `data-maxrun`: Max number of waterfall runs. +- `data-preloader`: String of the preloader json object. +- `data-customcss`: Custom css string. +- `data-customlogo`: String of the customlogo json object. +- `data-passbackurl`: passbackurl. +- `data-av_gdpr`: '1' or '0'. +- `data-av_consent`: Consent string. +- `data-av_url`: Current url. +- `data-av_subid`: Subid string. diff --git a/ads/vendors/anyclip.js b/ads/vendors/anyclip.js new file mode 100644 index 0000000000000..b851e0cfd7708 --- /dev/null +++ b/ads/vendors/anyclip.js @@ -0,0 +1,41 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +const requiredParams = ['pubname', 'widgetname']; + +const scriptHost = 'player.anyclip.com'; +const scriptPath = 'anyclip-widget/lre-widget/prod/v1/src'; +const scriptName = 'aclre-amp-loader.js'; +const scriptUrl = `https://${scriptHost}/${scriptPath}/${scriptName}`; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function anyclip(global, data) { + validateData(data, requiredParams); + + global.addEventListener('message', () => { + global.context.renderStart(); + }); + + loadScript(global, scriptUrl, () => { + global.anyclip = global.anyclip || {}; + global.anyclip.getWidget = global.anyclip.getWidget || function () {}; + }); +} diff --git a/ads/vendors/anyclip.md b/ads/vendors/anyclip.md new file mode 100644 index 0000000000000..362331d2aa9fd --- /dev/null +++ b/ads/vendors/anyclip.md @@ -0,0 +1,51 @@ + + +# AnyClip + +## Example + +```html + + +``` + +## Configuration + +For additional details and support contact support@anyclip.com. + +### Required parameters + +- `data-pub-name`: the publisher name +- `data-widget-name`: the widget id + +### Optional parameters + +- `data-widget-url`: the widget source url +- `data-lre-body-bgc`: color of background of the widget +- `data-ac-embed-mode`: the widget embed mode +- `data-ourl`: override url +- `data-plid`: playlist ID +- `data-ar`: aspect ratio +- `data-sid`: given session ID +- `data-tm-*`: misc passthrough params diff --git a/ads/vendors/appnexus.js b/ads/vendors/appnexus.js new file mode 100644 index 0000000000000..5bee27ad8e317 --- /dev/null +++ b/ads/vendors/appnexus.js @@ -0,0 +1,159 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData, writeScript} from '../../3p/3p'; +import {setStyles} from '../../src/style'; + +const APPNEXUS_AST_URL = 'https://acdn.adnxs.com/ast/ast.js'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function appnexus(global, data) { + const args = []; + args.push('size=' + data.width + 'x' + data.height); + if (data.tagid) { + validateData(data, ['tagid']); + args.push('id=' + encodeURIComponent(data.tagid)); + writeScript(global, constructTtj(args)); + return; + } else if (data.member && data.code) { + validateData(data, ['member', 'code']); + args.push('member=' + encodeURIComponent(data.member)); + args.push('inv_code=' + encodeURIComponent(data.code)); + writeScript(global, constructTtj(args)); + return; + } + + /** + * Construct the TTJ URL. + * Note params should be properly encoded first (use encodeURIComponent); + * @param {!Array} args query string params to add to the base URL. + * @return {string} Formated TTJ URL. + */ + function constructTtj(args) { + let url = 'https://ib.adnxs.com/ttj?'; + for (let i = 0; i < args.length; i++) { + //append arg to query. Please encode arg first. + url += args[i] + '&'; + } + + return url; + } + + appnexusAst(global, data); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function appnexusAst(global, data) { + validateData(data, ['adUnits']); + let apntag; + if (context.isMaster) { + // in case we are in the master iframe, we load AST + context.master.apntag = context.master.apntag || {}; + context.master.apntag.anq = context.master.apntag.anq || []; + apntag = context.master.apntag; + + context.master.adUnitTargetIds = context.master.adUnitTargetIds || []; + + context.master.adUnitTargetIds = data.adUnits.map( + (adUnit) => adUnit.targetId + ); + + apntag.anq.push(() => { + if (data.pageOpts) { + apntag.anq.push(() => { + //output console information + apntag.debug = data.debug || false; + apntag.setPageOpts(data.pageOpts); + }); + } + + /** @type {!Array} */ (data.adUnits).forEach((adUnit) => { + apntag.defineTag(adUnit); + }); + }); + loadScript(global, APPNEXUS_AST_URL, () => { + apntag.anq.push(() => { + apntag.loadTags(); + }); + }); + } + + const div = global.document.createElement('div'); + div.setAttribute('id', data.target); + const divContainer = global.document.getElementById('c'); + if (divContainer) { + divContainer.appendChild(div); + setStyles(divContainer, { + top: '50%', + left: '50%', + bottom: '', + right: '', + transform: 'translate(-50%, -50%)', + }); + } + + if (!apntag) { + apntag = context.master.apntag; + //preserve a global reference + /** @type {{showTag: function(string, Object)}} global.apntag */ + global.apntag = context.master.apntag; + } + + if (!context.isMaster && data.adUnits) { + const newAddUnits = data.adUnits.filter((adUnit) => { + return context.master.adUnitTargetIds.indexOf(adUnit.targetId) === -1; + }); + if (newAddUnits.length) { + apntag.anq.push(() => { + /** @type {!Array} */ (newAddUnits).forEach((adUnit) => { + apntag.defineTag(adUnit); + context.master.adUnitTargetIds.push(adUnit.targetId); + }); + apntag.loadTags(); + }); + } + } + + // check for ad responses received for a slot but before listeners are + // registered, for example when an above-the-fold ad is scrolled into view + apntag.anq.push(() => { + if (typeof apntag.checkAdAvailable === 'function') { + const getAd = apntag.checkAdAvailable(data.target); + getAd({resolve: isAdAvailable, reject: context.noContentAvailable}); + } + }); + + apntag.anq.push(() => { + apntag.onEvent('adAvailable', data.target, isAdAvailable); + apntag.onEvent('adNoBid', data.target, context.noContentAvailable); + }); + + /** + * resolve getAd with an available ad object + * + * @param {{targetId: string}} adObj + */ + function isAdAvailable(adObj) { + global.context.renderStart({width: adObj.width, height: adObj.height}); + global.apntag.showTag(adObj.targetId, global.window); + } +} diff --git a/ads/appnexus.md b/ads/vendors/appnexus.md similarity index 100% rename from ads/appnexus.md rename to ads/vendors/appnexus.md diff --git a/ads/vendors/appvador.js b/ads/vendors/appvador.js new file mode 100644 index 0000000000000..427d86fc1340a --- /dev/null +++ b/ads/vendors/appvador.js @@ -0,0 +1,52 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function appvador(global, data) { + validateData(data, ['id'], ['options', 'jsType', 'customScriptSrc']); + + const container = global.document.getElementById('c'); + const apvDiv = global.document.createElement('div'); + apvDiv.setAttribute('id', 'apvad-' + data.id); + container.appendChild(apvDiv); + + const scriptUrl = data.customScriptSrc + ? data.customScriptSrc + : 'https://cdn.apvdr.com/js/' + + (data.jsType ? encodeURIComponent(data.jsType) : 'VastAdUnit') + + '.min.js'; + const apvScript = + 'new APV.' + + (data.jsType ? data.jsType : 'VASTAdUnit') + + '({s:"' + + data.id + + '",isAmpAd:true' + + (data.options ? ',' + data.options : '') + + '}).load();'; + + const cb = function () { + const apvLoadScript = global.document.createElement('script'); + apvLoadScript.text = apvScript; + container.appendChild(apvLoadScript); + }; + + writeScript(global, scriptUrl, cb); +} diff --git a/ads/appvador.md b/ads/vendors/appvador.md similarity index 91% rename from ads/appvador.md rename to ads/vendors/appvador.md index a0ff9d4122943..efa942b76d57c 100644 --- a/ads/appvador.md +++ b/ads/vendors/appvador.md @@ -34,10 +34,10 @@ For configuration semantics, contact [AppVador](http://www.appvador.com/). ### Required Parameters -- `data-id` +- `data-id` ### Optional parameters -- `data-options` -- `data-js-type` -- `data-custom-script-src` +- `data-options` +- `data-js-type` +- `data-custom-script-src` diff --git a/ads/vendors/atomx.js b/ads/vendors/atomx.js new file mode 100644 index 0000000000000..6e6ee1b5ccd76 --- /dev/null +++ b/ads/vendors/atomx.js @@ -0,0 +1,41 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function atomx(global, data) { + const optionals = ['click', 'uv1', 'uv2', 'uv3', 'context']; + + validateData(data, ['id'], optionals); + + const args = [ + 'size=' + data.width + 'x' + data.height, + 'id=' + encodeURIComponent(data.id), + ]; + + for (let i = 0; i < optionals.length; i++) { + const optional = optionals[i]; + if (optional in data) { + args.push(optional + '=' + encodeURIComponent(data[optional])); + } + } + + writeScript(global, 'https://s.ato.mx/p.js#' + args.join('&')); +} diff --git a/ads/vendors/atomx.md b/ads/vendors/atomx.md new file mode 100644 index 0000000000000..d043ec68efb40 --- /dev/null +++ b/ads/vendors/atomx.md @@ -0,0 +1,37 @@ + + +# Atomx + +## Example + +```html + +``` + +## Configuration + +For configuration information, see [atomx documentation](https://wiki.atomx.com/tags). + +### Required Parameters + +- `data-id` - placement ID + +### Optional parameters + +- `data-click` - URL to pre-pend to the click URL to enable tracking. +- `data-uv1`, `data-uv2`, `data-uv3` - User value to pass in to the tag. Can be used to track & report on custom values. Needs to be a whole number between 1 and 4,294,967,295. +- `data-context` - Conversion Callback Context diff --git a/ads/vendors/baidu.js b/ads/vendors/baidu.js new file mode 100644 index 0000000000000..73b47602174cc --- /dev/null +++ b/ads/vendors/baidu.js @@ -0,0 +1,55 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function baidu(global, data) { + validateData(data, ['cproid']); + + const id = '_' + Math.random().toString(36).slice(2); + const container = global.document.createElement('div'); + container.id = id; + global.document.getElementById('c').appendChild(container); + + global.slotbydup = global.slotbydup || []; + global.slotbydup.push({ + id: data['cproid'], + container: id, + display: 'inlay-fix', + async: true, + }); + + global.addEventListener('message', () => { + global.context.renderStart(); + }); + + loadScript( + global, + 'https://dup.baidustatic.com/js/dm.js', + () => {}, + () => { + // noContentAvailable should be called, + // if parent iframe receives no message. + // setTimeout can work, but it's not that reliable. + // So, only the faliure of JS loading is dealed with for now. + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/baidu.md b/ads/vendors/baidu.md similarity index 95% rename from ads/baidu.md rename to ads/vendors/baidu.md index 9ad1d51e17b82..ea3459bdb4a19 100644 --- a/ads/baidu.md +++ b/ads/vendors/baidu.md @@ -28,4 +28,4 @@ For additional detials and support, see [baidu ad union website](http://union.ba ### Required Parameters -- `data-cproid` - baidu union ad id +- `data-cproid` - baidu union ad id diff --git a/ads/vendors/beaverads.js b/ads/vendors/beaverads.js new file mode 100644 index 0000000000000..d64ff843093ee --- /dev/null +++ b/ads/vendors/beaverads.js @@ -0,0 +1,41 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function beaverads(global, data) { + validateData(data, ['blockId']); + + const url = + 'https://code.beaverads.com/data/' + + encodeURIComponent(data['blockId']) + + '.js?async=1&div=c'; + + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/beaverads.md b/ads/vendors/beaverads.md similarity index 97% rename from ads/beaverads.md rename to ads/vendors/beaverads.md index 467aa3d440604..aa221d9734c33 100644 --- a/ads/beaverads.md +++ b/ads/vendors/beaverads.md @@ -29,4 +29,4 @@ For more information, please [see FAQ](https://www.beaverads.com/faq). Required parameters: -- data-block-id +- data-block-id diff --git a/ads/vendors/bidtellect.js b/ads/vendors/bidtellect.js new file mode 100644 index 0000000000000..7601facdfb69f --- /dev/null +++ b/ads/vendors/bidtellect.js @@ -0,0 +1,55 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function bidtellect(global, data) { + const requiredParams = ['t', 'pid', 'sid']; + const optionalParams = [ + 'sname', + 'pubid', + 'pubname', + 'renderid', + 'bestrender', + 'autoplay', + 'playbutton', + 'videotypeid', + 'videocloseicon', + 'targetid', + 'bustframe', + ]; + validateData(data, requiredParams, optionalParams); + let params = '?t=' + encodeURIComponent(data.t); + params += '&pid=' + encodeURIComponent(data.pid); + params += '&sid=' + encodeURIComponent(data.sid); + if (data.width) { + params += '&w=' + encodeURIComponent(data.width); + } + if (data.height) { + params += '&h=' + encodeURIComponent(data.height); + } + optionalParams.forEach(function (param) { + if (data[param]) { + params += '&' + param + '=' + encodeURIComponent(data[param]); + } + }); + const url = 'https://cdn.bttrack.com/js/infeed/2.0/infeed.min.js' + params; + writeScript(global, url); +} diff --git a/ads/vendors/bidtellect.md b/ads/vendors/bidtellect.md new file mode 100644 index 0000000000000..fe011bdd8f3af --- /dev/null +++ b/ads/vendors/bidtellect.md @@ -0,0 +1,55 @@ + + +# Bidtellect + +## Example + +```html + + +``` + +## Configuration + +For details on the configuration semantics, contact [Bidtellect](mailto:technology@bidtellect.com). + +### Required parameters + +- `data-t`: Parent publisher security token. +- `data-pid`: The unique identifier for your placement. +- `data-sid`: Unique identifier for the site. + +### Optional Parameters: + +- `data-sname`: Name of site that corresponds to the Site ID. +- `data-pubid`: Unique identifier for the publisher. +- `data-pubname`: Name of publisher that corresponds to the Publisher ID. +- `data-renderid`: Unique identifier of the placement widget. +- `data-bestrender`: Provides the best size and cropping for the placement. +- `data-autoplay`: Enables autoplay for video placements. +- `data-playbutton`: Onscreen play button for video placements. +- `data-videotypeid`: Defines how it will be rendered the video player. +- `data-videocloseicon`: Enable close button on the video player. +- `data-targetid`: Allows the placement to render inside a target HTML element. +- `data-bustframe`: Allows the placement to bust out of nested iframes recursively. diff --git a/ads/vendors/blade.js b/ads/vendors/blade.js new file mode 100644 index 0000000000000..33f39b9218452 --- /dev/null +++ b/ads/vendors/blade.js @@ -0,0 +1,87 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {tryParseJson} from '../../src/core/types/object/json'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function blade(global, data) { + // ensure mandatory fields + validateData(data, [ + 'width', + 'height', + 'blade_api_key', + 'blade_player_id', + 'blade_player_type', + ]); + + const marcosObj = tryParseJson(data['blade_macros']) || {}; + marcosObj['rand'] = Math.random().toString(); + marcosObj['page_url'] = marcosObj['page_url'] || global.context.canonicalUrl; + const macros = {...marcosObj}; + macros.width = data.width; + macros.height = data.height; + + const containerId = `player-${data['blade_api_key']}-${data['blade_player_id']}`; + createContainer(containerId); + + const bladeConfig = `_bladeConfig-${containerId}`; + global[bladeConfig] = { + playerId: data['blade_player_id'], + apiKey: data['blade_api_key'], + version: '1.0', + macros, + }; + const ctx = global.context; + + const bladeOnLoad = `_bladeOnLoad-${containerId}`; + global[bladeOnLoad] = function (error, player) { + if (error) { + global.context.noContentAvailable(); + return; + } + ctx.reportRenderedEntityIdentifier(containerId); + ctx.renderStart({ + width: player.width, + height: player.height, + }); + }; + + const servingDomain = data.servingDomain + ? encodeURIComponent(data.servingDomain) + : 'ssr.streamrail.net'; + + loadScript( + global, + `https://${servingDomain}/js/${data['blade_api_key']}/${data['blade_player_id']}/player.js?t=${data['blade_player_type']}&callback=${bladeOnLoad}&config=${bladeConfig}&c=${containerId}`, + undefined, + () => { + global.context.noContentAvailable(); + } + ); + /** + * @param {string} elemId + */ + function createContainer(elemId) { + const d = global.document.createElement('div'); + d.id = elemId; + d.classList.add('blade'); + global.document.getElementById('c').appendChild(d); + } +} diff --git a/ads/blade.md b/ads/vendors/blade.md similarity index 90% rename from ads/blade.md rename to ads/vendors/blade.md index a160d6313dc98..95d724f622785 100644 --- a/ads/blade.md +++ b/ads/vendors/blade.md @@ -34,8 +34,8 @@ limitations under the License. ### Required parameters -- `width` -- `height` -- `data-blade_player_type` -- `data-blade_player_id` -- `data-blade_api_key` +- `width` +- `height` +- `data-blade_player_type` +- `data-blade_player_id` +- `data-blade_api_key` diff --git a/ads/vendors/brainy.js b/ads/vendors/brainy.js new file mode 100644 index 0000000000000..38daff7a068ea --- /dev/null +++ b/ads/vendors/brainy.js @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function brainy(global, data) { + validateData(data, [], ['aid', 'slotId']); + + const url = + 'https://proparm.jp/ssp/p/js1' + + '?_aid=' + + encodeURIComponent(data['aid']) + + '&_slot=' + + encodeURIComponent(data['slotId']); + + writeScript(global, url); +} diff --git a/ads/brainy.md b/ads/vendors/brainy.md similarity index 96% rename from ads/brainy.md rename to ads/vendors/brainy.md index 30d8d1bc4acea..5718975e3592e 100644 --- a/ads/brainy.md +++ b/ads/vendors/brainy.md @@ -35,5 +35,5 @@ For configuration details and to generate your tags, contact http://www.opt.ne.j Supported parameters: -- `data-aid` -- `data-slot-id` +- `data-aid` +- `data-slot-id` diff --git a/ads/vendors/bringhub.js b/ads/vendors/bringhub.js new file mode 100644 index 0000000000000..6a77038a7ae99 --- /dev/null +++ b/ads/vendors/bringhub.js @@ -0,0 +1,41 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function bringhub(global, data) { + global._bringhub = global._bringhub || { + viewId: global.context.pageViewId, + htmlURL: data['htmlurl'] || global.context.canonicalUrl, + ampURL: data['ampurl'] || global.context.sourceUrl, + referrer: data['referrer'] || global.context.referrer, + }; + + writeScript( + global, + `https://static.bh-cdn.com/msf/amp-loader.js?v=${Date.now()}`, + function () { + loadScript( + global, + `https://static.bh-cdn.com/msf/amp-widget.js?v=${global._bringhub.hash}` + ); + } + ); +} diff --git a/ads/vendors/bringhub.md b/ads/vendors/bringhub.md new file mode 100644 index 0000000000000..66be2e3ba0942 --- /dev/null +++ b/ads/vendors/bringhub.md @@ -0,0 +1,40 @@ + + +# Bringhub + +## Example installation of the Bringhub Mini-Storefront + +### Basic + +```html + + +``` + +## Configuration + +### Optional parameters + +- `htmlURL`: The URL of the standard html version of the page. Defaults to `global.context.canonicalURL`. +- `ampURL`: The URL of the AMP version of the page. Defaults to `global.context.sourceUrl`. +- `articleSelector`: The CSS Selector of the article body on the page. Contact your Bringhub Account Manager for requirements. diff --git a/ads/vendors/broadstreetads.js b/ads/vendors/broadstreetads.js new file mode 100644 index 0000000000000..1a164d1bc71d5 --- /dev/null +++ b/ads/vendors/broadstreetads.js @@ -0,0 +1,58 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function broadstreetads(global, data) { + validateData( + data, + ['network', 'zone', 'width', 'height'], + ['keywords', 'place'] + ); + + data.place = data.place || 0; + + const placeholderID = 'placement_' + data.zone + '_' + data.place; + + // placeholder div + const d = global.document.createElement('div'); + d.setAttribute('id', placeholderID); + global.document.getElementById('c').appendChild(d); + + global.broadstreet = global.broadstreet || {}; + global.broadstreet.loadAMPZone = + global.broadstreet.loadAMPZone || (() => ({})); + global.broadstreet.run = global.broadstreet.run || []; + global.broadstreet.run.push(() => { + global.broadstreet.loadAMPZone(d, { + amp: true, + ampGlobal: global, + ampData: data, + height: data.height, + keywords: data.keywords, + networkId: data.network, + place: data.place, + softKeywords: true, + width: data.width, + zoneId: data.zone, + }); + }); + loadScript(global, 'https://cdn.broadstreetads.com/init-2.min.js'); +} diff --git a/ads/broadstreetads.md b/ads/vendors/broadstreetads.md similarity index 90% rename from ads/broadstreetads.md rename to ads/vendors/broadstreetads.md index 62df5c1e4922a..fb407399eaac5 100644 --- a/ads/broadstreetads.md +++ b/ads/vendors/broadstreetads.md @@ -35,12 +35,12 @@ For configuration semantics, see the [Broadstreet Ads documentation](https://inf ### Required parameters -- `width` -- `height` -- `data-network` -- `data-zone` +- `width` +- `height` +- `data-network` +- `data-zone` ### Optional parameters -- `data-place` -- `data-keywords` +- `data-place` +- `data-keywords` diff --git a/ads/vendors/byplay.js b/ads/vendors/byplay.js new file mode 100644 index 0000000000000..cddf4df14cd65 --- /dev/null +++ b/ads/vendors/byplay.js @@ -0,0 +1,28 @@ +/** + * Copyright 2019 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. + */ +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function byplay(global, data) { + validateData(data, ['vastUrl']); + + global.BYPLAY_VAST_URL = data['vastUrl']; + + loadScript(global, 'https://cdn.byplay.net/amp-byplay-v2.js'); +} diff --git a/ads/byplay.md b/ads/vendors/byplay.md similarity index 97% rename from ads/byplay.md rename to ads/vendors/byplay.md index bd3f130382779..3885bd16c1d81 100644 --- a/ads/byplay.md +++ b/ads/vendors/byplay.md @@ -34,4 +34,4 @@ For configuration details and to generate your tags, please contact email: [bypl Required parameters: -- `data-vast-url` +- `data-vast-url` diff --git a/ads/vendors/caajainfeed.js b/ads/vendors/caajainfeed.js new file mode 100644 index 0000000000000..e81602b937e63 --- /dev/null +++ b/ads/vendors/caajainfeed.js @@ -0,0 +1,53 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function caajainfeed(global, data) { + validateData( + data, + [], + [ + 'adSpot', + 'format', + 'test', + 'optout', + 'offset', + 'ipv4', + 'ipv6', + 'networkReachability', + 'osName', + 'osVersion', + 'osLang', + 'osTimezone', + 'deviceVersion', + 'appId', + 'appVersion', + 'kv', + 'uids', + 'template', + 'protocol', + 'fields', + ] + ); + + global.caAjaInfeedConfig = data; + loadScript(global, 'https://cdn.amanad.adtdp.com/sdk/ajaamp.js'); +} diff --git a/ads/vendors/caajainfeed.md b/ads/vendors/caajainfeed.md new file mode 100644 index 0000000000000..796c9a8d1fbff --- /dev/null +++ b/ads/vendors/caajainfeed.md @@ -0,0 +1,60 @@ + + +# CA A.J.A. Infeed + +## Example + +```html + + +``` + +## Configuration + +For configuration details, please email amb-nad@cyberagent.co.jp. + +### Required parameters + +- `data-ad-spot` + +### Optional parameters + +- `data-format` +- `data-test` +- `data-optout` +- `data-offset` +- `data-ipv4` +- `data-ipv6` +- `data-network-reachability` +- `data-os-name` +- `data-os-version` +- `data-os-lang` +- `data-os-timezone` +- `data-device-version` +- `data-app-id` +- `data-app-version` +- `data-kv` +- `data-uids` +- `data-template` +- `data-protocol` +- `data-fields` diff --git a/ads/vendors/capirs.js b/ads/vendors/capirs.js new file mode 100644 index 0000000000000..728c18300f7bf --- /dev/null +++ b/ads/vendors/capirs.js @@ -0,0 +1,99 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function capirs(global, data) { + validateData(data, ['begunAutoPad', 'begunBlockId']); + + if (data['customCss']) { + const style = global.document.createElement('style'); + + if (style.styleSheet) { + style.styleSheet.cssText = data['customCss']; + } else { + style.appendChild(global.document.createTextNode(data['customCss'])); + } + + global.document.getElementById('c').appendChild(style); + } + + global['begun_callbacks'] = { + lib: { + init: () => { + const block = global.document.createElement('div'); + block.id = 'x-' + Math.round(Math.random() * 1e8).toString(36); + + global.document.getElementById('c').appendChild(block); + + global['Adf']['banner']['ssp'](block.id, data['params'], { + 'begun-auto-pad': data['begunAutoPad'], + 'begun-block-id': data['begunBlockId'], + }); + }, + }, + block: { + draw: (feed) => { + const banner = feed['banners']['graph'][0]; + + global.context.renderStart({ + width: getWidth(global, banner), + height: banner.height, + }); + + const reportId = 'capirs-' + banner['banner_id']; + global.context.reportRenderedEntityIdentifier(reportId); + }, + unexist: function () { + global.context.noContentAvailable(); + }, + }, + }; + + loadScript(global, '//ssp.rambler.ru/capirs_async.js'); +} + +/** + * @param {!Window} global + * @param {!Object} banner + * @return {*} TODO(#23582): Specify return type + */ +function getWidth(global, banner) { + let width; + + if (isResponsiveAd(banner)) { + width = Math.max( + global.document.documentElement./*OK*/ clientWidth, + global.window./*OK*/ innerWidth || 0 + ); + } else { + width = banner.width; + } + + return width; +} + +/** + * @param {!Object} banner + * @return {boolean} + */ +function isResponsiveAd(banner) { + return banner.width.indexOf('%') !== -1; +} diff --git a/ads/capirs.md b/ads/vendors/capirs.md similarity index 92% rename from ads/capirs.md rename to ads/vendors/capirs.md index 06924ec99a7a8..09772b2770ac3 100644 --- a/ads/capirs.md +++ b/ads/vendors/capirs.md @@ -36,6 +36,6 @@ For semantics of configuration, please see Rambler SSP documentation. Supported parameters: -- `data-begun-auto-pad` -- `data-begun-block-id` -- `data-custom-css` +- `data-begun-auto-pad` +- `data-begun-block-id` +- `data-custom-css` diff --git a/ads/vendors/caprofitx.js b/ads/vendors/caprofitx.js new file mode 100644 index 0000000000000..a995c841327ec --- /dev/null +++ b/ads/vendors/caprofitx.js @@ -0,0 +1,26 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function caprofitx(global, data) { + global.caprofitxConfig = data; + loadScript(global, 'https://cdn.caprofitx.com/tags/amp/profitx_amp.js'); +} diff --git a/ads/caprofitx.md b/ads/vendors/caprofitx.md similarity index 100% rename from ads/caprofitx.md rename to ads/vendors/caprofitx.md diff --git a/ads/vendors/cedato.js b/ads/vendors/cedato.js new file mode 100644 index 0000000000000..a55c93de7ded5 --- /dev/null +++ b/ads/vendors/cedato.js @@ -0,0 +1,75 @@ +/** + * Copyright 2018 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. + */ + +import {parseUrlDeprecated} from '../../src/url'; +import {setStyles} from '../../src/style'; +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function cedato(global, data) { + const requiredParams = ['id']; + const optionalParams = [ + 'domain', + 'servingDomain', + 'subid', + 'version', + 'extraParams', + ]; + validateData(data, requiredParams, optionalParams); + + if (!data || !data.id) { + global.context.noContentAvailable(); + return; + } + + const cb = Math.floor(Math.random() * 10000); + const domain = + data.domain || parseUrlDeprecated(global.context.sourceUrl).origin; + + /* Create div for ad to target */ + const playerDiv = global.document.createElement('div'); + playerDiv.id = 'video' + data.id + cb; + setStyles(playerDiv, { + width: '100%', + height: '100%', + }); + const playerScript = global.document.createElement('script'); + const servingDomain = data.servingDomain + ? encodeURIComponent(data.servingDomain) + : 'algovid.com'; + const srcParams = [ + 'https://p.' + servingDomain + '/player/player.js', + '?p=' + encodeURIComponent(data.id), + '&cb=' + cb, + '&w=' + encodeURIComponent(data.width), + '&h=' + encodeURIComponent(data.height), + data.version ? '&pv=' + encodeURIComponent(data.version) : '', + data.subid ? '&subid=' + encodeURIComponent(data.subid) : '', + domain ? '&d=' + encodeURIComponent(domain) : '', + data.extraParams || '', // already encoded url query string + ]; + + playerScript.onload = () => { + global.context.renderStart(); + }; + + playerScript.src = srcParams.join(''); + playerDiv.appendChild(playerScript); + global.document.getElementById('c').appendChild(playerDiv); +} diff --git a/ads/vendors/cedato.md b/ads/vendors/cedato.md new file mode 100644 index 0000000000000..844d78b50a3cb --- /dev/null +++ b/ads/vendors/cedato.md @@ -0,0 +1,46 @@ + + +# Cedato + +## Example + +```html + + +``` + +## Configuration + +For additional details and support contact support@cedato.com. + +### Required parameters + +- `data-id`: the id of the player - supply ID + +### Optional parameters + +- `data-domain`: page domain reported to the player +- `data-serving-domain`: the domain from which the player is served +- `data-subid`: player subid +- `data-version`: version of the player that is being used +- `data-extra-params`: additional player tag parameters can be set in the 'extra-params' query string, all parts have to be encoded. diff --git a/ads/vendors/colombia.js b/ads/vendors/colombia.js new file mode 100644 index 0000000000000..df5550d0ec2d6 --- /dev/null +++ b/ads/vendors/colombia.js @@ -0,0 +1,53 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function colombia(global, data) { + validateData(data, [ + 'clmb_slot', + 'clmb_position', + 'clmb_section', + 'clmb_divid', + 'loadingStrategy', + ]); + // push the two object into the '_colombia' global + (global._colombia = global._colombia || []).push({ + clmbslot: data.clmb_slot, + clmbposition: data.clmb_position, + clmbsection: data.clmb_section, + clmbdivid: data.clmb_divid, + }); + // install observation on entering/leaving the view + global.context.observeIntersection(function (newrequest) { + /** @type {!Array} */ (newrequest).forEach(function (d) { + if (d.intersectionRect.height > 0) { + global._colombia.push({ + visible: true, + rect: d, + }); + } + }); + }); + loadScript( + global, + 'https://static.clmbtech.com/ad/commons/js/colombia-amp.js' + ); +} diff --git a/ads/colombia.md b/ads/vendors/colombia.md similarity index 89% rename from ads/colombia.md rename to ads/vendors/colombia.md index 7f3924862336f..26c53cc0e7e8f 100644 --- a/ads/colombia.md +++ b/ads/vendors/colombia.md @@ -37,6 +37,6 @@ For configuration semantics, contact care@timesadcenter.com. Supported parameters: -- `data-clmb_slot`: Ad slot -- `data-clmb_position` : Ad position -- `data-clmb_section` : Ad sections +- `data-clmb_slot`: Ad slot +- `data-clmb_position` : Ad position +- `data-clmb_section` : Ad sections diff --git a/ads/vendors/conative.js b/ads/vendors/conative.js new file mode 100644 index 0000000000000..35ad61c113a98 --- /dev/null +++ b/ads/vendors/conative.js @@ -0,0 +1,53 @@ +/** + * Copyright 2019 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function conative(global, data) { + validateData(data, ['domain', 'adslot', 'height'], ['preview']); + + data.domain = data.domain || null; + data.adslot = data.adslot || null; + data.preview = data.preview || null; + + window.dmConativeData = window.dmConativeData || {}; + window.dmConativeData.domain = window.dmConativeData.domain || data.domain; + window.dmConativeData.adslot = window.dmConativeData.adslot || data.adslot; + window.dmConativeData.preview = window.dmConativeData.preview || data.preview; + window.dmConativeData.visibility = window.dmConativeData.visibility || 0; + + window.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + window.dmConativeData.visibility = parseInt( + (c.intersectionRect.height / c.boundingClientRect.height) * 100, + 10 + ); + }); + }); + + if (data.domain) { + writeScript( + global, + '//s3-eu-west-1.amazonaws.com/ccc-adscript/serve/domain/' + + data.domain + + '/config.js' + ); + } +} diff --git a/ads/conative.md b/ads/vendors/conative.md similarity index 85% rename from ads/conative.md rename to ads/vendors/conative.md index 6c18dbd2fe158..020bc4cb3ba2b 100644 --- a/ads/conative.md +++ b/ads/vendors/conative.md @@ -20,7 +20,7 @@ CONATIVE support for AMP. ## Example -- All CONATIVE `` tags require the `width`, `height`, `layout`, `sizes` and `type="conative"` parameters. +- All CONATIVE `` tags require the `width`, `height`, `layout`, `sizes` and `type="conative"` parameters. ```html { + global.context.renderStart(); + }; + + script.src = 'https://cdn.connatix.com/min/connatix.renderer.infeed.min.js'; + global.document.getElementById('c').appendChild(script); +} diff --git a/ads/connatix.md b/ads/vendors/connatix.md similarity index 97% rename from ads/connatix.md rename to ads/vendors/connatix.md index 4aa8b1ef01d76..9154452bef979 100644 --- a/ads/connatix.md +++ b/ads/vendors/connatix.md @@ -35,4 +35,4 @@ For configuration semantics, contact contact@connatix.com. ### Required parameters -- `data-connatix` +- `data-connatix` diff --git a/ads/vendors/contentad.js b/ads/vendors/contentad.js new file mode 100644 index 0000000000000..0f5102e29cb1e --- /dev/null +++ b/ads/vendors/contentad.js @@ -0,0 +1,59 @@ +/** + * Copyright 2016 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. + */ + +import {parseUrlDeprecated} from '../../src/url'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function contentad(global, data) { + validateData(data, [], ['id', 'd', 'wid', 'url']); + global.id = data.id; + global.d = data.d; + global.wid = data.wid; + global.url = data.url; + + /* Create div for ad to target */ + const cadDiv = window.document.createElement('div'); + cadDiv.id = 'contentad' + global.wid; + window.document.body.appendChild(cadDiv); + + /* Pass Source URL */ + let {sourceUrl} = window.context; + if (data.url) { + const domain = data.url || window.atob(data.d); + sourceUrl = sourceUrl.replace(parseUrlDeprecated(sourceUrl).host, domain); + } + + /* Build API URL */ + const cadApi = + 'https://api.content-ad.net/Scripts/widget2.aspx' + + '?id=' + + encodeURIComponent(global.id) + + '&d=' + + encodeURIComponent(global.d) + + '&wid=' + + global.wid + + '&url=' + + encodeURIComponent(sourceUrl) + + '&cb=' + + Date.now(); + + /* Call Content.ad Widget */ + writeScript(global, cadApi); +} diff --git a/ads/contentad.md b/ads/vendors/contentad.md similarity index 91% rename from ads/contentad.md rename to ads/vendors/contentad.md index d642384c55bd6..600ce7b96eba9 100644 --- a/ads/contentad.md +++ b/ads/vendors/contentad.md @@ -42,6 +42,6 @@ For configuration semantics, please see [Content.ad AMP Widget](http://help.cont Supported parameters: -- `data-id`: Ad Widget GUID -- `data-d`: Ad Widget Domain ID -- `data-wid`: Ad Widget ID +- `data-id`: Ad Widget GUID +- `data-d`: Ad Widget Domain ID +- `data-wid`: Ad Widget ID diff --git a/ads/vendors/criteo.js b/ads/vendors/criteo.js new file mode 100644 index 0000000000000..32e50b1c2b9f5 --- /dev/null +++ b/ads/vendors/criteo.js @@ -0,0 +1,51 @@ +/** + * Copyright 2015 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. + */ + +import {dev} from '../../src/log'; +import {loadScript} from '../../3p/3p'; + +/* global Criteo: false */ + +/** @const {string} */ +const TAG = 'CRITEO'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function criteo(global, data) { + loadScript(global, 'https://static.criteo.net/js/ld/publishertag.js', () => { + if (!data.tagtype || data.tagtype === 'passback') { + Criteo.DisplayAd({ + zoneid: data.zone, + containerid: 'c', + integrationmode: 'amp', + }); + } else if (data.tagtype === 'rta' || data.tagtype === 'standalone') { + dev().error( + TAG, + 'You are using a deprecated Criteo integration', + data.tagtype + ); + } else { + dev().error( + TAG, + 'You are using an unknown Criteo integration', + data.tagtype + ); + } + }); +} diff --git a/ads/criteo.md b/ads/vendors/criteo.md similarity index 89% rename from ads/criteo.md rename to ads/vendors/criteo.md index f73a2b690ad57..20a152e855485 100644 --- a/ads/criteo.md +++ b/ads/vendors/criteo.md @@ -43,5 +43,5 @@ The ad size is based on the setup of your Criteo zone. The `width` and `height` Supported parameters: -- `data-tagtype`: identifies the used Criteo technology. Must be "passback". Required. -- `data-zone`: your Criteo zone identifier. Required. +- `data-tagtype`: identifies the used Criteo technology. Must be "passback". Required. +- `data-zone`: your Criteo zone identifier. Required. diff --git a/ads/vendors/csa.js b/ads/vendors/csa.js new file mode 100644 index 0000000000000..b96a5581fee35 --- /dev/null +++ b/ads/vendors/csa.js @@ -0,0 +1,398 @@ +/** + * Copyright 2015 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. + */ +import {devAssert} from '../../src/log'; +import {getStyle, setStyle, setStyles} from '../../src/style'; +import {loadScript, validateData} from '../../3p/3p'; +import {tryParseJson} from '../../src/core/types/object/json'; + +// Keep track of current height of AMP iframe +let currentAmpHeight = null; + +// Height of overflow element +const overflowHeight = 40; + +/** + * Enum for different AdSense Products + * @enum {number} + * @visibleForTesting + */ +export const AD_TYPE = { + /** Value if we can't determine which product to request */ + UNSUPPORTED: 0, + /** AdSense for Search */ + AFS: 1, + /** AdSense for Shopping */ + AFSH: 2, + /** AdSense for Shopping, backfilled with AdSense for Search */ + AFSH_BACKFILL: 3, +}; + +/** + * Request Custom Search Ads (Adsense for Search or AdSense for Shopping). + * @param {!Window} global The window object of the iframe + * @param {!Object} data + */ +export function csa(global, data) { + // Get parent width in case we want to override + const width = global.document.body./*OK*/ clientWidth; + + validateData( + data, + [], + [ + 'afshPageOptions', + 'afshAdblockOptions', + 'afsPageOptions', + 'afsAdblockOptions', + 'ampSlotIndex', + ] + ); + + // Add the ad container to the document + const containerDiv = global.document.createElement('div'); + const containerId = 'csacontainer'; + containerDiv.id = containerId; + global.document.getElementById('c').appendChild(containerDiv); + + const pageOptions = {source: 'amp', referer: global.context.referrer}; + const adblockOptions = {container: containerId}; + + // Parse all the options + const afshPage = Object.assign( + Object(tryParseJson(data['afshPageOptions'])), + pageOptions + ); + const afsPage = Object.assign( + Object(tryParseJson(data['afsPageOptions'])), + pageOptions + ); + const afshAd = Object.assign( + Object(tryParseJson(data['afshAdblockOptions'])), + adblockOptions + ); + const afsAd = Object.assign( + Object(tryParseJson(data['afsAdblockOptions'])), + adblockOptions + ); + + // Special case for AFSh when "auto" is the requested width + if (afshAd['width'] == 'auto') { + afshAd['width'] = width; + } + + // Event listener needed for iOS9 bug + global.addEventListener( + 'orientationchange', + orientationChangeHandler.bind(null, global, containerDiv) + ); + + // Only call for ads once the script has loaded + loadScript( + global, + 'https://www.google.com/adsense/search/ads.js', + requestCsaAds.bind(null, global, data, afsPage, afsAd, afshPage, afshAd) + ); +} + +/** + * Resize the AMP iframe if the CSA container changes in size upon rotation. + * This is needed for an iOS bug found in versions 10.0.1 and below that + * doesn't properly reflow the iframe upon orientation change. + * @param {!Window} global The window object of the iframe + * @param {!Element} containerDiv The CSA container + */ +function orientationChangeHandler(global, containerDiv) { + // Save the height of the container before the event listener triggers + const oldHeight = getStyle(containerDiv, 'height'); + global.setTimeout(() => { + // Force DOM reflow and repaint. + // eslint-disable-next-line no-unused-vars + const ignore = global.document.body./*OK*/ offsetHeight; + // Capture new height. + const newHeight = getStyle(containerDiv, 'height'); + // In older versions of iOS, this height will be different because the + // container height is resized. + // In Chrome and iOS 10.0.2 the height is the same because + // the container isn't resized. + if (oldHeight != newHeight && newHeight != currentAmpHeight) { + // style.height returns "60px" (for example), so turn this into an int + const newHeightPx = parseInt(newHeight, 10); + // Also update the onclick function to resize to the right height. + const overflow = global.document.getElementById('overflow'); + if (overflow) { + overflow.onclick = () => + requestResizeInternal(global, containerDiv, newHeightPx); + } + // Resize the container to the correct height. + requestResizeInternal(global, containerDiv, newHeightPx); + } + }, 250); /* 250 is time in ms to wait before executing orientation */ +} + +/** + * Hanlder for when a resize request succeeds + * Hide the overflow and resize the container + * @param {!Window} global The window object of the iframe + * @param {!Element} container The CSA container + * @param {number} requestedHeight The height of the resize request + * @visibleForTesting + */ +export function resizeSuccessHandler(global, container, requestedHeight) { + currentAmpHeight = requestedHeight; + const overflow = global.document.getElementById('overflow'); + if (overflow) { + setStyle(overflow, 'display', 'none'); + resizeCsa(container, requestedHeight); + } +} + +/** + * Hanlder for When a resize request is denied + * If the container is larger than the AMP container and an overflow already + * exists, show the overflow and resize the container to fit inside the AMP + * container. If an overflow doesn't exist, create one. + * @param {!Window} global The window object of the iframe + * @param {!Element} container The CSA container + * @param {number} requestedHeight The height of the resize request + * @visibleForTesting + */ +export function resizeDeniedHandler(global, container, requestedHeight) { + const overflow = global.document.getElementById('overflow'); + const containerHeight = parseInt(getStyle(container, 'height'), 10); + if (containerHeight > currentAmpHeight) { + if (overflow) { + setStyle(overflow, 'display', ''); + resizeCsa(container, currentAmpHeight - overflowHeight); + } else { + createOverflow(global, container, requestedHeight); + } + } +} + +/** + * Make a request for either AFS or AFSh + * @param {!Window} global The window object of the iframe + * @param {!Object} data The data passed in by the partner + * @param {!Object} afsP The parsed AFS page options object + * @param {!Object} afsA The parsed AFS adblock options object + * @param {!Object} afshP The parsed AFSh page options object + * @param {!Object} afshA The parsed AFSh adblock options object + */ +function requestCsaAds(global, data, afsP, afsA, afshP, afshA) { + const type = getAdType(data); + const callback = callbackWithNoBackfill.bind(null, global); + const callbackBackfill = callbackWithBackfill.bind(null, global, afsP, afsA); + + switch (type) { + case AD_TYPE.AFS: + /** Do not backfill, request AFS */ + afsA['adLoadedCallback'] = callback; + global._googCsa('ads', afsP, afsA); + break; + case AD_TYPE.AFSH: + /** Do not backfill, request AFSh */ + afshA['adLoadedCallback'] = callback; + global._googCsa('plas', afshP, afshA); + break; + case AD_TYPE.AFSH_BACKFILL: + /** Backfill with AFS, request AFSh */ + afshA['adLoadedCallback'] = callbackBackfill; + global._googCsa('plas', afshP, afshA); + break; + } +} + +/** + * Helper function to determine which product to request + * @param {!Object} data The data passed in by the partner + * @return {number} Enum of ad type + */ +function getAdType(data) { + if (data['afsPageOptions'] != null && data['afshPageOptions'] == null) { + return AD_TYPE.AFS; + } + if (data['afsPageOptions'] == null && data['afshPageOptions'] != null) { + return AD_TYPE.AFSH; + } + if (data['afsPageOptions'] != null && data['afshPageOptions'] != null) { + return AD_TYPE.AFSH_BACKFILL; + } else { + return AD_TYPE.UNSUPPORTED; + } +} + +/** + * The adsLoadedCallback for requests without a backfill. If ads were returned, + * resize the iframe. If ads weren't returned, tell AMP we don't have ads. + * @param {!Window} global The window object of the iframe + * @param {string} containerName The name of the CSA container + * @param {boolean} hasAd Whether or not CSA returned an ad + * @visibleForTesting + */ +export function callbackWithNoBackfill(global, containerName, hasAd) { + if (hasAd) { + resizeIframe(global, containerName); + } else { + global.context.noContentAvailable(); + } +} + +/** + * The adsLoadedCallback for requests with a backfill. If ads were returned, + * resize the iframe. If ads weren't returned, backfill the ads. + * @param {!Window} global The window object of the iframe + * @param {!Object} page The parsed AFS page options to backfill the unit with + * @param {!Object} ad The parsed AFS page options to backfill the unit with + * @param {string} containerName The name of the CSA container + * @param {boolean} hasAd Whether or not CSA returned an ad + * @visibleForTesting + */ +export function callbackWithBackfill(global, page, ad, containerName, hasAd) { + if (hasAd) { + resizeIframe(global, containerName); + } else { + ad['adLoadedCallback'] = callbackWithNoBackfill.bind(null, global); + global['_googCsa']('ads', page, ad); + } +} + +/** + * CSA callback function to resize the iframe when ads were returned + * @param {!Window} global + * @param {string} containerName Name of the container ('csacontainer') + * @visibleForTesting + */ +export function resizeIframe(global, containerName) { + // Get actual height of container + const container = global.document.getElementById(containerName); + const height = container./*OK*/ offsetHeight; + // Set initial AMP height + currentAmpHeight = + global.context.initialIntersection.boundingClientRect.height; + + // If the height of the container is larger than the height of the + // initially requested AMP tag, add the overflow element + if (height > currentAmpHeight) { + createOverflow(global, container, height); + } + // Attempt to resize to actual CSA container height + requestResizeInternal(global, devAssert(container), height); +} + +/** + * Helper function to call requestResize + * @param {!Window} global + * @param {!Element} container + * @param {number} height + */ +function requestResizeInternal(global, container, height) { + global.context + .requestResize(undefined, height) + .then(() => { + resizeSuccessHandler(global, container, height); + }) + .catch(() => { + resizeDeniedHandler(global, container, height); + }); +} + +/** + * Helper function to create an overflow element + * @param {!Window} global The window object of the iframe + * @param {!Element} container HTML element of the CSA container + * @param {number} height The full height the CSA container should be when the + * overflow element is clicked. + */ +function createOverflow(global, container, height) { + const overflow = getOverflowElement(global); + // When overflow is clicked, resize to full height + overflow.onclick = () => requestResizeInternal(global, container, height); + global.document.getElementById('c').appendChild(overflow); + // Resize the CSA container to not conflict with overflow + resizeCsa(container, currentAmpHeight - overflowHeight); +} + +/** + * Helper function to create the base overflow element + * @param {!Window} global The window object of the iframe + * @return {!Element} + */ +function getOverflowElement(global) { + const overflow = global.document.createElement('div'); + overflow.id = 'overflow'; + setStyles(overflow, { + position: 'absolute', + height: overflowHeight + 'px', + width: '100%', + }); + overflow.appendChild(getOverflowLine(global)); + overflow.appendChild(getOverflowChevron(global)); + return overflow; +} + +/** + * Helper function to create a line element for the overflow element + * @param {!Window} global The window object of the iframe + * @return {!Element} + */ +function getOverflowLine(global) { + const line = global.document.createElement('div'); + setStyles(line, { + background: 'rgba(0,0,0,.16)', + height: '1px', + }); + return line; +} + +/** + * Helper function to create a chevron element for the overflow element + * @param {!Window} global The window object of the iframe + * @return {!Element} + */ +function getOverflowChevron(global) { + const svg = + '' + + ' '; + + const chevron = global.document.createElement('div'); + setStyles(chevron, { + width: '36px', + height: '36px', + marginLeft: 'auto', + marginRight: 'auto', + display: 'block', + }); + chevron./*OK*/ innerHTML = svg; + return chevron; +} + +/** + * Helper function to resize the height of a CSA container and its child iframe + * @param {!Element} container HTML element of the CSA container + * @param {number} height Height to resize, in pixels + */ +function resizeCsa(container, height) { + const iframe = container.firstElementChild; + if (iframe) { + setStyles(iframe, { + height: height + 'px', + width: '100%', + }); + } + setStyle(container, 'height', height + 'px'); +} diff --git a/ads/google/csa.md b/ads/vendors/csa.md similarity index 77% rename from ads/google/csa.md rename to ads/vendors/csa.md index 40c2d6362196c..50f8c2adac04c 100644 --- a/ads/google/csa.md +++ b/ads/vendors/csa.md @@ -27,7 +27,7 @@ Javascript object you would pass in the ad request of a standard CSA request. @@ -47,7 +47,7 @@ Javascript object you would pass in the ad request of a standard CSA request. @@ -83,16 +83,10 @@ attributes. ## Requirements -- Each amp-ad tag contains one adblock. Only one **data-afs-adblock-options** - and/or one **data-afsh-adblock-options** attribute can be specified in the tag. -- Above the fold ads are required to have a minimum height of 300 pixels. -- When requesting ads above the fold: -- You must use the maxTop parameter instead of the number parameter to specify the number of ads. -- You can only request one ad ("maxTop": 1) in an ad unit that is above the fold. -- You must use a fallback div to show alternate content when no ads are returned. If no ads are returned the ad will not be collapsed because it is above the fold. - - - +- Each amp-ad tag contains one adblock. Only one **data-afs-adblock-options** + and/or one **data-afsh-adblock-options** attribute can be specified in the tag. +- Above the fold ads are required to have a minimum height of 300 pixels. +- When requesting ads above the fold: +- You must use the maxTop parameter instead of the number parameter to specify the number of ads. +- You can only request one ad ("maxTop": 1) in an ad unit that is above the fold. +- You must use a fallback div to show alternate content when no ads are returned. If no ads are returned the ad will not be collapsed because it is above the fold. diff --git a/ads/custom.md b/ads/vendors/custom.md similarity index 92% rename from ads/custom.md rename to ads/vendors/custom.md index c6c99806066d9..2c9e7157d24bb 100644 --- a/ads/custom.md +++ b/ads/vendors/custom.md @@ -20,7 +20,7 @@ Custom does not represent a specific network. Rather, it provides a way for a site to display simple ads on a self-service basis. You must provide your own ad server to deliver the ads in json format as shown below. -Each ad must contain a [mustache](https://github.com/ampproject/amphtml/blob/master/extensions/amp-mustache/amp-mustache.md) +Each ad must contain a [mustache](https://github.com/ampproject/amphtml/blob/main/extensions/amp-mustache/amp-mustache.md) template. Each ad must contain the URL that will be used to fetch data from the server. @@ -190,10 +190,10 @@ The ad server should return a json object containing a record for each slot in t slot id in `data-slot`. The record format is defined by your template. For the examples above, the record contains three fields: -- src - string to go into the source parameter of the image to be displayed. This can be a - web reference (in which case it must be `https:` or a `data:` URI including the base64-encoded image. -- href - URL to which the user is to be directed when he clicks on the ad -- info - A string with additional info about the ad that was served, mmaybe for use with analytics +- src - string to go into the source parameter of the image to be displayed. This can be a + web reference (in which case it must be `https:` or a `data:` URI including the base64-encoded image. +- href - URL to which the user is to be directed when he clicks on the ad +- info - A string with additional info about the ad that was served, mmaybe for use with analytics Here is an example response, assuming two slots named simply 1 and 2: @@ -222,7 +222,7 @@ If no slot was specified, the server returns a single template rather than an ar } ``` -The ad server must enforce [AMP CORS](https://github.com/ampproject/amphtml/blob/master/spec/amp-cors-requests.md#cors-security-in-amp). +The ad server must enforce [AMP CORS](https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-cors-requests.md#cors-security-in-amp). Here is an example set of the relevant response headers: ```html @@ -231,7 +231,7 @@ Access-Control-Allow-Origin:https://my--ad--server-com.cdn.ampproject.org ## Analytics -To get analytics of how your ads are performing, use the [amp-analytics](https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/amp-analytics.md) tag. +To get analytics of how your ads are performing, use the [amp-analytics](https://github.com/ampproject/amphtml/blob/main/extensions/amp-analytics/amp-analytics.md) tag. Here is an example of how to make it work with Google Analytics events. Note that the variables can be set either by the code that displays the page (as in `eventAction`) or in variables passed back by the ad server (as in `eventCategory` and `eventLabel`). diff --git a/ads/vendors/dable.js b/ads/vendors/dable.js new file mode 100644 index 0000000000000..d1c71de1e5ddb --- /dev/null +++ b/ads/vendors/dable.js @@ -0,0 +1,68 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function dable(global, data) { + // check required props + validateData(data, ['widgetId']); + + global.dable = + global.dable || + function () { + (global.dable.q = global.dable.q || []).push(arguments); + }; + global.dable( + 'setService', + data['serviceName'] || global.window.context.location.hostname + ); + global.dable('setURL', global.window.context.sourceUrl); + global.dable('setRef', global.window.context.referrer); + + const slot = global.document.createElement('div'); + slot.id = '_dbl_' + Math.floor(Math.random() * 100000); + slot.setAttribute('data-widget_id', data['widgetId']); + + const divContainer = global.document.getElementById('c'); + if (divContainer) { + divContainer.appendChild(slot); + } + + const itemId = data['itemId'] || ''; + const opts = {}; + + if (itemId) { + global.dable('sendLog', 'view', {id: itemId}); + } else { + opts.ignoreItems = true; + } + + // call render widget + global.dable('renderWidget', slot.id, itemId, opts, function (hasAd) { + if (hasAd) { + global.context.renderStart(); + } else { + global.context.noContentAvailable(); + } + }); + + // load the Dable script asynchronously + loadScript(global, 'https://static.dable.io/dist/plugin.min.js'); +} diff --git a/ads/dable.md b/ads/vendors/dable.md similarity index 93% rename from ads/dable.md rename to ads/vendors/dable.md index a69e9d7c98f87..0fc7b090ea5b0 100644 --- a/ads/dable.md +++ b/ads/vendors/dable.md @@ -37,9 +37,9 @@ For configuration details and to generate your tags, please contact https://admi ### Required parameters -- `data-widget-id` +- `data-widget-id` ### Optional parameters -- `data-item-id` -- `data-service-name` +- `data-item-id` +- `data-service-name` diff --git a/ads/vendors/digiteka.js b/ads/vendors/digiteka.js new file mode 100644 index 0000000000000..d87b7bffc2122 --- /dev/null +++ b/ads/vendors/digiteka.js @@ -0,0 +1,38 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function digiteka(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._digiteka_amp = { + allowed_data: ['mdtk', 'zone', 'adunit', 'params'], + mandatory_data: ['mdtk', 'zone'], + data, + }; + + validateData( + data, + global._digiteka_amp.mandatory_data, + global._digiteka_amp.allowed_data + ); + + loadScript(global, 'https://ot.digiteka.com/amp.js'); +} diff --git a/ads/vendors/digiteka.md b/ads/vendors/digiteka.md new file mode 100644 index 0000000000000..bfad4c5d1fa22 --- /dev/null +++ b/ads/vendors/digiteka.md @@ -0,0 +1,46 @@ + + +# Digiteka + +## Example + +### Basic + +```html + + +``` + +## Configuration + +For configuration semantics, please [contact Digiteka](http://digiteka.com). + +Supported parameters: + +- `mdtk` (required) +- `zone` (required) +- `adunit` (optionnal) +- `params` (optionnal) diff --git a/ads/vendors/directadvert.js b/ads/vendors/directadvert.js new file mode 100644 index 0000000000000..33a9814a2d7bb --- /dev/null +++ b/ads/vendors/directadvert.js @@ -0,0 +1,60 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {serializeQueryString} from '../../src/url'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function directadvert(global, data) { + validateData(data, ['blockId']); + + const params = /** @type {!JsonObject} */ ({ + 'async': 1, + 'div': 'c', + }); + + if (global.context.referrer) { + params['amp_rref'] = encodeURIComponent(global.context.referrer); + } + + if (global.context.canonicalUrl) { + params['amp_rurl'] = encodeURIComponent(global.context.canonicalUrl); + } + + const serverName = data['serverName'] || 'code.directadvert.ru'; + + const url = + '//' + + encodeURIComponent(serverName) + + '/data/' + + encodeURIComponent(data['blockId']) + + '.js?' + + serializeQueryString(params); + + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/directadvert.md b/ads/vendors/directadvert.md similarity index 95% rename from ads/directadvert.md rename to ads/vendors/directadvert.md index 5fbcf2694d372..210fa5894a3cd 100644 --- a/ads/directadvert.md +++ b/ads/vendors/directadvert.md @@ -29,8 +29,8 @@ For more information, please [see the Directadvert FAQ](https://www.directadvert ### Required parameters -- `data-block-id` +- `data-block-id` ### Optional parameters -- `data-server-name` +- `data-server-name` diff --git a/ads/vendors/distroscale.js b/ads/vendors/distroscale.js new file mode 100644 index 0000000000000..386b6633cf832 --- /dev/null +++ b/ads/vendors/distroscale.js @@ -0,0 +1,55 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function distroscale(global, data) { + validateData(data, ['pid'], ['zid', 'tid']); + let src = '//c.jsrdn.com/s/cs.js?p=' + encodeURIComponent(data.pid); + + if (data.zid) { + src += '&z=' + encodeURIComponent(data.zid); + } else { + src += '&z=amp'; + } + + if (data.tid) { + src += '&t=' + encodeURIComponent(data.tid); + } + + let srcUrl = global.context.sourceUrl; + + srcUrl = srcUrl.replace(/#.+/, '').replace(/\?.+/, ''); + + src += '&f=' + encodeURIComponent(srcUrl); + + global.dsAMPCallbacks = { + renderStart: global.context.renderStart, + noContentAvailable: global.context.noContentAvailable, + }; + loadScript( + global, + src, + () => {}, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/distroscale.md b/ads/vendors/distroscale.md similarity index 97% rename from ads/distroscale.md rename to ads/vendors/distroscale.md index e41c0a4946d0d..7723720eab74b 100644 --- a/ads/distroscale.md +++ b/ads/vendors/distroscale.md @@ -32,4 +32,4 @@ For configuration semantics, please [contact DistroScale](http://www.distroscale ### Required parameters -- `data-pid`: Partner ID +- `data-pid`: Partner ID diff --git a/ads/vendors/dotandads.js b/ads/vendors/dotandads.js new file mode 100644 index 0000000000000..e7f04ef210e01 --- /dev/null +++ b/ads/vendors/dotandads.js @@ -0,0 +1,26 @@ +/** + * Copyright 2015 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. + */ + +import {writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function dotandads(global, data) { + global.data = data; + writeScript(global, 'https://amp.ad.dotandad.com/dotandadsAmp.js'); +} diff --git a/ads/dotandads.md b/ads/vendors/dotandads.md similarity index 85% rename from ads/dotandads.md rename to ads/vendors/dotandads.md index 7a35f8f43bb06..c2e2c395beeeb 100644 --- a/ads/dotandads.md +++ b/ads/vendors/dotandads.md @@ -53,7 +53,7 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `sp`: sizepos (the ad size and position code) -- `mpo`: multipoint (an extraction parameter based on site) -- `mpt`: mediapoint tag (the box where the ad will be shown) -- `cid`: customer id +- `sp`: sizepos (the ad size and position code) +- `mpo`: multipoint (an extraction parameter based on site) +- `mpt`: mediapoint tag (the box where the ad will be shown) +- `cid`: customer id diff --git a/ads/vendors/dynad.js b/ads/vendors/dynad.js new file mode 100644 index 0000000000000..6092373793c10 --- /dev/null +++ b/ads/vendors/dynad.js @@ -0,0 +1,33 @@ +/** + * Copyright 2019 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. + */ + +import { + validateData, + validateSrcContains, + validateSrcPrefix, + writeScript, +} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function dynad(global, data) { + validateData(data, ['src'], []); + validateSrcPrefix('https:', data.src); + validateSrcContains('/t.dynad.net/', data.src); + writeScript(global, data.src); +} diff --git a/ads/dynad.md b/ads/vendors/dynad.md similarity index 94% rename from ads/dynad.md rename to ads/vendors/dynad.md index 9fdbfffbea490..0f34ff7c44d6e 100644 --- a/ads/dynad.md +++ b/ads/vendors/dynad.md @@ -34,4 +34,4 @@ For details on the configuration semantics, please contact the ad network or ref ### Direct URL call: -- `src` - Src value from script tag provided by the ad network. +- `src` - Src value from script tag provided by the ad network. diff --git a/ads/vendors/eadv.js b/ads/vendors/eadv.js new file mode 100644 index 0000000000000..4bda91a7a93a6 --- /dev/null +++ b/ads/vendors/eadv.js @@ -0,0 +1,29 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function eadv(global, data) { + validateData(data, ['x', 'u'], []); + writeScript( + global, + 'https://www.eadv.it/track/?x=' + data.x + '&u=' + data.u + ); +} diff --git a/ads/eadv.md b/ads/vendors/eadv.md similarity index 97% rename from ads/eadv.md rename to ads/vendors/eadv.md index 2113dc2715b30..a63949d515f00 100644 --- a/ads/eadv.md +++ b/ads/vendors/eadv.md @@ -35,5 +35,5 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-x` -- `data-u` +- `data-x` +- `data-u` diff --git a/ads/vendors/empower.js b/ads/vendors/empower.js new file mode 100644 index 0000000000000..432ce3c7822fc --- /dev/null +++ b/ads/vendors/empower.js @@ -0,0 +1,31 @@ +/** + * Copyright 2019 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function empower(global, data) { + validateData(data, ['site', 'zone'], ['category']); + global.category = data.category || 'general'; + global.site = data.site + ':general'; + global.zone = data.zone; + global.iwidth = data.width; + global.iheight = data.height; + writeScript(global, 'https://cdn.empower.net/sdk/amp-ad.min.js'); +} diff --git a/ads/empower.md b/ads/vendors/empower.md similarity index 85% rename from ads/empower.md rename to ads/vendors/empower.md index 773458037f42f..b840cc2473efa 100644 --- a/ads/empower.md +++ b/ads/vendors/empower.md @@ -37,9 +37,9 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-site`: Site identifier provided by Empower. -- `data-zone`: Zone ID of the ad space. +- `data-site`: Site identifier provided by Empower. +- `data-zone`: Zone ID of the ad space. ### Optional parameters -- `data-category`: Category identifier provided by Empower. +- `data-category`: Category identifier provided by Empower. diff --git a/ads/vendors/engageya.js b/ads/vendors/engageya.js new file mode 100644 index 0000000000000..dc5112545a225 --- /dev/null +++ b/ads/vendors/engageya.js @@ -0,0 +1,39 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function engageya(global, data) { + validateData(data, ['widgetids']); + + global._engageya = global._engageya || { + viewId: global.context.pageViewId, + widgetIds: data['widgetids'], + websiteId: data['websiteid'], + publisherId: data['publisherid'], + url: data['url'] || global.context.canonicalUrl, + ampURL: data['ampurl'] || global.context.sourceUrl, + mode: data['mode'] || 1, + style: data['stylecss'] || '', + referrer: global.context.referrer, + }; + + loadScript(global, 'https://widget.engageya.com/engageya_amp_loader.js'); +} diff --git a/ads/engageya.md b/ads/vendors/engageya.md similarity index 82% rename from ads/engageya.md rename to ads/vendors/engageya.md index f535341adfc36..fc7e73f4400ea 100644 --- a/ads/engageya.md +++ b/ads/vendors/engageya.md @@ -39,12 +39,12 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-widgetIds`: Widget ids -- `data-websiteId`: Website Id -- `data-publisherId`: Publisher Id +- `data-widgetIds`: Widget ids +- `data-websiteId`: Website Id +- `data-publisherId`: Publisher Id ### Optional parameters -- `data-url`: Current none amp version URL -- `data-ampUrl`: Current AMP page URL -- `data-styleCSS`: Additional style +- `data-url`: Current none amp version URL +- `data-ampUrl`: Current AMP page URL +- `data-styleCSS`: Additional style diff --git a/ads/vendors/epeex.js b/ads/vendors/epeex.js new file mode 100644 index 0000000000000..70ece97215752 --- /dev/null +++ b/ads/vendors/epeex.js @@ -0,0 +1,34 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function epeex(global, data) { + global._epeex = global._epeex || { + account: data['account'] || 'demoepeex', + channel: data['channel'] || '1', + htmlURL: data['htmlurl'] || encodeURIComponent(global.context.canonicalUrl), + ampURL: data['ampurl'] || encodeURIComponent(global.context.sourceUrl), + testMode: data['testmode'] || 'false', + }; + + // load the epeex AMP remote js file + loadScript(global, 'https://epeex.com/related/service/widget/amp/remote.js'); +} diff --git a/ads/epeex.md b/ads/vendors/epeex.md similarity index 91% rename from ads/epeex.md rename to ads/vendors/epeex.md index 196d37fe19f63..afd0c27cd2391 100644 --- a/ads/epeex.md +++ b/ads/vendors/epeex.md @@ -35,11 +35,11 @@ For semantics of configuration, please contact info@epeex.com. ### Required parameters -- `data-account` -- `data-channel` +- `data-account` +- `data-channel` ### Optional parameters -- `data-htmlurl` -- `data-ampurl` -- `data-testmode` +- `data-htmlurl` +- `data-ampurl` +- `data-testmode` diff --git a/ads/vendors/eplanning.js b/ads/vendors/eplanning.js new file mode 100644 index 0000000000000..836c4aa39fbec --- /dev/null +++ b/ads/vendors/eplanning.js @@ -0,0 +1,42 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function eplanning(global, data) { + validateData(data, [ + 'epl_si', + 'epl_isv', + 'epl_sv', + 'epl_sec', + 'epl_kvs', + 'epl_e', + ]); + // push the two object into the '_eplanning' global + (global._eplanning = global._eplanning || []).push({ + sI: data.epl_si, + isV: data.epl_isv, + sV: data.epl_sv, + sec: data.epl_sec, + kVs: data.epl_kvs, + e: data.epl_e, + }); + loadScript(global, 'https://us.img.e-planning.net/layers/epl-amp.js'); +} diff --git a/ads/eplanning.md b/ads/vendors/eplanning.md similarity index 85% rename from ads/eplanning.md rename to ads/vendors/eplanning.md index 06231eb3817f0..8e4490edc447e 100644 --- a/ads/eplanning.md +++ b/ads/vendors/eplanning.md @@ -40,9 +40,9 @@ For configuration semantics, please see [e-planning's documentation](https://www Supported parameters: -- `data-epl_si`: Site ID -- `data-epl_sv`: Default adserver -- `data-epl_isv`: Default CDN -- `data-epl_sec`: Section -- `data-epl_kvs`: Data keywords -- `data-epl_e`: Space name +- `data-epl_si`: Site ID +- `data-epl_sv`: Default adserver +- `data-epl_isv`: Default CDN +- `data-epl_sec`: Section +- `data-epl_kvs`: Data keywords +- `data-epl_e`: Space name diff --git a/ads/vendors/ezoic.js b/ads/vendors/ezoic.js new file mode 100644 index 0000000000000..1d75baf8b45e5 --- /dev/null +++ b/ads/vendors/ezoic.js @@ -0,0 +1,37 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ezoic(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['slot', 'targeting', 'extras']); + loadScript(global, 'https://g.ezoic.net/ezoic/ampad.js', () => { + loadScript( + global, + 'https://www.googletagservices.com/tag/js/gpt.js', + () => { + global.googletag.cmd.push(() => { + new window.EzoicAmpAd(global, data).createAd(); + }); + } + ); + }); +} diff --git a/ads/vendors/ezoic.md b/ads/vendors/ezoic.md new file mode 100644 index 0000000000000..9a0a6cbcd7248 --- /dev/null +++ b/ads/vendors/ezoic.md @@ -0,0 +1,56 @@ + + +# Ezoic + +## Example + +```html + + +``` + +## Ad size + +The ad size is the size of the ad that should be displayed. Make sure the `width` and `height` attributes of the `amp-ad` tag match the available ad size. + +## Configuration + +To generate tags, please visit https://svc.ezoic.com/publisher.php?login + +Supported parameters: + +- `data-slot`: the slot name corresponding to the ad position + +Supported via `json` attribute: + +- `targeting` +- `extras` + +## Consent Support + +Ezoic amp-ad adhere to a user's consent in the following ways: + +- `CONSENT_POLICY_STATE.SUFFICIENT`: Ezoic amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Ezoic amp-ad will display a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Ezoic amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN`: Ezoic amp-ad will not display an ad. diff --git a/ads/vendors/f1e.js b/ads/vendors/f1e.js new file mode 100644 index 0000000000000..03333f53646a8 --- /dev/null +++ b/ads/vendors/f1e.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function f1e(global, data) { + validateData(data, ['url', 'target'], []); + global.f1eData = data; + writeScript(global, 'https://img.ak.impact-ad.jp/util/f1e_amp.min.js'); +} diff --git a/ads/f1e.md b/ads/vendors/f1e.md similarity index 94% rename from ads/f1e.md rename to ads/vendors/f1e.md index 0b5c926222c19..c9030ffee0a69 100644 --- a/ads/f1e.md +++ b/ads/vendors/f1e.md @@ -35,5 +35,5 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-url` - Must start with "https:" -- `data-target` +- `data-url` - Must start with "https:" +- `data-target` diff --git a/ads/vendors/f1h.js b/ads/vendors/f1h.js new file mode 100644 index 0000000000000..b28a3c877176e --- /dev/null +++ b/ads/vendors/f1h.js @@ -0,0 +1,31 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function f1h(global, data) { + validateData(data, ['sectionId', 'slot']); + + const scriptUrl = + data['debugsrc'] || 'https://img.ak.impact-ad.jp/fh/f1h_amp.js'; + + global.f1hData = data; + loadScript(global, scriptUrl); +} diff --git a/ads/f1h.md b/ads/vendors/f1h.md similarity index 86% rename from ads/f1h.md rename to ads/vendors/f1h.md index bc3777236237f..75aaf9dc3e879 100644 --- a/ads/f1h.md +++ b/ads/vendors/f1h.md @@ -52,13 +52,13 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `sectionId`: ID of this section in inventory system. -- `slot`: ID of slot that will be showed in this ad block. -- `pubnetwork-lib`: Filepath of ad library. +- `sectionId`: ID of this section in inventory system. +- `slot`: ID of slot that will be showed in this ad block. +- `pubnetwork-lib`: Filepath of ad library. ### Optional parameters -- `custom`: usage example +- `custom`: usage example ```text { diff --git a/ads/vendors/feedad.js b/ads/vendors/feedad.js new file mode 100644 index 0000000000000..360263661584b --- /dev/null +++ b/ads/vendors/feedad.js @@ -0,0 +1,102 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {setStyle} from '../../src/style'; + +/** + * @typedef FeedAdGlobal + * @private + * + * @property {FeedAdAsync} feedad + */ + +/** + * @typedef {Object} FeedAdAsync + * @private + * + * @property {FeedAd} [sdk] + * @property {!Function[]} cmd + */ + +/** + * @typedef {Object} FeedAd + * @private + * + * @property {function(string)} init + * @property {function(string):Promise} requestAd + */ + +/** + * @typedef {Object} FeedAdResponse + * @private + * + * @property {function():HTMLElement} createAdContainer() + */ + +/** + * @typedef {Object} FeedAdData + * @private + * + * @property {string} clientToken + * @property {string} placementId + * @property {string} [background] + */ + +/** + * @param {!FeedAdGlobal} global + * @param {!FeedAdData} data + */ +export function feedad(global, data) { + validateData(data, ['clientToken', 'placementId'], ['background']); + + global.feedad = global.feedad || {cmd: []}; + global.feedad.cmd.push(() => { + global.feedad.sdk + .init(data.clientToken) + .then(() => global.feedad.sdk.requestAd(data.placementId)) + .then((response) => { + const ad = response.createAdContainer(); + const container = global.document.getElementById('c'); + applyContainerStyle(container, data); + container.appendChild(ad); + global.context.renderStart(); + global.context.reportRenderedEntityIdentifier('FeedAd'); + return response.promise; + }) + .catch(() => { + global.context.noContentAvailable(); + }); + }); + loadScript(global, 'https://web.feedad.com/sdk/feedad-async.js'); +} + +/** + * Centers the ad container within the AMP container. + * Applies the optional background color for the unfilled space. + * + * @param {HTMLElement} container + * @param {!FeedAdData} data + */ +function applyContainerStyle(container, data) { + setStyle(container, 'display', 'flex'); + setStyle(container, 'flexDirection', 'row'); + setStyle(container, 'justifyContent', 'stretch'); + setStyle(container, 'alignItems', 'center'); + if (data.background) { + setStyle(container, 'backgroundColor', data.background); + } +} diff --git a/ads/vendors/feedad.md b/ads/vendors/feedad.md new file mode 100644 index 0000000000000..b842e6afb2933 --- /dev/null +++ b/ads/vendors/feedad.md @@ -0,0 +1,48 @@ + + +# FeedAd + +## Example + +```html + + + +``` + +## Configuration + +### Required parameters: + +- `data-client-token`: The web client token for your FeedAd publisher account. It can be found in your publisher dashboard and was sent to you in your onboarding e-mail. +- `data-placement-id`: This identifies the placement within the FeedAd dashboard. We recommend giving it a meaningful name related to its position within the page, like "article-detail-sidebar". The name may consist of lowercase letters [a-z], numbers [0-9], and dashes [-]'. You can access your publisher dashboard at [https://admin.feedad.com](https://admin.feedad.com). If you do not yet have a FeedAd publisher account, please contact us via the contact form at [https://feedad.com/#contact](https://feedad.com/#contact). + +### Optional parameters: + +- `background`: A CSS color value for the background of the ad, in case the amp-ad element is larger than the ad itself. The ad will always try to request an optimal size, but AMP may reject this request. + +### Recommended parameter values: + +- `width` and `height`: Use values with a 16:9 aspect ratio, as most of our ads are videos of that same ratio. + +## Consent + +- FeedAd is GDPR compliant and reads TCF 2.0 consent strings. Add the IAB vendor 'FeedAd' (#781) in your CMP to ask for your users' consent to our targeted ads. diff --git a/ads/vendors/felmat.js b/ads/vendors/felmat.js new file mode 100644 index 0000000000000..b97392700a7a9 --- /dev/null +++ b/ads/vendors/felmat.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function felmat(global, data) { + validateData(data, ['host', 'fmt', 'fmk', 'fmp']); + global.fmParam = data; + writeScript( + global, + 'https://t.' + encodeURIComponent(data.host) + '/js/fmamp.js' + ); +} diff --git a/ads/felmat.md b/ads/vendors/felmat.md similarity index 93% rename from ads/felmat.md rename to ads/vendors/felmat.md index 79a1ffc06cfcb..d8ab2a4f4df56 100644 --- a/ads/felmat.md +++ b/ads/vendors/felmat.md @@ -37,7 +37,7 @@ For configuration details and to generate your tags, please contact https://www. Supported parameters: -- `data-host` -- `data-fmt` -- `data-fmk` -- `data-fmp` +- `data-host` +- `data-fmt` +- `data-fmk` +- `data-fmp` diff --git a/ads/vendors/finative.js b/ads/vendors/finative.js new file mode 100644 index 0000000000000..02ba5ce7b9d4a --- /dev/null +++ b/ads/vendors/finative.js @@ -0,0 +1,29 @@ +/** + * Copyright 2015 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. + */ + +import {writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function finative(global, data) { + writeScript( + global, + 'https://d.finative.cloud/cds/delivery/init?url=' + + encodeURIComponent(data.url) + ); +} diff --git a/ads/vendors/finative.md b/ads/vendors/finative.md new file mode 100644 index 0000000000000..3bf1eadb4b686 --- /dev/null +++ b/ads/vendors/finative.md @@ -0,0 +1,44 @@ + + +# finative + +​ + +## Example + +​ + +```html + + +``` + +​ + +## Configuration + +​ +Before starting any finative AMP setup, please reach out to your account manager for the most up to date documentation or contact [support@finative.com](mailto:support@finative.com). +​ +Supported parameters: +​ + +- `url`: Domain or URL from your Project diff --git a/ads/vendors/firstimpression.js b/ads/vendors/firstimpression.js new file mode 100644 index 0000000000000..abad3fd0b0215 --- /dev/null +++ b/ads/vendors/firstimpression.js @@ -0,0 +1,41 @@ +/** + * Copyright 2020 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. + */ + +import {parseQueryString} from '../../src/core/types/string/url'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function firstimpression(global, data) { + validateData(data, ['zoneId', 'websiteId']); + + const {hash, search} = global.context.location; + const parameters = Object.assign( + parseQueryString(hash), + parseQueryString(search) + ); + + const cdnHost = + 'https://' + (parameters['fi_ecdnhost'] || 'ecdn.firstimpression.io'); + + const cdnpath = parameters['fi_ecdnpath'] || '/static/js/fiamp.js'; + + global.params = data; + + writeScript(global, cdnHost + cdnpath); +} diff --git a/ads/vendors/firstimpression.md b/ads/vendors/firstimpression.md new file mode 100644 index 0000000000000..dbcb958df782b --- /dev/null +++ b/ads/vendors/firstimpression.md @@ -0,0 +1,21 @@ + + +# FirstImpression.io + +FirstImpression.io `amp-ad` tag is intended to be created dynamically by [`amp-auto-ads` extension](../extensions/amp-auto-ads/amp-auto-ads.md), which sets all the required parameters. + +For more information and integration instructions, please [contact your account manager](https://publishers.firstimpression.io) at FirstImpression.io. diff --git a/ads/vendors/flite.js b/ads/vendors/flite.js new file mode 100644 index 0000000000000..0db39ec3da388 --- /dev/null +++ b/ads/vendors/flite.js @@ -0,0 +1,55 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function flite(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['guid', 'mixins']); + const {guid} = data, + o = global, + e = encodeURIComponent, + x = 0; + let r = '', + dep = ''; + o.FLITE = o.FLITE || {}; + o.FLITE.config = o.FLITE.config || {}; + o.FLITE.config[guid] = o.FLITE.config[guid] || {}; + o.FLITE.config[guid].cb = Math.random(); + o.FLITE.config[guid].ts = +Number(new Date()); + r = global.context.location.href; + const m = r.match(new RegExp('[A-Za-z]+:[/][/][A-Za-z0-9.-]+')); + dep = data.mixins ? '&dep=' + data.mixins : ''; + const url = [ + 'https://r.flite.com/syndication/uscript.js?i=', + e(guid), + '&v=3', + dep, + '&x=us', + x, + '&cb=', + o.FLITE.config[guid].cb, + '&d=', + e((m && m[0]) || r), + '&tz=', + new Date().getTimezoneOffset(), + ].join(''); + loadScript(o, url); +} diff --git a/ads/flite.md b/ads/vendors/flite.md similarity index 96% rename from ads/flite.md rename to ads/vendors/flite.md index 23c5131e06cd4..8907abda11fba 100644 --- a/ads/flite.md +++ b/ads/vendors/flite.md @@ -35,5 +35,5 @@ For configuration and implementation details, please [contact Flite Support](htt Supported parameters: -- `data-guid` -- `data-mixins` +- `data-guid` +- `data-mixins` diff --git a/ads/vendors/fluct.js b/ads/vendors/fluct.js new file mode 100644 index 0000000000000..c0440c741650c --- /dev/null +++ b/ads/vendors/fluct.js @@ -0,0 +1,50 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +/* global adingoFluct: false */ +/* global fluctAdScript: false */ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function fluct(global, data) { + validateData(data, ['g', 'u']); + + if (data['tagtype'] === 'api') { + const cls = `fluct-unit-${data['u']}`; + const d = global.document.createElement('div'); + d.setAttribute('class', cls); + global.document.getElementById('c').appendChild(d); + + loadScript(global, 'https://pdn.adingo.jp/p.js', function () { + fluctAdScript.cmd.push(function (cmd) { + cmd.loadByGroup(data['g']); + cmd.display(`.${cls}`, data['u']); + }); + }); + } else { + writeScript( + global, + `https://cdn-fluct.sh.adingo.jp/f.js?G=${encodeURIComponent(data['g'])}`, + function () { + adingoFluct.showAd(data['u']); + } + ); + } +} diff --git a/ads/fluct.md b/ads/vendors/fluct.md similarity index 87% rename from ads/fluct.md rename to ads/vendors/fluct.md index af00705fbc5c7..06e21b20c81cb 100644 --- a/ads/fluct.md +++ b/ads/vendors/fluct.md @@ -25,15 +25,16 @@ limitations under the License. type="fluct" data-g="{GROUP-ID}" data-u="{UNIT-ID}" + data-tagtype="api" > ``` ## Configuration -For more information, please [contact fluct](https://corp.fluct.jp/en/contact.php). +For more information, please [contact fluct](https://en.fluct.jp/). ### Required parameters -- `data-g` -- `data-u` +- `data-g` +- `data-u` diff --git a/ads/vendors/forkmedia.js b/ads/vendors/forkmedia.js new file mode 100644 index 0000000000000..9cb16b3e90a35 --- /dev/null +++ b/ads/vendors/forkmedia.js @@ -0,0 +1,62 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function forkmedia(global, data) { + let src = null; + if (data.product === 'contextads') { + switch (data.format) { + case 'inread': + src = 'https://amp.contextads.live/inread/inread.js'; + break; + case 'vibe': + src = 'https://amp.contextads.live/vibe/iav_ia.js'; + break; + case 'display': + src = 'https://amp.contextads.live/display/display.js'; + break; + case 'impulse': + src = 'https://amp.contextads.live/impulse/impulse.js'; + break; + case 'interscroller': + src = 'https://amp.contextads.live/interscroller/fis.js'; + break; + case 'spark': + src = 'https://amp.contextads.live/spark/spark.js'; + break; + default: + src = 'https://amp.contextads.live/default.js'; + } + } else { + src = 'https://amp.contextads.live/default.js'; + } + + loadScript( + global, + src, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/forkmedia.md b/ads/vendors/forkmedia.md similarity index 80% rename from ads/forkmedia.md rename to ads/vendors/forkmedia.md index d1e0ffeb8c252..76f0711ed144b 100644 --- a/ads/forkmedia.md +++ b/ads/vendors/forkmedia.md @@ -20,11 +20,12 @@ limitations under the License. ```html ``` @@ -35,5 +36,5 @@ For details on the configuration semantics, please contact Fork Media. ### Required parameters -- `data-product` -- `data-html-access-allowed` +- `data-product` +- `data-html-access-allowed` diff --git a/ads/vendors/freewheel.js b/ads/vendors/freewheel.js new file mode 100644 index 0000000000000..26c85bcde83a0 --- /dev/null +++ b/ads/vendors/freewheel.js @@ -0,0 +1,55 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function freewheel(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._freewheel_amp = { + data, + }; + + validateData( + data, + ['zone'], + [ + 'zone', + 'gdpr', + 'gdpr_consent', + 'gdpr_consented_providers', + 'useCCPA_USPAPI', + '_fw_us_privacy', + 'useCMP', + 'zIndex', + 'blurDisplay', + 'timeline', + 'soundButton', + 'defaultMute', + 'onOver', + 'closeAction', + 'errorAction', + 'pauseRatio', + 'label', + 'vastUrlParams', + ] + ); + + loadScript(global, 'https://cdn.stickyadstv.com/prime-time/fw-amp.min.js'); +} diff --git a/ads/vendors/freewheel.md b/ads/vendors/freewheel.md new file mode 100644 index 0000000000000..008b975b78d0f --- /dev/null +++ b/ads/vendors/freewheel.md @@ -0,0 +1,65 @@ + + +# FreeWheel + +## Example + +### Expand-banner + +```html + + +``` + +### FloorAd + +```html + + + +``` + +## Configuration + +For details on the configuration semantics, please contact the FreeWheel support team : clientsidesdk@freewheel.tv + +Supported parameters: +All parameters are optional, unless otherwise stated + +- `data-zone` [required] +- `data-blurDisplay` +- `data-timeline` +- `data-soundButton` +- `data-defaultMute` +- `data-onOver` +- `data-closeAction` +- `data-errorAction` +- `data-pauseRatio` +- `data-label` +- `data-vastUrlParams` +- `data-gdpr` +- `data-gdpr_consent` +- `data-gdpr_consented_providers` +- `data-useCCPA_USPAPI` +- `data-_fw_us_privacy` +- `data-useCMP` diff --git a/ads/vendors/fusion.js b/ads/vendors/fusion.js new file mode 100644 index 0000000000000..a4b77a1426c0e --- /dev/null +++ b/ads/vendors/fusion.js @@ -0,0 +1,66 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {string=} input + * @return {JsonObject|undefined} + */ +function queryParametersToObject(input) { + if (!input) { + return undefined; + } + return input + .split('&') + .filter(Boolean) + .reduce((obj, val) => { + const kv = val.split('='); + return Object.assign(obj, {[kv[0]]: kv[1] || true}); + }, {}); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function fusion(global, data) { + validateData( + data, + [], + ['mediaZone', 'layout', 'adServer', 'space', 'parameters'] + ); + + const container = global.document.getElementById('c'); + const ad = global.document.createElement('div'); + ad.setAttribute('data-fusion-space', data.space); + container.appendChild(ad); + const parameters = queryParametersToObject(data.parameters); + + writeScript( + global, + 'https://assets.adtomafusion.net/fusion/latest/fusion-amp.min.js', + () => { + global.Fusion.apply(container, global.Fusion.loadAds(data, parameters)); + + global.Fusion.on.warning.run((ev) => { + if (ev.msg === 'Space not present in response.') { + global.context.noContentAvailable(); + } + }); + } + ); +} diff --git a/ads/fusion.md b/ads/vendors/fusion.md similarity index 92% rename from ads/fusion.md rename to ads/vendors/fusion.md index aa7ae0b0ecbbf..8ed9f3b8d6949 100644 --- a/ads/fusion.md +++ b/ads/vendors/fusion.md @@ -38,10 +38,10 @@ For configuration and implementation details, please contact the Fusion support Supported parameters: -- `data-ad-server` -- `data-media-zone` -- `data-layout` -- `data-space` -- `data-parameters` +- `data-ad-server` +- `data-media-zone` +- `data-layout` +- `data-space` +- `data-parameters` Parameters should be passed as `key&value` pairs `&` separated. Missing value equals `true`. So `...&isMobile&...` from the example above stands for `...&isMobile=true&...`. diff --git a/ads/vendors/genieessp.js b/ads/vendors/genieessp.js new file mode 100644 index 0000000000000..4592ac220dcad --- /dev/null +++ b/ads/vendors/genieessp.js @@ -0,0 +1,28 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function genieessp(global, data) { + validateData(data, ['vid', 'zid']); + + global.data = data; + writeScript(global, 'https://js.gsspcln.jp/l/amp.js'); +} diff --git a/ads/genieessp.md b/ads/vendors/genieessp.md similarity index 97% rename from ads/genieessp.md rename to ads/vendors/genieessp.md index b98b447085259..c64bb1f881594 100644 --- a/ads/genieessp.md +++ b/ads/vendors/genieessp.md @@ -37,5 +37,5 @@ For semantics of configuration, please see Geniee SSP documentation or [contact Supported parameters: -- `data-vid` -- `data-zid` +- `data-vid` +- `data-zid` diff --git a/ads/vendors/giraff.js b/ads/vendors/giraff.js new file mode 100644 index 0000000000000..95bb79adeb523 --- /dev/null +++ b/ads/vendors/giraff.js @@ -0,0 +1,49 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function giraff(global, data) { + validateData(data, ['blockName']); + + const serverName = data['serverName'] || 'code.giraff.io'; + const url = + '//' + + encodeURIComponent(serverName) + + '/data/widget-' + + encodeURIComponent(data['blockName']) + + '.js'; + + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); + + const anchorEl = global.document.createElement('div'); + const widgetId = data['widgetId'] ? '_' + data['widgetId'] : ''; + anchorEl.id = 'grf_' + data['blockName'] + widgetId; + global.document.getElementById('c').appendChild(anchorEl); +} diff --git a/ads/giraff.md b/ads/vendors/giraff.md similarity index 93% rename from ads/giraff.md rename to ads/vendors/giraff.md index 83032e55f3313..42105fecdf4ad 100644 --- a/ads/giraff.md +++ b/ads/vendors/giraff.md @@ -35,9 +35,9 @@ For more information, see [Giraff's documentation](https://www.giraff.io/help). ### Optional parameters -- `data-widget-id` -- `data-server-name` +- `data-widget-id` +- `data-server-name` ### Required parameters -- `data-block-name` +- `data-block-name` diff --git a/ads/vendors/glomex.js b/ads/vendors/glomex.js new file mode 100644 index 0000000000000..152c2162336aa --- /dev/null +++ b/ads/vendors/glomex.js @@ -0,0 +1,26 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function glomex(global, data) { + validateData(data, ['integrationId']); + loadScript(global, 'https://player.glomex.com/integration/1/amp-embed.js'); +} diff --git a/ads/vendors/glomex.md b/ads/vendors/glomex.md new file mode 100644 index 0000000000000..84ea63e78bb15 --- /dev/null +++ b/ads/vendors/glomex.md @@ -0,0 +1,47 @@ + + +# Glomex + +## Example + +```html + + +``` + +## Configuration + +For more information, see [Glomex's documentation](https://docs.glomex.com/publisher/video-player-integration/). + +### Optional parameters + +- `data-playlist-id` +- `data-placement` +- `data-playlist-index` +- `data-article-id` +- `data-article-category` + +### Required parameters + +- `data-integration-id` diff --git a/ads/vendors/gmossp.js b/ads/vendors/gmossp.js new file mode 100644 index 0000000000000..07f51e3ddb593 --- /dev/null +++ b/ads/vendors/gmossp.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +const gmosspFields = ['id']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function gmossp(global, data) { + validateData(data, gmosspFields, []); + + global.gmosspParam = data; + writeScript(global, 'https://cdn.gmossp-sp.jp/ads/amp.js'); +} diff --git a/ads/gmossp.md b/ads/vendors/gmossp.md similarity index 98% rename from ads/gmossp.md rename to ads/vendors/gmossp.md index 07c219f432229..8fde5bf4c937d 100644 --- a/ads/gmossp.md +++ b/ads/vendors/gmossp.md @@ -28,4 +28,4 @@ For configuration details and to generate your tags, please contact dev@ml.gmo-a Supported parameters: -- `data-id` +- `data-id` diff --git a/ads/vendors/gumgum.js b/ads/vendors/gumgum.js new file mode 100644 index 0000000000000..9df63369e22fb --- /dev/null +++ b/ads/vendors/gumgum.js @@ -0,0 +1,80 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {setStyles} from '../../src/style'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function gumgum(global, data) { + validateData(data, ['zone', 'slot']); + + const win = window, + ctx = win.context, + dom = global.document.getElementById('c'), + ampWidth = parseInt(data.width || '0', 10), + ampHeight = parseInt(data.height || '0', 10), + ggevents = global.ggevents || []; + + const {max} = Math, + slotId = parseInt(data.slot, 10), + onLoad = function (type) { + return function (evt) { + const ad = {width: 0, height: 0, ...(evt.ad || {})}, + identifier = ['GUMGUM', type, evt.id].join('_'); + ctx.reportRenderedEntityIdentifier(identifier); + ctx.renderStart({ + width: max(ampWidth, ad.width), + height: max(ampHeight, ad.height), + }); + }; + }, + noFill = function () { + ctx.noContentAvailable(); + }; + + // Ads logic starts + global.ggv2id = data.zone; + global.ggevents = ggevents; + global.sourceUrl = context.sourceUrl; + global.sourceReferrer = context.referrer; + + if (slotId) { + // Slot Ad + const ins = global.document.createElement('div'); + setStyles(ins, { + display: 'block', + width: '100%', + height: '100%', + }); + ins.setAttribute('data-gg-slot', slotId); + ins.setAttribute('pl', 2); + dom.appendChild(ins); + // Events + ggevents.push({ + 'slot.nofill': noFill, + 'slot.close': noFill, + 'slot.load': onLoad('SLOT'), + }); + // Main script + loadScript(global, 'https://js.gumgum.com/slot.js'); + } else { + // No valid configuration + ctx.noContentAvailable(); + } +} diff --git a/ads/gumgum.md b/ads/vendors/gumgum.md similarity index 92% rename from ads/gumgum.md rename to ads/vendors/gumgum.md index b0166ecc75615..d39aae2bedf82 100644 --- a/ads/gumgum.md +++ b/ads/vendors/gumgum.md @@ -37,5 +37,5 @@ For parameters, configuration or any questions, contact [GumGum](http://gumgum.c ### Required parameters -- `data-zone`: GumGum Zone ID -- `data-slot`: GumGum Slot Ad ID +- `data-zone`: GumGum Zone ID +- `data-slot`: GumGum Slot Ad ID diff --git a/ads/vendors/holder.js b/ads/vendors/holder.js new file mode 100644 index 0000000000000..b1e72cad50da8 --- /dev/null +++ b/ads/vendors/holder.js @@ -0,0 +1,33 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function holder(global, data) { + validateData(data, ['block'], []); + const wcl = global.context.location; + const n = navigator.userAgent; + let l = '&r' + Math.round(Math.random() * 10000000) + '&h' + wcl.href; + if (!(n.indexOf('Safari') != -1 && n.indexOf('Chrome') == -1)) { + l += '&c1'; + } + data.queue = l; + writeScript(global, 'https://i.holder.com.ua/js2/holder/ajax/ampv1.js'); +} diff --git a/ads/holder.md b/ads/vendors/holder.md similarity index 98% rename from ads/holder.md rename to ads/vendors/holder.md index f330a49e0af5f..920d24d25abf8 100644 --- a/ads/holder.md +++ b/ads/vendors/holder.md @@ -28,4 +28,4 @@ For configuration details and to generate your tags, please contact techinfo@hol Supported parameters: -- `data-block` +- `data-block` diff --git a/ads/vendors/ibillboard.js b/ads/vendors/ibillboard.js new file mode 100644 index 0000000000000..85c5cf2db4349 --- /dev/null +++ b/ads/vendors/ibillboard.js @@ -0,0 +1,37 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, validateSrcPrefix, writeScript} from '../../3p/3p'; + +const validHosts = [ + 'https://go.eu.bbelements.com', + 'https://go.idnes.bbelements.com', + 'https://go.goldbachpoland.bbelements.com', + 'https://go.pol.bbelements.com', + 'https://go.idmnet.bbelements.com', +]; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ibillboard(global, data) { + validateData(data, ['src']); + const {src} = data; + validateSrcPrefix(validHosts, src); + + writeScript(global, src); +} diff --git a/ads/ibillboard.md b/ads/vendors/ibillboard.md similarity index 91% rename from ads/ibillboard.md rename to ads/vendors/ibillboard.md index e9ce5555a198a..c20e9826e028a 100644 --- a/ads/ibillboard.md +++ b/ads/vendors/ibillboard.md @@ -38,4 +38,4 @@ For details on the configuration semantics, visit the [ibillboard website](http: ### Supported parameters -- `src`: Must use **https** protocol and must be from one of the allowed iBillboard hosts. +- `src`: Must use **https** protocol and must be from one of the allowed iBillboard hosts. diff --git a/ads/vendors/idealmedia.js b/ads/vendors/idealmedia.js new file mode 100644 index 0000000000000..b33cbb34a05b2 --- /dev/null +++ b/ads/vendors/idealmedia.js @@ -0,0 +1,62 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @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); + + 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/idealmedia.md b/ads/vendors/idealmedia.md similarity index 92% rename from ads/idealmedia.md rename to ads/vendors/idealmedia.md index 0f1620a32e205..e70d8eb9e8e1b 100644 --- a/ads/idealmedia.md +++ b/ads/vendors/idealmedia.md @@ -38,10 +38,10 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-publisher` -- `data-widget` -- `data-container` +- `data-publisher` +- `data-widget` +- `data-container` ### Optional parameters -- `data-url` +- `data-url` diff --git a/ads/vendors/imedia.js b/ads/vendors/imedia.js new file mode 100644 index 0000000000000..7ae9c78c540b4 --- /dev/null +++ b/ads/vendors/imedia.js @@ -0,0 +1,83 @@ +/** + * Copyright 2017 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. + */ + +import {computeInMasterFrame, loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function imedia(global, data) { + validateData(data, ['id', 'positions']); + const positions = JSON.parse(data.positions); + const mW = context.isMaster ? global : context.master; + + // create parent element + const parentElement = document.createElement('div'); + parentElement.id = data.id; + global.document.getElementById('c').appendChild(parentElement); + + // array of all ad elements and matching contexts through all iframes + if (!mW.inPagePositions) { + mW.inPagePositions = []; + } + mW.inPagePositions.push({parentElement, context: global.context}); + + computeInMasterFrame( + global, + 'imedia-load', + (done) => { + loadScript(global, 'https://i.imedia.cz/js/im3.js', () => { + if (global.im != null) { + mW.im = global.im; + mW.im.conf.referer = context.canonicalUrl; + + // send request to get all ads + mW.im.getAds(positions, { + AMPcallback: (ads) => { + mW.ads = ads; + done(null); + }, + }); + } + }); + }, + () => { + mW.inPagePositions = mW.inPagePositions.filter((inPagePostion) => { + let used = true; + positions.filter((position, index) => { + // match right element and zone to write advert from adserver + if (inPagePostion.parentElement.id == position.id) { + used = false; + position.id = inPagePostion.parentElement; // right element "c" to position obj. + if (mW.im.writeAd) { + mW.im.writeAd(mW.ads[index], position); + + // inform AMP runtime when the ad starts rendering + if (mW.ads[index].impress) { + inPagePostion.context.renderStart(); + } else { + inPagePostion.context.noContentAvailable(); + } + } + return false; + } + }); + return used; // remove (filter) element filled with add + }); + } + ); +} diff --git a/ads/imedia.md b/ads/vendors/imedia.md similarity index 89% rename from ads/imedia.md rename to ads/vendors/imedia.md index 143b9cd042320..daab648cf241c 100644 --- a/ads/imedia.md +++ b/ads/vendors/imedia.md @@ -35,10 +35,10 @@ For configuration semantics, see [Imedia's documentation](https://iimedia.sbeta. Required parameters: -- `data-id` -- `data-positions`: JSON value +- `data-id` +- `data-positions`: JSON value Required JSON fields: -- `id`: unique element id -- `zoneId`: advertisement identificator +- `id`: unique element id +- `zoneId`: advertisement identificator diff --git a/ads/vendors/imobile.js b/ads/vendors/imobile.js new file mode 100644 index 0000000000000..186376326f2de --- /dev/null +++ b/ads/vendors/imobile.js @@ -0,0 +1,25 @@ +/** + * Copyright 2015 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. + */ + +import {writeScript} from '../../3p/3p'; +/** + * @param {!Window} global + * @param {!Object} data + */ +export function imobile(global, data) { + global.imobileParam = data; + writeScript(global, 'https://spamp.i-mobile.co.jp/script/amp.js'); +} diff --git a/ads/imobile.md b/ads/vendors/imobile.md similarity index 94% rename from ads/imobile.md rename to ads/vendors/imobile.md index e0d4562f9e089..723e7f9690e60 100644 --- a/ads/imobile.md +++ b/ads/vendors/imobile.md @@ -36,6 +36,6 @@ For configuration details and to generate your tags, please contact inquiry@i-mo Supported parameters: -- `data-pid` -- `data-asid` -- `data-adtype` +- `data-pid` +- `data-asid` +- `data-adtype` diff --git a/ads/vendors/imonomy.js b/ads/vendors/imonomy.js new file mode 100644 index 0000000000000..c945cc8511afb --- /dev/null +++ b/ads/vendors/imonomy.js @@ -0,0 +1,147 @@ +/** + * Copyright 2018 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. + */ + +import {doubleclick} from '../../ads/google/doubleclick'; +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, writeScript} from '../../3p/3p'; + +const DEFAULT_TIMEOUT = 500; // ms +const EVENT_SUCCESS = 0; +const EVENT_TIMEOUT = 1; +const EVENT_ERROR = 2; +const EVENT_BADTAG = 3; +const imonomyData = ['pid', 'subId', 'timeout']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function imonomy(global, data) { + if (!('slot' in data)) { + global.CasaleArgs = data; + writeScript(global, `//tag.imonomy.com/${data.pid}/indexJTag.js`); + } else { + //DFP ad request call + let calledDoubleclick = false; + data.timeout = isNaN(data.timeout) ? DEFAULT_TIMEOUT : data.timeout; + const timer = setTimeout(() => { + callDoubleclick(EVENT_TIMEOUT); + }, data.timeout); + const callDoubleclick = function (code) { + if (calledDoubleclick) { + return; + } + calledDoubleclick = true; + clearTimeout(timer); + reportStats(data, code); + prepareData(data); + doubleclick(global, data); + }; + + if (typeof data.pid === 'undefined' || isNaN(data.pid)) { + callDoubleclick(EVENT_BADTAG); + return; + } + + global.IndexArgs = { + ampCallback: callDoubleclick, + ampSuccess: EVENT_SUCCESS, + ampError: EVENT_ERROR, + }; + + loadScript( + global, + `//tag.imonomy.com/amp/${data.pid}/amp.js`, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); + } +} + +/** + * @param {*} data + */ +function prepareData(data) { + for (const attr in data) { + if (hasOwn(data, attr) && imonomyData.indexOf(attr) >= 0) { + delete data[attr]; + } + } + data.targeting = data.targeting || {}; + data.targeting['IMONOMY_AMP'] = '1'; +} + +/** + * @param {*} data + * @param {number} code + */ +function reportStats(data, code) { + try { + if (code == EVENT_BADTAG) { + return; + } + const xhttp = new XMLHttpRequest(); + xhttp.withCredentials = true; + let unitFormat = ''; + let pageLocation = ''; + if (typeof window.context.location.href !== 'undefined') { + pageLocation = encodeURIComponent(window.context.location.href); + } + const {pid, subId} = data, + trackId = 'AMP', + notFirst = true, + cid = '', + abLabel = '', + rand = Math.random(); + if (!isNaN(data.width) && !isNaN(data.height)) { + unitFormat = `${data.width}x${data.height}`; + } + const uid = '', + isLocked = false, + isTrackable = false, + isClient = false, + tier = 0; + const baseUrl = '//srv.imonomy.com/internal/reporter'; + let unitCodeUrl = `${baseUrl}?v=2&subid=${subId}&sid=${pid}&`; + unitCodeUrl = unitCodeUrl + `format=${unitFormat}&ai=`; + unitCodeUrl = unitCodeUrl + `${trackId}&ctxu=${pageLocation}&`; + unitCodeUrl = unitCodeUrl + `fb=${notFirst}&`; + unitCodeUrl = unitCodeUrl + `cid=${cid} &ab=${abLabel}&cbs=${rand}`; + if (uid) { + unitCodeUrl = unitCodeUrl + `&uid=${uid}`; + } + if (isLocked) { + unitCodeUrl = unitCodeUrl + `&is_locked=${isLocked}`; + } + if (isTrackable) { + unitCodeUrl = unitCodeUrl + `&istrk=${isTrackable}`; + } + if (isClient) { + unitCodeUrl = unitCodeUrl + `&is_client=${isClient}`; + if (tier) { + unitCodeUrl = unitCodeUrl + `&tier=${tier}`; + } + } + + xhttp.open('GET', unitCodeUrl, true); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(); + } catch (e) {} +} diff --git a/ads/imonomy.md b/ads/vendors/imonomy.md similarity index 95% rename from ads/imonomy.md rename to ads/vendors/imonomy.md index 02f02e677c04c..82839c471b289 100644 --- a/ads/imonomy.md +++ b/ads/vendors/imonomy.md @@ -42,8 +42,8 @@ Ad size is based on the `width` and `height` attributes of the `amp-ad` tag by d ### Required parameters -- `data-pid` -- `data-sub-id` -- `data-slot` +- `data-pid` +- `data-sub-id` +- `data-slot` -Additional parameters including `json` will be passed through in the resulting call to DFP. For details please see the [Doubleclick documentation](https://github.com/ampproject/amphtml/blob/master/ads/google/doubleclick.md). +Additional parameters including `json` will be passed through in the resulting call to DFP. For details please see the [Doubleclick documentation](https://github.com/ampproject/amphtml/blob/main/ads/google/doubleclick.md). diff --git a/ads/vendors/improvedigital.js b/ads/vendors/improvedigital.js new file mode 100755 index 0000000000000..6345dbd214a54 --- /dev/null +++ b/ads/vendors/improvedigital.js @@ -0,0 +1,64 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function improvedigital(global, data) { + validateData(data, ['placement'], ['width', 'height', 'optin', 'keyvalue']); + + let url = + 'https://ad.360yield.com' + + '/adj?' + + 'p=' + + encodeURIComponent(data.placement) + + '&w=' + + encodeURIComponent(data.width) + + '&h=' + + encodeURIComponent(data.height) + + '&optin=' + + encodeURIComponent(data.optin) + + '&tz=' + + new Date().getTimezoneOffset(); + + const value = data.keyvalue; + let newData = ''; + const amps = '&'; + let validKey = 0; + + if (value && value.length > 0) { + const keys = value.split('&'); + for (let i = 0; i < keys.length; i++) { + if (!keys[i]) { + continue; + } + const segment = keys[i].split('='); + const segment1 = segment[1] ? encodeURIComponent(segment[1]) : ''; + if (validKey > 0) { + newData += amps; + } + validKey++; + newData += segment[0] + '=' + segment1; + } + } + if (newData) { + url += '&' + newData; + } + writeScript(global, url); +} diff --git a/ads/improvedigital.md b/ads/vendors/improvedigital.md similarity index 91% rename from ads/improvedigital.md rename to ads/vendors/improvedigital.md index de732b05329c6..c3932ba6822d5 100755 --- a/ads/improvedigital.md +++ b/ads/vendors/improvedigital.md @@ -50,6 +50,6 @@ information on how to get the required placement IDs and [optional] keyvalue cas ### Supported parameters -- `placement`: mandatory -- `optin` -- `keyvalue`: Fill in the keyvalues as written in the example. +- `placement`: mandatory +- `optin` +- `keyvalue`: Fill in the keyvalues as written in the example. diff --git a/ads/industrybrains.md b/ads/vendors/industrybrains.md similarity index 94% rename from ads/industrybrains.md rename to ads/vendors/industrybrains.md index fc1f04bc42c14..5388519da7486 100644 --- a/ads/industrybrains.md +++ b/ads/vendors/industrybrains.md @@ -36,6 +36,6 @@ For configuration semantics, see [Industrybrains documentation](https://www.indu Supported parameters: -- `data-cid` -- `data-width` -- `data-height` +- `data-cid` +- `data-width` +- `data-height` diff --git a/ads/vendors/inmobi.js b/ads/vendors/inmobi.js new file mode 100644 index 0000000000000..47b9fa947a2e0 --- /dev/null +++ b/ads/vendors/inmobi.js @@ -0,0 +1,46 @@ +/** + * Copyright 2016 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. + */ + +import {setStyle} from '../../src/style'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function inmobi(global, data) { + validateData(data, ['siteid', 'slotid'], []); + + const inmobiConf = { + siteid: data.siteid, + slot: data.slotid, + manual: true, + onError: (code) => { + if (code == 'nfr') { + global.context.noContentAvailable(); + setStyle(document.getElementById('my-ad-slot'), 'display', 'none'); + } + }, + onSuccess: () => { + global.context.renderStart(); + }, + }; + + writeScript(global, 'https://cf.cdn.inmobi.com/ad/inmobi.secure.js', () => { + global.document.write("
"); + global._inmobi.getNewAd(document.getElementById('my-ad-slot'), inmobiConf); + }); +} diff --git a/ads/inmobi.md b/ads/vendors/inmobi.md similarity index 90% rename from ads/inmobi.md rename to ads/vendors/inmobi.md index ac89e47949abb..4dc11f65c6d52 100644 --- a/ads/inmobi.md +++ b/ads/vendors/inmobi.md @@ -60,8 +60,8 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-siteid`: Site Id is the InMobi property id. You can get this from InMobi dashboard. -- `data-slotid`: Slot Id is the ad size. +- `data-siteid`: Site Id is the InMobi property id. You can get this from InMobi dashboard. +- `data-slotid`: Slot Id is the ad size. ### Test Ads diff --git a/ads/vendors/innity.js b/ads/vendors/innity.js new file mode 100644 index 0000000000000..d2b4d66f520cf --- /dev/null +++ b/ads/vendors/innity.js @@ -0,0 +1,41 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function innity(global, data) { + validateData(data, ['pub', 'zone'], ['channel']); + writeScript(global, 'https://cdn.innity.net/admanager.js', () => { + const innityAMPZone = global.innity_adZone; + const innityAMPTag = new innityAMPZone( + encodeURIComponent(data.pub), + encodeURIComponent(data.zone), + { + width: data.width, + height: data.height, + channel: data.channel ? encodeURIComponent(data.channel) : '', + } + ); + // AMP handling or noContentAvailable + innityAMPTag.amp(global.context); + // else renderStart (with at least house ad) + global.context.renderStart(); + }); +} diff --git a/ads/innity.md b/ads/vendors/innity.md similarity index 91% rename from ads/innity.md rename to ads/vendors/innity.md index 517a6e1fd5e54..d322e90ddc761 100644 --- a/ads/innity.md +++ b/ads/vendors/innity.md @@ -35,9 +35,9 @@ For semantics of configuration, please reach out to your account manager. ### Required parameters -- `data-pub`: Publisher ID -- `data-zone`: Zone ID +- `data-pub`: Publisher ID +- `data-zone`: Zone ID ### Optional parameters -- `data-channel`: Channel code +- `data-channel`: Channel code diff --git a/ads/vendors/insticator.js b/ads/vendors/insticator.js new file mode 100644 index 0000000000000..b86fd097fbcaf --- /dev/null +++ b/ads/vendors/insticator.js @@ -0,0 +1,99 @@ +/** + * Copyright 2019 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. + */ + +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function insticator(global, data) { + // validate passed data attributes + validateData(data, ['siteId', 'embedId']); + + // create insticator markup + global.document + .getElementById('c') + .appendChild(createTemplate(data['embedId'])); + + // create ads and embed + createAdsAndEmbed(data['siteId'], data['embedId']); +} + +/** + * Create HTML template to be populated later + * @param {string} embedId The Unique Identifier of this particular Embed + * @return {Element} HTML template + */ +function createTemplate(embedId) { + const template = document.createElement('template'); + template./*OK*/ innerHTML = ` +
+
+
+
+
+ `; + return template.content; +} + +/** + * Generates Ads and Embed + * @param {string} siteId Used to grab the ads file + * @param {string} embedId Used to grab the unique embed requested + */ +function createAdsAndEmbed(siteId, embedId) { + // helper vars + const a = window; + const c = document; + const s = 'script'; + const u = `//d3lcz8vpax4lo2.cloudfront.net/ads-code/${siteId}.js`; // vars from preconnect urls and data attributes on amp-embed tag + + // create insticator object on the window + 'Insticator' in a || + (a.Insticator = { + ad: { + loadAd: function (b) { + a.Insticator.ad.q.push(b); + }, + q: [], + }, + helper: {}, + embed: {}, + version: '4.0', + q: [], + amp: null, // this will get set to window.context which is the AMP API so we can access from our ads code + load: function (t, o) { + a.Insticator.amp = window.context; // set the Insticator object property amp to window.context which is the AMP API so we can access from our ads code + a.Insticator.q.push({t, o}); + }, + }); + + // load ads code + const b = c.createElement(s); + b.src = u; + b.async = !0; + const d = c.getElementsByTagName(s)[0]; + d.parentNode.insertBefore(b, d); + + // execute functions of insticator object on the window (load ads and embed) + a.Insticator.ad.loadAd('div-insticator-ad-1'); + a.Insticator.ad.loadAd('div-insticator-ad-2'); + a.Insticator.load('em', {id: embedId}); + + // now tell AMP runtime to start rendering ads + window.context.renderStart(); +} diff --git a/ads/insticator.md b/ads/vendors/insticator.md similarity index 90% rename from ads/insticator.md rename to ads/vendors/insticator.md index 8a2d15c770031..70b7c0e0ee606 100644 --- a/ads/insticator.md +++ b/ads/vendors/insticator.md @@ -37,5 +37,5 @@ For details on the configuration semantics, please contact [Insticator](https:// ### Required parameters -- `data-site-id`: ID of the site your Insticator Embed lives in -- `data-embed-id`: ID of your Insticator Embed +- `data-site-id`: ID of the site your Insticator Embed lives in +- `data-embed-id`: ID of your Insticator Embed diff --git a/ads/vendors/invibes.js b/ads/vendors/invibes.js new file mode 100644 index 0000000000000..468ebeb1e4c1e --- /dev/null +++ b/ads/vendors/invibes.js @@ -0,0 +1,73 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function invibes(global, data) { + global.invibesAmp = { + allowedData: ['adCateg', 'pid', 'customEndpoint'], + mandatoryData: [], + data, + }; + + validateData( + data, + global.invibesAmp.mandatoryData, + global.invibesAmp.allowedData + ); + + let url = data.customEndpoint || 'https://k.r66net.com/GetAmpLink'; + + if (data.adCateg) { + url = addQueryParam(url, 'adCateg', data.adCateg); + } + + if (data.pid) { + url = addQueryParam(url, 'pid', data.pid); + } + + if ( + window && + window.context && + window.context.location && + window.context.location.href + ) { + url = addQueryParam(url, 'referrerUrl', window.context.location.href); + } + + loadScript(global, url); +} + +/** + * @param {string} url + * @param {string} param + * @param {string} value + * @return {string} + */ +function addQueryParam(url, param, value) { + const paramValue = + encodeURIComponent(param) + '=' + encodeURIComponent(value); + if (url.indexOf('?') > -1) { + url += '&' + paramValue; + } else { + url += '?' + paramValue; + } + return url; +} diff --git a/ads/vendors/invibes.md b/ads/vendors/invibes.md new file mode 100644 index 0000000000000..2b06e7d5bb170 --- /dev/null +++ b/ads/vendors/invibes.md @@ -0,0 +1,50 @@ + + +# Invibes + +## Example + +```html + + +``` + +## Configuration + +For configuration semantics, please contact [Invibes](https://www.invibes.com/#section-contact-email) + +Supported parameters: + +- `data-pid` +- `data-ad-categ` +- `data-custom-endpoint` + +## User Consent Integration + +Invibes ad approaches user consent as follows: + +- `CONSENT_POLICY_STATE.UNKNOWN`: Serve a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. diff --git a/ads/vendors/iprom.js b/ads/vendors/iprom.js new file mode 100644 index 0000000000000..45da9c623d7f3 --- /dev/null +++ b/ads/vendors/iprom.js @@ -0,0 +1,58 @@ +/** + * Copyright 2015 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. + */ + +import {parseJson} from '../../src/core/types/object/json'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function iprom(global, data) { + validateData(data, ['zone', 'sitepath'], ['keywords', 'channels']); + + const ampdata = { + sitepath: '[]', + zone: [], + keywords: '', + channels: '', + ...data, + }; + + /** + * Callback for WriteScript + */ + function namespaceLoaded() { + const ipromNS = window.ipromNS || {}; + + ipromNS.AdTag = ipromNS.AdTag || {}; + + const config = { + sitePath: parseJson(ampdata.sitepath), + containerId: 'c', + zoneId: ampdata.zone, + prebid: true, + amp: true, + keywords: ampdata.keywords ? ampdata.keywords.split(',') : [], + channels: ampdata.channels ? ampdata.channels.split(',') : [], + }; + + const tag = new ipromNS.AdTag(config); + tag.init(); + } + + writeScript(global, 'https://cdn.ipromcloud.com/ipromNS.js', namespaceLoaded); +} diff --git a/ads/iprom.md b/ads/vendors/iprom.md similarity index 86% rename from ads/iprom.md rename to ads/vendors/iprom.md index e9fada23591a4..79158a126ba1d 100644 --- a/ads/iprom.md +++ b/ads/vendors/iprom.md @@ -40,10 +40,10 @@ information on how to get the required ad tag. Required: -- `data-zone`: Adserver zone ID -- `data-sitepath`: Sitepath of your page +- `data-zone`: Adserver zone ID +- `data-sitepath`: Sitepath of your page Optional: -- `data-keywords`: Comma delimited -- `data-channels`: Comma delimited +- `data-keywords`: Comma delimited +- `data-channels`: Comma delimited diff --git a/ads/vendors/ix.js b/ads/vendors/ix.js new file mode 100644 index 0000000000000..7eb8627e21032 --- /dev/null +++ b/ads/vendors/ix.js @@ -0,0 +1,131 @@ +/** + * Copyright 2016 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. + */ + +import {doubleclick} from '../../ads/google/doubleclick'; +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, writeScript} from '../../3p/3p'; + +const DEFAULT_TIMEOUT = 500; // ms +const EVENT_SUCCESS = 0; +const EVENT_TIMEOUT = 1; +const EVENT_ERROR = 2; +const EVENT_BADTAG = 3; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ix(global, data) { + if (!('slot' in data)) { + global.CasaleArgs = data; + writeScript(global, 'https://js-sec.indexww.com/indexJTag.js'); + } else { + //DFP ad request call + + const start = Date.now(); + let calledDoubleclick = false; + data.ixTimeout = isNaN(data.ixTimeout) ? DEFAULT_TIMEOUT : data.ixTimeout; + const timer = setTimeout(() => { + callDoubleclick(EVENT_TIMEOUT); + }, data.ixTimeout); + + const callDoubleclick = function (code) { + if (calledDoubleclick) { + return; + } + calledDoubleclick = true; + clearTimeout(timer); + reportStats(data.ixId, data.ixSlot, data.slot, start, code); + prepareData(data); + doubleclick(global, data); + }; + + if (typeof data.ixId === 'undefined' || isNaN(data.ixId)) { + callDoubleclick(EVENT_BADTAG); + return; + } + + global.IndexArgs = { + ampCallback: callDoubleclick, + ampSuccess: EVENT_SUCCESS, + ampError: EVENT_ERROR, + }; + + loadScript( + global, + 'https://js-sec.indexww.com/apl/amp.js', + undefined, + () => { + callDoubleclick(EVENT_ERROR); + } + ); + } +} + +/** + * @param {!Object} data + */ +function prepareData(data) { + for (const attr in data) { + if (hasOwn(data, attr) && /^ix[A-Z]/.test(attr)) { + delete data[attr]; + } + } + data.targeting = data.targeting || {}; + data.targeting['IX_AMP'] = '1'; +} + +/** + * @param {string} siteID + * @param {string} slotID + * @param {string} dfpSlot + * @param {number} start + * @param {number} code + */ +function reportStats(siteID, slotID, dfpSlot, start, code) { + try { + if (code == EVENT_BADTAG) { + return; + } + const xhttp = new XMLHttpRequest(); + xhttp.withCredentials = true; + + const deltat = Date.now() - start; + const ts = (start / 1000) >> 0; + const ets = (Date.now() / 1000) >> 0; + let url = 'https://as-sec.casalemedia.com/headerstats?s=' + siteID; + if (typeof window.context.location.href !== 'undefined') { + url += '&u=' + encodeURIComponent(window.context.location.href); + } + let stats = '{"p":"display","d":"mobile","t":' + ts + ','; + stats += '"sl":[{"s": "' + slotID + '",'; + stats += '"t":' + ets + ','; + stats += '"e": [{'; + if (code == EVENT_SUCCESS) { + stats += '"n":"amp-s",'; + } else if (code == EVENT_TIMEOUT) { + stats += '"n":"amp-t",'; + } else { + stats += '"n":"amp-e",'; + } + stats += '"v":"' + deltat + '",'; + stats += '"b": "INDX","x": "' + dfpSlot.substring(0, 64) + '"}]}]}'; + + xhttp.open('POST', url, true); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(stats); + } catch (e) {} +} diff --git a/ads/vendors/ix.md b/ads/vendors/ix.md new file mode 100644 index 0000000000000..a3a32a3976dc0 --- /dev/null +++ b/ads/vendors/ix.md @@ -0,0 +1,114 @@ + + +# Index Exchange AMP RTC + +Index Exchange (IX) supports [AMP Real Time Config (RTC)](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/rtc-publisher-implementation-guide.md) which allows Publishers to augment their ad requests with targeting information that is retrieved at runtime. This document provides instructions on adding IX as a vendor to AMP pages. + +## Configuration + +Each [amp-ad](https://amp.dev/documentation/components/amp-ad/) element that uses RTC must have the `rtc-config` attribute set with valid JSON. + +**Attributes** + +- ``: Required. IX `` tags require the `width`, `height`, and `type="doubleclick"` parameters.
+ **Note**: IX leverages AMP through Google Ad Manager (GAM, formerly DoubleClick for Publishers). + +- `data-slot`: Required. Data attributes to serve ads. + +- `data-multi-size`: Optional. A string of comma separated sizes, which if present, forces the tag to request an ad with all of the given sizes, including the primary size. The `width` and `height` attributes are always included as one of the valid sizes, unless overridden by `data-override-width` and `data-override-height` attributes which change the size of creatives eligible for the slot. For details refer to the [Multi-size Ad documentation](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-doubleclick-impl/multi-size.md). + +- `data-multi-size-validation`: Optional. If set to false, this allows secondary sizes specified in the `data-multi-size` attribute to be less than 2/3rds of the corresponding primary size. By default, this is assumed to be true. + +- `rtc-config`: JSON configuration data which handles the communication with AMP RTC. + - `vendors` : Required object. The key is `IndexExchange` and the value is the `SITE_ID`.
+ **Note:** Refer to the materials provided by your account team for your specific SITE_ID details. We recommend one SITE_ID per domain, per unique slot and size. To use more than one SITE_ID, contact your IX Representative. + - `timeoutMillis`: Optional integer. Defines the timeout in milliseconds for each individual RTC callout. The configured timeout must be greater than 0 and less than 1000ms. If omitted, the timeout value defaults to 1000ms. + +### Example: RTC Specification on an amp-ad + +``` + + + +``` + +The value of `rtc-config` must conform to the following specification: + +``` +{ + "vendors": { + "IndexExchange": {"SITE_ID": "123456"} + }, + "timeoutMillis": 1000 +} +``` + +### Example: Multi-size request on an amp-ad + +```html + + + +``` + +### Example: Multi-size request on an amp-ad ignoring size validation + +```html + + + +``` + +Additional parameters including JSON are passed through in the resulting call to GAM. For details refer to the [Google Ad Manager documentation](https://github.com/ampproject/amphtml/blob/main/extensions/amp-ad-network-doubleclick-impl/amp-ad-network-doubleclick-impl-internal.md). + +To learn about the required Google Ad Manager (GAM) configuration, refer to [Index Exchange Knowledge Base](https://kb.indexexchange.com/Mobile/About_AMP.htm). diff --git a/ads/vendors/jubna.js b/ads/vendors/jubna.js new file mode 100644 index 0000000000000..57655cfdbf599 --- /dev/null +++ b/ads/vendors/jubna.js @@ -0,0 +1,31 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function jubna(global, data) { + validateData(data, ['wid', 'pid']); + global._jubna = global._jubna || { + widgetID: data['wid'], + pubID: data['pid'], + referrer: global.context.referrer, + }; + loadScript(global, 'https://cdn.jubna.com/adscripts/jb_amp_loader.js'); +} diff --git a/ads/jubna.md b/ads/vendors/jubna.md similarity index 92% rename from ads/jubna.md rename to ads/vendors/jubna.md index 5c164aa3b2382..522a3c9bb4d8e 100644 --- a/ads/jubna.md +++ b/ads/vendors/jubna.md @@ -36,5 +36,5 @@ For details on the configuration semantics, please contact Jubna or refer to the ### Required parameters -- `wid`: Widget ID -- `pid`: Publisher ID || Uniquie ID of your account +- `wid`: Widget ID +- `pid`: Publisher ID || Uniquie ID of your account diff --git a/ads/vendors/kargo.js b/ads/vendors/kargo.js new file mode 100644 index 0000000000000..45743e8bd78b4 --- /dev/null +++ b/ads/vendors/kargo.js @@ -0,0 +1,72 @@ +/** + * Copyright 2016 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. + */ + +import {computeInMasterFrame, loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function kargo(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + + validateData(data, ['site', 'slot'], ['options']); + + // Kargo AdTag url + const kargoScriptUrl = + 'https://storage.cloud.kargo.com/ad/network/tag/v3/' + data.site + '.js'; + + // parse extra ad call options (optional) + let options = {}; + if (data.options != null) { + try { + options = JSON.parse(data.options); + } catch (e) {} + } + + // Add window source reference to ad options + options.source_window = global; + + computeInMasterFrame( + global, + 'kargo-load', + function (done) { + // load AdTag in Master window + loadScript(global, kargoScriptUrl, () => { + let success = false; + if (global.Kargo != null && global.Kargo.loaded) { + success = true; + } + + done(success); + }); + }, + (success) => { + if (success) { + const w = options.source_window; + + // Add reference to Kargo api to this window if it's not the Master window + if (!w.context.isMaster) { + w.Kargo = w.context.master.Kargo; + } + + w.Kargo.getAd(data.slot, options); + } else { + throw new Error('Kargo AdTag failed to load'); + } + } + ); +} diff --git a/ads/kargo.md b/ads/vendors/kargo.md similarity index 94% rename from ads/kargo.md rename to ads/vendors/kargo.md index 7daa9256cf0c0..a2c0f4950a5c2 100644 --- a/ads/kargo.md +++ b/ads/vendors/kargo.md @@ -36,6 +36,6 @@ For configuration semantics, [contact Kargo](http://www.kargo.com/contact/). Supported parameters: -- `data-site` -- `data-slot` -- `data-options` +- `data-site` +- `data-slot` +- `data-options` diff --git a/ads/vendors/ketshwa.js b/ads/vendors/ketshwa.js new file mode 100644 index 0000000000000..0e9a976b7df4b --- /dev/null +++ b/ads/vendors/ketshwa.js @@ -0,0 +1,40 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ketshwa(global, data) { + validateData(data, ['widgetid', 'externalid'], []); + + const {externalid, widgetid} = data; + const skey = `widget_${widgetid}`; + + const dv = global.document.createElement('div'); + dv.id = skey; + global.document.getElementById('c').appendChild(dv); + + writeScript( + global, + `https://widget-cdn.ketshwa.com/m/p/${widgetid}/${externalid}.js`, + () => { + global.KetshwaSDK.showWidget(widgetid, skey); + } + ); +} diff --git a/ads/vendors/ketshwa.md b/ads/vendors/ketshwa.md new file mode 100644 index 0000000000000..f41bd06bec626 --- /dev/null +++ b/ads/vendors/ketshwa.md @@ -0,0 +1,38 @@ + + +# Ketshwa + +Serves ads from [Ketshwa](https://www.ketshwa.com/). + +## Example + +### Widgets + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +#### Required parameters + +- `widgetid` +- `externalid` diff --git a/ads/vendors/kiosked.js b/ads/vendors/kiosked.js new file mode 100644 index 0000000000000..15ca41854cd09 --- /dev/null +++ b/ads/vendors/kiosked.js @@ -0,0 +1,50 @@ +/** + * Copyright 2016 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function kiosked(global, data) { + let scriptId; + validateData(data, ['scriptid'], []); + if (hasOwn(data, 'scriptid')) { + scriptId = data['scriptid']; + } + window.addEventListener( + 'kioskedAdRender', + function () { + global.context.renderStart(); + }, + false + ); + + window.addEventListener( + 'kioskedAdNoFill', + function () { + global.context.noContentAvailable(); + }, + false + ); + + writeScript( + global, + 'https://scripts.kiosked.com/loader/kiosked-ad.js?staticTagId=' + scriptId + ); +} diff --git a/ads/kiosked.md b/ads/vendors/kiosked.md similarity index 97% rename from ads/kiosked.md rename to ads/vendors/kiosked.md index 7c4c8323d959a..1959888affca3 100644 --- a/ads/kiosked.md +++ b/ads/vendors/kiosked.md @@ -29,4 +29,4 @@ For testing purposes you can use `data-scriptid="91"`. ### Required parameters -- `data-scriptid` +- `data-scriptid` diff --git a/ads/vendors/kixer.js b/ads/vendors/kixer.js new file mode 100644 index 0000000000000..4e47c1b9c6b6a --- /dev/null +++ b/ads/vendors/kixer.js @@ -0,0 +1,92 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {parseUrlDeprecated} from '../../src/url'; + +/* global +__kxamp: false, +__kx_ad_slots: false, +__kx_ad_start: false, +__kx_viewability: false, +*/ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function kixer(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + + validateData(data, ['adslot'], []); + + let inView = false; + let viewed = false; + let viewTimer = null; + + const d = global.document.createElement('div'); + d.id = '__kx_ad_' + data.adslot; + global.document.getElementById('c').appendChild(d); + + const kxload = function () { + d.removeEventListener('load', kxload, false); + if (d.childNodes.length > 0) { + global.context.renderStart(); + } else { + global.context.noContentAvailable(); + } + }; + d.addEventListener('load', kxload, false); // Listen for the kixer load event + + const kxviewCheck = function (intersectionEntry) { + inView = intersectionEntry.intersectionRatio > 0.5; // Half of the unit is in the viewport + if (inView) { + if (!viewed && viewTimer == null) { + // If the ad hasn't been viewed and the timer is not set + viewTimer = setTimeout(kxviewFire, 900); // Set a Timeout to check the ad in 900ms and fire the view + } + } else { + if (viewTimer) { + // If the Timeout is set + clearTimeout(viewTimer); // Clear the Timeout + viewTimer = null; + } + } + }; + + const kxviewFire = function () { + if (inView) { + // if the ad is still in the viewport + if (typeof __kx_viewability.process_locked === 'function') { + viewed = true; + __kx_viewability.process_locked(data.adslot); // Fire kixer view + } + } + }; + + global.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + kxviewCheck(c); + }); + }); + + loadScript(global, 'https://cdn.kixer.com/ad/load.js', () => { + global.__kx_domain = parseUrlDeprecated(global.context.sourceUrl).hostname; // Get domain + __kxamp[data.adslot] = 1; + __kx_ad_slots.push(data.adslot); + __kx_ad_start(); + }); +} diff --git a/ads/kixer.md b/ads/vendors/kixer.md similarity index 97% rename from ads/kixer.md rename to ads/vendors/kixer.md index 8fe02dcdbb70b..2fdc94d62b414 100644 --- a/ads/kixer.md +++ b/ads/vendors/kixer.md @@ -28,4 +28,4 @@ For ad slot setup, please [contact Kixer](http://kixer.com). Supported parameters: -- `data-adslot` +- `data-adslot` diff --git a/ads/vendors/kuadio.js b/ads/vendors/kuadio.js new file mode 100644 index 0000000000000..c713defcece94 --- /dev/null +++ b/ads/vendors/kuadio.js @@ -0,0 +1,45 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function kuadio(global, data) { + validateData( + data, + ['widgetId'], + ['region', 'baseUrl', 'betaMode', 'debugMode', 'fastParse', 'ref'] + ); + + global._pvmax = { + region: data.region, + baseUrl: data.baseUrl, + betaMode: data.betaMode === 'true', + debugMode: data.debugMode === 'true', + fastParse: data.fastParse !== 'false', + }; + + const e = global.document.createElement('div'); + e.className = '_pvmax_recommend'; + e.setAttribute('data-widget-id', data.widgetId); + e.setAttribute('data-ref', data.ref || global.context.canonicalUrl); + global.document.getElementById('c').appendChild(e); + + loadScript(global, 'https://api.pvmax.net/v1.0/pvmax.js'); +} diff --git a/ads/kuadio.md b/ads/vendors/kuadio.md similarity index 89% rename from ads/kuadio.md rename to ads/vendors/kuadio.md index 261fb675c581b..16c7d00538ea4 100644 --- a/ads/kuadio.md +++ b/ads/vendors/kuadio.md @@ -37,12 +37,12 @@ For configuration semantics, contact [Kuadio](https://www.tenmax.io/kuadio). ### Required parameters -- `data-widget-id` +- `data-widget-id` ### Optional parameters -- `data-region` -- `data-debug-mode` -- `data-beta-mode` -- `data-fast-parse` -- `data-ref` +- `data-region` +- `data-debug-mode` +- `data-beta-mode` +- `data-fast-parse` +- `data-ref` diff --git a/ads/vendors/lentainform.js b/ads/vendors/lentainform.js new file mode 100644 index 0000000000000..a23b562e58394 --- /dev/null +++ b/ads/vendors/lentainform.js @@ -0,0 +1,55 @@ +/** + * Copyright 2019 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. + */ + +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/lentainform.md b/ads/vendors/lentainform.md similarity index 91% rename from ads/lentainform.md rename to ads/vendors/lentainform.md index 2ce59e9b5f509..9fd78dd84b858 100644 --- a/ads/lentainform.md +++ b/ads/vendors/lentainform.md @@ -35,10 +35,11 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-publisher` -- `data-widget` -- `data-container` +- `data-publisher` +- `data-widget` +- `data-container` ### Optional parameters -- `data-url` +- `data-url` +- `data-options` diff --git a/ads/vendors/ligatus.js b/ads/vendors/ligatus.js new file mode 100644 index 0000000000000..32f5ab263ab81 --- /dev/null +++ b/ads/vendors/ligatus.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateSrcPrefix, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ligatus(global, data) { + const {src} = data; + validateSrcPrefix('https://a-ssl.ligatus.com/', src); + writeScript(global, src); +} diff --git a/ads/ligatus.md b/ads/vendors/ligatus.md similarity index 98% rename from ads/ligatus.md rename to ads/vendors/ligatus.md index b6f02c0531110..73272755f5984 100644 --- a/ads/ligatus.md +++ b/ads/vendors/ligatus.md @@ -34,4 +34,4 @@ For further configuration details, please contact mobile@ligatus.com Supported parameters: -- `src` +- `src` diff --git a/ads/vendors/lockerdome.js b/ads/vendors/lockerdome.js new file mode 100644 index 0000000000000..e9d9d0e92dcf3 --- /dev/null +++ b/ads/vendors/lockerdome.js @@ -0,0 +1,27 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function lockerdome(global, data) { + validateData(data, ['slot']); + global.SLOT = data.slot; + loadScript(global, 'https://cdn2.lockerdomecdn.com/_js/amp.js'); +} diff --git a/ads/lockerdome.md b/ads/vendors/lockerdome.md similarity index 98% rename from ads/lockerdome.md rename to ads/vendors/lockerdome.md index 13f38856944b9..4dc518a4fc8dc 100644 --- a/ads/lockerdome.md +++ b/ads/vendors/lockerdome.md @@ -34,4 +34,4 @@ For details on the configuration semantics, please contact [LockerDome](https:// ### Required parameters -- `data-slot` +- `data-slot` diff --git a/ads/vendors/logly.js b/ads/vendors/logly.js new file mode 100644 index 0000000000000..8fbe7ead5fd60 --- /dev/null +++ b/ads/vendors/logly.js @@ -0,0 +1,35 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function logly(global, data) { + validateData(data, ['adspotid']); + + const d = global.document.createElement('div'); + d.id = 'logly-lift-' + data['adspotid']; + global.document.getElementById('c').appendChild(d); + + const url = + 'https://l.logly.co.jp/lift_widget.js' + + `?adspot_id=${encodeURIComponent(data['adspotid'])}`; + + loadScript(global, url); +} diff --git a/ads/logly.md b/ads/vendors/logly.md similarity index 97% rename from ads/logly.md rename to ads/vendors/logly.md index 2e04cb0158698..27f810a7a64a9 100644 --- a/ads/logly.md +++ b/ads/vendors/logly.md @@ -35,4 +35,4 @@ For configuration details, please contact [LOGLY](https://www.logly.co.jp/forms/ Supported parameters: -- `data-adspotid` +- `data-adspotid` diff --git a/ads/vendors/loka.js b/ads/vendors/loka.js new file mode 100644 index 0000000000000..b84d517e486fa --- /dev/null +++ b/ads/vendors/loka.js @@ -0,0 +1,38 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function loka(global, data) { + validateData(data, ['unitParams'], []); + + global.lokaParams = data; + + const container = global.document.querySelector('#c'); + container.addEventListener('lokaUnitLoaded', (e) => { + if (e.detail.isReady) { + global.context.renderStart(); + } else { + global.context.noContentAvailable(); + } + }); + + loadScript(global, 'https://loka-cdn.akamaized.net/scene/amp.js'); +} diff --git a/ads/loka.md b/ads/vendors/loka.md similarity index 91% rename from ads/loka.md rename to ads/vendors/loka.md index 4ed9fb4b797f5..92eccbce79490 100644 --- a/ads/loka.md +++ b/ads/vendors/loka.md @@ -46,16 +46,16 @@ For configuration details and to generate your tags, please contact https://loka Supported parameters: -- `data-unit-params`: JSON value +- `data-unit-params`: JSON value Required JSON fields: -- `unit`: Ad type -- `id`: Delivery ID for Ad +- `unit`: Ad type +- `id`: Delivery ID for Ad Optional JSON fields: -- `tag`: User specified value +- `tag`: User specified value ## Design information diff --git a/ads/vendors/luckyads.js b/ads/vendors/luckyads.js new file mode 100644 index 0000000000000..bea547bcecb03 --- /dev/null +++ b/ads/vendors/luckyads.js @@ -0,0 +1,38 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function luckyads(global, data) { + validateData(data, ['src', 'laBlock']); + 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-la-block', data['laBlock']); + global.document.getElementById('c').appendChild(d); +} diff --git a/ads/vendors/luckyads.md b/ads/vendors/luckyads.md new file mode 100644 index 0000000000000..d9d94c215477c --- /dev/null +++ b/ads/vendors/luckyads.md @@ -0,0 +1,37 @@ + + +# LuckyAds + +Provides support for [LuckyAds](https://luckyads.pro/) widgets. + +### Required parameters + +- `src` +- `data-la-block` + +## Example + +```html + + +``` diff --git a/ads/vendors/macaw.js b/ads/vendors/macaw.js new file mode 100644 index 0000000000000..4098357aceb0c --- /dev/null +++ b/ads/vendors/macaw.js @@ -0,0 +1,41 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function macaw(global, data) { + validateData(data, ['blockId']); + + const url = + 'https://code.macaw.is/data/' + + encodeURIComponent(data['blockId']) + + '.js?async=1&div=c'; + + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/vendors/macaw.md b/ads/vendors/macaw.md new file mode 100644 index 0000000000000..a319f63745fd6 --- /dev/null +++ b/ads/vendors/macaw.md @@ -0,0 +1,31 @@ + + +# Macaw + +## Example + +```html + +``` + +## Configuration + +For more information, please [see FAQ](https://macaw.is/faq). + +Required parameters: + +- data-block-id diff --git a/ads/vendors/mads.js b/ads/vendors/mads.js new file mode 100644 index 0000000000000..b3795979b4a2f --- /dev/null +++ b/ads/vendors/mads.js @@ -0,0 +1,29 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function mads(global, data) { + validateData(data, ['adrequest'], []); + + writeScript(global, 'https://eu2.madsone.com/js/tags.js', function () { + window.MADSAdrequest.adrequest(JSON.parse(data.adrequest)); + }); +} diff --git a/ads/mads.md b/ads/vendors/mads.md similarity index 94% rename from ads/mads.md rename to ads/vendors/mads.md index a28d4815c06bb..2e1b222e38ffb 100644 --- a/ads/mads.md +++ b/ads/vendors/mads.md @@ -36,4 +36,4 @@ For semantics of configuration, please see [MADS documentation](http://wiki.mads Supported parameters: -- `data-adrequest`: MADS ad request parameters +- `data-adrequest`: MADS ad request parameters diff --git a/ads/mantis.js b/ads/vendors/mantis.js similarity index 96% rename from ads/mantis.js rename to ads/vendors/mantis.js index 5712ea12522b6..45978b9500008 100644 --- a/ads/mantis.js +++ b/ads/vendors/mantis.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {loadScript, validateData} from '../3p/3p'; +import {loadScript, validateData} from '../../3p/3p'; /** * @param {!Window} global diff --git a/ads/mantis.md b/ads/vendors/mantis.md similarity index 92% rename from ads/mantis.md rename to ads/vendors/mantis.md index c709d2ee6d509..c9233e5ff2c02 100644 --- a/ads/mantis.md +++ b/ads/vendors/mantis.md @@ -35,8 +35,8 @@ Please visit the [MANTIS® Ad Network website](https://www.mantisadnetwork.com) Supported parameters: -- `data-property` -- `data-zone` +- `data-property` +- `data-zone` ### Content Recommendation @@ -56,5 +56,5 @@ Depending on your page design, you may need to play with the `"heights="` parame Supported parameters: -- `data-property` -- `data-css` (Overrides the default CSS embedded by the script) +- `data-property` +- `data-css` (Overrides the default CSS embedded by the script) diff --git a/ads/vendors/marfeel.js b/ads/vendors/marfeel.js new file mode 100644 index 0000000000000..b33af862e62ae --- /dev/null +++ b/ads/vendors/marfeel.js @@ -0,0 +1,30 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function marfeel(global, data) { + validateData(data, ['tenant']); + + const {tenant, version} = data; + const versionQS = version ? `?v=${version}` : ''; + + loadScript(global, `https://live.mrf.io/amp-ad/${tenant}/index${versionQS}`); +} diff --git a/ads/vendors/marfeel.md b/ads/vendors/marfeel.md new file mode 100644 index 0000000000000..147fae7fbb615 --- /dev/null +++ b/ads/vendors/marfeel.md @@ -0,0 +1,46 @@ + + +# Marfeel + +## Example + +```html + + +``` + +## Configuration + +For additional details and support, please contact [Marfeel](https://marfeel.com). + +### Required parameters + +- `data-tenant` + +### Optional parameters + +- `data-multisize` +- `data-version` + +## Consent Support + +When [user consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required, Marfeel ad approaches user consent in the following ways: + +- `CONSENT_POLICY_STATE.SUFFICIENT`: Marfeel amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Marfeel amp-ad will display a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Marfeel amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN`: Marfeel amp-ad will display a non-personalized ad to the user. diff --git a/ads/vendors/mediaad.js b/ads/vendors/mediaad.js new file mode 100644 index 0000000000000..dc19f44244f4f --- /dev/null +++ b/ads/vendors/mediaad.js @@ -0,0 +1,53 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function mediaad(global, data) { + // ensure we have vlid publisher, placement and mode + // and exactly one page-type + validateData(data, ['medtag', 'publisher']); + + const d = document.getElementById('c'); + const meddiv = document.createElement('div'); + meddiv.setAttribute('id', data['medtag']); + d.appendChild(meddiv); + + global._mediaad = global._mediaad || []; + + // install observation on entering/leaving the view + global.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + if (c.intersectionRect.height) { + global._mediaad.push({ + medtag: data['medtag'], + publisher: data.publisher, + }); + } + }); + }); + + loadScript( + global, + `https://s1.mediaad.org/serve/${encodeURIComponent( + data.publisher + )}/loader.js` + ); +} diff --git a/ads/mediaad.md b/ads/vendors/mediaad.md similarity index 95% rename from ads/mediaad.md rename to ads/vendors/mediaad.md index dcf319482099a..7d801cc0a85d8 100644 --- a/ads/mediaad.md +++ b/ads/vendors/mediaad.md @@ -37,5 +37,5 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-publisher` -- `data-medtag` +- `data-publisher` +- `data-medtag` diff --git a/ads/vendors/medianet.js b/ads/vendors/medianet.js new file mode 100644 index 0000000000000..ce3738f0a3b79 --- /dev/null +++ b/ads/vendors/medianet.js @@ -0,0 +1,231 @@ +/** + * Copyright 2016 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. + */ + +import {computeInMasterFrame, validateData, writeScript} from '../../3p/3p'; +import {getSourceUrl, parseUrlDeprecated} from '../../src/url'; +import {hasOwn} from '../../src/core/types/object'; + +const mandatoryParams = ['tagtype', 'cid'], + optionalParams = [ + 'timeout', + 'crid', + 'misc', + 'slot', + 'targeting', + 'categoryExclusions', + 'tagForChildDirectedTreatment', + 'cookieOptions', + 'overrideWidth', + 'overrideHeight', + 'loadingStrategy', + 'consentNotificationId', + 'useSameDomainRenderingUntilDeprecated', + 'experimentId', + 'multiSize', + 'multiSizeValidation', + ]; +// useSameDomainRenderingUntilDeprecated is included to ensure publisher +// amp-tags don't break before 29th March + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function medianet(global, data) { + validateData(data, mandatoryParams, optionalParams); + + const publisherUrl = + global.context.canonicalUrl || getSourceUrl(global.context.location.href), + referrerUrl = global.context.referrer; + + if (data.tagtype === 'headerbidder') { + //parameter tagtype is used to identify the product the publisher is using. Going ahead we plan to support more product types. + loadHBTag(global, data, publisherUrl, referrerUrl); + } else if (data.tagtype === 'cm' && data.crid) { + loadCMTag(global, data, publisherUrl, referrerUrl); + } else { + global.context.noContentAvailable(); + } +} + +/** + * @return {{renderStartCb: (function(*=)), reportRenderedEntityIdentifierCb: (function(*=)), noContentAvailableCb: (function())}} + */ +function getCallbacksObject() { + return { + renderStartCb: (opt_data) => { + global.context.renderStart(opt_data); + }, + reportRenderedEntityIdentifierCb: (ampId) => { + global.context.reportRenderedEntityIdentifier(ampId); + }, + noContentAvailableCb: () => { + global.context.noContentAvailable(); + }, + }; +} + +/** + * @param {!Window} global + * @param {!Object} data + * @param {string} publisherUrl + * @param {?string} referrerUrl + */ +function loadCMTag(global, data, publisherUrl, referrerUrl) { + /** + * Sets macro type. + * @param {string} type + */ + function setMacro(type) { + if (!type) { + return; + } + const name = 'medianet_' + type; + if (hasOwn(data, type)) { + global[name] = data[type]; + } + } + + /** + * Sets additional data. + */ + function setAdditionalData() { + data.requrl = publisherUrl || ''; + data.refurl = referrerUrl || ''; + data.versionId = '211213'; + + setMacro('width'); + setMacro('height'); + setMacro('crid'); + setMacro('requrl'); + setMacro('refurl'); + setMacro('versionId'); + setMacro('misc'); + } + + /** + * Sets callback. + */ + function setCallbacks() { + global._mNAmp = getCallbacksObject(); + } + + /** + * Loads the script. + */ + function loadScript() { + let url = 'https://contextual.media.net/ampnmedianet.js?'; + url += 'cid=' + encodeURIComponent(data.cid); + url += '&https=1'; + url += '&requrl=' + encodeURIComponent(data.requrl); + url += '&refurl=' + encodeURIComponent(data.refurl); + writeScript(global, url); + } + + /** + * Initializer. + */ + function init() { + setAdditionalData(); + setCallbacks(); + loadScript(); + } + + init(); +} + +/** + * @param {!Window} global + * @param {!Object} data + * @param {string} publisherUrl + * @param {?string} referrerUrl + */ +function loadHBTag(global, data, publisherUrl, referrerUrl) { + /** + * Loads MNETAd. + */ + function loadMNETAd() { + if (loadMNETAd.alreadyCalled) { + return; + } + loadMNETAd.alreadyCalled = true; + + global.advBidxc = global.context.master.advBidxc; + if (global.advBidxc && typeof global.advBidxc.renderAmpAd === 'function') { + global.addEventListener('message', (event) => { + global.advBidxc.renderAmpAd(event, global); + }); + } + + data.targeting = data.targeting || {}; + + if ( + global.advBidxc && + typeof global.advBidxc.setAmpTargeting === 'function' + ) { + global.advBidxc.setAmpTargeting(global, data); + } + global.advBidxc.loadAmpAd(global, data); + } + + /** + * Handler for mnet. + */ + function mnetHBHandle() { + global.advBidxc = global.context.master.advBidxc; + if ( + global.advBidxc && + typeof global.advBidxc.registerAmpSlot === 'function' + ) { + global.advBidxc.registerAmpSlot({ + cb: loadMNETAd, + data, + winObj: global, + }); + } + } + + computeInMasterFrame( + global, + 'medianet-hb-load', + (done) => { + /*eslint "google-camelcase/google-camelcase": 0*/ + global.advBidxc_requrl = publisherUrl; + global.advBidxc_refurl = referrerUrl; + global.advBidxc = { + registerAmpSlot: () => {}, + setAmpTargeting: () => {}, + renderAmpAd: () => {}, + loadAmpAd: () => { + global.context.noContentAvailable(); + }, + }; + global.advBidxc.amp = getCallbacksObject(); + const publisherDomain = parseUrlDeprecated(publisherUrl).hostname; + writeScript( + global, + 'https://contextual.media.net/bidexchange.js?https=1&=1&cid=' + + encodeURIComponent(data.cid) + + '&dn=' + + encodeURIComponent(publisherDomain), + () => { + done(null); + } + ); + }, + mnetHBHandle + ); +} diff --git a/ads/vendors/medianet.md b/ads/vendors/medianet.md new file mode 100644 index 0000000000000..46ecd8738dc79 --- /dev/null +++ b/ads/vendors/medianet.md @@ -0,0 +1,102 @@ + + +# Media.net + +Media.net adapter supports the integration of both, its Contextual Monetization solution and the Header Bidding solution. +The example listed below states the configuration and the implementation related details. + +## Example + +### Media.net Contextual Monetization + +```html + + +``` + +### Media.net Header Bidder + +```html + + +``` + +## Configuration + +### Dimensions + +The ad size depends on the `width` and `height` attributes specified in the `amp-ad` tag. The `amp-ad` component requires the following mandatory HTML attributes to be added before parsing the Ad. + +- `width` +- `height` +- `type = "medianet"` + +If you have questions, please feel free to reach out to your Media.net contact. + +## Supported Parameters + +### Media.net Contextual Monetization + +**Mandatory Parameters** + +- `data-tagtype` - This parameter represents the product the publisher is using; It should be **`cm`** for our **Contextual Monetization solution** +- `data-cid` - Represents the unique customer identifier +- `data-crid` - Media.net Ad unit + +**Optional Parameters** + +- `data-misc` - Accepts a json value & used to send additional data + +### Media.net Header Bidder + +**Mandatory Parameters** + +- `data-tagtype` - This parameter represents the product the publisher is using; It should be **`headerbidder`** for our **Header Bidding solution** +- `data-cid` - Represents the unique customer identifier +- `data-slot` - Ad unit as specified in DFP +- `data-multi-size` - Multi-size support +- `data-multi-size-validation` - Multi-size support + +**Some of the parameters supported via Json attribute (DFP Parameters)** + +- `targeting` +- `categoryExclusions` +- `cookieOptions` +- `tagForChildDirectedTreatment` +- `targeting` + +## Support + +For further queries, please feel free to reach out to your contact at Media.net. + +Otherwise you can write to our support team: +Email: **pubsupport@media.net** diff --git a/ads/vendors/mediavine.js b/ads/vendors/mediavine.js new file mode 100644 index 0000000000000..c473eb98e0aaa --- /dev/null +++ b/ads/vendors/mediavine.js @@ -0,0 +1,29 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function mediavine(global, data) { + validateData(data, ['site']); + global.$mediavine = { + slug: data.site, + }; + loadScript(global, 'https://amp.mediavine.com/wrapper.min.js'); +} diff --git a/ads/vendors/mediavine.md b/ads/vendors/mediavine.md new file mode 100644 index 0000000000000..9f70996ffe1d3 --- /dev/null +++ b/ads/vendors/mediavine.md @@ -0,0 +1,41 @@ + + +# Mediavine + +## Example + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact [Mediavine](http://www.mediavine.com). Each site must be approved and signed up with [Mediavine](http://www.mediavine.com) prior to launch. The site name will be the same as name in the Mediavine script wrapper. The site name `amp-project` can be used for testing and will serve placeholder ads. + +### Required parameters + +- `data-site` - The site's unique name this ad will be served on. This is the same name from your Mediavine script wrapper. + +## User Consent Integration + +When [user consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required. Mediavine approaches user consent in the following ways: + +- `CONSENT_POLICY_STATE.SUFFICIENT`: Personalized Ads. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Non-Personalized Ads. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Personalized Ads. +- `CONSENT_POLICY_STATE.UNKNOWN`: Non-Personalized Ads. diff --git a/ads/vendors/medyanet.js b/ads/vendors/medyanet.js new file mode 100644 index 0000000000000..62e405c23772b --- /dev/null +++ b/ads/vendors/medyanet.js @@ -0,0 +1,61 @@ +/** + * Copyright 2015 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. + */ + +import {setStyles} from '../../src/style'; +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function medyanet(global, data) { + validateData(data, ['slot', 'domain']); + + global.adunit = data.slot; + global.size = '[' + data.width + ',' + data.height + ']'; + global.domain = data.domain; + + medyanetAds(global, data); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function medyanetAds(global, data) { + const f = global.document.createElement('iframe'); + f.setAttribute('id', 'adframe'); + f.setAttribute('width', data.width); + f.setAttribute('height', data.height); + f.setAttribute('frameborder', '0'); + f.setAttribute('marginheight', '0'); + f.setAttribute('marginwidth', '0'); + f.setAttribute('allowfullscreen', 'true'); + f.setAttribute('scrolling', 'no'); + setStyles(f, { + border: '0 none transparent', + position: 'relative', + }); + f.onload = function () { + window.context.renderStart(); + }; + f.src = `https://app.medyanetads.com/amp/medyanetads.html?bidderData=${global.domain}&adunit=${global.adunit}&size=${global.size}`; + const url = window.top.location.search.substring(1); + if (url && url.indexOf('hb=true') !== -1) { + f.src = f.src + '&hb=true'; + } + global.document.body.appendChild(f); +} diff --git a/ads/medyanet.md b/ads/vendors/medyanet.md similarity index 96% rename from ads/medyanet.md rename to ads/vendors/medyanet.md index b6c27a0d0883e..faf23f7fe795d 100644 --- a/ads/medyanet.md +++ b/ads/vendors/medyanet.md @@ -35,5 +35,5 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-slot` -- `data-domain` +- `data-slot` +- `data-domain` diff --git a/ads/vendors/meg.js b/ads/vendors/meg.js new file mode 100644 index 0000000000000..6f2ba6e94fb6f --- /dev/null +++ b/ads/vendors/meg.js @@ -0,0 +1,48 @@ +/** + * Copyright 2016 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. + */ +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function meg(global, data) { + validateData(data, ['code']); + const {code} = data; + const lang = global.encodeURIComponent(global.navigator.language); + const ref = global.encodeURIComponent(global.context.referrer); + const params = ['lang=' + lang, 'ref=' + ref].join('&'); + const url = 'https://apps.meg.com/embedjs/' + code + '?' + params; + global._megAdsLoaderCallbacks = { + onSuccess: () => { + global.context.renderStart(); + }, + onError: () => { + global.context.noContentAvailable(); + }, + }; + loadScript( + global, + url, + () => { + // Meg has been loaded + }, + () => { + // Cannot load meg embed.js + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/meg.md b/ads/vendors/meg.md similarity index 94% rename from ads/meg.md rename to ads/vendors/meg.md index cc0b4574ec37a..c7f1df2e27acd 100644 --- a/ads/meg.md +++ b/ads/vendors/meg.md @@ -30,4 +30,4 @@ For details on the configuration semantics, please contact support@meg.com Supported parameters: -- `data-code`: unique ad code given by Meg.com +- `data-code`: unique ad code given by Meg.com diff --git a/ads/vendors/mgid.js b/ads/vendors/mgid.js new file mode 100644 index 0000000000000..309df9c9ca208 --- /dev/null +++ b/ads/vendors/mgid.js @@ -0,0 +1,62 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @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); + + 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/mgid.md b/ads/vendors/mgid.md similarity index 91% rename from ads/mgid.md rename to ads/vendors/mgid.md index 30175d7441d42..0ccd524a8e957 100644 --- a/ads/mgid.md +++ b/ads/vendors/mgid.md @@ -38,10 +38,11 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-publisher` -- `data-widget` -- `data-container` +- `data-publisher` +- `data-widget` +- `data-container` ### Optional parameters -- `data-url` +- `data-url` +- `data-options` diff --git a/ads/vendors/microad.js b/ads/vendors/microad.js new file mode 100644 index 0000000000000..46cea764b8dbd --- /dev/null +++ b/ads/vendors/microad.js @@ -0,0 +1,33 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/* global MicroAd: false */ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function microad(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['spot', 'url', 'referrer', 'ifa', 'appid', 'geo']); + + global.document.getElementById('c').setAttribute('id', data.spot); + loadScript(global, 'https://j.microad.net/js/camp.js', () => { + MicroAd.Compass.showAd(data); + }); +} diff --git a/ads/microad.md b/ads/vendors/microad.md similarity index 89% rename from ads/microad.md rename to ads/vendors/microad.md index 41b4ae9ad6394..567d5353fd98a 100644 --- a/ads/microad.md +++ b/ads/vendors/microad.md @@ -39,11 +39,11 @@ For configuration details and to generate your tags, contact compass-support@mic Supported parameters: -- `width` -- `height` -- `data-spot` -- `data-url` -- `data-referrer` -- `data-ifa` -- `data-appid` -- `data-geo` +- `width` +- `height` +- `data-spot` +- `data-url` +- `data-referrer` +- `data-ifa` +- `data-appid` +- `data-geo` diff --git a/ads/vendors/miximedia.js b/ads/vendors/miximedia.js new file mode 100644 index 0000000000000..d33e5cf124eed --- /dev/null +++ b/ads/vendors/miximedia.js @@ -0,0 +1,50 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function miximedia(global, data) { + validateData(data, ['blockid']); + global._miximedia = global._miximedia || { + viewId: global.context.pageViewId, + blockId: data['blockid'], + htmlURL: data['canonical'] || global.context.canonicalUrl, + ampURL: data['ampurl'] || global.context.sourceUrl, + testMode: data['testmode'] || 'false', + referrer: data['referrer'] || global.context.referrer, + hostname: global.window.context.location.hostname, + clientId: window.context.clientId, + domFingerprint: window.context.domFingerprint, + location: window.context.location, + startTime: window.context.startTime, + }; + global._miximedia.AMPCallbacks = { + renderStart: global.context.renderStart, + noContentAvailable: global.context.noContentAvailable, + }; + // load the miximedia AMP JS file script asynchronously + const rand = Math.round(Math.random() * 100000000); + loadScript( + global, + 'https://amp.mixi.media/ampclient/mixi.js?rand=' + rand, + () => {}, + global.context.noContentAvailable + ); +} diff --git a/ads/miximedia.md b/ads/vendors/miximedia.md similarity index 95% rename from ads/miximedia.md rename to ads/vendors/miximedia.md index 6b5127afc2b34..7af779eb74565 100644 --- a/ads/miximedia.md +++ b/ads/vendors/miximedia.md @@ -30,4 +30,4 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-blockid` - insert your block_id +- `data-blockid` - insert your block_id diff --git a/ads/vendors/mixpo.js b/ads/vendors/mixpo.js new file mode 100644 index 0000000000000..24223459a20e1 --- /dev/null +++ b/ads/vendors/mixpo.js @@ -0,0 +1,49 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function mixpo(global, data) { + validateData(data, ['guid', 'subdomain']); + + const g = global, + cdnSubdomain = data.subdomain == 'www' ? 'cdn' : data.subdomain + '-cdn', + url = data.loader || `https://${cdnSubdomain}.mixpo.com/js/loader.js`; + + g.mixpoAd = { + amp: true, + noflash: true, + width: data.width, + height: data.height, + guid: data.guid, + subdomain: data.subdomain, + embedv: data.embedv, + clicktag: data.clicktag, + customTarget: data.customtarget, + dynClickthrough: data.dynclickthrough, + viewTracking: data.viewtracking, + customCSS: data.customcss, + local: data.local, + enableMRAID: data.enablemraid, + jsPlayer: data.jsplayer, + }; + + writeScript(g, url); +} diff --git a/ads/mixpo.md b/ads/vendors/mixpo.md similarity index 79% rename from ads/mixpo.md rename to ads/vendors/mixpo.md index 283a4901a6dbc..3533f0af0e4fc 100644 --- a/ads/mixpo.md +++ b/ads/vendors/mixpo.md @@ -35,18 +35,18 @@ For configuration and implementation details, please contact Mixpo support: `sup Required Attributes: -- `data-guid` -- `data-subdomain` +- `data-guid` +- `data-subdomain` Additional Supported Attributes: -- `data-embedv` -- `data-clicktag` -- `data-customtarget` -- `data-dynclickthrough` -- `data-viewtracking` -- `data-customcss` -- `data-local` -- `data-enablemraid` -- `data-jsplayer` -- `data-loader` +- `data-embedv` +- `data-clicktag` +- `data-customtarget` +- `data-dynclickthrough` +- `data-viewtracking` +- `data-customcss` +- `data-local` +- `data-enablemraid` +- `data-jsplayer` +- `data-loader` diff --git a/ads/vendors/monetizer101.js b/ads/vendors/monetizer101.js new file mode 100644 index 0000000000000..feacedf3cb602 --- /dev/null +++ b/ads/vendors/monetizer101.js @@ -0,0 +1,28 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function monetizer101(global, data) { + validateData(data, ['widget', 'config']); + global.widget = data.widget; + global.config = data.config; + writeScript(global, 'https://link.monetizer101.com/widget/amp/amp.js'); +} diff --git a/ads/monetizer101.md b/ads/vendors/monetizer101.md similarity index 96% rename from ads/monetizer101.md rename to ads/vendors/monetizer101.md index 3ccfa16de0072..c7415ab70c845 100644 --- a/ads/monetizer101.md +++ b/ads/vendors/monetizer101.md @@ -46,5 +46,5 @@ For more information, please [contact Monetizer101](http://monetizer101.com/appl Required parameters: -- `data-widget` -- `data-config` +- `data-widget` +- `data-config` diff --git a/ads/vendors/mox.js b/ads/vendors/mox.js new file mode 100644 index 0000000000000..04a768e2bb925 --- /dev/null +++ b/ads/vendors/mox.js @@ -0,0 +1,29 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} config + */ +export function mox(global, config) { + validateData(config, ['z', 'w', 'h'], ['u']); + + global.config = config; + + loadScript(global, config.u || 'https://ad.mox.tv/js/amp.min.js'); +} diff --git a/ads/mox.md b/ads/vendors/mox.md similarity index 86% rename from ads/mox.md rename to ads/vendors/mox.md index 371b88855fc7d..58130d4000486 100644 --- a/ads/mox.md +++ b/ads/vendors/mox.md @@ -28,10 +28,10 @@ Please visit our [website](https://mox.tv) for more information about us. #### Required parameters: -- `data-z` — Ad zone unique id -- `data-w` — Ad unit width -- `data-h` — Ad unit height +- `data-z` — Ad zone unique id +- `data-w` — Ad unit width +- `data-h` — Ad unit height #### Optional parameters: -- `data-u` — URL of invocation script +- `data-u` — URL of invocation script diff --git a/ads/vendors/my6sense.js b/ads/vendors/my6sense.js new file mode 100644 index 0000000000000..361a562528eb3 --- /dev/null +++ b/ads/vendors/my6sense.js @@ -0,0 +1,47 @@ +/** + * Copyright 2020 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. + */ + +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function my6sense(global, data) { + validateData(data, ['widgetKey']); + + const widgetTag = global.document.createElement('script'); + widgetTag.src = `//web-clients.mynativeplatform.com/web-clients/bootloaders/${data['widgetKey']}/bootloader.js`; + const url = + data['url'] && data['url'] !== '[PAGE_URL]' + ? data['url'] + : global.context.sourceUrl; + widgetTag.setAttribute('async', 'true'); + widgetTag.setAttribute('data-version', '3'); + widgetTag.setAttribute('data-url', url); + widgetTag.setAttribute('data-zone', data['zone'] || '[ZONE]'); + widgetTag.setAttribute('data-google-amp', 'true'); + widgetTag.setAttribute( + 'data-organic-clicks', + data['organicClicks'] || '[ORGANIC_TRACKING_PIXEL]' + ); + widgetTag.setAttribute( + 'data-paid-clicks', + data['paidClicks'] || '[PAID_TRACKING_PIXEL]' + ); + widgetTag.setAttribute('data-display-within-iframe', 'true'); + global.document.body.appendChild(widgetTag); +} diff --git a/ads/my6sense.md b/ads/vendors/my6sense.md similarity index 87% rename from ads/my6sense.md rename to ads/vendors/my6sense.md index 6fefeb498ef0e..9e1b71e10e672 100644 --- a/ads/my6sense.md +++ b/ads/vendors/my6sense.md @@ -38,11 +38,11 @@ For semantics of configuration and examples, sign-in and see the [My6sense platf ## Required parameters: -- `data-widget-key` : string, non-empty +- `data-widget-key` : string, non-empty ## Optional parameters: -- `data-zone` : string, non-empty -- `data-url` : string, non-empty -- `data-organic-clicks` : string, non-empty -- `data-paid-clicks` : string, non-empty +- `data-zone` : string, non-empty +- `data-url` : string, non-empty +- `data-organic-clicks` : string, non-empty +- `data-paid-clicks` : string, non-empty diff --git a/ads/vendors/myfinance.js b/ads/vendors/myfinance.js new file mode 100644 index 0000000000000..0a8d4ac6bfa5f --- /dev/null +++ b/ads/vendors/myfinance.js @@ -0,0 +1,52 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +const mandatoryFields = ['adType']; + +const adUrl = 'https://www.myfinance.com/amp/ad'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function myfinance(global, data) { + validateData(data, mandatoryFields); + if (!data['mf_referrer']) { + data['mf_referrer'] = + global.context.canonicalUrl || global.context.sourceUrl; + } + if (!data['ampClientId']) { + data['ampClientId'] = global.context.clientId; + } + const url = buildUrl(data); + global.MF_AMP_DATA = data; + writeScript(global, url); +} + +/** + * Generates the url to call for the script content + * @param {!Object} data + * @return {string} + */ +function buildUrl(data) { + const url = new URL(adUrl); + Object.entries(data).forEach((entry) => + url.searchParams.set(entry[0], entry[1]) + ); + return url.toString(); +} diff --git a/ads/vendors/myfinance.md b/ads/vendors/myfinance.md new file mode 100644 index 0000000000000..ec6c4af1df942 --- /dev/null +++ b/ads/vendors/myfinance.md @@ -0,0 +1,96 @@ + + +# MyFinance + +Serves ads from [MyFinance](https://www.myfinance.com/). + +## Example + +### Widgets + +#### Dynamic based on page content + +```html + + +``` + +#### Static from a Creative Set + +```html + + +``` + +#### Static Widget + +```html + + +``` + +### CRU + +```html + + +``` + +## Configuration + +### Widgets + +#### Required parameters + +- `data-ad-type` - set to `widget` + +#### Widgets require one of the following parameters to be set + +- `data-selector` - corresponds to the div id on the canonical page that has dynamic ads configured +- `data-creative-set` - the id of the creative set to pull ads from +- `data-widget` - the id of the widget to serve + +#### Optional parameters + +- `data-sub-id` - pass through for a partner sub id + +### Content Recommendation Unit + +#### Required parameters + +- `data-ad-type` - set to `cru` +- `data-campaign` - set to a valid campaign + +#### Optional parameters + +- `data-selector` - corresponds to the div id on the canonical page that has a pre-rendered cru configured diff --git a/ads/vendors/myoffrz.js b/ads/vendors/myoffrz.js new file mode 100644 index 0000000000000..b5ce9caddeb61 --- /dev/null +++ b/ads/vendors/myoffrz.js @@ -0,0 +1,34 @@ +/** + * Copyright 2020 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function myoffrz(global, data) { + validateData(data, ['config'], ['script']); + global.config = data.config; + global.sourceUrl = global.context.sourceUrl; + + // create an expected placeholder + const d = global.document.createElement('div'); + d.setAttribute('id', 'myoffrz'); + global.document.getElementById('c').appendChild(d); + + writeScript(global, data.script || 'https://cdn.myoffrz.io/amp/script.js'); +} diff --git a/ads/vendors/myoffrz.md b/ads/vendors/myoffrz.md new file mode 100644 index 0000000000000..a2969fa16d2f0 --- /dev/null +++ b/ads/vendors/myoffrz.md @@ -0,0 +1,40 @@ + + +# myoffrz + +## Example + +```html + + +``` + +## Configuration + +Supported parameters: + +- `data-config` + +Optional parameters: + +- `data-script` diff --git a/ads/vendors/mytarget.js b/ads/vendors/mytarget.js new file mode 100644 index 0000000000000..6999cab0da2e9 --- /dev/null +++ b/ads/vendors/mytarget.js @@ -0,0 +1,50 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {setStyles} from '../../src/style'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function mytarget(global, data) { + validateData(data, ['adSlot'], ['adQuery']); + + // Create ad tag placeholder + const container = global.document.createElement('ins'); + + container.setAttribute('class', 'mrg-tag'); + container.setAttribute('data-ad-slot', data['adSlot']); + if (data['adQuery']) { + container.setAttribute('data-ad-query', data['adQuery']); + } + setStyles(container, { + display: 'inline-block', + width: '100%', + height: '100%', + }); + global.document.getElementById('c').appendChild(container); + + // Add tag and callbacks to queue + (global.MRGtag = global.MRGtag || []).push({ + onNoAds: () => global.context.noContentAvailable(), + onAdsSuccess: () => global.context.renderStart(), + }); + + // Load main js asynchronously + loadScript(global, 'https://ad.mail.ru/static/ads-async.js'); +} diff --git a/ads/mytarget.md b/ads/vendors/mytarget.md similarity index 96% rename from ads/mytarget.md rename to ads/vendors/mytarget.md index 5e3056e4ea48d..671585b66023f 100644 --- a/ads/mytarget.md +++ b/ads/vendors/mytarget.md @@ -31,8 +31,8 @@ Please visit our [website](https://target.my.com) for more information about us. Required parameters: -- data-ad-slot +- data-ad-slot Optional parameters: -- data-ad-query +- data-ad-query diff --git a/ads/vendors/mywidget.js b/ads/vendors/mywidget.js new file mode 100644 index 0000000000000..7ae66d3373bc7 --- /dev/null +++ b/ads/vendors/mywidget.js @@ -0,0 +1,29 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function mywidget(global, data) { + validateData(data, ['cid']); + global.myWidgetInit = data; + + // load the myWidget initializer asynchronously + loadScript(global, 'https://likemore-go.imgsmail.ru/widget_amp.js'); +} diff --git a/ads/mywidget.md b/ads/vendors/mywidget.md similarity index 98% rename from ads/mywidget.md rename to ads/vendors/mywidget.md index d1299233d6470..f843a40aca830 100644 --- a/ads/mywidget.md +++ b/ads/vendors/mywidget.md @@ -37,4 +37,4 @@ For semantics of configuration, please see [myWidget documentation](https://widg ### Required parameters -- `data-cid` +- `data-cid` diff --git a/ads/vendors/nativeroll.js b/ads/vendors/nativeroll.js new file mode 100644 index 0000000000000..507550fee76af --- /dev/null +++ b/ads/vendors/nativeroll.js @@ -0,0 +1,58 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function nativeroll(global, data) { + validateData(data, ['gid']); + loadScript( + global, + 'https://cdn01.nativeroll.tv/js/seedr-player.min.js', + () => { + initPlayer(global, data); + } + ); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function initPlayer(global, data) { + const config = { + container: '#c', + desiredOffset: 50, + gid: data.gid, + onError: () => { + global.context.noContentAvailable(); + }, + onLoad: () => { + const height = + global.document.getElementsByClassName('nr-player')[0] + ./* OK */ offsetHeight; + global.context.requestResize(undefined, height); + }, + onDestroy: () => { + global.context.noContentAvailable(); + }, + }; + // eslint-disable-next-line no-undef + SeedrPlayer(config); +} diff --git a/ads/nativeroll.md b/ads/vendors/nativeroll.md similarity index 100% rename from ads/nativeroll.md rename to ads/vendors/nativeroll.md diff --git a/ads/vendors/nativery.js b/ads/vendors/nativery.js new file mode 100644 index 0000000000000..b3c9df4b5783c --- /dev/null +++ b/ads/vendors/nativery.js @@ -0,0 +1,59 @@ +/** + * Copyright 2018 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. + */ + +import {toArray} from '../../src/core/types/array'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function nativery(global, data) { + validateData(data, ['wid']); + const params = {...data}; + + // push the two object into the '_nativery' global + global._nativery = global._nativery || { + wid: data.wid, + referrer: data.referrer || global.context.referrer, + url: data.url || global.context.canonicalUrl, + viewId: global.context.pageViewId, + visible: 0, + params, + }; + + // must add listener for resize + global.addEventListener('amp-widgetCreated', function (e) { + if (e && e.detail) { + global.context.requestResize(undefined, e.detail.height); + } + }); + + // install observation to check if is in viewport + const unlisten = global.context.observeIntersection(function (changes) { + toArray(changes).forEach(function (c) { + global._nativery.visible = Math.floor( + (c.intersectionRect.height / c.boundingClientRect.height) * 100 + ); + if (global._nativery.visible) { + unlisten(); + } + }); + }); + + // load the nativery loader asynchronously + writeScript(global, `https://cdn.nativery.com/widget/js/natamp.js`); +} diff --git a/ads/nativery.md b/ads/vendors/nativery.md similarity index 100% rename from ads/nativery.md rename to ads/vendors/nativery.md diff --git a/ads/vendors/nativo.js b/ads/vendors/nativo.js new file mode 100644 index 0000000000000..0caf78ffc9012 --- /dev/null +++ b/ads/vendors/nativo.js @@ -0,0 +1,164 @@ +/** + * Copyright 2016 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. + */ +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function nativo(global, data) { + let ntvAd; + (function (ntvAd, global, data) { + global.history.replaceState( + null, + '', + location.pathname + location.hash.replace(/({).*(})/, '') + ); + // Private + let delayedAdLoad = false; + let percentageOfadViewed; + const loc = global.context.location; + /** + * @param {number} delay + * @return {boolean} + */ + function isValidDelayTime(delay) { + return ( + typeof delay != 'undefined' && !isNaN(delay) && parseInt(delay, 10) >= 0 + ); + } + /** + * @param {!Object} data + * @return {boolean} + */ + function isDelayedTimeStart(data) { + return ( + isValidDelayTime(data.delayByTime) && + 'delay' in data && + !('delayByView' in data) + ); + } + /** + * @param {!Object} data + * @return {boolean} + */ + function isDelayedViewStart(data) { + return isValidDelayTime(data.delayByTime) && 'delayByView' in data; + } + /** + * Loads ad when done. + */ + function loadAdWhenViewed() { + const g = global; + global.context.observeIntersection(function (positions) { + const coordinates = getLastPositionCoordinates(positions); + if ( + typeof coordinates.rootBounds != 'undefined' && + coordinates.intersectionRect.top == + coordinates.rootBounds.top + coordinates.boundingClientRect.y + ) { + if (isDelayedViewStart(data) && !delayedAdLoad) { + g.PostRelease.Start(); + delayedAdLoad = true; + } + } + }); + } + /** + * Loads ad when timeouts. + */ + function loadAdWhenTimedout() { + const g = global; + setTimeout(function () { + g.PostRelease.Start(); + delayedAdLoad = true; + }, parseInt(data.delayByTime, 10)); + } + /** + * @param {*} positions + * @return {*} TODO(#23582): Specify return type + */ + function getLastPositionCoordinates(positions) { + return positions[positions.length - 1]; + } + /** + * @param {number} percentage + */ + function setPercentageOfadViewed(percentage) { + percentageOfadViewed = percentage; + } + /** + * Used to track ad during scrolling event and trigger checkIsAdVisible + * method on PostRelease instance + * @param {*} positions + */ + function viewabilityConfiguration(positions) { + const coordinates = getLastPositionCoordinates(positions); + setPercentageOfadViewed( + (coordinates.intersectionRect.height * 100) / + coordinates.boundingClientRect.height / + 100 + ); + global.PostRelease.checkIsAdVisible(); + } + // Public + ntvAd.getPercentageOfadViewed = function () { + return percentageOfadViewed; + }; + ntvAd.getScriptURL = function () { + return 'https://s.ntv.io/serve/load.js'; + }; + // Configuration setup is based on the parameters/attributes associated with + // the amp-ad node + ntvAd.setupAd = function () { + global._prx = [['cfg.Amp']]; + global._prx.push(['cfg.RequestUrl', data['requestUrl'] || loc.href]); + for (const key in data) { + switch (key) { + case 'premium': + global._prx.push(['cfg.SetUserPremium']); + break; + case 'debug': + global._prx.push(['cfg.Debug']); + break; + case 'delay': + if (isValidDelayTime(data.delayByTime)) { + global._prx.push(['cfg.SetNoAutoStart']); + } + break; + } + } + }; + // Used to Delay Start and Initalize Tracking. This is a callback AMP will + // use once script is loaded + ntvAd.Start = function () { + if (isDelayedTimeStart(data)) { + loadAdWhenTimedout(); + } else if (isDelayedViewStart(data)) { + loadAdWhenViewed(); + } + global.PostRelease.checkAmpViewability = function () { + return ntvAd.getPercentageOfadViewed(); + }; + // ADD TRACKING HANDLER TO OBSERVER + global.context.observeIntersection(viewabilityConfiguration); + }; + })(ntvAd || (ntvAd = {}), global, data); + // Setup Configurations + ntvAd.setupAd(); + // Load Nativo Script + loadScript(global, ntvAd.getScriptURL(), ntvAd.Start); +} diff --git a/ads/nativo.md b/ads/vendors/nativo.md similarity index 96% rename from ads/nativo.md rename to ads/vendors/nativo.md index d5965308d6bcb..489a5c6f945d6 100644 --- a/ads/nativo.md +++ b/ads/vendors/nativo.md @@ -35,4 +35,4 @@ Before starting any Nativo AMP setup, please reach out to your account manager f ### Optional parameters -- `data-premium`: Switches to premium. +- `data-premium`: Switches to premium. diff --git a/ads/vendors/navegg.js b/ads/vendors/navegg.js new file mode 100644 index 0000000000000..898e61b26c86c --- /dev/null +++ b/ads/vendors/navegg.js @@ -0,0 +1,43 @@ +/** + * Copyright 2017 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. + */ + +import {doubleclick} from '../../ads/google/doubleclick'; +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function navegg(global, data) { + validateData(data, ['acc']); + const {acc} = data; + let seg, + nvg = function () {}; + delete data.acc; + nvg.prototype.getProfile = function () {}; + data.targeting = data.targeting || {}; + loadScript(global, 'https://tag.navdmp.com/amp.1.0.0.min.js', () => { + nvg = global[`nvg${acc}`] = new global['AMPNavegg']({ + acc, + }); + nvg.getProfile((nvgTargeting) => { + for (seg in nvgTargeting) { + data.targeting[seg] = nvgTargeting[seg]; + } + doubleclick(global, data); + }); + }); +} diff --git a/ads/navegg.md b/ads/vendors/navegg.md similarity index 95% rename from ads/navegg.md rename to ads/vendors/navegg.md index 09a17d534593f..403c62ef8dd10 100644 --- a/ads/navegg.md +++ b/ads/vendors/navegg.md @@ -32,6 +32,6 @@ To get Navegg integration working you only need to specify the `rtc-config` para ### Configuration -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/master/ads/google/doubleclick.md). +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). diff --git a/ads/vendors/nend.js b/ads/vendors/nend.js new file mode 100644 index 0000000000000..96924e80d6a9c --- /dev/null +++ b/ads/vendors/nend.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +const nendFields = ['nend_params']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function nend(global, data) { + validateData(data, nendFields, []); + + global.nendParam = data; + writeScript(global, 'https://js1.nend.net/js/amp.js'); +} diff --git a/ads/nend.md b/ads/vendors/nend.md similarity index 97% rename from ads/nend.md rename to ads/vendors/nend.md index 00cdbcae01eca..1655d02cdda1f 100644 --- a/ads/nend.md +++ b/ads/vendors/nend.md @@ -34,4 +34,4 @@ For configuration details and to generate your tags, please contact https://www. Supported parameters: -- `data-nend_params` +- `data-nend_params` diff --git a/ads/vendors/netletix.js b/ads/vendors/netletix.js new file mode 100644 index 0000000000000..be16593ea2477 --- /dev/null +++ b/ads/vendors/netletix.js @@ -0,0 +1,104 @@ +/** + * Copyright 2016 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. + */ + +import {addParamsToUrl, assertHttpsUrl} from '../../src/url'; +import {dev} from '../../src/log.js'; +import {dict} from '../../src/core/types/object'; +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +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; +const DEFAULT_NX_KEY = 'default'; +const DEFAULT_NX_UNIT = 'default'; +const DEFAULT_NX_WIDTH = 'fluid'; +const DEFAULT_NX_HEIGHT = 'fluid'; +const DEFAULT_NX_V = '0002'; +const DEFAULT_NX_SITE = 'none'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function netletix(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._netletix_amp = { + allowed_data: ['nxasync', 'nxv', 'nxsite', 'nxid', 'nxscript'], + mandatory_data: ['nxkey', 'nxunit', 'nxwidth', 'nxheight'], + data, + }; + + validateData( + data, + global._netletix_amp.mandatory_data, + global._netletix_amp.allowed_data + ); + + const nxh = data.nxheight || DEFAULT_NX_HEIGHT; + const nxw = data.nxwidth || DEFAULT_NX_WIDTH; + const 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 + ); + + window.addEventListener('message', (event) => { + if ( + event.data.type && + dev().assertString(event.data.type).startsWith('nx-') + ) { + switch (event.data.type) { + case 'nx-resize': + const renderconfig = { + 'width': event.data.width, + 'height': event.data.height, + }; + global.context.renderStart(renderconfig); + if ( + event.data.width && + event.data.height && + (event.data.width != nxw || event.data.height != nxh) + ) { + global.context.requestResize(event.data.width, event.data.height); + } + break; + case 'nx-empty': + global.context.noContentAvailable(); + break; + case 'nx-identifier': + global.context.reportRenderedEntityIdentifier(event.data.identifier); + break; + default: + break; + } + } + }); + + if (data.async && data.async.toLowerCase() === 'true') { + loadScript(global, url); + } else { + writeScript(global, url); + } +} diff --git a/ads/netletix.md b/ads/vendors/netletix.md similarity index 75% rename from ads/netletix.md rename to ads/vendors/netletix.md index b8c65bd38c7e3..6835910fbdb82 100644 --- a/ads/netletix.md +++ b/ads/vendors/netletix.md @@ -37,15 +37,15 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-nxkey`: string, non-empty -- `data-nxunit`: string, non-empty -- `data-nxwidth`: string, non-empty -- `data-nxheight`: string, non-empty +- `data-nxkey`: string, non-empty +- `data-nxunit`: string, non-empty +- `data-nxwidth`: string, non-empty +- `data-nxheight`: string, non-empty ### Optional parameters -- `data-nxasync`: string, non-empty -- `data-nxv`: string, non-empty -- `data-nxsite`: string, non-empty -- `data-nxid`: string, non-empty -- `data-nxscript`: string, non-empty +- `data-nxasync`: string, non-empty +- `data-nxv`: string, non-empty +- `data-nxsite`: string, non-empty +- `data-nxid`: string, non-empty +- `data-nxscript`: string, non-empty diff --git a/ads/vendors/noddus.js b/ads/vendors/noddus.js new file mode 100644 index 0000000000000..750b548babfbe --- /dev/null +++ b/ads/vendors/noddus.js @@ -0,0 +1,29 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function noddus(global, data) { + validateData(data, ['token']); + + global.noddus = data; + + writeScript(global, 'https://noddus.com/amp_loader.js'); +} diff --git a/ads/noddus.md b/ads/vendors/noddus.md similarity index 95% rename from ads/noddus.md rename to ads/vendors/noddus.md index 8eae70d8456c6..253c7a63b653c 100644 --- a/ads/noddus.md +++ b/ads/vendors/noddus.md @@ -34,4 +34,4 @@ For details on the configuration semantics, see [Noddus documentation](https://w ### Required parameters -- `data-token` - Publishers placement token +- `data-token` - Publishers placement token diff --git a/ads/vendors/nokta.js b/ads/vendors/nokta.js new file mode 100644 index 0000000000000..39a1e428a74d5 --- /dev/null +++ b/ads/vendors/nokta.js @@ -0,0 +1,34 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function nokta(global, data) { + validateData(data, ['category', 'site', 'zone']); + global.category = data.category; + global.site = data.site; + global.zone = data.zone; + global.iwidth = data.width; + global.iheight = data.height; + writeScript( + global, + 'https://static.virgul.com/theme/mockups/noktaamp/ampjs.js' + ); +} diff --git a/ads/nokta.md b/ads/vendors/nokta.md similarity index 88% rename from ads/nokta.md rename to ads/vendors/nokta.md index 16f70bc60138b..d0f1803cacf95 100644 --- a/ads/nokta.md +++ b/ads/vendors/nokta.md @@ -38,6 +38,6 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-category`: Site category for ad unit. -- `data-site`: Site descriptor for ad. -- `data-zone`: Zone id to show related ad. +- `data-category`: Site category for ad unit. +- `data-site`: Site descriptor for ad. +- `data-zone`: Zone id to show related ad. diff --git a/ads/vendors/nws.js b/ads/vendors/nws.js new file mode 100644 index 0000000000000..9d09b4a370e5e --- /dev/null +++ b/ads/vendors/nws.js @@ -0,0 +1,49 @@ +/** + * Copyright 2016 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. + */ + +import {validateSrcPrefix, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function nws(global, data) { + const {src} = data; + validateSrcPrefix( + [ + 'https://tags.nws.ai/', + 'https://echo.nws.press/', + 'https://stories.nws.ai/', + ], + src + ); + writeScript(global, src); +} + +// Keep the following for backwards compatibility + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function chargeads(global, data) { + const {src} = data; + validateSrcPrefix( + ['https://www.chargeplatform.com/', 'https://tags.chargeplatform.com/'], + src + ); + writeScript(global, src); +} diff --git a/ads/nws.md b/ads/vendors/nws.md similarity index 98% rename from ads/nws.md rename to ads/vendors/nws.md index 773f643679dfa..f21f96f93232e 100644 --- a/ads/nws.md +++ b/ads/vendors/nws.md @@ -37,4 +37,4 @@ For configuration semantics, please [contact Newsroom AI](https://www.nws.ai). Supported parameters: -- `src` +- `src` diff --git a/ads/vendors/oblivki.js b/ads/vendors/oblivki.js new file mode 100644 index 0000000000000..684c29e19e1f1 --- /dev/null +++ b/ads/vendors/oblivki.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +const oblivkiFields = ['id']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function oblivki(global, data) { + validateData(data, oblivkiFields, []); + + global.oblivkiParam = data; + writeScript(global, 'https://oblivki.biz/ads/amp.js'); +} diff --git a/ads/vendors/oblivki.md b/ads/vendors/oblivki.md new file mode 100644 index 0000000000000..0d43de91b689f --- /dev/null +++ b/ads/vendors/oblivki.md @@ -0,0 +1,39 @@ + + +# Oblivki + +## Example + +```html + + +``` + +## Configuration + +For configuration details and to generate your tags, please contact support@oblivki.biz + +Supported parameters: + +- `data-block-key` diff --git a/ads/vendors/onead.js b/ads/vendors/onead.js new file mode 100644 index 0000000000000..eca2d5e1b59a5 --- /dev/null +++ b/ads/vendors/onead.js @@ -0,0 +1,63 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function onead(global, data) { + validateData(data, [], ['playmode', 'uid', 'pid', 'host']); + global.Guoshi = { + queryAd: { + amp: {}, + }, + }; + global.ONEAD_AMP = { + playmode: data.playmode, + uid: data.uid, + pid: data.pid, + host: data.host, + }; + createOneadSlot(global); + createAdUnit(global); +} +/** + * @param {!Window} win + */ +function createOneadSlot(win) { + const slot = document.createElement('div'); + slot.id = 'onead-amp'; + win.document.getElementById('c').appendChild(slot); +} +/** + * @param {!Window} win + */ +function createAdUnit(win) { + win.ONEAD_AMP.isAMP = true; + const src = 'https://ad-specs.guoshipartners.com/static/js/onead-amp.min.js'; + const jsLoadCb = () => { + win.Guoshi.queryAd.amp.setup({ + playMode: win.ONEAD_AMP.playMode, + uid: win.ONEAD_AMP.uid, + pid: win.ONEAD_AMP.pid, + host: win.ONEAD_AMP.host, + }); + }; + + loadScript(win, src, jsLoadCb); +} diff --git a/ads/onead.md b/ads/vendors/onead.md similarity index 91% rename from ads/onead.md rename to ads/vendors/onead.md index 112ae526e8a9e..fced1385e5f00 100644 --- a/ads/onead.md +++ b/ads/vendors/onead.md @@ -36,9 +36,9 @@ limitations under the License. Supported parameters: -- `data-playmode` -- `data-uid` -- `data-pid` (optional) -- `data-host` (optional) +- `data-playmode` +- `data-uid` +- `data-pid` (optional) +- `data-host` (optional) For configuration details, please contact [OneAD](https://www.onead.com.tw/) diff --git a/ads/vendors/onnetwork.js b/ads/vendors/onnetwork.js new file mode 100644 index 0000000000000..513837e9b1fd2 --- /dev/null +++ b/ads/vendors/onnetwork.js @@ -0,0 +1,54 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, validateSrcPrefix, writeScript} from '../../3p/3p'; + +// Valid OnNetwork movie and ad source hosts +const hosts = { + video: 'https://video.onnetwork.tv', + cdn: 'https://cdn.onnetwork.tv', + cdnx: 'https://cdnx.onnetwork.tv', + vast: 'https://vast.onnetwork.tv', +}; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function onnetwork(global, data) { + validateData(data, [['src', 'sid', 'mid']]); + global.onnetwork = {ampData: data}; + const {mid, sid, src} = data; + let url; + + // Custom movie url using "data-src" attribute + if (src) { + validateSrcPrefix( + Object.keys(hosts).map((type) => hosts[type]), + src + ); + url = src; + } + // Movie tag using "data-sid" attribute + else if (sid) { + url = hosts.video + '/embed.php?ampsrc=1&sid=' + encodeURIComponent(sid); + // Movie placement using "data-mid" attribute + } else if (mid) { + url = hosts.video + '/embed.php?ampsrc=1&mid=' + encodeURIComponent(mid); + } + + writeScript(global, url); +} diff --git a/ads/onnetwork.md b/ads/vendors/onnetwork.md similarity index 91% rename from ads/onnetwork.md rename to ads/vendors/onnetwork.md index d1af60235c45c..198ef44390fab 100644 --- a/ads/onnetwork.md +++ b/ads/vendors/onnetwork.md @@ -53,7 +53,7 @@ information on how to get required movie tag or placement IDs. Only one of the mentioned parameters should be used at the same time. -- `data-sid` -- `data-mid` -- `src`: must use https protocol and must be from one of the - allowed OnNetwork hosts. +- `data-sid` +- `data-mid` +- `src`: must use https protocol and must be from one of the + allowed OnNetwork hosts. diff --git a/ads/vendors/openadstream.js b/ads/vendors/openadstream.js new file mode 100644 index 0000000000000..8ccae5aedc093 --- /dev/null +++ b/ads/vendors/openadstream.js @@ -0,0 +1,40 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function openadstream(global, data) { + validateData(data, ['adhost', 'sitepage', 'pos'], ['query']); + + let url = + 'https://' + + encodeURIComponent(data.adhost) + + '/3/' + + data.sitepage + + '/1' + + String(Math.random()).substring(2, 11) + + '@' + + data.pos; + + if (data.query) { + url = url + '?' + data.query; + } + writeScript(global, url); +} diff --git a/ads/openadstream.md b/ads/vendors/openadstream.md similarity index 86% rename from ads/openadstream.md rename to ads/vendors/openadstream.md index b35b678471a33..8110a6c7fa652 100644 --- a/ads/openadstream.md +++ b/ads/vendors/openadstream.md @@ -62,10 +62,10 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `adhost`: OAS cname. Must start with HTTPS. -- `sitepage`: Sitepage configured for this ad spot. -- `pos`: Position for the this ad spot. +- `adhost`: OAS cname. Must start with HTTPS. +- `sitepage`: Sitepage configured for this ad spot. +- `pos`: Position for the this ad spot. ### Optional parameters -- `query`: Query parameter to be sent with request. Keywords and keynames, taxonomy etc. +- `query`: Query parameter to be sent with request. Keywords and keynames, taxonomy etc. diff --git a/ads/vendors/openx.js b/ads/vendors/openx.js new file mode 100644 index 0000000000000..f51ccabde197f --- /dev/null +++ b/ads/vendors/openx.js @@ -0,0 +1,184 @@ +/** + * Copyright 2016 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. + */ + +import {doubleclick} from '../../ads/google/doubleclick'; +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +const {hasOwnProperty} = Object.prototype; + +/** + * Sort of like Object.assign. + * @param {!Object} target + * @param {!Object} source + * @return {!Object} + */ +function assign(target, source) { + for (const prop in source) { + if (hasOwnProperty.call(source, prop)) { + target[prop] = source[prop]; + } + } + + return target; +} + +/* global OX: false */ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function openx(global, data) { + const openxData = ['host', 'nc', 'auid', 'dfpSlot', 'dfp', 'openx']; + const dfpData = assign({}, data); // Make a copy for dfp. + + // TODO: check mandatory fields + validateData(data, [], openxData); + // Consolidate Doubleclick inputs for forwarding - + // conversion rules are explained in openx.md. + if (data.dfpSlot) { + // Anything starting with 'dfp' gets promoted. + openxData.forEach((openxKey) => { + if (openxKey in dfpData && openxKey !== 'dfp') { + if (openxKey.startsWith('dfp')) { + // Remove 'dfp' prefix, lowercase the first letter. + let fixKey = openxKey.substring(3); + fixKey = fixKey.substring(0, 1).toLowerCase() + fixKey.substring(1); + dfpData[fixKey] = data[openxKey]; + } + delete dfpData[openxKey]; + } + }); + + // Promote the whole 'dfp' object. + if ('dfp' in data) { + assign(dfpData, dfpData.dfp); + delete dfpData['dfp']; + } + } + + // Decide how to render. + if (data.host) { + let jssdk = `https://${data.host}/mw/1.0/jstag`; + + if (data.nc && data.dfpSlot) { + jssdk += '?nc=' + encodeURIComponent(data.nc); + if (data.auid) { + advanceImplementation(global, jssdk, dfpData, data); + } else { + standardImplementation(global, jssdk, dfpData); + } + } else if (data.auid) { + // Just show an ad. + global.OX_cmds = [ + () => { + const oxRequest = OX(); + const oxAnchor = global.document.createElement('div'); + global.document.body.appendChild(oxAnchor); + /*eslint "google-camelcase/google-camelcase": 0*/ + OX._requestArgs['bc'] = 'amp'; + oxRequest.addAdUnit(data.auid); + oxRequest.setAdSizes([data.width + 'x' + data.height]); + if (data.openx && data.openx.customVars) { + setCustomVars(oxRequest, filterCustomVar(data.openx.customVars)); + } + oxRequest.getOrCreateAdUnit(data.auid).set('anchor', oxAnchor); + global.context.renderStart(); + oxRequest.load(); + }, + ]; + loadScript(global, jssdk); + } + } else if (data.dfpSlot) { + // Fall back to a DFP ad. + doubleclick(global, dfpData); + } +} + +/** + * @param {!Window} global + * @param {string} jssdk + * @param {!Object} dfpData + */ +function standardImplementation(global, jssdk, dfpData) { + writeScript(global, jssdk, () => { + /*eslint "google-camelcase/google-camelcase": 0*/ + doubleclick(global, dfpData); + }); +} + +/** + * @param {!Window} global + * @param {string} jssdk + * @param {!Object} dfpData + * @param {*} data + */ +function advanceImplementation(global, jssdk, dfpData, data) { + const size = [data.width + 'x' + data.height]; + let customVars = {}; + if (data.openx && data.openx.customVars) { + customVars = filterCustomVar(data.openx.customVars); + } + global.OX_bidder_options = { + bidderType: 'hb_amp', + callback: () => { + const priceMap = global.oxhbjs && global.oxhbjs.getPriceMap(); + const slot = priceMap && priceMap['c']; + const targeting = slot + ? `${slot.size}_${slot.price},hb-bid-${slot.bid_id}` + : 'none_t'; + dfpData.targeting = dfpData.targeting || {}; + assign(dfpData.targeting, {oxb: targeting}); + doubleclick(global, dfpData); + }, + }; + global.OX_bidder_ads = [[data.dfpSlot, size, 'c', customVars]]; + loadScript(global, jssdk); +} + +/** + * @param {*} oxRequest + * @param {!Object} customVars + */ +function setCustomVars(oxRequest, customVars) { + const customVarKeys = Object.keys(customVars); + customVarKeys.forEach((customVarKey) => { + const customVarValue = customVars[customVarKey]; + if (Array.isArray(customVarValue)) { + /** @type {!Array} */ (customVarValue).forEach((value) => { + oxRequest.addVariable(customVarKey, value); + }); + } else { + oxRequest.addVariable(customVarKey, customVarValue); + } + }); +} + +/** + * @param {!Object} customVars + * @return {!Object} + */ +function filterCustomVar(customVars) { + const filterPattern = /^[A-Za-z0-9._]{1,20}$/; + const filteredKeys = Object.keys(customVars).filter((key) => + filterPattern.test(key) + ); + const filteredCustomVar = {}; + filteredKeys.forEach((key) => { + filteredCustomVar[key.toLowerCase()] = customVars[key]; + }); + return filteredCustomVar; +} diff --git a/ads/vendors/openx.md b/ads/vendors/openx.md new file mode 100644 index 0000000000000..ec302565bfa48 --- /dev/null +++ b/ads/vendors/openx.md @@ -0,0 +1,107 @@ + + +# OpenX + +## Examples + +- All OpenX `` tags require the `width`, `height`, and `type="openx"` parameters. +- Secure tags (HTTPS) are required for AMP. + +### OpenX Ad Server + +Display an OpenX Ad Unit. + +**Required**: + +- `data-auid` - The ad unit ID to display +- `data-host` - SSL-enabled OpenX delivery domain + +**Optional**: + +- `json` - Additional json options. + + - `customVars` - please refer to the [documentation](https://docs.openx.com/Content/developers/ad_request_api/custom_variables_in_ad_calls.html). + +```html + + +``` + +### OpenX Bidder + +OpenX header bidding. Parameters noted in the DoubleClick amp-ad [documentation](https://github.com/ampproject/amphtml/blob/main/ads/google/doubleclick.md) can be forwarded to DoubleClick by the following rules: + +1. Parameters like `data-dfp-{name}` will be converted to `data-{name}` and passed to DoubleClick +2. Everything under the json "dfp" key will be passed to DoubleClick + +**Required**: + +- `data-host` - SSL-enabled OpenX delivery domain +- `data-nc` - Network code '-' sitename +- `data-auid` - Open X Ad unit id to display +- `data-dfp-slot` - The DoubleClick slot + +**Optional**: + +- `json` - Additional json options. + + - `customVars` - please refer to the [documentation](https://docs.openx.com/Content/developers/ad_request_api/custom_variables_in_ad_calls.html). Also note that OpenX bidder limits these keys by the **allowlisted keys** set on your publisher settings. + +```html + + +``` + +### DoubleClick Fallback + +If no OpenX parameters are detected, the tag falls back to a proxy for the DoubleClick ad type. The same rules for +parameter conversion apply here as for bidder. + +**Required**: + +- `data-dfp-slot` - The DoubleClick slot + +**Optional**: + +- `json` - Additional json options. Only the "dfp" is currently respected. + +```html + + +``` diff --git a/ads/vendors/opinary.js b/ads/vendors/opinary.js new file mode 100644 index 0000000000000..85597f54b9ce6 --- /dev/null +++ b/ads/vendors/opinary.js @@ -0,0 +1,77 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @description Make canonicalUrl available from iframe + */ +function addCanonicalLinkTag(global) { + if (global.context.canonicalUrl) { + const link = global.document.createElement('link'); + link.setAttribute('rel', 'canonical'); + link.setAttribute('href', global.context.canonicalUrl); + global.document.head.appendChild(link); + } +} + +/** + * @param {!Window} global + * @param {!Object} data + * @return {?Node} + */ +function createContainer(global, data) { + // create div + const div = global.document.createElement('div'); + if (data.poll) { + div.className = 'opinary-widget-embed'; + div.dataset.customer = data.client; + div.dataset.poll = data.poll; + } else { + div.setAttribute('id', 'opinary-automation-placeholder'); + } + + return div; +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function opinary(global, data) { + validateData(data, ['client']); + + addCanonicalLinkTag(global); + + // c element is created by AMP + const c = global.document.getElementById('c'); + + // create div to detect if we are in AMP context + const isAmp = global.document.createElement('div'); + isAmp.setAttribute('id', 'opinaryAMP'); + c.appendChild(isAmp); + + // create div where poll should be shown + c.appendChild(createContainer(global, data)); + + // load script + if (data.poll) { + loadScript(global, `https://widgets.opinary.com/embed.js`); + } else { + loadScript(global, `https://widgets.opinary.com/a/${data.client}.js`); + } +} diff --git a/ads/vendors/opinary.md b/ads/vendors/opinary.md new file mode 100644 index 0000000000000..d278cc3391e37 --- /dev/null +++ b/ads/vendors/opinary.md @@ -0,0 +1,62 @@ + + +# Opinary + +## Example + +### AMS / Automated Matching System + +The automated matching system is an algorithm developed by Opinary which matches polls to articles. + +```html + + +``` + +### Embed / Manual Integration + +If you want to show a specific poll, you need to include the poll parameter, as shown in the example below. + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the Opinary account manager or refer to their documentation. + +### Required parameters + +- `data-client` - the customer name + +### Optional parameters + +- `data-poll` - the ID of the poll you want to show diff --git a/ads/vendors/outbrain.js b/ads/vendors/outbrain.js new file mode 100644 index 0000000000000..7e70ac16a4919 --- /dev/null +++ b/ads/vendors/outbrain.js @@ -0,0 +1,43 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function outbrain(global, data) { + // ensure we have valid widgetIds value + validateData(data, ['widgetids']); + + global._outbrain = global._outbrain || { + viewId: global.context.pageViewId, + widgetIds: data['widgetids'], + htmlURL: data['htmlurl'] || global.context.canonicalUrl, + ampURL: data['ampurl'] || global.context.sourceUrl, + fbk: data['fbk'] || '', + testMode: data['testmode'] || 'false', + styleFile: data['stylefile'] || '', + referrer: data['referrer'] || global.context.referrer, + }; + + // load the Outbrain AMP JS file + loadScript( + global, + 'https://widgets.outbrain.com/widgetAMP/outbrainAMP.min.js' + ); +} diff --git a/ads/vendors/outbrain.md b/ads/vendors/outbrain.md new file mode 100644 index 0000000000000..2dbe4d037a5b1 --- /dev/null +++ b/ads/vendors/outbrain.md @@ -0,0 +1,115 @@ + + +# Outbrain + +## Example installation of the Outbrain widget + +The examples below must be accompanied by AMP-enabled widgets delivered by Outbrain’s Account Management Team, do not directly install this code with existing widgets. + +### Basic + +```html + + +``` + +### Example code running our widget with a CMP & amp-consent module + +```html + + +``` + +### Sticky Ad + +```html + + + + +``` + +Note that `` component requires the following script to be included in the page: + +```html + +``` + +See [AMP documentation](https://amp.dev/documentation/components/amp-sticky-ad) for more information regarding `` component. + +## Configuration + +For details on the configuration semantics, please contact Outbrain’s Account Management Team.\ +These configurations are relevant for both `` and ``. + +### Required parameters + +- `data-widgetIds`: Widget Id/s Provided by Account Manager. + +### Optional parameters + +- `data-htmlURL`: The URL of the standard html version of the page. +- `data-ampURL`: The URL of the AMP version of the page. +- `data-styleFile`: Provide publisher an option to pass CSS file in order to inherit the design for the AMP displayed widget. **Consult with Account Manager regarding CSS options**. +- `data-block-on-consent`: Set this attribute without value in case you are using a CMP & the amp-consent module in order to make sure the consent info gets passed to the Outbrain widget correctly. + +### User Consent handling + +Outbrains AMP widgets are fully compliant with the european data protection regulations GDPR. For all users from the EU you should use a Consent Management Platform (CMP) to get the users consent decision. The widget will react to the consent information from the CMP and serve personalized widgets only if the provided TC string grants the right to do so. +If no TC string gets passed (because the widget is loaded before the user has made a choice or due to a wrong CMP implementation), the implementation code will render a non-personalized widget. + +While on regular pages Outbrain can access the user consent information directly from the CMP this info needs to get passed from the CMP to the widget element through the amp-consent module. In order to be able to send a personalized widget Outbrain needs to get the consent info (incl. the consent string) passed to it’s shared data object. + +If you are serving the Outbrain AMP widget to EU Users please make sure that: + +- you have a IAB complaint CMP properly setup on your page together with the amp-consent module +- you have added Outbrain to your approved vendors for your CMP (Outbrains Vendor ID is 164) with purposes 1-10 allowed +- The CMP is properly connected to the amp-consent module (follow your CMP provider instructions) +- you add the data-block-on-consent attribute to the amp-embed widget element + +Furthermore we encourage you to make sure that the widget does not get loaded before the user has made his choice through your CMP! +If this can’t be done you can also make sure that you send a TC String with non consent as a default value - in this case OB will be serving a non-personalized widget until the user has made a different choice. Please contact your CMP on how to set the no consent string for those cases. + +You’ll find more information on the official amp-consent documentation: +https://amp.dev/documentation/components/amp-consent/ + +## Troubleshooting + +### Widget is cut off + +According to the AMP API, "resizes are honored when the resize will not adjust the content the user is currently reading. That is, if the ad is above the viewport's contents, it'll resize. Same if it's below. If it's in the viewport, it ignores it." + +**Resolution** + +You can set an initial height of what the widget height is supposed to be. That is, instead of `height="100"`, if the widget's final height is 600px, then set `height="600"`. Setting the initial height **_will not_** finalize the widget height if it's different from the actual. The widget will resize to it's true dimensions after the widget leaves the viewport. diff --git a/ads/vendors/pixels.js b/ads/vendors/pixels.js new file mode 100644 index 0000000000000..01578210fef77 --- /dev/null +++ b/ads/vendors/pixels.js @@ -0,0 +1,41 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pixels(global, data) { + validateData(data, ['origin', 'sid', 'tag'], ['clickTracker', 'viewability']); + data.tag = data.tag.toString().toLowerCase(); + global._pixelsParam = data; + if (data.tag === 'sync') { + writeScript( + global, + 'https://cdn.adsfactor.net/amp/pixels-amp.min.js', + () => { + const pixelsAMPAd = global.pixelsAd; + const pixelsAMPTag = new pixelsAMPAd(data); + pixelsAMPTag.renderAmp(global.context); + global.context.renderStart(); + } + ); + } else { + global.context.noContentAvailable(); + } +} diff --git a/ads/vendors/pixels.md b/ads/vendors/pixels.md new file mode 100644 index 0000000000000..28dee798c468d --- /dev/null +++ b/ads/vendors/pixels.md @@ -0,0 +1,48 @@ + + +# Pixels + +## Example + +```html + + +``` + +## Configuration + +For additional details and support contact techteam@pixels.asia + +Required parameters: + +- data-origin - Specify which ad server group to handle the ad request. +- data-sid - Unique ad tag identifier. +- data-tag - Specify whether this tag is a sync tag. + +Optional parameters: + +- data-click-tracker - Specify whether there is a third party click-tracker. +- data-viewability - Specify whether the tag should record viewability statistics. diff --git a/ads/vendors/playstream.js b/ads/vendors/playstream.js new file mode 100644 index 0000000000000..d75d0874b8334 --- /dev/null +++ b/ads/vendors/playstream.js @@ -0,0 +1,38 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +const mandatoryParams = ['id']; +const optionalParams = ['fluid']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function playstream(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global.playstream = { + unitData: data['id'], + fluid: data['fluid'], + }; + validateData(data, mandatoryParams, optionalParams); + const searchParams = new URLSearchParams(data); + loadScript( + global, + 'https://app.playstream.media/js/amp.js?' + searchParams.toString() + ); +} diff --git a/ads/vendors/playstream.md b/ads/vendors/playstream.md new file mode 100644 index 0000000000000..a8ecafb30c82d --- /dev/null +++ b/ads/vendors/playstream.md @@ -0,0 +1,38 @@ + + +# PlayStream + +## Example + +```html + + +``` + +## Configuration + +Supported parameters: + +- `data-id` +- `data-fluid` diff --git a/ads/vendors/plista.js b/ads/vendors/plista.js new file mode 100644 index 0000000000000..7cc27a353e521 --- /dev/null +++ b/ads/vendors/plista.js @@ -0,0 +1,65 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function plista(global, data) { + // TODO: check mandatory fields + validateData( + data, + [], + [ + 'publickey', + 'widgetname', + 'urlprefix', + 'item', + 'geo', + 'categories', + 'countrycode', + ] + ); + const div = global.document.createElement('div'); + div.setAttribute('data-display', 'plista_widget_' + data.widgetname); + // container with id "c" is provided by amphtml + global.document.getElementById('c').appendChild(div); + window.PLISTA = { + publickey: data.publickey, + widgets: [ + { + name: data.widgetname, + pre: data.urlprefix, + }, + ], + item: data.item, + geo: data.geo, + categories: data.categories, + noCache: true, + useDocumentReady: false, + dataMode: 'data-display', + }; + + // load the plista modules asynchronously + loadScript( + global, + 'https://static' + + (data.countrycode ? '-' + encodeURIComponent(data.countrycode) : '') + + '.plista.com/async.js' + ); +} diff --git a/ads/plista.md b/ads/vendors/plista.md similarity index 92% rename from ads/plista.md rename to ads/vendors/plista.md index 18cf6e1faa4ef..48c4281fe578b 100644 --- a/ads/plista.md +++ b/ads/vendors/plista.md @@ -59,16 +59,16 @@ For semantics of configuration, please see [Plista's documentation](https://goo. Supported parameters: -- `data-countrycode` -- `data-publickey` -- `data-widgetname` -- `data-geo` -- `data-urlprefix` -- `data-categories` +- `data-countrycode` +- `data-publickey` +- `data-widgetname` +- `data-geo` +- `data-urlprefix` +- `data-categories` Supported via `json` attribute: -- `item` +- `item` ## Layout diff --git a/ads/vendors/polymorphicads.js b/ads/vendors/polymorphicads.js new file mode 100644 index 0000000000000..6c7ee66b8d0fc --- /dev/null +++ b/ads/vendors/polymorphicads.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function polymorphicads(global, data) { + validateData(data, ['adunit', 'params']); + global.polyParam = data; + writeScript(global, 'https://www.polymorphicads.jp/js/amp.js'); +} diff --git a/ads/polymorphicads.md b/ads/vendors/polymorphicads.md similarity index 94% rename from ads/polymorphicads.md rename to ads/vendors/polymorphicads.md index 090a7606a0368..ef25eb93959fe 100644 --- a/ads/polymorphicads.md +++ b/ads/vendors/polymorphicads.md @@ -35,5 +35,5 @@ For configuration details and to generate your tags, please contact [Polymorphic Supported parameters: -- `data-adunit`: adUnitId -- `data-params`: parameters +- `data-adunit`: adUnitId +- `data-params`: parameters diff --git a/ads/vendors/popin.js b/ads/vendors/popin.js new file mode 100644 index 0000000000000..0ab9fddd27488 --- /dev/null +++ b/ads/vendors/popin.js @@ -0,0 +1,36 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function popin(global, data) { + validateData(data, ['mediaid']); + + const d = global.document.createElement('div'); + d.id = '_popIn_amp_recommend'; + global.document.getElementById('c').appendChild(d); + + const url = + 'https://api.popin.cc/searchbox/' + + encodeURIComponent(data['mediaid']) + + '.js'; + + loadScript(global, url); +} diff --git a/ads/popin.md b/ads/vendors/popin.md similarity index 95% rename from ads/popin.md rename to ads/vendors/popin.md index 6809a26038e7a..caebdad7fc58a 100644 --- a/ads/popin.md +++ b/ads/vendors/popin.md @@ -36,6 +36,6 @@ For configuration details, please contact http://www.popin.cc/discovery/#contact Supported parameters: -- `height` -- `width` -- `data-mediaid` +- `height` +- `width` +- `data-mediaid` diff --git a/ads/vendors/postquare.js b/ads/vendors/postquare.js new file mode 100644 index 0000000000000..8e6055c3874b3 --- /dev/null +++ b/ads/vendors/postquare.js @@ -0,0 +1,43 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function postquare(global, data) { + validateData(data, ['widgetids']); + + global._postquare = global._postquare || { + viewId: global.context.pageViewId, + widgetIds: data['widgetids'], + websiteId: data['websiteid'], + publisherId: data['publisherid'], + url: data['url'] || global.context.canonicalUrl, + ampURL: data['ampurl'] || global.context.sourceUrl, + mode: data['mode'] || 1, + style: data['stylecss'] || '', + referrer: global.context.referrer, + }; + + if (data['mode'] == 100) { + loadScript(global, 'https://widget.engageya.com/pos_amp_loader.js'); + } else { + loadScript(global, 'https://widget.postquare.com/postquare_amp_loader.js'); + } +} diff --git a/ads/postquare.md b/ads/vendors/postquare.md similarity index 83% rename from ads/postquare.md rename to ads/vendors/postquare.md index 643c6286976c0..d6a35f0a8aca4 100644 --- a/ads/postquare.md +++ b/ads/vendors/postquare.md @@ -37,12 +37,12 @@ For details on the configuration semantics, please contact Postquare or refer to ### Required parameters -- `widgetIds`: Widget ids -- `websiteId`: Website Id -- `publisherId`: Publisher Id +- `widgetIds`: Widget ids +- `websiteId`: Website Id +- `publisherId`: Publisher Id ### Optional parameters -- `url`: Current none amp version URL -- `ampUrl`: Current AMP page URL -- `styleCSS`: Additional style +- `url`: Current none amp version URL +- `ampUrl`: Current AMP page URL +- `styleCSS`: Additional style diff --git a/ads/vendors/ppstudio.js b/ads/vendors/ppstudio.js new file mode 100644 index 0000000000000..d905fb863d7c7 --- /dev/null +++ b/ads/vendors/ppstudio.js @@ -0,0 +1,49 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ppstudio(global, data) { + validateData(data, ['crid', 'width', 'height', 'holderScript'], []); + + global._ppstudio = { + crid: data.crid, + width: data.width, + height: data.height, + holderScript: data.holderScript, + }; + + const e = global.document.createElement('script'); + e.id = 'pps-script-' + data.crid; + e.setAttribute('data-width', data.width); + e.setAttribute('data-height', data.height); + e.setAttribute('data-click-url', ''); + e.src = data.holderScript; + global.document.getElementById('c').appendChild(e); + + const i = global.document.createElement('ins'); + i.classList.add('ppstudio'); + i.setAttribute('data-pps-target-id', 'cr-' + data.crid); + global.document.getElementById('c').appendChild(i); + + loadScript(global, 'https://ads-cdn.tenmax.io/code/ppstudio.js', () => { + global.context.renderStart(); + }); +} diff --git a/ads/ppstudio.md b/ads/vendors/ppstudio.md similarity index 92% rename from ads/ppstudio.md rename to ads/vendors/ppstudio.md index d4337768cb1ac..9fea374cfa4fe 100644 --- a/ads/ppstudio.md +++ b/ads/vendors/ppstudio.md @@ -35,9 +35,9 @@ For configuration semantics, contact [TenMax](https://www.tenmax.io/en/). ### Required parameters -- `data-crid` -- `data-width` -- `data-height` -- `data-holderScript` +- `data-crid` +- `data-width` +- `data-height` +- `data-holderScript` ### Optional parameters diff --git a/ads/vendors/pressboard.js b/ads/vendors/pressboard.js new file mode 100644 index 0000000000000..061c7c28cc3b8 --- /dev/null +++ b/ads/vendors/pressboard.js @@ -0,0 +1,37 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pressboard(global, data) { + validateData(data, ['media']); + data.baseUrl = 'https://sr.studiostack.com'; + global.pbParams = data; + loadScript( + global, + data.baseUrl + '/js/amp-ad.js', + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/pressboard.md b/ads/vendors/pressboard.md similarity index 88% rename from ads/pressboard.md rename to ads/vendors/pressboard.md index 812e011b40745..69986fcae8bfc 100644 --- a/ads/pressboard.md +++ b/ads/vendors/pressboard.md @@ -25,8 +25,8 @@ limitations under the License. ## Configuration -For details on configuration, see [Pressboard documentation](http://help.pressboard.ca/publisher-resources/getting-started/implementing-google-amp). +For details on configuration, see [Pressboard documentation](https://help.pressboardmedia.com/implementing-google-amp). ### Required parameters -- `data-media` - Media ID +- `data-media` - Media ID diff --git a/ads/vendors/promoteiq.js b/ads/vendors/promoteiq.js new file mode 100644 index 0000000000000..630fdeddee282 --- /dev/null +++ b/ads/vendors/promoteiq.js @@ -0,0 +1,40 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; +import {user} from '../../src/log'; + +const TAG = 'PROMOTEIQ'; +const mandatoryDataFields = ['src', 'params', 'sfcallback']; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function promoteiq(global, data) { + validateData(data, mandatoryDataFields, []); + const sfInputs = parseJson(data['params']); + + loadScript(global, data['src'], () => { + if (!!global['TagDeliveryContent']) { + const sfCallback = new Function('response', data['sfcallback']); + global['TagDeliveryContent']['request'](sfInputs, sfCallback); + } else { + user().error(TAG, 'TagDeliveryContent object not loaded on page'); + } + }); +} diff --git a/ads/promoteiq.md b/ads/vendors/promoteiq.md similarity index 86% rename from ads/promoteiq.md rename to ads/vendors/promoteiq.md index 12b9ee313dbee..53d173fea3768 100644 --- a/ads/promoteiq.md +++ b/ads/vendors/promoteiq.md @@ -34,9 +34,9 @@ Provides support for AMP integration with [PromoteIQ](https://www.promoteiq.com/ ### Required parameters -- `data-src`: Publisher specific PromoteIQ CDN file. -- `data-input`: JSON stringified inputs. -- `data-sfcallback`: Stringified publisher rendering function. +- `data-src`: Publisher specific PromoteIQ CDN file. +- `data-input`: JSON stringified inputs. +- `data-sfcallback`: Stringified publisher rendering function. # Support diff --git a/ads/vendors/pubexchange.js b/ads/vendors/pubexchange.js new file mode 100644 index 0000000000000..610c8d01fc21b --- /dev/null +++ b/ads/vendors/pubexchange.js @@ -0,0 +1,36 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pubexchange(global, data) { + // ensure we have valid widgetIds value + validateData(data, ['publication', 'moduleId', 'moduleNum'], ['test']); + + global.PUBX = global.PUBX || { + pub: data['publication'], + modNum: data['moduleNum'], + modId: data['moduleId'], + test: data['test'], + }; + + // load the Outbrain AMP JS file + loadScript(global, 'https://main.pubexchange.com/loader-amp.min.js'); +} diff --git a/ads/pubexchange.md b/ads/vendors/pubexchange.md similarity index 75% rename from ads/pubexchange.md rename to ads/vendors/pubexchange.md index a29890cd94ad8..b60d05c943b36 100644 --- a/ads/pubexchange.md +++ b/ads/vendors/pubexchange.md @@ -41,10 +41,10 @@ For semantics of configuration, please see [PubExchange's documentation](https:/ ### Required parameters -- `data-publication`: Shortcode identifying publication provided by PubExchange account manager -- `data-module-id`: Shortcode identifying module provided by PubExchange account manager -- `data-module-num`: ID identifying module provided by PubExchange account manager +- `data-publication`: Shortcode identifying publication provided by PubExchange account manager +- `data-module-id`: Shortcode identifying module provided by PubExchange account manager +- `data-module-num`: ID identifying module provided by PubExchange account manager ### Optional parameters -- `data-test`: Pass the parameter with the "true" value to test the PubExchange module +- `data-test`: Pass the parameter with the "true" value to test the PubExchange module diff --git a/ads/vendors/pubguru.js b/ads/vendors/pubguru.js new file mode 100644 index 0000000000000..a84a18e5115e0 --- /dev/null +++ b/ads/vendors/pubguru.js @@ -0,0 +1,38 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pubguru(global, data) { + validateData(data, ['publisher', 'slot']); + + global.$pubguru = data; + + const el = global.document.createElement('div'); + el.setAttribute('id', 'the-ad-unit'); + + global.document.getElementById('c').appendChild(el); + loadScript( + global, + 'https://amp.pubguru.org/amp.' + + encodeURIComponent(data.publisher) + + '.min.js' + ); +} diff --git a/ads/pubguru.md b/ads/vendors/pubguru.md similarity index 100% rename from ads/pubguru.md rename to ads/vendors/pubguru.md diff --git a/ads/vendors/pubmatic.js b/ads/vendors/pubmatic.js new file mode 100644 index 0000000000000..16f0d3e089ad3 --- /dev/null +++ b/ads/vendors/pubmatic.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript} from '../../3p/3p'; + +/* global PubMatic: false */ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pubmatic(global, data) { + loadScript(global, 'https://ads.pubmatic.com/AdServer/js/amp.js', () => { + data.kadpageurl = global.context.sourceUrl || global.context.location.href; + PubMatic.showAd(data); + }); +} diff --git a/ads/pubmatic.md b/ads/vendors/pubmatic.md similarity index 96% rename from ads/pubmatic.md rename to ads/vendors/pubmatic.md index 97e04587972a3..86bbfde79e757 100644 --- a/ads/pubmatic.md +++ b/ads/vendors/pubmatic.md @@ -34,6 +34,6 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `json` -- `width` -- `height` +- `json` +- `width` +- `height` diff --git a/ads/vendors/pubmine.js b/ads/vendors/pubmine.js new file mode 100644 index 0000000000000..65a229bfa238b --- /dev/null +++ b/ads/vendors/pubmine.js @@ -0,0 +1,95 @@ +/** + * Copyright 2016 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. + */ + +import {CONSENT_POLICY_STATE} from '../../src/core/constants/consent-state'; +import {loadScript, validateData} from '../../3p/3p'; + +const pubmineOptional = ['section', 'pt', 'ht', 'npaOnUnknownConsent'], + pubmineRequired = ['siteid'], + pubmineURL = 'https://s.pubmine.com/head.js'; + +/** + * @param {!Object} data + * @param {!Window} global + */ +function initMasterFrame(data, global) { + /* + * INSUFFICIENT and UNKNOWN should be treated as INSUFFICIENT + * unless state is UNKNOWN and `data-npa-on-unknown-consent=false` + */ + const paUnknown = + data['npaOnUnknownConsent'] !== undefined && + 'false' == data['npaOnUnknownConsent']; + const ctxt = global.context; + const consent = + ctxt.initialConsentState === null || + ctxt.initialConsentState === CONSENT_POLICY_STATE.SUFFICIENT || + ctxt.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED || + (ctxt.initialConsentState === CONSENT_POLICY_STATE.UNKNOWN && paUnknown); + + global['__ATA_PP'] = { + pt: data['pt'] || 1, + ht: data['ht'] || 1, + tn: 'amp', + amp: true, + consent: consent ? 1 : 0, + siteid: Number(data['siteid']) || undefined, + }; + global['__ATA'] = global['__ATA'] || {}; + global['__ATA']['cmd'] = global['__ATA']['cmd'] || []; + loadScript(global, pubmineURL); +} + +/** + * @param {string} slotId + * @param {!Window} global + */ +function createSlot(slotId, global) { + const containerEl = global.document.getElementById('c'); + const adSlot = global.document.createElement('div'); + adSlot.setAttribute('id', slotId); + containerEl.appendChild(adSlot); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pubmine(global, data) { + validateData(data, pubmineRequired, pubmineOptional); + + const sectionId = data['siteid'] + (data['section'] || '1'); + + const slotConfig = { + sectionId, + height: data.height == 250 ? 250 : data.height - 15, + width: data.width, + window: global, + }; + + const slotId = `atatags-${sectionId}`; + + createSlot(slotId, global); + const {isMaster} = global.context; + if (isMaster) { + initMasterFrame(data, global); + } + const master = isMaster ? global : global.context.master; + master['__ATA']['cmd']['push'](function () { + master['__ATA']['insertStyles'](global); + master['__ATA']['initSlot'](slotId, slotConfig); + }); +} diff --git a/ads/vendors/pubmine.md b/ads/vendors/pubmine.md new file mode 100644 index 0000000000000..b9f56d73ff8ec --- /dev/null +++ b/ads/vendors/pubmine.md @@ -0,0 +1,69 @@ + + +# Pubmine + +## Example + +### Basic + +```html + + +``` + +### With all attributes + +```html + + +``` + +## Configuration + +For further configuration information, please [contact Pubmine](https://wordpress.com/help/contact). + +Please note that the height parameter should be 15 greater than your ad size to ensure there is enough room for the "Report this ad" link. + +### Required parameters + +- `data-siteid`: Pubmine publisher site number. + +### Optional parameters + +- `data-section`: Pubmine slot identifier +- `data-pt`: Enum value for page type +- `data-ht`: Enum value for hosting type +- `data-npa-on-unknown-consent`: Flag for allowing/prohibiting non-personalized-ads on unknown consent. + +## Consent Support + +Pubmine's amp-ad adheres to a user's consent in the following ways: + +- No `data-block-on-consent` attribute: Pubmine amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.SUFFICIENT`: Pubmine amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Pubmine amp-ad will display a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Pubmine amp-ad will display a personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN`: Pubmine amp-ad will display a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN` and `data-npa-on-unknown-consent=false`: Pubmine amp-ad will display a personalized ad to the user. diff --git a/ads/vendors/puffnetwork.js b/ads/vendors/puffnetwork.js new file mode 100644 index 0000000000000..6fdfb0287c8ff --- /dev/null +++ b/ads/vendors/puffnetwork.js @@ -0,0 +1,27 @@ +/** + * Copyright 2019 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function puffnetwork(global, data) { + validateData(data, ['chid']); + global.pn = data; + writeScript(global, 'https://static.puffnetwork.com/amp_ad.js'); +} diff --git a/ads/puffnetwork.md b/ads/vendors/puffnetwork.md similarity index 95% rename from ads/puffnetwork.md rename to ads/vendors/puffnetwork.md index 44b99d1e4e869..48866f3395606 100644 --- a/ads/puffnetwork.md +++ b/ads/vendors/puffnetwork.md @@ -28,6 +28,6 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-chid` -- `width` -- `height` +- `data-chid` +- `width` +- `height` diff --git a/ads/vendors/pulse.js b/ads/vendors/pulse.js new file mode 100644 index 0000000000000..0a8aaaae63faf --- /dev/null +++ b/ads/vendors/pulse.js @@ -0,0 +1,28 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pulse(global, data) { + validateData(data, ['sid']); + global.pulseInit = data; + // load the pulse initializer asynchronously + loadScript(global, 'https://static.pulse.mail.ru/pulse-widget-amp.js'); +} diff --git a/ads/vendors/pulse.md b/ads/vendors/pulse.md new file mode 100644 index 0000000000000..c192b52115a13 --- /dev/null +++ b/ads/vendors/pulse.md @@ -0,0 +1,27 @@ + + +# Pulse + +## Example + +```html + +``` + +### Required parameters + +- `data-sid` diff --git a/ads/vendors/pulsepoint.js b/ads/vendors/pulsepoint.js new file mode 100644 index 0000000000000..e23d3d4daf890 --- /dev/null +++ b/ads/vendors/pulsepoint.js @@ -0,0 +1,79 @@ +/** + * Copyright 2015 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. + */ + +import {doubleclick} from '../../ads/google/doubleclick'; +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function pulsepoint(global, data) { + // TODO: check mandatory fields + validateData(data, [], ['pid', 'tagid', 'tagtype', 'slot', 'timeout']); + if (data.tagtype === 'hb') { + headerBidding(global, data); + } else { + tag(global, data); + } +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function tag(global, data) { + writeScript( + global, + 'https://tag.contextweb.com/getjs.aspx?action=VIEWAD' + + '&cwpid=' + + encodeURIComponent(data.pid) + + '&cwtagid=' + + encodeURIComponent(data.tagid) + + '&cwadformat=' + + encodeURIComponent(data.width + 'X' + data.height) + ); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function headerBidding(global, data) { + loadScript(global, 'https://ads.contextweb.com/ht.js', () => { + const hbConfig = { + timeout: data.timeout || 1000, + slots: [ + { + cp: data.pid, + ct: data.tagid, + cf: data.width + 'x' + data.height, + placement: data.slot, + elementId: 'c', + }, + ], + done(targeting) { + doubleclick(global, { + width: data.width, + height: data.height, + slot: data.slot, + targeting: targeting[data.slot], + }); + }, + }; + new window.PulsePointHeaderTag(hbConfig).init(); + }); +} diff --git a/ads/pulsepoint.md b/ads/vendors/pulsepoint.md similarity index 78% rename from ads/pulsepoint.md rename to ads/vendors/pulsepoint.md index dcb85edebcb24..d0987ade21505 100644 --- a/ads/pulsepoint.md +++ b/ads/vendors/pulsepoint.md @@ -52,9 +52,9 @@ For semantics of configuration, please see [PulsePoint's documentation](https:// Supported parameters: -- `pid`: Publisher Id -- `tagid`: Tag Id -- `tagtype`: Tag Type. "hb" represents Header bidding, otherwise treated as regular tag. -- `size`: Ad Size represented 'widthxheight' -- `slot`: DFP slot id, required for header bidding tag -- `timeout`: optional timeout for header bidding, default is 1000ms. +- `pid`: Publisher Id +- `tagid`: Tag Id +- `tagtype`: Tag Type. "hb" represents Header bidding, otherwise treated as regular tag. +- `size`: Ad Size represented 'widthxheight' +- `slot`: DFP slot id, required for header bidding tag +- `timeout`: optional timeout for header bidding, default is 1000ms. diff --git a/ads/vendors/purch.js b/ads/vendors/purch.js new file mode 100644 index 0000000000000..975c8d2fa8fa6 --- /dev/null +++ b/ads/vendors/purch.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, validateSrcPrefix, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function purch(global, data) { + validateData(data, [], ['pid', 'divid', 'config']); + global.data = data; + + const adsrc = 'https://ramp.purch.com/serve/creative_amp.js'; + validateSrcPrefix('https:', adsrc); + writeScript(global, adsrc); +} diff --git a/ads/purch.md b/ads/vendors/purch.md similarity index 81% rename from ads/purch.md rename to ads/vendors/purch.md index 1ea077fa1f5c9..b14768de99ac1 100644 --- a/ads/purch.md +++ b/ads/vendors/purch.md @@ -36,7 +36,7 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-pid`: placement id -- `data-divid`: div id of unit -- `data-config`: Optinal parameter to control the ad behaviour. -- `data-config.targeting`: Optinal config parameter to pass key-values to DFP/GAM. +- `data-pid`: placement id +- `data-divid`: div id of unit +- `data-config`: Optinal parameter to control the ad behaviour. +- `data-config.targeting`: Optinal config parameter to pass key-values to DFP/GAM. diff --git a/ads/vendors/quoraad.js b/ads/vendors/quoraad.js new file mode 100644 index 0000000000000..9f6c722c3a5cb --- /dev/null +++ b/ads/vendors/quoraad.js @@ -0,0 +1,27 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function quoraad(global, data) { + validateData(data, ['adid']); + global.ampAdParam = data; + writeScript(global, 'https://a.quora.com/amp_ad.js'); +} diff --git a/ads/quoraad.md b/ads/vendors/quoraad.md similarity index 98% rename from ads/quoraad.md rename to ads/vendors/quoraad.md index 1142472f49403..7f0c6af75d7f5 100644 --- a/ads/quoraad.md +++ b/ads/vendors/quoraad.md @@ -30,4 +30,4 @@ Please consult internal reference documents on AMP. This is used only for first- Required parameter: -- `data-aid` +- `data-aid` diff --git a/ads/vendors/rakutenunifiedads.js b/ads/vendors/rakutenunifiedads.js new file mode 100644 index 0000000000000..c0f89bf21d71a --- /dev/null +++ b/ads/vendors/rakutenunifiedads.js @@ -0,0 +1,33 @@ +/** + * Copyright 2020 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function rakutenunifiedads(global, data) { + validateData(data, ['id']); + if (hasOwn(data, 'env')) { + data.env = `${data.env}-`; + } else { + data.env = ''; + } + global.runa = data; + writeScript(global, `https://${data.env}s-cdn.rmp.rakuten.co.jp/js/amp.js`); +} diff --git a/ads/vendors/rakutenunifiedads.md b/ads/vendors/rakutenunifiedads.md new file mode 100644 index 0000000000000..f3251b39363fc --- /dev/null +++ b/ads/vendors/rakutenunifiedads.md @@ -0,0 +1,86 @@ + + +# Rakuten Unified Ads + +## Example + +```html + + +``` + +```html + + +``` + +## Configuration + +### Required parameters + +- `data-id` : Your adspot id +- `type` : fixed value `rakutenunifiedads` + +### Optional parameters + +- `data-env` : Environment of server for Not production. e.g. `dev`, `stg`, `tst` +- `data-genre` : Genre object +- `data-ifa` : IFA string +- `data-targeting` : Targeting object +- `data-iscode` : ID type. 'true': `data-id` works as code + +### Set id as adSpot code + +- `data-id` : Your code with `data-iscode` +- `data-iscode` : ID type. 'true': id works as code + +```html + + +``` + +### How to handle responsive design + +Please refer to [Create responsive AMP pages](https://amp.dev/documentation/guides-and-tutorials/develop/style_and_layout/responsive_design/) + +```html + + +``` diff --git a/ads/vendors/rbinfox.js b/ads/vendors/rbinfox.js new file mode 100644 index 0000000000000..8b3e92fcf4062 --- /dev/null +++ b/ads/vendors/rbinfox.js @@ -0,0 +1,67 @@ +/** + * Copyright 2017 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. + */ +import {loadScript, validateData, validateSrcPrefix} from '../../3p/3p'; + +const jsnPrefix = 'https://rb.infox.sg/'; +const n = 'infoxContextAsyncCallbacks'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function rbinfox(global, data) { + validateData(data, ['src']); + const {src} = data; + validateSrcPrefix(jsnPrefix, src); + + addToQueue(global, src); + loadScript(global, src); +} + +/** + * @param {!Window} global + * @param {string} renderTo + */ +function createContainer(global, renderTo) { + const d = global.document.createElement('div'); + d.id = renderTo; + global.document.getElementById('c').appendChild(d); +} + +/** + * @param {string} src + * @return {string} + */ +function getBlockId(src) { + const parts = src.split('/'); + return parts[parts.length - 1]; +} + +/** + * @param {!Window} global + * @param {string} src + */ +function addToQueue(global, src) { + const blockId = getBlockId(src); + const ctx = n + blockId; + global[ctx] = global[ctx] || []; + global[ctx].push(() => { + const renderTo = 'infox_' + blockId; + // Create container + createContainer(global, renderTo); + global['INFOX' + blockId].renderTo(renderTo); + }); +} diff --git a/ads/rbinfox.md b/ads/vendors/rbinfox.md similarity index 98% rename from ads/rbinfox.md rename to ads/vendors/rbinfox.md index cea03e39d9ab4..efee0c8e3345b 100644 --- a/ads/rbinfox.md +++ b/ads/vendors/rbinfox.md @@ -34,4 +34,4 @@ For semantics of configuration, please see [Rb.Infox documentation](https://adm. ### Required parameters -- `src` +- `src` diff --git a/ads/vendors/rcmwidget.js b/ads/vendors/rcmwidget.js new file mode 100644 index 0000000000000..0a01dd1a142a1 --- /dev/null +++ b/ads/vendors/rcmwidget.js @@ -0,0 +1,49 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +const WIDGET_DEFAULT_NODE_ID = 'rcm-widget'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function rcmwidget(global, data) { + validateData( + data, + ['rcmId', 'nodeId', 'blockId', 'templateName', 'projectId'], + ['contextItemId'] + ); + + 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 new file mode 100644 index 0000000000000..3a8e7fdcf2b70 --- /dev/null +++ b/ads/vendors/rcmwidget.md @@ -0,0 +1,31 @@ + + +# Rcmwidget + +## Example + +```html + +``` + +### Required parameters + +- `data-rcm-id` +- `data-node-id` +- `data-block-id` +- `data-template-name` +- `data-project-id` diff --git a/ads/vendors/readmo.js b/ads/vendors/readmo.js new file mode 100644 index 0000000000000..bfb6caca78e9d --- /dev/null +++ b/ads/vendors/readmo.js @@ -0,0 +1,44 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function readmo(global, data) { + validateData(data, ['section']); + + const config = { + container: '#c', + amp: true, + }; + + if (data.url) { + global.publisherUrl = data.url; + } + + Object.keys(data).forEach((property) => { + config[property] = data[property]; + }); + + (global.readmo = global.readmo || []).push(config); + + loadScript(global, 'https://s.yimg.com/dy/ads/readmo.js', () => + global.context.renderStart() + ); +} diff --git a/ads/vendors/readmo.md b/ads/vendors/readmo.md new file mode 100644 index 0000000000000..310acea1a1a1e --- /dev/null +++ b/ads/vendors/readmo.md @@ -0,0 +1,48 @@ + + +# ReadMo + +## Example + +ReadMo only requires a section code to run. Please work with your account manager to properly configure your AMP section. + +### Basic + +```html + + +``` + +### Required parameters + +- `data-section` : A unique identifier that represents your site and placement + +### Optional parameters + +- `data-module` : Defines the type of module to render (`end-of-article`, `smart-feed`, `smart-feed-video`, `side-rail`) +- `data-infinite` : If true, enables infinite feed for your module +- `data-title` : The title that appears above the module (defaults to "You May Like") +- `data-sponsored-by-label` : Text override to the default "Sponsored by" label that appears next to the sponsors name +- `data-url` : Publisher url override +- `json` : Use this to pass additional configuration properties (ex: `json='{ "contentId": 1234 }'`) diff --git a/ads/vendors/realclick.js b/ads/vendors/realclick.js new file mode 100644 index 0000000000000..730a3bf5a0acd --- /dev/null +++ b/ads/vendors/realclick.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function realclick(global, data) { + validateData(data, ['mcode']); + global.rcParams = data; + loadScript(global, 'https://ssp.realclick.co.kr/amp/ad.js'); +} diff --git a/ads/realclick.md b/ads/vendors/realclick.md similarity index 98% rename from ads/realclick.md rename to ads/vendors/realclick.md index a088fa6a6b3ab..803ad613c76fc 100644 --- a/ads/realclick.md +++ b/ads/vendors/realclick.md @@ -34,4 +34,4 @@ For configuration details and to generate your tags, please contact [Realclick]( Supported parameters: -- `data-mcode` +- `data-mcode` diff --git a/ads/vendors/recomad.js b/ads/vendors/recomad.js new file mode 100644 index 0000000000000..bb40d498d3c9e --- /dev/null +++ b/ads/vendors/recomad.js @@ -0,0 +1,70 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * Add a container for the recomAD widget, + * which will be discovered by the script automatically. + * + * @param {Element} container + * @param {string} appId + * @param {string} widgetId + * @param {string} searchTerm + * @param {string} origin + * @param {string} baseUrl + * @param {string} puid + */ +function createWidgetContainer( + container, + appId, + widgetId, + searchTerm, + origin, + baseUrl, + puid +) { + container.className = 's24widget'; + + container.setAttribute('data-app-id', appId); + container.setAttribute('data-widget-id', widgetId); + searchTerm && container.setAttribute('data-search-term', searchTerm); + origin && container.setAttribute('data-origin', origin); + baseUrl && container.setAttribute('data-base-url', baseUrl); + puid && container.setAttribute('data-puid', puid); + + window.document.body.appendChild(container); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function recomad(global, data) { + validateData(data, ['appId', 'widgetId', ['searchTerm', 'origin']]); + + createWidgetContainer( + window.document.createElement('div'), + data['appId'], + data['widgetId'], + data['searchTerm'] || '', + data['origin'] || '', + data['baseUrl'] || '', + data['puid'] || '' + ); + + loadScript(window, 'https://widget.s24.com/js/s24widget.min.js'); +} diff --git a/ads/recomad.md b/ads/vendors/recomad.md similarity index 81% rename from ads/recomad.md rename to ads/vendors/recomad.md index e1006e2cf5056..d168a5133ac93 100644 --- a/ads/recomad.md +++ b/ads/vendors/recomad.md @@ -44,16 +44,16 @@ For details on the configuration semantics, please contact the [ad network](#con ### Required parameters -- `data-app-id` : Your app id -- `data-widget-id` : Your widget id +- `data-app-id` : Your app id +- `data-widget-id` : Your widget id Please contact us at cooperations@s24.com to receive an `app id` and a `widget id`. ### Required _at least one of these two_ parameters -- `data-search-term` : Required if _recomAD Search_. The search term you would like to get products for -- `data-origin` : Required if _recomAD Semantic_. Your canonical link of your original page +- `data-search-term` : Required if _recomAD Search_. The search term you would like to get products for +- `data-origin` : Required if _recomAD Semantic_. Your canonical link of your original page ### Optional parameters -- `data-puid` : Your tracking id for the end user +- `data-puid` : Your tracking id for the end user diff --git a/ads/vendors/recreativ.js b/ads/vendors/recreativ.js new file mode 100644 index 0000000000000..0da417a2f387d --- /dev/null +++ b/ads/vendors/recreativ.js @@ -0,0 +1,39 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function recreativ(global, data) { + validateData(data, ['bn']); + const target = global.document.createElement('div'); + target.id = 'bn_' + data['bn']; + global.document.getElementById('c').appendChild(target); + + loadScript( + global, + 'https://go.rcvlink.com/static/amp.js', + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/vendors/recreativ.md b/ads/vendors/recreativ.md new file mode 100644 index 0000000000000..c9a2ddc7bf072 --- /dev/null +++ b/ads/vendors/recreativ.md @@ -0,0 +1,46 @@ + + +# Recreativ + +Provides support for [Recreativ](https://recreativ.com/) widgets. + +## Example + +### With fixed height + +```html + +``` + +### With fixed size + +```html + +``` + +## Configuration + +For details on the configuration semantics, please contact [Recreativ](https://recreativ.com/help#contacts). + +### Required parameters + +- `data-bn` diff --git a/ads/vendors/relap.js b/ads/vendors/relap.js new file mode 100644 index 0000000000000..0897b1eb6be14 --- /dev/null +++ b/ads/vendors/relap.js @@ -0,0 +1,71 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function relap(global, data) { + validateData(data, [], ['token', 'url', 'anchorid', 'version']); + + 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'], + anchorEl: global.document.getElementById('c'), + position: 'append', + events: { + onReady: function () { + window.context.renderStart(); + }, + onNoContent: function () { + window.context.noContentAvailable(); + }, + }, + }); + }; + + loadScript(global, 'https://relap.io/v7/relap.js'); + } else { + window.relapV6WidgetReady = function () { + window.context.renderStart(); + }; + + window.relapV6WidgetNoSimilarPages = function () { + window.context.noContentAvailable(); + }; + + const anchorEl = global.document.createElement('div'); + anchorEl.id = data['anchorid']; + global.document.getElementById('c').appendChild(anchorEl); + + const url = `https://relap.io/api/v6/head.js?token=${encodeURIComponent( + data['token'] + )}&url=${encodeURIComponent(urlParam)}`; + loadScript(global, url); + } +} diff --git a/ads/relap.md b/ads/vendors/relap.md similarity index 93% rename from ads/relap.md rename to ads/vendors/relap.md index 1f80091a2741e..72f590a1d0315 100644 --- a/ads/relap.md +++ b/ads/vendors/relap.md @@ -37,7 +37,7 @@ For semantics of configuration, please see Relap's documentation. Currently supp Supported parameters: -- `data-token` -- `data-url` -- `data-anchorid` -- `data-version` +- `data-token` +- `data-url` +- `data-anchorid` +- `data-version` diff --git a/ads/vendors/relappro.js b/ads/vendors/relappro.js new file mode 100644 index 0000000000000..d5b6c47af9331 --- /dev/null +++ b/ads/vendors/relappro.js @@ -0,0 +1,31 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function relappro(global, data) { + validateData(data, [], ['slotId', 'nameAdUnit', 'requirements']); + global.params = data; + + loadScript( + global, + 'https://cdn.relappro.com/adservices/amp/relappro.amp.min.js' + ); +} diff --git a/ads/relappro.md b/ads/vendors/relappro.md similarity index 93% rename from ads/relappro.md rename to ads/vendors/relappro.md index 9afc64c3347b3..d92edcd8e3ca8 100644 --- a/ads/relappro.md +++ b/ads/vendors/relappro.md @@ -34,6 +34,6 @@ For configuration details and to generate your tags, please contact us at [Relap Supported parameters: -- `data-slot-id` -- `data-name-ad-unit` -- `data-requirements` +- `data-slot-id` +- `data-name-ad-unit` +- `data-requirements` diff --git a/ads/vendors/remixd.js b/ads/vendors/remixd.js new file mode 100644 index 0000000000000..8bc4aad12e8b2 --- /dev/null +++ b/ads/vendors/remixd.js @@ -0,0 +1,40 @@ +/** + * Copyright 2020 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. + */ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function remixd(global, data) { + global._rmxd = {}; + global._rmxd.url = data.url || global.context.sourceUrl; + global._rmxd.amp = true; + const sriptVersion = data.version || '5'; + const tagUrl = + 'https://tags.remixd.com/player/v' + + sriptVersion + + '/index.js?cb=' + + Math.random(); + + document.write( + '<' + + 'script src="' + + encodeURI(tagUrl) + + '" id="remixd-audio-player-script"><' + + '/script>' + ); + global.context.renderStart(); +} diff --git a/ads/vendors/remixd.md b/ads/vendors/remixd.md new file mode 100644 index 0000000000000..30fd3d26c118f --- /dev/null +++ b/ads/vendors/remixd.md @@ -0,0 +1,39 @@ + + +# Remixd + +## Example + +```html + + +``` + +## Configuration + +Before starting any Remixd AMP setup, please reach out to your account manager for the most up to date documentation or contact [contact@remixd.com](mailto:contact@remixd.com). + +### Optional parameters + +- `data-url`: The amp page source URL. diff --git a/ads/vendors/revcontent.js b/ads/vendors/revcontent.js new file mode 100644 index 0000000000000..8a05d8b5bd650 --- /dev/null +++ b/ads/vendors/revcontent.js @@ -0,0 +1,76 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function revcontent(global, data) { + let endpoint = + 'https://labs-cdn.revcontent.com/build/amphtml/revcontent.amp.min.js'; + + 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'; + } + } + + 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', + ]; + + data.endpoint = data.endpoint ? data.endpoint : 'trends.revcontent.com'; + + validateData(data, required, optional); + global.data = data; + if (data.loadscript) { + loadScript(window, endpoint); + } else { + writeScript(window, endpoint); + } +} diff --git a/ads/revcontent.md b/ads/vendors/revcontent.md similarity index 90% rename from ads/revcontent.md rename to ads/vendors/revcontent.md index d4d18beb7df53..1570c8318a8db 100644 --- a/ads/revcontent.md +++ b/ads/vendors/revcontent.md @@ -50,15 +50,15 @@ For semantics of configuration, please see [Revcontent's documentation](https:// Supported parameters: -- `data-id` -- `data-revcontent` -- `data-env` -- `data-wrapper` -- `data-endpoint` -- `data-ssl` -- `data-testing` -- `data-loadscript` -- `data-sub-ids` +- `data-id` +- `data-revcontent` +- `data-env` +- `data-wrapper` +- `data-endpoint` +- `data-ssl` +- `data-testing` +- `data-loadscript` +- `data-sub-ids` ## Auto-sizing of Ads diff --git a/ads/vendors/revjet.js b/ads/vendors/revjet.js new file mode 100644 index 0000000000000..77fe80d9141f1 --- /dev/null +++ b/ads/vendors/revjet.js @@ -0,0 +1,36 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function revjet(global, data) { + validateData(data, ['tag', 'key'], ['plc', 'opts', 'params']); + + global._revjetData = {...data}; + + loadScript( + global, + 'https://cdn.revjet.com/~cdn/JS/03/amp.js', + /* opt_cb */ undefined, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/revjet.md b/ads/vendors/revjet.md similarity index 92% rename from ads/revjet.md rename to ads/vendors/revjet.md index ad3a15ee8f5b9..a85fa64296117 100644 --- a/ads/revjet.md +++ b/ads/vendors/revjet.md @@ -38,11 +38,11 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-tag` -- `data-key` +- `data-tag` +- `data-key` ### Optional parameters -- `data-plc` -- `data-opts` -- `data-params` +- `data-plc` +- `data-opts` +- `data-params` diff --git a/ads/vendors/rfp.js b/ads/vendors/rfp.js new file mode 100644 index 0000000000000..85ecb824c15d7 --- /dev/null +++ b/ads/vendors/rfp.js @@ -0,0 +1,27 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function rfp(global, data) { + validateData(data, ['adspotId'], ['stylesheetUrl', 'country']); + global.rfpData = data; + writeScript(global, 'https://js.rfp.fout.jp/rfp-amp.js'); +} diff --git a/ads/rfp.md b/ads/vendors/rfp.md similarity index 93% rename from ads/rfp.md rename to ads/vendors/rfp.md index 9aa552b2991ef..ee76efebe2302 100644 --- a/ads/rfp.md +++ b/ads/vendors/rfp.md @@ -28,9 +28,9 @@ For configuration details and to generate your tags, please contact https://www. ### Required parameters -- `data-adspot-id` +- `data-adspot-id` ### Optional parameters -- `data-stylesheet-url` -- `data-country` +- `data-stylesheet-url` +- `data-country` diff --git a/ads/vendors/rnetplus.js b/ads/vendors/rnetplus.js new file mode 100644 index 0000000000000..ee62af8cc9667 --- /dev/null +++ b/ads/vendors/rnetplus.js @@ -0,0 +1,57 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData, validateSrcPrefix} from '../../3p/3p'; + +const jsnPrefix = 'https://api.rnet.plus/'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function rnetplus(global, data) { + validateData(data, ['src']); + const {src} = data; + validateSrcPrefix(jsnPrefix, src); + createContainer(global, 'rnetplus_' + getBlockId(src)); + loadScript(global, src); +} + +/** + * @param {!Window} global + * @param {string} renderTo + */ +function createContainer(global, renderTo) { + const d = global.document.createElement('div'); + d.id = renderTo; + global.document.getElementById('c').appendChild(d); +} + +/** + * @param {string} src + * @return {string} + */ +function getBlockId(src) { + const parts = src.split('?'); + const vars = parts[1].split('&'); + for (let j = 0; j < vars.length; ++j) { + const pair = vars[j].split('='); + if (pair[0] == 'blockId') { + return pair[1]; + } + } + return '660'; +} diff --git a/ads/rnetplus.md b/ads/vendors/rnetplus.md similarity index 99% rename from ads/rnetplus.md rename to ads/vendors/rnetplus.md index 24ce03b240c6d..26bc61bcd33f5 100644 --- a/ads/rnetplus.md +++ b/ads/vendors/rnetplus.md @@ -36,4 +36,4 @@ For details on the configuration semantics, please contact [Rambler](https://adm ### Required parameters -- `src` +- `src` diff --git a/ads/vendors/rubicon.js b/ads/vendors/rubicon.js new file mode 100644 index 0000000000000..2d727e5f8db0f --- /dev/null +++ b/ads/vendors/rubicon.js @@ -0,0 +1,69 @@ +/** + * Copyright 2015 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function rubicon(global, data) { + // TODO: check mandatory fields + validateData( + data, + [], + [ + 'account', + 'site', + 'zone', + 'size', + 'kw', + 'visitor', + 'inventory', + 'method', + 'callback', + ] + ); + + if (data.method === 'smartTag') { + smartTag(global, data); + } +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function smartTag(global, data) { + /* eslint-disable */ + global.rp_account = data.account; + global.rp_site = data.site; + global.rp_zonesize = data.zone + '-' + data.size; + global.rp_adtype = 'js'; + global.rp_page = context.sourceUrl; + global.rp_kw = data.kw; + global.rp_visitor = data.visitor; + global.rp_inventory = data.inventory; + global.rp_amp = 'st'; + global.rp_callback = data.callback; + /* eslint-enable */ + writeScript( + global, + 'https://ads.rubiconproject.com/ad/' + + encodeURIComponent(data.account) + + '.js' + ); +} diff --git a/ads/rubicon.md b/ads/vendors/rubicon.md similarity index 92% rename from ads/rubicon.md rename to ads/vendors/rubicon.md index 8dcb0be6d0bfa..be67d9ff31508 100644 --- a/ads/rubicon.md +++ b/ads/vendors/rubicon.md @@ -71,13 +71,13 @@ By default the ad size is based on the `width` and `height` attributes of the `a #### Smart Tag -- `data-method` -- `data-account` -- `data-site` -- `data-zone` -- `data-size` +- `data-method` +- `data-account` +- `data-site` +- `data-zone` +- `data-size` ##### First Party Data & Keywords -- `data-kw` -- `json` - for visitor and inventory data +- `data-kw` +- `json` - for visitor and inventory data diff --git a/ads/vendors/runative.js b/ads/vendors/runative.js new file mode 100644 index 0000000000000..ead1eb4b214a0 --- /dev/null +++ b/ads/vendors/runative.js @@ -0,0 +1,104 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +const requiredParams = ['spot']; +const optionsParams = [ + 'keywords', + 'adType', + 'param1', + 'param2', + 'param3', + 'subid', + 'cols', + 'rows', + 'title', + 'titlePosition', + 'adsByPosition', +]; +const adContainerId = 'runative_id'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function runative(global, data) { + // ensure we have valid widgetIds value + validateData(data, requiredParams, optionsParams); + + const adContainer = global.document.getElementById('c'); + const adNativeContainer = getAdContainer(global); + const initScript = getInitAdScript(global, data); + + adContainer.appendChild(adNativeContainer); + + // load the RUNative AMP JS file + loadScript(global, '//cdn.run-syndicate.com/sdk/v1/n.js', () => { + global.document.body.appendChild(initScript); + }); +} + +/** + * @param {!Object} data + * @return {JsonObject} + */ +function getInitData(data) { + const initKeys = requiredParams.concat(optionsParams); + const initParams = {}; + + initKeys.forEach((key) => { + if (key in data) { + const initKey = key === 'adType' ? 'type' : key; + + initParams[initKey] = data[key]; + } + }); + + initParams['element_id'] = adContainerId; + + return parseJson(initParams); +} + +/** + * @param {!Window} global + * @return {?Node} + */ +function getAdContainer(global) { + const container = global.document.createElement('div'); + + container['id'] = adContainerId; + + return container; +} + +/** + * @param {!Window} global + * @param {!Object} data + * @return {?Node} + */ +function getInitAdScript(global, data) { + const scriptElement = global.document.createElement('script'); + const initData = getInitData(data); + const initScript = global.document.createTextNode( + `NativeAd(${JSON.stringify(initData)});` + ); + + scriptElement.appendChild(initScript); + + return scriptElement; +} diff --git a/ads/vendors/runative.md b/ads/vendors/runative.md new file mode 100644 index 0000000000000..7464ff81909b3 --- /dev/null +++ b/ads/vendors/runative.md @@ -0,0 +1,55 @@ + + +# RUNative + +Serves ads from the [RUNative](https://www.runative.com/). + +## Example + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +### Required parameters + +- `data-spot` - code spot + +### Optional parameters + +- `data-ad-type` - types of ads: `img-left`, `img-right`, `label-over`, `label-under` +- `data-keywords` - title of ad +- `data-title` - title of ad +- `data-cols` - number of cols 1 till 6 +- `data-rows` - number of rows 1 till 6 +- `data-title-position` - position of ad title (`left` or `right`) +- `data-ads-by-position` - position of runative logo (`left` or `right`) diff --git a/ads/vendors/sas.js b/ads/vendors/sas.js new file mode 100644 index 0000000000000..98fa2a8131ea6 --- /dev/null +++ b/ads/vendors/sas.js @@ -0,0 +1,100 @@ +/** + * Copyright 2019 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. + */ + +import {getMultiSizeDimensions} from '../../ads/google/utils'; +import {parseJson} from '../../src/core/types/object/json'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sas(global, data) { + let url, adHost, whSize; + const plainFields = ['site', 'area', 'mid']; + validateData( + data, + ['customerName'], + ['adHost', 'site', 'size', 'area', 'mid', 'tags', 'multiSize'] + ); + + if (typeof data.adHost === 'undefined') { + adHost = encodeURIComponent(data['customerName']) + '-ads.aimatch.com'; + } else { + adHost = encodeURIComponent(data['adHost']); + } + + url = '//' + adHost + '/' + data['customerName'] + '/jserver'; + + const {multiSize} = data; + const primaryWidth = parseInt(data.width, 10); + const primaryHeight = parseInt(data.height, 10); + let dimensions; + let multiSizeValid = false; + + if (multiSize) { + try { + dimensions = getMultiSizeDimensions( + multiSize, + primaryWidth, + primaryHeight, + true + ); + multiSizeValid = true; + dimensions.unshift([primaryWidth, primaryHeight]); + } catch (e) { + // okay to error here + } + } + + for (let idx = 0; idx < plainFields.length; idx++) { + if (data[plainFields[idx]]) { + if (typeof data[plainFields[idx]] !== 'undefined') { + url += + '/' + + plainFields[idx] + + '=' + + encodeURIComponent(data[plainFields[idx]]); + } + } + } + + //Size and multi-size + if (typeof data.size !== 'undefined') { + url += '/SIZE=' + encodeURIComponent(data.size); + if (typeof multiSize !== 'undefined' && multiSizeValid) { + url += ',' + encodeURIComponent(multiSize); + } + } else if (typeof multiSize !== 'undefined' && multiSizeValid) { + whSize = primaryWidth + 'x' + primaryHeight; + url += + '/SIZE=' + + encodeURIComponent(whSize) + + ',' + + encodeURIComponent(multiSize); + } + + // Tags + if (typeof data.tags !== 'undefined') { + const tags = parseJson(data.tags); + for (const tag in tags) { + url += '/' + tag + '=' + encodeURIComponent(tags[tag]); + } + } + writeScript(global, url, () => { + global.context.renderStart(); + }); +} diff --git a/ads/sas.md b/ads/vendors/sas.md similarity index 82% rename from ads/sas.md rename to ads/vendors/sas.md index 19106f6666073..b549fdc94b11c 100644 --- a/ads/sas.md +++ b/ads/vendors/sas.md @@ -20,15 +20,16 @@ limitations under the License. ```html ``` @@ -39,11 +40,12 @@ For configuration details, please email support@sas.com. ### Required parameters -- `data-customer-name` +- `data-customer-name` ### Optional parameters -- `data-size` -- `data-site` -- `data-area` -- `data-tags` +- `data-size` +- `data-site` +- `data-area` +- `data-tags` +- `data-multi-size` diff --git a/ads/vendors/seedingalliance.js b/ads/vendors/seedingalliance.js new file mode 100644 index 0000000000000..b643139aaa6d3 --- /dev/null +++ b/ads/vendors/seedingalliance.js @@ -0,0 +1,29 @@ +/** + * Copyright 2015 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. + */ + +import {writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function seedingalliance(global, data) { + writeScript( + global, + 'https://d.nativendo.de/cds/delivery/init?url=' + + encodeURIComponent(data.url) + ); +} diff --git a/ads/seedingalliance.md b/ads/vendors/seedingalliance.md similarity index 95% rename from ads/seedingalliance.md rename to ads/vendors/seedingalliance.md index ae982365df75b..9516a3d8cdf30 100644 --- a/ads/seedingalliance.md +++ b/ads/vendors/seedingalliance.md @@ -41,4 +41,4 @@ Before starting any Seeding Alliance AMP setup, please reach out to your account Supported parameters: ​ -- `url`: Domain or URL from your Project +- `url`: Domain or URL from your Project diff --git a/ads/vendors/sekindo.js b/ads/vendors/sekindo.js new file mode 100644 index 0000000000000..721c34d090bb3 --- /dev/null +++ b/ads/vendors/sekindo.js @@ -0,0 +1,55 @@ +/** + * Copyright 2018 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sekindo(global, data) { + validateData(data, ['spaceid']); + const pubUrl = encodeURIComponent(global.context.sourceUrl); + const excludesSet = {ampSlotIndex: 1, type: 1}; + const customParamMap = {spaceid: 's', width: 'x', height: 'y'}; + let query = + 'isAmpProject=1&pubUrl=' + + pubUrl + + '&cbuster=' + + global.context.startTime + + '&'; + let getParam = ''; + for (const key in data) { + if (hasOwn(data, key)) { + if (typeof excludesSet[key] == 'undefined') { + getParam = + typeof customParamMap[key] == 'undefined' ? key : customParamMap[key]; + query += getParam + '=' + encodeURIComponent(data[key]) + '&'; + } + } + } + loadScript( + global, + 'https://live.sekindo.com/live/liveView.php?' + query, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/sekindo.md b/ads/vendors/sekindo.md similarity index 95% rename from ads/sekindo.md rename to ads/vendors/sekindo.md index 73f9e3e03f3f5..2cec7b3c15599 100644 --- a/ads/sekindo.md +++ b/ads/vendors/sekindo.md @@ -28,4 +28,4 @@ For details on the configuration semantics, please visit [www.sekindo.com](http: ### Required parameters -- `data-spaceId` - Ad unit unique id +- `data-spaceId` - Ad unit unique id diff --git a/ads/vendors/sharethrough.js b/ads/vendors/sharethrough.js new file mode 100644 index 0000000000000..6a7f11faf5a1b --- /dev/null +++ b/ads/vendors/sharethrough.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sharethrough(global, data) { + validateData(data, ['pkey'], []); + global.pkey = data.pkey; + writeScript(global, 'https://sdk.sharethrough.com/amp.js'); +} diff --git a/ads/sharethrough.md b/ads/vendors/sharethrough.md similarity index 92% rename from ads/sharethrough.md rename to ads/vendors/sharethrough.md index 601ed7a41db6a..2d5e7dc3980ff 100644 --- a/ads/sharethrough.md +++ b/ads/vendors/sharethrough.md @@ -37,4 +37,4 @@ For semantics of configuration, please [contact Sharethrough](mailto:pubsupport@ ### Required parameters -- `data-pkey`: (String, non-empty) The unique identifier for your placement +- `data-pkey`: (String, non-empty) The unique identifier for your placement diff --git a/ads/vendors/shemedia.js b/ads/vendors/shemedia.js new file mode 100644 index 0000000000000..46704e373572d --- /dev/null +++ b/ads/vendors/shemedia.js @@ -0,0 +1,27 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function shemedia(global, data) { + validateData(data, ['slotType', 'boomerangPath']); + + loadScript(global, 'https://ads.shemedia.com/static/amp.js'); +} diff --git a/ads/shemedia.md b/ads/vendors/shemedia.md similarity index 81% rename from ads/shemedia.md rename to ads/vendors/shemedia.md index aaae53e054beb..bc58eef22683a 100644 --- a/ads/shemedia.md +++ b/ads/vendors/shemedia.md @@ -36,12 +36,12 @@ Your site must be an active member of the [SHE Media Partner Network](http://www ### Required parameters -- `data-slot-type` - SHE Media slot type. -- `data-boomerang-path` - Boomerang path. +- `data-slot-type` - SHE Media slot type. +- `data-boomerang-path` - Boomerang path. ### Optional parameters -- `json` - Boomerang configuration key values can be passed using the `boomerangConfig` property. Custom targeting key values can be passed to Boomerang using the `targeting` property. +- `json` - Boomerang configuration key values can be passed using the `boomerangConfig` property. Custom targeting key values can be passed to Boomerang using the `targeting` property. ### Support diff --git a/ads/vendors/sklik.js b/ads/vendors/sklik.js new file mode 100644 index 0000000000000..d089b84ba6cf6 --- /dev/null +++ b/ads/vendors/sklik.js @@ -0,0 +1,38 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript} from '../../3p/3p'; + +/* global sklikProvider: false */ + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sklik(global, data) { + loadScript(global, 'https://c.imedia.cz/js/amp.js', () => { + const parentId = 'sklik_parent'; + + const parentElement = document.createElement('div'); + parentElement.id = parentId; + window.document.body.appendChild(parentElement); + + data.elm = parentId; + data.url = global.context.canonicalUrl; + + sklikProvider.show(data); + }); +} diff --git a/ads/sklik.md b/ads/vendors/sklik.md similarity index 96% rename from ads/sklik.md rename to ads/vendors/sklik.md index adc3fb4b2b7a3..927d92def7d4e 100644 --- a/ads/sklik.md +++ b/ads/vendors/sklik.md @@ -34,6 +34,6 @@ For semantics of configuration, please see [Sklik.czdocumentation](https://napov Supported parameters: -- `width` -- `height` -- `json` +- `width` +- `height` +- `json` diff --git a/ads/vendors/slimcutmedia.js b/ads/vendors/slimcutmedia.js new file mode 100644 index 0000000000000..7a6898bebfc98 --- /dev/null +++ b/ads/vendors/slimcutmedia.js @@ -0,0 +1,43 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function slimcutmedia(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._scm_amp = { + allowed_data: ['pid', 'ffc'], + mandatory_data: ['pid'], + data, + }; + + validateData( + data, + global._scm_amp.mandatory_data, + global._scm_amp.allowed_data + ); + + loadScript( + global, + 'https://static.freeskreen.com/publisher/' + + encodeURIComponent(data.pid) + + '/freeskreen.min.js' + ); +} diff --git a/ads/slimcutmedia.md b/ads/vendors/slimcutmedia.md similarity index 96% rename from ads/slimcutmedia.md rename to ads/vendors/slimcutmedia.md index 5a4cb3d20d4f7..3258d07aeb36f 100644 --- a/ads/slimcutmedia.md +++ b/ads/vendors/slimcutmedia.md @@ -35,8 +35,8 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-pid` +- `data-pid` ### Optional parameters -- `data-ffc` +- `data-ffc` diff --git a/ads/vendors/smartadserver.js b/ads/vendors/smartadserver.js new file mode 100644 index 0000000000000..7327207735bca --- /dev/null +++ b/ads/vendors/smartadserver.js @@ -0,0 +1,29 @@ +/** + * Copyright 2015 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. + */ + +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/smartadserver.md b/ads/vendors/smartadserver.md similarity index 100% rename from ads/smartadserver.md rename to ads/vendors/smartadserver.md diff --git a/ads/vendors/smartclip.js b/ads/vendors/smartclip.js new file mode 100644 index 0000000000000..0a4dc58b5c8a4 --- /dev/null +++ b/ads/vendors/smartclip.js @@ -0,0 +1,49 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function smartclip(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._smartclip_amp = { + allowed_data: ['extra'], + mandatory_data: ['plc', 'sz'], + data, + }; + + validateData( + data, + global._smartclip_amp.mandatory_data, + global._smartclip_amp.allowed_data + ); + + const rand = Math.round(Math.random() * 100000000); + + loadScript( + global, + 'https://des.smartclip.net/ads?type=dyn&plc=' + + encodeURIComponent(data.plc) + + '&sz=' + + encodeURIComponent(data.sz) + + (data.extra ? '&' + encodeURI(data.extra) : '') + + '&rnd=' + + rand + ); +} diff --git a/ads/smartclip.md b/ads/vendors/smartclip.md similarity index 90% rename from ads/smartclip.md rename to ads/vendors/smartclip.md index af94aeb0fee7b..5d044049534e9 100644 --- a/ads/smartclip.md +++ b/ads/vendors/smartclip.md @@ -37,6 +37,6 @@ Supported parameters: All parameters are mandatory, only `data-extra` is optional. -- `data-plc` (String, non-empty) -- `data-sz` (String, non-empty) -- `data-extra` (String) +- `data-plc` (String, non-empty) +- `data-sz` (String, non-empty) +- `data-extra` (String) diff --git a/ads/vendors/smi2.js b/ads/vendors/smi2.js new file mode 100644 index 0000000000000..8326dc74c1e8c --- /dev/null +++ b/ads/vendors/smi2.js @@ -0,0 +1,50 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function smi2(global, data) { + validateData(data, ['blockid']); + global._smi2 = global._smi2 || { + viewId: global.context.pageViewId, + blockId: data['blockid'], + htmlURL: data['canonical'] || global.context.canonicalUrl, + ampURL: data['ampurl'] || global.context.sourceUrl, + testMode: data['testmode'] || 'false', + referrer: data['referrer'] || global.context.referrer, + hostname: global.window.context.location.hostname, + clientId: window.context.clientId, + domFingerprint: window.context.domFingerprint, + location: window.context.location, + startTime: window.context.startTime, + }; + global._smi2.AMPCallbacks = { + renderStart: global.context.renderStart, + noContentAvailable: global.context.noContentAvailable, + }; + // load the smi2 AMP JS file script asynchronously + const rand = Math.round(Math.random() * 100000000); + loadScript( + global, + 'https://amp.smi2.ru/ampclient/ampfecth.js?rand=' + rand, + () => {}, + global.context.noContentAvailable + ); +} diff --git a/ads/smi2.md b/ads/vendors/smi2.md similarity index 94% rename from ads/smi2.md rename to ads/vendors/smi2.md index 0d598563221f7..c7151529da808 100644 --- a/ads/smi2.md +++ b/ads/vendors/smi2.md @@ -30,5 +30,5 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `data-blockid` - insert your block_id -- `height` +- `data-blockid` - insert your block_id +- `height` diff --git a/ads/vendors/smilewanted.js b/ads/vendors/smilewanted.js new file mode 100644 index 0000000000000..f33ff633ef65a --- /dev/null +++ b/ads/vendors/smilewanted.js @@ -0,0 +1,27 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function smilewanted(global, data) { + // For more flexibility, we construct the call to SmileWanted's URL in the external loader, based on the data received from the AMP tag. + global.smilewantedConfig = data; + loadScript(global, 'https://prebid.smilewanted.com/amp/amp.js'); +} diff --git a/ads/smilewanted.md b/ads/vendors/smilewanted.md similarity index 100% rename from ads/smilewanted.md rename to ads/vendors/smilewanted.md diff --git a/ads/vendors/sogouad.js b/ads/vendors/sogouad.js new file mode 100644 index 0000000000000..1ab3f5c9b3da0 --- /dev/null +++ b/ads/vendors/sogouad.js @@ -0,0 +1,44 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sogouad(global, data) { + validateData(data, ['slot', 'w', 'h'], ['responsive']); + const slot = global.document.getElementById('c'); + const ad = global.document.createElement('div'); + const sogouUn = 'sogou_un'; + global[sogouUn] = window[sogouUn] || []; + if (data.w === '100%') { + global[sogouUn].push({ + id: data.slot, + ele: ad, + }); + } else { + global[sogouUn].push({ + id: data.slot, + ele: ad, + w: data.w, + h: data.h, + }); + } + slot.appendChild(ad); + loadScript(global, 'https://theta.sogoucdn.com/wap/js/aw.js'); +} diff --git a/ads/sogouad.md b/ads/vendors/sogouad.md similarity index 82% rename from ads/sogouad.md rename to ads/vendors/sogouad.md index 6d89e41630214..50db68fa2c3b4 100644 --- a/ads/sogouad.md +++ b/ads/vendors/sogouad.md @@ -49,12 +49,12 @@ For details on the configuration semantics, please contact the ad network or ref Responsive mode: -- `data-slot`: slot id of Sogou ads -- `data-w`: always be 20 -- `data-h`: slot's height info from Sogou ads +- `data-slot`: slot id of Sogou ads +- `data-w`: always be 20 +- `data-h`: slot's height info from Sogou ads Fixed-height mode: -- `data-slot`: slot id of Sogou ads -- `data-w`: always be 100% -- `data-h` slot's height info from Sogou ads +- `data-slot`: slot id of Sogou ads +- `data-w`: always be 100% +- `data-h` slot's height info from Sogou ads diff --git a/ads/vendors/sona.js b/ads/vendors/sona.js new file mode 100644 index 0000000000000..b692f12679cba --- /dev/null +++ b/ads/vendors/sona.js @@ -0,0 +1,51 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sona(global, data) { + validateData(data, ['config'], ['responsive']); + + // Additional validation + const dataConfig = data['config']; + const adConfig = parseJson(dataConfig); + + // Add configuration + const configScript = global.document.createElement('SCRIPT'); + const config = global.document.createTextNode( + '(sona = window.sona || ' + JSON.stringify(adConfig) + ')' + ); + configScript.appendChild(config); + + // Set up amp-ad + const slot = global.document.getElementById('c'); + const ad = global.document.createElement('SONA-WIDGET'); + ad.setAttribute('auto-responsive', ''); + ad.className = 'ad-tag'; + + // setup ad from sona + slot.appendChild(ad); + slot.appendChild(configScript); + + // Initialise sona widget and get Image/Video + const scriptUrl = 'https://cdn.sonaserve.com/v1.1/dist.js'; + loadScript(global, scriptUrl); +} diff --git a/ads/vendors/sona.md b/ads/vendors/sona.md new file mode 100644 index 0000000000000..982eed75a3b68 --- /dev/null +++ b/ads/vendors/sona.md @@ -0,0 +1,39 @@ + + +# Sona + +## Examples + +```html + + +``` + +## Configuration + +Access your sona poral. After creating or updating the client, copy the snippet given an paste into your amp page. + +### Required parameters + +- `data-json`: sona configuration in JSON format +- `width` + `height`: Required for all `` units. Specifies the ad size. +- `type`: set to "sona" diff --git a/ads/vendors/sortable.js b/ads/vendors/sortable.js new file mode 100644 index 0000000000000..8b901e0693992 --- /dev/null +++ b/ads/vendors/sortable.js @@ -0,0 +1,38 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sortable(global, data) { + validateData(data, ['site', 'name'], ['responsive']); + + const slot = global.document.getElementById('c'); + const ad = global.document.createElement('div'); + const size = + data.responsive === 'true' ? 'auto' : data.width + 'x' + data.height; + ad.className = 'ad-tag'; + ad.setAttribute('data-ad-name', data.name); + ad.setAttribute('data-ad-size', size); + slot.appendChild(ad); + loadScript( + global, + 'https://tags-cdn.deployads.com/a/' + encodeURIComponent(data.site) + '.js' + ); +} diff --git a/ads/sortable.md b/ads/vendors/sortable.md similarity index 79% rename from ads/sortable.md rename to ads/vendors/sortable.md index 13465f90b7088..d6eb418e4ccc5 100644 --- a/ads/sortable.md +++ b/ads/vendors/sortable.md @@ -55,11 +55,11 @@ No explicit configuration is needed for a given sortable amp-ad, though each sit ### Required parameters -- `data-name`: The name of the ad unit. -- `data-site`: The site/domain this ad will be served on (effectively an account id) -- `width` + `height`: Required for all `` units. Specifies the ad size. -- `type`: Always set to "sortable" +- `data-name`: The name of the ad unit. +- `data-site`: The site/domain this ad will be served on (effectively an account id) +- `width` + `height`: Required for all `` units. Specifies the ad size. +- `type`: Always set to "sortable" ### Optional parameters -- `data-reponsive`: When set to true indicates that the ad slot has multiple potential sizes. +- `data-reponsive`: When set to true indicates that the ad slot has multiple potential sizes. diff --git a/ads/vendors/sovrn.js b/ads/vendors/sovrn.js new file mode 100644 index 0000000000000..a77c63ad4964b --- /dev/null +++ b/ads/vendors/sovrn.js @@ -0,0 +1,39 @@ +/** + * Copyright 2016 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. + */ +/* + ********* + * Existing sovrn customers feel free to contact amp-implementations@sovrn.com + * for assistance with setting up your amp-ad tagid New customers please see + * www.sovrn.com to sign up and get started! + ********* + */ +import {writeScript} from '../../3p/3p'; +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sovrn(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global.width = data.width; + global.height = data.height; + global.domain = data.domain; + global.u = data.u; + global.iid = data.iid; + global.aid = data.aid; + global.z = data.z; + global.tf = data.tf; + writeScript(global, 'https://ap.lijit.com/www/sovrn_amp/sovrn_ads.js'); +} diff --git a/ads/sovrn.md b/ads/vendors/sovrn.md similarity index 85% rename from ads/sovrn.md rename to ads/vendors/sovrn.md index a732f1d745e62..5f0c5f49f84d7 100644 --- a/ads/sovrn.md +++ b/ads/vendors/sovrn.md @@ -48,11 +48,11 @@ For new customers, please see www.sovrn.com to sign up and get started! Supported parameters: -- `data-width` -- `data-height` -- `data-domain` -- `data-u` -- `data-iid` -- `data-aid` -- `data-tf: A Boolean value used only for testing. Either remove it or set it to false for real world production work. -- `data-z` +- `data-width` +- `data-height` +- `data-domain` +- `data-u` +- `data-iid` +- `data-aid` +- `data-tf: A Boolean value used only for testing. Either remove it or set it to false for real world production work. +- `data-z` diff --git a/ads/vendors/speakol.js b/ads/vendors/speakol.js new file mode 100644 index 0000000000000..21b0dac810b83 --- /dev/null +++ b/ads/vendors/speakol.js @@ -0,0 +1,40 @@ +/* eslint-disable require-jsdoc */ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ + +export function speakol(global, data) { + validateData(data, ['widgetid']); + + (global.spksdk = global.spksdk || []).push({ + // eslint-disable-next-line google-camelcase/google-camelcase + widget_id: `wi-${data['widgetid']}`, + element: `wi-${data['widgetid']}`, + }); + const d = global.document.createElement('div'); + d.classList.add('speakol-widget'); + d.id = 'wi-' + data['widgetid']; + + global.document.getElementById('c').appendChild(d); + + loadScript(global, 'https://cdn.speakol.com/widget/js/speakol-widget-v2.js'); +} diff --git a/ads/speakol.md b/ads/vendors/speakol.md similarity index 95% rename from ads/speakol.md rename to ads/vendors/speakol.md index 712d5c7e9eaa6..db9f891ae43ba 100644 --- a/ads/speakol.md +++ b/ads/vendors/speakol.md @@ -35,4 +35,4 @@ For configuration details and to generate your tags, please refer to [your publi parameters: -- `data-widgetid`: widget id. Required. +- `data-widgetid`: widget id. Required. diff --git a/ads/vendors/spotx.js b/ads/vendors/spotx.js new file mode 100644 index 0000000000000..9c1ce2a105875 --- /dev/null +++ b/ads/vendors/spotx.js @@ -0,0 +1,56 @@ +/** + * Copyright 2015 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function spotx(global, data) { + // ensure we have valid channel id + validateData(data, ['spotx_channel_id', 'width', 'height']); + + // Because 3p's loadScript does not allow for data attributes, + // we will write the JS tag ourselves. + const script = global.document.createElement('script'); + + data['spotx_content_width'] = data.spotx_content_width || data.width; + data['spotx_content_height'] = data.spotx_content_height || data.height; + data['spotx_content_page_url'] = + global.context.location.href || global.context.sourceUrl; + + // Add data-* attribute for each data value passed in. + for (const key in data) { + if (hasOwn(data, key) && key.startsWith('spotx_')) { + script.setAttribute(`data-${key}`, data[key]); + } + } + + global['spotx_ad_done_function'] = function (spotxAdFound) { + if (!spotxAdFound) { + global.context.noContentAvailable(); + } + }; + + // TODO(KenneyE): Implement AdLoaded callback in script to accurately trigger + // renderStart() + script.onload = global.context.renderStart; + + script.src = `//js.spotx.tv/easi/v1/${data['spotx_channel_id']}.js`; + global.document.body.appendChild(script); +} diff --git a/ads/spotx.md b/ads/vendors/spotx.md similarity index 96% rename from ads/spotx.md rename to ads/vendors/spotx.md index 9f58adb0b3607..3cef336af9766 100644 --- a/ads/spotx.md +++ b/ads/vendors/spotx.md @@ -50,6 +50,6 @@ The SpotX `amp-ad` integration has many of the same capabilities and options as ### Required parameters -- `data-spotx_channel_id` -- `width` -- `height` +- `data-spotx_channel_id` +- `width` +- `height` diff --git a/ads/vendors/springAds.js b/ads/vendors/springAds.js new file mode 100644 index 0000000000000..500e8382595d3 --- /dev/null +++ b/ads/vendors/springAds.js @@ -0,0 +1,62 @@ +/** + * Copyright 2015 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. + */ + +import {computeInMasterFrame, loadScript} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +/** + * @param context + */ + +const initSlotList = (context) => { + context.master.availableSlots = context.master.availableSlots || {}; +}; + +const registerSlot = (slot) => { + context.master.availableSlots[slot.slotName] = slot; +}; + +// eslint-disable-next-line require-jsdoc +export function springAds(global, data) { + computeInMasterFrame( + global, + 'springAds', + () => { + initSlotList(context); + }, + () => {} + ); + if (data.adssetup) { + const adSSetup = parseJson(data.adssetup); + adSSetup['isAMP'] = !0; + adSSetup['availableSlots'] = context.master.availableSlots; + context.master.adSSetup = global.adSSetup = adSSetup; + const sitename = adSSetup['publisher'].match(/(.*)\..*/)[1]; + loadScript( + global, + 'https://www.asadcdn.com/adlib/pages/' + sitename + '_amp.js' + ); + } else { + registerSlot({ + global, + document, + context, + slotName: data['adslot'], + }); + const adlib = window.ASCDP || context.master.ASCDP || ''; + adlib && adlib.adS.renderAd(data.adslot); + } +} diff --git a/ads/vendors/springAds.md b/ads/vendors/springAds.md new file mode 100644 index 0000000000000..546cd1b832f66 --- /dev/null +++ b/ads/vendors/springAds.md @@ -0,0 +1,64 @@ + + +# Spring AdTechnology AmpAd Integration + +## Example + +```html + + + + +``` + +For a maintenanced uptodate documentation please refer to our +[official springAds document](https://github.com/spring-media/adsolutions-implementationReference/blob/master/publisher-amp-reference.md) + +## Configuration + +for further information regarding this implementation, please contact adtechnology@axelspringer.de + +## Optional features + +- Loading placeholder for ads, see [Placeholders in amp-ad](https://amp.dev/documentation/components/amp-ad#placeholder). +- No ad fallback for ads, see [No ad in amp-ad](https://amp.dev/documentation/components/amp-ad#no-ad-available). diff --git a/ads/vendors/ssp.js b/ads/vendors/ssp.js new file mode 100644 index 0000000000000..0a249b69984af --- /dev/null +++ b/ads/vendors/ssp.js @@ -0,0 +1,237 @@ +/** + * Copyright 2019 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. + */ + +import {computeInMasterFrame, loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; +import {setStyle, setStyles} from '../../src/style'; + +/* + * How to develop: + * https://github.com/ampproject/amphtml/blob/main/docs/getting-started-e2e.md + */ + +/** + * @param {!Array.} array + * @param {!Function} iteratee + * + * @return {Object} + */ +export function keyBy(array, iteratee) { + return array.reduce( + (itemById, item) => Object.assign(itemById, {[iteratee(item)]: item}), + {} + ); +} + +/** + * @param {!Object} fetchingSSPs + * @param {!Function} cb + */ +export function runWhenFetchingSettled(fetchingSSPs, cb) { + const sspCleanupInterval = setInterval(() => { + if (!Object.keys(fetchingSSPs).length) { + clearInterval(sspCleanupInterval); + cb(); + } + }, 100); +} + +/** + * @param {!Element} element + * @param {boolean} center + * @param {Object} dimensions + */ +export function handlePosition(element, center, dimensions) { + const styles = { + ...(center + ? { + position: 'absolute', + top: '50%', + left: '50%', + 'max-width': '100%', + transform: 'translate(-50%, -50%)', + '-ms-transform': 'translate(-50%, -50%)', + } + : {}), + ...(dimensions || {}), + }; + setStyles(element, styles); +} + +/** + * @param {!MessageEvent} e + * @param {!Element} element + */ +export function handlePositionResponsive(e, element) { + try { + const {height} = JSON.parse(e.data); + if (height) { + handlePosition(element, true, { + height: `${height}px`, + }); + } + } catch (e) { + // no-op + } +} + +/** + * @param {number} availableWidth + * @param {!Object} data + * @return {?Object} + */ +export function sizeAgainstWindow(availableWidth, data) { + if (data.width > availableWidth) { + const newWidth = availableWidth; + const newHeight = data.height / (data.width / availableWidth); + return {width: newWidth, height: newHeight}; + } +} + +/** + * @param {!Element} element + */ +export function forceElementReflow(element) { + // force reflow + setStyle(element, 'display', 'none'); + element./*OK*/ offsetHeight; + setStyle(element, 'display', 'block'); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ssp(global, data) { + // validate AMP input data- attributes + validateData(data, ['position'], ['site']); + + let position = {id: -1}; + + try { + position = parseJson(data.position); + + if (position['id'] === undefined) { + position = {id: -1}; + } + } catch (error) { + global.context.noContentAvailable(); + return; + } + + if (position['id'] === -1) { + global.context.noContentAvailable(); + return; + } + + // This is super important. Without this any variables on context are not shared + const mW = global.context.isMaster ? global : global.context.master; + + // create parent element + const parentElement = document.createElement('div'); + + parentElement.id = position['id']; + + // https://github.com/ampproject/amphtml/tree/main/ads#the-iframe-sandbox + global.document.getElementById('c').appendChild(parentElement); + + // validate dimensions against available space (window) + const sizing = sizeAgainstWindow(parentElement./*OK*/ clientWidth, data); + + // https://github.com/ampproject/amphtml/blob/main/3p/3p.js#L186 + computeInMasterFrame( + global, + 'ssp-load', + (done) => { + loadScript(global, 'https://ssp.imedia.cz/static/js/ssp.js', () => { + // This callback is run just once for amp-ad with same type + // Script will inject "sssp" object on Window + if (!global['sssp']) { + done(false); + return; + } + + /** @type {{config: Function, getAds: Function, writeAd: Function}} */ + const sssp = global['sssp']; + + sssp.config({ + site: data.site || global.context.canonicalUrl, + }); + + // propagate relevant data across all ad units + mW.sssp = sssp; + mW.fetchingSSPs = {}; + + done(true); + }); + }, + (loaded) => { + if (!loaded) { + global.context.noContentAvailable(); + return; + } + + // perform cleanup only after all SSP XHRs are settled + const noContent = () => { + runWhenFetchingSettled(mW.fetchingSSPs, () => + global.context.noContentAvailable() + ); + }; + + // register XHR and start fetching + mW.fetchingSSPs[position.zoneId] = true; + + mW.sssp.getAds([position], { + // 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']]; + + if (!ad || ['error', 'empty'].includes(ad.type)) { + noContent(); + } else { + // listen to message with "height" property -> for responsive ads (111x111) -> set new height / center + if (ad.responsive) { + global.addEventListener('message', (e) => { + handlePositionResponsive(e, parentElement); + }); + } + + // listen to intersections and force element reflow (external DSPs) + if (['APPNEXUS', 'PUBMATIC'].includes(ad.dsp)) { + global.context.observeIntersection(() => { + forceElementReflow(parentElement); + }); + } + + // SSP need parentElement as value in "position.id" + mW.sssp.writeAd(ad, {...position, id: parentElement}); + + // init dimensions / centering + const d = ad.responsive ? {width: '100%', height: '100%'} : null; + handlePosition(parentElement, true, d); + global.context.renderStart(sizing); + } + + // unregister XHR + delete mW.fetchingSSPs[position.zoneId]; + }, + }); + } + ); +} diff --git a/ads/ssp.md b/ads/vendors/ssp.md similarity index 82% rename from ads/ssp.md rename to ads/vendors/ssp.md index 47ebcfd38cb69..9184e04e1374d 100644 --- a/ads/ssp.md +++ b/ads/vendors/ssp.md @@ -61,10 +61,10 @@ Required parameters: ### `data-position` -- Object must have required keys `id`, `width`, `height`, `zoneId` (Watch out for uppercase "I" in "id"). -- Every position MUST have unique `id`, if you duplicate some id, Ad may be used from another position. -- Attributes `width` and `height` are from AMP specification, and they will set fixed border around Ad. -- Attributes `data-width` and `data-height` are used to fetch SSP Ads on the server (They can different). +- Object must have required keys `id`, `width`, `height`, `zoneId` (Watch out for uppercase "I" in "id"). +- Every position MUST have unique `id`, if you duplicate some id, Ad may be used from another position. +- Attributes `width` and `height` are from AMP specification, and they will set fixed border around Ad. +- Attributes `data-width` and `data-height` are used to fetch SSP Ads on the server (They can different). ## Contact diff --git a/ads/vendors/strossle.js b/ads/vendors/strossle.js new file mode 100644 index 0000000000000..520713ea366aa --- /dev/null +++ b/ads/vendors/strossle.js @@ -0,0 +1,48 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +const renderTo = 'strossle-widget'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function strossle(global, data) { + validateData(data, ['widgetid']); + global._strossle = global._strossle || { + widgetId: data['widgetid'], + }; + + createContainer(global, data); + loadScript( + global, + 'https://widgets.sprinklecontent.com/v2/sprinkle.js', + () => {} + ); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function createContainer(global, data) { + const d = global.document.createElement('div'); + d.className = renderTo; + d.setAttribute('data-spklw-widget', data['widgetid']); + global.document.getElementById('c').appendChild(d); +} diff --git a/ads/strossle.md b/ads/vendors/strossle.md similarity index 93% rename from ads/strossle.md rename to ads/vendors/strossle.md index 75b8da9cb2d42..d2a1f030a58bc 100644 --- a/ads/strossle.md +++ b/ads/vendors/strossle.md @@ -31,5 +31,5 @@ For details on the configuration semantics, please contact [Strossle](https://st ### Required parameters -- `data-widgetid` - past your unique widget id here -- `height` - widget height +- `data-widgetid` - past your unique widget id here +- `height` - widget height diff --git a/ads/vendors/sulvo.js b/ads/vendors/sulvo.js new file mode 100644 index 0000000000000..8e3492d8f7f9c --- /dev/null +++ b/ads/vendors/sulvo.js @@ -0,0 +1,25 @@ +/** + * Copyright 2019 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. + */ +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sulvo(global, data) { + global.sulvoAmpAdData = data; + loadScript(global, 'https://live.demand.supply/up.amp.js'); +} diff --git a/ads/sulvo.md b/ads/vendors/sulvo.md similarity index 96% rename from ads/sulvo.md rename to ads/vendors/sulvo.md index adbc048e94714..cabaa549f803a 100644 --- a/ads/sulvo.md +++ b/ads/vendors/sulvo.md @@ -49,4 +49,4 @@ For further information, please contact [Sulvo](https://sulvo.com/). ### Required parameters -- `data-ad`: Ad ID provided by Sulvo. +- `data-ad`: Ad ID provided by Sulvo. diff --git a/ads/vendors/sunmedia.js b/ads/vendors/sunmedia.js new file mode 100644 index 0000000000000..682826f4afa4c --- /dev/null +++ b/ads/vendors/sunmedia.js @@ -0,0 +1,41 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function sunmedia(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._sunmedia_amp = { + allowed_data: ['cskp', 'crst', 'cdb', 'cid'], + mandatory_data: ['cid'], + data, + }; + + validateData( + data, + global._sunmedia_amp.mandatory_data, + global._sunmedia_amp.allowed_data + ); + + loadScript( + global, + 'https://vod.addevweb.com/sunmedia/amp/ads/SMIntextAMP.js' + ); +} diff --git a/ads/sunmedia.md b/ads/vendors/sunmedia.md similarity index 86% rename from ads/sunmedia.md rename to ads/vendors/sunmedia.md index b693debcf1673..41b2953263be8 100644 --- a/ads/sunmedia.md +++ b/ads/vendors/sunmedia.md @@ -37,9 +37,9 @@ For further information, please contact [SunMedia](http://sunmedia.tv/#contact). ### Required parameters -- `data-cid`: Client ID provided by SunMedia +- `data-cid`: Client ID provided by SunMedia ### Optional parameters -- `data-cskp`: Indicates skip button enabled -- `data-crst`: Indicates restart option enabled +- `data-cskp`: Indicates skip button enabled +- `data-crst`: Indicates restart option enabled diff --git a/ads/vendors/svknative.js b/ads/vendors/svknative.js new file mode 100644 index 0000000000000..8e988a2f9c912 --- /dev/null +++ b/ads/vendors/svknative.js @@ -0,0 +1,46 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function svknative(global, data) { + // ensure we have valid widgetid value + validateData(data, ['widgetid']); + + const s = global.document.createElement('script'); + const scriptKey = + 'svknativeampwidget_' + Math.floor(Math.random() * 10000000); + + s.setAttribute('data-key', scriptKey); + global.document.getElementById('c').appendChild(s); + + (function (w, a) { + (w[a] = w[a] || []).push({ + 'script_key': scriptKey, + 'settings': { + 'w': data['widgetid'], + 'amp': true, + }, + }); + })(global, '_svk_n_widgets'); + + // load the SVK Native AMP JS file + loadScript(global, 'https://widget.svk-native.ru/js/embed.js'); +} diff --git a/ads/svknative.md b/ads/vendors/svknative.md similarity index 95% rename from ads/svknative.md rename to ads/vendors/svknative.md index 458502c38ccf5..99c6357dc6d4b 100644 --- a/ads/svknative.md +++ b/ads/vendors/svknative.md @@ -34,4 +34,4 @@ For details on the configuration semantics, please contact your SVK Native perso ### Required parameters -- `data-widgetid` - insert your WidgetId +- `data-widgetid` - insert your WidgetId diff --git a/ads/vendors/swoop.js b/ads/vendors/swoop.js new file mode 100644 index 0000000000000..1c89b293cfbb0 --- /dev/null +++ b/ads/vendors/swoop.js @@ -0,0 +1,48 @@ +/** + * Copyright 2017 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. + */ + +import {computeInMasterFrame, loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function swoop(global, data) { + // Required properties + validateData(data, ['layout', 'placement', 'publisher', 'slot']); + + computeInMasterFrame( + global, + 'swoop-load', + (done) => { + global.swoopIabConfig = data; + + loadScript(global, 'https://www.swoop-amp.com/amp.js', () => + done(global.Swoop != null) + ); + }, + (success) => { + if (success) { + if (!global.context.isMaster) { + global.context.master.Swoop.announcePlace(global, data); + } + } else { + global.context.noContentAvailable(); + throw new Error('Swoop failed to load'); + } + } + ); +} diff --git a/ads/swoop.md b/ads/vendors/swoop.md similarity index 85% rename from ads/swoop.md rename to ads/vendors/swoop.md index c52c1df0a51b3..54fe3e6c113a5 100644 --- a/ads/swoop.md +++ b/ads/vendors/swoop.md @@ -40,7 +40,7 @@ For details on the configuration semantics, please contact the ad network or ref ### Required parameters -- `layout`: AMP layout style, should match the `layout` attribute of the `amp-ad` tag -- `publisher`: Publisher ID -- `placement`: Placement type -- `slot`: Slot ID +- `layout`: AMP layout style, should match the `layout` attribute of the `amp-ad` tag +- `publisher`: Publisher ID +- `placement`: Placement type +- `slot`: Slot ID diff --git a/ads/vendors/taboola.js b/ads/vendors/taboola.js new file mode 100644 index 0000000000000..f5cf545a05be9 --- /dev/null +++ b/ads/vendors/taboola.js @@ -0,0 +1,84 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function taboola(global, data) { + // do not copy the following attributes from the 'data' object + // to _tablloa global object + const denylist = ['height', 'type', 'width', 'placement', 'mode']; + + // ensure we have vlid publisher, placement and mode + // and exactly one page-type + validateData(data, [ + 'publisher', + 'placement', + 'mode', + ['article', 'video', 'photo', 'search', 'category', 'homepage', 'other'], + ]); + + // setup default values for referrer and url + const params = { + referrer: data.referrer || global.context.referrer, + url: data.url || global.context.canonicalUrl, + }; + + // copy none denylisted attribute to the 'params' map + Object.keys(data).forEach((k) => { + if (denylist.indexOf(k) === -1) { + params[k] = data[k]; + } + }); + + // push the two object into the '_taboola' global + (global._taboola = global._taboola || []).push([ + { + viewId: global.context.pageViewId, + publisher: data.publisher, + placement: data.placement, + mode: data.mode, + framework: 'amp', + container: 'c', + }, + params, + {flush: true}, + ]); + + // install observation on entering/leaving the view + global.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + if (c.intersectionRect.height) { + global._taboola.push({ + visible: true, + rects: c, + placement: data.placement, + }); + } + }); + }); + + // load the taboola loader asynchronously + loadScript( + global, + `https://cdn.taboola.com/libtrc/${encodeURIComponent( + data.publisher + )}/loader.js` + ); +} diff --git a/ads/taboola.md b/ads/vendors/taboola.md similarity index 84% rename from ads/taboola.md rename to ads/vendors/taboola.md index a212b40fd0a1d..715a1825d6733 100644 --- a/ads/taboola.md +++ b/ads/vendors/taboola.md @@ -41,14 +41,14 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-publisher` -- `data-placement` -- `data-mode` -- `data-article` -- `data-video` -- `data-photo` -- `data-home` -- `data-category` -- `data-others` -- `data-url` -- `data-referrer` +- `data-publisher` +- `data-placement` +- `data-mode` +- `data-article` +- `data-video` +- `data-photo` +- `data-home` +- `data-category` +- `data-others` +- `data-url` +- `data-referrer` diff --git a/ads/vendors/tcsemotion.js b/ads/vendors/tcsemotion.js new file mode 100644 index 0000000000000..6879718f71314 --- /dev/null +++ b/ads/vendors/tcsemotion.js @@ -0,0 +1,30 @@ +/** + * Copyright 2019 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. + */ +import {validateData, writeScript} from '../../3p/3p'; +/** + * @param {!Window} global + * @param {!Object} d + */ +export function tcsemotion(global, d) { + validateData(d, ['zone', 'delhost']); + global.djaxData = d; + if (d.hb && d.hb == 'true') { + global.djaxData.hb = true; + } else { + global.djaxData.hb = false; + } + writeScript(global, 'https://ads.tcsemotion.com/www/delivery/amphb.js'); +} diff --git a/ads/vendors/tcsemotion.md b/ads/vendors/tcsemotion.md new file mode 100644 index 0000000000000..9d1930de9b7c2 --- /dev/null +++ b/ads/vendors/tcsemotion.md @@ -0,0 +1,29 @@ +# TcsEmotion + +## Example + +```html + + +``` + +## Configuration + +Please refer to [Tcsemotion](https://tcsemotion.com/) for more +information on how to get zone id and other details + +### Required parameters + +- `data-zone`: the id of the zone provided by adserver +- `data-delhost`: target domain url + +### Optional parameters + +- `data-hb`: enable this field to true when you are using header bidding diff --git a/ads/vendors/teads.js b/ads/vendors/teads.js new file mode 100644 index 0000000000000..1d1ede92b9e08 --- /dev/null +++ b/ads/vendors/teads.js @@ -0,0 +1,119 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function teads(global, data) { + /*eslint "google-camelcase/google-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 + ); + + const QueryString = function () { + // This function is anonymous, is executed immediately and + // the return value is assigned to QueryString! + const query_string = {} + const a = document.createElement('a') + a.href = global.context.sourceUrl + const query = a.search.substring(1) + const vars = query.split("&") + for (var i = 0; i < vars.length; i++) { + const pair = vars[i].split("=") + // If first entry with this name + if (typeof query_string[pair[0]] === "undefined") { + query_string[pair[0]] = decodeURIComponent(pair[1]) + // If second entry with this name + } else if (typeof query_string[pair[0]] === "string") { + const arr = [query_string[pair[0]], decodeURIComponent(pair[1])] + query_string[pair[0]] = arr + // If third or later entry with this name + } else { + query_string[pair[0]].push(decodeURIComponent(pair[1])) + } + } + return query_string + }() + + if ( + QueryString.js || + QueryString.second_asset_url || + QueryString.pid || + QueryString.page || + QueryString.content + ) { + + if (QueryString.tracking) { + (global.teads || (global.teads = {})).TRACKING_URL = QueryString.tracking + } + + // FOR-3052: Split teads-format into 2 assets + if (QueryString.second_asset_url) { + (global.teads || (global.teads = {})).FORMAT_WITH_PLAYER_URL = QueryString.second_asset_url + } + + const ttag = () => { + global.teads + .ad(QueryString.pid || 47405, { + type: 'VastUrl', + content: QueryString.content || 'https://a.teads.tv/vast/preview/50221', + settings: { + values: { + pageId: QueryString.page || 42266, + placementId: QueryString.pid || 47405, + adType: QueryString.adtype || 'video' + }, + components: { + progressBar: QueryString.pb || false + } + }, + headerBiddingProvider: 'debugFormat' + }) + .page(QueryString.page || 42266) + .placement(QueryString.pid || 47405, { format: 'inread', slot: { selector: 'body' } }) + .serve() + } + + loadScript(global, `//${QueryString.js || 's8t.teads.tv/media/format/v3/teads-format.min.js'}`, ttag) + } else 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/teads.md b/ads/vendors/teads.md new file mode 100644 index 0000000000000..d335fdf95c9ec --- /dev/null +++ b/ads/vendors/teads.md @@ -0,0 +1,47 @@ + + +# Teads + +## Example + +```html + + +``` + +## Configuration + +For configuration semantics, please contact [Teads](http://teads.tv/fr/contact/). + +Supported parameters: + +- `data-pid` + +## User Consent Integration + +When [user consent](https://github.com/ampproject/amphtml/blob/main/extensions/amp-consent/amp-consent.md#blocking-behaviors) is required, Teads ad approaches user consent in the following ways: + +- `CONSENT_POLICY_STATE.SUFFICIENT`: Serve a personalized ad to the user. +- `CONSENT_POLICY_STATE.INSUFFICIENT`: Serve a non-personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED`: Serve a personalized ad to the user. +- `CONSENT_POLICY_STATE.UNKNOWN`: Serve a non-personalized ad to the user.. diff --git a/ads/vendors/temedya.js b/ads/vendors/temedya.js new file mode 100644 index 0000000000000..4f8b803da3d07 --- /dev/null +++ b/ads/vendors/temedya.js @@ -0,0 +1,39 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function temedya(global, data) { + validateData(data, ['widgetid']); + global._temedya = global._temedya || { + widgetId: data['widgetid'], + }; + global._temedya.AMPCallbacks = { + renderStart: global.context.renderStart, + noContentAvailable: global.context.noContentAvailable, + }; + // load the temedya AMP JS file script asynchronously + loadScript( + global, + 'https://widget.cdn.vidyome.com/builds/loader-amp.js', + () => {}, + global.context.noContentAvailable + ); +} diff --git a/ads/temedya.md b/ads/vendors/temedya.md similarity index 96% rename from ads/temedya.md rename to ads/vendors/temedya.md index c651ea0cd72a5..f41d8c0b1d3ee 100644 --- a/ads/temedya.md +++ b/ads/vendors/temedya.md @@ -35,4 +35,4 @@ limitations under the License. ### Required parameters -- `data-widgetId`: Widget ID +- `data-widgetId`: Widget ID diff --git a/ads/vendors/torimochi.js b/ads/vendors/torimochi.js new file mode 100644 index 0000000000000..c57bf616d9af2 --- /dev/null +++ b/ads/vendors/torimochi.js @@ -0,0 +1,42 @@ +/** + * Copyright 2019 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. + */ + +import {parseJson} from '../../src/core/types/object/json'; +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function torimochi(global, data) { + validateData(data, ['area', 'adtype']); + + if (data.width < global.width) { + global.width = data.width; + } + global.height = data.height; + global.area = data['area']; + global.adtype = data['adtype']; + global.tcid = data['tcid']; + global.wid = data['wid']; + global.extra = parseJson(data['extra'] || '{}'); + global.context.renderStart({width: global.width, height: global.height}); + + const url = + 'https://asset.torimochi-ad.net/js/torimochi_ad_amp.min.js?v=' + Date.now(); + + writeScript(global, url); +} diff --git a/ads/torimochi.md b/ads/vendors/torimochi.md similarity index 92% rename from ads/torimochi.md rename to ads/vendors/torimochi.md index d189026014068..ace3227abd9ee 100644 --- a/ads/torimochi.md +++ b/ads/vendors/torimochi.md @@ -37,11 +37,11 @@ Supported parameters: ### Required parameters -- `data-adtype` -- `data-area` -- `data-wid` +- `data-adtype` +- `data-area` +- `data-wid` ### Optional parameters -- `data-tcid` -- `data-extra` +- `data-tcid` +- `data-extra` diff --git a/ads/vendors/tracdelight.js b/ads/vendors/tracdelight.js new file mode 100644 index 0000000000000..28b053493b415 --- /dev/null +++ b/ads/vendors/tracdelight.js @@ -0,0 +1,31 @@ +/** + * Copyright 2018 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function tracdelight(global, data) { + const mandatoryFields = ['widget_id', 'access_key']; + const optionalFields = ['mode']; + + validateData(data, mandatoryFields, optionalFields); + + global.tdData = data; + writeScript(global, 'https://scripts.tracdelight.io/amp.js'); +} diff --git a/ads/tracdelight.md b/ads/vendors/tracdelight.md similarity index 85% rename from ads/tracdelight.md rename to ads/vendors/tracdelight.md index f23a976b8ef9a..04f9a55f01359 100644 --- a/ads/tracdelight.md +++ b/ads/vendors/tracdelight.md @@ -36,12 +36,12 @@ For semantics of configuration, please see [ad network documentation](http://doc Required parameters: -- data-widget_id: the id of the widget -- data-access_key: the access key +- data-widget_id: the id of the widget +- data-access_key: the access key Optional parameters -- data-mode: how to embed the widget. Possible values are "iframe" or "inline" (default: "iframe") +- data-mode: how to embed the widget. Possible values are "iframe" or "inline" (default: "iframe") ## Support diff --git a/ads/vendors/triplelift.js b/ads/vendors/triplelift.js new file mode 100644 index 0000000000000..71fed4bf318cf --- /dev/null +++ b/ads/vendors/triplelift.js @@ -0,0 +1,27 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateSrcPrefix} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function triplelift(global, data) { + const {src} = data; + validateSrcPrefix('https://ib.3lift.com/', src); + loadScript(global, src); +} diff --git a/ads/triplelift.md b/ads/vendors/triplelift.md similarity index 98% rename from ads/triplelift.md rename to ads/vendors/triplelift.md index c12e76ea1659f..3fca1effce99a 100644 --- a/ads/triplelift.md +++ b/ads/vendors/triplelift.md @@ -37,4 +37,4 @@ For configuration semantics, please [contact TripleLift](http://triplelift.com). Supported parameters: -- `src` +- `src` diff --git a/ads/vendors/trugaze.js b/ads/vendors/trugaze.js new file mode 100644 index 0000000000000..d2b9acf094e25 --- /dev/null +++ b/ads/vendors/trugaze.js @@ -0,0 +1,26 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript} from '../../3p/3p'; + +/** + * @param {!Window} global + */ +export function trugaze(global) { + // For simplicity and flexibility, all validations are performed in the + // Trugaze's URL based on the data received + loadScript(global, 'https://cdn.trugaze.io/amp-init-v1.js'); +} diff --git a/ads/trugaze.md b/ads/vendors/trugaze.md similarity index 100% rename from ads/trugaze.md rename to ads/vendors/trugaze.md diff --git a/ads/vendors/uas.js b/ads/vendors/uas.js new file mode 100644 index 0000000000000..4a32b859b7c0c --- /dev/null +++ b/ads/vendors/uas.js @@ -0,0 +1,104 @@ +/** + * Copyright 2018 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. + */ + +import {hasOwn} from '../../src/core/types/object'; +import {loadScript, validateData} from '../../3p/3p'; +import {setStyles} from '../../src/style'; + +/** + * @param {!Object} theObject + * @param {!Function} callback + */ +function forEachOnObject(theObject, callback) { + if (typeof theObject === 'object' && theObject !== null) { + if (typeof callback === 'function') { + for (const key in theObject) { + if (hasOwn(theObject, key)) { + callback(key, theObject[key]); + } + } + } + } +} + +/** + * @param {!Window} global + */ +function centerAd(global) { + const e = global.document.getElementById('c'); + if (e) { + setStyles(e, { + top: '50%', + left: '50%', + bottom: '', + right: '', + transform: 'translate(-50%, -50%)', + }); + } +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function uas(global, data) { + validateData( + data, + ['accId', 'adUnit', 'sizes'], + [ + 'locLat', + 'locLon', + 'locSrc', + 'pageURL', + 'targetings', + 'extraParams', + 'visibility', + ] + ); + global.Phoenix = {EQ: []}; + const uasDivId = 'uas-amp-slot'; + global.document.write('
'); + loadScript(global, 'https://ads.pubmatic.com/AdServer/js/phoenix.js', () => { + global.Phoenix.EQ.push(function () { + global.Phoenix.enableSingleRequestCallMode(); + global.Phoenix.setInfo('AMP', 1); // Need to set the AMP flag + global.Phoenix.setInfo('ACCID', data.accId); + // Reading PAGEURL from sourceUrl or location.href + global.Phoenix.setInfo( + 'PAGEURL', + global.context.sourceUrl || global.context.location.href + ); + data.pageURL && global.Phoenix.setInfo('PAGEURL', data.pageURL); + data.locLat && global.Phoenix.setInfo('LAT', data.locLat); + data.locLon && global.Phoenix.setInfo('LON', data.locLon); + data.locSrc && global.Phoenix.setInfo('LOC_SRC', data.locSrc); + const slot = global.Phoenix.defineAdSlot( + data.adUnit, + data.sizes, + uasDivId + ); + slot.setVisibility(1); + forEachOnObject(data.targetings, function (key, value) { + slot.setTargeting(key, value); + }); + forEachOnObject(data.extraParams, function (key, value) { + slot.setExtraParameters(key, value); + }); + global.Phoenix.display(uasDivId); + }); + }); + centerAd(global); +} diff --git a/ads/uas.md b/ads/vendors/uas.md similarity index 80% rename from ads/uas.md rename to ads/vendors/uas.md index 637cd7f7f680c..963629f96e729 100644 --- a/ads/uas.md +++ b/ads/vendors/uas.md @@ -73,19 +73,19 @@ For details on the configuration semantics, please contact the ad network or ref ### Supported parameters via `json` attribute -- `accId` Account Id (mandatory) -- `adUnit` AdUnitId (mandatory) -- `sizes` Array of sizes (mandatory) -- `locLat` Geo-location latitude -- `locLon` Geo-location longitude -- `locSrc` Geo-location source -- `pageURL` Set custom page URL -- `targetings` key-value pairs -- `extraParams` key-value pairs to be passed to PubMatic SSP +- `accId` Account Id (mandatory) +- `adUnit` AdUnitId (mandatory) +- `sizes` Array of sizes (mandatory) +- `locLat` Geo-location latitude +- `locLon` Geo-location longitude +- `locSrc` Geo-location source +- `pageURL` Set custom page URL +- `targetings` key-value pairs +- `extraParams` key-value pairs to be passed to PubMatic SSP ### Unsupported Ad Formats -- Interstitials -- Expandables. Work is in progress -- Flash -- Creatives served over HTTP +- Interstitials +- Expandables. Work is in progress +- Flash +- Creatives served over HTTP diff --git a/ads/vendors/ucfunnel.js b/ads/vendors/ucfunnel.js new file mode 100644 index 0000000000000..dc8c523fa967a --- /dev/null +++ b/ads/vendors/ucfunnel.js @@ -0,0 +1,27 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function ucfunnel(global, data) { + validateData(data, ['siteId']); + loadScript(window, 'https://ads.aralego.com/ampsdk'); + window.context.renderStart(); +} diff --git a/ads/ucfunnel.md b/ads/vendors/ucfunnel.md similarity index 88% rename from ads/ucfunnel.md rename to ads/vendors/ucfunnel.md index 778bbd14717fa..33bffe5725592 100644 --- a/ads/ucfunnel.md +++ b/ads/vendors/ucfunnel.md @@ -31,8 +31,8 @@ limitations under the License. ### Required Ad Parameters -- `data-site-id` - unique publisher identifier, supplied by ucfunnel. +- `data-site-id` - unique publisher identifier, supplied by ucfunnel. ### Optional parameters -- `data-schain` - supply chain info. +- `data-schain` - supply chain info. diff --git a/ads/vendors/unruly.js b/ads/vendors/unruly.js new file mode 100644 index 0000000000000..f5a45f9ab3ce5 --- /dev/null +++ b/ads/vendors/unruly.js @@ -0,0 +1,33 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + * @param {!Function} [scriptLoader=loadScript] + */ +export function unruly(global, data, scriptLoader = loadScript) { + validateData(data, ['siteId']); + + global.unruly = global.unruly || {}; + global.unruly.native = { + siteId: data.siteId, + }; + + scriptLoader(global, 'https://video.unrulymedia.com/native/native-loader.js'); +} diff --git a/ads/unruly.md b/ads/vendors/unruly.md similarity index 91% rename from ads/unruly.md rename to ads/vendors/unruly.md index bb0352711dbaf..1d25b3b59fb09 100644 --- a/ads/unruly.md +++ b/ads/vendors/unruly.md @@ -31,4 +31,4 @@ limitations under the License. ### Required Ad Parameters -- `data-site-id` - unique publisher identifier, supplied by Unruly. +- `data-site-id` - unique publisher identifier, supplied by Unruly. diff --git a/ads/vendors/uzou.js b/ads/vendors/uzou.js new file mode 100644 index 0000000000000..b98a1f805efcc --- /dev/null +++ b/ads/vendors/uzou.js @@ -0,0 +1,75 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function uzou(global, data) { + validateData(data, ['widgetParams'], []); + + const prefixMap = { + test: 'dev-', + development: 'dev-', + staging: 'staging-', + production: '', + }; + + const widgetParams = parseJson(data['widgetParams']); + const akamaiHost = widgetParams['akamaiHost'] || 'speee-ad.akamaized.net'; + const placementCode = widgetParams['placementCode']; + const mode = widgetParams['mode'] || 'production'; + const entryPoint = `https://${prefixMap[mode]}${akamaiHost}/tag/${placementCode}/js/outer-frame.min.js`; + + const d = global.document.createElement('div'); + d.className = `uz-${placementCode} uz-ny`; + + const container = global.document.getElementById('c'); + container.appendChild(d); + + const uzouInjector = { + url: fixedEncodeURIComponent( + widgetParams['url'] || + global.context.canonicalUrl || + global.context.sourceUrl + ), + referer: widgetParams['referer'] || global.context.referrer, + }; + ['adServerHost', 'akamaiHost', 'iframeSrcPath'].forEach(function (elem) { + if (widgetParams[elem]) { + uzouInjector[elem] = widgetParams[elem]; + } + }); + global.UzouInjector = uzouInjector; + + loadScript(global, entryPoint, () => { + global.context.renderStart(); + }); +} + +/** + * encode URI based on RFC 3986 + * @param {string} str url string + * @return {*} TODO(#23582): Specify return type + */ +function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16); + }); +} diff --git a/ads/uzou.md b/ads/vendors/uzou.md similarity index 94% rename from ads/uzou.md rename to ads/vendors/uzou.md index 54cd441632381..d36d1af9350e8 100644 --- a/ads/uzou.md +++ b/ads/vendors/uzou.md @@ -38,5 +38,5 @@ For the widget design or configuration details, please contact your account mana Supported parameters: -- widget-params - - JSON string including your placement code. +- widget-params + - JSON string including your placement code. diff --git a/ads/vendors/valuecommerce.js b/ads/vendors/valuecommerce.js new file mode 100644 index 0000000000000..de2ae81693242 --- /dev/null +++ b/ads/vendors/valuecommerce.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function valuecommerce(global, data) { + validateData(data, ['pid'], ['sid', 'vcptn', 'om']); + global.vcParam = data; + writeScript(global, 'https://amp.valuecommerce.com/amp_bridge.js'); +} diff --git a/ads/valuecommerce.md b/ads/vendors/valuecommerce.md similarity index 100% rename from ads/valuecommerce.md rename to ads/vendors/valuecommerce.md diff --git a/ads/vendors/vdoai.js b/ads/vendors/vdoai.js new file mode 100644 index 0000000000000..958cf196bc547 --- /dev/null +++ b/ads/vendors/vdoai.js @@ -0,0 +1,29 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript} from '../../3p/3p'; +/** + * @param {!Window} global + * @param {!Object} data + */ +export function vdoai(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global.vdo_ai_ = { + unitData: data['unitid'], + unitTagname: data['tagname'], + }; + loadScript(global, 'https://a.vdo.ai/core/dependencies_amp/remote.js'); +} diff --git a/ads/vdoai.md b/ads/vendors/vdoai.md similarity index 95% rename from ads/vdoai.md rename to ads/vendors/vdoai.md index 5c86b26792e75..5e3a981381c67 100644 --- a/ads/vdoai.md +++ b/ads/vendors/vdoai.md @@ -34,5 +34,5 @@ limitations under the License. Supported parameters: -- `data-unitid` -- `data-tagname` +- `data-unitid` +- `data-tagname` diff --git a/ads/vendors/verizonmedia.js b/ads/vendors/verizonmedia.js new file mode 100644 index 0000000000000..73fbba4c33db3 --- /dev/null +++ b/ads/vendors/verizonmedia.js @@ -0,0 +1,26 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {validateData, writeScript} from '../../3p/3p'; +/** + * @param {!Window} global + * @param {{config: string}} data + */ +export function verizonmedia(global, data) { + validateData(data, ['config']); + global.jacData = data; + writeScript(global, 'https://jac.yahoosandbox.com/amp/jac.js'); +} diff --git a/ads/vendors/verizonmedia.md b/ads/vendors/verizonmedia.md new file mode 100644 index 0000000000000..b02b685ba2fa3 --- /dev/null +++ b/ads/vendors/verizonmedia.md @@ -0,0 +1,57 @@ + + +# VerizonMedia + +## Example + +```html + + +``` + +## Configuration + +For semantics of configuration, please see ad network documentation. + +### Required Parameters: + +`data-config` - Config for ad call + +### Optional parameters: + +`data-stylesheet` - stylesheet to use inside iframe + +### Configuration Details + +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/videointelligence.js b/ads/vendors/videointelligence.js new file mode 100644 index 0000000000000..80d91ed91cba6 --- /dev/null +++ b/ads/vendors/videointelligence.js @@ -0,0 +1,27 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function videointelligence(global, data) { + validateData(data, ['publisherId', 'channelId']); + + loadScript(global, 'https://s.vi-serve.com/tagLoaderAmp.js'); +} diff --git a/ads/videointelligence.md b/ads/vendors/videointelligence.md similarity index 89% rename from ads/videointelligence.md rename to ads/vendors/videointelligence.md index bf8ff6995c2f1..f138259498578 100644 --- a/ads/videointelligence.md +++ b/ads/vendors/videointelligence.md @@ -36,11 +36,11 @@ For configuration information, please check [docs.vi.ai/](https://docs.vi.ai/gen ### Required parameters -- `data-publisher-id` -- `data-channel-id` +- `data-publisher-id` +- `data-channel-id` ### Optional parameters -- `data-placement-id` -- `data-iab-category` -- `data-language` +- `data-placement-id` +- `data-iab-category` +- `data-language` diff --git a/ads/vendors/videonow.js b/ads/vendors/videonow.js new file mode 100644 index 0000000000000..5e5a35d57f158 --- /dev/null +++ b/ads/vendors/videonow.js @@ -0,0 +1,85 @@ +/** + * Copyright 2015 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; +import {tryDecodeUriComponent} from '../../src/core/types/string/url'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function videonow(global, data) { + const mandatoryAttributes = ['pid']; + const optionalAttributes = ['kind', 'src']; + + let customTag = ''; + let logLevel = null; + let vnModule = ''; + + if (global && global.name) { + const p = parseJson(global.name); + if ( + p && + p['attributes'] && + p['attributes']['_context'] && + p['attributes']['_context']['location'] && + p['attributes']['_context']['location']['href'] + ) { + const {href} = p['attributes']['_context']['location']; + const logLevelParsed = /[?&]vn_debug\b(?:=(\d+))?/.exec(href); + const vnModuleParsed = /vn_module=([^&]*)/.exec(href); + const customTagParsed = /vn_init_module=([^&]*)/.exec(href); + + logLevel = (logLevelParsed && logLevelParsed[1]) || null; + vnModule = (vnModuleParsed && vnModuleParsed[1]) || ''; + customTag = (customTagParsed && customTagParsed[1]) || ''; + } + } + validateData(data, mandatoryAttributes, optionalAttributes); + + const profileId = data.pid || 1; + + // production version by default + let script = + (customTag && tryDecodeUriComponent(customTag)) || + (data.src && tryDecodeUriComponent(data.src)) || + 'https://cdn.videonow.ru/vn_init_module.js'; + + script = addParam(script, 'amp', 1); + script = addParam(script, 'profileId', profileId); + if (logLevel !== null) { + script = addParam(script, 'vn_debug', String(logLevel)); + } + if (vnModule) { + script = addParam(script, 'vn_module', vnModule); + } + + loadScript(global, script); +} + +/** + * @param {string} script + * @param {string} name + * @param {string|number}value + * @return {string} + */ +function addParam(script, name, value) { + if (script.indexOf(name) < 0) { + script += (~script.indexOf('?') ? '&' : '?') + name + '=' + value; + } + return script; +} diff --git a/ads/videonow.md b/ads/vendors/videonow.md similarity index 98% rename from ads/videonow.md rename to ads/vendors/videonow.md index 015486c0a1fc3..a8b9db776b776 100644 --- a/ads/videonow.md +++ b/ads/vendors/videonow.md @@ -35,4 +35,4 @@ For semantics of configuration, please contact [Videonow](http://videonow.ru/htm Supported parameters: -- `data-pid` +- `data-pid` diff --git a/ads/vendors/viralize.js b/ads/vendors/viralize.js new file mode 100644 index 0000000000000..22916466521ed --- /dev/null +++ b/ads/vendors/viralize.js @@ -0,0 +1,53 @@ +/** + * Copyright 2017 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. + */ + +import {addParamsToUrl} from '../../src/url'; +import {loadScript, validateData} from '../../3p/3p'; +import {parseJson} from '../../src/core/types/object/json'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function viralize(global, data) { + const endpoint = 'https://ads.viralize.tv/display/'; + const required = ['zid']; + const optional = ['extra']; + + validateData(data, required, optional); + + const defaultLocation = 'sel-#c>script'; + const pubPlatform = 'amp'; + + const queryParams = parseJson(data.extra || '{}'); + queryParams['zid'] = data.zid; + queryParams['pub_platform'] = pubPlatform; + if (!queryParams['location']) { + queryParams['location'] = defaultLocation; + } + if (!queryParams['u']) { + queryParams['u'] = global.context.sourceUrl; + } + + const scriptUrl = addParamsToUrl(endpoint, queryParams); + + loadScript( + global, + scriptUrl, + () => global.context.renderStart(), + () => global.context.noContentAvailable() + ); +} diff --git a/ads/viralize.md b/ads/vendors/viralize.md similarity index 87% rename from ads/viralize.md rename to ads/vendors/viralize.md index 250a343d164db..698ec2b882b87 100644 --- a/ads/viralize.md +++ b/ads/vendors/viralize.md @@ -35,8 +35,8 @@ For further configuration details, please contact [Viralize](https://viralize.co ## Required parameters -- `data-zid`: Id of the unit. +- `data-zid`: Id of the unit. ## Optional parameters -- `data-extra`: JSON object representing any other query parameter that could be passed to the unit. +- `data-extra`: JSON object representing any other query parameter that could be passed to the unit. diff --git a/ads/vendors/vlyby.js b/ads/vendors/vlyby.js new file mode 100644 index 0000000000000..f0ec8b2831d84 --- /dev/null +++ b/ads/vendors/vlyby.js @@ -0,0 +1,74 @@ +/** + * Copyright 2016 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. + */ +/* eslint-disable require-jsdoc */ +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function vlyby(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + global._vlyby_amp = { + allowed_data: ['publisherid', 'placementid', 'pubref'], + mandatory_data: ['publisherid', 'placementid'], + data: { + pubref: '', + publisherid: '', + placementid: '', + ...data, + }, + }; + + validateData(data, global._vlyby_amp.mandatory_data); + + const rand = Math.round(Math.random() * 100000000); + + // install observation on entering/leaving the view + global.context.observeIntersection(function (changes) { + /** @type {!Array} */ (changes).forEach(function (c) { + if (global._vlyby_amp) { + global._vlyby_amp.rects = c; + } + }); + }); + + //create Container + const containerId = 'qad' + rand; + createContainer(global, containerId); + + //create Script + createScript(global, containerId); + + function createScript(global, id) { + const s = global.document.createElement('script'); + const referrer = data['pubref'] || global.context.canonicalUrl; + + s.setAttribute('type', 'text/javascript'); + s.setAttribute('async', 'true'); + s.setAttribute('src', '//cdn.vlyby.com/amp/qad/qad-outer2.js'); + s.setAttribute('data-PubId', data['publisherid']); + s.setAttribute('data-PlacementId', data['placementid']); + s.setAttribute('data-DivId', id); + s.setAttribute('data-PubRef', referrer); + global.document.getElementById('c').appendChild(s); + } + function createContainer(global, id) { + const d = global.document.createElement('div'); + d.id = id; + global.document.getElementById('c').appendChild(d); + } +} diff --git a/ads/vendors/vlyby.md b/ads/vendors/vlyby.md new file mode 100644 index 0000000000000..5cc9ab9b1cf8e --- /dev/null +++ b/ads/vendors/vlyby.md @@ -0,0 +1,43 @@ + + +# vlyby + +## Example + +```html + + +``` + +## Configuration + +Supported parameters: + +All parameters are mandatory, only `data-pubref` is optional. + +- `data-publisherid` (String, non-empty) +- `data-placementid` (String, non-empty) +- `data-pubref` (String) diff --git a/ads/vendors/vmfive.js b/ads/vendors/vmfive.js new file mode 100644 index 0000000000000..0102c6c588d35 --- /dev/null +++ b/ads/vendors/vmfive.js @@ -0,0 +1,72 @@ +/** + * Copyright 2017 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. + */ + +import {validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function vmfive(global, data) { + /*eslint "google-camelcase/google-camelcase": 0*/ + const mandatory_fields = ['appKey', 'placementId', 'adType']; + const optional_fields = []; + + const {adType, appKey, placementId} = data; + + global._vmfive_amp = {appKey, placementId, adType}; + + validateData(data, mandatory_fields, optional_fields); + + createAdUnit(global, placementId, adType); + setupSDKReadyCallback(global, appKey); + parallelDownloadScriptsAndExecuteInOrder(global); +} + +/** + * @param {!Window} win + */ +function parallelDownloadScriptsAndExecuteInOrder(win) { + [ + 'https://vawpro.vm5apis.com/man.js', + 'https://man.vm5apis.com/dist/adn-web-sdk.js', + ].forEach(function (src) { + const script = document.createElement('script'); + script.src = src; + script.async = false; + win.document.head.appendChild(script); + }); +} + +/** + * @param {!Window} win + * @param {string} placementId + * @param {string} adType + */ +function createAdUnit(win, placementId, adType) { + const el = document.createElement('vmfive-ad-unit'); + el.setAttribute('placement-id', placementId); + el.setAttribute('ad-type', adType); + win.document.getElementById('c').appendChild(el); +} + +/** + * @param {!Window} win + * @param {string} appKey + */ +function setupSDKReadyCallback(win, appKey) { + win.onVM5AdSDKReady = (sdk) => sdk.init({appKey}); +} diff --git a/ads/vmfive.md b/ads/vendors/vmfive.md similarity index 95% rename from ads/vmfive.md rename to ads/vendors/vmfive.md index d1942c172ec5d..de86731012c6f 100644 --- a/ads/vmfive.md +++ b/ads/vendors/vmfive.md @@ -66,6 +66,6 @@ For configuration details and to generate your tags, please contact http://vmfiv Supported parameters: -- `data-app-key` -- `data-placement-id` -- `data-ad-type` +- `data-app-key` +- `data-placement-id` +- `data-ad-type` diff --git a/ads/vendors/webediads.js b/ads/vendors/webediads.js new file mode 100644 index 0000000000000..7908990ef4de8 --- /dev/null +++ b/ads/vendors/webediads.js @@ -0,0 +1,34 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * Created by Webedia on 07/03/16 - last updated on 11/10/16 + * @param {!Window} global + * @param {!Object} data + */ +export function webediads(global, data) { + validateData(data, ['site', 'page', 'position'], ['query']); + loadScript(global, 'https://eu1.wbdds.com/amp.min.js', () => { + global.wads.amp.init({ + 'site': data.site, + 'page': data.page, + 'position': data.position, + 'query': data.query ? data.query : '', + }); + }); +} diff --git a/ads/webediads.md b/ads/vendors/webediads.md similarity index 84% rename from ads/webediads.md rename to ads/vendors/webediads.md index a8740753a0215..96072c2827b6a 100644 --- a/ads/webediads.md +++ b/ads/vendors/webediads.md @@ -77,10 +77,10 @@ For details on the configuration semantics, please contact the ad network or ref All parameters are mandatory, only "query" can be empty. -- `data-site` (String, non-empty) -- `data-page` (String, non-empty) -- `data-position` (String, non-empty) -- `data-query` (String) - - `key` are separated with `&` - - `value` are separted with `|` - - **Example**: `key1=value1|value2|value3&key2=value4&key3=value5|value6` +- `data-site` (String, non-empty) +- `data-page` (String, non-empty) +- `data-position` (String, non-empty) +- `data-query` (String) + - `key` are separated with `&` + - `value` are separted with `|` + - **Example**: `key1=value1|value2|value3&key2=value4&key3=value5|value6` diff --git a/ads/weborama.js b/ads/vendors/weborama.js similarity index 97% rename from ads/weborama.js rename to ads/vendors/weborama.js index 86ad07de73d65..c26c2621ba1c3 100644 --- a/ads/weborama.js +++ b/ads/vendors/weborama.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {validateData, writeScript} from '../3p/3p'; +import {validateData, writeScript} from '../../3p/3p'; /** * @param {!Window} global diff --git a/ads/vendors/weborama.md b/ads/vendors/weborama.md new file mode 100644 index 0000000000000..10b7fd4a987e3 --- /dev/null +++ b/ads/vendors/weborama.md @@ -0,0 +1,106 @@ + + +# Weborama + +## Example + +**Display tag:** + +See below for an example of usage or our display tag, adapted for use with AMP websites: + +```html + +
Loading ad.
+
Ad could not be loaded.
+
+``` + +**Conversion tag:** + +In order to add conversion trackers to your page, please use the AMP pixel component that will be supplied to you by your Weborama contact. +The values mentioned between brackets `[]` should be replaced by the proper values. + +`DOCUMENT_REFERRER`, `SOURCE_URL` and `RANDOM` should remain in the URL, as AMP takes care of the automatic substitution of these values. + +```html + +``` + +## Configuration + +**Placeholder and fallback:** + +The placeholder and fallback `div` elements are completely optional and can be left out. + +- The placeholder is shown while the ad is loading. +- The fallback is served when there is no ad to show for some reason. + +**Dimensions:** + +By default the ad size is based on the `width` and `height` attributes of the `amp-ad` tag. These are listed as mandatory parameters. + +The AMP ad component requires the following HTML attributes to be added before the ad will be parsed as a Weborama ad: + +- width +- height +- type="weborama-display" + +**Mandatory data parameters:** + +Without valid values for these parameters we won't be able to display an ad: + +- `data-wbo_account_id` +- `data-wbo_tracking_element_id` +- `data-wbo_fullhost` + +**Examples of optional parameters:** + +Here are some extra parameters that might be set on the AMP ad: + +- `data-wbo_random` - Used as session identifier by some 3rd party ad systems +- `data-wbo_publisherclick` - Adds a publisher redirect for the exit click. +- `data-wbo_vars` - Sends variables to the creative. e.g.: `color=green&weather=rainy` +- `data-wbo_debug` - Launch the ad in debug mode. +- ... ask your contact at Weborama for more details. + +## Current restrictions + +- AMP ads are launched in a cross-origin iframe, so there currently is no support for some rich media, amongst which: + - Expandables + - Floorads, Interstitials and other types of layers + - Flash ads + - Creatives served over HTTP. +- Click and impression trackers can't be added to the tag. They need to be added through Weborama's ad platform: WCM. +- At the moment we don't support dynamic resizing of the iframe. Support for this will be added on request. + +## Support + +If you have any questions, please refer to your contact at Weborama. Otherwise you can contact our TIER 2 support: + +- E: tier2support@weborama.nl +- T: +31 (0) 20 52 46 690 diff --git a/ads/vendors/whopainfeed.js b/ads/vendors/whopainfeed.js new file mode 100644 index 0000000000000..cb4bd06a65138 --- /dev/null +++ b/ads/vendors/whopainfeed.js @@ -0,0 +1,34 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function whopainfeed(global, data) { + validateData(data, ['siteid']); + + global._whopainfeed = global._whopainfeed || { + viewId: global.context.pageViewId, + siteId: data['siteid'], + testMode: data['testmode'] || 'false', + template: data['template'] || 'default', + }; + + loadScript(global, 'https://widget.infeed.com.ar/widget/widget-amp.js'); +} diff --git a/ads/whopainfeed.md b/ads/vendors/whopainfeed.md similarity index 93% rename from ads/whopainfeed.md rename to ads/vendors/whopainfeed.md index 4c644cfd10fbc..18e968d52bf97 100644 --- a/ads/whopainfeed.md +++ b/ads/vendors/whopainfeed.md @@ -40,11 +40,11 @@ These configurations are relevant for both `` and ``. ### Required parameters -- `data-siteId`: Site ID provided by Whopa InFeed Team. +- `data-siteId`: Site ID provided by Whopa InFeed Team. ### Optional parameters -- `data-template`: The Template of Widget. +- `data-template`: The Template of Widget. **Resolution** diff --git a/ads/vendors/widespace.js b/ads/vendors/widespace.js new file mode 100644 index 0000000000000..3ca0ab0bf4db5 --- /dev/null +++ b/ads/vendors/widespace.js @@ -0,0 +1,42 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function widespace(global, data) { + const WS_AMP_CODE_VER = '1.0.1'; + // Optional demography parameters. + let demo = []; + + demo = ['Gender', 'Country', 'Region', 'City', 'Postal', 'Yob'].map((d) => { + return 'demo' + d; + }); + + validateData(data, ['sid'], demo); + + const url = + 'https://engine.widespace.com/map/engine/dynamic?isamp=1' + + '&ver=' + + WS_AMP_CODE_VER + + '&#sid=' + + encodeURIComponent(data.sid); + + writeScript(global, url); +} diff --git a/ads/widespace.md b/ads/vendors/widespace.md similarity index 90% rename from ads/widespace.md rename to ads/vendors/widespace.md index 6ff861e2908b9..95590e94e83d3 100644 --- a/ads/widespace.md +++ b/ads/vendors/widespace.md @@ -53,12 +53,12 @@ For configuration details, please contact integrations@widespace.com. ### Required parameters -- `data-sid` +- `data-sid` ### Optional demography parameters -- `data-demo-Gender` -- `data-demo-Country` -- `data-demo-Region` -- `data-demo-City` -- `data-demo-Yob` +- `data-demo-Gender` +- `data-demo-Country` +- `data-demo-Region` +- `data-demo-City` +- `data-demo-Yob` diff --git a/ads/vendors/wisteria.js b/ads/vendors/wisteria.js new file mode 100644 index 0000000000000..cf68071c75fe5 --- /dev/null +++ b/ads/vendors/wisteria.js @@ -0,0 +1,39 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function wisteria(global, data) { + const d = global.document.createElement('div'); + d.id = '_wisteria_recommend_contents'; + global.document.getElementById('c').appendChild(d); + //get canonical url + const originalUrl = global.context.canonicalUrl; + validateData(data, ['siteId', 'templateNumber']); + loadScript( + global, + 'https://wisteria-js.excite.co.jp/wisteria.js?site_id=' + + data['siteId'] + + '&template_no=' + + data['templateNumber'] + + '&original_url=' + + originalUrl + ); +} diff --git a/ads/wisteria.md b/ads/vendors/wisteria.md similarity index 89% rename from ads/wisteria.md rename to ads/vendors/wisteria.md index 202fc33df7f31..949eb3c472f2e 100644 --- a/ads/wisteria.md +++ b/ads/vendors/wisteria.md @@ -36,7 +36,7 @@ For configuration details, please contact https://www.excite.co.jp/guide/wisteri Supported parameters: -- data-site-id (required) -- data-template-number (required) -- height (optional) -- width (optional) +- data-site-id (required) +- data-template-number (required) +- height (optional) +- width (optional) diff --git a/ads/vendors/wpmedia.js b/ads/vendors/wpmedia.js new file mode 100644 index 0000000000000..8e6f0e5b7198f --- /dev/null +++ b/ads/vendors/wpmedia.js @@ -0,0 +1,32 @@ +/** + * Copyright 2017 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function wpmedia(global, data) { + validateData(data, ['slot', 'bunch'], ['sn', 'slots']); + + // const url = 'http://localhost/wpjslib.js'; + const url = 'https://std.wpcdn.pl/wpjslib/wpjslib-amp.js'; + + writeScript(global, url, function () { + window.run(data); + }); +} diff --git a/ads/wpmedia.md b/ads/vendors/wpmedia.md similarity index 94% rename from ads/wpmedia.md rename to ads/vendors/wpmedia.md index 27d7596040215..d1a48e9ff3cda 100644 --- a/ads/wpmedia.md +++ b/ads/vendors/wpmedia.md @@ -44,13 +44,13 @@ limitations under the License. ### Required parameter -- `data-slot` -- `data-bunch` +- `data-slot` +- `data-bunch` ### Optional parameters -- `data-slots` -- `data-sn` +- `data-slots` +- `data-sn` ## Configuration diff --git a/ads/vendors/xlift.js b/ads/vendors/xlift.js new file mode 100644 index 0000000000000..7d63a391dd749 --- /dev/null +++ b/ads/vendors/xlift.js @@ -0,0 +1,56 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function xlift(global, data) { + validateData(data, ['mediaid']); + + global.xliftParams = data; + const d = global.document.createElement('div'); + d.id = '_XL_recommend'; + global.document.getElementById('c').appendChild(d); + + d.addEventListener('SuccessLoadedXliftAd', function (e) { + e.detail = e.detail || {adSizeInfo: {}}; + global.context.renderStart(e.detail.adSizeInfo); + }); + d.addEventListener('FailureLoadedXliftAd', function () { + global.context.noContentAvailable(); + }); + + //assign XliftAmpHelper property to global(window) + global.XliftAmpHelper = null; + + loadScript( + global, + 'https://cdn.x-lift.jp/resources/common/xlift_amp.js', + () => { + if (!global.XliftAmpHelper) { + global.context.noContentAvailable(); + } else { + global.XliftAmpHelper.show(); + } + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/xlift.md b/ads/vendors/xlift.md similarity index 93% rename from ads/xlift.md rename to ads/vendors/xlift.md index 5af8d8077125d..9c80e466fe38f 100644 --- a/ads/xlift.md +++ b/ads/vendors/xlift.md @@ -28,4 +28,4 @@ For configuration details and to generate your tags, please contact https://www. #### Required parameters -- `data-mediaid`: For loading JavaScript for each media. +- `data-mediaid`: For loading JavaScript for each media. diff --git a/ads/vendors/yahoo.js b/ads/vendors/yahoo.js new file mode 100644 index 0000000000000..6c25a437f5e73 --- /dev/null +++ b/ads/vendors/yahoo.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yahoo(global, data) { + validateData(data, ['sid', 'site', 'sa']); + global.yadData = data; + writeScript(global, 'https://s.yimg.com/aaq/ampad/display.js'); +} diff --git a/ads/yahoo.md b/ads/vendors/yahoo.md similarity index 93% rename from ads/yahoo.md rename to ads/vendors/yahoo.md index a6d6e36faf8a7..a1be69bcda8df 100644 --- a/ads/yahoo.md +++ b/ads/vendors/yahoo.md @@ -36,8 +36,8 @@ For configuration details, please contact https://advertising.yahoo.com/contact. Supported parameters: -- `height` -- `width` -- `data-sid` -- `data-site` -- `data-sa` +- `height` +- `width` +- `data-sid` +- `data-site` +- `data-sa` diff --git a/ads/vendors/yahoofedads.js b/ads/vendors/yahoofedads.js new file mode 100644 index 0000000000000..3c82c1cfc26e5 --- /dev/null +++ b/ads/vendors/yahoofedads.js @@ -0,0 +1,55 @@ +/** + * Copyright 2020 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yahoofedads(global, data) { + validateData(data, [ + 'adUnit', + 'site', + 'region', + 'lang', + 'sa', + 'spaceId', + 'url', + ]); + + global.amp = true; + global.adConfig = { + 'adPositionOverride': data.adPositionOverride, + 'adUnit': data.adUnit, + 'forceSource': data.forceSource, + 'height': data.height, + 'lang': data.lang, + 'publisherUrl': data.url, + 'region': data.region, + 'sa': data.sa, + 'sectionId': data.sectionId, + 'site': data.site, + 'spaceId': data.spaceId, + 'width': data.width, + }; + + loadScript( + global, + 'https://s.yimg.com/aaq/ampyahoofedads/yahoofedads.js', + () => global.context.renderStart() + ); +} diff --git a/ads/vendors/yahoofedads.md b/ads/vendors/yahoofedads.md new file mode 100644 index 0000000000000..da639216ba8d8 --- /dev/null +++ b/ads/vendors/yahoofedads.md @@ -0,0 +1,57 @@ + + +# Yahoo Native-Display Ads Federation + +## Example + +Yahoo Native-Display Ads Federation only requires a configured section code to run. Please work with your account manager to configure your AMP sections. + +### Basic + +```html + + +``` + +### Required parameters + +- `data-site`, `data-region`, `data-lang`: Unique site/region/lang code that represents your site +- `data-ad-unit`: The AD position with federation config, valid value can be `pencil`, `moments` or `outstream` +- `data-sa`: The site-attribute used by display AD to render +- `data-url`: Url the AD is hosted in for tracking purpose +- `data-space-id`: Use this to provide what space id need to be used on picking up Display AD + +### Optional override parameters + +- `data-ad-position-override`: The position which will be fetched from backend mandatorily, it can be `lrec`, `wfpad`, `reservedMoments`, `nativeMoments` or `nativeRegular` +- `data-section-id`: Use this ot override what section id need to be used on picking up Native AD diff --git a/ads/vendors/yahoojp.js b/ads/vendors/yahoojp.js new file mode 100644 index 0000000000000..aedb73c9fa89b --- /dev/null +++ b/ads/vendors/yahoojp.js @@ -0,0 +1,30 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yahoojp(global, data) { + validateData(data, ['yadsid'], []); + global.yahoojpParam = data; + writeScript( + global, + 'https://s.yimg.jp/images/listing/tool/yads/ydn/amp/amp.js' + ); +} diff --git a/ads/yahoojp.md b/ads/vendors/yahoojp.md similarity index 97% rename from ads/yahoojp.md rename to ads/vendors/yahoojp.md index 3d3d8dc212bfd..49ac9792bddb5 100644 --- a/ads/yahoojp.md +++ b/ads/vendors/yahoojp.md @@ -29,4 +29,4 @@ For configuration details and to generate your tags, please contact http://marke Supported parameters: -- `data-yadsid` +- `data-yadsid` diff --git a/ads/vendors/yahoonativeads.js b/ads/vendors/yahoonativeads.js new file mode 100644 index 0000000000000..261da680ae6db --- /dev/null +++ b/ads/vendors/yahoonativeads.js @@ -0,0 +1,44 @@ +/** + * Copyright 2019 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yahoonativeads(global, data) { + validateData(data, ['code', 'url']); + + global.publisherUrl = data.url; + global.amp = true; + + const config = {}; + + if (data.key) { + config.apiKey = data.key; + } + + Object.keys(data).forEach((property) => { + config[property] = data[property]; + }); + + (global.native = global.native || []).push(config); + + loadScript(global, 'https://s.yimg.com/dy/ads/native.js', () => + global.context.renderStart() + ); +} diff --git a/ads/vendors/yahoonativeads.md b/ads/vendors/yahoonativeads.md new file mode 100644 index 0000000000000..88dafd25cd234 --- /dev/null +++ b/ads/vendors/yahoonativeads.md @@ -0,0 +1,48 @@ + + +# Yahoo Native Ads + +## Example + +Yahoo Native Ads requires a unique section code to run. Please reach out to your account manager with any questions about configuring your AMP placements or requesting new section codes. + +### Basic + +```html + + +``` + +### Required parameters + +- `data-code` : A unique section code that represents your site and placement. +- `data-url` : The URL of the site your section code is allowed to run on. + +### Optional parameters + +- `data-key` : The API key assigned to your site. Used for legacy integrations. +- `data-api-key` : The API key assigned to your site. Used for legacy integrations. +- `data-enabled-ad-feedback` : If defined, a feedback indicator will be shown on eligable ad placements. +- `data-image-type` : The preferred image rendering "square", "rectangle" or "thumbnail". +- `json` : You can use this field to define your entire config, if desired (ex: `json='{ "imageType": "square" }'`) diff --git a/ads/vendors/yandex.js b/ads/vendors/yandex.js new file mode 100644 index 0000000000000..26c25c94dc463 --- /dev/null +++ b/ads/vendors/yandex.js @@ -0,0 +1,76 @@ +/** + * Copyright 2017 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. + */ +import {loadScript, validateData} from '../../3p/3p'; + +const n = 'yandexContextAsyncCallbacks'; +const renderTo = 'yandex_rtb'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yandex(global, data) { + validateData(data, ['blockId'], ['data', 'onRender', 'onError']); + + addToQueue(global, data); + loadScript(global, 'https://an.yandex.ru/system/context_amp.js'); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function addToQueue(global, data) { + global[n] = global[n] || []; + global[n].push(() => { + // Create container + createContainer(global, renderTo); + + // Show Ad in container + global.Ya.Context.AdvManager.render( + { + blockId: data.blockId, + statId: data.statId, + renderTo, + data: data.data, + async: true, + onRender: () => { + if (typeof data.onRender === 'function') { + data.onRender(); + } + global.context.renderStart(); + }, + }, + () => { + if (typeof data.onError === 'function') { + data.onError(); + } else { + global.context.noContentAvailable(); + } + } + ); + }); +} + +/** + * @param {!Window} global + * @param {string} id + */ +function createContainer(global, id) { + const d = global.document.createElement('div'); + d.id = id; + global.document.getElementById('c').appendChild(d); +} diff --git a/ads/yandex.md b/ads/vendors/yandex.md similarity index 89% rename from ads/yandex.md rename to ads/vendors/yandex.md index b23601d06cf8a..cc4a41059c1b6 100644 --- a/ads/yandex.md +++ b/ads/vendors/yandex.md @@ -29,11 +29,11 @@ For semantics of configuration, please see [Yandex's documentation](https://yand ### Required parameters -- `data-block-id` +- `data-block-id` ### Optional parameters -- `data-data` -- `data-html-access-allowed` -- `data-on-render` -- `data-on-error` +- `data-data` +- `data-html-access-allowed` +- `data-on-render` +- `data-on-error` diff --git a/ads/vendors/yektanet.js b/ads/vendors/yektanet.js new file mode 100644 index 0000000000000..3a510d021611a --- /dev/null +++ b/ads/vendors/yektanet.js @@ -0,0 +1,51 @@ +/** + * Copyright 2018 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yektanet(global, data) { + validateData(data, ['publisherName', 'scriptName', 'posId'], ['adType']); + + const isBanner = data['adType'] === 'banner'; + + const container = document.getElementById('c'); + const adDiv = document.createElement('div'); + adDiv.setAttribute('id', data['posId']); + if (isBanner) { + adDiv.setAttribute('class', 'yn-bnr'); + } + container.appendChild(adDiv); + + const now = new Date(); + const version = [ + now.getFullYear(), + now.getMonth(), + now.getDate(), + now.getHours(), + ].join('0'); + + const scriptSrc = isBanner + ? 'https://cdn.yektanet.com/template/bnrs/yn_bnr.min.js' + : `https://cdn.yektanet.com/js/${encodeURIComponent( + data['publisherName'] + )}/${encodeURIComponent(data['scriptName'])}`; + + loadScript(global, `${scriptSrc}?v=${version}`); +} diff --git a/ads/vendors/yektanet.md b/ads/vendors/yektanet.md new file mode 100644 index 0000000000000..8594bf73f5299 --- /dev/null +++ b/ads/vendors/yektanet.md @@ -0,0 +1,70 @@ + + +# Yektanet + +## Example + +### Native Ads + +```html + + +``` + +### Banners + +```html + + +``` + +### Sticky Banners + +```html + + + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +Supported parameters: + +- `data-ad-type` +- `data-publisher-name` +- `data-script-name` +- `data-pos-id` diff --git a/ads/vendors/yengo.js b/ads/vendors/yengo.js new file mode 100644 index 0000000000000..79944fc50f682 --- /dev/null +++ b/ads/vendors/yengo.js @@ -0,0 +1,41 @@ +/** + * Copyright 2017 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yengo(global, data) { + validateData(data, ['blockId']); + + const url = + 'https://code.yengo.com/data/' + + encodeURIComponent(data['blockId']) + + '.js?async=1&div=c'; + + loadScript( + global, + url, + () => { + global.context.renderStart(); + }, + () => { + global.context.noContentAvailable(); + } + ); +} diff --git a/ads/yengo.md b/ads/vendors/yengo.md similarity index 97% rename from ads/yengo.md rename to ads/vendors/yengo.md index 255a0984a638a..651da4f92542e 100644 --- a/ads/yengo.md +++ b/ads/vendors/yengo.md @@ -28,4 +28,4 @@ For more information, please [see Yengo's FAQ](http://www.yengo.com/text/faqs?pu ### Required parameters -- `data-block-id` +- `data-block-id` diff --git a/ads/vendors/yieldbot.js b/ads/vendors/yieldbot.js new file mode 100644 index 0000000000000..54c0bc46a0cfb --- /dev/null +++ b/ads/vendors/yieldbot.js @@ -0,0 +1,88 @@ +/** + * Copyright 2015 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. + */ + +import {getMultiSizeDimensions} from '../../ads/google/utils'; +import {loadScript, validateData} from '../../3p/3p'; +import {rethrowAsync} from '../../src/core/error'; +import {user} from '../../src/log'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function yieldbot(global, data) { + validateData(data, ['psn', 'ybSlot', 'slot']); + + global.ybotq = global.ybotq || []; + + loadScript(global, 'https://cdn.yldbt.com/js/yieldbot.intent.amp.js', () => { + global.ybotq.push(() => { + try { + const multiSizeDataStr = data.multiSize || null; + const primaryWidth = parseInt(data.overrideWidth || data.width, 10); + const primaryHeight = parseInt(data.overrideHeight || data.height, 10); + let dimensions; + + if (multiSizeDataStr) { + dimensions = getMultiSizeDimensions( + multiSizeDataStr, + primaryWidth, + primaryHeight, + false + ); + dimensions.unshift([primaryWidth, primaryHeight]); + } else { + dimensions = [[primaryWidth, primaryHeight]]; + } + + global.yieldbot.psn(data.psn); + global.yieldbot.enableAsync(); + if (window.context.isMaster) { + global.yieldbot.defineSlot(data.ybSlot, {sizes: dimensions}); + global.yieldbot.go(); + } else { + const slots = {}; + slots[data.ybSlot] = dimensions; + global.yieldbot.nextPageview(slots); + } + } catch (e) { + rethrowAsync(e); + } + }); + + global.ybotq.push(() => { + try { + const targeting = global.yieldbot.getSlotCriteria(data['ybSlot']); + data['targeting'] = data['targeting'] || {}; + for (const key in targeting) { + data.targeting[key] = targeting[key]; + } + } catch (e) { + rethrowAsync(e); + } + user().warn( + 'AMP-AD', + 'type="yieldbot" will no longer ' + + 'be supported starting on March 29, 2018.' + + ' Please use your amp-ad-network and RTC to configure a' + + ' Yieldbot callout vendor. Refer to' + + ' https://github.com/ampproject/amphtml/blob/main/' + + 'extensions/amp-a4a/rtc-publisher-implementation-guide.md' + + '#setting-up-rtc-config for more information.' + ); + }); + }); +} diff --git a/ads/yieldbot.md b/ads/vendors/yieldbot.md similarity index 79% rename from ads/yieldbot.md rename to ads/vendors/yieldbot.md index 3292cb8763d2c..171fb9997938c 100644 --- a/ads/yieldbot.md +++ b/ads/vendors/yieldbot.md @@ -25,14 +25,14 @@ Yieldbot can be configured as a demand source by using the Real Time Config (RTC | **`YB_PSN`** | Yieldbot publisher site number | | **`YB_SLOT`** | Yieldbot slot identifier | -For further information on RTC please see the [RTC Publisher Implementation Guide](https://github.com/ampproject/amphtml/blob/master/extensions/amp-a4a/rtc-publisher-implementation-guide.md) and see the example Doubleclick configuration below. +For further information on RTC please see the [RTC Publisher Implementation Guide](https://github.com/ampproject/amphtml/blob/main/extensions/amp-a4a/rtc-publisher-implementation-guide.md) and see the example Doubleclick configuration below. ## Doubleclick RTC Configuration To specify a Doubleclick `amp-ad` integration with Yieldbot, include the vendor `"yieldbot"` in your `rtc-config` tag attributed as shown in the example below. This particular example shows that the Yieldbot demand configuration for the respective Doubleclick slot, `/2476204/medium-rectangle` where: -- `"YB_PSN": "1234"`, Yieldbot publisher number -- `"YB_SLOT": "medrec"`, Yieldbot slot name +- `"YB_PSN": "1234"`, Yieldbot publisher number +- `"YB_SLOT": "medrec"`, Yieldbot slot name ```html { + let success = false; + if (!global.showadAMPAdapter) { + global.showadAMPAdapter = { + registerSlot: () => {}, + }; + loadScript(global, scriptUrl, () => { + if (global.showadAMPAdapter.inited) { + success = true; + } + done(success); + }); + } else { + done(true); + } + }, + (success) => { + if (success) { + global.showadAMPAdapter = global.context.master.showadAMPAdapter; + global.showadAMPAdapter.registerSlot(data, global); + } else { + throw new Error('Yieldpro AdTag failed to load'); + } + } + ); +} diff --git a/ads/vendors/yieldpro.md b/ads/vendors/yieldpro.md new file mode 100644 index 0000000000000..3c6752e7b4675 --- /dev/null +++ b/ads/vendors/yieldpro.md @@ -0,0 +1,100 @@ + + +# YieldPro + +## Examples + +### Single ad + +```html + + +``` + +### Multi instance ads + +```html + + + + +``` + +### Using custom params and custom ad server url + +```html + + +``` + +## Configuration + +For details on the configuration semantics, please contact the ad network or refer to their documentation. + +### Required parameters + +- `sectionId`: ID of this section in ad server. +- `slot`: ID of ad slot in ad server. +- `pubnetwork`: ID of the publisher that in ad server. + +### Optional parameters + +- `instance`: ID of section instance in case we multiple times used the same section on the same page. Can contain only letters and numbers.Strictly required to use the same section multiple times per page. +- `click3rd`: 3rd party click watcher. +- `adServerUrl` +- `cacheSafe` +- `pageIdModifier` +- `custom`: Custom targeting properties. You may use 3 types for its properties: `{String}`, `{Number}` and `{Array}`. The following array usage example translates into: `arrayKey=value1&arrayKey=1&stringKey=stringValue...` + + ```text + { + arrayKey: [ "value1", 1 ], + stringKey: 'stringValue' + } + ``` diff --git a/ads/vendors/zedo.js b/ads/vendors/zedo.js new file mode 100644 index 0000000000000..1460a2581601d --- /dev/null +++ b/ads/vendors/zedo.js @@ -0,0 +1,78 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function zedo(global, data) { + // check mandatory fields + validateData( + data, + ['superId', 'network', 'placementId', 'channel', 'publisher', 'dim'], + ['charset', 'callback', 'renderer'] + ); + + loadScript(global, 'https://ss3.zedo.com/gecko/tag/Gecko.amp.min.js', () => { + const {ZGTag} = global; + const charset = data.charset || ''; + const callback = data.callback || function () {}; + const geckoTag = new ZGTag( + data.superId, + data.network, + '', + '', + charset, + callback + ); + geckoTag.setAMP(); + // define placement + const placement = geckoTag.addPlacement( + data.placementId, + data.channel, + data.publisher, + data.dim, + data.width, + data.height + ); + if (data.renderer) { + for (const key in data.renderer) { + placement.includeRenderer( + data.renderer[key].name, + data.renderer[key].value + ); + } + } else { + placement.includeRenderer('display', {}); + } + //create a slot div to display ad + const slot = global.document.createElement('div'); + slot.id = 'zdt_' + data.placementId; + + const divContainer = global.document.getElementById('c'); + if (divContainer) { + divContainer.appendChild(slot); + } + + // call load ads + geckoTag.loadAds(); + + // call div ready + geckoTag.placementReady(data.placementId); + }); +} diff --git a/ads/zedo.md b/ads/vendors/zedo.md similarity index 84% rename from ads/zedo.md rename to ads/vendors/zedo.md index 3d5f886219e01..ee1c7ba41c243 100644 --- a/ads/zedo.md +++ b/ads/vendors/zedo.md @@ -41,15 +41,15 @@ For semantics of configuration, please contact support@zedo.com. ### Required parameters -- `data-super-id` -- `data-network` -- `data-placement-id` -- `data-channel` -- `data-publisher` -- `data-dim` +- `data-super-id` +- `data-network` +- `data-placement-id` +- `data-channel` +- `data-publisher` +- `data-dim` ### Optional parameters -- `data-charset` -- `data-callback` -- `data-renderer` +- `data-charset` +- `data-callback` +- `data-renderer` diff --git a/ads/vendors/zen.js b/ads/vendors/zen.js new file mode 100644 index 0000000000000..d2905b6c50e81 --- /dev/null +++ b/ads/vendors/zen.js @@ -0,0 +1,75 @@ +/** + * Copyright 2017 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. + */ +import {loadScript, validateData} from '../../3p/3p'; + +const n = 'yandexZenAsyncCallbacks'; +const renderTo = 'zen-widget'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function zen(global, data) { + validateData( + data, + ['clid'], + ['size', 'orientation', 'successCallback', 'failCallback'] + ); + + addToQueue(global, data); + loadScript(global, 'https://zen.yandex.ru/widget-loader'); +} + +/** + * @param {!Window} global + * @param {!Object} data + */ +function addToQueue(global, data) { + global[n] = global[n] || []; + global[n].push(() => { + // Create container + createContainer(global, renderTo); + + const {YandexZen} = global; + const config = Object.assign(data, { + clid: JSON.parse(data.clid), + container: `#${renderTo}`, + isAMP: true, + successCallback: () => { + if (typeof data.successCallback === 'function') { + data.successCallback(); + } + }, + failCallback: () => { + if (typeof data.failCallback === 'function') { + data.failCallback(); + } + }, + }); + + YandexZen.renderWidget(config); + }); +} + +/** + * @param {!Window} global + * @param {string} id + */ +function createContainer(global, id) { + const d = global.document.createElement('div'); + d.id = id; + global.document.getElementById('c').appendChild(d); +} diff --git a/ads/zen.md b/ads/vendors/zen.md similarity index 89% rename from ads/zen.md rename to ads/vendors/zen.md index 2b2b78d5aa677..b568d0ee9cc0c 100644 --- a/ads/zen.md +++ b/ads/vendors/zen.md @@ -28,12 +28,12 @@ For details on the configuration semantics, please see [Zen's documentation](htt ### Required parameters -- `data-clid` +- `data-clid` ### Optional parameters -- `width` -- `data-size` -- `data-orientation` -- `data-on-render` -- `data-on-error` +- `width` +- `data-size` +- `data-orientation` +- `data-on-render` +- `data-on-error` diff --git a/ads/vendors/zergnet.js b/ads/vendors/zergnet.js new file mode 100644 index 0000000000000..b63ae3407ab6e --- /dev/null +++ b/ads/vendors/zergnet.js @@ -0,0 +1,27 @@ +/** + * Copyright 2016 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. + */ + +import {validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function zergnet(global, data) { + validateData(data, ['zergid'], []); + global.zergnetWidgetId = data.zergid; + writeScript(global, 'https://www.zergnet.com/zerg-amp.js'); +} diff --git a/ads/zergnet.md b/ads/vendors/zergnet.md similarity index 98% rename from ads/zergnet.md rename to ads/vendors/zergnet.md index 2e7cac91993d2..d0f73f768eee2 100644 --- a/ads/zergnet.md +++ b/ads/vendors/zergnet.md @@ -38,4 +38,4 @@ For details on the configuration semantics, please contact the ad network or ref Supported parameters: -- `data-zergid` +- `data-zergid` diff --git a/ads/vendors/zucks.js b/ads/vendors/zucks.js new file mode 100644 index 0000000000000..630e3a6dcbb06 --- /dev/null +++ b/ads/vendors/zucks.js @@ -0,0 +1,50 @@ +/** + * Copyright 2016 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. + */ + +import {loadScript, validateData, writeScript} from '../../3p/3p'; + +/** + * @param {!Window} global + * @param {!Object} data + */ +export function zucks(global, data) { + validateData(data, ['frameId']); + if (data['adtype'] === 'zoe') { + loadScript(global, 'https://j.zoe.zucks.net/zoe.min.js', function () { + const frameId = data['frameId']; + const elementId = 'zucks-widget-parent'; + + const d = global.document.createElement('ins'); + d.id = elementId; + global.document.getElementById('c').appendChild(d); + + if (data['zoeMultiAd'] !== 'true') { + (global.gZgokZoeQueue = global.gZgokZoeQueue || []).push({frameId}); + } + + (global.gZgokZoeWidgetQueue = global.gZgokZoeWidgetQueue || []).push({ + frameId, + parent: `#${elementId}`, + }); + }); + } else if (data['adtype'] === 'native') { + const s = global.document.createElement('script'); + s.src = `https://j.zucks.net.zimg.jp/n?f=${data['frameId']}`; + global.document.getElementById('c').appendChild(s); + } else { + writeScript(global, `https://j.zucks.net.zimg.jp/j?f=${data['frameId']}`); + } +} diff --git a/ads/zucks.md b/ads/vendors/zucks.md similarity index 98% rename from ads/zucks.md rename to ads/vendors/zucks.md index f722979815791..18c2bad6ff276 100644 --- a/ads/zucks.md +++ b/ads/vendors/zucks.md @@ -71,4 +71,4 @@ For more information, please [contact Zucks](https://zucks.co.jp/contact/). Supported parameters: -- `data-frame-id` +- `data-frame-id` diff --git a/ads/videointelligence.js b/ads/videointelligence.js deleted file mode 100644 index 8f0b1090b9184..0000000000000 --- a/ads/videointelligence.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2018 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function videointelligence(global, data) { - validateData(data, ['publisherId', 'channelId']); - - loadScript(global, 'https://s.vi-serve.com/tagLoaderAmp.js'); -} diff --git a/ads/videonow.js b/ads/videonow.js deleted file mode 100644 index 92cc8dd5a788f..0000000000000 --- a/ads/videonow.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {loadScript, validateData} from '../3p/3p'; -import {parseJson} from '../src/json'; -import {tryDecodeUriComponent} from '../src/url'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function videonow(global, data) { - const mandatoryAttributes = ['pid', 'width', 'height']; - const optionalAttributes = ['kind', 'src']; - - let customTag = ''; - let logLevel = null; - let vnModule = ''; - - if (global && global.name) { - const p = parseJson(global.name); - if ( - p && - p['attributes'] && - p['attributes']['_context'] && - p['attributes']['_context']['location'] && - p['attributes']['_context']['location']['href'] - ) { - const {href} = p['attributes']['_context']['location']; - const logLevelParsed = /[?&]vn_debug\b(?:=(\d+))?/.exec(href); - const vnModuleParsed = /vn_module=([^&]*)/.exec(href); - const customTagParsed = /vn_init_module=([^&]*)/.exec(href); - - logLevel = (logLevelParsed && logLevelParsed[1]) || null; - vnModule = (vnModuleParsed && vnModuleParsed[1]) || ''; - customTag = (customTagParsed && customTagParsed[1]) || ''; - } - } - validateData(data, mandatoryAttributes, optionalAttributes); - - const profileId = data.pid || 1; - - // production version by default - let script = - (customTag && tryDecodeUriComponent(customTag)) || - (data.src && tryDecodeUriComponent(data.src)) || - 'https://cdn.videonow.ru/vn_init_module.js'; - - script = addParam(script, 'amp', 1); - script = addParam(script, 'profileId', profileId); - if (logLevel !== null) { - script = addParam(script, 'vn_debug', String(logLevel)); - } - if (vnModule) { - script = addParam(script, 'vn_module', vnModule); - } - - loadScript(global, script); -} - -/** - * @param {string} script - * @param {string} name - * @param {string|number}value - * @return {string} - */ -function addParam(script, name, value) { - if (script.indexOf(name) < 0) { - script += (~script.indexOf('?') ? '&' : '?') + name + '=' + value; - } - return script; -} diff --git a/ads/viralize.js b/ads/viralize.js deleted file mode 100644 index 8e3af774c044d..0000000000000 --- a/ads/viralize.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {addParamsToUrl} from '../src/url'; -import {loadScript, validateData} from '../3p/3p'; -import {parseJson} from '../src/json'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function viralize(global, data) { - const endpoint = 'https://ads.viralize.tv/display/'; - const required = ['zid']; - const optional = ['extra']; - - validateData(data, required, optional); - - const defaultLocation = 'sel-#c>script'; - const pubPlatform = 'amp'; - - const queryParams = parseJson(data.extra || '{}'); - queryParams['zid'] = data.zid; - queryParams['pub_platform'] = pubPlatform; - if (!queryParams['location']) { - queryParams['location'] = defaultLocation; - } - if (!queryParams['u']) { - queryParams['u'] = global.context.sourceUrl; - } - - const scriptUrl = addParamsToUrl(endpoint, queryParams); - - loadScript( - global, - scriptUrl, - () => global.context.renderStart(), - () => global.context.noContentAvailable() - ); -} diff --git a/ads/vmfive.js b/ads/vmfive.js deleted file mode 100644 index b230b00f3d1cc..0000000000000 --- a/ads/vmfive.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function vmfive(global, data) { - /*eslint "google-camelcase/google-camelcase": 0*/ - const mandatory_fields = ['appKey', 'placementId', 'adType']; - const optional_fields = []; - - const {appKey, placementId, adType} = data; - - global._vmfive_amp = {appKey, placementId, adType}; - - validateData(data, mandatory_fields, optional_fields); - - createAdUnit(global, placementId, adType); - setupSDKReadyCallback(global, appKey); - parallelDownloadScriptsAndExecuteInOrder(global); -} - -/** - * @param {!Window} win - */ -function parallelDownloadScriptsAndExecuteInOrder(win) { - [ - 'https://vawpro.vm5apis.com/man.js', - 'https://man.vm5apis.com/dist/adn-web-sdk.js', - ].forEach(function (src) { - const script = document.createElement('script'); - script.src = src; - script.async = false; - win.document.head.appendChild(script); - }); -} - -/** - * @param {!Window} win - * @param {string} placementId - * @param {string} adType - */ -function createAdUnit(win, placementId, adType) { - const el = document.createElement('vmfive-ad-unit'); - el.setAttribute('placement-id', placementId); - el.setAttribute('ad-type', adType); - win.document.getElementById('c').appendChild(el); -} - -/** - * @param {!Window} win - * @param {string} appKey - */ -function setupSDKReadyCallback(win, appKey) { - win.onVM5AdSDKReady = (sdk) => sdk.init({appKey}); -} diff --git a/ads/webediads.js b/ads/webediads.js deleted file mode 100644 index c3942b607e3aa..0000000000000 --- a/ads/webediads.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * Created by Webedia on 07/03/16 - last updated on 11/10/16 - * @param {!Window} global - * @param {!Object} data - */ -export function webediads(global, data) { - validateData(data, ['site', 'page', 'position'], ['query']); - loadScript(global, 'https://eu1.wbdds.com/amp.min.js', () => { - global.wads.amp.init({ - 'site': data.site, - 'page': data.page, - 'position': data.position, - 'query': data.query ? data.query : '', - }); - }); -} diff --git a/ads/weborama.md b/ads/weborama.md deleted file mode 100644 index cbd3a9846016c..0000000000000 --- a/ads/weborama.md +++ /dev/null @@ -1,106 +0,0 @@ - - -# Weborama - -## Example - -**Display tag:** - -See below for an example of usage or our display tag, adapted for use with AMP websites: - -```html - -
Loading ad.
-
Ad could not be loaded.
-
-``` - -**Conversion tag:** - -In order to add conversion trackers to your page, please use the AMP pixel component that will be supplied to you by your Weborama contact. -The values mentioned between brackets `[]` should be replaced by the proper values. - -`DOCUMENT_REFERRER`, `SOURCE_URL` and `RANDOM` should remain in the URL, as AMP takes care of the automatic substitution of these values. - -```html - -``` - -## Configuration - -**Placeholder and fallback:** - -The placeholder and fallback `div` elements are completely optional and can be left out. - -- The placeholder is shown while the ad is loading. -- The fallback is served when there is no ad to show for some reason. - -**Dimensions:** - -By default the ad size is based on the `width` and `height` attributes of the `amp-ad` tag. These are listed as mandatory parameters. - -The AMP ad component requires the following HTML attributes to be added before the ad will be parsed as a Weborama ad: - -- width -- height -- type="weborama-display" - -**Mandatory data parameters:** - -Without valid values for these parameters we won't be able to display an ad: - -- `data-wbo_account_id` -- `data-wbo_tracking_element_id` -- `data-wbo_fullhost` - -**Examples of optional parameters:** - -Here are some extra parameters that might be set on the AMP ad: - -- `data-wbo_random` - Used as session identifier by some 3rd party ad systems -- `data-wbo_publisherclick` - Adds a publisher redirect for the exit click. -- `data-wbo_vars` - Sends variables to the creative. e.g.: `color=green&weather=rainy` -- `data-wbo_debug` - Launch the ad in debug mode. -- ... ask your contact at Weborama for more details. - -## Current restrictions - -- AMP ads are launched in a cross-origin iframe, so there currently is no support for some rich media, amongst which: - - Expandables - - Floorads, Interstitials and other types of layers - - Flash ads - - Creatives served over HTTP. -- Click and impression trackers can't be added to the tag. They need to be added through Weborama's ad platform: WCM. -- At the moment we don't support dynamic resizing of the iframe. Support for this will be added on request. - -## Support - -If you have any questions, please refer to your contact at Weborama. Otherwise you can contact our TIER 2 support: - -- E: tier2support@weborama.nl -- T: +31 (0) 20 52 46 690 diff --git a/ads/whopainfeed.js b/ads/whopainfeed.js deleted file mode 100644 index a202e786c3228..0000000000000 --- a/ads/whopainfeed.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function whopainfeed(global, data) { - validateData(data, ['siteid']); - - global._whopainfeed = global._whopainfeed || { - viewId: global.context.pageViewId, - siteId: data['siteid'], - testMode: data['testmode'] || 'false', - template: data['template'] || 'default', - }; - - loadScript(global, 'https://widget.infeed.com.ar/widget/widget-amp.js'); -} diff --git a/ads/widespace.js b/ads/widespace.js deleted file mode 100644 index 3ceeb6e2ea136..0000000000000 --- a/ads/widespace.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function widespace(global, data) { - const WS_AMP_CODE_VER = '1.0.1'; - // Optional demography parameters. - let demo = []; - - demo = ['Gender', 'Country', 'Region', 'City', 'Postal', 'Yob'].map((d) => { - return 'demo' + d; - }); - - validateData(data, ['sid'], demo); - - const url = - 'https://engine.widespace.com/map/engine/dynamic?isamp=1' + - '&ver=' + - WS_AMP_CODE_VER + - '&#sid=' + - encodeURIComponent(data.sid); - - writeScript(global, url); -} diff --git a/ads/wisteria.js b/ads/wisteria.js deleted file mode 100644 index a37282f909d8b..0000000000000 --- a/ads/wisteria.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function wisteria(global, data) { - const d = global.document.createElement('div'); - d.id = '_wisteria_recommend_contents'; - global.document.getElementById('c').appendChild(d); - //get canonical url - const originalUrl = global.context.canonicalUrl; - validateData(data, ['siteId', 'templateNumber']); - loadScript( - global, - 'https://wisteria-js.excite.co.jp/wisteria.js?site_id=' + - data['siteId'] + - '&template_no=' + - data['templateNumber'] + - '&original_url=' + - originalUrl - ); -} diff --git a/ads/wpmedia.js b/ads/wpmedia.js deleted file mode 100644 index 75a4f8630d5a4..0000000000000 --- a/ads/wpmedia.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function wpmedia(global, data) { - validateData(data, ['slot', 'bunch'], ['sn', 'slots']); - - // const url = 'http://localhost/wpjslib.js'; - const url = 'https://std.wpcdn.pl/wpjslib/wpjslib-amp.js'; - - writeScript(global, url, function () { - window.run(data); - }); -} diff --git a/ads/xlift.js b/ads/xlift.js deleted file mode 100644 index 9fd982df6a53c..0000000000000 --- a/ads/xlift.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function xlift(global, data) { - validateData(data, ['mediaid']); - - global.xliftParams = data; - const d = global.document.createElement('div'); - d.id = '_XL_recommend'; - global.document.getElementById('c').appendChild(d); - - d.addEventListener('SuccessLoadedXliftAd', function (e) { - e.detail = e.detail || {adSizeInfo: {}}; - global.context.renderStart(e.detail.adSizeInfo); - }); - d.addEventListener('FailureLoadedXliftAd', function () { - global.context.noContentAvailable(); - }); - - //assign XliftAmpHelper property to global(window) - global.XliftAmpHelper = null; - - loadScript( - global, - 'https://cdn.x-lift.jp/resources/common/xlift_amp.js', - () => { - if (!global.XliftAmpHelper) { - global.context.noContentAvailable(); - } else { - global.XliftAmpHelper.show(); - } - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/yahoo.js b/ads/yahoo.js deleted file mode 100644 index c427884f28aa9..0000000000000 --- a/ads/yahoo.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yahoo(global, data) { - validateData(data, ['sid', 'site', 'sa']); - global.yadData = data; - writeScript(global, 'https://s.yimg.com/aaq/ampad/display.js'); -} diff --git a/ads/yahoofedads.js b/ads/yahoofedads.js deleted file mode 100644 index 37e53b0c2a68f..0000000000000 --- a/ads/yahoofedads.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yahoofedads(global, data) { - validateData(data, [ - 'adUnit', - 'site', - 'region', - 'lang', - 'sa', - 'spaceId', - 'url', - ]); - - global.amp = true; - global.adConfig = { - 'adUnit': data.adUnit, - 'forceSource': data.forceSource, - 'lang': data.lang, - 'publisherUrl': data.url, - 'region': data.region, - 'sa': data.sa, - 'sectionId': data.sectionId, - 'site': data.site, - 'spaceId': data.spaceId, - }; - - loadScript( - global, - 'https://s.yimg.com/cv/apiv2/dy/fedads/fedads.mock.js', - () => global.context.renderStart() - ); -} diff --git a/ads/yahoofedads.md b/ads/yahoofedads.md deleted file mode 100644 index e435008ace2a7..0000000000000 --- a/ads/yahoofedads.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# Yahoo Native-Display Ads Federation - -## Example - -Yahoo Native-Display Ads Federation only requires a configured section code to run. Please work with your account manager to configure your AMP sections. - -### Basic - -```html - - -``` - -### Required parameters - -- `data-site`, `data-region`, `data-lang`: Unique site/region/lang code that represents your site -- `data-ad-unit`: The template used by picked Native AD -- `data-sa`: The site-attribute used by display AD to render -- `data-url` : Url the AD is hosted in for tracking purpose - -### Optional override parameters - -- `data-space-id`: Use this to override what space id need to be used on picking up Display AD -- `data-section-id`: Use this ot override what section id need to be used on picking up Native AD diff --git a/ads/yahoojp.js b/ads/yahoojp.js deleted file mode 100644 index a26a1c143c8e6..0000000000000 --- a/ads/yahoojp.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yahoojp(global, data) { - validateData(data, ['yadsid'], []); - global.yahoojpParam = data; - writeScript( - global, - 'https://s.yimg.jp/images/listing/tool/yads/ydn/amp/amp.js' - ); -} diff --git a/ads/yahoonativeads.js b/ads/yahoonativeads.js deleted file mode 100644 index 4d2c284d9ec07..0000000000000 --- a/ads/yahoonativeads.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2019 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yahoonativeads(global, data) { - validateData(data, ['key', 'code', 'url']); - - (global.native = global.native || []).push(data.code); - - global.apiKey = data.key; - global.publisherUrl = data.url; - global.amp = true; - - loadScript(global, 'https://s.yimg.com/dy/ads/native.js', () => - global.context.renderStart() - ); -} diff --git a/ads/yahoonativeads.md b/ads/yahoonativeads.md deleted file mode 100644 index 764eb536c7ddc..0000000000000 --- a/ads/yahoonativeads.md +++ /dev/null @@ -1,41 +0,0 @@ - - -# Yahoo Native Ads - -## Example - -Yahoo Native Ads only requires a configured section code to run. Please work with your account manager to configure your AMP sections. - -### Basic - -```html - - -``` - -### Required parameters - -- `data-code` : Unique section code that represents your site and placement -- `data-key` : Unique API key that was issued for your site -- `data-url` : Url that your API key and section code are valid to run on diff --git a/ads/yandex.js b/ads/yandex.js deleted file mode 100644 index 33bf5ced20ddf..0000000000000 --- a/ads/yandex.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright 2017 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. - */ -import {loadScript, validateData} from '../3p/3p'; - -const n = 'yandexContextAsyncCallbacks'; -const renderTo = 'yandex_rtb'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yandex(global, data) { - validateData(data, ['blockId'], ['data', 'onRender', 'onError']); - - addToQueue(global, data); - loadScript( - global, - 'https://yastatic.net/partner-code/loaders/context_amp.js' - ); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function addToQueue(global, data) { - global[n] = global[n] || []; - global[n].push(() => { - // Create container - createContainer(global, renderTo); - - // Show Ad in container - global.Ya.Context.AdvManager.render( - { - blockId: data.blockId, - statId: data.statId, - renderTo, - data: data.data, - async: true, - onRender: () => { - if (typeof data.onRender === 'function') { - data.onRender(); - } - global.context.renderStart(); - }, - }, - () => { - if (typeof data.onError === 'function') { - data.onError(); - } else { - global.context.noContentAvailable(); - } - } - ); - }); -} - -/** - * @param {!Window} global - * @param {string} id - */ -function createContainer(global, id) { - const d = global.document.createElement('div'); - d.id = id; - global.document.getElementById('c').appendChild(d); -} diff --git a/ads/yengo.js b/ads/yengo.js deleted file mode 100644 index ed4bd3651a14b..0000000000000 --- a/ads/yengo.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2017 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yengo(global, data) { - validateData(data, ['blockId']); - - const url = - 'https://code.yengo.com/data/' + - encodeURIComponent(data['blockId']) + - '.js?async=1&div=c'; - - loadScript( - global, - url, - () => { - global.context.renderStart(); - }, - () => { - global.context.noContentAvailable(); - } - ); -} diff --git a/ads/yieldbot.js b/ads/yieldbot.js deleted file mode 100644 index 69ebf23ed21eb..0000000000000 --- a/ads/yieldbot.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {getMultiSizeDimensions} from '../ads/google/utils'; -import {loadScript, validateData} from '../3p/3p'; -import {rethrowAsync, user} from '../src/log'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yieldbot(global, data) { - validateData(data, ['psn', 'ybSlot', 'slot']); - - global.ybotq = global.ybotq || []; - - loadScript(global, 'https://cdn.yldbt.com/js/yieldbot.intent.amp.js', () => { - global.ybotq.push(() => { - try { - const multiSizeDataStr = data.multiSize || null; - const primaryWidth = parseInt(data.overrideWidth || data.width, 10); - const primaryHeight = parseInt(data.overrideHeight || data.height, 10); - let dimensions; - - if (multiSizeDataStr) { - dimensions = getMultiSizeDimensions( - multiSizeDataStr, - primaryWidth, - primaryHeight, - false - ); - dimensions.unshift([primaryWidth, primaryHeight]); - } else { - dimensions = [[primaryWidth, primaryHeight]]; - } - - global.yieldbot.psn(data.psn); - global.yieldbot.enableAsync(); - if (window.context.isMaster) { - global.yieldbot.defineSlot(data.ybSlot, {sizes: dimensions}); - global.yieldbot.go(); - } else { - const slots = {}; - slots[data.ybSlot] = dimensions; - global.yieldbot.nextPageview(slots); - } - } catch (e) { - rethrowAsync(e); - } - }); - - global.ybotq.push(() => { - try { - const targeting = global.yieldbot.getSlotCriteria(data['ybSlot']); - data['targeting'] = data['targeting'] || {}; - for (const key in targeting) { - data.targeting[key] = targeting[key]; - } - } catch (e) { - rethrowAsync(e); - } - user().warn( - 'AMP-AD', - 'type="yieldbot" will no longer ' + - 'be supported starting on March 29, 2018.' + - ' Please use your amp-ad-network and RTC to configure a' + - ' Yieldbot callout vendor. Refer to' + - ' https://github.com/ampproject/amphtml/blob/master/' + - 'extensions/amp-a4a/rtc-publisher-implementation-guide.md' + - '#setting-up-rtc-config for more information.' - ); - }); - }); -} diff --git a/ads/yieldmo.js b/ads/yieldmo.js deleted file mode 100644 index 6cc66c83f1dec..0000000000000 --- a/ads/yieldmo.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yieldmo(global, data) { - const ymElem = global.document.createElement('div'); - ymElem.id = 'ym_' + data.ymid; - ymElem.className = 'ym'; - ymElem.dataset['ampEnabled'] = true; - global.document.getElementById('c').appendChild(ymElem); - - const swimLane = Math.round((5 * Math.random()) / 3); - const ymJs = 'https://static.yieldmo.com/ym.' + swimLane + '.js'; - - loadScript(global, ymJs); -} diff --git a/ads/yieldone.js b/ads/yieldone.js deleted file mode 100644 index 14349502ef1a9..0000000000000 --- a/ads/yieldone.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2015 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yieldone(global, data) { - validateData(data, ['pubid', 'pid', 'width', 'height'], []); - - global.yieldoneParam = data; - writeScript( - global, - 'https://img.ak.impact-ad.jp/ic/pone/commonjs/yone-amp.js' - ); -} diff --git a/ads/yieldpro.js b/ads/yieldpro.js deleted file mode 100644 index 92906fca39e26..0000000000000 --- a/ads/yieldpro.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {computeInMasterFrame, loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function yieldpro(global, data) { - validateData( - data, - ['sectionId', 'slot', 'pubnetwork'], - [ - 'instance', - 'custom', - 'adServerUrl', - 'cacheSafe', - 'pageIdModifier', - 'click3rd', - 'debugsrc', - ] - ); - //TODO support dmp and cookie - - const SCRIPT_HOST = 'creatives.yieldpro.eu/showad_'; - - let scriptUrl = 'https://' + SCRIPT_HOST + data['pubnetwork'] + '.js'; - - if (data['debugsrc']) { - scriptUrl = data['debugsrc']; - } - - computeInMasterFrame( - global, - 'yieldpro-request', - (done) => { - let success = false; - const masterWin = this; - if (!masterWin.showadAMPAdapter) { - masterWin.showadAMPAdapter = { - registerSlot: () => {}, - }; - loadScript(this, scriptUrl, () => { - if (masterWin.showadAMPAdapter.inited) { - success = true; - } - done(success); - }); - } else { - done(true); - } - }, - (success) => { - if (success) { - global.showadAMPAdapter = global.context.master.showadAMPAdapter; - global.showadAMPAdapter.registerSlot(data, global); - } else { - throw new Error('Yieldpro AdTag failed to load'); - } - } - ); -} diff --git a/ads/yieldpro.md b/ads/yieldpro.md deleted file mode 100644 index 6c3bed5cdfccc..0000000000000 --- a/ads/yieldpro.md +++ /dev/null @@ -1,100 +0,0 @@ - - -# YieldPro - -## Examples - -### Single ad - -```html - - -``` - -### Multi instance ads - -```html - - - - -``` - -### Using custom params and custom ad server url - -```html - - -``` - -## Configuration - -For details on the configuration semantics, please contact the ad network or refer to their documentation. - -### Required parameters - -- `sectionId`: ID of this section in ad server. -- `slot`: ID of ad slot in ad server. -- `pubnetwork`: ID of the publisher that in ad server. - -### Optional parameters - -- `instance`: ID of section instance in case we multiple times used the same section on the same page. Can contain only letters and numbers.Strictly required to use the same section multiple times per page. -- `click3rd`: 3rd party click watcher. -- `adServerUrl` -- `cacheSafe` -- `pageIdModifier` -- `custom`: Custom targeting properties. You may use 3 types for its properties: `{String}`, `{Number}` and `{Array}`. The following array usage example translates into: `arrayKey=value1&arrayKey=1&stringKey=stringValue...` - - ```text - { - arrayKey: [ "value1", 1 ], - stringKey: 'stringValue' - } - ``` diff --git a/ads/zedo.js b/ads/zedo.js deleted file mode 100644 index 3a846e02e6631..0000000000000 --- a/ads/zedo.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function zedo(global, data) { - // check mandatory fields - validateData( - data, - ['superId', 'network', 'placementId', 'channel', 'publisher', 'dim'], - ['charset', 'callback', 'renderer'] - ); - - loadScript(global, 'https://ss3.zedo.com/gecko/tag/Gecko.amp.min.js', () => { - const {ZGTag} = global; - const charset = data.charset || ''; - const callback = data.callback || function () {}; - const geckoTag = new ZGTag( - data.superId, - data.network, - '', - '', - charset, - callback - ); - geckoTag.setAMP(); - // define placement - const placement = geckoTag.addPlacement( - data.placementId, - data.channel, - data.publisher, - data.dim, - data.width, - data.height - ); - if (data.renderer) { - for (const key in data.renderer) { - placement.includeRenderer( - data.renderer[key].name, - data.renderer[key].value - ); - } - } else { - placement.includeRenderer('display', {}); - } - //create a slot div to display ad - const slot = global.document.createElement('div'); - slot.id = 'zdt_' + data.placementId; - - const divContainer = global.document.getElementById('c'); - if (divContainer) { - divContainer.appendChild(slot); - } - - // call load ads - geckoTag.loadAds(); - - // call div ready - geckoTag.placementReady(data.placementId); - }); -} diff --git a/ads/zen.js b/ads/zen.js deleted file mode 100644 index 0ea812b3dc676..0000000000000 --- a/ads/zen.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2017 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. - */ -import {loadScript, validateData} from '../3p/3p'; - -const n = 'yandexZenAsyncCallbacks'; -const renderTo = 'zen-widget'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function zen(global, data) { - validateData( - data, - ['clid'], - ['size', 'orientation', 'successCallback', 'failCallback'] - ); - - addToQueue(global, data); - loadScript(global, 'https://zen.yandex.ru/widget-loader'); -} - -/** - * @param {!Window} global - * @param {!Object} data - */ -function addToQueue(global, data) { - global[n] = global[n] || []; - global[n].push(() => { - // Create container - createContainer(global, renderTo); - - const {YandexZen} = global; - const config = Object.assign(data, { - clid: JSON.parse(data.clid), - container: `#${renderTo}`, - isAMP: true, - successCallback: () => { - if (typeof data.successCallback === 'function') { - data.successCallback(); - } - }, - failCallback: () => { - if (typeof data.failCallback === 'function') { - data.failCallback(); - } - }, - }); - - YandexZen.renderWidget(config); - }); -} - -/** - * @param {!Window} global - * @param {string} id - */ -function createContainer(global, id) { - const d = global.document.createElement('div'); - d.id = id; - global.document.getElementById('c').appendChild(d); -} diff --git a/ads/zergnet.js b/ads/zergnet.js deleted file mode 100644 index 10477ac43c42f..0000000000000 --- a/ads/zergnet.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function zergnet(global, data) { - validateData(data, ['zergid'], []); - global.zergnetWidgetId = data.zergid; - writeScript(global, 'https://www.zergnet.com/zerg-amp.js'); -} diff --git a/ads/zucks.js b/ads/zucks.js deleted file mode 100644 index 57b25b02e8cf0..0000000000000 --- a/ads/zucks.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2016 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. - */ - -import {loadScript, validateData, writeScript} from '../3p/3p'; - -/** - * @param {!Window} global - * @param {!Object} data - */ -export function zucks(global, data) { - validateData(data, ['frameId']); - if (data['adtype'] === 'zoe') { - loadScript(global, 'https://j.zoe.zucks.net/zoe.min.js', function () { - const frameId = data['frameId']; - const elementId = 'zucks-widget-parent'; - - const d = global.document.createElement('ins'); - d.id = elementId; - global.document.getElementById('c').appendChild(d); - - if (data['zoeMultiAd'] !== 'true') { - (global.gZgokZoeQueue = global.gZgokZoeQueue || []).push({frameId}); - } - - (global.gZgokZoeWidgetQueue = global.gZgokZoeWidgetQueue || []).push({ - frameId, - parent: `#${elementId}`, - }); - }); - } else if (data['adtype'] === 'native') { - const s = global.document.createElement('script'); - s.src = `https://j.zucks.net.zimg.jp/n?f=${data['frameId']}`; - global.document.getElementById('c').appendChild(s); - } else { - writeScript(global, `https://j.zucks.net.zimg.jp/j?f=${data['frameId']}`); - } -} diff --git a/amp.js b/amp.js new file mode 100755 index 0000000000000..727c5fae82e77 --- /dev/null +++ b/amp.js @@ -0,0 +1,92 @@ +#!/usr/bin/env node +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { + createTask, + finalizeRunner, +} = require('./build-system/task-runner/amp-task-runner'); + +/** + * All the AMP tasks. Keep this list alphabetized. + * + * The three params used below are: + * 1. Name of the amp task to be invoked. + * E.g. amp default + * 2. Name of the function in the source file. + * E.g. defaultTask() + * If not specified, this is assumed to be the same as the task name. + * 3. Basename of the source file in build-system/tasks/. + * E.g. build-system/tasks/default-task.js + * If not specified, this is assumed to be the same as the task name. + */ +createTask('analytics-vendor-configs', 'analyticsVendorConfigs'); +createTask('ava'); +createTask('babel-plugin-tests', 'babelPluginTests'); +createTask('build'); +createTask('bundle-size', 'bundleSize'); +createTask('caches-json', 'cachesJson'); +createTask('check-analytics-vendors-list', 'checkAnalyticsVendorsList'); +createTask('check-asserts', 'checkAsserts'); +createTask('check-build-system', 'checkBuildSystem'); +createTask('check-exact-versions', 'checkExactVersions'); +createTask('check-invalid-whitespaces', 'checkInvalidWhitespaces'); +createTask('check-links', 'checkLinks'); +createTask('check-owners', 'checkOwners'); +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('coverage-map', 'coverageMap'); +createTask('css'); +createTask('default', 'defaultTask', 'default-task'); +createTask('dep-check', 'depCheck'); +createTask('dev-dashboard-tests', 'devDashboardTests'); +createTask('dist'); +createTask('e2e'); +createTask('firebase'); +createTask('get-zindex', 'getZindex'); +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'); +createTask('prettify'); +createTask('release'); +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'); +createTask('validator-cpp', 'validatorCpp', 'validator'); +createTask('validator-webui', 'validatorWebui', 'validator'); +createTask('visual-diff', 'visualDiff'); + +/** + * Finalize the task runner after all tasks have been created. + */ +finalizeRunner(); diff --git a/babel.config.js b/babel.config.js index 37f85457a5a31..1d0e4f29d037e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -25,31 +25,27 @@ 'use strict'; -const log = require('fancy-log'); -const { - getDepCheckConfig, - getPostClosureConfig, - getPreClosureConfig, - getTestConfig, - getUnminifiedConfig, -} = require('./build-system/babel-config'); -const {cyan, yellow} = require('ansi-colors'); +const {cyan, yellow} = require('./build-system/common/colors'); +const {log} = require('./build-system/common/logging'); /** - * Mapping of babel transform callers to their corresponding babel configs. + * Mapping of each babel transform caller to the name of the function that + * returns its config. */ const babelTransforms = new Map([ - ['babel-jest', {}], - ['dep-check', getDepCheckConfig()], - ['post-closure', getPostClosureConfig()], - ['pre-closure', getPreClosureConfig()], - ['test', getTestConfig()], - ['unminified', getUnminifiedConfig()], + ['babel-jest', 'getEmptyConfig'], + ['post-closure', 'getPostClosureConfig'], + ['pre-closure', 'getPreClosureConfig'], + ['test', 'getTestConfig'], + ['unminified', 'getUnminifiedConfig'], + ['minified', 'getMinifiedConfig'], + ['@babel/eslint-parser', 'getEslintConfig'], ]); /** - * Main entry point. Returns babel config corresponding to the caller, or a - * blank config if the caller is unrecognized. + * Main entry point. Returns babel config corresponding to the caller, or an + * empty object if the caller is unrecognized. Configs are lazy-required when + * requested so we don't unnecessarily compute the entire set for all callers. * * @param {!Object} api * @return {!Object} @@ -59,7 +55,8 @@ module.exports = function (api) { return callerObj ? callerObj.name : ''; }); if (callerName && babelTransforms.has(callerName)) { - return babelTransforms.get(callerName); + const configFunctionName = babelTransforms.get(callerName); + return require('./build-system/babel-config')[configFunctionName](); } else { log( yellow('WARNING:'), diff --git a/build-system/.eslintrc.js b/build-system/.eslintrc.js index 4916b987de2ce..c518208ea589a 100644 --- a/build-system/.eslintrc.js +++ b/build-system/.eslintrc.js @@ -39,14 +39,13 @@ module.exports = { 'local/no-bigint': 0, 'local/no-dynamic-import': 0, 'local/no-export-side-effect': 0, - 'local/no-for-of-statement': 0, 'local/no-function-async': 0, 'local/no-function-generator': 0, 'local/no-has-own-property-method': 0, 'local/no-import-meta': 0, + 'local/no-invalid-this': 0, 'local/no-module-exports': 0, 'local/no-rest': 0, 'local/no-spread': 0, - 'require-jsdoc': 0, }, }; diff --git a/build-system/OWNERS b/build-system/OWNERS index 32c5ea6d75438..48bef26a3d1fc 100644 --- a/build-system/OWNERS +++ b/build-system/OWNERS @@ -1,10 +1,18 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [{name: 'ampproject/wg-infra'}], }, + { + pattern: '{package.json,package-lock.json}', + owners: [{name: 'ampproject/wg-infra', requestReviews: false}], + }, + { + pattern: 'tsconfig.json', + owners: [{name: 'rileyajones', notify: true}], + }, ], } diff --git a/build-system/babel-config/OWNERS b/build-system/babel-config/OWNERS index f254838b68bcc..efc9b448a63bf 100644 --- a/build-system/babel-config/OWNERS +++ b/build-system/babel-config/OWNERS @@ -1,12 +1,11 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [ {name: 'ampproject/wg-infra'}, - {name: 'ampproject/wg-runtime'}, {name: 'ampproject/wg-performance'}, ], }, diff --git a/build-system/babel-config/dep-check-config.js b/build-system/babel-config/dep-check-config.js deleted file mode 100644 index 2dc015349f118..0000000000000 --- a/build-system/babel-config/dep-check-config.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright 2020 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. - */ -'use strict'; - -/** - * Gets the config for babel transforms run during `gulp dep-check`. - * - * @return {!Object} - */ -function getDepCheckConfig() { - const reactJsxPlugin = [ - '@babel/plugin-transform-react-jsx', - { - pragma: 'Preact.createElement', - pragmaFrag: 'Preact.Fragment', - useSpread: true, - }, - ]; - const presetEnv = [ - '@babel/preset-env', - { - bugfixes: true, - modules: 'commonjs', - loose: true, - targets: {'browsers': ['Last 2 versions']}, - }, - ]; - const depCheckPlugins = [ - './build-system/babel-plugins/babel-plugin-transform-fix-leading-comments', - './build-system/babel-plugins/babel-plugin-transform-promise-resolve', - '@babel/plugin-transform-react-constant-elements', - '@babel/plugin-transform-classes', - reactJsxPlugin, - ]; - const depCheckPresets = [presetEnv]; - return { - compact: false, - plugins: depCheckPlugins, - presets: depCheckPresets, - }; -} - -module.exports = { - getDepCheckConfig, -}; diff --git a/build-system/babel-config/empty-config.js b/build-system/babel-config/empty-config.js new file mode 100644 index 0000000000000..37e542bc8514f --- /dev/null +++ b/build-system/babel-config/empty-config.js @@ -0,0 +1,27 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * Helper that creates an empty babel config object. + * + * @return {!Object} + */ +const getEmptyConfig = () => ({}); + +module.exports = { + getEmptyConfig, +}; diff --git a/build-system/babel-config/eslint-config.js b/build-system/babel-config/eslint-config.js new file mode 100644 index 0000000000000..aec6069dad16f --- /dev/null +++ b/build-system/babel-config/eslint-config.js @@ -0,0 +1,55 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * Gets the config for babel transforms run during `amp lint`. + * + * @return {!Object} + */ +function getEslintConfig() { + const presetEnv = [ + '@babel/preset-env', + { + shippedProposals: true, + modules: false, + targets: {esmodules: true}, + }, + ]; + + return { + compact: false, + presets: [presetEnv], + plugins: [enableSyntax], + }; +} + +/** + * @return {{ + * manipulateOptions: {Function(_opts: *, parserOpts: *): void} + * }} + */ +function enableSyntax() { + return { + manipulateOptions(_opts, parserOpts) { + parserOpts.plugins.push('jsx', 'importAssertions'); + }, + }; +} + +module.exports = { + getEslintConfig, +}; diff --git a/build-system/babel-config/helpers.js b/build-system/babel-config/helpers.js index 1229d98192331..bc7c3ded89f30 100644 --- a/build-system/babel-config/helpers.js +++ b/build-system/babel-config/helpers.js @@ -39,7 +39,7 @@ function getExperimentConstant() { } /** - * Computes options for the minify-replace plugin + * Computes options for minify-replace and returns the plugin object. * * @return {Array} */ @@ -56,7 +56,14 @@ function getReplacePlugin() { }; } - const replacements = [createReplacement('IS_ESM', argv.esm)]; + // We build on the idea that SxG is an upgrade to the ESM build. + // Therefore, all conditions set by ESM will also hold for SxG. + // However, we will also need to introduce a separate IS_SxG flag + // for conditions only true for SxG. + const replacements = [ + createReplacement('IS_ESM', argv.esm || argv.sxg), + createReplacement('IS_SXG', argv.sxg), + ]; const experimentConstant = getExperimentConstant(); if (experimentConstant) { @@ -91,7 +98,47 @@ function getReplacePlugin() { return ['minify-replace', {replacements}]; } +/** + * Returns a Babel plugin that replaces the global identifier with the correct + * alternative. Used before transforming test code with esbuild. + * + * @return {Array} + */ +function getReplaceGlobalsPlugin() { + return [ + (babel) => { + const {types: t} = babel; + return { + visitor: { + ReferencedIdentifier(path) { + const {node, scope} = path; + if (node.name !== 'global') { + return; + } + if (scope.getBinding('global')) { + return; + } + const possibleNames = ['globalThis', 'self']; + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis#browser_compatibility + if (argv.ie) { + possibleNames.shift(); + } + const name = possibleNames.find((name) => !scope.getBinding(name)); + if (!name) { + throw path.buildCodeFrameError( + 'Could not replace `global` with globalThis identifier' + ); + } + path.replaceWith(t.identifier(name)); + }, + }, + }; + }, + ]; +} + module.exports = { getExperimentConstant, getReplacePlugin, + getReplaceGlobalsPlugin, }; diff --git a/build-system/babel-config/minified-config.js b/build-system/babel-config/minified-config.js new file mode 100644 index 0000000000000..cde17d0828615 --- /dev/null +++ b/build-system/babel-config/minified-config.js @@ -0,0 +1,98 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS-IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const argv = require('minimist')(process.argv.slice(2)); +const {getReplacePlugin} = require('./helpers'); + +/** + * Gets the config for minified babel transforms run, used by 3p vendors. + * + * @return {!Object} + */ +function getMinifiedConfig() { + const replacePlugin = getReplacePlugin(); + const reactJsxPlugin = [ + '@babel/plugin-transform-react-jsx', + { + pragma: 'Preact.createElement', + pragmaFrag: 'Preact.Fragment', + useSpread: true, + }, + ]; + + const plugins = [ + 'optimize-objstr', + './build-system/babel-plugins/babel-plugin-transform-fix-leading-comments', + './build-system/babel-plugins/babel-plugin-transform-promise-resolve', + '@babel/plugin-transform-react-constant-elements', + reactJsxPlugin, + argv.esm + ? './build-system/babel-plugins/babel-plugin-transform-dev-methods' + : null, + [ + './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', + {freeze: false}, + ], + './build-system/babel-plugins/babel-plugin-is_minified-constant-transformer', + './build-system/babel-plugins/babel-plugin-transform-html-template', + './build-system/babel-plugins/babel-plugin-transform-jss', + './build-system/babel-plugins/babel-plugin-transform-version-call', + './build-system/babel-plugins/babel-plugin-transform-simple-array-destructure', + './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, + argv.fortesting + ? null + : './build-system/babel-plugins/babel-plugin-transform-json-configuration', + argv.fortesting + ? null + : [ + './build-system/babel-plugins/babel-plugin-amp-mode-transformer', + {isEsmBuild: !!argv.esm}, + ], + argv.fortesting + ? null + : './build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer', + ].filter(Boolean); + const presetEnv = [ + '@babel/preset-env', + { + bugfixes: true, + modules: false, + targets: argv.esm ? {esmodules: true} : {ie: 11, chrome: 41}, + }, + ]; + return { + compact: false, + plugins, + presets: [presetEnv], + retainLines: true, + }; +} + +module.exports = { + getMinifiedConfig, +}; diff --git a/build-system/babel-config/post-closure-config.js b/build-system/babel-config/post-closure-config.js index 7e88a437c3033..73b0e9a2e453d 100644 --- a/build-system/babel-config/post-closure-config.js +++ b/build-system/babel-config/post-closure-config.js @@ -18,12 +18,12 @@ const argv = require('minimist')(process.argv.slice(2)); /** - * Gets the config for post-closure babel transforms run during `gulp dist --esm`. + * Gets the config for post-closure babel transforms run during `amp dist --esm`. * * @return {!Object} */ function getPostClosureConfig() { - if (!argv.esm) { + if (!argv.esm && !argv.sxg) { return {}; } diff --git a/build-system/babel-config/pre-closure-config.js b/build-system/babel-config/pre-closure-config.js index c1b7bbd0e1c38..7ef22c1ce8ff0 100644 --- a/build-system/babel-config/pre-closure-config.js +++ b/build-system/babel-config/pre-closure-config.js @@ -16,10 +16,10 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const {getExperimentConstant, getReplacePlugin} = require('./helpers'); +const {getReplacePlugin} = require('./helpers'); /** - * Gets the config for pre-closure babel transforms run during `gulp dist`. + * Gets the config for pre-closure babel transforms run during `amp dist`. * * @return {!Object} */ @@ -29,37 +29,6 @@ function getPreClosureConfig() { const isTestTask = testTasks.some((task) => argv._.includes(task)); const isFortesting = argv.fortesting || isTestTask; - // For experiment, remove FixedLayer import from v0.js, otherwise remove - // from amp-viewer-integration - const fixedLayerImport = - getExperimentConstant() == 'MOVE_FIXED_LAYER' - ? './../fixed-layer' - : '../../../src/service/fixed-layer'; - const filterImportsPlugin = [ - 'filter-imports', - { - imports: { - // Imports removed for all ESM builds. - './polyfills/document-contains': ['installDocContains'], - './polyfills/domtokenlist': ['installDOMTokenList'], - './polyfills/fetch': ['installFetch'], - './polyfills/math-sign': ['installMathSign'], - './polyfills/object-assign': ['installObjectAssign'], - './polyfills/object-values': ['installObjectValues'], - './polyfills/promise': ['installPromise'], - './polyfills/array-includes': ['installArrayIncludes'], - './ie-media-bug': ['ieMediaCheckAndFix'], - '../third_party/css-escape/css-escape': ['cssEscape'], - // Imports that are not needed for valid transformed documents. - '../build/ampshared.css': ['cssText', 'ampSharedCss'], - '../build/ampdoc.css': ['cssText', 'ampDocCss'], - // Srcset fallbacks aren't needed in ESM builds - '../src/utils/img': ['guaranteeSrcForSrcsetUnsupportedBrowsers'], - // Used by experiment - [fixedLayerImport]: ['FixedLayer'], - }, - }, - ]; const reactJsxPlugin = [ '@babel/plugin-transform-react-jsx', { @@ -70,30 +39,36 @@ function getPreClosureConfig() { ]; const replacePlugin = getReplacePlugin(); const preClosurePlugins = [ + 'optimize-objstr', + argv.coverage ? 'babel-plugin-istanbul' : null, './build-system/babel-plugins/babel-plugin-transform-fix-leading-comments', './build-system/babel-plugins/babel-plugin-transform-promise-resolve', '@babel/plugin-transform-react-constant-elements', reactJsxPlugin, - './build-system/babel-plugins/babel-plugin-transform-inline-configure-component', - // TODO(alanorozco): Remove `replaceCallArguments` once serving infra is up. - argv.esm + 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', + {freeze: false}, + ], './build-system/babel-plugins/babel-plugin-is_minified-constant-transformer', './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-version-call', './build-system/babel-plugins/babel-plugin-transform-simple-array-destructure', + './build-system/babel-plugins/babel-plugin-transform-default-assignment', replacePlugin, './build-system/babel-plugins/babel-plugin-transform-amp-asserts', - argv.esm ? filterImportsPlugin : null, // TODO(erwinm, #28698): fix this in fixit week - //argv.esm + // argv.esm //? './build-system/babel-plugins/babel-plugin-transform-function-declarations' //: null, !isCheckTypes @@ -106,7 +81,7 @@ function getPreClosureConfig() { ] : null, !(isFortesting || isCheckTypes) - ? './build-system/babel-plugins/babel-plugin-is_dev-constant-transformer' + ? './build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer' : null, ].filter(Boolean); const presetEnv = [ @@ -117,12 +92,13 @@ function getPreClosureConfig() { targets: {esmodules: true}, }, ]; - const preClosurePresets = argv.esm ? [presetEnv] : []; + const preClosurePresets = argv.esm || argv.sxg ? [presetEnv] : []; const preClosureConfig = { compact: false, plugins: preClosurePlugins, presets: preClosurePresets, retainLines: true, + sourceMaps: true, }; return preClosureConfig; } diff --git a/build-system/babel-config/test-config.js b/build-system/babel-config/test-config.js index fd19a8b007edc..9af75f4871b15 100644 --- a/build-system/babel-config/test-config.js +++ b/build-system/babel-config/test-config.js @@ -16,10 +16,10 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const {getReplacePlugin} = require('./helpers'); +const {getReplaceGlobalsPlugin, getReplacePlugin} = require('./helpers'); /** - * Gets the config for babel transforms run during `gulp [unit|integration]`. + * Gets the config for babel transforms run during `amp [unit|integration|e2e]`. * * @return {!Object} */ @@ -55,11 +55,15 @@ function getTestConfig() { }, ]; const replacePlugin = getReplacePlugin(); + const replaceGlobalsPlugin = getReplaceGlobalsPlugin(); const testPlugins = [ argv.coverage ? instanbulPlugin : null, replacePlugin, + replaceGlobalsPlugin, + './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-fix-leading-comments', + './build-system/babel-plugins/babel-plugin-transform-jss', './build-system/babel-plugins/babel-plugin-transform-promise-resolve', '@babel/plugin-transform-react-constant-elements', '@babel/plugin-transform-classes', @@ -68,12 +72,9 @@ function getTestConfig() { const testPresets = [presetEnv]; return { compact: false, - // We do not want to ignore our dependencies in test mode, since some may - // be using newer features that need to be transpiled down for our test - // browsers. - ignore: [], plugins: testPlugins, presets: testPresets, + sourceMaps: 'inline', }; } diff --git a/build-system/babel-config/unminified-config.js b/build-system/babel-config/unminified-config.js index 1ef3ed2755192..2c7a40aa5f615 100644 --- a/build-system/babel-config/unminified-config.js +++ b/build-system/babel-config/unminified-config.js @@ -15,10 +15,14 @@ */ 'use strict'; +const argv = require('minimist')(process.argv.slice(2)); +const { + VERSION: internalRuntimeVersion, +} = require('../compile/internal-version'); const {getReplacePlugin} = require('./helpers'); /** - * Gets the config for babel transforms run during `gulp build`. + * Gets the config for babel transforms run during `amp build`. * * @return {!Object} */ @@ -31,30 +35,41 @@ function getUnminifiedConfig() { useSpread: true, }, ]; + + const targets = + argv.esm || argv.sxg ? {esmodules: true} : {browsers: ['Last 2 versions']}; const presetEnv = [ '@babel/preset-env', { bugfixes: true, - modules: 'commonjs', + modules: false, loose: true, - targets: {'browsers': ['Last 2 versions']}, + targets, }, ]; const replacePlugin = getReplacePlugin(); const unminifiedPlugins = [ + argv.coverage ? 'babel-plugin-istanbul' : null, replacePlugin, + './build-system/babel-plugins/babel-plugin-transform-json-import', + [ + './build-system/babel-plugins/babel-plugin-transform-internal-version', + {version: internalRuntimeVersion}, + ], './build-system/babel-plugins/babel-plugin-transform-json-configuration', + './build-system/babel-plugins/babel-plugin-transform-jss', './build-system/babel-plugins/babel-plugin-transform-fix-leading-comments', './build-system/babel-plugins/babel-plugin-transform-promise-resolve', '@babel/plugin-transform-react-constant-elements', '@babel/plugin-transform-classes', reactJsxPlugin, - ]; + ].filter(Boolean); const unminifiedPresets = [presetEnv]; return { compact: false, plugins: unminifiedPlugins, presets: unminifiedPresets, + sourceMaps: 'inline', }; } diff --git a/build-system/babel-plugins/OWNERS b/build-system/babel-plugins/OWNERS index 81b36bd711d79..eeccea4248aa8 100644 --- a/build-system/babel-plugins/OWNERS +++ b/build-system/babel-plugins/OWNERS @@ -1,12 +1,11 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [ {name: 'ampproject/wg-infra'}, - {name: 'ampproject/wg-runtime'}, {name: 'ampproject/wg-performance'}, {name: 'erwinmombay', notify: true}, {name: 'jridgewell', notify: true}, 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 c9040fea662e6..d686ef9c71315 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 @@ -19,7 +19,7 @@ * and getMode().localDev to true. * @param {Object} babelTypes */ -const {resolve, dirname} = require('path'); +const {dirname, join, relative, resolve} = require('path').posix; let shouldResolveDevelopmentMode = true; @@ -35,17 +35,17 @@ module.exports = function ({types: t}) { }, visitor: { ImportDeclaration({node}, state) { - const {specifiers, source} = node; + const {source, specifiers} = node; if (!source.value.endsWith('/mode')) { return; } specifiers.forEach((specifier) => { if (specifier.imported && specifier.imported.name === 'getMode') { - const filepath = resolve( - dirname(state.file.opts.filename), - source.value + const filepath = relative( + join(__dirname, '../../../'), + resolve(dirname(state.file.opts.filename), source.value) ); - if (filepath.endsWith('/amphtml/src/mode')) { + if (filepath.endsWith('src/mode')) { getModeFound = true; } } diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js deleted file mode 100644 index 2e5638b6d1aa0..0000000000000 --- a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/index.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2018 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. - */ - -/** - * Changes the values of IS_DEV to false and IS_MINIFIED to true. - * The above said variables are in src/mode.js file. - * @param {{types: string}} options - * @return {!Object} - */ -module.exports = function ({types: t}) { - return { - visitor: { - VariableDeclarator(path) { - const {node} = path; - const {id, init} = node; - if ( - t.isIdentifier(id, {name: 'IS_DEV'}) && - t.isBooleanLiteral(init, {value: true}) - ) { - node.init = t.booleanLiteral(false); - } - }, - }, - }; -}; diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/input.js b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/input.js deleted file mode 100644 index 21f1ed57dc773..0000000000000 --- a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/input.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright 2018 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. - */ - - -const IS_DEV = true; -const IS_EXPANDED = false; diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/output.js b/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/output.js deleted file mode 100644 index 9319a005fcae9..0000000000000 --- a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/output.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright 2018 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. - */ -const IS_DEV = false; -const IS_EXPANDED = false; diff --git a/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/index.js new file mode 100644 index 0000000000000..f2912031a0860 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/index.js @@ -0,0 +1,38 @@ +/** + * Copyright 2018 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. + */ + +/** + * Changes the value of IS_FORTESTING to false. + * The above variable is in src/mode.js. + * @param {{types: string}} options + * @return {!Object} + */ +module.exports = function ({types: t}) { + return { + visitor: { + VariableDeclarator(path) { + const {node} = path; + const {id, init} = node; + if ( + t.isIdentifier(id, {name: 'IS_FORTESTING'}) && + t.isBooleanLiteral(init, {value: true}) + ) { + node.init = t.booleanLiteral(false); + } + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/input.js b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/input.js new file mode 100644 index 0000000000000..7eafb7e9bc9c8 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/input.js @@ -0,0 +1,19 @@ +/** + * Copyright 2018 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. + */ + + +const IS_FORTESTING = true; +const IS_EXPANDED = false; diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/options.json b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/options.json similarity index 100% rename from build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/isdev-transform/options.json rename to build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/options.json diff --git a/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/output.js b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/output.js new file mode 100644 index 0000000000000..addce9217cc80 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/isdev-transform/output.js @@ -0,0 +1,17 @@ +/** + * Copyright 2018 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. + */ +const IS_FORTESTING = false; +const IS_EXPANDED = false; diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/no-transform/input.js b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/no-transform/input.js similarity index 100% rename from build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/no-transform/input.js rename to build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/no-transform/input.js diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/no-transform/options.json b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/no-transform/options.json similarity index 100% rename from build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/no-transform/options.json rename to build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/no-transform/options.json diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/no-transform/output.js b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/no-transform/output.js similarity index 100% rename from build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/fixtures/transform-assertions/no-transform/output.js rename to build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/fixtures/transform-assertions/no-transform/output.js diff --git a/build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/index.js b/build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/index.js similarity index 100% rename from build-system/babel-plugins/babel-plugin-is_dev-constant-transformer/test/index.js rename to build-system/babel-plugins/babel-plugin-is_fortesting-constant-transformer/test/index.js diff --git a/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js b/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js index 0e2beb39bbb1c..644f604ceaaf5 100644 --- a/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js +++ b/build-system/babel-plugins/babel-plugin-is_minified-constant-transformer/index.js @@ -15,8 +15,8 @@ */ /** - * Changes the values of IS_DEV to false and IS_MINIFIED to true. - * The above said variables are in src/mode.js file. + * Changes the value of IS_MINIFIED to true. + * The above variable is in src/mode.js and src/core/minified-mode.js. * @param {Object} babelTypes * @return {!Object} */ diff --git a/build-system/babel-plugins/babel-plugin-transform-annotation-to-extern/index.js b/build-system/babel-plugins/babel-plugin-transform-annotation-to-extern/index.js index 0ac66e70a3f17..10301ab9f0fbb 100644 --- a/build-system/babel-plugins/babel-plugin-transform-annotation-to-extern/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-annotation-to-extern/index.js @@ -43,7 +43,7 @@ module.exports = function (babel) { let shouldEmitTypedefs; return { pre() { - const {writeToFile = false, emitTypedefs = false} = this.opts; + const {emitTypedefs = false, writeToFile = false} = this.opts; shouldWriteToFile = writeToFile; shouldEmitTypedefs = emitTypedefs; }, diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/index.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/index.js new file mode 100644 index 0000000000000..9434311e73a1b --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/index.js @@ -0,0 +1,100 @@ +/** + * Copyright 2020 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. + */ + +/** + * Injects alias identifiers for values with default assignments. + * Closure can't correctly type-narrow the defaulted value to exclude `undefined`, + * but it can figure out the aliased value correctly. + * + * @example + * In: + * ``` + * function foo(foo = 1, { bar = 2 }) { + * foo; + * bar; + * } + * ``` + * + * Out: + * ``` + * function foo(foo = 1, { bar = 2 }) { + * let _foo = foo; + * let _bar = bar; + * _foo; + * _bar; + * } + * ``` + */ + +module.exports = function ({types: t}) { + return { + visitor: { + AssignmentPattern(path) { + const left = path.get('left'); + if (!left.isIdentifier()) { + throw left.buildCodeFrameError( + [ + 'Can only fix default assignment type of identifiers.', + 'Please replace with a parameter, and destructure in the function body.', + ].join('\n\t') + ); + } + + const root = path.find( + ({parentPath}) => parentPath.isStatement() || parentPath.isFunction() + ); + if (!root.parentPath.isFunction()) { + // only transform param destructures + return; + } + + const {scope} = path; + const {name} = left.node; + const newName = scope.generateUid(name); + + const {referencePaths} = scope.getBinding(name); + const ancestry = path.getAncestry().slice().reverse(); + for (const ref of referencePaths) { + const refAncestry = ref.getAncestry().slice().reverse(); + + const min = Math.min(ancestry.length, refAncestry.length); + for (let i = 0; i < min; i++) { + const a = ancestry[i]; + const r = refAncestry[i]; + if (a.container !== r.container) { + break; + } + if (a === root) { + throw ref.buildCodeFrameError( + 'default assignment of default assignment' + ); + } + } + } + + scope.rename(name, newName); + scope.removeBinding(newName); + left.node.name = name; + + scope.push({ + id: t.identifier(newName), + init: t.identifier(name), + kind: 'let', + }); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/input.js new file mode 100644 index 0000000000000..569aff39effbc --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/input.js @@ -0,0 +1,29 @@ +/** + * Copyright 2020 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. + */ + +function test({a = 1, b: bb = 2}, [c = 3]) { + a; + bb + c; +} + +class Foo { + test({a = 1, b: bb = 2}, [c = 3]) { + a; + bb + c; + } +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/options.json new file mode 100644 index 0000000000000..a47282a6be9a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/output.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/output.js new file mode 100644 index 0000000000000..b0e794741a1f1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param-destructures/output.js @@ -0,0 +1,41 @@ +/** + * Copyright 2020 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. + */ +function test({ + a = 1, + b: bb = 2 +}, [c = 3]) { + let _a = a, + _bb = bb, + _c = c; + _a; + _bb; + _c; +} + +class Foo { + test({ + a = 1, + b: bb = 2 + }, [c = 3]) { + let _a2 = a, + _bb2 = bb, + _c2 = c; + _a2; + _bb2; + _c2; + } + +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/input.js new file mode 100644 index 0000000000000..f2bbfaf79ef5d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/input.js @@ -0,0 +1,25 @@ +/** + * Copyright 2020 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. + */ + +function test(param = 1) { + param; +} + +class Foo { + test(param = 1) { + param; + } +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/options.json new file mode 100644 index 0000000000000..a47282a6be9a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/output.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/output.js new file mode 100644 index 0000000000000..2c829122e4073 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/should-transform-param/output.js @@ -0,0 +1,27 @@ +/** + * Copyright 2020 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. + */ +function test(param = 1) { + let _param = param; + _param; +} + +class Foo { + test(param = 1) { + let _param2 = param; + _param2; + } + +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-on-param-assignment-with-destructure/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-on-param-assignment-with-destructure/input.js new file mode 100644 index 0000000000000..26f47494019f9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-on-param-assignment-with-destructure/input.js @@ -0,0 +1,18 @@ +/** + * Copyright 2020 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. + */ + +function test({x} = {}) { +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-on-param-assignment-with-destructure/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-on-param-assignment-with-destructure/options.json new file mode 100644 index 0000000000000..48839d18b2cff --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-on-param-assignment-with-destructure/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["../../../.."], + "throws": "Can only fix default assignment type of identifiers.\n\tPlease replace with a parameter, and destructure in the function body." +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default-destructure/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default-destructure/input.js new file mode 100644 index 0000000000000..7204095eb60c5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default-destructure/input.js @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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. + */ + +function test({a = 1}, {b = a}) { + a; + b; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default-destructure/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default-destructure/options.json new file mode 100644 index 0000000000000..04f21c0863258 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default-destructure/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["../../../.."], + "throws": "default assignment of default assignment" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default/input.js new file mode 100644 index 0000000000000..43817c0243bd3 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default/input.js @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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. + */ + +function test(a = 1, {b = a}) { + a; + b; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default/options.json new file mode 100644 index 0000000000000..04f21c0863258 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-destructure-references-default/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["../../../.."], + "throws": "default assignment of default assignment" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default-destructure/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default-destructure/input.js new file mode 100644 index 0000000000000..3a944024fbc4c --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default-destructure/input.js @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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. + */ + +function test({a = 1}, b = a) { + a; + b; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default-destructure/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default-destructure/options.json new file mode 100644 index 0000000000000..04f21c0863258 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default-destructure/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["../../../.."], + "throws": "default assignment of default assignment" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default/input.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default/input.js new file mode 100644 index 0000000000000..c32c1f2dc5aa0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default/input.js @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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. + */ + +function test(a = 1, b = a) { + a; + b; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default/options.json b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default/options.json new file mode 100644 index 0000000000000..04f21c0863258 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/fixtures/transform-assertions/throws-when-default-references-default/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["../../../.."], + "throws": "default assignment of default assignment" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/index.js b/build-system/babel-plugins/babel-plugin-transform-default-assignment/test/index.js similarity index 100% rename from build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/index.js rename to build-system/babel-plugins/babel-plugin-transform-default-assignment/test/index.js diff --git a/build-system/babel-plugins/babel-plugin-transform-dev-methods/index.js b/build-system/babel-plugins/babel-plugin-transform-dev-methods/index.js index da99a4bc8212d..a52caaacf67d1 100644 --- a/build-system/babel-plugins/babel-plugin-transform-dev-methods/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-dev-methods/index.js @@ -14,36 +14,38 @@ * limitations under the License. */ -const {resolve, dirname} = require('path'); +const {dirname, join, relative, resolve} = require('path').posix; -// Returns a new Map} -// key is a valid callee name to potentially remove. -// value.detected indicates if the callee name (key) was imported into the current module. -// value.removeable is the array of property names that can be removed. -// Example: ['dev', {detected: false, removeable: ['fine']}] would mean ... `dev().fine(...)` can be removed. +/** + * Returns a new Map} + * key is a valid callee name to potentially remove. + * value.detected indicates if the callee name (key) was imported into the current module. + * value.removeable is the array of property names that can be removed. + * Example: ['dev', {detected: false, removeable: ['fine']}] would mean ... `dev().fine(...)` can be removed. + * @return {Map}} + */ function defaultCalleeToPropertiesMap() { return new Map([ [ 'dev', { detected: false, - removeable: ['info', 'fine'], + removeable: ['info', 'fine', 'warn'], }, ], [ 'user', { detected: false, - removeable: ['fine'], + removeable: ['info', 'fine', 'warn'], }, ], ]); } // This Babel Plugin removes -// 1. `dev().info(...)` -// 2. `dev().fine(...)` -// 3. `user().fine(...)` +// - `dev().(info|fine|warn)(...)` +// - `user().(info|fine|warn)(...)` // CallExpressions for production ESM builds. module.exports = function () { let calleeToPropertiesMap = defaultCalleeToPropertiesMap(); @@ -60,18 +62,18 @@ module.exports = function () { }, ImportDeclaration({node}, state) { // Only remove the CallExpressions if this module imported the correct method ('dev') from '/log'. - const {specifiers, source} = node; + const {source, specifiers} = node; if (!source.value.endsWith('/log')) { return; } specifiers.forEach((specifier) => { if (specifier.imported) { - const filepath = resolve( - dirname(state.file.opts.filename), - source.value + const filepath = relative( + join(__dirname, '../../../'), + resolve(dirname(state.file.opts.filename), source.value) ); - if (filepath.endsWith('/amphtml/src/log')) { + if (filepath.endsWith('src/log')) { const propertyMapped = calleeToPropertiesMap.get( specifier.imported.name ); diff --git a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/input.js b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/input.js index 463b0cbbe4e64..64e84c83b99bf 100644 --- a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/input.js +++ b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/input.js @@ -34,7 +34,8 @@ function hello() { ); dev().fine(TAG, 'fine'); user().fine(TAG, 'fine'); - user().info('Should not be removed'); + user().info('Should be removed'); + user().error('Should not be removed'); return false; } @@ -45,8 +46,8 @@ export function helloAgain() { fromLocation.search ); dev().fine(TAG, 'fine'); - user().fine(TAG, 'fine'); - user().info('Should not be removed'); + user().warn(TAG, 'warn'); + user().error('Should not be removed'); return false; } @@ -59,6 +60,7 @@ class Foo { ); dev().fine(TAG, 'fine'); user().fine(TAG, 'fine'); - user().info('Should not be removed'); + dev().error(TAG, 'Should not be removed'); + user().error('Should not be removed'); } } \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/output.mjs b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/output.mjs index 717c30aa8aacf..c4cea728c8058 100644 --- a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/output.mjs +++ b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-both-usage/output.mjs @@ -16,21 +16,21 @@ import { dev, user } from '../../../../../../../src/log'; dev().info; user().fine; -user().info('Should not be removed'); function hello() { - user().info('Should not be removed'); + user().error('Should not be removed'); return false; } export function helloAgain() { - user().info('Should not be removed'); + user().error('Should not be removed'); return false; } class Foo { method() { - user().info('Should not be removed'); + dev().error(TAG, 'Should not be removed'); + user().error('Should not be removed'); } } diff --git a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/input.js b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/input.js index bcb67c20c55f3..92cccc555df79 100644 --- a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/input.js +++ b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/input.js @@ -18,23 +18,23 @@ import {user} from '../../../../../../../src/log'; user().fine(TAG, 'fine'); user().fine; -user().info('Should not be removed'); +user().error('Should not be removed'); function hello() { user().fine(TAG, 'fine'); - user().info('Should not be removed'); + user().info('Should be removed'); return false; } export function helloAgain() { user().fine(TAG, 'fine'); - user().info('Should not be removed'); + user().error('Should not be removed'); return false; } class Foo { method() { user().fine(TAG, 'fine'); - user().info('Should not be removed'); + user().info('Should be removed'); } } \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/output.mjs b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/output.mjs index ce398914d8083..cb12412e75083 100644 --- a/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/output.mjs +++ b/build-system/babel-plugins/babel-plugin-transform-dev-methods/test/fixtures/transform-assertions/imported-user-usage/output.mjs @@ -15,21 +15,18 @@ */ import { user } from '../../../../../../../src/log'; user().fine; -user().info('Should not be removed'); +user().error('Should not be removed'); function hello() { - user().info('Should not be removed'); return false; } export function helloAgain() { - user().info('Should not be removed'); + user().error('Should not be removed'); return false; } class Foo { - method() { - user().info('Should not be removed'); - } + method() {} } diff --git a/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/input.js b/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/input.js index ce0be9e64d075..616cf306a57f4 100644 --- a/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/input.js +++ b/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/input.js @@ -15,7 +15,7 @@ */ /** - * @protected {!Object|null} + * @protected {?Object} */ foo().bar; diff --git a/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/output.js b/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/output.js index ce0be9e64d075..616cf306a57f4 100644 --- a/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/output.js +++ b/build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/test/fixtures/transform/basic/output.js @@ -15,7 +15,7 @@ */ /** - * @protected {!Object|null} + * @protected {?Object} */ foo().bar; diff --git a/build-system/babel-plugins/babel-plugin-transform-function-declarations/index.js b/build-system/babel-plugins/babel-plugin-transform-function-declarations/index.js index 591cfefc13bb6..d891dcaa3872f 100644 --- a/build-system/babel-plugins/babel-plugin-transform-function-declarations/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-function-declarations/index.js @@ -19,7 +19,7 @@ module.exports = function ({types: t}) { /** * This transform is targetted toward these types only. - * @param {*} path + * @param {BabelPath} path * @return {boolean} */ function isNotFunction(path) { @@ -30,7 +30,7 @@ module.exports = function ({types: t}) { /** * This transform cannot safely convert generator functions. - * @param {*} path + * @param {BabelPath} path * @return {boolean} */ function isGenerator(path) { @@ -39,7 +39,7 @@ module.exports = function ({types: t}) { /** * Only transform a body with a single return statement. - * @param {*} path + * @param {BabelPath} path * @return {boolean} */ function isNotSingleReturnStatement(path) { @@ -52,7 +52,7 @@ module.exports = function ({types: t}) { /** * Only convert functions that do not contains usage of `arguments`. - * @param {*} path + * @param {BabelPath} path * @return {boolean} */ function containsArgumentsUsage(path) { @@ -71,7 +71,7 @@ module.exports = function ({types: t}) { /** * If the FunctionDeclaration contains a ThisExpression, converting from a FunctionDeclaration to a * VariableDeclaration => VariableDeclarator => ArrowFunctionExpression isn't necessarily valid. - * @param {*} path + * @param {BabelPath} path * @return {boolean} */ function containsThisExpression(path) { @@ -87,7 +87,7 @@ module.exports = function ({types: t}) { /** * If the FunctionDeclaration identifier is used a manner besides a CallExpression, bail. - * @param {*} path + * @param {BabelPath} path * @param {string} name * @return {boolean} */ @@ -98,16 +98,29 @@ module.exports = function ({types: t}) { ); } + /** + * @param {CompilerNode} node + * @return {ReturnType} + */ function createArrowFunctionExpression(node) { - const {params, body, async} = t.cloneNode(node); + const {async, body, params} = t.cloneNode(node); return t.arrowFunctionExpression(params, body.body[0].argument, async); } + /** + * @param {BabelPath} path + * @param {string} name + * @return {ReturnType} + */ function createVariableDeclarator(path, name) { const arrowFunction = createArrowFunctionExpression(path.node); return t.variableDeclarator(t.identifier(name), arrowFunction); } + /** + * @param {BabelPath} path + * @return {ReturnType} + */ function createVariableDeclaration(path) { const declarator = createVariableDeclarator(path, path.node.id.name); const declaration = t.variableDeclaration('let', [declarator]); @@ -125,7 +138,7 @@ module.exports = function ({types: t}) { (path) => referencesAreOnlyCallExpressions(path, path.get('id').node.name), ]; - // If CallExpression names should be exhempt from arrow conversion, denote them here. + // If CallExpression names should be exempt from arrow conversion, denote them here. const EXEMPTED_EXPRESSION_NAME_REGEXS = [/registerService/]; const EXPRESSION_BAIL_OUT_CONDITIONS = [ @@ -137,11 +150,13 @@ module.exports = function ({types: t}) { (path) => { const callExpression = path.findParent((p) => p.isCallExpression()); if (callExpression) { - const {name} = (callExpression.node && callExpression.node.callee) || { + const {name, property} = (callExpression.node && + callExpression.node.callee) || { name: null, + property: null, }; - return EXEMPTED_EXPRESSION_NAME_REGEXS.every((regexp) => - regexp.test(name) + return EXEMPTED_EXPRESSION_NAME_REGEXS.every( + (regexp) => regexp.test(name) || regexp.test(property.name) ); } diff --git a/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/input.js b/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/input.js index 2c74debd662cb..7aefb4c339b02 100644 --- a/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/input.js +++ b/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/input.js @@ -15,4 +15,7 @@ */ registerServiceBuilder(win, 'story-analytics', function() { return new StoryAnalyticsService(); -}); \ No newline at end of file +}); + +geoDeferred = new Deferred(); +AMP.registerServiceForDoc('foo', function(){return geoDeferred.promise;}); \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/output.js b/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/output.js index a22a7c0839bef..dfcca91b730cb 100644 --- a/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/output.js +++ b/build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/output.js @@ -15,4 +15,8 @@ */ registerServiceBuilder(win, 'story-analytics', function () { return new StoryAnalyticsService(); -}); \ No newline at end of file +}); +geoDeferred = new Deferred(); +AMP.registerServiceForDoc('foo', function () { + return geoDeferred.promise; +}); diff --git a/build-system/babel-plugins/babel-plugin-transform-html-template/index.js b/build-system/babel-plugins/babel-plugin-transform-html-template/index.js index 14b6dff5bd77c..f8e88c020f9ef 100644 --- a/build-system/babel-plugins/babel-plugin-transform-html-template/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-html-template/index.js @@ -15,8 +15,8 @@ */ const { - staticTemplateTags, staticTemplateFactoryFns, + staticTemplateTags, } = require('../static-template-metadata'); const {minify} = require('html-minifier'); @@ -97,9 +97,8 @@ module.exports = function ({types: t}) { hoistedIdentifier = t.clone(INSERTED_TEMPLATES.get(template)); } else { // Template not hoisted. Hoist it. - hoistedIdentifier = path.scope.generateUidIdentifier( - 'template' - ); + hoistedIdentifier = + path.scope.generateUidIdentifier('template'); const program = path.findParent((path) => path.isProgram()); program.scope.push({ diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/index.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/index.js deleted file mode 100644 index e7593e76e504a..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/index.js +++ /dev/null @@ -1,223 +0,0 @@ -/** - * Copyright 2020 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. - */ -const {dirname, relative, join} = require('path'); -const {transformFileSync} = require('@babel/core'); - -/** - * @fileoverview - * Finds `configureComponent(MyConstructor, {foo: 'bar'})` calls and: - * - * 1. Inlines imported `MyConstructor` in current scope. - * - * 2. Replaces `*.STATIC_CONFIG_.foo` accesses to their value as - * defined in config object `{foo: 'bar'}`. - * - * 3. Replaces `configureComponent(...)` call with identifier for inlined, static - * `MyConstructor`. - */ - -const calleeName = 'configureComponent'; -const replacedMember = 'STATIC_CONFIG_'; - -/** - * Sub-plugin that transforms inlined file that exports wrapped constructor. - * @param {{types: string}} options - * @return {!Object} - */ -function transformRedefineInline({types: t}) { - const propValueNode = (propValues, key, opt_default) => - propValues[key] || opt_default || t.identifier('undefined'); - - function unjsdoc({leadingComments}) { - if (!leadingComments) { - return; - } - for (let i = 0; i < leadingComments.length; i++) { - const comment = leadingComments[i]; - comment.value = comment.value.replace(/^\*/, ' [removed]'); - } - } - - function unexport(path) { - if (path.node.declaration) { - path.replaceWith(path.node.declaration); - return; - } - path.remove(); - } - - return { - name: 'transform-redefine-inline', - visitor: { - ExportDefaultDeclaration: unexport, - ExportNamedDeclaration: unexport, - ExportAllDeclaration: unexport, - ImportDeclaration(path, {opts}) { - const {source} = path.node; - if (source.value.startsWith('.')) { - source.value = join(opts.from, source.value).replace(/^[^.]/, './$&'); - } - }, - MemberExpression(path, {opts}) { - // Handle x.y.{...}.$replacedMember prop accesses - if (!t.isIdentifier(path.node.property, {name: replacedMember})) { - return; - } - - const assignment = path.find( - ({parent, parentKey, parentPath}) => - parentKey == 'left' && - t.isAssignmentExpression(parent) && - t.isExpressionStatement(parentPath.parent) - ); - - if (assignment) { - unjsdoc(assignment.parentPath.parent); - assignment.parentPath.parentPath.remove(); - return; - } - - if ( - t.isMemberExpression(path.parent) && - t.isIdentifier(path.parent.property) - ) { - const {name} = path.parent.property; - path.parentPath.replaceWith(propValueNode(opts.propValues, name)); - return; - } - - if ( - t.isVariableDeclarator(path.parent) && - t.isObjectPattern(path.parent.id) && - t.isVariableDeclaration(path.parentPath.parent) - ) { - const assignments = path.parent.id.properties.map(({key, value}) => - t.variableDeclarator( - value.left || value, - propValueNode(opts.propValues, key.name, value.right) - ) - ); - path.parentPath.replaceWithMultiple(assignments); - } - }, - }, - }; -} - -/** - * Transforms using transformRedefineInline sub-plugin. - * @param {string} sourceFilename - * @param {!Object} opts - * @return {!Object} - */ -const redefineInline = (sourceFilename, opts) => - transformFileSync(sourceFilename.toString(), { - configFile: false, - code: false, - ast: true, - sourceType: 'module', - plugins: [[transformRedefineInline, opts]], - }); - -/** - * Replaces `configureComponent()` wrapping calls. - * @param {{types: string}} options - * @return {!Object} - */ -module.exports = function ({types: t}) { - function getImportPath(nodes, name) { - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - if ( - t.isImportDeclaration(node) && - node.specifiers.find(({imported}) => t.isIdentifier(imported, {name})) - ) { - return node.source.value; - } - } - } - - return { - name: 'transform-inline-decl-extensions', - visitor: { - CallExpression(path, {file}) { - if (!t.isIdentifier(path.node.callee, {name: calleeName})) { - return; - } - - const [importedId, propsObj] = path.node.arguments; - if (!t.isIdentifier(importedId) || !t.isObjectExpression(propsObj)) { - return; - } - - const program = path.findParent((p) => t.isProgram(p)); - - const importPath = getImportPath(program.node.body, importedId.name); - if (!importPath) { - return; - } - - for (const name in program.scope.bindings) { - if (name) { - path.scope.rename(name, program.scope.generateUid(name)); - } - } - - const propValues = Object.create(null); - - for (const {key, value} of propsObj.properties) { - const {name} = key; - - if (t.isMemberExpression(value)) { - throw path.buildCodeFrameError( - `${replacedMember} properties must not be assigned to members. ` + - 'Set necessary values to program-level constants.' - ); - } - - if (t.isIdentifier(value)) { - const binding = path.scope.getBinding(value.name); - if (!binding || !t.isProgram(binding.scope.block)) { - throw path.buildCodeFrameError( - `ids used in ${replacedMember} must be defined as ` + - 'program-level constants.' - ); - } - propValues[name] = value; - continue; - } - - const id = program.scope.generateUidIdentifier(name); - program.scope.push({id, init: value, kind: 'const'}); - propValues[name] = id; - } - - const currentDirname = dirname(file.opts.filename); - const importedModule = join(currentDirname, importPath); - - // TODO(go.amp.dev/issue/26948): sourcemaps - const importedInline = redefineInline(require.resolve(importedModule), { - propValues, - from: relative(currentDirname, dirname(importedModule)), - }); - - program.unshiftContainer('body', importedInline.ast.program.body); - - path.replaceWith(t.identifier(importedId.name)); - }, - }, - }; -}; diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/input-base-class.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/input-base-class.js deleted file mode 100644 index b6dc3ef7c861d..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/input-base-class.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2020 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. - */ -const {a, b, c} = x.STATIC_CONFIG_; -const {a: a1, b: b1, c: c1} = x.y.STATIC_CONFIG_; - -export class Destructuring { - method() { - const {a, b: bRenamed, c} = this.STATIC_CONFIG_; - } - withDefaultValues() { - const { - a = 'default value for a', - b: renamedBbbb = 'default value for b', - c = 'default value for c', - d = 'default value for d', - e: renamedE = 'default value for e', - } = this.STATIC_CONFIG_; - } - unset() { - const { - a, - thisPropIsUnset, - thisPropIsUnsetToo, - } = this.STATIC_CONFIG_; - } -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/input.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/input.js deleted file mode 100644 index f62ac7b6ad7bd..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/input.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2020 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. - */ -import {Destructuring} from './input-base-class'; -foo( - configureComponent(Destructuring, { - a: 'value for a', - b: 'value for b', - c: 'value for c', - }) -); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/options.json b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/options.json deleted file mode 100644 index ece2997aa86d3..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["../../../../"], - "sourceType": "module" -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/output.mjs b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/output.mjs deleted file mode 100644 index 3afe6c6134340..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/destructuring/output.mjs +++ /dev/null @@ -1,51 +0,0 @@ -const _a = 'value for a', - _b = 'value for b', - _c = 'value for c'; - -/** - * Copyright 2020 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. - */ -const a = _a, - b = _b, - c = _c; -const a1 = _a, - b1 = _b, - c1 = _c; - -class Destructuring { - method() { - const a = _a, - bRenamed = _b, - c = _c; - } - - withDefaultValues() { - const a = _a, - renamedBbbb = _b, - c = _c, - d = 'default value for d', - renamedE = 'default value for e'; - } - - unset() { - const a = _a, - thisPropIsUnset = undefined, - thisPropIsUnsetToo = undefined; - } - -} - -import { Destructuring as _Destructuring } from './input-base-class'; -foo(_Destructuring); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/input-base-class.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/input-base-class.js deleted file mode 100644 index d901ebd08f67c..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/input-base-class.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2020 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. - */ -export class DirectAccess { - setProps() { - this.STATIC_CONFIG_.foo; - somethingSomething(this.STATIC_CONFIG_.bar); - tacos(this.STATIC_CONFIG_.nestedObject.baz); - } - - unsetProps() { - return this.STATIC_CONFIG_.thisPropIsUnset; - } - - propsSetToIds() { - return this.STATIC_CONFIG_.scopedId; - } -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/input.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/input.js deleted file mode 100644 index 8f58e6e44c007..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/input.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2020 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. - */ -import {DirectAccess} from './input-base-class'; - -const scopedId = 'value for scopedId'; - -foo( - configureComponent(DirectAccess, { - scopedId, - foo: 'value for foo', - bar: 'value for bar', - nestedObject: {foo: 'foo'}, - }) -); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/options.json b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/options.json deleted file mode 100644 index ece2997aa86d3..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["../../../../"], - "sourceType": "module" -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/output.mjs b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/output.mjs deleted file mode 100644 index 31e2916bdcc1e..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/direct-access/output.mjs +++ /dev/null @@ -1,41 +0,0 @@ -const _foo = 'value for foo', - _bar = 'value for bar', - _nestedObject = { - foo: 'foo' -}; - -/** - * Copyright 2020 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. - */ -class DirectAccess { - setProps() { - _foo; - somethingSomething(_bar); - tacos(_nestedObject.baz); - } - - unsetProps() { - return undefined; - } - - propsSetToIds() { - return _scopedId; - } - -} - -import { DirectAccess as _DirectAccess } from './input-base-class'; -const _scopedId = 'value for scopedId'; -foo(_DirectAccess); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/input-nested-directory/input-base-class.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/input-nested-directory/input-base-class.js deleted file mode 100644 index 519f1ddb2bb15..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/input-nested-directory/input-base-class.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2020 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. - */ -import '../../backwards'; -import './for/wards'; - -import {leaveThis} from 'alone'; -import {baz} from './foo/bar'; - -export class RelativeImports {} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/input.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/input.js deleted file mode 100644 index 43daed5b3f7d1..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/input.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2020 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. - */ -import {RelativeImports} from './input-nested-directory/input-base-class'; - -foo( - configureComponent(RelativeImports, { - a: 'value for a', - b: 'value for b', - d: 'value for c', - }) -); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/options.json b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/options.json deleted file mode 100644 index ece2997aa86d3..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["../../../../"], - "sourceType": "module" -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/output.mjs b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/output.mjs deleted file mode 100644 index 0d5fb9cbbab93..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/relative-imports/output.mjs +++ /dev/null @@ -1,28 +0,0 @@ -const _a = 'value for a', - _b = 'value for b', - _d = 'value for c'; - -/** - * Copyright 2020 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. - */ -import "../backwards"; -import "./input-nested-directory/for/wards"; -import { leaveThis } from 'alone'; -import { baz } from "./input-nested-directory/foo/bar"; - -class RelativeImports {} - -import { RelativeImports as _RelativeImports } from './input-nested-directory/input-base-class'; -foo(_RelativeImports); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/input-base-class.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/input-base-class.js deleted file mode 100644 index 99c46a77faec3..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/input-base-class.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2020 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. - */ -export class RemoveAssignment { - method() { - /** @private {something} */ - this.STATIC_CONFIG_ = {}; - - /** @private {something} */ - this.STATIC_CONFIG_.whatever = 'foo'; - } -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/input.js b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/input.js deleted file mode 100644 index 6073bbe36711b..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/input.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2020 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. - */ -import {RemoveAssignment} from './input-base-class'; - -foo(configureComponent(RemoveAssignment, {})); diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/options.json b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/options.json deleted file mode 100644 index ece2997aa86d3..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["../../../../"], - "sourceType": "module" -} diff --git a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/output.mjs b/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/output.mjs deleted file mode 100644 index 4dd0e7d73a757..0000000000000 --- a/build-system/babel-plugins/babel-plugin-transform-inline-configure-component/test/fixtures/inline-configure-component/remove-assignment/output.mjs +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright 2020 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. - */ -class RemoveAssignment { - method() {} - -} - -import { RemoveAssignment as _RemoveAssignment } from './input-base-class'; -foo(_RemoveAssignment); diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/index.js b/build-system/babel-plugins/babel-plugin-transform-internal-version/index.js new file mode 100644 index 0000000000000..4322a1f0d4b08 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/index.js @@ -0,0 +1,61 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = function (babel, options = {}) { + const {types: t} = babel; + const {version} = options; + + if (!version) { + throw new Error( + 'Need to specify runtime version as an option to babel transformer' + ); + } + + return { + name: 'transform-internal-version', + + visitor: { + StringLiteral(path) { + const {node} = path; + const {value} = node; + if (!value.includes('$internalRuntimeVersion$')) { + return; + } + + const replacement = t.stringLiteral( + value.replace(/\$internalRuntimeVersion\$/g, version) + ); + t.inherits(replacement, node); + path.replaceWith(replacement); + }, + + TemplateElement(path) { + const {node} = path; + const {cooked, raw} = node.value; + if (!raw.includes('$internalRuntimeVersion$')) { + return; + } + + const replacement = t.templateElement({ + cooked: cooked.replace(/\$internalRuntimeVersion\$/g, version), + raw: raw.replace(/\$internalRuntimeVersion\$/g, version), + }); + t.inherits(replacement, node); + path.replaceWith(replacement); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/input.js b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/input.js new file mode 100644 index 0000000000000..9c5499316dd7f --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/input.js @@ -0,0 +1,24 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +0; // Stop directive parsing + +"$internalRuntimeVersion$"; +'before$internalRuntimeVersion$after'; + +function internalRuntimeVersion() { + return '$internalRuntimeVersion$'; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/options.json b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/options.json new file mode 100644 index 0000000000000..893793d83dba1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["../../../../", {"version": "123"}]] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/output.js b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/output.js new file mode 100644 index 0000000000000..78df5e15ac899 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/basic/output.js @@ -0,0 +1,23 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +0; // Stop directive parsing + +"123"; +"before123after"; + +function internalRuntimeVersion() { + return "123"; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/input.js b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/input.js new file mode 100644 index 0000000000000..83d7e0f2d19a6 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/input.js @@ -0,0 +1,26 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +0; // Stop directive parsing + +`$internalRuntimeVersion$`; +`before$internalRuntimeVersion$after`; + +function internalRuntimeVersion() { + `$internalRuntimeVersion$`; + `\$internalRuntimeVersion$`; + `$\internalRuntimeVersion$`; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/options.json b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/options.json new file mode 100644 index 0000000000000..893793d83dba1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["../../../../", {"version": "123"}]] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/output.js b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/output.js new file mode 100644 index 0000000000000..d35fab32e67b1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/fixtures/transform/template-element/output.js @@ -0,0 +1,25 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +0; // Stop directive parsing + +`123`; +`before123after`; + +function internalRuntimeVersion() { + `123`; + `\123`; + `$\internalRuntimeVersion$`; +} diff --git a/build-system/babel-plugins/babel-plugin-transform-internal-version/test/index.js b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/index.js new file mode 100644 index 0000000000000..762295c3a9c84 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-internal-version/test/index.js @@ -0,0 +1,19 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/index.js b/build-system/babel-plugins/babel-plugin-transform-json-import/index.js new file mode 100644 index 0000000000000..b622e5fdbb0af --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/index.js @@ -0,0 +1,100 @@ +/** + * Copyright 2020 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. + */ + +const {dirname, join, relative, resolve} = require('path'); +const {readFileSync} = require('fs'); + +// Transforms JSON imports into a `JSON.parse` call: +// +// Input: +// ``` +// import key from './options.json' assert { type: 'json' }; +// ``` +// +// Output: +// ``` +// const key = JSON.parse('{"imported": "json"}'); +// ``` +module.exports = function ({template, types: t}, options) { + const {freeze = true} = options; + + return { + manipulateOptions(_opts, parserOpts) { + parserOpts.plugins.push('importAssertions'); + }, + + visitor: { + ImportDeclaration(path) { + const {assertions, source, specifiers} = path.node; + if (!assertions || assertions.length === 0) { + return; + } + + if (assertions.length !== 1) { + throw path.buildCodeFrameError( + 'too many import assertions, we only support `assert { "type": "json" }`' + ); + } + const assertion = assertions[0]; + if ( + !t.isIdentifier(assertion.key, {name: 'type'}) && + !t.isStringLiteral(assertion.key, {value: 'type'}) + ) { + throw path.buildCodeFrameError( + 'unknown assertion, we only support `assert { "type": "json" }`' + ); + } + if (!t.isStringLiteral(assertion.value, {value: 'json'})) { + throw path.buildCodeFrameError( + 'unknown type assertion, we only support `assert { "type": "json" }`' + ); + } + + const specifier = specifiers[0].local; + const jsonPath = relative( + join(__dirname, '..', '..', '..'), + resolve(dirname(this.file.opts.filename), source.value) + ); + let json; + try { + json = JSON.parse(readFileSync(jsonPath, 'utf8')); + } catch (e) { + throw path.buildCodeFrameError( + `could not load JSON file at '${jsonPath}'` + ); + } + + if (freeze) { + path.replaceWith( + template.statement + .ast`const ${specifier} = JSON.parse('${JSON.stringify( + json + )}', function(key, val) { + if (typeof val === 'object') Object.freeze(val); + return val; + });` + ); + return; + } + + path.replaceWith( + template.statement + .ast`const ${specifier} = JSON.parse('${JSON.stringify(json)}');` + ); + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/input.js new file mode 100644 index 0000000000000..05b2e1a392ef7 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/input.js @@ -0,0 +1,18 @@ +/** + * Copyright 2020 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. + */ + +import json from './404.json' assert { type: 'json' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/options.json new file mode 100644 index 0000000000000..55236288a6b21 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "throws": "could not load JSON file at 'build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-cant-load-json/404.json'" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-assertion/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-assertion/input.js new file mode 100644 index 0000000000000..c16088d63e2e5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-assertion/input.js @@ -0,0 +1,18 @@ +/** + * Copyright 2020 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. + */ + +import json from './options.json' assert { more: 'other' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-assertion/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-assertion/options.json new file mode 100644 index 0000000000000..008b6f6448061 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-assertion/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "throws": "The only accepted module attribute is `type` (17:43)" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-json-assertion/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-json-assertion/input.js new file mode 100644 index 0000000000000..8a741e52b7a4d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-json-assertion/input.js @@ -0,0 +1,18 @@ +/** + * Copyright 2020 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. + */ + +import json from './options.json' assert { type: 'other' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-json-assertion/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-json-assertion/options.json new file mode 100644 index 0000000000000..a654819d4bb08 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-not-type-json-assertion/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "throws": "unknown type assertion, we only support `assert { \"type\": \"json\" }`" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-too-many-assertions/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-too-many-assertions/input.js new file mode 100644 index 0000000000000..bf8b5356330ea --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-too-many-assertions/input.js @@ -0,0 +1,18 @@ +/** + * Copyright 2020 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. + */ + +import json from './options.json' assert { type: 'json', more: 'other' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-too-many-assertions/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-too-many-assertions/options.json new file mode 100644 index 0000000000000..00c74d1aa6c11 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/error-too-many-assertions/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "throws": "The only accepted module attribute is `type` (17:57)" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/input.js new file mode 100644 index 0000000000000..98d9f9b0f46f5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/input.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +import key from './options.json' assert { type: 'json' }; +import string from './options.json' assert { "type": 'json' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/options.json new file mode 100644 index 0000000000000..defb592a2a3b5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/options.json @@ -0,0 +1,4 @@ +{ + "plugins": [["../../../..", {"freeze": true}]], + "sourceType": "module" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/output.mjs b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/output.mjs new file mode 100644 index 0000000000000..2c01e1fa90ff0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-freeze/output.mjs @@ -0,0 +1,23 @@ +/** + * Copyright 2020 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. + */ +const key = JSON.parse("{\"plugins\":[[\"../../../..\",{\"freeze\":true}]],\"sourceType\":\"module\"}", function (key, val) { + if (typeof val === 'object') Object.freeze(val); + return val; +}); +const string = JSON.parse("{\"plugins\":[[\"../../../..\",{\"freeze\":true}]],\"sourceType\":\"module\"}", function (key, val) { + if (typeof val === 'object') Object.freeze(val); + return val; +}); diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/input.js new file mode 100644 index 0000000000000..98d9f9b0f46f5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/input.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +import key from './options.json' assert { type: 'json' }; +import string from './options.json' assert { "type": 'json' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/options.json new file mode 100644 index 0000000000000..df60916a1255d --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/options.json @@ -0,0 +1,4 @@ +{ + "plugins": [["../../../..", {"freeze": false}]], + "sourceType": "module" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/output.mjs b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/output.mjs new file mode 100644 index 0000000000000..b862d0800a56f --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform-no-freeze/output.mjs @@ -0,0 +1,17 @@ +/** + * Copyright 2020 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. + */ +const key = JSON.parse("{\"plugins\":[[\"../../../..\",{\"freeze\":false}]],\"sourceType\":\"module\"}"); +const string = JSON.parse("{\"plugins\":[[\"../../../..\",{\"freeze\":false}]],\"sourceType\":\"module\"}"); diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/input.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/input.js new file mode 100644 index 0000000000000..98d9f9b0f46f5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/input.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +import key from './options.json' assert { type: 'json' }; +import string from './options.json' assert { "type": 'json' }; + diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/options.json b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/options.json new file mode 100644 index 0000000000000..4020c566b4d90 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/output.mjs b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/output.mjs new file mode 100644 index 0000000000000..084b4643d22fa --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/fixtures/transform-assertions/transform/output.mjs @@ -0,0 +1,23 @@ +/** + * Copyright 2020 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. + */ +const key = JSON.parse("{\"plugins\":[\"../../../..\"],\"sourceType\":\"module\"}", function (key, val) { + if (typeof val === 'object') Object.freeze(val); + return val; +}); +const string = JSON.parse("{\"plugins\":[\"../../../..\"],\"sourceType\":\"module\"}", function (key, val) { + if (typeof val === 'object') Object.freeze(val); + return val; +}); diff --git a/build-system/babel-plugins/babel-plugin-transform-json-import/test/index.js b/build-system/babel-plugins/babel-plugin-transform-json-import/test/index.js new file mode 100644 index 0000000000000..bb2e50c56b6af --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-json-import/test/index.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/create-hash.js b/build-system/babel-plugins/babel-plugin-transform-jss/create-hash.js new file mode 100644 index 0000000000000..bd75969db79f0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/create-hash.js @@ -0,0 +1,36 @@ +/** + * Copyright 2020 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. + */ + +const crypto = require('crypto'); + +// This is in its own file in order to make it easy to stub in tests. +module.exports = { + createHash: (filepath) => + crypto + .createHash('sha256') + .update(toPosix(filepath)) + .digest('hex') + .slice(0, 7), +}; + +/** + * To support Windows, use posix separators for all filepath hashes. + * @param {string} filepath + * @return {string} + */ +function toPosix(filepath) { + return filepath.replace(/\\\\?/g, '/'); +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/index.js b/build-system/babel-plugins/babel-plugin-transform-jss/index.js new file mode 100644 index 0000000000000..0b257d009abd8 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/index.js @@ -0,0 +1,385 @@ +/** + * Copyright 2020 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. + */ + +/** + * Takes a .jss.js file and transforms the `useStyles` export to remove side effects + * and directly return the classes map. Also includes special key 'CSS' in the classes + * object with the entire CSS string. + * + * @example + * In: + * ``` + * import {createUseStyles} from 'react-jss' + * + * const jss = { button: { fontSize: 12 }} + * export const useStyles = createUseStyles(jss); + * + * import {useStyles} from './imported.jss'; + * useStyles().button; + * ``` + * + * Out: + * ``` + * const jss = { button: { fontSize: 12 }} + * const _classes = {button: 'button-1'} + * export const useStyles = () => _classes; + * export const CSS = '.button-1 { font-size: 12px }' + * export const $button = 'button-1'; + * + * import {useStyles} from './imported.jss'; + * import {$button as _$button} from './imported.jss'; + * _$button + * ``` + */ + +const hash = require('./create-hash'); +const {addNamed} = require('@babel/helper-module-imports'); +const {create} = require('jss'); +const {default: preset} = require('jss-preset-default'); +const {join, relative} = require('path'); +const {transformCssSync} = require('../../tasks/css/jsify-css-sync'); + +module.exports = function ({template, types: t}) { + /** + * @param {string} filename + * @return {boolean} + */ + function isJssFile(filename) { + return filename.endsWith('.jss.js'); + } + + /** + * @param {string} name + * @return {string} + */ + function classnameId(name) { + return `\$${name}`; + } + + /** + * @param {Path} reference + * @param {string} importedName + * @return {?Path} + */ + function findImportDeclaration(reference, importedName) { + if (!reference.isIdentifier()) { + return null; + } + + const binding = reference.scope.getBinding(reference.node.name); + if (!binding || binding.kind !== 'module') { + return null; + } + + const {path} = binding; + const {parentPath} = path; + + if ( + !parentPath.isImportDeclaration() || + !path.isImportSpecifier() || + !t.isIdentifier(path.node.imported, {name: importedName}) + ) { + return null; + } + + return parentPath; + } + + /** + * Replacess MemberExpressions. + * @example + * In: + * ``` + * import {useStyles} from 'foo'; + * const a = useStyles(); + * a.b; + * useStyles().c; + * ``` + * Out: + * ``` + * import {useStyles} from 'foo'; + * import {$b as _$b} from 'foo'; + * import {$c as _$c} from 'foo'; + * $_b; + * $_c; + * ``` + * @param {Path} importDeclaration + * @param {Path} memberExpression + * @return {boolean} + */ + function replaceMemberExpression(importDeclaration, memberExpression) { + if (!memberExpression.isMemberExpression({computed: false})) { + return false; + } + const {property} = memberExpression.node; + if (!t.isIdentifier(property)) { + return false; + } + + const localId = getImportIdentifier(importDeclaration, property.name); + memberExpression.replaceWith(t.cloneNode(localId)); + + return true; + } + + /** + * Replaces VariableDeclarations that use ObjectPatterns (destructuring). + * @example + * In: + * ``` + * import {useStyles} from 'foo'; + * const {a, ...rest} = useStyles(); + * ``` + * Out: + * ``` + * import {useStyles} from 'foo'; + * import {$a as _$a} from 'foo'; + * const {a: _unused, ...rest} = useStyles(); + * const a = $_a; + * ``` + * @param {Path} importDeclaration + * @param {Path} variableDeclarator + * @return {boolean} + */ + function replaceObjectPattern(importDeclaration, variableDeclarator) { + if ( + !variableDeclarator.isVariableDeclarator() || + !t.isObjectPattern(variableDeclarator.node.id) + ) { + return false; + } + const {properties} = variableDeclarator.node.id; + const replacedPropertyCount = properties.reduce((count, property) => { + const {computed, key, value} = property; + if (computed || !t.isIdentifier(value) || !t.isIdentifier(key)) { + return count; + } + const importId = getImportIdentifier(importDeclaration, key.name); + const declaration = variableDeclarator.parentPath; + declaration.insertAfter( + template.statement.ast( + `${declaration.node.kind} ${value.name} = ${importId.name}` + ) + ); + // Unused props are required to allow ...rest: + // const {a: _unused, ...rest} = useStyles(); + // const a = _$a; + property.value = variableDeclarator.scope.generateUidIdentifier('unused'); + return count + 1; + }, 0); + if (properties.length === replacedPropertyCount) { + variableDeclarator.remove(); + } + return true; + } + + /** + * Replaces VariableDeclarators by following their references. + * @example + * In: + * ``` + * import {useStyles} from 'foo'; + * const a = useStyles(); + * a.b; + * ``` + * Out: + * ``` + * import {useStyles} from 'foo'; + * import {$b as _$b} from 'foo'; + * $_b; + * ``` + * @param {Path} importDeclaration + * @param {Path} variableDeclarator + * @return {boolean} + */ + function replaceVariableDeclaratorRefs( + importDeclaration, + variableDeclarator + ) { + if ( + !variableDeclarator.isVariableDeclarator() || + !t.isIdentifier(variableDeclarator.node.id) + ) { + return false; + } + const {referencePaths} = + variableDeclarator.scope.bindings[variableDeclarator.node.id.name]; + const replacedReferenceCount = referencePaths.reduce( + (count, identifier) => + replaceExpression(importDeclaration, identifier) ? count + 1 : count, + 0 + ); + if (referencePaths.length === replacedReferenceCount) { + variableDeclarator.remove(); + } + return true; + } + + /** + * @param {Path} importDeclaration + * @param {Path} callExpressionOrIdentifier + * @return {boolean} + */ + function replaceExpression(importDeclaration, callExpressionOrIdentifier) { + const {parentPath} = callExpressionOrIdentifier; + return ( + replaceMemberExpression(importDeclaration, parentPath) || + replaceObjectPattern(importDeclaration, parentPath) || + replaceVariableDeclaratorRefs(importDeclaration, parentPath) + ); + } + + /** + * @param {Path} importDeclaration + * @param {string} name + * @return {string} + */ + function getImportIdentifier(importDeclaration, name) { + return addNamed( + importDeclaration, + classnameId(name), + importDeclaration.node.source.value + ); + } + + const seen = new Map(); + /** + * @param {string} JSS + * @param {string} filename + * @return {{ + * visitor: { + * Program: {Function(path: string, state: *): void}, + * CallExpression: {Function(path: string, state: *): void}, + * ImportDeclaration: {Function(path: string, state: *): void}, + * } + * }} + */ + function compileJss(JSS, filename) { + const relativeFilepath = relative(join(__dirname, '../../..'), filename); + const filehash = hash.createHash(relativeFilepath); + const jss = create({ + ...preset(), + createGenerateId: () => { + return (rule) => { + const dashCaseKey = rule.key.replace( + /([A-Z])/g, + (c) => `-${c.toLowerCase()}` + ); + const className = `${dashCaseKey}-${filehash}`; + if (seen.has(className)) { + throw new Error( + `Classnames must be unique across all files. Found a duplicate: ${className}` + ); + } + seen.set(className, filename); + return className; + }; + }, + }); + return jss.createStyleSheet(JSS); + } + + return { + visitor: { + Program(_path, state) { + const {filename} = state.file.opts; + seen.forEach((file, key) => { + if (file === filename) { + seen.delete(key); + } + }); + }, + + CallExpression(path, state) { + const callee = path.get('callee'); + + // Replace users that import JSS. + const importDeclaration = findImportDeclaration(callee, 'useStyles'); + if (importDeclaration) { + replaceExpression(importDeclaration, path); + return; + } + + // Replace JSS exporter + const {filename} = state.file.opts; + if (!isJssFile(filename)) { + return; + } + + if (!callee.isIdentifier({name: 'createUseStyles'})) { + return; + } + + const {confident, value: JSS} = path.get('arguments.0').evaluate(); + if (!confident) { + throw path.buildCodeFrameError( + `First argument to createUseStyles must be statically evaluatable.` + ); + } + const sheet = compileJss(JSS, filename); + if ('CSS' in sheet.classes) { + throw path.buildCodeFrameError( + 'Cannot have class named CSS in your JSS object.' + ); + } + + // Create the classes var. + // This is required for compatibility when a useStyles() result is + // passed around. + const id = path.scope.generateUidIdentifier('classes'); + const init = t.valueToNode(sheet.classes); + path.scope.push({id, init}); + path.scope.bindings[id.name].path.parentPath.addComment( + 'leading', + '* @enum {string}' + ); + + // Replace useStyles with a getter for the new `classes` var. + path.replaceWith(template.expression.ast`(() => ${t.cloneNode(id)})`); + + // Export each classname. + const exportDeclaration = path.findParent( + (p) => p.type === 'ExportNamedDeclaration' + ); + for (const key in sheet.classes) { + const id = t.identifier(classnameId(key)); + const init = t.valueToNode(sheet.classes[key]); + exportDeclaration.insertBefore( + template.statement.ast`export const ${id} = ${init}` + ); + } + + // Export a variable named CSS with the compiled CSS. + const {css} = transformCssSync(sheet.toString()); + const cssStr = t.stringLiteral(css); + const cssExport = template.ast`export const CSS = ${cssStr}`; + exportDeclaration.insertAfter(cssExport); + }, + + // Remove the import for react-jss + ImportDeclaration(path, state) { + const {filename} = state.file.opts; + if (!isJssFile(filename)) { + return; + } + + if (path.node.source.value === 'react-jss') { + path.remove(); + } + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/input.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/input.js new file mode 100644 index 0000000000000..3e489713c5126 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/input.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +import {createUseStyles} from 'react-jss'; +const JSS = {button: {fontSize: 12}}; +export const useStyles = createUseStyles(JSS); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/options.json new file mode 100644 index 0000000000000..f72c9aa3f4957 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "filename": "component.js" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/output.mjs new file mode 100644 index 0000000000000..e26871f07bbf7 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-non-jss-file/output.mjs @@ -0,0 +1,22 @@ +/** + * Copyright 2020 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. + */ +import { createUseStyles } from 'react-jss'; +const JSS = { + button: { + fontSize: 12 + } +}; +export const useStyles = createUseStyles(JSS); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/input.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/input.js new file mode 100644 index 0000000000000..c134476e3ecb8 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/input.js @@ -0,0 +1,25 @@ +/** + * Copyright 2020 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. + */ + +import {createUseStyles} from 'react-jss'; + +export const useStyles = createUseStyles({ + floatLeft: {float: 'left'}, +}); + +// These next lines should be unaffected by jss transform. +export const unrelated = 5; +unrelated + unrelated == unrelated * 2; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/options.json new file mode 100644 index 0000000000000..0e3c7a1b03985 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "filename": "component.jss.js" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/output.mjs new file mode 100644 index 0000000000000..091472ce06222 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/ignore-unrelated-exports/output.mjs @@ -0,0 +1,26 @@ +/** @enum {string}*/ +var _classes = { + floatLeft: "float-left-07984bd" +}; +export const $floatLeft = "float-left-07984bd"; + +/** + * Copyright 2020 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. + */ +export const useStyles = () => _classes; // These next lines should be unaffected by jss transform. + +export const CSS = ".float-left-07984bd{float:left}"; +export const unrelated = 5; +unrelated + unrelated == unrelated * 2; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/input.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/input.mjs new file mode 100644 index 0000000000000..821c11ff5fb98 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/input.mjs @@ -0,0 +1,31 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// useStyles() references that don't belong to a MemberExpression should be +// preserve the classname object. + +// TODO(alanorozco): It would be nice if these cases were linted to prevent +// deopt. + +import {useStyles} from './foo.jss'; +import {useStyles as useAnotherStyles} from './bar.jss'; + +console.log(useStyles()); +console.log(useStyles().thisOneIsOptimized); + +const a = useAnotherStyles(); +console.log(a) +console.log(a.thisOneIsOptimized) diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/options.json new file mode 100644 index 0000000000000..a47282a6be9a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/output.mjs new file mode 100644 index 0000000000000..337b61667d25a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-deopt-use-styles/output.mjs @@ -0,0 +1,29 @@ +import { $thisOneIsOptimized as _$thisOneIsOptimized2 } from "./bar.jss"; +import { $thisOneIsOptimized as _$thisOneIsOptimized } from "./foo.jss"; + +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// useStyles() references that don't belong to a MemberExpression should be +// preserve the classname object. +// TODO(alanorozco): It would be nice if these cases were linted to prevent +// deopt. +import { useStyles } from './foo.jss'; +import { useStyles as useAnotherStyles } from './bar.jss'; +console.log(useStyles()); +console.log(_$thisOneIsOptimized); +const a = useAnotherStyles(); +console.log(a); +console.log(_$thisOneIsOptimized2); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/input.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/input.mjs new file mode 100644 index 0000000000000..2f1f8d6d6212e --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/input.mjs @@ -0,0 +1,37 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {useStyles} from '../foo.jss'; +import {useStyles as useSomeOtherNameWithStyles} from './something.jss'; + +const a = useStyles(); +console.log(a.b); +console.log(a.x); + +function x() { + const x = useSomeOtherNameWithStyles(); + return x.foo; +} + +useStyles().x; + +const { + one, + two: twoRenamed, + ...rest +} = useStyles(); + +let {single} = useStyles(); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/options.json new file mode 100644 index 0000000000000..a47282a6be9a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/output.mjs new file mode 100644 index 0000000000000..0b9f4881be85b --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-use-styles/output.mjs @@ -0,0 +1,41 @@ +import { $single as _$single } from "../foo.jss"; +import { $two as _$two } from "../foo.jss"; +import { $one as _$one } from "../foo.jss"; +import { $x as _$x2 } from "../foo.jss"; +import { $foo as _$foo } from "./something.jss"; +import { $x as _$x } from "../foo.jss"; +import { $b as _$b } from "../foo.jss"; + +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useStyles } from '../foo.jss'; +import { useStyles as useSomeOtherNameWithStyles } from './something.jss'; +console.log(_$b); +console.log(_$x); + +function x() { + return _$foo; +} + +_$x2; +const { + one: _unused, + two: _unused2, + ...rest +} = useStyles(); +const twoRenamed = _$two; +const one = _$one; +let single = _$single; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/input.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/input.mjs new file mode 100644 index 0000000000000..9dd5b0c9696b3 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/input.mjs @@ -0,0 +1,24 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {useStyles} from 'foo'; + +const direct = useStyles(); +direct.memberExpression; +const {objectPatternProperty} = direct; +const indirect = direct; +indirect.memberExpressionIndirect; +const {objectPatternPropertyIndirect} = indirect; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/options.json new file mode 100644 index 0000000000000..a47282a6be9a9 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/output.mjs new file mode 100644 index 0000000000000..15ef1e0ed8638 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-replace-variable-refs/output.mjs @@ -0,0 +1,25 @@ +import { $objectPatternPropertyIndirect as _$objectPatternPropertyIndirect } from "foo"; +import { $memberExpressionIndirect as _$memberExpressionIndirect } from "foo"; +import { $objectPatternProperty as _$objectPatternProperty } from "foo"; +import { $memberExpression as _$memberExpression } from "foo"; + +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useStyles } from 'foo'; +_$memberExpression; +const objectPatternProperty = _$objectPatternProperty; +_$memberExpressionIndirect; +const objectPatternPropertyIndirect = _$objectPatternPropertyIndirect; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-classname/input.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-classname/input.js new file mode 100644 index 0000000000000..d8fba9fc2a007 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-classname/input.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +// Throws because cant use classname "CSS", as it conflicts with this transform. +import {createUseStyles} from 'react-jss'; +export const useStyles = createUseStyles({CSS: {fontSize: 12}}); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-classname/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-classname/options.json new file mode 100644 index 0000000000000..aa03f1ce07794 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-classname/options.json @@ -0,0 +1,6 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "filename": "component.jss.js", + "throws": "CSS" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-confidence/input.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-confidence/input.js new file mode 100644 index 0000000000000..411572a0b72cb --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-confidence/input.js @@ -0,0 +1,21 @@ +/** + * Copyright 2020 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. + */ + +// Throws because object spread is not statically evaluable. +import {createUseStyles} from 'react-jss'; +const foo = {foo: 7} +const JSS = {...foo} +export const useStyles = createUseStyles(JSS); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-confidence/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-confidence/options.json new file mode 100644 index 0000000000000..533161d0e18b1 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-throw-confidence/options.json @@ -0,0 +1,6 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "filename": "component.jss.js", + "throws": "statically evaluatable" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/input.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/input.js new file mode 100644 index 0000000000000..22a0e05a09017 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/input.js @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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. + */ +import {createUseStyles} from 'react-jss' + +const JSS = {button: {fontSize: 12}}; + +export const useStyles = createUseStyles(JSS); \ No newline at end of file diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/options.json new file mode 100644 index 0000000000000..0e3c7a1b03985 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "filename": "component.jss.js" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/output.mjs new file mode 100644 index 0000000000000..4150a4b53e1d5 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-jss-var/output.mjs @@ -0,0 +1,28 @@ +/** @enum {string}*/ +var _classes = { + button: "button-21aa4a8" +}; + +/** + * Copyright 2020 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. + */ +const JSS = { + button: { + fontSize: 12 + } +}; +export const $button = "button-21aa4a8"; +export const useStyles = () => _classes; +export const CSS = ".button-21aa4a8{font-size:12px}"; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/input.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/input.js new file mode 100644 index 0000000000000..2c112dcaeb258 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/input.js @@ -0,0 +1,26 @@ +/** + * Copyright 2020 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. + */ + +import {createUseStyles} from 'react-jss'; + +export const useStyles = createUseStyles({ + floatLeft: {float: 'left', border: 'black solid 1px'}, + fill: { + display: 'block', + position: 'relative', + flex: '1 1 auto', + }, +}); diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/options.json b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/options.json new file mode 100644 index 0000000000000..0e3c7a1b03985 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/options.json @@ -0,0 +1,5 @@ +{ + "plugins": ["../../../.."], + "sourceType": "module", + "filename": "component.jss.js" +} diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/output.mjs b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/output.mjs new file mode 100644 index 0000000000000..9cec9b482330e --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/fixtures/transform-assertions/should-transform-literal/output.mjs @@ -0,0 +1,25 @@ +/** @enum {string}*/ +var _classes = { + floatLeft: "float-left-a6c6677", + fill: "fill-a6c6677" +}; +export const $floatLeft = "float-left-a6c6677"; +export const $fill = "fill-a6c6677"; + +/** + * Copyright 2020 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. + */ +export const useStyles = () => _classes; +export const CSS = ".float-left-a6c6677{float:left;border:1px solid #000}.fill-a6c6677{-ms-flex:1 1 auto;flex:1 1 auto;display:block;position:relative}"; diff --git a/build-system/babel-plugins/babel-plugin-transform-jss/test/index.js b/build-system/babel-plugins/babel-plugin-transform-jss/test/index.js new file mode 100644 index 0000000000000..fd57322cb9d11 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-jss/test/index.js @@ -0,0 +1,66 @@ +/** + * Copyright 2020 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. + */ + +const babel = require('@babel/core'); +const path = require('path'); +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); + +const fileContents = ` +import {createUseStyles} from 'react-jss'; +export const useStyles = createUseStyles({button: {fontSize: 12}});`; + +const plugins = [path.join(__dirname, '..')]; +const caller = {name: 'babel-jest'}; + +// eslint-disable-next-line no-undef +test('transforming the same file contents twice should throw if there is a hash collision with filename', () => { + let filename; + expect(() => { + stubCreateHash(() => { + filename = 'test1.jss.js'; + babel.transformSync(fileContents, {filename, plugins, caller}); + filename = 'test2.jss.js'; + babel.transformSync(fileContents, {filename, plugins, caller}); + }); + }).toThrow(/Classnames must be unique across all files/); +}); + +// eslint-disable-next-line no-undef +test('transforming same exact file twice is fine (e.g. watch mode)', () => { + const filename = 'test.jss.js'; + babel.transformSync(fileContents, {filename, plugins, caller}); + babel.transformSync(fileContents, {filename, plugins, caller}); +}); + +/** + * A stubs create-hash then calls the provided function. + * @param {Function} fn + */ +function stubCreateHash(fn) { + const hash = require('../create-hash'); + const originalCreateHash = hash.createHash; + hash.createHash = () => 'abcedf'; + + try { + fn(); + hash.createHash = originalCreateHash; + } catch (err) { + hash.createHash = originalCreateHash; + throw err; + } +} diff --git a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/index.js b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/index.js index 33cb6696af3d1..74ebec6f6f0dc 100644 --- a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/index.js @@ -14,13 +14,24 @@ * limitations under the License. */ -const filepath = require('path'); +const pathmodule = require('path'); const {addNamed} = require('@babel/helper-module-imports'); +/** + * @param {*} babel + * @param {{ + * importFrom?: string, + * }=} options + * @return {{ + * visitor: { + * CallExpression: {Funcion(path: string): void} + * } + * }} + */ module.exports = function (babel, options = {}) { const {types: t} = babel; const promiseResolveMatcher = t.buildMatchMemberExpression('Promise.resolve'); - const {importFrom = 'src/resolved-promise'} = options; + const {importFrom = 'src/core/data-structures/promise'} = options; return { visitor: { @@ -41,7 +52,10 @@ module.exports = function (babel, options = {}) { // Relative will return "foo" instead of "./foo". And if it returned // a "../foo", making it "./../foo" doesn't hurt. const source = - './' + filepath.relative(filepath.dirname(filename), importFrom); + './' + + toPosix( + pathmodule.relative(pathmodule.dirname(filename), importFrom) + ); const resolvedPromise = addNamed(path, 'resolvedPromise', source, { importedType: 'es6', }); @@ -50,3 +64,13 @@ module.exports = function (babel, options = {}) { }, }; }; + +/** + * Even though we are using the path module, JS Modules should never have + * their paths specified in Windows format. + * @param {string} path + * @return {string} + */ +function toPosix(path) { + return path.replace(/\\\\?/g, '/'); +} diff --git a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/promise-resolve/output.mjs b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/promise-resolve/output.mjs index f2d643e7faa07..da2bcedd962bd 100644 --- a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/promise-resolve/output.mjs +++ b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/promise-resolve/output.mjs @@ -1,5 +1,5 @@ -import { resolvedPromise as _resolvedPromise2 } from "./../../../../../../../src/resolved-promise"; -import { resolvedPromise as _resolvedPromise } from "./../../../../../../../src/resolved-promise"; +import { resolvedPromise as _resolvedPromise2 } from "./../../../../../../../src/core/data-structures/promise"; +import { resolvedPromise as _resolvedPromise } from "./../../../../../../../src/core/data-structures/promise"; /** * Copyright 2020 The AMP HTML Authors. All Rights Reserved. diff --git a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/options.json b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/options.json index 2f34c4868080b..e1f5d9897e8ea 100644 --- a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/options.json +++ b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/options.json @@ -3,7 +3,7 @@ [ "../../../..", { - "importFrom": "build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/resolve-promise" + "importFrom": "build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/core/data-structures/promise" } ] ], diff --git a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/output.mjs b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/output.mjs index b05f8978a0f7a..bf6f5a36fac43 100644 --- a/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/output.mjs +++ b/build-system/babel-plugins/babel-plugin-transform-promise-resolve/test/fixtures/transform/relative-importing/output.mjs @@ -1,5 +1,5 @@ -import { resolvedPromise as _resolvedPromise2 } from "./resolve-promise"; -import { resolvedPromise as _resolvedPromise } from "./resolve-promise"; +import { resolvedPromise as _resolvedPromise2 } from "./core/data-structures/promise"; +import { resolvedPromise as _resolvedPromise } from "./core/data-structures/promise"; /** * Copyright 2020 The AMP HTML Authors. All Rights Reserved. diff --git a/build-system/babel-plugins/babel-plugin-transform-prune-namespace/index.js b/build-system/babel-plugins/babel-plugin-transform-prune-namespace/index.js index 09898d5390a62..b5035671f69fd 100644 --- a/build-system/babel-plugins/babel-plugin-transform-prune-namespace/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-prune-namespace/index.js @@ -38,6 +38,10 @@ module.exports = function () { }, }; + /** + * @param {BabelPath} path + * @return {BabelPath|void} + */ function deepestMember(path) { while (true) { const object = path.get('object'); @@ -48,12 +52,26 @@ module.exports = function () { } } + /** + * + * @param {BabelPath} path + * @param {T} namespaceMember + * @param {T} state + * @return {void} + */ function memberInAssignmentExpression(path, namespaceMember, state) { const {name} = namespaceMember.node.property; state.declaredNames.set(name, path.parentPath); } - function memberExpression(path, namespaceMember, state) { + /** + * + * @param {BabelPath} _path + * @param {T} namespaceMember + * @param {T} state + * @return {void} + */ + function memberExpression(_path, namespaceMember, state) { const {name} = namespaceMember.node.property; state.usedNames.add(name); } diff --git a/build-system/babel-plugins/babel-plugin-transform-stringish-literals/index.js b/build-system/babel-plugins/babel-plugin-transform-stringish-literals/index.js index 8845b9e557372..8118538dd0b09 100644 --- a/build-system/babel-plugins/babel-plugin-transform-stringish-literals/index.js +++ b/build-system/babel-plugins/babel-plugin-transform-stringish-literals/index.js @@ -20,6 +20,11 @@ module.exports = function ({types: t}) { const cloneNodes = (nodes) => nodes.map((node) => t.cloneNode(node)); const escapeValue = (value) => String(value).replace(ESCAPE_REGEX, '\\$&'); + /** + * @param {CompilerNode} clonedQuasis + * @param {number} index + * @return {number|undefined} + */ function whichCloneQuasi(clonedQuasis, index) { for (let i = index; i >= 0; i--) { const quasi = clonedQuasis[i]; @@ -29,6 +34,10 @@ module.exports = function ({types: t}) { } } + /** + * @param {CompilerNode} leftPath + * @param {CompilerNode} rightPath + */ function joinTemplateLiterals(leftPath, rightPath) { const {node: leftNode} = leftPath; const {node: rightNode} = rightPath; @@ -47,6 +56,9 @@ module.exports = function ({types: t}) { rightPath.remove(); } + /** + * @param {BabelPath} path + */ function joinMaybeTemplateLiteral(path) { const left = path.get('left'); const right = path.get('right'); @@ -95,6 +107,9 @@ module.exports = function ({types: t}) { }, }, + /** + * @param {BabelPath} path + */ TemplateLiteral(path) { // Convert any items inside a template literal that are static literals. // `foo{'123'}bar` => `foo123bar` diff --git a/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/input.js b/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/input.js index 55a33d53bb7fb..503c3c85be06a 100644 --- a/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/input.js +++ b/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/input.js @@ -24,12 +24,12 @@ let numberStart = 1 + `/foo`; let stringStart = '1' + `/foo`; let numberEnd = `foo/` + 1; let stringEnd = `foo/` + '1'; -let illegalCharacterString = `Invalid share providers configuration for in bookend. ` + 'Value must be `true` or a params object.'; +let illegalCharacterString = `Invalid share providers configuration for story. ` + 'Value must be `true` or a params object.'; let illegalCharacterTemplate = `Invalid ${x}` + 'Value must be `true` or a params object.'; let illegalEscapeValue = `Invalid ${x}` + '${foo}'; inverted: { - let illegalCharacterString = 'Value must be `true` or a params object. ' + `Invalid share providers configuration for in bookend.`; + let illegalCharacterString = 'Value must be `true` or a params object. ' + `Invalid share providers configuration for story.`; let illegalCharacterTemplate = 'Value must be `true` or a params object. ' + `Invalid ${x}`; let illegalEscapeValue = '${foo}' + `Invalid ${x}`; } diff --git a/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/output.js b/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/output.js index 4b6cc52338e78..6e03c014dcdb0 100644 --- a/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/output.js +++ b/build-system/babel-plugins/babel-plugin-transform-stringish-literals/test/fixtures/transform/binary-expression/output.js @@ -22,12 +22,12 @@ let numberStart = "1/foo"; let stringStart = "1/foo"; let numberEnd = "foo/1"; let stringEnd = "foo/1"; -let illegalCharacterString = "Invalid share providers configuration for in bookend. Value must be `true` or a params object."; +let illegalCharacterString = "Invalid share providers configuration for story. Value must be `true` or a params object."; let illegalCharacterTemplate = `Invalid ${x}Value must be \`true\` or a params object.`; let illegalEscapeValue = `Invalid ${x}\${foo}`; inverted: { - let illegalCharacterString = "Value must be `true` or a params object. Invalid share providers configuration for in bookend."; + let illegalCharacterString = "Value must be `true` or a params object. Invalid share providers configuration for story."; let illegalCharacterTemplate = `Value must be \`true\` or a params object. Invalid ${x}`; let illegalEscapeValue = `\${foo}Invalid ${x}`; } diff --git a/build-system/common/OWNERS b/build-system/common/OWNERS index 32c5ea6d75438..002922bb9cc93 100644 --- a/build-system/common/OWNERS +++ b/build-system/common/OWNERS @@ -1,10 +1,10 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { - owners: [{name: 'ampproject/wg-infra'}], + owners: [{name: 'ampproject/wg-infra'}, {name: 'rsimha', notify: true}], }, ], } diff --git a/build-system/common/check-package-manager.js b/build-system/common/check-package-manager.js index 25b8ea4883dbc..7f8bc629a279d 100644 --- a/build-system/common/check-package-manager.js +++ b/build-system/common/check-package-manager.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node /** * Copyright 2017 The AMP HTML Authors. All Rights Reserved. * @@ -15,92 +16,104 @@ */ 'use strict'; +/** + * @fileoverview Perform checks on the AMP toolchain. + */ + /* * NOTE: DO NOT use non-native node modules in this file. * This file runs before installing any packages, * so it must work with vanilla NodeJS code. * github.com/ampproject/amphtml/pull/19386 */ -const fs = require('fs'); const https = require('https'); -const {getStdout, getStderr} = require('./exec'); +const {getStdout} = require('./process'); const setupInstructionsUrl = - 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-quick.md#one-time-setup'; + 'https://github.com/ampproject/amphtml/blob/main/docs/getting-started-quick.md#one-time-setup'; const nodeDistributionsUrl = 'https://nodejs.org/dist/index.json'; -const gulpHelpUrl = - 'https://medium.com/gulpjs/gulp-sips-command-line-interface-e53411d4467'; - -const yarnExecutable = 'npx yarn'; -const gulpExecutable = 'npx gulp'; -const pythonExecutable = 'python'; - -const wrongGulpPaths = [ - '/bin/', - '/sbin/', - '/usr/bin/', - '/usr/sbin/', - '/usr/local/bin/', - '/usr/local/sbin/', -]; const warningDelaySecs = 10; const updatesNeeded = new Set(); +const npmInfoMessage = `${red( + '*** The AMP project now uses npm for package management ***' +)} +For more info, see ${cyan('http://go.amp.dev/issue/30518')}. + +${yellow('To install all packages:')} +${cyan('$')} npm install + +${yellow('To install a new (runtime) package to "dependencies":')} +${cyan('$')} npm install [package_name@version] + +${yellow('To install a new (toolset) package to "devDependencies":')} +${cyan('$')} npm install --save-dev [package_name@version] + +${yellow('To update a package:')} +${cyan('$')} npm update [package_name@version] + +${yellow('To uninstall a package:')} +${cyan('$')} npm uninstall [package_name] + +${yellow('For detailed instructions, see')} ${cyan(setupInstructionsUrl)}`; + // Color formatting libraries may not be available when this script is run. +/** + * Formats the text to appear red + * + * @param {string} text + * @return {string} + */ function red(text) { return '\x1b[31m' + text + '\x1b[0m'; } +/** + * Formats the text to appear cyan + * + * @param {string} text + * @return {string} + */ function cyan(text) { return '\x1b[36m' + text + '\x1b[0m'; } +/** + * Formats the text to appear green + * + * @param {string} text + * @return {string} + */ function green(text) { return '\x1b[32m' + text + '\x1b[0m'; } +/** + * Formats the text to appear yellow + * + * @param {string} text + * @return {string} + */ function yellow(text) { return '\x1b[33m' + text + '\x1b[0m'; } /** - * @fileoverview Perform checks on the AMP toolchain. - */ - -// If npm is being run, print a message and cause 'npm install' to fail. -function ensureYarn() { - if (process.env.npm_execpath.indexOf('yarn') === -1) { - console.log( - red('*** The AMP project uses yarn for package management ***'), - '\n' - ); - console.log(yellow('To install all packages:')); - console.log(cyan('$'), 'yarn', '\n'); - console.log( - yellow('To install a new (runtime) package to "dependencies":') - ); - console.log(cyan('$'), 'yarn add --exact [package_name@version]', '\n'); - console.log( - yellow('To install a new (toolset) package to "devDependencies":') - ); - console.log( - cyan('$'), - 'yarn add --dev --exact [package_name@version]', - '\n' - ); - console.log(yellow('To upgrade a package:')); - console.log(cyan('$'), 'yarn upgrade --exact [package_name@version]', '\n'); - console.log(yellow('To remove a package:')); - console.log(cyan('$'), 'yarn remove [package_name]', '\n'); - console.log( - yellow('For detailed instructions, see'), - cyan(setupInstructionsUrl), - '\n' - ); + * If yarn is being run, print a message and cause 'yarn install' to fail. + * See https://github.com/yarnpkg/yarn/issues/5063 for details on how the + * package manager being used is determined. + **/ +function ensureNpm() { + if (!process.env.npm_execpath.includes('npm')) { + console.log(npmInfoMessage); process.exit(1); } } -// Check the node version and print a warning if it is not the latest LTS. +/** + * Check the node version and print a warning if it is not the latest LTS. + * + * @return {Promise} + **/ function checkNodeVersion() { const nodeVersion = getStdout('node --version').trim(); return new Promise((resolve) => { @@ -165,6 +178,12 @@ function checkNodeVersion() { }); } +/** + * Extracts the latest node version from a JSON object containing version info + * + * @param {!Object} distributionsJson + * @return {string} + */ function getNodeLatestLtsVersion(distributionsJson) { if (distributionsJson) { // Versions are in descending order, so the first match is the latest lts. @@ -180,147 +199,32 @@ function getNodeLatestLtsVersion(distributionsJson) { } } -// If yarn is being run, perform a version check and proceed with the install. -function checkYarnVersion() { - const yarnVersion = getStdout(yarnExecutable + ' --version').trim(); - const yarnInfo = getStdout(yarnExecutable + ' info --json yarn').trim(); - const yarnInfoJson = JSON.parse(yarnInfo.split('\n')[0]); // First line - const stableVersion = getYarnStableVersion(yarnInfoJson); - if (stableVersion === '') { - console.log( - yellow( - 'WARNING: Something went wrong. ' + - 'Could not determine the stable version of yarn.' - ) - ); - } else if (yarnVersion !== stableVersion) { - console.log( - yellow('WARNING: Detected yarn version'), - cyan(yarnVersion) + yellow('. Recommended (stable) version is'), - cyan(stableVersion) + yellow('.') - ); - console.log( - yellow('⤷ To fix this, run'), - cyan('"curl -o- -L https://yarnpkg.com/install.sh | bash"'), - yellow('or see'), - cyan('https://yarnpkg.com/docs/install'), - yellow('for instructions.') - ); - updatesNeeded.add('yarn'); - } else { - console.log( - green('Detected'), - cyan('yarn'), - green('version'), - cyan(yarnVersion + ' (stable)') + green('. Installing packages...') - ); - } -} - -function getYarnStableVersion(infoJson) { - if ( - infoJson && - infoJson.hasOwnProperty('data') && - infoJson.data.hasOwnProperty('version') - ) { - return infoJson.data.version; - } else { - return ''; - } -} - -function getParentShellPath() { - const nodePath = process.env.PATH; - const pathSeparator = process.platform == 'win32' ? ';' : ':'; - // nodejs adds a few extra variables to $PATH, ending with '../../bin/node-gyp-bin'. - // See https://github.com/nodejs/node-convergence-archive/blob/master/deps/npm/lib/utils/lifecycle.js#L81-L85 - return nodePath.split(`node-gyp-bin${pathSeparator}`).pop(); -} - -function runGulpChecks() { - const firstInstall = !fs.existsSync('node_modules'); - const globalPackages = getStdout(yarnExecutable + ' global list').trim(); - const globalGulp = globalPackages.match(/"gulp@.*" has binaries/); - const defaultGulpPath = getStdout('which gulp', { - 'env': {'PATH': getParentShellPath()}, - }).trim(); - const wrongGulp = wrongGulpPaths.some((path) => - defaultGulpPath.startsWith(path) +/** + * If npm is being run, log its version and proceed with the install. + */ +function logNpmVersion() { + const npmVersion = getStdout('npm --version').trim(); + console.log( + green('Detected'), + cyan('npm'), + green('version'), + cyan(npmVersion) + green('. Installing packages...') ); - if (globalGulp) { - console.log( - yellow('WARNING: Detected a global install of'), - cyan('gulp') + yellow('. It is recommended that you use'), - cyan('gulp-cli'), - yellow('instead.') - ); - console.log( - yellow('⤷ To fix this, run'), - cyan('"yarn global remove gulp"'), - yellow('followed by'), - cyan('"yarn global add gulp-cli"') + yellow('.') - ); - console.log( - yellow('⤷ See'), - cyan(gulpHelpUrl), - yellow('for more information.') - ); - updatesNeeded.add('gulp'); - } - if (wrongGulp) { - console.log( - yellow('WARNING: Found'), - cyan('gulp'), - yellow('in an unexpected location:'), - cyan(defaultGulpPath) + yellow('.') - ); - console.log( - yellow('⤷ To fix this, consider removing'), - cyan(defaultGulpPath), - yellow('from your default'), - cyan('$PATH') + yellow(', or deleting it.') - ); - console.log( - yellow('⤷ Run'), - cyan('"which gulp"'), - yellow('for more information.') - ); - updatesNeeded.add('gulp'); - } - if (!firstInstall) { - const gulpVersions = getStdout(gulpExecutable + ' --version').trim(); - const gulpVersion = gulpVersions.match(/Local version[:]? (.*?)$/); - if (gulpVersion && gulpVersion.length == 2) { - console.log( - green('Detected'), - cyan('gulp'), - green('version'), - cyan(gulpVersion[1]) + green('.') - ); - } else { - console.log( - yellow( - 'WARNING: ' + - 'Could not determine the local version of gulp. ' + - '(This is normal during install / upgrade.)' - ) - ); - } - } } +/** + * Checks if the local version of python is 2.7 or 3 + */ function checkPythonVersion() { // Python 2.7 is EOL but still supported - // Python 3.5+ are still supported (TODO: deprecate 3.5 on 2020-09-13) + // Python 3.6+ are still supported // https://devguide.python.org/#status-of-python-branches - const recommendedVersion = '2.7 or 3.5+'; - const recommendedVersionRegex = /^2\.7|^3\.[5-9]/; + const recommendedVersion = '2.7 or 3.6+'; + const recommendedVersionRegex = /^2\.7|^3\.(?:[6-9]|1\d)/; // Python2 prints its version to stderr (fixed in Python 3.4) // See: https://bugs.python.org/issue18338 - const pythonVersionResult = - getStderr(`${pythonExecutable} --version`).trim() || - getStdout(`${pythonExecutable} --version`).trim(); + const pythonVersionResult = getStdout('python --version 2>&1').trim(); const pythonVersion = pythonVersionResult.match(/Python (.*?)$/); if (pythonVersion && pythonVersion.length == 2) { const versionNumber = pythonVersion[1]; @@ -349,7 +253,7 @@ function checkPythonVersion() { ); console.log( yellow('⤷ To fix this, make sure'), - cyan(pythonExecutable), + cyan('python'), yellow('is in your'), cyan('PATH'), yellow('and is version'), @@ -358,42 +262,40 @@ function checkPythonVersion() { } } -function main() { - // Yarn is already used by default on Travis, so there is nothing more to do. - if (process.env.TRAVIS) { - return 0; +/** + * Runs checks for the package manager and tooling being used. + * @return {Promise} + */ +async function main() { + ensureNpm(); + await checkNodeVersion(); + checkPythonVersion(); + logNpmVersion(); + if (updatesNeeded.size) { + console.log( + yellow('\nWARNING: Detected problems with'), + cyan(Array.from(updatesNeeded).join(', ')) + ); + console.log( + yellow('⤷ Continuing install in'), + cyan(warningDelaySecs), + yellow('seconds...') + ); + console.log( + yellow('⤷ Press'), + cyan('Ctrl + C'), + yellow('to abort and fix...') + ); + let resolver; + const deferred = new Promise((resolverIn) => { + resolver = resolverIn; + }); + setTimeout(() => { + console.log(yellow('\nAttempting to install packages...')); + resolver(); + }, warningDelaySecs * 1000); + return deferred; } - ensureYarn(); - return checkNodeVersion().then(() => { - runGulpChecks(); - checkPythonVersion(); - checkYarnVersion(); - if (!process.env.TRAVIS && updatesNeeded.size > 0) { - console.log( - yellow('\nWARNING: Detected problems with'), - cyan(Array.from(updatesNeeded).join(', ')) - ); - console.log( - yellow('⤷ Continuing install in'), - cyan(warningDelaySecs), - yellow('seconds...') - ); - console.log( - yellow('⤷ Press'), - cyan('Ctrl + C'), - yellow('to abort and fix...') - ); - let resolver; - const deferred = new Promise((resolverIn) => { - resolver = resolverIn; - }); - setTimeout(() => { - console.log(yellow('\nAttempting to install packages...')); - resolver(); - }, warningDelaySecs * 1000); - return deferred; - } - }); } main(); diff --git a/build-system/common/ci.js b/build-system/common/ci.js new file mode 100644 index 0000000000000..c28b840802996 --- /dev/null +++ b/build-system/common/ci.js @@ -0,0 +1,253 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Provides various kinds of CI state. + * + * References: + * GitHub Actions: https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables + * CircleCI: https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables + */ + +/** + * Shorthand to extract an environment variable. + * @param {string} key + * @return {string} + */ +function env(key) { + return process.env[key] ?? ''; +} + +/** + * Returns true if this is a CI build. + * @return {boolean} + */ +function isCiBuild() { + return !!env('CI'); +} + +/** + * Returns true if this is a GitHub Actions build. + * @return {boolean} + */ +function isGithubActionsBuild() { + return !!env('GITHUB_ACTIONS'); +} + +/** + * Returns true if this is a CircleCI build. + * @return {boolean} + */ +function isCircleciBuild() { + return !!env('CIRCLECI'); +} + +/** + * Constants for reduced code size. + */ +const isGithubActions = isGithubActionsBuild(); +const isCircleci = isCircleciBuild(); + +/** + * Used to filter CircleCI PR branches created directly on the amphtml repo + * (e.g.) PRs created from the GitHub web UI. Must match `push_builds_only` + * in .circleci/config.yml. + * @param {string} branchName + * @return {boolean} + */ +function isCircleciPushBranch(branchName) { + return branchName == 'main' || /^amp-release-.*$/.test(branchName); +} + +/** + * Returns true if this is a PR build. + * @return {boolean} + */ +function isPullRequestBuild() { + return isGithubActions + ? env('GITHUB_EVENT_NAME') === 'pull_request' + : isCircleci + ? !isCircleciPushBranch(env('CIRCLE_BRANCH')) + : false; +} + +/** + * Returns true if this is a push build. + * @return {boolean} + */ +function isPushBuild() { + return isGithubActions + ? env('GITHUB_EVENT_NAME') === 'push' + : isCircleci + ? isCircleciPushBranch(env('CIRCLE_BRANCH')) + : false; +} + +/** + * Returns the name of the PR branch. + * @return {string} + */ +function ciPullRequestBranch() { + return isGithubActions + ? env('GITHUB_HEAD_REF') + : isCircleci + ? env('CIRCLE_BRANCH') + : ''; +} + +/** + * Returns the commit SHA being tested by a PR build. + * @return {string} + */ +function ciPullRequestSha() { + return isGithubActions + ? require(env('GITHUB_EVENT_PATH')).pull_request.head.sha + : isCircleci + ? env('CIRCLE_SHA1') + : ''; +} + +/** + * Returns the branch for push builds. + * @return {string} + */ +function ciPushBranch() { + return isGithubActions + ? env('GITHUB_REF') + : isCircleci + ? env('CIRCLE_BRANCH') + : ''; +} + +/** + * Returns the commit SHA being tested by a push build. + * @return {string} + */ +function ciCommitSha() { + return isGithubActions + ? env('GITHUB_SHA') + : isCircleci + ? env('CIRCLE_SHA1') + : ''; +} + +/** + * Returns the ID of the current build. + * @return {string} + */ +function ciBuildId() { + return isGithubActions + ? env('GITHUB_RUN_ID') + : isCircleci + ? env('CIRCLE_WORKFLOW_ID') + : ''; +} + +/** + * Returns the URL of the current build. + * @return {string} + */ +function ciBuildUrl() { + return isGithubActions + ? `${env('GITHUB_SERVER_URL')}/${env('GITHUB_REPOSITORY')}/actions/runs/${env('GITHUB_RUN_ID')}` // prettier-ignore + : isCircleci + ? `https://app.circleci.com/pipelines/workflows/${env('CIRCLE_WORKFLOW_ID')}` // prettier-ignore + : ''; +} + +/** + * Returns the ID of the current job. + * @return {string} + */ +function ciJobId() { + return isGithubActions + ? env('GITHUB_RUN_NUMBER') + : isCircleci + ? env('CIRCLE_JOB') + : ''; +} + +/** + * Returns the URL of the current job. + * @return {string} + */ +function ciJobUrl() { + return isGithubActions + ? // TODO(rsimha): Try to reverse engineer the GH Actions job URL from the build URL. + `${env('GITHUB_SERVER_URL')}/${env('GITHUB_REPOSITORY')}/actions/runs/${env('GITHUB_RUN_ID')}` // prettier-ignore + : isCircleci + ? env('CIRCLE_BUILD_URL') + : ''; +} + +/** + * Returns the merge commit for a CircleCI PR build. CIRCLECI_MERGE_COMMIT is + * populated by .circleci/fetch_merge_commit.sh. + * @return {string} + */ +function circleciPrMergeCommit() { + return isCircleci ? env('CIRCLECI_MERGE_COMMIT') : ''; +} + +/** + * Returns an identifier that is unique to each CircleCI job. This is different + * from the workflow ID, which is common across all jobs in a workflow. + * @return {string} + */ +function circleciBuildNumber() { + return isCircleci ? env('CIRCLE_BUILD_NUM') : ''; +} + +/** + * Returns the repo slug for the ongoing build. + * @return {string} + */ +function ciRepoSlug() { + return isGithubActions + ? env('GITHUB_REPOSITORY') + : isCircleci + ? `${env('CIRCLE_PROJECT_USERNAME')}/${env('CIRCLE_PROJECT_REPONAME')}` + : ''; +} + +/** + * Returns the commit SHA being tested by a push or PR build. + * @return {string} + */ +function ciBuildSha() { + return isPullRequestBuild() ? ciPullRequestSha() : ciCommitSha(); +} + +module.exports = { + ciBuildId, + ciBuildSha, + ciBuildUrl, + ciCommitSha, + ciJobId, + ciJobUrl, + ciPullRequestBranch, + ciPullRequestSha, + ciPushBranch, + circleciBuildNumber, + circleciPrMergeCommit, + ciRepoSlug, + isCiBuild, + isCircleciBuild, + isGithubActionsBuild, + isPullRequestBuild, + isPushBuild, +}; diff --git a/build-system/common/colors.js b/build-system/common/colors.js new file mode 100644 index 0000000000000..b437edff8187f --- /dev/null +++ b/build-system/common/colors.js @@ -0,0 +1,30 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const colors = require('kleur/colors'); + +/** + * @fileoverview kleur/color provides a collection of untyped color formatting + * functions. This file provides a generically typed wrapper for each of them in + * order to satisfy our type checks without changing any functionality. For more + * info, see https://github.com/lukeed/kleur/blob/master/colors.{mjs,d.ts}. + */ +module.exports = Object.entries(colors).reduce((map, [key, formatter]) => { + map[key] = + typeof formatter == 'function' + ? (txt) => /** @type {function} */ (formatter)(txt) + : formatter; + return map; +}, /** @type {Record string>} */ ({})); diff --git a/build-system/common/ctrlcHandler.js b/build-system/common/ctrlcHandler.js index 2129f8e9a1c22..28e526c1313dd 100644 --- a/build-system/common/ctrlcHandler.js +++ b/build-system/common/ctrlcHandler.js @@ -14,32 +14,29 @@ * limitations under the License. */ -const colors = require('ansi-colors'); -const log = require('fancy-log'); -const {execScriptAsync, exec} = require('./exec'); -const {isTravisBuild} = require('./travis'); +const colors = require('./colors'); +const {exec, execScriptAsync} = require('./exec'); +const {logLocalDev} = require('./logging'); -const {green, cyan} = colors; +const {cyan, green} = colors; const killCmd = process.platform == 'win32' ? 'taskkill /f /pid' : 'kill -KILL'; const killSuffix = process.platform == 'win32' ? '>NUL' : ''; /** * Creates an async child process that handles Ctrl + C and immediately cancels - * the ongoing `gulp` task. + * the ongoing `amp` task. * * @param {string} command * @return {number} */ exports.createCtrlcHandler = function (command) { - if (!isTravisBuild()) { - log( - green('Running'), - cyan(command) + green('. Press'), - cyan('Ctrl + C'), - green('to cancel...') - ); - } + logLocalDev( + green('Running'), + cyan(command) + green('. Press'), + cyan('Ctrl + C'), + green('to cancel...') + ); const killMessage = green('\nDetected ') + cyan('Ctrl + C') + @@ -64,7 +61,7 @@ exports.createCtrlcHandler = function (command) { /** * Exits the Ctrl C handler process. * - * @param {string} handlerProcess + * @param {string|number} handlerProcess */ exports.exitCtrlcHandler = function (handlerProcess) { const exitCmd = killCmd + ' ' + handlerProcess + ' ' + killSuffix; diff --git a/build-system/common/default-pre-push b/build-system/common/default-pre-push index 9d022c31ea8c0..25d45a48693fe 100755 --- a/build-system/common/default-pre-push +++ b/build-system/common/default-pre-push @@ -18,30 +18,30 @@ # "./build-system/common/enable-git-pre-push.sh" # # Note: The checks in this file must not take more than a few seconds to run. -# Time consuming checks that call gulp build, or run all the tests are +# Time consuming checks that call amp build, or run all the tests are # forbidden. If you'd like to add something like that to your pre-push, do so # by directly editing .git/hooks/pre-push instead of editing this default hook. GREEN() { echo -e "\033[0;32m$1\033[0m"; } CYAN() { echo -e "\033[0;36m$1\033[0m"; } -GULP_LINT_LOCAL="gulp lint --local_changes" -GULP_PRETTIFY_LOCAL="gulp prettify --local_changes" -GULP_UNIT_LOCAL="gulp unit --local_changes --headless" +AMP_LINT_LOCAL="amp lint --local_changes" +AMP_PRETTIFY_LOCAL="amp prettify --local_changes" +AMP_UNIT_LOCAL="amp unit --local_changes --headless" echo $(GREEN "Running") $(CYAN "pre-push") $(GREEN "hooks. (Run") $(CYAN "git push --no-verify") $(GREEN "to skip them.)") echo -e "\n" -echo $(GREEN "Running") $(CYAN "$GULP_LINT_LOCAL") -eval $GULP_LINT_LOCAL || exit 1 +echo $(GREEN "Running") $(CYAN "$AMP_LINT_LOCAL") +eval $AMP_LINT_LOCAL || exit 1 echo -e "\n" -echo $(GREEN "Running") $(CYAN "$GULP_PRETTIFY_LOCAL") -eval $GULP_PRETTIFY_LOCAL || exit 1 +echo $(GREEN "Running") $(CYAN "$AMP_PRETTIFY_LOCAL") +eval $AMP_PRETTIFY_LOCAL || exit 1 echo -e "\n" -echo $(GREEN "Running") $(CYAN "$GULP_UNIT_LOCAL") -eval $GULP_UNIT_LOCAL || exit 1 +echo $(GREEN "Running") $(CYAN "$AMP_UNIT_LOCAL") +eval $AMP_UNIT_LOCAL || exit 1 echo -e "\n" echo $(GREEN "Done with") $(CYAN "pre-push") $(GREEN "hooks. Pushing commits to GitHub...") diff --git a/build-system/common/diff.js b/build-system/common/diff.js new file mode 100644 index 0000000000000..e1d4455868bef --- /dev/null +++ b/build-system/common/diff.js @@ -0,0 +1,82 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const argv = require('minimist')(process.argv.slice(2)); +const tempy = require('tempy'); +const {blue, bold, cyan, red} = require('./colors'); +const {getStdout} = require('./process'); +const {log, logWithoutTimestamp} = require('./logging'); +const {writeFile} = require('fs-extra'); + +/** + * Diffs a file against content that might replace it. + * @param {string} filepath + * @param {string} content + * @param {Array=} gitDiffFlags + * @return {!Promise} + */ +const diffTentative = (filepath, content, gitDiffFlags = ['-U1']) => + tempy.write.task(content, (temporary) => + getStdout( + [ + 'git -c color.ui=always diff', + ...gitDiffFlags, + filepath, + temporary, + ].join(' ') + ) + .trim() + .replace(new RegExp(temporary, 'g'), `/${filepath}`) + ); + +/** + * Diffs a file against new content. + * If `argv.fix` is true, the file is written with the new content, otherwise + * errors out. + * @param {string} callerTask + * @param {string} filepath + * @param {string} tentative + * @param {Array=} opt_gitDiffFlags + */ +async function writeDiffOrFail( + callerTask, + filepath, + tentative, + opt_gitDiffFlags +) { + const diff = await diffTentative(filepath, tentative, opt_gitDiffFlags); + + if (!diff.length) { + return; + } + + logWithoutTimestamp(); + logWithoutTimestamp(diff); + logWithoutTimestamp(); + + if (!argv.fix) { + log(red('ERROR:'), cyan(filepath), 'is missing the changes above.'); + log('⤷ To automatically apply them, run', cyan(`amp ${callerTask} --fix`)); + throw new Error(`${filepath} is outdated`); + } + + await writeFile(filepath, tentative); + log('Wrote', bold(blue(filepath))); +} + +module.exports = { + diffTentative, + writeDiffOrFail, +}; diff --git a/build-system/common/enable-git-pre-push.sh b/build-system/common/enable-git-pre-push.sh index 3a3be2d7585ef..77135a3596c37 100755 --- a/build-system/common/enable-git-pre-push.sh +++ b/build-system/common/enable-git-pre-push.sh @@ -13,10 +13,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the license. -# + # This script adds a pre-push hook to .git/hooks/, which runs some basic tests # before running "git push". -# # To enable it, run this script: "./build-system/common/enable-git-pre-push.sh" diff --git a/build-system/common/esbuild-babel.js b/build-system/common/esbuild-babel.js new file mode 100644 index 0000000000000..f5873ca8d0a95 --- /dev/null +++ b/build-system/common/esbuild-babel.js @@ -0,0 +1,140 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const babel = require('@babel/core'); +const path = require('path'); +const {debug} = require('../compile/debug-compilation-lifecycle'); +const {TransformCache, batchedRead, md5} = require('./transform-cache'); + +/** + * Used to cache babel transforms done by esbuild. + * @const {TransformCache} + */ +let transformCache; + +/** + * Creates a babel plugin for esbuild for the given caller. Optionally enables + * caching to speed up transforms. + * @param {string} callerName + * @param {boolean} enableCache + * @param {function(): void} preSetup + * @param {function(): void} postLoad + * @return {!Object} + */ +function getEsbuildBabelPlugin( + callerName, + enableCache, + preSetup = () => {}, + postLoad = () => {} +) { + if (!transformCache) { + transformCache = new TransformCache('.babel-cache', '.js'); + } + + /** + * @param {string} filename + * @param {string} contents + * @param {string} hash + * @param {Object} babelOptions + * @return {Promise} + */ + async function transformContents(filename, contents, hash, babelOptions) { + if (enableCache) { + const cached = transformCache.get(hash); + if (cached) { + return cached; + } + } + + debug('pre-babel', filename, contents); + const promise = babel + .transformAsync(contents, babelOptions) + .then((result) => { + const {code, map} = result || {}; + debug('post-babel', filename, code, map); + return code; + }); + + if (enableCache) { + transformCache.set(hash, promise); + } + + return promise.finally(postLoad); + } + + return { + name: 'babel', + + async setup(build) { + preSetup(); + + const babelOptions = + babel.loadOptions({caller: {name: callerName}}) || {}; + const optionsHash = md5( + JSON.stringify({babelOptions, argv: process.argv.slice(2)}) + ); + + build.onLoad({filter: /\.[cm]?js$/, namespace: ''}, async (file) => { + const filename = file.path; + const {contents, hash} = await batchedRead(filename, optionsHash); + + const transformed = await transformContents( + filename, + contents, + hash, + getFileBabelOptions(babelOptions, filename) + ); + return {contents: transformed}; + }); + }, + }; +} + +const CJS_TRANSFORMS = new Set([ + 'transform-modules-commonjs', + 'proposal-dynamic-import', + 'syntax-dynamic-import', + 'proposal-export-namespace-from', + 'syntax-export-namespace-from', +]); + +/** + * @param {!Object} babelOptions + * @param {string} filename + * @return {!Object} + */ +function getFileBabelOptions(babelOptions, filename) { + // Patch for leaving files within node_modules as esm, since esbuild will break when trying + // to process a module file that contains CJS exports. This function is called after + // babel.loadOptions, therefore all of the plugins from preset-env have already been applied. + // and must be disabled individually. + if (filename.includes('node_modules')) { + const plugins = babelOptions.plugins.filter( + ({key}) => !CJS_TRANSFORMS.has(key) + ); + babelOptions = {...babelOptions, plugins}; + } + + return { + ...babelOptions, + filename, + filenameRelative: path.basename(filename), + }; +} + +module.exports = { + getEsbuildBabelPlugin, +}; diff --git a/build-system/common/exec.js b/build-system/common/exec.js index 94c3b43e6cb9f..aff451ff38109 100644 --- a/build-system/common/exec.js +++ b/build-system/common/exec.js @@ -20,49 +20,43 @@ */ const childProcess = require('child_process'); +const {log} = require('./logging'); +const {spawnProcess} = require('./process'); +const {yellow} = require('./colors'); const shellCmd = process.platform == 'win32' ? 'cmd' : '/bin/bash'; -/** - * Spawns the given command in a child process with the given options. - * - * @param {string} cmd - * @param {?Object} options - * @return {!Object} - */ -function spawnProcess(cmd, options) { - return childProcess.spawnSync(cmd, {shell: shellCmd, ...options}); -} - /** * Executes the provided command with the given options, returning the process * object. * * @param {string} cmd - * @param {?Object} options + * @param {?Object=} options * @return {!Object} */ -function exec(cmd, options) { - options = options || {'stdio': 'inherit'}; +function exec(cmd, options = {'stdio': 'inherit'}) { return spawnProcess(cmd, options); } /** - * Executes the provided shell script in an asynchronous process. + * Executes the provided shell script in an asynchronous process. Special-cases + * the AMP task runner so that it is correctly spawned on all platforms (node + * shebangs do not work on Windows). * * @param {string} script * @param {?Object} options - * @return {!Object} + * @return {!childProcess.ChildProcessWithoutNullStreams} */ function execScriptAsync(script, options) { - return childProcess.spawn(script, {shell: shellCmd, ...options}); + const scriptToSpawn = script.startsWith('amp ') ? `node ${script}` : script; + return childProcess.spawn(scriptToSpawn, {shell: shellCmd, ...options}); } /** * Executes the provided command, and terminates the program in case of failure. * * @param {string} cmd - * @param {?Object} options + * @param {?Object=} options */ function execOrDie(cmd, options) { const p = exec(cmd, options); @@ -86,47 +80,28 @@ function execWithError(cmd) { } /** - * Executes the provided command, returning the process object. + * Executes the provided command, piping the parent process' stderr, throwing + * an error with the provided message the command fails, and returns the + * process object. * @param {string} cmd - * @param {?Object} options + * @param {string} msg * @return {!Object} */ -function getOutput(cmd, options = {}) { - const p = spawnProcess(cmd, { - 'cwd': options.cwd || process.cwd(), - 'env': options.env || process.env, - 'stdio': options.stdio || 'pipe', - 'encoding': options.encoding || 'utf-8', - }); +function execOrThrow(cmd, msg) { + const p = exec(cmd, {'stdio': ['inherit', 'inherit', 'pipe']}); + if (p.status && p.status != 0) { + log(yellow('ERROR:'), msg); + const error = new Error(p.stderr); + error.status = p.status; + throw error; + } return p; } -/** - * Executes the provided command, returning its stdout. - * @param {string} cmd - * @param {?Object} options - * @return {string} - */ -function getStdout(cmd, options) { - return getOutput(cmd, options).stdout; -} - -/** - * Executes the provided command, returning its stderr. - * @param {string} cmd - * @param {?Object} options - * @return {string} - */ -function getStderr(cmd, options) { - return getOutput(cmd, options).stderr; -} - module.exports = { exec, execOrDie, execScriptAsync, execWithError, - getOutput, - getStderr, - getStdout, + execOrThrow, }; diff --git a/build-system/common/git.js b/build-system/common/git.js index 06c20cd1077c7..6e0d52e07611d 100644 --- a/build-system/common/git.js +++ b/build-system/common/git.js @@ -20,39 +20,39 @@ */ const { - isTravisBuild, - isTravisPullRequestBuild, - travisPullRequestBranch, - travisPullRequestSha, -} = require('./travis'); -const {getStdout} = require('./exec'); - -/** - * Returns the commit at which the current branch was forked off of master. - * On Travis, there is an additional merge commit, so we must pick the first of - * the boundary commits (prefixed with a -) returned by git rev-list. - * On local branches, this is merge base of the current branch off of master. + ciPullRequestBranch, + ciPullRequestSha, + isCiBuild, + isPullRequestBuild, +} = require('./ci'); +const {getStdout} = require('./process'); + +/** + * Returns the commit at which the current PR branch was forked off of the main + * branch. During CI, there is an additional merge commit, so we must pick the + * first of the boundary commits (prefixed with a -) returned by git rev-list. + * On local branches, this is merge base of the current branch off of the main + * branch. * @return {string} */ function gitBranchCreationPoint() { - if (isTravisBuild()) { - const traviPrSha = travisPullRequestSha(); + if (isPullRequestBuild()) { + const prSha = ciPullRequestSha(); return getStdout( - `git rev-list --boundary ${traviPrSha}...master | grep "^-" | head -n 1 | cut -c2-` + `git rev-list --boundary ${prSha}...origin/main | grep "^-" | head -n 1 | cut -c2-` ).trim(); } - return gitMergeBaseLocalMaster(); + return gitMergeBaseLocalMain(); } /** - * Returns the `master` parent of the merge commit (current HEAD) on Travis. - * Note: This is not the same as origin/master (a moving target), since new - * commits can be merged while a Travis build is in progress. - * See https://travis-ci.community/t/origin-master-moving-forward-between-build-stages/4189/6 + * Returns the main branch parent of the merge commit (current HEAD) during CI + * builds. This is not the same as origin/
(a moving target), since + * new commits can be merged while a CI build is in progress. * @return {string} */ -function gitTravisMasterBaseline() { - return getStdout('git merge-base origin/master HEAD').trim(); +function gitCiMainBaseline() { + return getStdout('git merge-base origin/main HEAD').trim(); } /** @@ -74,29 +74,29 @@ function gitDiffNameOnly() { } /** - * Returns the list of files changed relative to the branch point off of master, - * one on each line. + * Returns the list of files changed relative to the branch point off of the + * main branch, one on each line. * @return {!Array} */ -function gitDiffNameOnlyMaster() { - const masterBaseline = gitMasterBaseline(); - return getStdout(`git diff --name-only ${masterBaseline}`).trim().split('\n'); +function gitDiffNameOnlyMain() { + const mainBaseline = gitMainBaseline(); + return getStdout(`git diff --name-only ${mainBaseline}`).trim().split('\n'); } /** - * Returns the list of files changed relative to the branch point off of master, - * in diffstat format. + * Returns the list of files changed relative to the branch point off of the + * main branch in diffstat format. * @return {string} */ -function gitDiffStatMaster() { - const masterBaseline = gitMasterBaseline(); - return getStdout(`git -c color.ui=always diff --stat ${masterBaseline}`); +function gitDiffStatMain() { + const mainBaseline = gitMainBaseline(); + return getStdout(`git -c color.ui=always diff --stat ${mainBaseline}`); } /** * Returns a detailed log of commits included in a PR check, starting with (and - * including) the branch point off of master. Limited to commits in the past - * 30 days to keep the output sane. + * including) the branch point off of the main branch. Limited to commits in the + * past 30 days to keep the output length manageable. * * @return {string} */ @@ -111,11 +111,11 @@ function gitDiffCommitLog() { /** * Returns the list of files added by the local branch relative to the branch - * point off of master, one on each line. + * point off of the main branch, one on each line. * @return {!Array} */ -function gitDiffAddedNameOnlyMaster() { - const branchPoint = gitMergeBaseLocalMaster(); +function gitDiffAddedNameOnlyMain() { + const branchPoint = gitMergeBaseLocalMain(); return getStdout(`git diff --name-only --diff-filter=ARC ${branchPoint}`) .trim() .split('\n'); @@ -130,13 +130,14 @@ function gitDiffColor() { } /** - * Returns the full color diff of the given file relative to the branch point off of master. + * Returns the full color diff of the given file relative to the branch point + * off of the main branch. * @param {string} file * @return {string} */ -function gitDiffFileMaster(file) { - const masterBaseline = gitMasterBaseline(); - return getStdout(`git -c color.ui=always diff -U1 ${masterBaseline} ${file}`); +function gitDiffFileMain(file) { + const mainBaseline = gitMainBaseline(); + return getStdout(`git -c color.ui=always diff -U1 ${mainBaseline} ${file}`); } /** @@ -144,8 +145,8 @@ function gitDiffFileMaster(file) { * @return {string} */ function gitBranchName() { - return isTravisPullRequestBuild() - ? travisPullRequestBranch() + return isPullRequestBuild() + ? ciPullRequestBranch() : getStdout('git rev-parse --abbrev-ref HEAD').trim(); } @@ -154,8 +155,8 @@ function gitBranchName() { * @return {string} */ function gitCommitHash() { - if (isTravisPullRequestBuild()) { - return travisPullRequestSha(); + if (isPullRequestBuild()) { + return ciPullRequestSha(); } return getStdout('git rev-parse --verify HEAD').trim(); } @@ -169,7 +170,8 @@ function gitCommitterEmail() { } /** - * Returns list of commit SHAs and their cherry-pick status from master. + * Returns list of commit SHAs and their cherry-pick status from the main + * branch. * * `git cherry ` returns a list of commit SHAs. While the exact * mechanism is too complicated for this comment (run `git help cherry` for a @@ -177,16 +179,11 @@ function gitCommitterEmail() { * from are prefixed with '- ', and those that were not are prefixed * with '+ '. * - * @return {!Array<{sha: string, isCherryPick: boolean}>} + * @return {!Array} */ -function gitCherryMaster() { - return getStdout('git cherry master') - .trim() - .split('\n') - .map((line) => ({ - isCherryPick: line.substring(0, 2) == '- ', - sha: line.substring(2), - })); +function gitCherryMain() { + const stdout = getStdout('git cherry main').trim(); + return stdout ? stdout.split('\n') : []; } /** @@ -197,29 +194,31 @@ function gitCherryMaster() { * @return {string} */ function gitCommitFormattedTime(ref = 'HEAD') { + const envPrefix = process.platform == 'win32' ? 'set TZ=UTC &&' : 'TZ=UTC'; return getStdout( - `TZ=UTC git log ${ref} -1 --pretty="%cd" --date=format-local:%y%m%d%H%M%S` + `${envPrefix} git log ${ref} -1 --pretty="%cd" --date=format-local:%y%m%d%H%M%S` ).trim(); } /** - * Returns the merge base of the current branch off of master when running on - * a local workspace. + * Returns the merge base of the current branch off of the main branch when + * running on a local workspace. * @return {string} */ -function gitMergeBaseLocalMaster() { - return getStdout('git merge-base master HEAD').trim(); +function gitMergeBaseLocalMain() { + return getStdout('git merge-base main HEAD').trim(); } /** - * Returns the master baseline commit, regardless of running environment. + * Returns the baseline commit from the main branch, regardless of running + * environment. * @return {string} */ -function gitMasterBaseline() { - if (isTravisBuild()) { - return gitTravisMasterBaseline(); +function gitMainBaseline() { + if (isCiBuild()) { + return gitCiMainBaseline(); } - return gitMergeBaseLocalMaster(); + return gitMergeBaseLocalMain(); } /** @@ -235,18 +234,18 @@ function gitDiffPath(path, commit) { module.exports = { gitBranchCreationPoint, gitBranchName, - gitCherryMaster, + gitCherryMain, gitCommitFormattedTime, gitCommitHash, gitCommitterEmail, - gitDiffAddedNameOnlyMaster, + gitDiffAddedNameOnlyMain, gitDiffColor, gitDiffCommitLog, - gitDiffFileMaster, + gitDiffFileMain, gitDiffNameOnly, - gitDiffNameOnlyMaster, + gitDiffNameOnlyMain, gitDiffPath, - gitDiffStatMaster, - gitTravisMasterBaseline, + gitDiffStatMain, + gitCiMainBaseline, shortSha, }; diff --git a/build-system/common/logging.js b/build-system/common/logging.js new file mode 100644 index 0000000000000..765b8e81f63d0 --- /dev/null +++ b/build-system/common/logging.js @@ -0,0 +1,119 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const {bold, gray, yellow} = require('./colors'); +const {isCiBuild} = require('./ci'); + +/** + * Used by tests to wrap progress dots. Attempts to match the terminal width + * during local development and defaults to 150 if it couldn't be determined. + */ +const dotWrappingWidth = isCiBuild() ? 150 : process.stdout.columns ?? 150; + +/** + * Used by CI job scripts to print a prefix before top-level logging lines. + */ +let loggingPrefix = ''; + +/** + * Logs messages with a timestamp. The timezone suffix is dropped. + * @param {...string} messages + */ +function log(...messages) { + const timestamp = new Date().toTimeString().split(' ')[0]; + const prefix = `[${gray(timestamp)}]`; + console.log(prefix, ...messages); +} + +/** + * Sets the logging prefix for the ongoing PR check job + * @param {string} prefix + */ +function setLoggingPrefix(prefix) { + loggingPrefix = prefix; +} + +/** + * Returns the formatted logging prefix for the ongoing PR check job + * @return {string} + */ +function getLoggingPrefix() { + return bold(yellow(loggingPrefix)); +} + +/** + * Logs messages only during local development + * @param {...string} messages + */ +function logLocalDev(...messages) { + if (!isCiBuild()) { + log(...messages); + } +} + +/** + * Logs messages on the same line to indicate progress + * @param {...string} messages + */ +function logOnSameLine(...messages) { + if (!isCiBuild() && process.stdout.isTTY) { + process.stdout.moveCursor(0, -1); + process.stdout.cursorTo(0); + process.stdout.clearLine(0); + } + log(...messages); +} + +/** + * Logs messages on the same line only during local development + * @param {...string} messages + */ +function logOnSameLineLocalDev(...messages) { + if (!isCiBuild()) { + logOnSameLine(...messages); + } +} + +/** + * Logs messages without a timestamp + * @param {...string} messages + */ +function logWithoutTimestamp(...messages) { + console.log(...messages); +} + +/** + * Logs messages without a timestamp only during local development + * @param {...string} messages + */ +function logWithoutTimestampLocalDev(...messages) { + if (!isCiBuild()) { + console.log(...messages); + } +} + +module.exports = { + dotWrappingWidth, + getLoggingPrefix, + log, + logLocalDev, + logOnSameLine, + logOnSameLineLocalDev, + logWithoutTimestamp, + logWithoutTimestampLocalDev, + setLoggingPrefix, +}; diff --git a/build-system/common/npm-checks.js b/build-system/common/npm-checks.js new file mode 100644 index 0000000000000..fcdcb30a19f1c --- /dev/null +++ b/build-system/common/npm-checks.js @@ -0,0 +1,88 @@ +/** + * Copyright 2019 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. + */ +'use strict'; + +/** + * @fileoverview + * This script makes sure that package.json and package-lock.json are in sync + * and up to date. + */ + +const fs = require('fs-extra'); +const path = require('path'); +const {cyan, red} = require('./colors'); +const {exec} = require('./exec'); +const {gitDiffColor, gitDiffNameOnly} = require('./git'); +const {log, logWithoutTimestamp} = require('./logging'); + +/** + * Ensures that the package files in the given directory were generated with a + * compatible version of npm and do not have any missing changes. Defaults to + * the repo root when a directory isn't specified. + * + * @param {string=} dir + */ +function runNpmChecks(dir = '.') { + const relativeDir = path.relative(process.cwd(), dir); + const targetDir = dir == '.' ? 'the repo root' : cyan(relativeDir); + log('Running', cyan('npm'), 'checks under', targetDir + '...'); + const packageLockFile = path.join(relativeDir, 'package-lock.json'); + + // Check the lockfile version. + if (fs.readJsonSync(packageLockFile).lockfileVersion != 1) { + log( + red('ERROR:'), + cyan(packageLockFile), + 'was generated with an incorrect version of', + cyan('npm') + '.' + ); + log( + '⤷ To fix this, make sure you are using the version of', + cyan('npm'), + 'that came pre-installed with the latest LTS version of', + cyan('node') + '.' + ); + throw new Error('Incorrect lockfile version'); + } + + // Run `npm i` and check for changes. (`npm ci` doesn't update lockfiles.) + const installCmd = 'npm install' + (dir == '.' ? '' : ` --prefix ${dir}`); + exec(installCmd, {'stdio': 'ignore'}); + const filesChanged = gitDiffNameOnly(); + if (filesChanged.includes(packageLockFile)) { + log( + red('ERROR:'), + 'This PR did not properly update', + cyan(packageLockFile) + '.' + ); + log( + '⤷ To fix this, sync your branch to', + cyan('ampproject/amphtml/main') + ', run', + cyan('npm install'), + 'under', + targetDir + ', and push a new commit containing the changes.' + ); + log('Expected changes:'); + logWithoutTimestamp(gitDiffColor()); + throw new Error('Lockfile not updated'); + } + + log('All', cyan('npm'), 'checks passed.', dir == '.' ? '\n' : ''); +} + +module.exports = { + runNpmChecks, +}; diff --git a/build-system/common/process.js b/build-system/common/process.js new file mode 100644 index 0000000000000..88ad0b06391ac --- /dev/null +++ b/build-system/common/process.js @@ -0,0 +1,82 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Provides functions for executing tasks in a child process. + * Separated from `exec` to allow import before `npm` installs dependencies. + */ + +const childProcess = require('child_process'); + +const shellCmd = process.platform == 'win32' ? 'cmd' : '/bin/bash'; + +/** + * Spawns the given command in a child process with the given options. + * Special-cases the AMP task runner so that it is correctly spawned on all + * platforms (node shebangs do not work on Windows). + * + * @param {string} cmd + * @param {?Object} options + * @return {!Object} + */ +function spawnProcess(cmd, options) { + const cmdToSpawn = cmd.startsWith('amp ') ? `node ${cmd}` : cmd; + return childProcess.spawnSync(cmdToSpawn, {shell: shellCmd, ...options}); +} + +/** + * Executes the provided command, returning the process object. + * @param {string} cmd + * @param {?Object=} options + * @return {!Object} + */ +function getOutput(cmd, options = {}) { + const p = spawnProcess(cmd, { + 'cwd': options.cwd || process.cwd(), + 'env': options.env || process.env, + 'stdio': options.stdio || 'pipe', + 'encoding': options.encoding || 'utf-8', + }); + return p; +} + +/** + * Executes the provided command, returning its stdout. + * @param {string} cmd + * @param {?Object=} options + * @return {string} + */ +function getStdout(cmd, options) { + return getOutput(cmd, options).stdout; +} + +/** + * Executes the provided command, returning its stderr. + * @param {string} cmd + * @param {?Object=} options + * @return {string} + */ +function getStderr(cmd, options) { + return getOutput(cmd, options).stderr; +} + +module.exports = { + getOutput, + getStderr, + getStdout, + spawnProcess, +}; diff --git a/build-system/common/sa-travis-key.json.enc b/build-system/common/sa-travis-key.json.enc deleted file mode 100644 index 00306b8925bbe..0000000000000 Binary files a/build-system/common/sa-travis-key.json.enc and /dev/null differ diff --git a/build-system/common/transform-cache.js b/build-system/common/transform-cache.js new file mode 100644 index 0000000000000..8becae997682c --- /dev/null +++ b/build-system/common/transform-cache.js @@ -0,0 +1,131 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const crypto = require('crypto'); +const fs = require('fs-extra'); +const path = require('path'); + +/** + * Cache for storing transformed files on both memory and on disk. + */ +class TransformCache { + /** + * @param {string} cacheName + * @param {string} fileExtension + */ + constructor(cacheName, fileExtension) { + /** @type {string} */ + this.fileExtension = fileExtension; + + /** @type {string} */ + this.cacheDir = path.resolve(__dirname, '..', '..', cacheName); + fs.ensureDirSync(this.cacheDir); + + /** @type {Map>} */ + this.transformMap = new Map(); + + /** @type {Set} */ + this.fsCache = new Set(fs.readdirSync(this.cacheDir)); + } + + /** + * @param {string} hash + * @return {null|Promise} + */ + get(hash) { + const cached = this.transformMap.get(hash); + if (cached) { + return cached; + } + const filename = hash + this.fileExtension; + if (this.fsCache.has(filename)) { + const transformedPromise = fs.readFile( + path.join(this.cacheDir, filename) + ); + this.transformMap.set(hash, transformedPromise); + return transformedPromise; + } + return null; + } + + /** + * @param {string} hash + * @param {Promise} transformPromise + */ + set(hash, transformPromise) { + if (this.transformMap.has(hash)) { + throw new Error('Read race: Attempting to transform a file twice.'); + } + this.transformMap.set(hash, transformPromise); + const filepath = path.join(this.cacheDir, hash) + this.fileExtension; + transformPromise.then((contents) => fs.outputFile(filepath, contents)); + } +} + +/** + * Returns the md5 hash of provided args. + * + * @param {...(string|Buffer)} args + * @return {string} + */ +function md5(...args) { + const hash = crypto.createHash('md5'); + for (const a of args) { + hash.update(a); + } + return hash.digest('hex'); +} + +/** + * Used to cache file reads, since some (esbuild) will have multiple "loads" per + * file. This batches consecutive reads into a single, and then clears its cache + * item for the next load. + * @private @const {!Map>} + */ +const readCache = new Map(); + +/** + * Returns the string contents and hash of the file at the specified path. If + * multiple reads are requested for the same file before the first read has + * completed, the result will be reused. + * + * @param {string} path + * @param {string=} optionsHash + * @return {{contents: string, hash: string}} + */ +function batchedRead(path, optionsHash) { + let read = readCache.get(path); + if (!read) { + read = fs + .readFile(path) + .then((contents) => ({ + contents, + hash: md5(contents, optionsHash ?? ''), + })) + .finally(() => { + readCache.delete(path); + }); + readCache.set(path, read); + } + + return read; +} + +module.exports = { + batchedRead, + md5, + TransformCache, +}; diff --git a/build-system/common/travis.js b/build-system/common/travis.js deleted file mode 100644 index 6cefe84b18698..0000000000000 --- a/build-system/common/travis.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -const colors = require('ansi-colors'); -const log = require('fancy-log'); - -const {red, cyan} = colors; - -/** - * @fileoverview Provides functions that extract various kinds of Travis state. - */ - -/** - * Returns true if this is a Travis build. - * @return {boolean} - */ -function isTravisBuild() { - return !!process.env.TRAVIS; -} - -/** - * Returns true if this is a Travis PR build. - * @return {boolean} - */ -function isTravisPullRequestBuild() { - return isTravisBuild() && process.env.TRAVIS_EVENT_TYPE === 'pull_request'; -} - -/** - * Returns true if this is a Travis Push build. - * @return {boolean} - */ -function isTravisPushBuild() { - return isTravisBuild() && process.env.TRAVIS_EVENT_TYPE === 'push'; -} - -/** - * Returns the build number of the ongoing Travis build. - * @return {string} - */ -function travisBuildNumber() { - if (!isTravisBuild()) { - log( - red('ERROR:'), - 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_BUILD_NUMBER') + '.' - ); - } - return process.env.TRAVIS_BUILD_NUMBER; -} - -/** - * Returns the job number of the ongoing Travis job. - * @return {string} - */ -function travisJobNumber() { - if (!isTravisBuild()) { - log( - red('ERROR:'), - 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_JOB_NUMBER') + '.' - ); - } - return process.env.TRAVIS_JOB_NUMBER; -} - -/** - * Return the job URL of the ongoing Travis job. - * @return {string} - */ -function travisJobUrl() { - if (!isTravisBuild()) { - log( - red('ERROR:'), - 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_JOB_WEB_URL') + '.' - ); - } - return process.env.TRAVIS_JOB_WEB_URL; -} - -/** - * Returns the repo slug associated with the ongoing Travis build. - * @return {string} - */ -function travisRepoSlug() { - if (!isTravisBuild()) { - log( - red('ERROR:'), - 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_REPO_SLUG') + '.' - ); - } - return process.env.TRAVIS_REPO_SLUG; -} - -/** - * Returns the commit SHA being tested by the ongoing Travis PR build. - * @return {string} - */ -function travisPullRequestSha() { - if (!isTravisPullRequestBuild()) { - log( - red('ERROR:'), - 'This is not a Travis PR build. Cannot get', - cyan('process.env.TRAVIS_PULL_REQUEST_SHA') + '.' - ); - } - return process.env.TRAVIS_PULL_REQUEST_SHA; -} - -/** - * Returns the name of the branch being tested by the ongoing Travis PR build. - * @return {string} - */ -function travisPullRequestBranch() { - if (!isTravisPullRequestBuild()) { - log( - red('ERROR:'), - 'This is not a Travis PR build. Cannot get', - cyan('process.env.TRAVIS_PULL_REQUEST_BRANCH') + '.' - ); - } - return process.env.TRAVIS_PULL_REQUEST_BRANCH; -} - -/** - * Returns the Travis branch for push builds. - * @return {string} - */ -function travisPushBranch() { - if (!isTravisPushBuild()) { - log( - red('ERRROR:'), - 'This is not a Travis push build. Cannot get', - cyan('process.env.TRAVIS_BRANCH') + '.' - ); - } - return process.env.TRAVIS_BRANCH; -} - -/** - * Returns the commit SHA being tested by the ongoing Travis build. - * @return {string} - */ -function travisCommitSha() { - if (!isTravisBuild()) { - log( - red('ERROR:'), - 'This is not a Travis build. Cannot get', - cyan('process.env.TRAVIS_COMMIT') + '.' - ); - } - return process.env.TRAVIS_COMMIT; -} - -module.exports = { - isTravisBuild, - isTravisPullRequestBuild, - isTravisPushBuild, - travisBuildNumber, - travisCommitSha, - travisJobNumber, - travisJobUrl, - travisPullRequestBranch, - travisPullRequestSha, - travisPushBranch, - travisRepoSlug, -}; diff --git a/build-system/common/update-packages.js b/build-system/common/update-packages.js new file mode 100644 index 0000000000000..946cfd2fc26b9 --- /dev/null +++ b/build-system/common/update-packages.js @@ -0,0 +1,303 @@ +/** + * Copyright 2017 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. + */ +'use strict'; + +const checkDependencies = require('check-dependencies'); +const del = require('del'); +const fs = require('fs-extra'); +const path = require('path'); +const {cyan, red} = require('./colors'); +const {execOrDie} = require('./exec'); +const {getOutput} = require('./process'); +const {isCiBuild} = require('./ci'); +const {log, logLocalDev} = require('./logging'); +const {runNpmChecks} = require('./npm-checks'); + +/** + * Writes the given contents to the patched file if updated + * @param {string} patchedName Name of patched file + * @param {string} file Contents to write + */ +function writeIfUpdated(patchedName, file) { + if (!fs.existsSync(patchedName) || fs.readFileSync(patchedName) != file) { + fs.writeFileSync(patchedName, file); + logLocalDev('Patched', cyan(patchedName)); + } +} + +/** + * Patches Web Animations polyfill by wrapping its body into `install` function. + * This gives us an option to call polyfill directly on the main window + * or a friendly iframe. + */ +function patchWebAnimations() { + // Copies web-animations-js into a new file that has an export. + const patchedName = + 'node_modules/web-animations-js/web-animations.install.js'; + let file = fs + .readFileSync('node_modules/web-animations-js/web-animations.min.js') + .toString(); + // Replace |requestAnimationFrame| with |window|. + file = file.replace(/requestAnimationFrame/g, function (a, b) { + if (file.charAt(b - 1) == '.') { + return a; + } + return 'window.' + a; + }); + // Fix web-animations-js code that violates strict mode. + // See https://github.com/ampproject/amphtml/issues/18612 and + // https://github.com/web-animations/web-animations-js/issues/46 + file = file.replace(/b.true=a/g, 'b?b.true=a:true'); + + // Fix web-animations-js code that attempts to write a read-only property. + // See https://github.com/ampproject/amphtml/issues/19783 and + // https://github.com/web-animations/web-animations-js/issues/160 + file = file.replace(/this\._isFinished\s*=\s*\!0,/, ''); + + // Wrap the contents inside the install function. + file = + 'export function installWebAnimations(window) {\n' + + 'var document = window.document;\n' + + file + + '\n' + + '}\n'; + writeIfUpdated(patchedName, file); +} + +/** + * Patches Intersection Observer polyfill by wrapping its body into `install` + * function. + * This gives us an option to control when and how the polyfill is installed. + * The polyfill can only be installed on the root context. + */ +function patchIntersectionObserver() { + // Copies intersection-observer into a new file that has an export. + const patchedName = + 'node_modules/intersection-observer/intersection-observer.install.js'; + let file = fs + .readFileSync('node_modules/intersection-observer/intersection-observer.js') + .toString(); + + // Wrap the contents inside the install function. + file = `export function installIntersectionObserver() {\n${file}\n}\n`; + writeIfUpdated(patchedName, file); +} + +/** + * Patches Resize Observer polyfill by wrapping its body into `install` + * function. + * This gives us an option to control when and how the polyfill is installed. + * The polyfill can only be installed on the root context. + */ +function patchResizeObserver() { + // Copies intersection-observer into a new file that has an export. + const patchedName = + 'node_modules/resize-observer-polyfill/ResizeObserver.install.js'; + let file = fs + .readFileSync( + 'node_modules/resize-observer-polyfill/dist/ResizeObserver.js' + ) + .toString(); + + // Wrap the contents inside the install function. + file = `export function installResizeObserver(global) {\n${file}\n}\n` + // For some reason Closure fails on this three lines. Babel is fine. + .replace( + "typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :", + '' + ) + .replace( + "typeof define === 'function' && define.amd ? define(factory) :", + '' + ) + .replace('}(this, (function () {', '}(global, (function () {'); + writeIfUpdated(patchedName, file); +} + +/** + * Patches Shadow DOM polyfill by wrapping its body into `install` + * function. + * This gives us an option to control when and how the polyfill is installed. + * The polyfill can only be installed on the root context. + */ +function patchShadowDom() { + // Copies webcomponents-sd into a new file that has an export. + const patchedName = + 'node_modules/@webcomponents/webcomponentsjs/bundles/webcomponents-sd.install.js'; + + let file = '(function() {'; + // HTMLElement is replaced, but the original needs to be used for the polyfill + // since it manipulates "own" properties. See `src/polyfills/custom-element.js`. + file += 'var HTMLElementOrig = window.HTMLElementOrig || window.HTMLElement;'; + file += 'window.HTMLElementOrig = HTMLElementOrig;'; + file += ` + (function() { + var origContains = document.contains; + if (origContains) { + Object.defineProperty(document, '__shady_native_contains', {value: origContains}); + } + Object.defineProperty(document, 'contains', { + configurable: true, + value: function(node) { + if (node === this) { + return true; + } + if (this.documentElement) { + return this.documentElement.contains(node); + } + return false; + } + }); + })(); + `; + + /** + * @param {string} file + * @return {string} + */ + function transformScript(file) { + // Use the HTMLElement from above. + file = file.replace(/\bHTMLElement\b/g, 'HTMLElementOrig'); + return file; + } + + // Relevant DOM polyfills + file += transformScript( + fs + .readFileSync( + 'node_modules/@webcomponents/webcomponentsjs/bundles/webcomponents-pf_dom.js' + ) + .toString() + ); + file += transformScript( + fs + .readFileSync( + 'node_modules/@webcomponents/webcomponentsjs/bundles/webcomponents-sd.js' + ) + .toString() + ); + file += '})();'; + + // ESM binaries fail on this expression. + file = file.replace( + '"undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this', + 'window' + ); + // Disable any integration with CE. + file = file.replace(/window\.customElements/g, 'window.__customElements'); + + writeIfUpdated(patchedName, file); +} +/** + * Adds a missing export statement to the preact module. + */ +function patchPreact() { + fs.ensureDirSync('node_modules/preact/dom'); + const file = `export { render, hydrate } from 'preact';`; + writeIfUpdated('node_modules/preact/dom/index.js', file); +} + +/** + * Deletes the map file for rrule, which breaks closure compiler. + * TODO(rsimha): Remove this workaround after a fix is merged for + * https://github.com/google/closure-compiler/issues/3720. + */ +function removeRruleSourcemap() { + const rruleMapFile = 'node_modules/rrule/dist/es5/rrule.js.map'; + if (fs.existsSync(rruleMapFile)) { + del.sync(rruleMapFile); + logLocalDev('Deleted', cyan(rruleMapFile)); + } +} + +/** + * Checks if all packages are current, and if not, runs `npm install`. + */ +function updateDeps() { + const results = checkDependencies.sync({ + verbose: true, + log: () => {}, + error: console.log, + }); + if (results.depsWereOk) { + log('All packages in', cyan('node_modules'), 'are up to date.'); + } else { + log('Running', cyan('npm install') + '...'); + execOrDie('npm install'); + } +} + +/** + * This function updates repo root packages. + * + * 1. Update root-level packages if necessary. + * 2. Apply various custom patches if not already applied. + * 3. During CI, make sure that the root package files were correctly updated. + * + * During local development, work is done only during first time install and + * soon after a repo sync. At all other times, this function is a no-op and + * returns almost instantly. + */ +function updatePackages() { + updateDeps(); + patchWebAnimations(); + patchIntersectionObserver(); + patchResizeObserver(); + patchShadowDom(); + patchPreact(); + removeRruleSourcemap(); + if (isCiBuild()) { + runNpmChecks(); + } +} + +/** + * This function updates the packages in a given task directory. + * + * 1. During CI, do a clean install. + * 2. During local development, do an incremental install if necessary. + * 3. Since install scripts can be async, `await` the process object. + * 4. Since script output is noisy, capture and print the stderr if needed. + * 5. During CI, if not skipped, ensure package files were correctly updated. + * + * @param {string} dir + * @param {boolean=} skipNpmChecks + * @return {Promise} + */ +async function updateSubpackages(dir, skipNpmChecks = false) { + const results = checkDependencies.sync({packageDir: dir}); + const relativeDir = path.relative(process.cwd(), dir); + if (results.depsWereOk) { + const nodeModulesDir = path.join(relativeDir, 'node_modules'); + log('All packages in', cyan(nodeModulesDir), 'are up to date.'); + } else { + const installCmd = isCiBuild() ? 'npm ci' : 'npm install'; + log('Running', cyan(installCmd), 'in', cyan(relativeDir) + '...'); + const output = await getOutput(`${installCmd} --prefix ${dir}`); + if (output.status !== 0) { + log(red('ERROR:'), output.stderr); + throw new Error('Installation failed'); + } + } + if (isCiBuild() && !skipNpmChecks) { + runNpmChecks(dir); + } +} + +module.exports = { + updatePackages, + updateSubpackages, +}; diff --git a/build-system/common/utils.js b/build-system/common/utils.js index 416ddc21eaaa7..da3a269d50ace 100644 --- a/build-system/common/utils.js +++ b/build-system/common/utils.js @@ -15,27 +15,28 @@ */ const argv = require('minimist')(process.argv.slice(2)); +const experimentsConfig = require('../global-configs/experiments-config.json'); const fs = require('fs-extra'); const globby = require('globby'); -const log = require('fancy-log'); -const path = require('path'); const {clean} = require('../tasks/clean'); +const {cyan, green, red, yellow} = require('./colors'); +const {default: ignore} = require('ignore'); const {doBuild} = require('../tasks/build'); const {doDist} = require('../tasks/dist'); -const {execOrDie} = require('./exec'); -const {gitDiffNameOnlyMaster} = require('./git'); -const {green, cyan, yellow} = require('ansi-colors'); -const {isTravisBuild} = require('./travis'); - -const ROOT_DIR = path.resolve(__dirname, '../../'); +const {gitDiffNameOnlyMain} = require('./git'); +const {log, logLocalDev} = require('./logging'); /** * Performs a clean build of the AMP runtime in testing mode. - * Used by `gulp e2e|integration|visual_diff`. + * Used by `amp e2e|integration|visual_diff`. + * + * @param {boolean} opt_compiled pass true to build the compiled runtime + * (`amp dist` instead of `amp build`). Otherwise uses the value of + * --compiled to determine which build to generate. */ -async function buildRuntime() { +async function buildRuntime(opt_compiled = false) { await clean(); - if (argv.compiled) { + if (argv.compiled || opt_compiled === true) { await doDist({fortesting: true}); } else { await doBuild({fortesting: true}); @@ -43,29 +44,39 @@ async function buildRuntime() { } /** - * Logs a message on the same line to indicate progress - * - * @param {string} message + * Extracts and validates the config for the given experiment. + * @param {string} experiment + * @return {?Object} */ -function logOnSameLine(message) { - if (!isTravisBuild() && process.stdout.isTTY) { - process.stdout.moveCursor(0, -1); - process.stdout.cursorTo(0); - process.stdout.clearLine(); - } - log(message); +function getExperimentConfig(experiment) { + const config = experimentsConfig[experiment]; + const valid = + config?.name && + config?.define_experiment_constant && + config?.expiration_date_utc && + new Number(new Date(config.expiration_date_utc)) >= Date.now(); + return valid ? config : null; +} + +/** + * Returns the names of all valid experiments. + * @return {!Array} + */ +function getValidExperiments() { + return Object.keys(experimentsConfig).filter(getExperimentConfig); } /** * Gets the list of files changed on the current branch that match the given - * array of glob patterns + * array of glob patterns using the given options. * * @param {!Array} globs + * @param {!Object} options * @return {!Array} */ -function getFilesChanged(globs) { - const allFiles = globby.sync(globs, {dot: true}); - return gitDiffNameOnlyMaster().filter((changedFile) => { +function getFilesChanged(globs, options) { + const allFiles = globby.sync(globs, options); + return gitDiffNameOnlyMain().filter((changedFile) => { return fs.existsSync(changedFile) && allFiles.includes(changedFile); }); } @@ -77,53 +88,72 @@ function getFilesChanged(globs) { * @return {!Array} */ function logFiles(files) { - if (!isTravisBuild()) { - log(green('INFO: ') + 'Checking the following files:'); - for (const file of files) { - log(cyan(file)); - } + logLocalDev(green('INFO: ') + 'Checking the following files:'); + for (const file of files) { + logLocalDev(cyan(file)); } return files; } /** - * Extracts the list of files from argv.files. + * Extracts the list of files from argv.files. Throws an error if no matching + * files were found. * * @return {Array} */ function getFilesFromArgv() { - return argv.files - ? globby.sync(argv.files.split(',').map((s) => s.trim())) - : []; + if (!argv.files) { + return []; + } + // TODO(#30223): globby only takes posix globs. Find a Windows alternative. + const toPosix = (str) => str.replace(/\\\\?/g, '/'); + const globs = Array.isArray(argv.files) ? argv.files : argv.files.split(','); + const allFiles = []; + for (const glob of globs) { + const files = globby.sync(toPosix(glob.trim())); + if (files.length == 0) { + log(red('ERROR:'), 'Argument', cyan(glob), 'matched zero files.'); + throw new Error('Argument matched zero files.'); + } + allFiles.push(...files); + } + return allFiles; } /** * Gets a list of files to be checked based on command line args and the given - * file matching globs. Used by tasks like prettify, check-links, etc. + * file matching globs. Used by tasks like prettify, lint, check-links, etc. + * Optionally takes in options for globbing and a file containing ignore rules. * * @param {!Array} globs * @param {Object=} options + * @param {string=} ignoreFile * @return {!Array} */ -function getFilesToCheck(globs, options = {}) { +function getFilesToCheck(globs, options = {}, ignoreFile = undefined) { + const ignored = ignore(); + if (ignoreFile) { + const ignoreRules = fs.readFileSync(ignoreFile, 'utf8'); + ignored.add(ignoreRules); + } if (argv.files) { - return logFiles(getFilesFromArgv()); + return logFiles(ignored.filter(getFilesFromArgv())); } if (argv.local_changes) { - const filesChanged = getFilesChanged(globs); + const filesChanged = ignored.filter(getFilesChanged(globs, options)); if (filesChanged.length == 0) { log(green('INFO: ') + 'No files to check in this PR'); return []; } return logFiles(filesChanged); } - return globby.sync(globs, options); + return ignored.filter(globby.sync(globs, options)); } /** * Ensures that a target is only called with `--files` or `--local_changes` * - * @param {string} taskName name of the gulp task. + * @param {string} taskName name of the amp task. * @return {boolean} if the use is valid. */ function usesFilesOrLocalChanges(taskName) { @@ -132,13 +162,13 @@ function usesFilesOrLocalChanges(taskName) { log( yellow('NOTE 1:'), 'It is infeasible for', - cyan(`gulp ${taskName}`), + cyan(`amp ${taskName}`), 'to check all files in the repo at once.' ); log( yellow('NOTE 2:'), 'Please run', - cyan(`gulp ${taskName}`), + cyan(`amp ${taskName}`), 'with', cyan('--files'), 'or', @@ -148,27 +178,11 @@ function usesFilesOrLocalChanges(taskName) { return validUsage; } -/** - * Runs 'yarn' to install packages in a given directory. - * - * @param {string} dir - */ -function installPackages(dir) { - log( - 'Running', - cyan('yarn'), - 'to install packages in', - cyan(path.relative(ROOT_DIR, dir)) + '...' - ); - execOrDie(`npx yarn --cwd ${dir}`, {'stdio': 'ignore'}); -} - module.exports = { buildRuntime, - getFilesChanged, + getExperimentConfig, + getValidExperiments, getFilesFromArgv, getFilesToCheck, - installPackages, - logOnSameLine, usesFilesOrLocalChanges, }; diff --git a/build-system/compile/OWNERS b/build-system/compile/OWNERS index 635f2ab0686f5..1c128a0a10473 100644 --- a/build-system/compile/OWNERS +++ b/build-system/compile/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ @@ -7,10 +7,18 @@ owners: [{name: 'ampproject/wg-infra'}], }, { - pattern: '{bundles.config,sources}.js', + pattern: 'sources.js', owners: [ {name: 'ampproject/wg-infra'}, - {name: 'ampproject/wg-runtime'}, + {name: 'ampproject/wg-performance'}, + ], + }, + { + pattern: 'bundles.config.*', + owners: [ + {name: 'ampproject/wg-bento'}, + {name: 'ampproject/wg-components'}, + {name: 'ampproject/wg-infra'}, {name: 'ampproject/wg-performance'}, ], }, diff --git a/build-system/compile/bundles.config.extensions.json b/build-system/compile/bundles.config.extensions.json new file mode 100644 index 0000000000000..981bd3054b9cf --- /dev/null +++ b/build-system/compile/bundles.config.extensions.json @@ -0,0 +1,728 @@ +[ + {"name": "amp-3d-gltf", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-3q-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-access", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-access-laterpay", + "version": ["0.1", "0.2"], + "latestVersion": "0.2", + "options": {"hasCss": true} + }, + {"name": "amp-access-poool", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-access-scroll", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-accordion", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-accordion", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + {"name": "amp-action-macro", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-ad", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-ad-custom", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-ad-exit", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-ad-network-adsense-impl", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-ad-network-adzerk-impl", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-ad-network-doubleclick-impl", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-ad-network-fake-impl", + "version": "0.1", + "latestVersion": "0.1" + }, + {"name": "amp-ad-network-nws-impl", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-ad-network-valueimpression-impl", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-addthis", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-analytics", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-anim", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-animation", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-animation-polyfill", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-apester-media", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-app-banner", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-audio", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-auto-ads", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-auto-lightbox", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-autocomplete", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-base-carousel", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-base-carousel", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + {"name": "amp-beopinion", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-bind", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-bodymovin-animation", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-brid-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-brightcove", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-byside-content", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-cache-url", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-call-tracking", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-carousel", + "version": ["0.1", "0.2"], + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-connatix-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-consent", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-crypto-polyfill", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-dailymotion", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-date-countdown", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-date-countdown", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "npm": true + } + }, + { + "name": "amp-date-display", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-date-display", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "npm": true + } + }, + { + "name": "amp-date-picker", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-delight-player", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-dynamic-css-classes", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-embedly-card", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-experiment", "version": ["0.1", "1.0"], "latestVersion": "0.1"}, + {"name": "amp-facebook", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-facebook-comments", + "version": ["0.1", "1.0"], + "latestVersion": "0.1" + }, + {"name": "amp-facebook-like", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-facebook-page", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-fit-text", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-fit-text", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + {"name": "amp-font", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-form", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-fx-collection", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-fx-flying-carpet", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-geo", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-gfycat", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-gist", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-google-assistant-assistjs", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-google-document-embed", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-gwd-animation", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-hulu", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-iframe", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-iframely", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-ima-video", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-image-lightbox", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-image-slider", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-image-viewer", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-imgur", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-inline-gallery", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "cssBinaries": [ + "amp-inline-gallery", + "amp-inline-gallery-captions", + "amp-inline-gallery-pagination", + "amp-inline-gallery-slide", + "amp-inline-gallery-thumbnails" + ] + } + }, + { + "name": "amp-inline-gallery", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "cssBinaries": ["amp-inline-gallery-pagination"], + "npm": true + } + }, + {"name": "amp-inputmask", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-instagram", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-instagram", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + { + "name": "amp-install-serviceworker", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-intersection-observer-polyfill", + "version": "0.1", + "latestVersion": "0.1" + }, + {"name": "amp-izlesene", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-jwplayer", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-kaltura-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-lightbox", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-lightbox", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + { + "name": "amp-lightbox-gallery", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-link-rewriter", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-list", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-live-list", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-loader", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-mathml", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-mega-menu", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-megaphone", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-minute-media-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-mowplayer", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-mraid", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-mustache", "version": ["0.1", "0.2"], "latestVersion": "0.2"}, + { + "name": "amp-nested-menu", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-next-page", + "version": ["0.1", "1.0"], + "latestVersion": "1.0", + "options": {"hasCss": true} + }, + {"name": "amp-nexxtv-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-o2-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-onetap-google", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-ooyala-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-orientation-observer", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-pan-zoom", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-pinterest", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-playbuzz", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-position-observer", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-powr-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-reach-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-recaptcha-input", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-redbull-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-reddit", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-render", + "version": "1.0", + "latestVersion": "1.0" + }, + { + "name": "amp-resize-observer-polyfill", + "version": "0.1", + "latestVersion": "0.1" + }, + {"name": "amp-riddle-quiz", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-script", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-selector", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-selector", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + { + "name": "amp-shadow-dom-polyfill", + "version": "0.1", + "latestVersion": "0.1", + "options": {"noWrapper": true} + }, + { + "name": "amp-sidebar", + "version": ["0.1", "0.2", "1.0"], + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-skimlinks", "version": "0.1", "latestVersion": "0.1"}, + { + "// 1": "`amp-slides` is deprecated", + "// 2": "Please use `` instead.", + "name": "amp-slides", + "version": "0.1", + "latestVersion": "0.1" + }, + {"name": "amp-smartlinks", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-social-share", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-social-share", + "version": "1.0", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-soundcloud", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-springboard-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-standalone", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-sticky-ad", + "version": "1.0", + "latestVersion": "1.0", + "options": {"hasCss": true} + }, + { + "name": "amp-story", + "version": "1.0", + "latestVersion": "1.0", + "options": { + "hasCss": true, + "cssBinaries": [ + "amp-story-consent", + "amp-story-draggable-drawer-header", + "amp-story-hint", + "amp-story-info-dialog", + "amp-story-open-page-attachment", + "amp-story-share", + "amp-story-share-menu", + "amp-story-system-layer", + "amp-story-tooltip", + "amp-story-unsupported-browser-layer", + "amp-story-viewport-warning-layer" + ] + } + }, + { + "name": "amp-story-360", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-story-auto-ads", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "cssBinaries": [ + "amp-story-auto-ads-ad-badge", + "amp-story-auto-ads-attribution", + "amp-story-auto-ads-cta-button", + "amp-story-auto-ads-inabox", + "amp-story-auto-ads-progress-bar", + "amp-story-auto-ads-shared" + ] + } + }, + { + "name": "amp-story-auto-analytics", + "version": "0.1", + "latestVersion": "0.1" + }, + { + "name": "amp-story-dev-tools", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-story-education", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-story-interactive", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "cssBinaries": [ + "amp-story-interactive-binary-poll", + "amp-story-interactive-poll", + "amp-story-interactive-quiz", + "amp-story-interactive-results" + ] + } + }, + { + "name": "amp-story-panning-media", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-story-player", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-stream-gallery", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "hasCss": true + } + }, + { + "name": "amp-stream-gallery", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + }, + { + "name": "amp-subscriptions", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-subscriptions-google", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-tiktok", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-timeago", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-timeago", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "npm": true + } + }, + { + "name": "amp-truncate-text", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "cssBinaries": ["amp-truncate-text", "amp-truncate-text-shadow"] + } + }, + {"name": "amp-twitter", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-twitter", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "npm": true + } + }, + { + "name": "amp-user-notification", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-video", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-video", + "version": "1.0", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-video-docking", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-video-iframe", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-video-iframe", + "version": "1.0", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + { + "name": "amp-viewer-integration", + "version": "0.1", + "latestVersion": "0.1", + "options": { + "// 1": "The viewer integration code needs to run asap, so that viewers", + "// 2": "can influence document state asap. Otherwise the document may ", + "// 3": "take a long time to learn that it should start process other", + "// 4": "extensions faster.", + "loadPriority": "high" + } + }, + {"name": "amp-vimeo", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-vimeo", + "version": "1.0", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-vine", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-viqeo-player", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-viz-vega", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-vk", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-web-push", + "version": "0.1", + "latestVersion": "0.1", + "options": {"hasCss": true} + }, + {"name": "amp-wistia-player", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-yotpo", "version": "0.1", "latestVersion": "0.1"}, + {"name": "amp-youtube", "version": "0.1", "latestVersion": "0.1"}, + { + "name": "amp-youtube", + "version": "1.0", + "latestVersion": "0.1", + "options": { + "hasCss": true, + "npm": true + } + } +] diff --git a/build-system/compile/bundles.config.js b/build-system/compile/bundles.config.js index 33e203bddabe1..4abb7d23343ef 100644 --- a/build-system/compile/bundles.config.js +++ b/build-system/compile/bundles.config.js @@ -14,30 +14,20 @@ * limitations under the License. */ 'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); -const log = require('fancy-log'); +const extensionBundles = require('./bundles.config.extensions.json'); const wrappers = require('./compile-wrappers'); +const {cyan, red} = require('../common/colors'); +const {log} = require('../common/logging'); const {VERSION: internalRuntimeVersion} = require('./internal-version'); -/** - * @enum {string} - */ -const TYPES = (exports.TYPES = { - AD: '_base_ad', - MEDIA: '_base_media', - MISC: '_base_misc', -}); - /** * Used to generate top-level JS build targets */ exports.jsBundles = { 'polyfills.js': { srcDir: './src/', - srcFilename: 'polyfills.js', + srcFilename: 'polyfills/index.js', destDir: './build/', minifiedDestDir: './build/', }, @@ -98,6 +88,17 @@ exports.jsBundles = { includePolyfills: false, }, }, + 'amp-script-proxy-iframe.js': { + srcDir: './3p/', + srcFilename: 'amp-script-proxy-iframe.js', + destDir: './dist.3p/current', + minifiedDestDir: './dist.3p/' + internalRuntimeVersion, + options: { + minifiedName: 'amp-script-proxy-iframe.js', + include3pDirectories: true, + includePolyfills: false, + }, + }, 'iframe-transport-client-lib.js': { srcDir: './3p/', srcFilename: 'iframe-transport-client-lib.js', @@ -145,6 +146,16 @@ exports.jsBundles = { includePolyfills: false, }, }, + 'amp-story-entry-point.js': { + srcDir: './src/amp-story-player/amp-story-entry-point/', + srcFilename: 'amp-story-entry-point.js', + destDir: './dist', + minifiedDestDir: './dist', + options: { + minifiedName: 'amp-story-entry-point-v0.js', + includePolyfills: false, + }, + }, 'amp-story-player.js': { srcDir: './src/amp-story-player/', srcFilename: 'amp-story-player.js', @@ -175,8 +186,6 @@ exports.jsBundles = { minifiedName: 'v0.js', includePolyfills: true, wrapper: wrappers.mainBinary, - esmPassCompilation: argv.esm, - includeOnlyESMLevelPolyfills: argv.esm, }, }, 'amp-shadow.js': { @@ -206,985 +215,7 @@ exports.jsBundles = { /** * Used to generate extension build targets */ -exports.extensionBundles = [ - { - name: 'amp-3d-gltf', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-3q-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-access', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-access-laterpay', - version: ['0.1', '0.2'], - latestVersion: '0.2', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-access-scroll', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-access-poool', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-accordion', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-action-macro', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-ad', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.AD, - }, - { - name: 'amp-ad-custom', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-ad-network-adsense-impl', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-ad-network-adzerk-impl', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-ad-network-doubleclick-impl', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-ad-network-fake-impl', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-ad-exit', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-addthis', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-analytics', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-anim', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-animation', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-apester-media', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MEDIA, - }, - { - name: 'amp-app-banner', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-audio', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-auto-ads', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-autocomplete', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-auto-lightbox', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-base-carousel', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-beopinion', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-bind', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-bodymovin-animation', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-brid-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-delight-player', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MEDIA, - }, - { - name: 'amp-brightcove', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-byside-content', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-kaltura-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-call-tracking', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-carousel', - version: ['0.1', '0.2'], - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-consent', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-connatix-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-crypto-polyfill', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-dailymotion', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-date-countdown', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-date-display', - version: ['0.1', '1.0'], - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-google-document-embed', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-dynamic-css-classes', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-embedly-card', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-experiment', - version: ['0.1', '1.0'], - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-facebook', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-facebook-comments', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-facebook-like', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-facebook-page', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-fit-text', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-fit-text', - version: '1.0', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-font', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-form', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-fx-collection', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-fx-flying-carpet', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-geo', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-gfycat', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-gist', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-gwd-animation', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-hulu', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-iframe', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-ima-video', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-image-lightbox', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-image-slider', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-imgur', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-inline-gallery', - version: '0.1', - latestVersion: '0.1', - options: { - hasCss: true, - cssBinaries: [ - 'amp-inline-gallery', - 'amp-inline-gallery-pagination', - 'amp-inline-gallery-thumbnails', - ], - }, - type: TYPES.MISC, - }, - { - name: 'amp-inputmask', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - postPrepend: ['third_party/inputmask/bundle.js'], - }, - { - name: 'amp-instagram', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-install-serviceworker', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-intersection-observer-polyfill', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-izlesene', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-jwplayer', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-lightbox', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-lightbox-gallery', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-list', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-live-list', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-loader', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-mathml', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-mega-menu', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-megaphone', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-mustache', - version: ['0.1', '0.2'], - latestVersion: '0.2', - type: TYPES.MISC, - }, - { - name: 'amp-nested-menu', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-next-page', - version: ['0.1', '1.0'], - latestVersion: '1.0', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-nexxtv-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-o2-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-ooyala-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-pinterest', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-playbuzz', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MEDIA, - }, - { - name: 'amp-reach-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-redbull-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-reddit', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-riddle-quiz', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-script', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-share-tracking', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-sidebar', - version: ['0.1', '0.2'], - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-skimlinks', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-smartlinks', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-soundcloud', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-springboard-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-standalone', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-sticky-ad', - version: '1.0', - latestVersion: '1.0', - options: {hasCss: true}, - type: TYPES.AD, - }, - { - name: 'amp-story', - version: '0.1', - latestVersion: '1.0', - options: { - hasCss: true, - cssBinaries: [ - 'amp-story-bookend', - 'amp-story-consent', - 'amp-story-hint', - 'amp-story-unsupported-browser-layer', - 'amp-story-viewport-warning-layer', - 'amp-story-info-dialog', - 'amp-story-share', - 'amp-story-share-menu', - 'amp-story-system-layer', - ], - }, - type: TYPES.MISC, - }, - { - name: 'amp-story', - version: '1.0', - latestVersion: '1.0', - options: { - hasCss: true, - cssBinaries: [ - 'amp-story-bookend', - 'amp-story-consent', - 'amp-story-draggable-drawer-header', - 'amp-story-hint', - 'amp-story-info-dialog', - 'amp-story-reaction', - 'amp-story-reaction-poll', - 'amp-story-reaction-quiz', - 'amp-story-share', - 'amp-story-share-menu', - 'amp-story-system-layer', - 'amp-story-tooltip', - 'amp-story-unsupported-browser-layer', - 'amp-story-viewport-warning-layer', - ], - }, - type: TYPES.MISC, - }, - { - name: 'amp-story-auto-ads', - version: '0.1', - latestVersion: '0.1', - options: { - hasCss: true, - cssBinaries: [ - 'amp-story-auto-ads-ad-badge', - 'amp-story-auto-ads-attribution', - ], - }, - type: TYPES.MISC, - }, - { - name: 'amp-story-education', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-stream-gallery', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-selector', - version: ['0.1', '1.0'], - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-web-push', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-wistia-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-position-observer', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-orientation-observer', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-date-picker', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - postPrepend: ['third_party/react-dates/bundle.js'], - }, - { - name: 'amp-image-viewer', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-subscriptions', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-subscriptions-google', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-pan-zoom', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-recaptcha-input', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - /** - * @deprecated `amp-slides` is deprecated and will be deleted before 1.0. - * Please see {@link AmpCarousel} with `type=slides` attribute instead. - */ - { - name: 'amp-slides', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-social-share', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-social-share', - version: '1.0', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-timeago', - version: ['0.1', '1.0'], - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-truncate-text', - version: '0.1', - latestVersion: '0.1', - options: { - hasCss: true, - cssBinaries: ['amp-truncate-text', 'amp-truncate-text-shadow'], - }, - type: TYPES.MISC, - }, - { - name: 'amp-twitter', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-user-notification', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - }, - { - name: 'amp-vimeo', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-vine', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-viz-vega', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MISC, - postPrepend: [ - 'third_party/d3/d3.js', - 'third_party/d3-geo-projection/d3-geo-projection.js', - 'third_party/vega/vega.js', - ], - }, - { - name: 'amp-google-vrview-image', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-viewer-assistance', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-viewer-integration', - version: '0.1', - latestVersion: '0.1', - options: { - // The viewer integration code needs to run asap, so that viewers - // can influence document state asap. Otherwise the document may take - // a long time to learn that it should start process other extensions - // faster. - loadPriority: 'high', - }, - type: TYPES.MISC, - }, - { - name: 'amp-video', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-video-docking', - version: '0.1', - latestVersion: '0.1', - options: {hasCss: true}, - type: TYPES.MEDIA, - }, - { - name: 'amp-video-iframe', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-viqeo-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-vk', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-yotpo', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-youtube', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-mowplayer', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-powr-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, - { - name: 'amp-mraid', - version: '0.1', - latestVersion: '0.1', - type: TYPES.AD, - }, - { - name: 'amp-link-rewriter', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MISC, - }, - { - name: 'amp-minute-media-player', - version: '0.1', - latestVersion: '0.1', - type: TYPES.MEDIA, - }, -].sort((a, b) => a.name.localeCompare(b.name)); +exports.extensionBundles = extensionBundles; /** * Used to alias a version of an extension to an older deprecated version. @@ -1194,6 +225,10 @@ exports.extensionAliasBundles = { version: '1.0', aliasedVersion: '0.1', }, + 'amp-story': { + version: '1.0', + aliasedVersion: '0.1', + }, }; /** @@ -1205,19 +240,13 @@ exports.extensionAliasBundles = { */ function verifyBundle_(condition, field, message, name, found) { if (!condition) { - log( - colors.red('ERROR:'), - colors.cyan(field), - message, - colors.cyan(name), - '\n' + found - ); + log(red('ERROR:'), cyan(field), message, cyan(name), '\n' + found); process.exit(1); } } exports.verifyExtensionBundles = function () { - exports.extensionBundles.forEach((bundle) => { + extensionBundles.forEach((bundle, i) => { const bundleString = JSON.stringify(bundle, null, 2); verifyBundle_( 'name' in bundle, @@ -1226,6 +255,13 @@ exports.verifyExtensionBundles = function () { '', bundleString ); + verifyBundle_( + i === 0 || bundle.name.localeCompare(extensionBundles[i - 1].name) >= 0, + 'name', + 'is out of order. extensionBundles should be alphabetically sorted by name.', + bundle.name, + bundleString + ); verifyBundle_( 'version' in bundle, 'version', @@ -1252,20 +288,5 @@ exports.verifyExtensionBundles = function () { bundle.name, JSON.stringify(duplicates, null, 2) ); - verifyBundle_( - 'type' in bundle, - 'type', - 'is missing from', - bundle.name, - bundleString - ); - const validTypes = Object.keys(TYPES).map((x) => TYPES[x]); - verifyBundle_( - validTypes.some((validType) => validType === bundle.type), - 'type', - `is not one of ${validTypes.join(',')} in`, - bundle.name, - bundleString - ); }); }; diff --git a/build-system/compile/check-for-unknown-deps.js b/build-system/compile/check-for-unknown-deps.js index 1ded914341c36..1008216bd38e8 100644 --- a/build-system/compile/check-for-unknown-deps.js +++ b/build-system/compile/check-for-unknown-deps.js @@ -15,38 +15,39 @@ */ 'use strict'; -const log = require('fancy-log'); -const through = require('through2'); -const {red, cyan, yellow} = require('ansi-colors'); +const argv = require('minimist')(process.argv.slice(2)); +const fs = require('fs-extra'); +const {cyan, red, yellow} = require('../common/colors'); +const {log} = require('../common/logging'); /** * Searches for the identifier "module$", which Closure uses to uniquely * reference module imports. If any are found, that means Closure couldn't * import the module correctly. - * - * @return {!Stream} + * @param {string} file + * @return {Promise} */ -exports.checkForUnknownDeps = function () { +async function checkForUnknownDeps(file) { const regex = /[\w$]*module\$[\w$]+/; - - return through.obj(function (file, encoding, cb) { - const contents = file.contents.toString(); - if (!contents.includes('module$')) { - // Fast check, since regexes can backtrack like crazy. - return cb(null, file); - } - - const match = regex.exec(contents) || [ - `couldn't parse the dep. Look for "module$" in the file`, - ]; - - log( - red('Error:'), - `Unknown dependency ${cyan(match[0])} found in ${cyan(file.relative)}` - ); + const contents = await fs.readFile(file, 'utf-8'); + if (!contents.includes('module$')) { + // Fast check, since regexes can backtrack like crazy. + return; + } + const match = regex.exec(contents) || [ + `couldn't parse the dep. Look for "module$" in the file`, + ]; + log( + red('Error:'), + `Unknown dependency ${cyan(match[0])} found in ${cyan(file)}` + ); + if (argv.debug) { + log(red('Output file contents:')); log(yellow(contents)); - const err = new Error('Compilation failed due to unknown dependency'); - err.showStack = false; - cb(err, file); - }); + } + throw new Error('Compilation failed due to unknown dependency'); +} + +module.exports = { + checkForUnknownDeps, }; diff --git a/build-system/compile/closure-compile.js b/build-system/compile/closure-compile.js index 9c7f318f05eae..a59c11399ccb9 100644 --- a/build-system/compile/closure-compile.js +++ b/build-system/compile/closure-compile.js @@ -15,148 +15,96 @@ */ 'use strict'; -const argv = require('minimist')(process.argv.slice(2)); -const closureCompiler = require('google-closure-compiler'); -const log = require('fancy-log'); -const path = require('path'); -const pumpify = require('pumpify'); -const sourcemaps = require('gulp-sourcemaps'); -const {cyan, red, yellow} = require('ansi-colors'); -const {EventEmitter} = require('events'); -const {highlight} = require('cli-highlight'); - -let compilerErrors = ''; +const compiler = require('@ampproject/google-closure-compiler'); +const vinylFs = require('vinyl-fs'); +const {cyan, red, yellow} = require('../common/colors'); +const {getBabelOutputDir} = require('./pre-closure-babel'); +const {log, logWithoutTimestamp} = require('../common/logging'); /** - * Formats a closure compiler error message into a more readable form by - * dropping the closure compiler plugin's logging prefix and then syntax - * highlighting the error text. + * Logs a closure compiler error message after syntax highlighting it and then + * formatting it into a more readable form by dropping the plugin's logging + * prefix, normalizing paths, and emphasizing errors and warnings. * @param {string} message - * @return {string} */ -function formatClosureCompilerError(message) { - const closurePluginLoggingPrefix = /^.*?gulp-google-closure-compiler.*?: /; - message = highlight(message, {ignoreIllegals: true}) - .replace(closurePluginLoggingPrefix, '') - .replace(/ WARNING /g, yellow(' WARNING ')) - .replace(/ ERROR /g, red(' ERROR ')); - return message; +function logClosureCompilerError(message) { + log(red('ERROR:')); + const babelOutputDir = `${getBabelOutputDir()}/`; + const loggingPrefix = /^.*?gulp-google-closure-compiler.*?: /; + const {highlight} = require('cli-highlight'); // Lazy-required to speed up task loading. + const highlightedMessage = highlight(message, {ignoreIllegals: true}); + const formattedMessage = highlightedMessage + .replace(loggingPrefix, '') + .replace(new RegExp(babelOutputDir, 'g'), '') + .replace(/ ERROR /g, red(' ERROR ')) + .replace(/ WARNING /g, yellow(' WARNING ')); + logWithoutTimestamp(formattedMessage); } /** - * Handles a closure error during multi-pass compilation. Optionally doesn't - * emit a fatal error when compilation fails and signals the error so subsequent - * operations can be skipped (used in watch mode). - * - * @param {Error} err + * Handles a closure error during compilation and type checking. Passes through + * the error except in watch mode, where we want to print a failure message and + * continue. + * @param {!compiler.PluginError} err * @param {string} outputFilename * @param {?Object} options - * @param {?Function} resolve + * @return {!compiler.PluginError|undefined} */ -function handleCompilerError(err, outputFilename, options, resolve) { - logError(`${red('ERROR:')} Could not minify ${cyan(outputFilename)}`); - if (options && options.continueOnError) { - options.errored = true; - if (resolve) { - resolve(); +function handleClosureCompilerError(err, outputFilename, options) { + if (options.typeCheckOnly) { + if (!options.logger) { + log(`${red('ERROR:')} Type checking failed`); } - } else { - emitError(err); + return err; } + log(`${red('ERROR:')} Could not minify ${cyan(outputFilename)}`); + if (options.continueOnError) { + options.errored = true; + return; + } + return err; } /** - * Handles a closure error during type checking - * - * @param {Error} err - */ -function handleTypeCheckError(err) { - logError(red('Type checking failed:')); - emitError(err); -} - -/** - * Emits an error to the caller - * - * @param {Error} err - */ -function emitError(err) { - err.showStack = false; - new EventEmitter().emit('error', err); -} - -/** - * Prints an error message when compilation fails - * @param {string} message + * Initializes closure compiler with the given set of flags. We use the gulp + * streaming plugin because invoking a command with a long list of --fs flags + * on Windows exceeds the command line size limit. The stream mode is 'IN' + * because output files and sourcemaps are written directly to disk. + * @param {Array} flags + * @param {!Object} options + * @return {!Object} */ -function logError(message) { - log(`${message}\n` + formatClosureCompilerError(compilerErrors)); +function initializeClosure(flags, options) { + const pluginOptions = { + streamMode: 'IN', + logger: options.logger || logClosureCompilerError, + }; + return compiler.gulp()(flags, pluginOptions); } /** - * Normalize the sourcemap file paths before pushing into Closure. - * Closure don't follow Gulp's normal sourcemap "root" pattern. Gulp considers - * all files to be relative to the CWD by default, meaning a file `src/foo.js` - * with a sourcemap alongside points to `src/foo.js`. Closure considers each - * file relative to the sourcemap. Since the sourcemap for `src/foo.js` "lives" - * in `src/`, it ends up resolving to `src/src/foo.js`. - * - * @param {!Stream} closureStream - * @return {!Stream} + * Runs closure compiler with the given set of flags. + * @param {string} outputFilename + * @param {!Object} options + * @param {Array} flags + * @param {Array} srcFiles + * @return {Promise} */ -function makeSourcemapsRelative(closureStream) { - const relativeSourceMap = sourcemaps.mapSources((source, file) => { - const dir = path.dirname(file.sourceMap.file); - return path.relative(dir, source); +function runClosure(outputFilename, options, flags, srcFiles) { + return new Promise((resolve, reject) => { + vinylFs + .src(srcFiles, {base: getBabelOutputDir()}) + .pipe(initializeClosure(flags, options)) + .on('error', (err) => { + const reason = handleClosureCompilerError(err, outputFilename, options); + reason ? reject(reason) : resolve(); + }) + .on('end', resolve) + .pipe(vinylFs.dest('.')); }); - - return pumpify.obj(relativeSourceMap, closureStream); -} - -/** - * @param {Array} compilerOptions - * @param {?string} nailgunPort - * @return {stream.Writable} - */ -function gulpClosureCompile(compilerOptions, nailgunPort) { - const initOptions = { - extraArguments: ['-XX:+TieredCompilation'], // Significant speed up! - }; - const pluginOptions = { - platform: ['java'], // Override the binary used by closure compiler - logger: (errors) => (compilerErrors = errors), // Capture compiler errors - }; - - // On Mac OS and Linux, speed up compilation using nailgun (unless the - // --disable_nailgun flag was passed in) - // See https://github.com/facebook/nailgun. - if ( - !argv.disable_nailgun && - (process.platform == 'darwin' || process.platform == 'linux') - ) { - compilerOptions = [ - '--nailgun-port', - nailgunPort, - 'org.ampproject.AmpCommandLineRunner', - '--', - ].concat(compilerOptions); - pluginOptions.platform = ['native']; // nailgun-runner isn't a java binary - initOptions.extraArguments = null; // Already part of nailgun-server - } else { - // For other platforms, or if nailgun is explicitly disabled, use AMP's - // custom runner.jar - closureCompiler.compiler.JAR_PATH = require.resolve( - `../runner/dist/${nailgunPort}/runner.jar` - ); - } - - return makeSourcemapsRelative( - closureCompiler.gulp(initOptions)(compilerOptions, pluginOptions) - ); } module.exports = { - gulpClosureCompile, - handleCompilerError, - handleTypeCheckError, + logClosureCompilerError, + runClosure, }; diff --git a/build-system/compile/compile-wrappers.js b/build-system/compile/compile-wrappers.js index 77275e2a5d794..209a11093f712 100644 --- a/build-system/compile/compile-wrappers.js +++ b/build-system/compile/compile-wrappers.js @@ -18,9 +18,15 @@ const {VERSION} = require('./internal-version'); // If there is a sync JS error during initial load, // at least try to unhide the body. +// If "AMP" is already an object then that means another runtime has already +// been initialized and the current runtime must exit early. This can occur +// if multiple AMP libraries are included in the html or when both the module +// and nomodule runtimes execute in older browsers such as safari < 11. exports.mainBinary = 'var global=self;self.AMP=self.AMP||[];' + - 'try{(function(_){\n<%= contents %>})(AMP._=AMP._||{})}catch(e){' + + 'try{(function(_){' + + 'if(self.AMP&&!Array.isArray(self.AMP))return;' + + '\n<%= contents %>})(AMP._=AMP._||{})}catch(e){' + 'setTimeout(function(){' + 'var s=document.body.style;' + 's.opacity=1;' + @@ -28,27 +34,7 @@ exports.mainBinary = 's.animation="none";' + 's.WebkitAnimation="none;"},1000);throw e};'; -exports.extension = function ( - name, - loadPriority, - intermediateDeps, - opt_splitMarker -) { - opt_splitMarker = opt_splitMarker || ''; - - let deps = ''; - if (intermediateDeps && intermediateDeps.length) { - deps = 'i:'; - function quote(s) { - return `"${s}"`; - } - if (intermediateDeps.length == 1) { - deps += quote(intermediateDeps[0]); - } else { - deps += `[${intermediateDeps.map(quote).join(',')}]`; - } - deps += ','; - } +exports.extension = function (name, version, latest, isModule, loadPriority) { let priority = ''; if (loadPriority) { if (loadPriority != 'high') { @@ -56,9 +42,12 @@ exports.extension = function ( } priority = 'p:"high",'; } + // Use a numeric value instead of boolean. "m" stands for "module" + const m = isModule ? 1 : 0; return ( - `(self.AMP=self.AMP||[]).push({n:"${name}",${priority}${deps}` + - `v:"${VERSION}",f:(function(AMP,_){${opt_splitMarker}\n` + + `(self.AMP=self.AMP||[]).push({n:"${name}",ev:"${version}",l:${latest},` + + `${priority}` + + `v:"${VERSION}",m:${m},f:(function(AMP,_){\n` + '<%= contents %>\n})});' ); }; diff --git a/build-system/compile/compile.js b/build-system/compile/compile.js index f09d0a3887b89..5a64ba9f203b4 100644 --- a/build-system/compile/compile.js +++ b/build-system/compile/compile.js @@ -17,24 +17,16 @@ const argv = require('minimist')(process.argv.slice(2)); const del = require('del'); const fs = require('fs-extra'); -const gap = require('gulp-append-prepend'); -const gulp = require('gulp'); -const gulpIf = require('gulp-if'); -const nop = require('gulp-nop'); -const pathModule = require('path'); -const rename = require('gulp-rename'); -const sourcemaps = require('gulp-sourcemaps'); -const { - gulpClosureCompile, - handleCompilerError, - handleTypeCheckError, -} = require('./closure-compile'); +const globby = require('globby'); +const path = require('path'); const {checkForUnknownDeps} = require('./check-for-unknown-deps'); -const {checkTypesNailgunPort, distNailgunPort} = require('../tasks/nailgun'); const {CLOSURE_SRC_GLOBS} = require('./sources'); -const {isTravisBuild} = require('../common/travis'); +const {cpus} = require('os'); +const {cyan, green} = require('../common/colors'); +const {log, logLocalDev} = require('../common/logging'); const {postClosureBabel} = require('./post-closure-babel'); -const {preClosureBabel, handlePreClosureError} = require('./pre-closure-babel'); +const {preClosureBabel} = require('./pre-closure-babel'); +const {runClosure} = require('./closure-compile'); const {sanitize} = require('./sanitize'); const {VERSION: internalRuntimeVersion} = require('./internal-version'); const {writeSourcemaps} = require('./helpers'); @@ -42,26 +34,54 @@ const {writeSourcemaps} = require('./helpers'); const queue = []; let inProgress = 0; -// There's a race in the gulp plugin of closure compiler that gets exposed -// during various local development scenarios. -// See https://github.com/google/closure-compiler-npm/issues/9 -const MAX_PARALLEL_CLOSURE_INVOCATIONS = isTravisBuild() - ? 2 - : parseInt(argv.closure_concurrency, 10) || 1; +const MAX_PARALLEL_CLOSURE_INVOCATIONS = + parseInt(argv.closure_concurrency, 10) || cpus().length; -// Compiles AMP with the closure compiler. This is intended only for -// production use. During development we intend to continue using -// babel, as it has much faster incremental compilation. -exports.closureCompile = async function ( +/** + * @typedef {{ + * esmPassCompilation?: string, + * wrapper?: string, + * extraGlobs?: string, + * include3pDirectories?: boolean, + * includePolyfills?: boolean, + * externs?: string[], + * compilationLevel?: string, + * verboseLogging?: boolean, + * typeCheckOnly?: boolean, + * skipUnknownDepsCheck?: boolean, + * warningLevel?: boolean, + * noAddDeps?: boolean, + * continueOnError?: boolean, + * errored?: boolean, + * }} + */ +let OptionsDef; + +/** + * Compiles AMP with the closure compiler. This is intended only for + * production use. During development we intend to continue using + * babel, as it has much faster incremental compilation. + * + * @param {string|string[]} entryModuleFilename + * @param {string} outputDir + * @param {string} outputFilename + * @param {!OptionsDef} options + * @param {{startTime?: number}=} timeInfo + * @return {Promise} + */ +async function closureCompile( entryModuleFilename, outputDir, outputFilename, options, - timeInfo + timeInfo = {} ) { // Rate limit closure compilation to MAX_PARALLEL_CLOSURE_INVOCATIONS // concurrent processes. return new Promise(function (resolve, reject) { + /** + * Kicks off the first closure invocation. + */ function start() { inProgress++; compile( @@ -79,6 +99,10 @@ exports.closureCompile = async function ( (reason) => reject(reason) ); } + + /** + * Keeps track of the invocation count. + */ function next() { if (!queue.length) { return; @@ -90,8 +114,11 @@ exports.closureCompile = async function ( queue.push(start); next(); }); -}; +} +/** + * Cleans up the placeholder directories for fake build modules. + */ function cleanupBuildDir() { del.sync('build/fake-module'); del.sync('build/patched-module'); @@ -100,30 +127,105 @@ function cleanupBuildDir() { fs.mkdirsSync('build/fake-module/src/polyfills/'); fs.mkdirsSync('build/fake-polyfills/src/polyfills'); } -exports.cleanupBuildDir = cleanupBuildDir; -function compile( - entryModuleFilenames, - outputDir, - outputFilename, - options, - timeInfo -) { - function shouldAppendSourcemappingURLText(file) { - // Do not append sourceMappingURL if its a sourcemap - return ( - pathModule.extname(file.path) !== '.map' && options.esmPassCompilation +/** + * Generates a list of source files based on various factors. + * TODO(wg-infra, wg-performance): Clean up unnecessary files. + * + * @param {string[]} entryModuleFilenames + * @param {!OptionsDef} options + * @return {!Array} + */ +function getSrcs(entryModuleFilenames, options) { + const unneededFiles = [ + 'build/fake-module/third_party/babel/custom-babel-helpers.js', + ]; + const srcs = [...CLOSURE_SRC_GLOBS]; + entryModuleFilenames.forEach((filename) => { + // For extensions, include all JS files in the extension directory. + // Note: The glob added to srcs must be a posix glob on all platforms. + if (filename.startsWith('extensions')) { + srcs.push( + filename + .replace(path.basename(filename), path.join('**', '*.js')) + .split(path.win32.sep) + .join(path.posix.sep) + ); + } + }); + if (options.extraGlobs) { + srcs.push.apply(srcs, options.extraGlobs); + } + if (options.include3pDirectories) { + srcs.push('3p/**/*.js', 'ads/**/*.js'); + } + // Many files include the polyfills, but we only want to deliver them + // once. Since all files automatically wait for the main binary to load + // this works fine. + if (options.includePolyfills) { + srcs.push( + '!build/fake-module/src/polyfills/index.js', + '!build/fake-module/src/polyfills/**/*.js', + '!build/fake-polyfills/**/*.js' ); + } else { + srcs.push('!src/polyfills/index.js', '!build/fake-polyfills/**/*.js'); + unneededFiles.push('build/fake-module/src/polyfills/index.js'); + } + // Negative globstars must come at the end. + srcs.push( + // Don't include rollup configs + '!**/rollup.config.js', + // Don't include tests. + '!**_test.js', + '!**/test-*.js', + '!**/test-e2e/*.js', + // Don't include externs. + '!**/*.extern.js' + ); + unneededFiles.forEach(function (fake) { + if (!fs.existsSync(fake)) { + fs.writeFileSync( + fake, + '// Not needed in closure compiler\nexport function deadCode() {}' + ); + } + }); + return srcs; +} + +/** + * Generates the set of options with which to invoke Closure compiler. + * TODO(wg-infra,wg-performance): Clean up unnecessary options. + * + * @param {string} outputFilename + * @param {!OptionsDef} options + * @return {!Object} + */ +function generateCompilerOptions(outputFilename, options) { + // Determine externs + let externs = options.externs || []; + if (!options.noAddDeps) { + externs = [ + 'third_party/web-animations-externs/web_animations.js', + 'third_party/react-externs/externs.js', + 'third_party/moment/moment.extern.js', + ...globby.sync('src/core{,/**}/*.extern.js'), + ...globby.sync('build-system/externs/*.extern.js'), + ...externs, + ]; } const hideWarningsFor = [ 'third_party/amp-toolbox-cache-url/', 'third_party/caja/', 'third_party/closure-library/sha384-generated.js', + 'third_party/closure-responding-channel', 'third_party/d3/', 'third_party/inputmask/', 'third_party/mustache/', 'third_party/react-dates/', + 'third_party/resize-observer-polyfill/', 'third_party/set-dom/', 'third_party/subscriptions-project/', 'third_party/vega/', @@ -131,252 +233,243 @@ function compile( 'node_modules/', 'build/patched-module/', ]; - const baseExterns = [ - 'build-system/externs/amp.extern.js', - 'build-system/externs/dompurify.extern.js', - 'build-system/externs/layout-jank.extern.js', - 'build-system/externs/performance-observer.extern.js', - 'third_party/web-animations-externs/web_animations.js', - 'third_party/moment/moment.extern.js', - 'third_party/react-externs/externs.js', - 'build-system/externs/preact.extern.js', - ]; - const define = [`VERSION=${internalRuntimeVersion}`]; + const define = [`VERSION=${internalRuntimeVersion}`, 'AMP_MODE=true']; if (argv.pseudo_names) { define.push('PSEUDO_NAMES=true'); } + let wrapper = options.wrapper + ? options.wrapper.replace('<%= contents %>', '%output%') + : `(function(){%output%})();`; + wrapper = `${wrapper}\n\n//# sourceMappingURL=${outputFilename}.map`; - return new Promise(function (resolve, reject) { - if (!(entryModuleFilenames instanceof Array)) { - entryModuleFilenames = [entryModuleFilenames]; - } - const unneededFiles = [ - 'build/fake-module/third_party/babel/custom-babel-helpers.js', - ]; - let wrapper = '(function(){%output%})();'; - if (options.wrapper) { - wrapper = options.wrapper.replace('<%= contents %>', '%output%'); - } - const srcs = [...CLOSURE_SRC_GLOBS]; - // Add needed path for extensions. - // Instead of globbing all extensions, this will only add the actual - // extension path for much quicker build times. - entryModuleFilenames.forEach(function (filename) { - if (!filename.includes('extensions/')) { - return; - } - const path = filename.replace(/\/[^/]+\.js$/, '/**/*.js'); - srcs.push(path); - }); - if (options.extraGlobs) { - srcs.push.apply(srcs, options.extraGlobs); - } - if (options.include3pDirectories) { - srcs.push('3p/**/*.js', 'ads/**/*.js'); - } - // For ESM Builds, exclude ampdoc and ampshared css from inclusion. - // These styles are guaranteed to already be present on elgible documents. - if (options.esmPassCompilation) { - srcs.push('!build/ampdoc.css.js', '!build/ampshared.css.js'); - } - // Many files include the polyfills, but we only want to deliver them - // once. Since all files automatically wait for the main binary to load - // this works fine. - if (options.includeOnlyESMLevelPolyfills) { - const polyfills = fs.readdirSync('src/polyfills'); - const polyfillsShadowList = polyfills.filter((p) => { - // custom-elements polyfill must be included. - return p !== 'custom-elements.js'; - }); - srcs.push( - '!build/fake-module/src/polyfills.js', - '!build/fake-module/src/polyfills/**/*.js', - '!build/fake-polyfills/src/polyfills.js', - 'src/polyfills/custom-elements.js', - 'build/fake-polyfills/**/*.js' - ); - polyfillsShadowList.forEach((polyfillFile) => { - srcs.push(`!src/polyfills/${polyfillFile}`); - fs.writeFileSync( - 'build/fake-polyfills/src/polyfills/' + polyfillFile, - 'export function install() {}' - ); - }); - } else if (options.includePolyfills) { - srcs.push( - '!build/fake-module/src/polyfills.js', - '!build/fake-module/src/polyfills/**/*.js', - '!build/fake-polyfills/**/*.js' - ); - } else { - srcs.push('!src/polyfills.js', '!build/fake-polyfills/**/*.js'); - unneededFiles.push('build/fake-module/src/polyfills.js'); - } - // Negative globstars must come at the end. - srcs.push( - // Don't include rollup configs - '!**/rollup.config.js', - // Don't include tests. - '!**_test.js', - '!**/test-*.js', - '!**/test-e2e/*.js', - // Don't include externs. - '!**/*.extern.js' - ); - unneededFiles.forEach(function (fake) { - if (!fs.existsSync(fake)) { - fs.writeFileSync( - fake, - '// Not needed in closure compiler\n' + - 'export function deadCode() {}' - ); - } - }); - - let externs = baseExterns; - if (options.externs) { - externs = externs.concat(options.externs); - } - externs.push('build-system/externs/amp.multipass.extern.js'); + /** + * TODO(#28387) write a type for this. + * @type {Object} + */ + /* eslint "google-camelcase/google-camelcase": 0*/ + const compilerOptions = { + compilation_level: options.compilationLevel || 'SIMPLE_OPTIMIZATIONS', + // Turns on more optimizations. + assume_function_wrapper: true, + language_in: 'ECMASCRIPT_2020', + // Do not transpile down to ES5 if running with `--esm`, since we do + // limited transpilation in Babel. + language_out: argv.esm || argv.sxg ? 'NO_TRANSPILE' : 'ECMASCRIPT5_STRICT', + // We do not use the polyfills provided by closure compiler. + // If you need a polyfill. Manually include them in the + // respective top level polyfills/*.js files. + rewrite_polyfills: false, + externs, + js_module_root: [ + // Do _not_ include 'node_modules/' in js_module_root with 'NODE' + // resolution or bad things will happen (#18600). + 'build/patched-module/', + 'build/fake-module/', + 'build/fake-polyfills/', + ], + module_resolution: 'NODE', + package_json_entry_names: 'module,main', + process_common_js_modules: true, + // PRUNE strips all files from the input set that aren't explicitly + // required. + dependency_mode: options.noAddDeps ? 'SORT_ONLY' : 'PRUNE', + output_wrapper: wrapper, + source_map_include_content: !!argv.full_sourcemaps, + // These arrays are filled in below. + jscomp_error: [], + jscomp_warning: [], + jscomp_off: [], + define, + hide_warnings_for: hideWarningsFor, + // TODO(amphtml): Change 'QUIET' to 'DEFAULT'. + warning_level: argv.warning_level ?? options.warningLevel ?? 'QUIET', + extra_annotation_name: ['visibleForTesting', 'restricted'], + }; + if (argv.pseudo_names) { + // Some optimizations get turned off when pseudo_names is on. + // This causes some errors caused by the babel transformations + // that we apply like unreachable code because we turn a conditional + // falsey. (ex. is IS_FORTESTING transformation which causes some conditionals + // to be unreachable/suspicious code since the whole expression is + // falsey) + compilerOptions.jscomp_off.push('uselessCode', 'externsValidation'); + } + if (argv.pretty_print) { + compilerOptions.formatting = 'PRETTY_PRINT'; + } - /* eslint "google-camelcase/google-camelcase": 0*/ - const compilerOptions = { - compilation_level: options.compilationLevel || 'SIMPLE_OPTIMIZATIONS', - // Turns on more optimizations. - assume_function_wrapper: true, - language_in: 'ECMASCRIPT_2020', - // Do not transpile down to ES5 if running with `--esm`, since we do - // limited transpilation in Babel. - language_out: argv.esm ? 'NO_TRANSPILE' : 'ECMASCRIPT5', - // We do not use the polyfills provided by closure compiler. - // If you need a polyfill. Manually include them in the - // respective top level polyfills.js files. - rewrite_polyfills: false, - externs, - js_module_root: [ - // Do _not_ include 'node_modules/' in js_module_root with 'NODE' - // resolution or bad things will happen (#18600). - 'build/patched-module/', - 'build/fake-module/', - 'build/fake-polyfills/', - ], - entry_point: entryModuleFilenames, - module_resolution: 'NODE', - package_json_entry_names: 'module,main', - process_common_js_modules: true, - // This strips all files from the input set that aren't explicitly - // required. - dependency_mode: 'PRUNE', - output_wrapper: wrapper, - source_map_include_content: !!argv.full_sourcemaps, - warning_level: options.verboseLogging ? 'VERBOSE' : 'DEFAULT', - // These arrays are filled in below. - jscomp_error: [], - jscomp_warning: [], - jscomp_off: [], - define, - hide_warnings_for: hideWarningsFor, - }; - if (argv.pseudo_names) { - // Some optimizations get turned off when pseudo_names is on. - // This causes some errors caused by the babel transformations - // that we apply like unreachable code because we turn a conditional - // falsey. (ex. is IS_DEV transformation which causes some conditionals - // to be unreachable/suspicious code since the whole expression is - // falsey) - compilerOptions.jscomp_off.push('uselessCode', 'externsValidation'); - } - if (argv.pretty_print) { - compilerOptions.formatting = 'PRETTY_PRINT'; - } + // See https://github.com/google/closure-compiler/wiki/Warnings#warnings-categories + // for a full list of closure's default error / warning levels. + // NOTE: We do not use jscomp_warning because they are silenced by closure + // unless there are accompanying errors. Pick a side and use either one of + // jscomp_error or jscomp_off. + if (options.typeCheckOnly) { + compilerOptions.checks_only = true; + // Note: compilation_level is SIMPLE_OPTIMIZATIONS during type checking. + // Making it WHITESPACE_ONLY will disable type-checking, so don't do that. + compilerOptions.define.push('TYPECHECK_ONLY=true'); + // These aren't type-check errors by default, but should be. + compilerOptions.jscomp_error.push( + 'accessControls', + 'checkDebuggerStatement', + 'conformanceViolations', + 'checkTypes', + 'const', + 'constantProperty', + 'globalThis', + 'misplacedTypeAnnotation', + 'missingProperties', + 'strictMissingProperties', + 'visibility' + ); + // These are type-check errors / warnings by default, but cannot be. + compilerOptions.jscomp_off.push( + 'moduleLoad', // Breaks type-only modules: google/closure-compiler#3041 + 'unknownDefines' // Closure complains about VERSION + ); + compilerOptions.conformance_configs = + 'build-system/test-configs/conformance-config.textproto'; + } else { + // These aren't compilation errors by default, but should be. + compilerOptions.jscomp_error.push( + 'missingProperties', + 'strictMissingProperties', + 'visibility' + ); + // Thse are compilation errors / warnings by default, but cannot be. + compilerOptions.jscomp_off.push( + 'accessControls', // Silences spurious JSC_BAD_PRIVATE_GLOBAL_ACCESS + 'moduleLoad', // Breaks type-only modules: google/closure-compiler#3041 + 'unknownDefines' // Closure complains about VERSION + ); + } - // See https://github.com/google/closure-compiler/wiki/Warnings#warnings-categories - // for a full list of closure's default error / warning levels. - if (options.typeCheckOnly) { - compilerOptions.checks_only = true; + if (compilerOptions.define.length == 0) { + delete compilerOptions.define; + } + return compilerOptions; +} - // Don't modify compilation_level to a lower level since - // it won't do strict type checking if its whitespace only. - compilerOptions.define.push('TYPECHECK_ONLY=true'); - compilerOptions.jscomp_error.push( - 'accessControls', - 'conformanceViolations', - 'checkTypes', - 'const', - 'constantProperty', - 'globalThis', - 'misplacedTypeAnnotation' - ); - compilerOptions.jscomp_off.push('moduleLoad', 'unknownDefines'); - compilerOptions.conformance_configs = - 'build-system/test-configs/conformance-config.textproto'; +/** + * Generates the full set of flags with which to invoke closure compiler. + * + * @param {!OptionsDef} options + * @param {!Object} compilerOptions + * @param {!Array} entryModuleFilenames + * @param {string} destFile + * @param {string} sourcemapFile + * @return {!Array} + */ +function generateFlags( + options, + compilerOptions, + entryModuleFilenames, + destFile, + sourcemapFile +) { + const compilerFlags = []; + Object.keys(compilerOptions).forEach(function (option) { + const value = compilerOptions[option]; + if (Array.isArray(value)) { + value.forEach(function (item) { + compilerFlags.push('--' + option + '=' + item); + }); } else { - compilerOptions.jscomp_warning.push('accessControls', 'moduleLoad'); - compilerOptions.jscomp_off.push('unknownDefines'); - } - - if (compilerOptions.define.length == 0) { - delete compilerOptions.define; - } - - const compilerOptionsArray = []; - Object.keys(compilerOptions).forEach(function (option) { - const value = compilerOptions[option]; - if (value instanceof Array) { - value.forEach(function (item) { - compilerOptionsArray.push('--' + option + '=' + item); - }); + if (value != null) { + compilerFlags.push('--' + option + '=' + value); } else { - if (value != null) { - compilerOptionsArray.push('--' + option + '=' + value); - } else { - compilerOptionsArray.push('--' + option); - } + compilerFlags.push('--' + option); } - }); - - if (options.typeCheckOnly) { - return gulp - .src(srcs, {base: '.'}) - .pipe(sourcemaps.init()) - .pipe(preClosureBabel()) - .on('error', (err) => handlePreClosureError(err, outputFilename)) - .pipe(gulpClosureCompile(compilerOptionsArray, checkTypesNailgunPort)) - .on('error', (err) => handleTypeCheckError(err)) - .pipe(nop()) - .on('end', resolve); - } else { - timeInfo.startTime = Date.now(); - return gulp - .src(srcs, {base: '.'}) - .pipe(sourcemaps.init()) - .pipe(preClosureBabel()) - .on('error', (err) => - handlePreClosureError(err, outputFilename, options, resolve) - ) - .pipe(gulpClosureCompile(compilerOptionsArray, distNailgunPort)) - .on('error', (err) => - handleCompilerError(err, outputFilename, options, resolve) - ) - .pipe(rename(`${outputDir}/${outputFilename}`)) - .pipe( - gulpIf( - !argv.pseudo_names && !options.skipUnknownDepsCheck, - checkForUnknownDeps() - ) - ) - .on('error', reject) - .pipe( - gulpIf( - shouldAppendSourcemappingURLText, - gap.appendText(`\n//# sourceMappingURL=${outputFilename}.map`) - ) - ) - .pipe(postClosureBabel()) - .pipe(sanitize()) - .pipe(writeSourcemaps(options)) - .pipe(gulp.dest('.')) - .on('end', resolve); } }); + const entryPointFlags = entryModuleFilenames.map( + (entryPoint) => `--entry_point=${entryPoint}` + ); + compilerFlags.push(...entryPointFlags); + if (!options.typeCheckOnly) { + const outputFileFlag = `--js_output_file=${destFile}`; + const sourcemapFlag = `--create_source_map=${sourcemapFile}`; + compilerFlags.push(outputFileFlag, sourcemapFlag); + } + return compilerFlags; +} + +/** + * @param {string[]|string} entryModuleFilenames + * @param {string} outputDir + * @param {string} outputFilename + * @param {!OptionsDef} options + * @param {{startTime?: number}} timeInfo + * @return {Promise} + */ +async function compile( + entryModuleFilenames, + outputDir, + outputFilename, + options, + timeInfo +) { + if (timeInfo) { + timeInfo.startTime = Date.now(); + } + if (!Array.isArray(entryModuleFilenames)) { + entryModuleFilenames = [entryModuleFilenames]; + } + const destFile = `${outputDir}/${outputFilename}`; + const sourcemapFile = `${destFile}.map`; + const compilerOptions = generateCompilerOptions(outputFilename, options); + const srcs = options.noAddDeps + ? entryModuleFilenames.concat(options.extraGlobs || []) + : getSrcs(entryModuleFilenames, options); + const transformedSrcFiles = await Promise.all( + globby + .sync(srcs) + .map((src) => preClosureBabel(src, outputFilename, options)) + ); + if (options.errored && options.continueOnError) { + return; // Watch build. Bail on transform errors. + } + const flags = generateFlags( + options, + compilerOptions, + entryModuleFilenames, + destFile, + sourcemapFile + ); + await runClosure(outputFilename, options, flags, transformedSrcFiles); + if (options.errored && options.continueOnError) { + return; // Watch build. Bail on compilation errors. + } + if (!options.typeCheckOnly) { + if (!argv.pseudo_names && !options.skipUnknownDepsCheck) { + await checkForUnknownDeps(destFile); + } + await postClosureBabel(destFile); + await sanitize(destFile); + await writeSourcemaps(sourcemapFile, options); + } +} + +/** + * Indicates the current closure concurrency and how to override it. + */ +function printClosureConcurrency() { + log( + green('Using up to'), + cyan(MAX_PARALLEL_CLOSURE_INVOCATIONS.toString()), + green('concurrent invocations of closure compiler.') + ); + if (!argv.closure_concurrency) { + logLocalDev( + green('⤷ Use'), + cyan('--closure_concurrency=N'), + green('to change this number.') + ); + } } + +module.exports = { + cleanupBuildDir, + closureCompile, + printClosureConcurrency, +}; diff --git a/build-system/compile/debug-compilation-lifecycle.js b/build-system/compile/debug-compilation-lifecycle.js index b00554c6419fa..b4bd8191ee686 100644 --- a/build-system/compile/debug-compilation-lifecycle.js +++ b/build-system/compile/debug-compilation-lifecycle.js @@ -15,11 +15,11 @@ */ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); const fs = require('fs'); -const log = require('fancy-log'); const path = require('path'); const tempy = require('tempy'); +const {cyan, red} = require('../common/colors'); +const {log} = require('../common/logging'); const logFile = path.resolve(process.cwd(), 'dist', 'debug-compilation.log'); @@ -30,6 +30,7 @@ const pad = (value, length) => const LIFECYCLES = { 'pre-babel': 'pre-babel', + 'post-babel': 'post-babel', 'pre-closure': 'pre-closure', 'closured-pre-babel': 'closured-pre-babel', 'closured-pre-terser': 'closured-pre-terser', @@ -41,11 +42,18 @@ const LIFECYCLES = { * * @param {string} lifecycle * @param {string} fullpath - * @param {Buffer} content - * @param {Object} sourcemap + * @param {?string=} content + * @param {Object=} sourcemap */ function debug(lifecycle, fullpath, content, sourcemap) { if (argv.debug && Object.keys(LIFECYCLES).includes(lifecycle)) { + if (!content) { + content = fs.readFileSync(fullpath, 'utf-8'); + } + const sourcemapPath = `${fullpath}.map`; + if (!sourcemap && fs.existsSync(sourcemapPath)) { + sourcemap = fs.readFileSync(sourcemapPath, 'utf-8'); + } const contentsPath = tempy.writeSync(content); if (sourcemap) { fs.writeFileSync( @@ -55,17 +63,17 @@ function debug(lifecycle, fullpath, content, sourcemap) { } fs.appendFileSync( logFile, - `${pad(lifecycle, 20)}: ${pad( - path.basename(fullpath), - 30 - )} ${contentsPath}\n` + `${pad(lifecycle, 20)}: ${pad(fullpath, 100)} ${contentsPath}\n` ); } } +/** + * Logs debug information. + */ function displayLifecycleDebugging() { if (argv.debug) { - log(colors.white('Debug Lifecycles: ') + colors.red(logFile)); + log(cyan('Debug Lifecycles: ') + red(logFile)); } } diff --git a/build-system/compile/helpers.js b/build-system/compile/helpers.js index a0b0b9a09c228..2ac23bfe54028 100644 --- a/build-system/compile/helpers.js +++ b/build-system/compile/helpers.js @@ -15,16 +15,21 @@ */ 'use strict'; -const minimist = require('minimist'); -const sourcemaps = require('gulp-sourcemaps'); +const argv = require('minimist')(process.argv.slice(2)); +const fs = require('fs-extra'); +const path = require('path'); +const {getBabelOutputDir} = require('./pre-closure-babel'); const {VERSION: internalRuntimeVersion} = require('./internal-version'); -const argv = minimist(process.argv.slice(2)); - +/** + * Computes the base url for sourcemaps. Custom sourcemap URLs have placeholder + * {version} that should be replaced with the actual version. Also, ensures + * that a trailing slash exists. + * @param {Object} options + * @return {string} + */ function getSourceMapBase(options) { if (argv.sourcemap_url) { - // Custom sourcemap URLs have placeholder {version} that should be - // replaced with the actual version. Also, ensure trailing slash exists. return String(argv.sourcemap_url) .replace(/\{version\}/g, internalRuntimeVersion) .replace(/([^/])$/, '$1/'); @@ -35,11 +40,36 @@ function getSourceMapBase(options) { return `https://raw.githubusercontent.com/ampproject/amphtml/${internalRuntimeVersion}/`; } -function writeSourcemaps(options) { - return sourcemaps.write('.', { +/** + * Updates all filepaths in the sourcemap output. + * @param {Object} sourcemaps + */ +function updatePaths(sourcemaps) { + const babelOutputDir = getBabelOutputDir(); + sourcemaps.sources = sourcemaps.sources.map((source) => + source.startsWith(babelOutputDir) + ? path.relative(babelOutputDir, source) + : source + ); + if (sourcemaps.file) { + sourcemaps.file = path.basename(sourcemaps.file); + } +} + +/** + * Writes the sourcemap output to disk. + * @param {string} sourcemapsFile + * @param {Object} options + * @return {Promise} + */ +async function writeSourcemaps(sourcemapsFile, options) { + const sourcemaps = await fs.readJson(sourcemapsFile); + updatePaths(sourcemaps); + const extra = { sourceRoot: getSourceMapBase(options), includeContent: !!argv.full_sourcemaps, - }); + }; + await fs.writeJSON(sourcemapsFile, {...sourcemaps, ...extra}); } module.exports = { diff --git a/build-system/compile/internal-version.js b/build-system/compile/internal-version.js index f5294d07e91e1..64f5a93edc5fa 100644 --- a/build-system/compile/internal-version.js +++ b/build-system/compile/internal-version.js @@ -16,7 +16,7 @@ 'use strict'; const minimist = require('minimist'); -const {gitCherryMaster, gitCommitFormattedTime} = require('../common/git'); +const {gitCherryMain, gitCommitFormattedTime} = require('../common/git'); // Allow leading zeros in --version_override, e.g. 0000000000001 const argv = minimist(process.argv.slice(2), { @@ -27,17 +27,16 @@ const argv = minimist(process.argv.slice(2), { * Generates the AMP version number. * * Version numbers are determined using the following algorithm: - * - Count the number () of cherry-picked commits on this branch that came - * from the `master` branch, until reaching `master` or the first commit that - * was added directly on this branch (if the current commit is on `master`'s - * commit history, or only contains new commits that are not cherry-picked - * from `master`, then is 0). + * - Count the number () of cherry-picked commits on this branch, including + * commits not on the main branch . If this branch only contains new commits + * that are not cherry-picked, then is 0). * - Find the commit () before the last cherry-picked commit from the - * `master` branch (if the current branch is `master`, or otherwise in - * `master`'s commit history, then the current commit is ). + * main branch (if the current branch is the main branch, or otherwise in + * its commit history, then the current commit is ). * - Find the commit time of (.time). Note that commit time might be * different from author time! e.g., commit time might be the time that a PR - * was merged into `master`, or a commit was cherry-picked onto the branch; + * was merged into the main branch, or a commit was cherry-picked onto the + * branch; * author time is when the original author of the commit ran the "git commit" * command. * - The version number is .time.format("YYmmDDHHMM", "UTC") + (the @@ -47,34 +46,34 @@ const argv = minimist(process.argv.slice(2), { * that will fail to build. This should never happen. * * Examples: - * 1. The version number of a release built from the HEAD commit on `master`, - * where that HEAD commit was committed on April 25, 2020 2:31 PM EDT would - * be `2004251831000`. + * 1. The version number of a release built from the HEAD commit on the main + * branch, where that HEAD commit was committed on April 25, 2020 2:31 PM EDT + * would be `2004251831000`. * - EDT is UTC-4, so the hour value changes from EDT's 14 to UTC's 18. - * - The last three digits are 000 as this commit is on `master`. + * - The last three digits are 000 as this commit is on the main branch. * * 2. The version number of a release built from a local working branch (e.g., - * on a developer's workstation) that was split off from a `master` commit + * on a developer's workstation) that was split off from a main branch commit * from May 6, 2021 10:40 AM PDT and has multiple commits that exist only on * local working branch would be `2105061840000`. * - PDT is UTC-7, so the hour value changes from PDT's 10 to UTC's 17. * - The last three digits are 000 as this commit is on a branch that was - * split from `master`, and does not have any cherry-picked commits. + * split from the main branch, and does not have any cherry-picked commits. * * 3. For a release built from a local working branch that was split off from a - * `master` commit from November 9, 2021 11:48 PM PST, and then: - * - had one commit that was cherry-picked from `master`, + * main branch commit from November 9, 2021 11:48 PM PST, and then: + * - had one commit that was cherry-picked from the main branch, * - followed by two commits that were created directly on the branch, the * last of which was commited on November 23, 2021 6:38 PM PST, - * - followed by twelve commits that were cherry-picked from `master`, then - * its version number would be `2111240238012`. - * - The latest twelve commits are cherry-picks from `master`, and the one - * before them is not, so our last three digits are set to 012. + * - followed by twelve commits that were cherry-picked from the main branch, + * then its version number would be `2111240238012`. + * - The latest twelve commits are cherry-picks from the main branch, and the + * one before them is not, so our last three digits are set to 012. * - PST is UTC-8, so the hour value changes from PST's 18 to UTC's 2, and * one day is added. * * The version number can be manually overridden by passing --version_override - * to the `gulp build`/`gulp dist` command. + * to the `amp build`/`amp dist` command. * * @return {string} AMP version number (always 13 digits long) */ @@ -87,17 +86,12 @@ function getVersion() { return version; } - let numberOfCherryPicks = 0; - const commitCherriesInfo = gitCherryMaster().reverse(); - for (const {isCherryPick} of commitCherriesInfo) { - if (!isCherryPick) { - break; - } - numberOfCherryPicks++; - } + const numberOfCherryPicks = gitCherryMain().length; if (numberOfCherryPicks > 999) { throw new Error( - `This branch has ${numberOfCherryPicks} cherry-picks, which is more than 999, the maximum allowed number of cherry-picks!` + `This branch has ${numberOfCherryPicks} cherry-picks, which is more ` + + 'than 999, the maximum allowed number of cherry-picks! Please make ' + + 'sure your local main branch is up to date.' ); } @@ -105,8 +99,8 @@ function getVersion() { `HEAD~${numberOfCherryPicks}` ).slice(0, -2); - numberOfCherryPicks = String(numberOfCherryPicks).padStart(3, '0'); - return `${lastCommitFormattedTime}${numberOfCherryPicks}`; + const numberOfCherryPicksStr = String(numberOfCherryPicks).padStart(3, '0'); + return `${lastCommitFormattedTime}${numberOfCherryPicksStr}`; } // Used to e.g. references the ads binary from the runtime to get version lock. diff --git a/build-system/compile/log-messages.js b/build-system/compile/log-messages.js index 670cedbebe555..92b49345691e7 100644 --- a/build-system/compile/log-messages.js +++ b/build-system/compile/log-messages.js @@ -15,8 +15,8 @@ */ const argv = require('minimist')(process.argv.slice(2)); const fs = require('fs-extra'); -const log = require('fancy-log'); -const {cyan} = require('colors'); +const {cyan} = require('../common/colors'); +const {log} = require('../common/logging'); const pathPrefix = 'dist/log-messages'; @@ -37,7 +37,8 @@ const formats = { }; /** @return {!Promise>} */ -const extractedItems = () => fs.readJson(extractedPath).then(Object.values); +const extractedItems = async () => + fs.readJson(extractedPath).then(Object.values); /** * Format extracted messages table in multiple outputs, keyed by id. diff --git a/build-system/compile/post-closure-babel.js b/build-system/compile/post-closure-babel.js index e01c34154482b..519d320d4e1ed 100644 --- a/build-system/compile/post-closure-babel.js +++ b/build-system/compile/post-closure-babel.js @@ -16,20 +16,27 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); const babel = require('@babel/core'); +const fs = require('fs-extra'); const path = require('path'); -const remapping = require('@ampproject/remapping'); +const Remapping = require('@ampproject/remapping'); const terser = require('terser'); -const through = require('through2'); -const {debug, CompilationLifecycles} = require('./debug-compilation-lifecycle'); +const {CompilationLifecycles, debug} = require('./debug-compilation-lifecycle'); +const {jsBundles} = require('./bundles.config.js'); + +/** @type {Remapping.default} */ +const remapping = /** @type {*} */ (Remapping); + +let mainBundles; /** * Minify passed string. * * @param {string} code - * @return {Object} + * @param {string} filename + * @return {Promise>} */ -function terserMinify(code) { - const minified = terser.minify(code, { +async function terserMinify(code, filename) { + const options = { mangle: false, compress: { defaults: false, @@ -42,7 +49,21 @@ function terserMinify(code) { keep_quoted_props: true, }, sourceMap: true, - }); + }; + const basename = path.basename(filename, argv.esm ? '.mjs' : '.js'); + if (!mainBundles) { + mainBundles = Object.keys(jsBundles).map((key) => { + const bundle = jsBundles[key]; + if (bundle.options && bundle.options.minifiedName) { + return path.basename(bundle.options.minifiedName, '.js'); + } + return path.basename(key, '.js'); + }); + } + if (mainBundles.includes(basename)) { + options.output.preamble = ';'; + } + const minified = await terser.minify(code, options); return { compressed: minified.code, @@ -53,55 +74,43 @@ function terserMinify(code) { /** * Apply Babel Transforms on output from Closure Compuler, then cleanup added * space with Terser. Used only in esm mode. - * - * @return {!Promise} + * @param {string} file + * @return {Promise} */ -exports.postClosureBabel = function () { - return through.obj(function (file, enc, next) { - if (!argv.esm || path.extname(file.path) === '.map') { - debug( - CompilationLifecycles['complete'], - file.path, - file.contents, - file.sourceMap - ); - return next(null, file); - } +async function postClosureBabel(file) { + if ((!argv.esm && !argv.sxg) || path.extname(file) === '.map') { + debug(CompilationLifecycles['complete'], file); + return; + } - const map = file.sourceMap; + debug(CompilationLifecycles['closured-pre-babel'], file); + /** @type {?babel.TransformOptions} */ + const babelOptions = babel.loadOptions({caller: {name: 'post-closure'}}); + const {code, map: babelMap} = + (await babel.transformFileAsync(file, babelOptions ?? undefined)) || {}; + if (!code || !babelMap) { + throw new Error(`Error transforming contents of ${file}`); + } - debug( - CompilationLifecycles['closured-pre-babel'], - file.path, - file.contents, - file.sourceMap - ); - const {code, map: babelMap} = babel.transformSync(file.contents, { - caller: {name: 'post-closure'}, - }); - - debug( - CompilationLifecycles['closured-pre-terser'], - file.path, - file.contents, - file.sourceMap - ); - const {compressed, terserMap} = terserMinify(code); + debug(CompilationLifecycles['closured-pre-terser'], file, code, babelMap); + const {compressed, terserMap} = await terserMinify(code, path.basename(file)); + await fs.outputFile(file, compressed); - file.contents = Buffer.from(compressed, 'utf-8'); - file.sourceMap = remapping( - [terserMap, babelMap, map], - () => null, - !argv.full_sourcemaps - ); - - debug( - CompilationLifecycles['complete'], - file.path, - file.contents, - file.sourceMap - ); + const closureMap = await fs.readJson(`${file}.map`, 'utf-8'); + const sourceMap = remapping( + [terserMap, babelMap, closureMap], + () => null, + !argv.full_sourcemaps + ); + debug( + CompilationLifecycles['complete'], + file, + compressed?.toString(), + sourceMap + ); + await fs.writeJson(`${file}.map`, sourceMap); +} - return next(null, file); - }); +module.exports = { + postClosureBabel, }; diff --git a/build-system/compile/pre-closure-babel.js b/build-system/compile/pre-closure-babel.js index ddbc953b947d9..6f385e1762094 100644 --- a/build-system/compile/pre-closure-babel.js +++ b/build-system/compile/pre-closure-babel.js @@ -15,139 +15,144 @@ */ 'use strict'; -const crypto = require('crypto'); +const babel = require('@babel/core'); +const fs = require('fs-extra'); const globby = require('globby'); -const gulpBabel = require('gulp-babel'); -const log = require('fancy-log'); -const through = require('through2'); -const {BABEL_SRC_GLOBS, THIRD_PARTY_TRANSFORM_GLOBS} = require('./sources'); -const {debug, CompilationLifecycles} = require('./debug-compilation-lifecycle'); -const {EventEmitter} = require('events'); -const {red, cyan} = require('ansi-colors'); +const path = require('path'); +const tempy = require('tempy'); +const {BABEL_SRC_GLOBS} = require('./sources'); +const {CompilationLifecycles, debug} = require('./debug-compilation-lifecycle'); +const {cyan, red} = require('../common/colors'); +const {log} = require('../common/logging'); +const {TransformCache, batchedRead, md5} = require('../common/transform-cache'); /** * Files on which to run pre-closure babel transforms. * * @private @const {!Array} */ -const filesToTransform = getFilesToTransform(); +let filesToTransform; /** - * Used to cache babel transforms. + * Directory used to output babel transformed files for closure compilation. * - * @private @const {!Object} + * @private @const {string} */ -const cache = Object.create(null); +let outputDir; /** - * Computes the set of files on which to run pre-closure babel transforms. + * Used to cache pre-closure babel transforms. * - * @return {!Array} + * @const {TransformCache} */ -function getFilesToTransform() { - return globby - .sync([...BABEL_SRC_GLOBS, '!node_modules/', '!third_party/']) - .concat(globby.sync(THIRD_PARTY_TRANSFORM_GLOBS)); -} +let transformCache; /** - * @param {!Buffer} contents + * Returns the name of the babel output directory if it has been created. + * * @return {string} */ -function sha256(contents) { - const hash = crypto.createHash('sha256'); - hash.update(contents); - return hash.digest('hex'); +function getBabelOutputDir() { + return outputDir || ''; } /** - * Apply babel transforms prior to closure compiler pass. - * - * When a source file is transformed for the first time, it is written to an - * in-memory cache from where it is retrieved every subsequent time without - * invoking babel. + * Computes the set of files on which to run pre-closure babel transforms. * - * @return {!Promise} + * @return {!Array} */ -function preClosureBabel() { - const babel = gulpBabel({caller: {name: 'pre-closure'}}); - - return through.obj((file, enc, next) => { - const {relative, path} = file; - if (!filesToTransform.includes(relative)) { - return next(null, file); - } +function getFilesToTransform() { + return globby.sync([...BABEL_SRC_GLOBS, '!node_modules/', '!third_party/']); +} - const hash = sha256(file.contents); - const cached = cache[path]; - if (cached && cached.hash === hash) { - return next(null, cached.file.clone()); +/** + * Apply babel transforms prior to closure compiler pass, store the transformed + * file in an output directory (used by closure compiler), and return the path + * of the transformed file. + * + * When a source file is transformed for the first time, it is written to a + * persistent transform cache from where it is retrieved every subsequent time + * without invoking babel. A change to the file contents or to the invocation + * arguments will invalidate the cached result and re-transform the file. + * + * @param {string} file + * @param {string} outputFilename + * @param {!Object} options + * @return {Promise} + */ +async function preClosureBabel(file, outputFilename, options) { + if (!outputDir) { + outputDir = tempy.directory(); + } + if (!transformCache) { + transformCache = new TransformCache('.pre-closure-cache', '.js'); + } + if (!filesToTransform) { + filesToTransform = getFilesToTransform(); + } + const transformedFile = path.join(outputDir, file); + if (!filesToTransform.includes(file)) { + if (!(await fs.exists(transformedFile))) { + await fs.copy(file, transformedFile); } - - let data, err; - debug( - CompilationLifecycles['pre-babel'], - file.path, - file.contents, - file.sourceMap + return transformedFile; + } + try { + debug(CompilationLifecycles['pre-babel'], file); + const babelOptions = + babel.loadOptions({caller: {name: 'pre-closure'}}) || {}; + const optionsHash = md5( + JSON.stringify({babelOptions, argv: process.argv.slice(2)}) ); - function onData(d) { - babel.off('error', onError); - data = d; + const {contents, hash} = await batchedRead(file, optionsHash); + const cachedPromise = transformCache.get(hash); + if (cachedPromise) { + if (!(await fs.exists(transformedFile))) { + await fs.outputFile(transformedFile, await cachedPromise); + } + } else { + const transformPromise = babel + .transformAsync(contents, { + ...babelOptions, + filename: file, + filenameRelative: path.basename(file), + sourceFileName: path.relative(process.cwd(), file), + }) + .then((result) => result?.code); + transformCache.set(hash, transformPromise); + await fs.outputFile(transformedFile, await transformPromise); + debug(CompilationLifecycles['pre-closure'], transformedFile); } - function onError(e) { - babel.off('data', onData); - err = e; + } catch (err) { + const reason = handlePreClosureError(err, outputFilename, options); + if (reason) { + throw reason; } - babel.once('data', onData); - babel.once('error', onError); - babel.write(file, enc, () => { - if (err) { - return next(err); - } - - debug( - CompilationLifecycles['pre-closure'], - file.path, - data.contents, - data.sourceMap - ); - cache[path] = { - file: data, - hash, - }; - next(null, data.clone()); - }); - }); + } + return transformedFile; } /** - * Handles a pre-closure babel error. Optionally doesn't emit a fatal error when - * compilation fails and signals the error so subsequent operations can be - * skipped (used in watch mode). + * Handles a pre-closure babel error. Returns an error when transformation fails + * except except in watch mode, where we want to print a message and continue. * * @param {Error} err * @param {string} outputFilename - * @param {?Object} options - * @param {?Function} resolve + * @param {?Object=} options + * @return {Error|undefined} */ -function handlePreClosureError(err, outputFilename, options, resolve) { +function handlePreClosureError(err, outputFilename, options) { log(red('ERROR:'), err.message, '\n'); - const reasonMessage = `Could not compile ${cyan(outputFilename)}`; + const reasonMessage = `Could not transform ${cyan(outputFilename)}`; if (options && options.continueOnError) { log(red('ERROR:'), reasonMessage); options.errored = true; - if (resolve) { - resolve(); - } - } else { - const reason = new Error(reasonMessage); - reason.showStack = false; - new EventEmitter().emit('error', reason); + return; } + return new Error(reasonMessage); } module.exports = { - handlePreClosureError, + getBabelOutputDir, preClosureBabel, }; diff --git a/build-system/compile/sanitize.js b/build-system/compile/sanitize.js index aa352b1999146..19dfe229d7271 100644 --- a/build-system/compile/sanitize.js +++ b/build-system/compile/sanitize.js @@ -14,45 +14,38 @@ * limitations under the License. */ 'use strict'; -const prettier = require('prettier'); -const through = require('through2'); const argv = require('minimist')(process.argv.slice(2)); +const fs = require('fs-extra'); +const prettier = require('prettier'); -exports.sanitize = function () { - return through.obj((file, enc, next) => { - if (!argv.sanitize_vars_for_diff) { - return next(null, file); - } - - const contents = file.contents.toString(); - prettier.resolveConfig(file.relative).then((options) => { - try { - // Normalize the length of all jscomp variables, so that prettier will - // format it the same. - const replaced = Object.create(null); - let count = 0; - const presanitize = contents.replace( - /(?:[a-zA-Z$_][a-zA-Z$_0-9]*)?(?:JSCompiler|jscomp)[a-zA-Z$_0-9]*/g, - (match) => - replaced[match] || - (replaced[match] = `___${String(count++).padStart(6, '0')}___`) - ); - - const formatted = prettier.format(presanitize, { - ...options, - parser: 'babel', - }); - - // Finally, strip the numbers from the sanitized jscomp variables. This - // is so that a single extra variable doesn't cause thousands of diffs. - const sanitized = formatted.replace(/___\d+___/g, '______'); +/** + * Sanitizes variable names in minified output to aid in debugging. + * 1. Normalizes the length of all jscomp variables, so that prettier will + * format it the same. + * 2. Strips numbers from the sanitized jscomp variables so that a single extra + * variable doesn't cause thousands of diffs. + * @param {string} file + * @return {Promise} + */ +async function sanitize(file) { + if (!argv.sanitize_vars_for_diff) { + return; + } + const contents = await fs.readFile(file, 'utf-8'); + const config = await prettier.resolveConfig(file); + const options = {filepath: file, parser: 'babel', ...config}; + const replaced = Object.create(null); + let count = 0; + const presanitize = contents.replace( + /(?:[a-zA-Z$_][a-zA-Z$_0-9]*)?(?:JSCompiler|jscomp)[a-zA-Z$_0-9]*/g, + (match) => + replaced[match] || + (replaced[match] = `___${String(count++).padStart(6, '0')}___`) + ); + const formatted = prettier.format(presanitize, options); + const sanitized = formatted.replace(/___\d+___/g, '______'); + await fs.outputFile(file, sanitized); +} - file.contents = Buffer.from(sanitized); - next(null, file); - } catch (e) { - next(e); - } - }, next); - }); -}; +module.exports = {sanitize}; diff --git a/build-system/compile/sources.js b/build-system/compile/sources.js index 626e32fd8dac8..6275e17d3d521 100644 --- a/build-system/compile/sources.js +++ b/build-system/compile/sources.js @@ -24,24 +24,33 @@ const COMMON_GLOBS = [ 'third_party/amp-toolbox-cache-url/**/*.js', 'third_party/caja/html-sanitizer.js', 'third_party/closure-library/sha384-generated.js', + 'third_party/closure-responding-channel/closure-bundle.js', 'third_party/css-escape/css-escape.js', 'third_party/d3/**/*.js', 'third_party/fuzzysearch/index.js', 'third_party/inputmask/**/*.js', 'third_party/mustache/**/*.js', 'third_party/react-dates/bundle.js', - 'third_party/optimized-svg-icons/social-share-svgs.js', + 'third_party/resize-observer-polyfill/ResizeObserver.install.js', 'third_party/set-dom/set-dom.js', 'third_party/subscriptions-project/*.js', 'third_party/timeagojs/**/*.js', 'third_party/vega/**/*.js', 'third_party/webcomponentsjs/ShadowCSS.js', + 'third_party/zuho/**/*.js', 'node_modules/dompurify/package.json', 'node_modules/dompurify/dist/purify.es.js', 'node_modules/intersection-observer/package.json', 'node_modules/intersection-observer/intersection-observer.install.js', + 'node_modules/resize-observer-polyfill/package.json', + 'node_modules/resize-observer-polyfill/ResizeObserver.install.js', + 'node_modules/obj-str/package.json', + 'node_modules/obj-str/dist/obj-str.mjs', 'node_modules/promise-pjs/package.json', 'node_modules/promise-pjs/promise.mjs', + 'node_modules/rrule/dist/es5/rrule.js', + 'node_modules/timeago.js/package.json', + 'node_modules/timeago.js/**/*.js', 'node_modules/web-animations-js/package.json', 'node_modules/web-animations-js/web-animations.install.js', 'node_modules/web-activities/package.json', @@ -53,11 +62,14 @@ const COMMON_GLOBS = [ 'node_modules/@ampproject/viewer-messaging/package.json', 'node_modules/@ampproject/viewer-messaging/messaging.js', 'node_modules/@ampproject/worker-dom/package.json', - 'node_modules/@ampproject/worker-dom/dist/amp/main.mjs', + 'node_modules/@ampproject/worker-dom/dist/amp-production/main.mjs', 'node_modules/preact/package.json', 'node_modules/preact/dist/*.js', + 'node_modules/preact/dom/*.js', 'node_modules/preact/hooks/package.json', 'node_modules/preact/hooks/dist/*.js', + 'node_modules/preact/compat/package.json', + 'node_modules/preact/compat/dist/*.js', ]; /** @@ -102,6 +114,10 @@ const CLOSURE_SRC_GLOBS = [ 'extensions/amp-animation/**/*.js', // Needed for amp-carousel 0.2, amp-inline-gallery, amp-stream-gallery 'extensions/amp-base-carousel/**/*.js', + // Needed for carousel autolightbox + 'extensions/amp-lightbox-gallery/1.0/*.js', + // Needed for amp-lightbox-gallery using amp-lightbox + 'extensions/amp-lightbox/1.0/*.js', // For amp-bind in the web worker (ww.js). 'extensions/amp-bind/**/*.js', // Needed to access to Variant interface from other extensions @@ -116,18 +132,22 @@ const CLOSURE_SRC_GLOBS = [ 'extensions/amp-access/**/*.js', // Needed for AmpStoryVariableService 'extensions/amp-story/**/*.js', + // Needed for story ad inabox + 'extensions/amp-story-auto-ads/**/*.js', // Needed for SubscriptionsService 'extensions/amp-subscriptions/**/*.js', // Needed to access UserNotificationManager from other extensions 'extensions/amp-user-notification/**/*.js', - // Needed for VideoService - 'extensions/amp-video-service/**/*.js', + // Needed for video components in Bento. + 'extensions/amp-video/1.0/**/*.js', + // amp-video-iframe 0.1 and 1.0 share this file. + 'extensions/amp-video-iframe/amp-video-iframe-api.js', + // amp-vimeo 0.1 and 1.0 share this file. + 'extensions/amp-vimeo/vimeo-api.js', // Needed to access ConsentPolicyManager from other extensions 'extensions/amp-consent/**/*.js', // Needed to access AmpGeo type for service locator 'extensions/amp-geo/**/*.js', - // Needed for AmpViewerAssistanceService - 'extensions/amp-viewer-assistance/**/*.js', // Needed for amp-smartlinks dep on amp-skimlinks 'extensions/amp-skimlinks/0.1/**/*.js', 'src/*.js', @@ -135,24 +155,15 @@ const CLOSURE_SRC_GLOBS = [ '!third_party/babel/custom-babel-helpers.js', // Exclude since it's not part of the runtime/extension binaries. '!extensions/amp-access/0.1/amp-login-done.js', - 'builtins/**.js', + 'builtins/**/*.js', // 'node_modules/core-js/modules/**.js', // Not sure what these files are, but they seem to duplicate code // one level below and confuse the compiler. '!node_modules/core-js/modules/library/**.js', + '!extensions/**/dist/**/*.js', ].concat(COMMON_GLOBS); -/** - * NOTE: 3p code is generally excluded from the transform process. - * The globs here are force-transformed anyway. - */ -const THIRD_PARTY_TRANSFORM_GLOBS = [ - // JSX syntax should undergo usual transforms - 'third_party/optimized-svg-icons/social-share-svgs.js', -]; - module.exports = { BABEL_SRC_GLOBS, CLOSURE_SRC_GLOBS, - THIRD_PARTY_TRANSFORM_GLOBS, }; diff --git a/build-system/compile/typescript.js b/build-system/compile/typescript.js deleted file mode 100644 index 93c7a839cfd97..0000000000000 --- a/build-system/compile/typescript.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright 2018 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. - */ -'use strict'; - -const colors = require('ansi-colors'); -const fs = require('fs-extra'); -const log = require('fancy-log'); -const path = require('path'); -const ts = require('typescript'); -const tsickle = require('tsickle'); -const {endBuildStep} = require('../tasks/helpers'); - -/** - * Given a file path `foo/bar.js`, transpiles the TypeScript entry point of - * the same name `foo/bar.ts` and all direct and indirect TypeScript imports. - * - * @param {string} srcDir - * @param {string} srcFilename - * @return {!Promise} - */ -exports.transpileTs = function (srcDir, srcFilename) { - const startTime = Date.now(); - const tsEntry = path.join(srcDir, srcFilename).replace(/\.js$/, '.ts'); - const tsConfig = ts.convertCompilerOptionsFromJson( - { - 'module': 'esnext', - 'target': 'esnext', - }, - srcDir - ); - const tsOptions = tsConfig.options; - if (tsConfig.errors.length) { - log(colors.red('TSickle:'), tsickle.formatDiagnostics(tsConfig.errors)); - } - - const compilerHost = ts.createCompilerHost(tsOptions); - const program = ts.createProgram([tsEntry], tsOptions, compilerHost); - - // TODO(choumx): This was partially copy-pasta'd from tsickle. Add a default - // to tsickle so this can be an optional param. - const pathToModuleName = (context, fileName) => { - fileName = fileName.replace(/\.js$/, ''); - if (fileName[0] === '.') { - // './foo' or '../foo'. - // Resolve the path against the dirname of the current module. - fileName = path.join(path.dirname(context), fileName); - } - return fileName; - }; - const transformerHost = { - host: compilerHost, - options: tsOptions, - pathToModuleName, - shouldSkipTsickleProcessing: () => false, - transformTypesToClosure: true, - }; - return tsickle - .emitWithTsickle( - program, - transformerHost, - compilerHost, - tsOptions, - undefined, - (filePath, contents) => { - fs.writeFileSync(filePath, contents, {encoding: 'utf-8'}); - } - ) - .then((emitResult) => { - const diagnostics = ts - .getPreEmitDiagnostics(program) - .concat(emitResult.diagnostics); - if (diagnostics.length) { - log(colors.red('TSickle:'), tsickle.formatDiagnostics(diagnostics)); - } - endBuildStep('Transpiled', srcFilename, startTime); - }); -}; diff --git a/build-system/eslint-rules/OWNERS b/build-system/eslint-rules/OWNERS index 81b36bd711d79..eeccea4248aa8 100644 --- a/build-system/eslint-rules/OWNERS +++ b/build-system/eslint-rules/OWNERS @@ -1,12 +1,11 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [ {name: 'ampproject/wg-infra'}, - {name: 'ampproject/wg-runtime'}, {name: 'ampproject/wg-performance'}, {name: 'erwinmombay', notify: true}, {name: 'jridgewell', notify: true}, diff --git a/build-system/eslint-rules/always-call-chai-methods.js b/build-system/eslint-rules/always-call-chai-methods.js index 90247c8648b0b..41889a4badbc0 100644 --- a/build-system/eslint-rules/always-call-chai-methods.js +++ b/build-system/eslint-rules/always-call-chai-methods.js @@ -20,7 +20,6 @@ const chai = require('chai'); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); -// test/_init_tests.js chai.Assertion.addMethod('attribute'); chai.Assertion.addMethod('class'); chai.Assertion.addMethod('display'); diff --git a/build-system/eslint-rules/closure-type-primitives.js b/build-system/eslint-rules/closure-type-primitives.js index b9dbc3b881271..e934768aaf6fa 100644 --- a/build-system/eslint-rules/closure-type-primitives.js +++ b/build-system/eslint-rules/closure-type-primitives.js @@ -15,7 +15,7 @@ */ 'use strict'; -const doctrine = require('doctrine'); +const doctrine = require('@jridgewell/doctrine'); const traverse = require('traverse'); /** @typedef {!Object} */ @@ -40,7 +40,9 @@ module.exports = function (context) { fixable: 'code', }, Program: function () { - const comments = /** @type {!Array} */ (sourceCode.getAllComments()); + const comments = /** @type {!Array} */ ( + sourceCode.getAllComments() + ); comments .map((node) => parseClosureComments(context, node)) .forEach((comment) => checkClosureComments(context, comment)); @@ -90,7 +92,7 @@ function checkClosureComments(context, closureComment) { return; } - const {parsed, node} = closureComment; + const {node, parsed} = closureComment; traverse(parsed).forEach((astNode) => { if (!astNode) { return; @@ -156,7 +158,7 @@ function checkNonNullableNodes(context, node, astNode) { return; } - const {type, name} = astNode.expression; + const {name, type} = astNode.expression; if (type === 'FunctionType') { reportNonNullablePrimitive(context, node, 'function'); } else if (type === 'UndefinedLiteral') { diff --git a/build-system/eslint-rules/dict-string-keys.js b/build-system/eslint-rules/dict-string-keys.js index b3322a182e824..d3c5c97b6a58f 100644 --- a/build-system/eslint-rules/dict-string-keys.js +++ b/build-system/eslint-rules/dict-string-keys.js @@ -37,20 +37,25 @@ module.exports = function (context) { }; }; +/** + * @param {*} node + * @param {*} context + */ function checkNode(node, context) { if (node.type === 'ObjectExpression') { node.properties.forEach(function (prop) { - if (!prop.key.raw && !prop.computed) { + if (!prop.key || (!prop.key.raw && !prop.computed)) { + const {name = `[${prop.type}]`} = prop.key || prop.argument || {}; context.report({ - node, + node: prop, message: - 'Found: ' + - prop.key.name + - '. The keys of the Object ' + - 'Literal Expression passed into `dict` must have string keys.', + `Found: ${name}.` + + 'The Object Literal Expression passed into `dict` must only contain string keyed properties.', }); } - checkNode(prop.value, context); + if (prop.value) { + checkNode(prop.value, context); + } }); } else if (node.type === 'ArrayExpression') { node.elements.forEach(function (elem) { diff --git a/build-system/eslint-rules/forbidden-terms-config.js b/build-system/eslint-rules/forbidden-terms-config.js new file mode 100644 index 0000000000000..8e2256e61f709 --- /dev/null +++ b/build-system/eslint-rules/forbidden-terms-config.js @@ -0,0 +1,148 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {readFileSync} = require('fs'); + +/** + * @fileoverview + * Ensures that forbidden-terms.js is up-to-date by reporting listed files that + * do not include the allowed term. + */ + +const allowlistProperty = 'allowlist'; + +module.exports = { + meta: {fixable: 'code'}, + create(context) { + // Some files are allowlisted for multiple terms, so caching their content + // greatly speeds up this rule. + const fileContent = {}; + + /** + * @param {string} filename + * @return {string} + */ + function readFileContent(filename) { + try { + return ( + fileContent[filename] || + (fileContent[filename] = readFileSync(filename, 'utf-8')) + ); + } catch (_) { + return null; + } + } + + /** + * @param {Object} fixer + * @param {Node} node + */ + function* removeFromArray(fixer, node) { + const {text} = context.getSourceCode(); + let {start} = node; + while (/\s/.test(text[start - 1])) { + start--; + } + yield fixer.removeRange([start, node.end]); + + const after = context.getTokenAfter(node); + if (after.type === 'Punctuator' && after.value === ',') { + node = after; + yield fixer.remove(after); + } + + const [nextComment] = context.getCommentsAfter(node); + if ( + nextComment && + text.substr(node.end, nextComment.start - node.end).indexOf('\n') < 0 + ) { + yield fixer.remove(nextComment); + } + } + + return { + ['Property' + + `[key.name='${allowlistProperty}']` + + "[value.type='ArrayExpression']" + + "[parent.parent.type='Property']"]: function (node) { + const termProperty = node.parent.parent; + const termKey = termProperty.key; + + const termKeyIsIdentifier = termKey.type === 'Identifier'; + if (termProperty.computed || termKeyIsIdentifier) { + let fix; + if (!termProperty.computed && termKeyIsIdentifier) { + // we can replace non-computed ids with string literals + fix = function (fixer) { + return fixer.replaceText(termKey, `'${termKey.name}'`); + }; + } + context.report({ + node: termKey, + message: 'Term keys should be string literals.', + fix, + }); + return; + } + + if (node.value.elements.length < 1) { + context.report({ + node, + message: `Remove empty ${allowlistProperty}`, + fix(fixer) { + return removeFromArray(fixer, node); + }, + }); + return; + } + + const termRegexp = new RegExp(termKey.value, 'gm'); + + for (const stringLiteral of node.value.elements) { + if (!stringLiteral.type.endsWith('Literal')) { + context.report({ + node: stringLiteral, + message: `${allowlistProperty} should only contain string literals`, + }); + continue; + } + + const filename = stringLiteral.value; + const content = readFileContent(filename); + + if (content && content.match(termRegexp)) { + continue; + } + + context.report({ + node: stringLiteral, + message: content + ? 'File does not use this term.' + : 'File does not exist.', + suggest: [ + { + desc: `Remove from ${allowlistProperty}`, + fix(fixer) { + return removeFromArray(fixer, stringLiteral); + }, + }, + ], + }); + } + }, + }; + }, +}; diff --git a/build-system/eslint-rules/html-template.js b/build-system/eslint-rules/html-template.js index c4be545daff2c..4795be84e4ca7 100644 --- a/build-system/eslint-rules/html-template.js +++ b/build-system/eslint-rules/html-template.js @@ -17,11 +17,21 @@ const { staticTemplateFactories, - staticTemplateTags, staticTemplateFactoryFns, + staticTemplateTags, } = require('../babel-plugins/static-template-metadata'); +/** + * @param {*} context + * @return {{ + * CallExpression: {Function(node: CompilerNode): void} + * TaggedTemplateExpression: {Function(node: CompilerNode): void} + * }} + */ module.exports = function (context) { + /** + * @param {CompilerNode} node + */ function tagCannotBeCalled(node) { const {name} = node.callee; context.report({ @@ -34,6 +44,9 @@ module.exports = function (context) { }); } + /** + * @param {CompilerNode} node + */ function factoryUsage(node) { const {parent} = node; const {name} = node.callee; @@ -41,7 +54,8 @@ module.exports = function (context) { const expectedTagName = staticTemplateFactories[name]; if (parent.type === 'TaggedTemplateExpression' && parent.tag === node) { - return tagUsage(parent, `${name}()`); + tagUsage(parent, `${name}()`); + return; } if ( @@ -71,6 +85,10 @@ module.exports = function (context) { }); } + /** + * @param {CompilerNode} node + * @param {string} opt_name + */ function tagUsage(node, opt_name) { const {quasi, tag} = node; if (quasi.expressions.length !== 0) { @@ -107,7 +125,7 @@ module.exports = function (context) { const {start} = template; for (let i = 0; i < invalids.length; i++) { - const {tag, offset} = invalids[i]; + const {offset, tag} = invalids[i]; context.report({ node: template, loc: sourceCode.getLocFromIndex(start + offset), @@ -117,10 +135,18 @@ module.exports = function (context) { } } + /** + * @param {*} string + * @return {{ + * tag: string, + * offset: number, + * }[]} + */ function invalidVoidTag(string) { // Void tags are defined at // https://html.spec.whatwg.org/multipage/syntax.html#void-elements - const invalid = /<(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([a-zA-Z-]+)( [^>]*)?\/>/g; + const invalid = + /<(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([a-zA-Z-]+)( [^>]*)?\/>/g; const matches = []; let match; diff --git a/build-system/eslint-rules/json-configuration.js b/build-system/eslint-rules/json-configuration.js index 5bcda606d2ddd..6a9af9c2d5a22 100644 --- a/build-system/eslint-rules/json-configuration.js +++ b/build-system/eslint-rules/json-configuration.js @@ -16,8 +16,8 @@ 'use strict'; /** - * Finds the jsonConfiguration helper function from src/json.js, and performs - * validation on its input. + * Finds the jsonConfiguration helper function from src/core/types/object/json, + * and performs validation on its input. */ module.exports = function (context) { @@ -25,6 +25,10 @@ module.exports = function (context) { (name) => `CallExpression[callee.name=${name}]` ); + /** + * @param {*} node + * @return {Object} + */ function verifyPath(node) { for (let n = node; n; n = n.parent) { const {parent} = n; diff --git a/build-system/eslint-rules/jss-animation-name.js b/build-system/eslint-rules/jss-animation-name.js new file mode 100644 index 0000000000000..291a55925ebb7 --- /dev/null +++ b/build-system/eslint-rules/jss-animation-name.js @@ -0,0 +1,72 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * Prevents animation/animationName from using global names. They should + * always be scoped with $. + * + * Bad: + * const JSS = { + * foo: {animationName: 'bar-global'}, + * foo: {animation: '0.5s bar-global infinite'}, + * }; + * Good: + * const JSS = { + * foo: {animationName: '$bar-local'}, + * foo: {animation: '0.5s $bar-local infinite'}, + * }; + * @return {!Object} + */ +module.exports = function (context) { + return { + Property(node) { + if (!/\.jss.js$/.test(context.getFilename())) { + return; + } + // Could be {key: val} identifier or {'key': val} string + const keyName = node.key.name || node.key.value; + const isAnimation = keyName === 'animation'; + const isAnimationName = + keyName === 'animationName' || keyName === 'animation-name'; + if (!isAnimationName && !isAnimation) { + return; + } + if (typeof node.value.value !== 'string') { + context.report({ + node: node.value, + message: + `Use string literals for ${keyName} values.` + + (isAnimation + ? '\n(animation-* properties other than animation-name are exempt from this rule.)' + : ''), + }); + return; + } + if ( + (isAnimationName && !/^\$/.test(node.value.value)) || + (isAnimation && !/(^| )\$/.test(node.value.value)) + ) { + context.report({ + node: node.value, + message: `The animation name in property ${keyName} should start with $${ + isAnimationName ? ` (e.g. $${node.value.value})` : '' + }.\nThis scopes it to a @keyframes rule present in this module.`, + }); + } + }, + }; +}; diff --git a/build-system/eslint-rules/no-array-destructuring.js b/build-system/eslint-rules/no-array-destructuring.js index 0f9752840dd85..f6f32d93ec59b 100644 --- a/build-system/eslint-rules/no-array-destructuring.js +++ b/build-system/eslint-rules/no-array-destructuring.js @@ -27,6 +27,10 @@ // const [b] = value; // function bad([b]) {} module.exports = function (context) { + /** + * @param {*} node + * @return {boolean} + */ function isAllowed(node) { const {parent} = node; if (parent.type !== 'VariableDeclarator') { @@ -45,11 +49,14 @@ module.exports = function (context) { } if (callee.type === 'MemberExpression') { - const {object, property, computed} = callee; + const {computed, object, property} = callee; if (computed) { return false; } - if (object.type !== 'Identifier' || object.name !== 'preact') { + if ( + object.type !== 'Identifier' || + object.name.toLowerCase() !== 'preact' + ) { return false; } if (property.type !== 'Identifier' || !property.name.startsWith('use')) { diff --git a/build-system/eslint-rules/no-duplicate-name-typedef.js b/build-system/eslint-rules/no-duplicate-name-typedef.js index 68de4de695901..2db0b1b650907 100644 --- a/build-system/eslint-rules/no-duplicate-name-typedef.js +++ b/build-system/eslint-rules/no-duplicate-name-typedef.js @@ -36,11 +36,8 @@ module.exports = function (context) { }, VariableDeclaration(node) { - if (!node.leadingComments) { - return; - } - - const typedefComment = node.leadingComments.find((comment) => { + const leadingComments = context.getCommentsBefore(node); + const typedefComment = leadingComments.find((comment) => { return comment.type === 'Block' && /@typedef/.test(comment.value); }); diff --git a/build-system/eslint-rules/no-es2015-number-props.js b/build-system/eslint-rules/no-es2015-number-props.js index a3ea07517a7c2..c8e6cdd1d167a 100644 --- a/build-system/eslint-rules/no-es2015-number-props.js +++ b/build-system/eslint-rules/no-es2015-number-props.js @@ -27,6 +27,10 @@ const INVALID_PROPS = [ 'parseInt', ]; +/** + * @param {string} property + * @return {boolean} + */ function isInvalidProperty(property) { return INVALID_PROPS.indexOf(property) != -1; } diff --git a/build-system/eslint-rules/no-for-of-statement.js b/build-system/eslint-rules/no-for-of-statement.js deleted file mode 100644 index e1ed87e5b46b5..0000000000000 --- a/build-system/eslint-rules/no-for-of-statement.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2016 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. - */ -'use strict'; - -module.exports = function (context) { - return { - ForOfStatement: function (node) { - context.report({node, message: 'No for-of statement allowed.'}); - }, - }; -}; diff --git a/build-system/eslint-rules/no-forbidden-terms.js b/build-system/eslint-rules/no-forbidden-terms.js new file mode 100644 index 0000000000000..2bc32abbb29b6 --- /dev/null +++ b/build-system/eslint-rules/no-forbidden-terms.js @@ -0,0 +1,50 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {cyan} = require('../common/colors'); +const {matchForbiddenTerms} = require('../test-configs/forbidden-terms'); +const {relative} = require('path'); + +/** + * @fileoverview + * Reports forbidden terms found by regex. + * See test-configs/forbidden-terms.js + */ + +const addPeriod = (str) => (/.\s*$/.test(str) ? str : `${str}.`); + +module.exports = function (context) { + return { + Program() { + const filename = relative(process.cwd(), context.getFilename()); + const sourceCode = context.getSourceCode(); + const contents = sourceCode.text; + + for (const terms of context.options) { + for (const report of matchForbiddenTerms(filename, contents, terms)) { + const {loc, match, message} = report; + const formattedMatch = cyan(`"${match}"`); + context.report({ + loc, + message: + `Forbidden: ${formattedMatch}.` + + (message ? ` ${addPeriod(message)}` : ''), + }); + } + } + }, + }; +}; diff --git a/build-system/eslint-rules/no-has-own-property-method.js b/build-system/eslint-rules/no-has-own-property-method.js index 9d27cb29905c6..242ad20295a9a 100644 --- a/build-system/eslint-rules/no-has-own-property-method.js +++ b/build-system/eslint-rules/no-has-own-property-method.js @@ -28,7 +28,7 @@ module.exports = { node, message: 'Do not use hasOwnProperty directly. ' + - 'Use hasOwn from src/utils/object.js instead.', + 'Use hasOwn from src/core/types/object instead.', }); } }, diff --git a/build-system/eslint-rules/no-import-rename.js b/build-system/eslint-rules/no-import-rename.js index d45706e0fd895..ad0865b880a25 100644 --- a/build-system/eslint-rules/no-import-rename.js +++ b/build-system/eslint-rules/no-import-rename.js @@ -21,19 +21,19 @@ const path = require('path'); // name. This aids in writing lint rules for these imports. // // GOOD -// import { dict } from 'src/utils/object'; +// import { dict } from 'src/core/types/object'; // dict(); // // BAD -// import * as obj from 'src/utils/object'; +// import * as obj from 'src/core/types/object'; // obj.dict() // // Bad -// import { dict as otherName } from 'src/utils/object'; +// import { dict as otherName } from 'src/core/types/object'; // otherName() const imports = { - 'src/utils/object': ['dict'], + 'src/core/types/object': ['dict'], 'src/static-template': ['htmlFor'], 'src/experiments': ['isExperimentOn'], 'src/style': [ @@ -44,13 +44,18 @@ const imports = { 'setStyle', 'setStyles', ], - 'src/css': ['escapeCssSelectorIdent', 'escapeCssSelectorNth'], + 'src/core/dom/css': ['escapeCssSelectorIdent', 'escapeCssSelectorNth'], 'src/dom': ['scopedQuerySelector', 'scopedQuerySelectorAll'], 'src/log': ['user', 'dev'], 'src/mode': ['getMode'], }; module.exports = function (context) { + /** + * @param {*} node + * @param {string} modulePath + * @param {*} mods + */ function ImportSpecifier(node, modulePath, mods) { const {imported, local} = node; const {name} = imported; @@ -71,6 +76,14 @@ module.exports = function (context) { }); } + /** + * @param {*} node + * @param {string} modulePath + * @param {*} mods + * @return {{ + * ImportDeclaration: {Function(node: *): void}, + * }} + */ function ImportNamespaceSpecifier(node, modulePath, mods) { const ns = node.local.name; const variable = context.getScope().set.get(ns); diff --git a/build-system/eslint-rules/no-invalid-this.js b/build-system/eslint-rules/no-invalid-this.js new file mode 100644 index 0000000000000..ec4e2c82f9157 --- /dev/null +++ b/build-system/eslint-rules/no-invalid-this.js @@ -0,0 +1,109 @@ +/** + * Copyright 2019 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. + */ +'use strict'; + +// Disables use of the `this` value when we suspect that it is using the +// implicit global `this`. `this` is only valid inside class +// methods/constructors, and nested arrow functions. +// +// We special case calls to `fn.bind`, `fn.call`, and `fn.apply`, since they +// are usually forwarding the `this` context and not direclty using it. The +// called function should be corrected, not the caller. +// +// Good: +// class Foo { +// test() { +// this; +// } +// } +// function Bar() { +// this; +// } +// obj.baz = function() { +// this; +// }; +// +// Bad: +// var foo = function() { +// this; +// }; +// function bar() { +// this; +// } +module.exports = { + meta: { + fixable: 'code', + }, + + create(context) { + return { + ThisExpression(node) { + const ancestors = context.getAncestors().slice().reverse(); + + const maybeCall = ancestors[0]; + if ( + maybeCall.type === 'CallExpression' && + maybeCall.arguments[0] === node + ) { + const {callee} = maybeCall; + if (callee.type === 'MemberExpression' && !callee.computed) { + const {property} = callee; + if ( + property.type === 'Identifier' && + (property.name === 'bind' || + property.name === 'call' || + property.name === 'apply') + ) { + return; + } + } + } + + for (let i = 0; i < ancestors.length; i++) { + const ancestor = ancestors[i]; + switch (ancestor.type) { + case 'FunctionExpression': + const parent = ancestors[i + 1]; + // Allow functions that are used as methods. + if ( + parent.type === 'Property' || + parent.type === 'MethodDefinition' || + (parent.type === 'AssignmentExpression' && + parent.left.type === 'MemberExpression') + ) { + return; + } + // fallthrough + + case 'FunctionDeclaration': + const {id} = ancestor; + // Allow legacy function constructors + if (id && /^[A-Z]/.test(id.name)) { + return; + } + + case 'Program': + return context.report({ + node, + message: + '`this` looks to be using the implicit global `this` value on accident.', + }); + } + } + }, + }; + }, +}; diff --git a/build-system/eslint-rules/no-is-amp-alt.js b/build-system/eslint-rules/no-is-amp-alt.js deleted file mode 100644 index b59d1f944b8b7..0000000000000 --- a/build-system/eslint-rules/no-is-amp-alt.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2018 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. - */ -'use strict'; - -const selector = 'AssignmentExpression Identifier[name=IS_AMP_ALT]'; -module.exports = function (context) { - return { - [selector]: function (node) { - context.report({ - node, - message: 'No Assignment to IS_AMP_ALT global property allowed', - }); - }, - }; -}; diff --git a/build-system/eslint-rules/no-mixed-interpolation.js b/build-system/eslint-rules/no-mixed-interpolation.js index f3bc564126c03..4ea783f6a49c7 100644 --- a/build-system/eslint-rules/no-mixed-interpolation.js +++ b/build-system/eslint-rules/no-mixed-interpolation.js @@ -95,7 +95,7 @@ module.exports = { return; } - const {variadic, messageArgPos} = metadata; + const {messageArgPos, variadic} = metadata; // If method is not variadic we don't need to check. if (!variadic) { return; diff --git a/build-system/eslint-rules/no-private-props.js b/build-system/eslint-rules/no-private-props.js new file mode 100644 index 0000000000000..4772bf69552dd --- /dev/null +++ b/build-system/eslint-rules/no-private-props.js @@ -0,0 +1,66 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * Ensures private properties are not used in the file. If they are, they must + * be quoted. + * + * @return {!Object} + */ +module.exports = { + meta: { + fixable: 'code', + }, + + create(context) { + return { + MemberExpression(node) { + if (node.computed || !node.property.name.endsWith('_')) { + return; + } + + context.report({ + node, + message: + 'Unquoted private properties are not allowed in BaseElement. Please use quotes', + fix(fixer) { + const {object} = node; + return fixer.replaceTextRange( + [object.end, node.end], + `['${node.property.name}']` + ); + }, + }); + }, + + MethodDefinition(node) { + if (node.computed || !node.key.name.endsWith('_')) { + return; + } + + context.report({ + node, + message: + 'Unquoted private methods are not allowed in BaseElement. Please use quotes', + fix(fixer) { + return fixer.replaceText(node.key, `['${node.key.name}']`); + }, + }); + }, + }; + }, +}; diff --git a/build-system/eslint-rules/no-static-this.js b/build-system/eslint-rules/no-static-this.js new file mode 100644 index 0000000000000..ae49eb360cf4a --- /dev/null +++ b/build-system/eslint-rules/no-static-this.js @@ -0,0 +1,95 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +// Forbids use of `this` inside static class methods +// +// Good: +// ``` +// class Foo { +// static s() { +// Foo.prop; +// } +// +// i() { +// this.prop; +// Foo.prop; +// } +// } +// +// Foo.prop = 1; +// ``` +// +// Bad: +// ``` +// class Foo { +// static s() { +// this.prop; +// } +// } +// +// Foo.prop = 1; +// ``` +module.exports = { + meta: { + type: 'problem', + docs: { + description: + "Disallow using `this` within static functions, since Closure Compiler's Advanced Compilation breaks it", + context: 'https://github.com/google/closure-compiler/issues/2397', + }, + }, + create(context) { + return { + 'MethodDefinition[static=true] ThisExpression': function (node) { + const ancestry = context.getAncestors().slice().reverse(); + let i = 0; + for (; i < ancestry.length; i++) { + const ancestor = ancestry[i]; + const {type} = ancestor; + + // Arrow functions inherit `this` from their lexical scope, so we need + // to continue searching if it's an arrow. + if ( + !type.includes('Function') || + type === 'ArrowFunctionExpression' + ) { + continue; + } + + // If the direct parent of this function is a static method definition, + // then we've found our root method. If it's non-static, then it's a + // nested class expression's instance method, which is safe. + if (i < ancestry.length - 2) { + const parent = ancestry[i + 1]; + if (parent.type === 'MethodDefinition' && parent.static) { + break; + } + } + + // Found a function with it's own `this`, so it's safe. + return; + } + + context.report({ + node, + message: + '`this` in static methods is broken by Advanced Closure Compiler optimizations. Please use the class name directly.', + }); + }, + }; + }, +}; diff --git a/build-system/eslint-rules/no-unload-listener.js b/build-system/eslint-rules/no-unload-listener.js index 40db209720d6b..33b2cbaa39d01 100644 --- a/build-system/eslint-rules/no-unload-listener.js +++ b/build-system/eslint-rules/no-unload-listener.js @@ -50,7 +50,7 @@ module.exports = function (context) { return; } - const {value, type} = arg; + const {type, value} = arg; if (type !== 'Literal' || typeof value !== 'string') { return; } diff --git a/build-system/eslint-rules/objstr-literal.js b/build-system/eslint-rules/objstr-literal.js new file mode 100644 index 0000000000000..8234480200b0d --- /dev/null +++ b/build-system/eslint-rules/objstr-literal.js @@ -0,0 +1,47 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = function objstrLiteral(context) { + return { + CallExpression(node) { + if (node.callee.type !== 'Identifier' || node.callee.name !== 'objstr') { + return; + } + if ( + node.arguments.length !== 1 || + node.arguments[0].type !== 'ObjectExpression' + ) { + context.report({ + node, + message: `${node.callee.name}() must have a single argument that is an Object Expression Literal`, + }); + return; + } + for (const {argument, key, type} of node.arguments[0].properties) { + if (!key) { + context.report({ + node, + message: `${ + node.callee.name + }() must only contain keyed props, found [${type}] ${ + (argument && argument.name) || '(unknown)' + }`, + }); + } + } + }, + }; +}; diff --git a/build-system/eslint-rules/preact.js b/build-system/eslint-rules/preact.js index 76bab2f75dff1..a9638dd38030d 100644 --- a/build-system/eslint-rules/preact.js +++ b/build-system/eslint-rules/preact.js @@ -35,6 +35,9 @@ module.exports = { }, create(context) { + /** + * @param {*} node + */ function requirePreact(node) { if (imported) { return; diff --git a/build-system/eslint-rules/prefer-deferred-promise.js b/build-system/eslint-rules/prefer-deferred-promise.js index 2680aac5b05e8..0df568215eebb 100644 --- a/build-system/eslint-rules/prefer-deferred-promise.js +++ b/build-system/eslint-rules/prefer-deferred-promise.js @@ -16,6 +16,14 @@ 'use strict'; module.exports = function (context) { + /** + * @param {*} node + * @param {string} name + * @return {{ + * CallExpression: {Function(*): undefined}, + * NewExpression: {Function(*): undefined} + * } | boolean} + */ function isAssignment(node, name) { if (node.type !== 'AssignmentExpression') { return false; diff --git a/build-system/eslint-rules/prefer-destructuring.js b/build-system/eslint-rules/prefer-destructuring.js index 00f349d8d43cf..79760201335cd 100644 --- a/build-system/eslint-rules/prefer-destructuring.js +++ b/build-system/eslint-rules/prefer-destructuring.js @@ -20,7 +20,19 @@ module.exports = { fixable: 'code', }, + /** + * @param {EslintContext} context + * @return {{ + * VariableDeclarator: {Function(node: CompilerNode): void}, + * 'BlockStatement, Program': {Function (node: CompilerNode): void}, + * }} + */ create(context) { + /** + * @param {CompilerNode} node + * @param {boolean=} renamable + * @return {boolean} + */ function shouldBeDestructure(node, renamable = false) { const {id, init} = node; @@ -33,11 +45,11 @@ module.exports = { } const {name} = id; - const {object, property, computed} = init; + const {computed, object, property} = init; if ( computed || object.type === 'Super' || - property.leadingComments || + context.getCommentsBefore(property).length > 0 || property.type !== 'Identifier' ) { return false; @@ -46,6 +58,10 @@ module.exports = { return renamable || property.name === name; } + /** + * @param {CompilerNode} node + * @return {boolean} + */ function shouldBeIdempotent(node) { while (node.type === 'MemberExpression') { node = node.object; @@ -54,6 +70,14 @@ module.exports = { return node.type === 'Identifier' || node.type === 'ThisExpression'; } + /** + * + * @param {Map} map + * @param {K} key + * @param {CompilerNode} node + * @param {*} declaration + * @return {string[]} + */ function setStruct(map, key, node, declaration) { if (map.has(key)) { const struct = map.get(key); @@ -72,6 +96,9 @@ module.exports = { } } + /** + * @param {Map[]} maps + */ function processMaps(maps) { for (let i = 0; i < maps.length; i++) { const map = maps[i]; @@ -80,8 +107,12 @@ module.exports = { } } + /** + * @param {*} struct + * @param {*} base + */ function processVariables(struct, base) { - const {names, nodes, declarations, node} = struct; + const {declarations, names, node, nodes} = struct; if (nodes.size === 0) { return; @@ -118,13 +149,16 @@ module.exports = { } return { + /** + * @param {CompilerNode} node + */ VariableDeclarator(node) { if (!shouldBeDestructure(node)) { return; } const {init} = node; - if (init.leadingComments) { + if (context.getCommentsInside(node).length > 0) { return; } @@ -141,6 +175,9 @@ module.exports = { }); }, + /** + * @param {CompilerNode} node + */ 'BlockStatement, Program': function (node) { const {body} = node; const sourceCode = context.getSourceCode(); @@ -161,7 +198,7 @@ module.exports = { const decl = declarations[j]; const {id, init} = decl; - if (!init || init.leadingComments) { + if (!init || context.getCommentsInside(decl).length > 0) { continue; } @@ -193,12 +230,13 @@ module.exports = { const names = setStruct(variables, base, decl, node); const {properties} = id; for (let k = 0; k < properties.length; k++) { - const {key} = properties[k]; - if (key.type !== 'Identifier') { - // Deep destructuring, too complicated. - return; + const prop = properties[k]; + names.add(sourceCode.getText(prop)); + if (!prop.key) { + // rest element + processMaps([letMap, constMap]); + break; } - names.add(key.name); } } } diff --git a/build-system/eslint-rules/prefer-spread-props.js b/build-system/eslint-rules/prefer-spread-props.js index da179d27eebaf..bf45e0b29723f 100644 --- a/build-system/eslint-rules/prefer-spread-props.js +++ b/build-system/eslint-rules/prefer-spread-props.js @@ -40,38 +40,37 @@ module.exports = { const sourceCode = context.getSourceCode(); return { - 'CallExpression[callee.object.name="Object"][callee.property.name="assign"]': function ( - node - ) { - const args = node.arguments; - if (args.length <= 1) { - return; - } + 'CallExpression[callee.object.name="Object"][callee.property.name="assign"]': + function (node) { + const args = node.arguments; + if (args.length <= 1) { + return; + } - const first = args[0]; - if (first.type !== 'ObjectExpression') { - return; - } - for (const arg of args) { - if (arg.type === 'SpreadElement') { + const first = args[0]; + if (first.type !== 'ObjectExpression') { return; } - } + for (const arg of args) { + if (arg.type === 'SpreadElement') { + return; + } + } - context.report({ - node, - message: 'Prefer using object literals with spread property syntax', + context.report({ + node, + message: 'Prefer using object literals with spread property syntax', - fix(fixer) { - const texts = args.map((arg) => `...${sourceCode.getText(arg)}`); - if (first.properties.length === 0) { - texts.shift(); - } + fix(fixer) { + const texts = args.map((arg) => `...${sourceCode.getText(arg)}`); + if (first.properties.length === 0) { + texts.shift(); + } - return fixer.replaceText(node, `({${texts.join(',')}})`); - }, - }); - }, + return fixer.replaceText(node, `({${texts.join(',')}})`); + }, + }); + }, }; }, }; diff --git a/build-system/eslint-rules/prefer-unnested-spread-objects.js b/build-system/eslint-rules/prefer-unnested-spread-objects.js index 19df38eb8b105..0d7f6c59652b3 100644 --- a/build-system/eslint-rules/prefer-unnested-spread-objects.js +++ b/build-system/eslint-rules/prefer-unnested-spread-objects.js @@ -50,6 +50,11 @@ module.exports = { const spreadElement = ':matches(ObjectExpression > ExperimentalSpreadProperty, ObjectExpression > SpreadElement)'; + /** + * @param {Array} array + * @param {T} item + * @return {?T} + */ function findAfter(array, item) { const index = array.indexOf(item); return index < array.length - 1 ? array[index + 1] : null; @@ -62,7 +67,7 @@ module.exports = { message: 'Nesting an object under an object spread is not useful', fix(fixer) { - const {properties, parent} = node; + const {parent, properties} = node; const texts = properties.map((prop) => sourceCode.getText(prop)); if (texts.length > 0) { diff --git a/build-system/eslint-rules/private-prop-names.js b/build-system/eslint-rules/private-prop-names.js index f455751818b40..7fbf9da266140 100644 --- a/build-system/eslint-rules/private-prop-names.js +++ b/build-system/eslint-rules/private-prop-names.js @@ -54,8 +54,8 @@ module.exports = function (context) { return { MethodDefinition: function (node) { if ( - hasPrivateAnnotation(node.leadingComments) && - !hasTrailingUnderscore(node.key.name) + hasPrivateAnnotation(context.getCommentsBefore(node)) && + !hasTrailingUnderscore(node.key.name || node.key.value) ) { context.report({ node, @@ -66,9 +66,11 @@ module.exports = function (context) { AssignmentExpression: function (node) { if ( node.parent.type == 'ExpressionStatement' && - hasPrivateAnnotation(node.parent.leadingComments) && + hasPrivateAnnotation(context.getCommentsBefore(node.parent)) && isThisMemberExpression(node.left) && - !hasTrailingUnderscore(node.left.property.name) + !hasTrailingUnderscore( + node.left.property.name || node.left.property.value + ) ) { context.report({ node, diff --git a/build-system/eslint-rules/query-selector.js b/build-system/eslint-rules/query-selector.js index d15809400f50f..9fdd4b61c84f1 100644 --- a/build-system/eslint-rules/query-selector.js +++ b/build-system/eslint-rules/query-selector.js @@ -15,7 +15,12 @@ */ 'use strict'; +const cssWhat = require('css-what'); + module.exports = function (context) { + /** + * @param {CompilerNode} node + */ function callQuerySelector(node) { const {callee} = node; @@ -28,17 +33,24 @@ module.exports = function (context) { return; } - if (property.leadingComments) { - const ok = property.leadingComments.some((comment) => { - return comment.value === 'OK'; - }); - if (ok) { - return; - } + const leadingComments = context.getCommentsBefore(property); + const ok = leadingComments.some((comment) => { + return comment.value === 'OK'; + }); + if (ok) { + return; } const selector = getSelector(node, 0); + if (!isValidSelector(selector)) { + context.report({ + node, + message: 'Failed to parse CSS Selector `' + selector + '`', + }); + return; + } + // What are we calling querySelector on? let obj = callee.object; if (obj.type === 'CallExpression') { @@ -70,23 +82,33 @@ module.exports = function (context) { }); } + /** + * @param {CompilerNode} node + */ function callScopedQuerySelector(node) { const {callee} = node; if (!callee.name.startsWith('scopedQuerySelector')) { return; } - if (node.leadingComments) { - const ok = node.leadingComments.some((comment) => { - return comment.value === 'OK'; - }); - if (ok) { - return; - } + const leadingComments = context.getCommentsBefore(node); + const ok = leadingComments.some((comment) => { + return comment.value === 'OK'; + }); + if (ok) { + return; } const selector = getSelector(node, 1); + if (!isValidSelector(selector)) { + context.report({ + node, + message: 'Failed to parse CSS Selector `' + selector + '`', + }); + return; + } + if (selectorNeedsScope(selector)) { return; } @@ -99,6 +121,11 @@ module.exports = function (context) { }); } + /** + * @param {CompilerNode} node + * @param {number} argIndex + * @return {string} + */ function getSelector(node, argIndex) { const arg = node.arguments[argIndex]; let selector; @@ -124,6 +151,8 @@ module.exports = function (context) { ); if (callee.name === 'escapeCssSelectorIdent') { + // Add in a basic identifier to represent the call. + accumulator += 'foo'; if (inNthChild) { context.report({ node: expression, @@ -135,6 +164,8 @@ module.exports = function (context) { } continue; } else if (callee.name === 'escapeCssSelectorNth') { + // Add in a basic nth-selector to represent the call. + accumulator += '1'; if (!inNthChild) { context.report({ node: expression, @@ -157,7 +188,7 @@ module.exports = function (context) { }); } - selector = quasis.join(''); + selector = accumulator + quasis[quasis.length - 1]; } else { if (arg.type === 'BinaryExpression') { context.report({node: arg, message: 'Use a template literal string'}); @@ -165,18 +196,35 @@ module.exports = function (context) { selector = 'dynamic value'; } + return selector; + } + + /** + * @param {string} selector + * @return {boolean} + */ + function isValidSelector(selector) { + try { + cssWhat.parse(selector); + return true; + } catch (e) { + return false; + } + } + + /** + * Checks if the selector is using grandchild selector semantics + * `node.querySelector('child grandchild')` or `'child>grandchild'` But, + * specifically allow multi-selectors `'div, span'`. + * @param {string} selector + * @return {boolean} + */ + function selectorNeedsScope(selector) { // strip out things that can't affect children selection selector = selector.replace(/\(.*\)|\[.*\]/, function (match) { return match[0] + match[match.length - 1]; }); - return selector; - } - - // Checks if the selector is using grandchild selector semantics - // `node.querySelector('child grandchild')` or `'child>grandchild'` But, - // specifically allow multi-selectors `'div, span'`. - function selectorNeedsScope(selector) { // This regex actually verifies there is no whitespace (implicit child // semantics) or `>` chars (direct child semantics). The one exception is // for `,` multi-selectors, which can have whitespace. diff --git a/build-system/eslint-rules/unused-private-field.js b/build-system/eslint-rules/unused-private-field.js index 62a7b68b8341d..c63215294da67 100644 --- a/build-system/eslint-rules/unused-private-field.js +++ b/build-system/eslint-rules/unused-private-field.js @@ -19,13 +19,27 @@ module.exports = { meta: { fixable: 'code', }, - create(context) { + /** + * @type {{ + * used: Set, + * declared: Map, + * }[]} + * */ const stack = []; + /** + * @return {{ + * used: Set, + * declared: Map, + * }} + */ function current() { return stack[stack.length - 1]; } + /** + * @return {boolean} + */ function shouldIgnoreFile() { return /\b(test|examples)\b/.test(context.getFilename()); } @@ -37,6 +51,10 @@ module.exports = { '@override': uncheckableUse, }; + /** + * @param {CompilerNode} node + * @return {Function(): void|void} + */ function checkerForAnnotation(node) { const comments = context.getCommentsBefore(node); @@ -53,7 +71,12 @@ module.exports = { return unannotatedUse; } - // Restricteds must be used in the file, but not in the class. + /** + * Restricteds must be used in the file, but not in the class. + * @param {CompilerNode} node + * @param {string} name + * @param {boolean} used + */ function restrictedUse(node, name, used) { if (used) { const message = [ @@ -105,7 +128,13 @@ module.exports = { context.report({node, message}); } - // VisibleForTestings must not be used in the class. + /** + * VisibleForTestings must not be used in the class. + * + * @param {CompilerNode} node + * @param {string} name + * @param {boolean} used + */ function visibleForTestingUse(node, name, used) { if (!used) { return; @@ -119,12 +148,20 @@ module.exports = { context.report({node, message}); } - // Protected and Override are uncheckable. Let Closure handle that. + /** + * Protected and Override are uncheckable. Let Closure handle that. + */ function uncheckableUse() { // Noop. } - // Unannotated fields must be used in the class + /** + * Unannotated fields must be used in the class + * + * @param {CompilerNode} node + * @param {string} name + * @param {boolean} used + */ function unannotatedUse(node, name, used) { if (used) { return; @@ -141,6 +178,11 @@ module.exports = { context.report({node, message}); } + /** + * @param {CompilerNode} node + * @param {boolean=} needsThis + * @return {boolean} + */ function shouldCheckMember(node, needsThis = true) { const {computed, object, property} = node; if ( @@ -154,6 +196,10 @@ module.exports = { return isPrivateName(property); } + /** + * @param {CompilerNode} node + * @return {boolean} + */ function isAssignment(node) { const {parent} = node; if (!parent) { @@ -162,6 +208,10 @@ module.exports = { return parent.type === 'AssignmentExpression' && parent.left === node; } + /** + * @param {CompilerNode} node + * @return {boolean} + */ function isPrivateName(node) { return node.name.endsWith('_'); } @@ -180,7 +230,7 @@ module.exports = { return; } - const {used, declared} = stack.pop(); + const {declared, used} = stack.pop(); declared.forEach((node, name) => { const checker = checkerForAnnotation(node); diff --git a/build-system/eslint-rules/vsync.js b/build-system/eslint-rules/vsync.js index 317c3c2cf649a..5a875e873ed16 100644 --- a/build-system/eslint-rules/vsync.js +++ b/build-system/eslint-rules/vsync.js @@ -32,13 +32,12 @@ module.exports = function (context) { return; } - if (property.leadingComments) { - const ok = property.leadingComments.some((comment) => { - return comment.value === 'OK'; - }); - if (ok) { - return; - } + const leadingComments = context.getCommentsBefore(property); + const ok = leadingComments.some((comment) => { + return comment.value === 'OK'; + }); + if (ok) { + return; } context.report({ diff --git a/build-system/externs/OWNERS b/build-system/externs/OWNERS index 66f01bd6d028e..55a36524d91d2 100644 --- a/build-system/externs/OWNERS +++ b/build-system/externs/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/externs/amp.extern.js b/build-system/externs/amp.extern.js index 518b470ef05ed..11f7bceb7d8bf 100644 --- a/build-system/externs/amp.extern.js +++ b/build-system/externs/amp.extern.js @@ -31,7 +31,8 @@ * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch * * @typedef {{ - * body: (!JsonObject|!FormData|!FormDataWrapperInterface|undefined|string), + * responseType: (string|undefined), + * body: (!JsonObject|!FormData|!FormDataWrapperInterface|!Array|string|undefined|null), * cache: (string|undefined), * credentials: (string|undefined), * headers: (!JsonObject|undefined), @@ -42,47 +43,63 @@ * prerenderSafe: (boolean|undefined), * }} */ -var FetchInitDef; +let FetchInitDef; + +// TODO: add MediaQueryListEvent to closure's builtin externs. +/** + * @typedef {{ + * matches: function():boolean, + * media: string + * }} + */ +let MediaQueryListEvent; /** * Externed due to being passed across component/runtime boundary. * @typedef {{xhrUrl: string, fetchOpt: !FetchInitDef}} */ -var FetchRequestDef; +let FetchRequestDef; /** @constructor */ -var FormDataWrapperInterface = function () {}; +let FormDataWrapperInterface = function () {}; FormDataWrapperInterface.prototype.entries = function () {}; FormDataWrapperInterface.prototype.getFormData = function () {}; FormData.prototype.entries = function () {}; -/** - * A type for Objects that can be JSON serialized or that come from - * JSON serialization. Requires the objects fields to be accessed with - * bracket notation object['name'] to make sure the fields do not get - * obfuscated. - * @constructor - * @dict - */ -function JsonObject() {} - -/** - * @typedef {{ - * YOU_MUST_USE: string, - * jsonLiteral: function(), - * TO_MAKE_THIS_TYPE: string, - * }} - */ -var InternalJsonLiteralTypeDef; - /** * Force the dataset property to be handled as a JsonObject. * @type {!JsonObject} */ Element.prototype.dataset; +/** Needed for partial shadow DOM polyfill used in shadow docs. */ +Element.prototype.__AMP_SHADOW_ROOT; + +/** @type {?ShadowRoot} */ +Element.prototype.shadowRoot; + +// Fullscreen methods +// TODO: upstream these types to closure. +Document.prototype.cancelFullScreen; +Document.prototype.webkitExitFullscreen; +Element.prototype.cancelFullScreen; +Element.prototype.exitFullscreen; +Element.prototype.webkitExitFullscreen; +Element.prototype.webkitCancelFullScreen; +Element.prototype.mozCancelFullScreen; +Element.prototype.msExitFullscreen; +Element.prototype.requestFullscreen; +Element.prototype.requestFullScreen; +Element.prototype.webkitRequestFullscreen; +Element.prototype.webkitEnterFullscreen; +Element.prototype.msRequestFullscreen; +Element.prototype.mozRequestFullScreen; + +/** @type {boolean|undefined} */ +Element.prototype.webkitDisplayingFullscreen; + /** * - n is the name. * - f is the function body of the extension. @@ -96,25 +113,53 @@ Element.prototype.dataset; */ function ExtensionPayload() {} -/** @type {string} */ +/** + * Extension name. + * @type {string} + */ ExtensionPayload.prototype.n; -/** @type {function(!Object,!Object)} */ -ExtensionPayload.prototype.f; +/** + * Extension version. + * @type {string} + */ +ExtensionPayload.prototype.ev; + +/** + * Whether this extension version is the latest version. + * @type {boolean} + */ +ExtensionPayload.prototype.l; -/** @type {string|undefined} */ +/** + * Priority. + * @type {string|undefined} + */ ExtensionPayload.prototype.p; -/** @type {string} */ +/** + * RTV (release) version. + * @type {string} + */ ExtensionPayload.prototype.v; -/** @type {!Array|string|undefined} */ -ExtensionPayload.prototype.i; +/** + * If the value of "m" is 1 then the current extension is of type "module", + * else it is of type "nomodule". + * @type {number} + */ +ExtensionPayload.prototype.m; + +/** + * Install function. + * @type {function(!Object,!Object)} + */ +ExtensionPayload.prototype.f; /** * @typedef {?JsonObject|undefined|string|number|!Array} */ -var JsonValue; +let JsonValue; /** * @constructor @@ -143,16 +188,13 @@ VideoAnalyticsDetailsDef.prototype.state; VideoAnalyticsDetailsDef.prototype.width; // Node.js global -var process = {}; +let process = {}; process.env; process.env.NODE_ENV; -/** @type {boolean|undefined} */ -window.IS_AMP_ALT; - // Exposed to ads. // Preserve these filedNames so they can be accessed by 3p code. -window.context = {}; +window.context; window.context.sentinel; window.context.clientId; window.context.initialLayoutRect; @@ -181,23 +223,39 @@ window.context.tagName; // Safeframe // TODO(bradfrizzell) Move to its own extern. Not relevant to all AMP. -/* @type {?Object} */ -window.sf_ = {}; -/* @type {?Object} */ +window.sf_; window.sf_.cfg; // Exposed to custom ad iframes. -/* @type {!Function} */ +/** @type {function(function(!Object, function(!Object)), !Array=, !Array=)} */ window.draw3p; // AMP's globals +window.testLocation; +window.Location.originalHash; window.__AMP_SERVICES; window.__AMP_TEST; window.__AMP_TEST_IFRAME; window.__AMP_TAG; window.__AMP_TOP; window.__AMP_PARENT; -window.AMP = {}; +window.__AMP_WEAKREF_ID; +window.__AMP_URL_CACHE; +window.__AMP_LOG; + +/** @type {undefined|boolean} */ +window.ENABLE_LOG; + +// TODO: uncomment line below when src/mode.js is added to typechecking. +// https://github.com/ampproject/amphtml/issues/34099 + +// /** @type {undefined|../../src/mode.ModeDef} */ +window.__AMP_MODE; + +/** @type {boolean|undefined} */ +window.AMP_DEV_MODE; + +window.AMP; window.AMP._ = {}; window.AMP.push; window.AMP.title; @@ -207,7 +265,6 @@ window.AMP.ampdoc; window.AMP.config; window.AMP.config.urls; window.AMP.BaseElement; -window.AMP.BaseTemplate; window.AMP.registerElement; window.AMP.registerTemplate; window.AMP.registerServiceForDoc; @@ -216,11 +273,20 @@ window.AMP.toggleExperiment; window.AMP.setLogLevel; window.AMP.setTickFunction; window.AMP.viewer; -window.AMP.viewport = {}; +window.AMP.viewport; window.AMP.viewport.getScrollLeft; window.AMP.viewport.getScrollWidth; window.AMP.viewport.getWidth; +/** + * This symbol is exposed by bundles transformed by `scoped-require.js` to avoid + * polluting the global namespace with `require`. + * It allows AMP extensions to consume code injected into their binaries that + * cannot be run through Closure Compiler, e.g. React code with JSX. + * @type {!function(string):?} + */ +window.AMP.require; + /** @type {function(!HTMLElement, !Document, !string, Object)} */ window.AMP.attachShadowDoc = function (element, document, url, options) {}; @@ -314,7 +380,7 @@ let VegaParser; * @typedef {{parse: VegaParser}} */ let VegaObject; -/* @type {!VegaObject} */ +/** @type {!VegaObject} */ window.vg; // amp-date-picker externs @@ -323,15 +389,30 @@ window.vg; */ let ReactRender = function () {}; +/** @constructor */ +let RRule; +/** + * @param {Date} unusedDt + * @param {boolean=} unusedInc + * @return {?Date} + */ +RRule.prototype.before = function (unusedDt, unusedInc) {}; +/** + * @param {Date} unusedDt + * @param {boolean=} unusedInc + * @return {?Date} + */ +RRule.prototype.after = function (unusedDt, unusedInc) {}; + /** * @dict */ let PropTypes = {}; /** - * @@dict + * @dict */ -let ReactDates = {}; +let ReactDates; /** @constructor */ ReactDates.DayPickerSingleDateController; @@ -363,7 +444,7 @@ ReactDatesConstants.HORIZONTAL_ORIENTATION; /** * @constructor */ -var Inputmask = class {}; +let Inputmask = class {}; /** @param {!Object} unusedOpts */ Inputmask.extendAliases = function (unusedOpts) {}; @@ -385,37 +466,165 @@ window.AMP.dependencies = {}; */ window.AMP.dependencies.inputmaskFactory = function (unusedElement) {}; -// Should have been defined in the closure compiler's extern file for -// IntersectionObserverEntry, but appears to have been omitted. -IntersectionObserverEntry.prototype.rootBounds; - // TODO (remove after we update closure compiler externs) window.PerformancePaintTiming; window.PerformanceObserver; Object.prototype.entryTypes; +Window.prototype.origin; +HTMLAnchorElement.prototype.origin; /** @typedef {number} */ -var time; - -/** - * This type signifies a callback that can be called to remove the listener. - * @typedef {function()} - */ -var UnlistenDef; +let time; /** * Just an element, but used with AMP custom elements.. - * @typedef {!Element} + * @constructor @extends {HTMLElement} */ -var AmpElement; +let AmpElement = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.R1 = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.deferredMount = function () {}; /** @return {!Signals} */ AmpElement.prototype.signals = function () {}; -var Signals = class {}; +/** */ +AmpElement.prototype.pause = function () {}; + +/** */ +AmpElement.prototype.unmount = function () {}; + +/** + * @param {number=} opt_parentPriority + * @return {!Promise} + */ +AmpElement.prototype.ensureLoaded = function (opt_parentPriority) {}; + +/** @return {?Element} */ +AmpElement.prototype.getPlaceholder = function () {}; + +/** @param {boolean} show */ +AmpElement.prototype.togglePlaceholder = function (show) {}; + +/** @return {{width: number, height: number}} */ +AmpElement.prototype.getLayoutSize = function () {}; + +/** + * TODO: remove this when typechecking is restored to AMP.BaseElement. + * @typedef {*} + */ +let BaseElement; + +/** + * @param {boolean=} opt_waitForBuild + * @return {!Promise} + */ +AmpElement.prototype.getImpl = function (opt_waitForBuild) {}; + +/** @return {!Promise} */ +AmpElement.prototype.buildInternal = function () {}; + +/** @return {!Promise} */ +AmpElement.prototype.mountInternal = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.isBuilt = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.isBuilding = function () {}; + +/** @return {number} */ +AmpElement.prototype.getBuildPriority = function () {}; + +/** @return {number} */ +AmpElement.prototype.getLayoutPriority = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.isRelayoutNeeded = function () {}; + +/** @return {boolean|number} */ +AmpElement.prototype.renderOutsideViewport = function () {}; + +/** @return {boolean|number} */ +AmpElement.prototype.idleRenderOutsideViewport = function () {}; + +/** @type {number|undefined} */ +AmpElement.prototype.layoutScheduleTime; + +/** @return {!Promise} */ +AmpElement.prototype.layoutCallback = function () {}; + +/** */ +AmpElement.prototype.unlayoutCallback = function () {}; + +/** @return {!Promise} */ +AmpElement.prototype.whenLoaded = function () {}; + +/** @param {boolean} pretendDisconnected */ +AmpElement.prototype.disconnect = function (pretendDisconnected) {}; + +/** @return {boolean} */ +AmpElement.prototype.reconstructWhenReparented = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.isBuildRenderBlocking = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.prerenderAllowed = function () {}; + +/** @return {string} */ +AmpElement.prototype.getLayout = function () {}; + +/** + * @param {{width: number, height: number, top: number, bottom: number}} layoutBox + * @param {boolean=} opt_sizeChanged + */ +AmpElement.prototype.updateLayoutBox = function (layoutBox, opt_sizeChanged) {}; + +/** */ +AmpElement.prototype.collapsedCallback = function () {}; + +/** @return {boolean} */ +AmpElement.prototype.isUpgraded = function () {}; + +/** @return {number} */ +AmpElement.prototype.getUpgradeDelayMs = function () {}; + +/** + * @param {boolean} overflown + * @param {number|undefined} requestedHeight + * @param {number|undefined} requestedWidth + */ +AmpElement.prototype.overflowCallback = function ( + overflown, + requestedHeight, + requestedWidth +) {}; + +/** + * @param {number|undefined} newHeight + * @param {number|undefined} newWidth + * @param {?} opt_newMargins + */ +AmpElement.prototype.applySize = function ( + newHeight, + newWidth, + opt_newMargins +) {}; + +/** */ +AmpElement.prototype.expand = function () {}; + +/** */ +AmpElement.prototype.collapse = function () {}; + +let Signals = class {}; /** * @param {string} unusedName - * @return {number|!Error|null} + * @return {?number|?Error} */ Signals.prototype.get = function (unusedName) {}; @@ -442,19 +651,19 @@ Signals.prototype.reset = function (unusedName) {}; // Temp until we figure out forward declarations /** @constructor */ -var AccessService = function () {}; +let AccessService = function () {}; /** @constructor @struct */ -var UserNotificationManager = function () {}; +let UserNotificationManager = function () {}; UserNotificationManager.prototype.get; /** @constructor @struct */ -var Cid = function () {}; +let Cid = function () {}; /** @constructor @struct */ -var Activity = function () {}; +let Activity = function () {}; /** @constructor */ -var AmpStoryVariableService = function () {}; +let AmpStoryVariableService = function () {}; // data -var data; +let data; data.tweetid; data.requestedHeight; data.requestedWidth; @@ -502,7 +711,7 @@ data.sitekey; data.fortesting; // 3p code -var twttr; +let twttr; twttr.events; twttr.events.bind; twttr.widgets; @@ -510,26 +719,26 @@ twttr.widgets.createTweet; twttr.widgets.createMoment; twttr.widgets.createTimeline; -var FB; +let FB; FB.init; -var gist; +let gist; gist.gistid; -var bodymovin; +let bodymovin; bodymovin.loadAnimation; -var animationHandler; +let animationHandler; animationHandler.play; animationHandler.pause; animationHandler.stop; animationHandler.goToAndStop; animationHandler.totalFrames; -var grecaptcha; +let grecaptcha; grecaptcha.execute; // Validator -var amp; +let amp; amp.validator; amp.validator.validateUrlAndLog = function (string, doc) {}; @@ -551,7 +760,7 @@ AccessService.prototype.getAuthdataField = function (field) {}; * cookieName: (string|undefined), * }} */ -var GetCidDef; +let GetCidDef; /** * @param {string|!GetCidDef} externalCidScope Name of the fallback cookie * for the case where this doc is not served by an AMP proxy. GetCidDef @@ -585,57 +794,60 @@ AmpStoryVariableService.prototype.onStateChange = function (event) {}; AmpStoryVariableService.pageIndex; AmpStoryVariableService.pageId; -var AMP = {}; -window.AMP; -// Externed explicitly because we do not export Class shaped names -// by default. -/** - * This uses the internal name of the type, because there appears to be no - * other way to reference an ES6 type from an extern that is defined in - * the app. - * @constructor @struct - * @extends {BaseElement$$module$src$base_element} - */ -AMP.BaseElement = class { - /** @param {!AmpElement} element */ - constructor(element) {} -}; - -/** - * This uses the internal name of the type, because there appears to be no - * other way to reference an ES6 type from an extern that is defined in - * the app. - * @constructor @struct - * @extends {AmpAdXOriginIframeHandler$$module$extensions$amp_ad$0_1$amp_ad_xorigin_iframe_handler} - */ -AMP.AmpAdXOriginIframeHandler = class { - /** - * @param {!AmpAd3PImpl$$module$extensions$amp_ad$0_1$amp_ad_3p_impl|!AmpA4A$$module$extensions$amp_a4a$0_1$amp_a4a} baseInstance - */ - constructor(baseInstance) {} -}; - -/** - * This uses the internal name of the type, because there appears to be no - * other way to reference an ES6 type from an extern that is defined in - * the app. - * @constructor @struct - * @extends {AmpAdUIHandler$$module$extensions$amp_ad$0_1$amp_ad_ui} - */ -AMP.AmpAdUIHandler = class { - /** - * @param {!AMP.BaseElement} baseInstance - */ - constructor(baseInstance) {} -}; - -AMP.BaseTemplate; - -AMP.RealTimeConfigManager; +// TODO: uncomment when typechecking restored to BaseElement. +// https://github.com/ampproject/amphtml/issues/34099 + +// // Externed explicitly because we do not export Class shaped names +// // by default. +// /** +// * This uses the internal name of the type, because there appears to be no +// * other way to reference an ES6 type from an extern that is defined in +// * the app. +// * @constructor @struct +// * @extends {BaseElement$$module$src$base_element} +// */ +// AMP.BaseElement = class { +// /** @param {!AmpElement} element */ +// constructor(element) {} +// }; + +// TODO: uncomment when typechecking restored to AmpAdXOriginIframeHandler. +// https://github.com/ampproject/amphtml/issues/34099 + +// /** +// * This uses the internal name of the type, because there appears to be no +// * other way to reference an ES6 type from an extern that is defined in +// * the app. +// * @constructor @struct +// * @extends {AmpAdXOriginIframeHandler$$module$extensions$amp_ad$0_1$amp_ad_xorigin_iframe_handler} +// */ +// AMP.AmpAdXOriginIframeHandler = class { +// /** +// * @param {!AmpAd3PImpl$$module$extensions$amp_ad$0_1$amp_ad_3p_impl|!AmpA4A$$module$extensions$amp_a4a$0_1$amp_a4a} baseInstance +// */ +// constructor(baseInstance) {} +// }; + +// TODO: uncomment when typechecking is restored to AmpAdUIHandler. +// https://github.com/ampproject/amphtml/issues/34099 + +// /** +// * This uses the internal name of the type, because there appears to be no +// * other way to reference an ES6 type from an extern that is defined in +// * the app. +// * @constructor @struct +// * @extends {AmpAdUIHandler$$module$extensions$amp_ad$0_1$amp_ad_ui} +// */ +// AMP.AmpAdUIHandler = class { +// /** +// * @param {!AMP.BaseElement} baseInstance +// */ +// constructor(baseInstance) {} +// }; /** * Actual filled values for this exists in - * extensions/amp-a4a/0.1/real-time-config-manager.js + * src/service/real-time-config/real-time-config-impl.js * @enum {string} */ const RTC_ERROR_ENUM = {}; @@ -645,16 +857,7 @@ const RTC_ERROR_ENUM = {}; rtcTime: number, callout: string, error: (RTC_ERROR_ENUM|undefined)}} */ -var rtcResponseDef; - -/** - * This symbol is exposed by browserify bundles transformed by - * `scoped-require.js` to avoid polluting the global namespace with `require`. - * It allows AMP extensions to consume code injected into their binaries that - * cannot be run through Closure Compiler, e.g. React code with JSX. - * @type {!function(string):?} - */ -AMP.require; +let rtcResponseDef; /** * TransitionDef function that accepts normtime, typically between 0 and 1 and @@ -664,7 +867,7 @@ AMP.require; * transition and "false" for ongoing. * @typedef {function(number, boolean):?|function(number):?} */ -var TransitionDef; +let TransitionDef; /////////////////// // amp-bind externs @@ -743,7 +946,7 @@ let BindSetStateOptionsDef; * !WebKeyframeAnimationDef * } */ -var WebAnimationDef; +let WebAnimationDef; /** * @mixes WebAnimationSelectorDef @@ -754,7 +957,7 @@ var WebAnimationDef; * animations: !Array, * }} */ -var WebMultiAnimationDef; +let WebMultiAnimationDef; /** * @mixes WebAnimationSelectorDef @@ -765,7 +968,7 @@ var WebMultiAnimationDef; * switch: !Array, * }} */ -var WebSwitchAnimationDef; +let WebSwitchAnimationDef; /** * @mixes WebAnimationSelectorDef @@ -776,7 +979,7 @@ var WebSwitchAnimationDef; * animation: string, * }} */ -var WebCompAnimationDef; +let WebCompAnimationDef; /** * @mixes WebAnimationSelectorDef @@ -787,12 +990,12 @@ var WebCompAnimationDef; * keyframes: (string|!WebKeyframesDef), * }} */ -var WebKeyframeAnimationDef; +let WebKeyframeAnimationDef; /** * @typedef {!Object|!Array>} */ -var WebKeyframesDef; +let WebKeyframesDef; /** * See https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffectTimingProperties @@ -809,7 +1012,7 @@ var WebKeyframesDef; * fill: (?|undefined), * }} */ -var WebAnimationTimingDef; +let WebAnimationTimingDef; /** * Indicates an extension to a type that allows specifying vars. Vars are @@ -818,7 +1021,7 @@ var WebAnimationTimingDef; * @mixin * @typedef {Object} */ -var WebAnimationVarsDef; +let WebAnimationVarsDef; /** * Defines media parameters for an animation. @@ -829,7 +1032,7 @@ var WebAnimationVarsDef; * supports: (string|undefined), * }} */ -var WebAnimationConditionalDef; +let WebAnimationConditionalDef; /** * @typedef {{ @@ -838,7 +1041,7 @@ var WebAnimationConditionalDef; * subtargets: (!Array|undefined), * }} */ -var WebAnimationSelectorDef; +let WebAnimationSelectorDef; /** * @mixes WebAnimationTimingDef @@ -849,9 +1052,9 @@ var WebAnimationSelectorDef; * selector: (string|undefined), * }} */ -var WebAnimationSubtargetDef; +let WebAnimationSubtargetDef; -var ampInaboxPositionObserver; +let ampInaboxPositionObserver; ampInaboxPositionObserver.observe; ampInaboxPositionObserver.getTargetRect; ampInaboxPositionObserver.getViewportRect; @@ -886,7 +1089,37 @@ class FeaturePolicy { getAllowlistForFeature(feature) {} } +/** @type {boolean} */ +HTMLVideoElement.prototype.playsInline; + /** - * @type {?FeaturePolicy} + * Going through the standardization process now. + * + * See https://developers.google.com/web/updates/2019/02/constructable-stylesheets. + * + * @param {string} cssText */ -HTMLIFrameElement.prototype.featurePolicy; +CSSStyleSheet.prototype.replaceSync = function (cssText) {}; + +/** + * @constructor @struct + */ +function ResizeObserverSize() {} + +/** @type {number} */ +ResizeObserverSize.prototype.inlineSize; + +/** @type {number} */ +ResizeObserverSize.prototype.blockSize; + +/** @type {!Array|undefined} */ +ResizeObserverEntry.prototype.borderBoxSize; + +/** @type {?function(!MediaQueryListEvent)} */ +MediaQueryList.prototype.onchange; + +/** @type {!Array|undefined} */ +ShadowRoot.prototype.adoptedStyleSheets; + +/** @type {undefined|boolean} */ +Error.prototype.expected; diff --git a/build-system/externs/amp.multipass.extern.js b/build-system/externs/amp.multipass.extern.js deleted file mode 100644 index 7c2a3259fdf8c..0000000000000 --- a/build-system/externs/amp.multipass.extern.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2018 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. - */ - -/** @externs */ - -// Place externs here if they are only needed in legacy multi pass compilation. - -var SomeBaseElementLikeClass; -SomeBaseElementLikeClass.prototype.layout_; - -/** @type {boolean} */ -SomeBaseElementLikeClass.prototype.inViewport_; - -SomeBaseElementLikeClass.prototype.actionMap_; - -SomeBaseElementLikeClass.prototype.defaultActionAlias_; - -// Externed explicitly because this private property is read across -// binaries. -Element.prototype.implementation_ = {}; diff --git a/build-system/externs/dompurify.extern.js b/build-system/externs/dompurify.extern.js index 85d0759ad78f8..648eaae6b09a7 100644 --- a/build-system/externs/dompurify.extern.js +++ b/build-system/externs/dompurify.extern.js @@ -23,8 +23,7 @@ DomPurifyConfig.prototype.ALLOWED_TAGS; DomPurifyConfig.prototype.ALLOWED_ATTR; DomPurifyConfig.prototype.FORBID_TAGS; DomPurifyConfig.prototype.FORBID_ATTR; -/** @type {?Object} */ -DomPurifyConfig.prototype.USE_PROFILES = {}; +DomPurifyConfig.prototype.USE_PROFILES; /** @type {boolean} */ DomPurifyConfig.prototype.USE_PROFILES.html; /** @type {boolean} */ diff --git a/build-system/externs/preact.extern.js b/build-system/externs/preact.extern.js index 3e38cf308d5fd..1a435e13777ec 100644 --- a/build-system/externs/preact.extern.js +++ b/build-system/externs/preact.extern.js @@ -20,7 +20,7 @@ var PreactDef = {}; /** - * @typedef {function(!JsonObject):PreactDef.Renderable} + * @typedef {function(?):PreactDef.Renderable} */ PreactDef.FunctionalComponent; @@ -31,11 +31,12 @@ PreactDef.VNode = function () {}; /** * @interface + * @template T */ PreactDef.Context = function () {}; /** - * @param {!JsonObject} props + * @param {{value: T, children: (?PreactDef.Renderable|undefined)}} props * @return {PreactDef.Renderable} */ PreactDef.Context.prototype.Provider = function (props) {}; @@ -54,3 +55,8 @@ PreactDef.SimpleRenderable; * @typedef {PreactDef.SimpleRenderable|!PreactDef.VNode|!Array>} */ PreactDef.Renderable; + +/** + * @typedef {{__html: ?string}} + */ +PreactDef.InnerHTML; diff --git a/build-system/global-configs/OWNERS b/build-system/global-configs/OWNERS index 8b621ec46039a..6e93a82b6597f 100644 --- a/build-system/global-configs/OWNERS +++ b/build-system/global-configs/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/global-configs/README.md b/build-system/global-configs/README.md index 08384581a748d..bf758d0938508 100644 --- a/build-system/global-configs/README.md +++ b/build-system/global-configs/README.md @@ -7,16 +7,19 @@ besides 1 or 0. # experiments-config.json -- `name`: Experiment name -- `environment`: Specify the type of environment the experiment runs. Only support `AMP` and `INABOX`. -- `command`: Command used to build the experiment -- `issue`: The issue tracker URL for this experiment -- `expiration_date_utc`: The experiment expiration date in YYYY-MM-DD format, in UTC. If an experiment is expired, it will fail the build process. This expiration date is only read during the build. As a result, the experiment will actually end on the following release date after the expiration. -- `define_experiment_constant`: (Optional) The flag that is used to section out experiment code. This is passed into the `minify-replace` babel plugin and defaults to `false`. If an experiment relies on `minify-replace` to replace its experiment flag, this value must be defined. +This config is used to run server side diverted experiments. Please review the instruction [here](../../docs/running-server-side-experiment.md) beforehand. + +- `name`: Experiment name +- `environment`: Specify the type of environment the experiment runs. Only support `AMP` and `INABOX`. +- `issue`: The issue tracker URL for this experiment +- `expiration_date_utc`: The experiment expiration date in YYYY-MM-DD format, in UTC. If an experiment is expired, it will fail the build process. This expiration date is only read during the build. As a result, the experiment will actually end on the following release date after the expiration. +- `define_experiment_constant`: (Optional) The flag that is used to section out experiment code. This is passed into the `minify-replace` babel plugin and defaults to `false`. If an experiment relies on `minify-replace` to replace its experiment flag, this value must be defined. + +Experiments will be built automatically for AMP releases by running `amp dist --esm --define_experiment_constant ${define_experiment_constant} && amp dist --define_experiment_constant ${define_experiment_constant}` # custom-config.json -If `build-system/global-configs/custom-config.json` exists at build time, its properties will overlay the active config. For example, consider `gulp dist --config=canary` with the following configs: +If `build-system/global-configs/custom-config.json` exists at build time, its properties will overlay the active config. For example, consider `amp dist --config=canary` with the following configs: `canary-config.json` (simplified for brevity) diff --git a/build-system/global-configs/canary-config.json b/build-system/global-configs/canary-config.json index 404d2da0dda62..61c89917dd372 100644 --- a/build-system/global-configs/canary-config.json +++ b/build-system/global-configs/canary-config.json @@ -1,43 +1,24 @@ { - "allow-doc-opt-in": [ - "amp-next-page", - "analytics-chunks", - "analytics-chunks-inabox" - ], - "allow-url-opt-in": ["pump-early-frame"], + "allow-doc-opt-in": ["amp-next-page"], + "allow-url-opt-in": [], "canary": 1, "a4aProfilingRate": 0.01, - "adsense-ad-size-optimization": 0.01, - "amp-access-iframe": 1, - "amp-action-macro": 1, - "amp-ad-ff-adx-ady": 0.01, - "amp-auto-ads-adsense-holdout": 0.1, - "amp-consent-restrict-fullscreen": 1, - "amp-list-init-from-state": 1, - "amp-mega-menu": 1, - "amp-nested-menu": 1, - "amp-playbuzz": 1, - "amp-sidebar-swipe-to-dismiss": 1, - "amp-story-responsive-units": 1, - "amp-story-v1": 1, - "ampdoc-closest": 1, - "as-use-attr-for-format": 0.01, - "chunked-amp": 1, + "adsense-ad-size-optimization": 1, "doubleclickSraExp": 0.01, "doubleclickSraReportExcludedBlock": 0.1, - "fix-inconsistent-responsive-height-selection": 0, - "fixed-elements-in-lightbox": 1, + "dfp-render-on-idle-cwv-exp": 1, + "expand-json-targeting": 1, "flexAdSlots": 0.05, - "hidden-mutation-observer": 1, - "intersect-resources": 1, + "flexible-bitrate": 0.1, "ios-fixed-no-transfer": 1, - "layoutbox-invalidate-on-scroll": 1, - "pump-early-frame": 1, - "random-subdomain-for-safeframe": 0.1, - "swg-gpay-api": 1, - "swg-gpay-native": 1, - "version-locking": 1, - "amp-ad-no-center-css": 1, - "analytics-chunks": 1, - "fie-init-chunking": 0.1 + "visibility-trigger-improvements": 1, + "ads-initialIntersection": 1, + "amp-cid-backup": 1, + "layout-aspect-ratio-css": 1, + "tcf-post-message-proxy-api": 1, + "amp-consent-granular-consent": 1, + "disable-a4a-non-sd": 1, + "3p-vendor-split": 1, + "story-ad-auto-advance": 1, + "story-ad-placements": 1 } diff --git a/build-system/global-configs/experiments-config.json b/build-system/global-configs/experiments-config.json index cb92459501bbc..40e9e2f4360ca 100644 --- a/build-system/global-configs/experiments-config.json +++ b/build-system/global-configs/experiments-config.json @@ -1,12 +1,11 @@ { "experimentA": {}, "experimentB": { - "name": "MoveFixedLayer", + "name": "amp-img in the deferred mount mode", "environment": "AMP", - "command": "gulp dist --define_experiment_constant=MOVE_FIXED_LAYER", - "issue": "https://github.com/ampproject/amphtml/issues/27120", - "expiration_date_utc": "2021-01-01", - "define_experiment_constant": "MOVE_FIXED_LAYER" + "issue": "https://github.com/ampproject/amphtml/issues/31915", + "expiration_date_utc": "2021-06-30", + "define_experiment_constant": "R1_IMG_DEFERRED_BUILD" }, "experimentC": {} } diff --git a/build-system/global-configs/experiments-const.json b/build-system/global-configs/experiments-const.json index 64f15e7af9e7e..332a29fe9a9f7 100644 --- a/build-system/global-configs/experiments-const.json +++ b/build-system/global-configs/experiments-const.json @@ -1,5 +1,6 @@ { - "INTERSECTION_OBSERVER_POLYFILL": true, - "INTERSECTION_OBSERVER_POLYFILL_INABOX": true, - "MOVE_FIXED_LAYER": false + "INI_LOAD_INOB": true, + "NO_SIGNING_RTV": true, + "R1_IMG_DEFERRED_BUILD": false, + "WITHIN_VIEWPORT_INOB": false } diff --git a/build-system/global-configs/prod-config.json b/build-system/global-configs/prod-config.json index a3e0724177eee..6df5eef504926 100644 --- a/build-system/global-configs/prod-config.json +++ b/build-system/global-configs/prod-config.json @@ -1,43 +1,21 @@ { - "allow-doc-opt-in": [ - "amp-next-page", - "analytics-chunks", - "analytics-chunks-inabox" - ], - "allow-url-opt-in": ["pump-early-frame"], + "allow-doc-opt-in": ["amp-next-page"], + "allow-url-opt-in": [], "canary": 0, "a4aProfilingRate": 0.01, - "adsense-ad-size-optimization": 0.01, - "amp-access-iframe": 1, - "amp-action-macro": 1, - "amp-accordion-display-locking": 0.01, - "amp-ad-ff-adx-ady": 0.01, - "amp-auto-ads-adsense-holdout": 0.1, - "amp-consent-restrict-fullscreen": 1, - "amp-list-init-from-state": 1, - "amp-mega-menu": 1, - "amp-nested-menu": 1, - "amp-playbuzz": 1, - "amp-sidebar-swipe-to-dismiss": 1, - "amp-story-responsive-units": 1, - "amp-story-v1": 1, - "ampdoc-closest": 1, - "as-use-attr-for-format": 0.01, - "chunked-amp": 1, + "adsense-ad-size-optimization": 1, "doubleclickSraExp": 0.01, "doubleclickSraReportExcludedBlock": 0.1, - "fix-inconsistent-responsive-height-selection": 0, - "fixed-elements-in-lightbox": 1, + "expand-json-targeting": 1, "flexAdSlots": 0.05, - "hidden-mutation-observer": 1, + "flexible-bitrate": 0.1, "ios-fixed-no-transfer": 0, - "layoutbox-invalidate-on-scroll": 1, - "pump-early-frame": 1, - "random-subdomain-for-safeframe": 0.1, - "swg-gpay-api": 1, - "swg-gpay-native": 1, - "version-locking": 1, - "amp-ad-no-center-css": 1, - "analytics-chunks": 1, - "fie-init-chunking": 0.1 + "visibility-trigger-improvements": 1, + "layout-aspect-ratio-css": 0, + "disable-a4a-non-sd": 1, + "tcf-post-message-proxy-api": 1, + "amp-consent-granular-consent": 1, + "amp-cid-backup": 1, + "3p-vendor-split": 0.5, + "story-ad-placements": 0.01 } diff --git a/build-system/global.d.ts b/build-system/global.d.ts new file mode 100644 index 0000000000000..92379997e0dbf --- /dev/null +++ b/build-system/global.d.ts @@ -0,0 +1,51 @@ +declare global { + interface CompilerNode { + type: string; + name: string; + arguments: []; + left: CompilerNode; + right: CompilerNode; + } + + interface BabelPath { + node: CompilerNode; + } + + interface EslintContext { + report: (val: any) => void; + } + + interface Window { + queryXpath: (xpath: string, root: unknown /** Puppeteer.ElementHandle */) => unknown[] | null; + AMP: Function[]; + viewer?: { + receivedMessages?: number; + }; + __coverage__: any; + } + + interface Error { + status?: string; + } + + namespace Mocha { + interface TestFunction { + configure: Function; + } + } + + namespace NodeJS { + interface Global { + repl?: () => Promise & { + controller; + env; + continue; + }; + Key?: string; + describes?: unknown; + expect?: Function; + } + } +} + +export { } diff --git a/build-system/npm-publish/build-npm-binaries.js b/build-system/npm-publish/build-npm-binaries.js new file mode 100644 index 0000000000000..b892bc8848280 --- /dev/null +++ b/build-system/npm-publish/build-npm-binaries.js @@ -0,0 +1,28 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * Builds npm binaries for specified Bento components. + */ + +const [extension] = process.argv.slice(2); +const {timedExecOrDie} = require('../pr-check/utils'); +const {updatePackages} = require('../common/update-packages'); + +updatePackages(); +timedExecOrDie(`amp build --extensions=${extension} --core_runtime_only`); +timedExecOrDie(`amp dist --extensions=${extension} --core_runtime_only`); diff --git a/build-system/npm-publish/get-extensions.js b/build-system/npm-publish/get-extensions.js new file mode 100644 index 0000000000000..8a484e634c5d1 --- /dev/null +++ b/build-system/npm-publish/get-extensions.js @@ -0,0 +1,27 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * Gets Bento components to publish. + */ + +const bundles = require('../compile/bundles.config.extensions.json'); +const extensions = bundles + .filter((bundle) => bundle.options?.npm) + .map((bundle) => ({'extension': bundle.name})); +console /*OK*/ + .log(JSON.stringify(extensions)); diff --git a/build-system/npm-publish/write-package-files.js b/build-system/npm-publish/write-package-files.js new file mode 100644 index 0000000000000..2aa67247891d5 --- /dev/null +++ b/build-system/npm-publish/write-package-files.js @@ -0,0 +1,151 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * Creates npm package files for a given component and AMP version. + */ + +const [extension, ampVersion] = process.argv.slice(2); +const {log} = require('../common/logging'); +const {stat, writeFile} = require('fs/promises'); +const {valid} = require('semver'); + +/** + * Determines whether to skip + * @param {string} extensionVersion + * @return {Promise} + */ +async function shouldSkip(extensionVersion) { + try { + await stat(`extensions/${extension}/${extensionVersion}`); + return false; + } catch { + log(`${extension} ${extensionVersion} : skipping, does not exist`); + return true; + } +} + +/** + * Write package.json + * @param {string} extensionVersion + */ +async function writePackageJson(extensionVersion) { + const extensionVersionArr = extensionVersion.split('.', 2); + const major = extensionVersionArr[0]; + const minor = ampVersion.slice(0, 10); + const patch = Number(ampVersion.slice(-3)); // npm trims leading zeroes in patch number, so mimic this in package.json + const version = `${major}.${minor}.${patch}`; + if ( + !valid(version) || + ampVersion.length != 13 || + extensionVersionArr[1] !== '0' + ) { + log( + 'Invalid semver version', + version, + 'or AMP version', + ampVersion, + 'or extension version', + extensionVersion + ); + process.exitCode = 1; + return; + } + + const json = { + name: `@ampproject/${extension}`, + version, + description: `AMP HTML ${extension} Component`, + author: 'The AMP HTML Authors', + license: 'Apache-2.0', + main: './dist/component.js', + module: './dist/component-preact.module.js', + exports: { + '.': './preact', + './preact': { + import: './dist/component-preact.module.js', + require: './dist/component-preact.js', + }, + './react': { + import: './dist/component-react.module.js', + require: './dist/component-react.js', + }, + }, + files: ['dist/*', 'react.js'], + repository: { + type: 'git', + url: 'https://github.com/ampproject/amphtml.git', + directory: `extensions/${extension}/${extensionVersion}`, + }, + homepage: `https://github.com/ampproject/amphtml/tree/main/extensions/${extension}/${extensionVersion}`, + peerDependencies: { + preact: '^10.2.1', + react: '^17.0.0', + }, + }; + + try { + await writeFile( + `extensions/${extension}/${extensionVersion}/package.json`, + JSON.stringify(json, null, 2) + ); + log( + extension, + extensionVersion, + ': created package.json for', + json.version + ); + } catch (e) { + log(e); + process.exitCode = 1; + return; + } +} + +/** + * Write react.js + * @param {string} extensionVersion + */ +async function writeReactJs(extensionVersion) { + const content = "module.exports = require('./dist/component-react');"; + try { + await writeFile( + `extensions/${extension}/${extensionVersion}/react.js`, + content + ); + log(extension, extensionVersion, ': created react.js'); + } catch (e) { + log(e); + process.exitCode = 1; + return; + } +} + +/** + * Main + */ +async function main() { + for (const version of ['1.0', '2.0']) { + if (await shouldSkip(version)) { + continue; + } + writePackageJson(version); + writeReactJs(version); + } +} + +main(); diff --git a/build-system/pr-check/OWNERS b/build-system/pr-check/OWNERS index 8425845cae3ee..002922bb9cc93 100644 --- a/build-system/pr-check/OWNERS +++ b/build-system/pr-check/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/pr-check/build-targets.js b/build-system/pr-check/build-targets.js index 87205da90bb37..6a3be40cb04d0 100644 --- a/build-system/pr-check/build-targets.js +++ b/build-system/pr-check/build-targets.js @@ -20,12 +20,75 @@ * This script sets the build targets for our PR check, where the build targets * determine which tasks are required to run for pull request builds. */ -const colors = require('ansi-colors'); const config = require('../test-configs/config'); +const globby = require('globby'); const minimatch = require('minimatch'); const path = require('path'); -const {gitDiffNameOnlyMaster} = require('../common/git'); -const {isTravisBuild} = require('../common/travis'); +const {cyan} = require('../common/colors'); +const {getLoggingPrefix, logWithoutTimestamp} = require('../common/logging'); +const {gitDiffNameOnlyMain} = require('../common/git'); +const {isCiBuild} = require('../common/ci'); + +/** + * Used to prevent the repeated recomputing of build targets during PR jobs. + */ +let buildTargets; + +/** + * Used to prevent the repeated expansion of globs during PR jobs. + */ +const fileLists = {}; + +/*** + * All of AMP's build targets that can be tested during CI. + * + * @enum {string} + */ +const Targets = { + AVA: 'AVA', + BABEL_PLUGIN: 'BABEL_PLUGIN', + BUILD_SYSTEM: 'BUILD_SYSTEM', + CACHES_JSON: 'CACHES_JSON', + DEV_DASHBOARD: 'DEV_DASHBOARD', + DOCS: 'DOCS', + E2E_TEST: 'E2E_TEST', + HTML_FIXTURES: 'HTML_FIXTURES', + INTEGRATION_TEST: 'INTEGRATION_TEST', + INVALID_WHITESPACES: 'INVALID_WHITESPACES', + LINT: 'LINT', + LINT_RULES: 'LINT_RULES', + OWNERS: 'OWNERS', + PACKAGE_UPGRADE: 'PACKAGE_UPGRADE', + PRESUBMIT: 'PRESUBMIT', + PRETTIFY: 'PRETTIFY', + RENOVATE_CONFIG: 'RENOVATE_CONFIG', + RUNTIME: 'RUNTIME', + SERVER: 'SERVER', + UNIT_TEST: 'UNIT_TEST', + VALIDATOR: 'VALIDATOR', + VALIDATOR_WEBUI: 'VALIDATOR_WEBUI', + VISUAL_DIFF: 'VISUAL_DIFF', +}; + +/** + * Files matching these targets are known not to affect the runtime. For all + * other targets, we play safe and default to adding the RUNTIME target, which + * will trigger all the runtime tests. + */ +const nonRuntimeTargets = [ + Targets.AVA, + Targets.CACHES_JSON, + Targets.DEV_DASHBOARD, + Targets.DOCS, + Targets.E2E_TEST, + Targets.INTEGRATION_TEST, + Targets.OWNERS, + Targets.RENOVATE_CONFIG, + Targets.UNIT_TEST, + Targets.VALIDATOR, + Targets.VALIDATOR_WEBUI, + Targets.VISUAL_DIFF, +]; /** * Checks if the given file is an OWNERS file. @@ -38,7 +101,7 @@ function isOwnersFile(file) { } /** - * Checks if the given file is of the form validator-.*\.(html|out|protoascii) + * Checks if the given file is of the form validator-.*\.(html|out|out.cpponly|protoascii) * * @param {string} file * @return {boolean} @@ -48,6 +111,7 @@ function isValidatorFile(file) { return ( name.startsWith('validator-') && (name.endsWith('.out') || + name.endsWith('.out.cpponly') || name.endsWith('.html') || name.endsWith('.protoascii')) ); @@ -55,20 +119,23 @@ function isValidatorFile(file) { /** * A dictionary of functions that match a given file to a given build target. + * Owners files are special because they live all over the repo, so most target + * matchers must first make sure they're not matching an owners file. */ const targetMatchers = { - 'AVA': (file) => { + [Targets.AVA]: (file) => { if (isOwnersFile(file)) { return false; } return ( file == 'build-system/tasks/ava.js' || - file.startsWith('build-system/tasks/csvify-size/') || file.startsWith('build-system/tasks/get-zindex/') || + file.startsWith('build-system/tasks/make-extension/') || + file.startsWith('build-system/tasks/markdown-toc/') || file.startsWith('build-system/tasks/prepend-global/') ); }, - 'BABEL_PLUGIN': (file) => { + [Targets.BABEL_PLUGIN]: (file) => { if (isOwnersFile(file)) { return false; } @@ -83,7 +150,20 @@ const targetMatchers = { file.startsWith('build-system/babel-config/') ); }, - 'CACHES_JSON': (file) => { + [Targets.BUILD_SYSTEM]: (file) => { + if (isOwnersFile(file)) { + return false; + } + return ( + file == 'build-system/tasks/check-build-system.js' || + file == 'build-system/tsconfig.json' || + (file.startsWith('build-system') && + (file.endsWith('.js') || + file.endsWith('.ts') || + file.endsWith('.json'))) + ); + }, + [Targets.CACHES_JSON]: (file) => { if (isOwnersFile(file)) { return false; } @@ -92,7 +172,7 @@ const targetMatchers = { file == 'build-system/global-configs/caches.json' ); }, - 'DEV_DASHBOARD': (file) => { + [Targets.DEV_DASHBOARD]: (file) => { if (isOwnersFile(file)) { return false; } @@ -102,16 +182,17 @@ const targetMatchers = { file.startsWith('build-system/server/app-index/') ); }, - 'DOCS': (file) => { + [Targets.DOCS]: (file) => { if (isOwnersFile(file)) { return false; } return ( + fileLists.linkCheckFiles.includes(file) || file == 'build-system/tasks/check-links.js' || - (path.extname(file) == '.md' && !file.startsWith('examples/')) + file.startsWith('build-system/tasks/markdown-toc/') ); }, - 'E2E_TEST': (file) => { + [Targets.E2E_TEST]: (file) => { if (isOwnersFile(file)) { return false; } @@ -122,13 +203,14 @@ const targetMatchers = { }) ); }, - 'FLAG_CONFIG': (file) => { - if (isOwnersFile(file)) { - return false; - } - return file.startsWith('build-system/global-configs/'); + [Targets.HTML_FIXTURES]: (file) => { + return ( + fileLists.htmlFixtureFiles.includes(file) || + file == 'build-system/tasks/validate-html-fixtures.js' || + file.startsWith('build-system/test-configs') + ); }, - 'INTEGRATION_TEST': (file) => { + [Targets.INTEGRATION_TEST]: (file) => { if (isOwnersFile(file)) { return false; } @@ -141,25 +223,64 @@ const targetMatchers = { }) ); }, - 'OWNERS': (file) => { + [Targets.INVALID_WHITESPACES]: (file) => { + return ( + fileLists.invalidWhitespaceFiles.includes(file) || + file == 'build-system/tasks/check-invalid-whitespaces.js' || + file.startsWith('build-system/test-configs') + ); + }, + [Targets.LINT]: (file) => { + if (isOwnersFile(file)) { + return false; + } + return ( + fileLists.lintFiles.includes(file) || + file == 'build-system/tasks/lint.js' || + file.startsWith('build-system/test-configs') + ); + }, + [Targets.LINT_RULES]: (file) => { + return file.endsWith('.eslintrc.js') || file == 'package.json'; + }, + [Targets.OWNERS]: (file) => { return isOwnersFile(file) || file == 'build-system/tasks/check-owners.js'; }, - 'PACKAGE_UPGRADE': (file) => { - return file == 'package.json' || file == 'yarn.lock'; + [Targets.PACKAGE_UPGRADE]: (file) => { + return file.endsWith('package.json') || file.endsWith('package-lock.json'); }, - 'RENOVATE_CONFIG': (file) => { + [Targets.PRESUBMIT]: (file) => { + if (isOwnersFile(file)) { + return false; + } + return ( + fileLists.presubmitFiles.includes(file) || + file == 'build-system/tasks/presubmit-checks.js' || + file.startsWith('build-system/test-configs') + ); + }, + [Targets.PRETTIFY]: (file) => { + // OWNERS files can be prettified. + return ( + fileLists.prettifyFiles.includes(file) || + file == '.prettierrc' || + file == '.prettierignore' || + file == 'build-system/tasks/prettify.js' + ); + }, + [Targets.RENOVATE_CONFIG]: (file) => { return ( file == '.renovaterc.json' || file == 'build-system/tasks/check-renovate-config.js' ); }, - 'RUNTIME': (file) => { + [Targets.RUNTIME]: (file) => { if (isOwnersFile(file)) { return false; } return file.startsWith('src/'); }, - 'SERVER': (file) => { + [Targets.SERVER]: (file) => { if (isOwnersFile(file)) { return false; } @@ -169,7 +290,7 @@ const targetMatchers = { file.startsWith('build-system/server/') ); }, - 'UNIT_TEST': (file) => { + [Targets.UNIT_TEST]: (file) => { if (isOwnersFile(file)) { return false; } @@ -181,12 +302,8 @@ const targetMatchers = { }) ); }, - 'VALIDATOR': (file) => { - if ( - isOwnersFile(file) || - file.startsWith('validator/webui/') || - file.startsWith('validator/java/') - ) { + [Targets.VALIDATOR]: (file) => { + if (isOwnersFile(file) || file.startsWith('validator/js/webui/')) { return false; } return ( @@ -195,25 +312,16 @@ const targetMatchers = { isValidatorFile(file) ); }, - 'VALIDATOR_JAVA': (file) => { - if (isOwnersFile(file)) { - return false; - } - return ( - file.startsWith('validator/java/') || - file === 'build-system/tasks/validator.js' - ); - }, - 'VALIDATOR_WEBUI': (file) => { + [Targets.VALIDATOR_WEBUI]: (file) => { if (isOwnersFile(file)) { return false; } return ( - file.startsWith('validator/webui/') || + file.startsWith('validator/js/webui/') || file === 'build-system/tasks/validator.js' ); }, - 'VISUAL_DIFF': (file) => { + [Targets.VISUAL_DIFF]: (file) => { if (isOwnersFile(file)) { return false; } @@ -226,47 +334,83 @@ const targetMatchers = { }; /** - * Populates buildTargets with a set of build targets contained in a PR after - * making sure they are valid. Used to determine which checks to perform / tests - * to run during PR builds. - * @param {string} fileName - * @return {boolean} + * Returns the set of build targets affected by a PR after making sure they are + * valid. Used to determine which checks to perform / tests to run during PR + * builds. Exits early if targets have already been populated. + * @return {Set} */ -function determineBuildTargets(fileName = 'build-targets.js') { - const filesChanged = gitDiffNameOnlyMaster(); - const buildTargets = new Set(); +function determineBuildTargets() { + if (buildTargets != undefined) { + return buildTargets; + } + expandFileLists(); + buildTargets = new Set(); + const filesChanged = gitDiffNameOnlyMain(); for (const file of filesChanged) { - let matched = false; + let isRuntimeFile = true; Object.keys(targetMatchers).forEach((target) => { const matcher = targetMatchers[target]; if (matcher(file)) { buildTargets.add(target); - matched = true; + if (nonRuntimeTargets.includes(target)) { + isRuntimeFile = false; + } } }); - if (!matched) { - buildTargets.add('RUNTIME'); // Default to RUNTIME for files that don't match a target. + if (isRuntimeFile) { + buildTargets.add(Targets.RUNTIME); } } - const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - console.log( - `${fileLogPrefix} Detected build targets:`, - colors.cyan(Array.from(buildTargets).sort().join(', ')) + const loggingPrefix = getLoggingPrefix(); + logWithoutTimestamp( + `${loggingPrefix} Detected build targets:`, + cyan(Array.from(buildTargets).sort().join(', ')) ); - // Test the runtime for babel plugin and server changes. - if (buildTargets.has('BABEL_PLUGIN') || buildTargets.has('SERVER')) { - buildTargets.add('RUNTIME'); - } - // Test all targets except VALIDATOR_JAVA on Travis during package upgrades. - if (isTravisBuild() && buildTargets.has('PACKAGE_UPGRADE')) { + // Test all targets during CI builds for package upgrades. + if (isCiBuild() && buildTargets.has(Targets.PACKAGE_UPGRADE)) { + logWithoutTimestamp( + `${loggingPrefix} Running all tests since this PR contains package upgrades...` + ); const allTargets = Object.keys(targetMatchers); - allTargets - .filter((target) => target != 'VALIDATOR_JAVA') - .forEach((target) => buildTargets.add(target)); + allTargets.forEach((target) => buildTargets.add(target)); } return buildTargets; } +/** + * Returns true if a PR affects one or more of the given build targets. + * + * @param {...string} targets + * @return {boolean} + */ +function buildTargetsInclude(...targets) { + if (buildTargets.size == 0) { + determineBuildTargets(); + } + return Array.from(targets).some((target) => buildTargets.has(target)); +} + +/** + * Helper that expands some of the config globs used to match files. Called once + * at the start in order to avoid repeated glob expansion. + */ +function expandFileLists() { + const globNames = [ + 'htmlFixtureGlobs', + 'invalidWhitespaceGlobs', + 'linkCheckGlobs', + 'lintGlobs', + 'presubmitGlobs', + 'prettifyGlobs', + ]; + for (const globName of globNames) { + const fileListName = globName.replace('Globs', 'Files'); + fileLists[fileListName] = globby.sync(config[globName], {dot: true}); + } +} + module.exports = { + buildTargetsInclude, determineBuildTargets, + Targets, }; diff --git a/build-system/pr-check/build.js b/build-system/pr-check/build.js deleted file mode 100644 index d92cecddd7f73..0000000000000 --- a/build-system/pr-check/build.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -/** - * @fileoverview - * This script builds the AMP runtime. - * This is run during the CI stage = build; job = build. - */ - -const colors = require('ansi-colors'); -const { - printChangeSummary, - startTimer, - stopTimer, - stopTimedJob, - timedExecOrDie: timedExecOrDieBase, - uploadBuildOutput, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); -const {runYarnChecks} = require('./yarn-checks'); - -const FILENAME = 'build.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -function main() { - const startTime = startTimer(FILENAME, FILENAME); - if (!runYarnChecks(FILENAME)) { - stopTimedJob(FILENAME, startTime); - return; - } - - if (!isTravisPullRequestBuild()) { - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp build --fortesting'); - uploadBuildOutput(FILENAME); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('UNIT_TEST') - ) { - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp build --fortesting'); - uploadBuildOutput(FILENAME); - } else { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Build'), - 'because this commit does not affect the runtime, flag configs,', - 'or integration tests.' - ); - } - } - - stopTimer(FILENAME, FILENAME, startTime); -} - -main(); diff --git a/build-system/pr-check/bundle-size.js b/build-system/pr-check/bundle-size.js new file mode 100644 index 0000000000000..a34f6426729c3 --- /dev/null +++ b/build-system/pr-check/bundle-size.js @@ -0,0 +1,51 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Script that runs the bundle-size checks during CI. + */ + +const {runCiJob} = require('./ci-job'); +const {skipDependentJobs, timedExecOrDie} = require('./utils'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'bundle-size.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp dist --noconfig --esm'); + timedExecOrDie('amp dist --noconfig'); + timedExecOrDie('amp bundle-size --on_push_build'); +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME)) { + timedExecOrDie('amp dist --noconfig --esm'); + timedExecOrDie('amp dist --noconfig'); + timedExecOrDie('amp bundle-size --on_pr_build'); + } else { + timedExecOrDie('amp bundle-size --on_skipped_build'); + skipDependentJobs(jobName, 'this PR does not affect the runtime'); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/checks.js b/build-system/pr-check/checks.js index fb44fa7466b50..c5f935168b8b1 100644 --- a/build-system/pr-check/checks.js +++ b/build-system/pr-check/checks.js @@ -16,104 +16,129 @@ 'use strict'; /** - * @fileoverview - * This script performs quick checks on the source code - * prior to running unit and integration tests. - * This is run during the CI stage = build; job = checks. + * @fileoverview Script that runs various checks during CI. */ -const { - printChangeSummary, - startTimer, - stopTimer, - stopTimedJob, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); const {reportAllExpectedTests} = require('../tasks/report-test-status'); -const {runYarnChecks} = require('./yarn-checks'); - -const FILENAME = 'checks.js'; -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -async function main() { - const startTime = startTimer(FILENAME, FILENAME); - if (!runYarnChecks(FILENAME)) { - stopTimedJob(FILENAME, startTime); - return; - } - - if (!isTravisPullRequestBuild()) { - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp check-exact-versions'); - timedExecOrDie('gulp lint'); - timedExecOrDie('gulp prettify'); - timedExecOrDie('gulp presubmit'); - timedExecOrDie('gulp ava'); - timedExecOrDie('gulp babel-plugin-tests'); - timedExecOrDie('gulp caches-json'); - timedExecOrDie('gulp dev-dashboard-tests'); - timedExecOrDie('gulp check-renovate-config'); - timedExecOrDie('gulp server-tests'); - timedExecOrDie('gulp dep-check'); - timedExecOrDie('gulp check-types'); - timedExecOrDie('gulp check-sourcemaps'); - timedExecOrDie('gulp performance-urls'); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - await reportAllExpectedTests(buildTargets); - timedExecOrDie('gulp update-packages'); - - timedExecOrDie('gulp check-exact-versions'); - timedExecOrDie('gulp lint'); - timedExecOrDie('gulp prettify'); - timedExecOrDie('gulp presubmit'); - timedExecOrDie('gulp performance-urls'); - - if (buildTargets.has('AVA')) { - timedExecOrDie('gulp ava'); - } - - if (buildTargets.has('BABEL_PLUGIN')) { - timedExecOrDie('gulp babel-plugin-tests'); - } - - if (buildTargets.has('CACHES_JSON')) { - timedExecOrDie('gulp caches-json'); - } - - // Check document links only for PR builds. - if (buildTargets.has('DOCS')) { - timedExecOrDie('gulp check-links --local_changes'); - } - - if (buildTargets.has('DEV_DASHBOARD')) { - timedExecOrDie('gulp dev-dashboard-tests'); - } - - // Validate owners syntax only for PR builds. - if (buildTargets.has('OWNERS')) { - timedExecOrDie('gulp check-owners --local_changes'); - } - - if (buildTargets.has('RENOVATE_CONFIG')) { - timedExecOrDie('gulp check-renovate-config'); - } - - if (buildTargets.has('SERVER')) { - timedExecOrDie('gulp server-tests'); - } - - if (buildTargets.has('RUNTIME')) { - timedExecOrDie('gulp dep-check'); - timedExecOrDie('gulp check-types'); - timedExecOrDie('gulp check-sourcemaps'); - } - } - - stopTimer(FILENAME, FILENAME, startTime); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); +const {timedExecOrDie} = require('./utils'); + +const jobName = 'checks.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp presubmit'); + timedExecOrDie('amp check-invalid-whitespaces'); + timedExecOrDie('amp validate-html-fixtures'); + timedExecOrDie('amp lint'); + timedExecOrDie('amp prettify'); + timedExecOrDie('amp ava'); + timedExecOrDie('amp check-build-system'); + timedExecOrDie('amp babel-plugin-tests'); + timedExecOrDie('amp caches-json'); + timedExecOrDie('amp dev-dashboard-tests'); + timedExecOrDie('amp check-exact-versions'); + timedExecOrDie('amp check-renovate-config'); + timedExecOrDie('amp server-tests'); + timedExecOrDie('amp make-extension --name=t --test --cleanup'); + timedExecOrDie('amp make-extension --name=t --test --cleanup --bento'); + timedExecOrDie('amp dep-check'); + timedExecOrDie('amp check-types'); + timedExecOrDie('amp check-sourcemaps'); + timedExecOrDie('amp performance-urls'); + timedExecOrDie('amp check-analytics-vendors-list'); + timedExecOrDie('amp check-video-interface-list'); + timedExecOrDie('amp get-zindex'); + timedExecOrDie('amp markdown-toc'); +} + +/** + * Steps to run during PR builds. + * @return {Promise} + */ +async function prBuildWorkflow() { + await reportAllExpectedTests(); + + if (buildTargetsInclude(Targets.PRESUBMIT)) { + timedExecOrDie('amp presubmit'); + } + + if (buildTargetsInclude(Targets.INVALID_WHITESPACES)) { + timedExecOrDie('amp check-invalid-whitespaces'); + } + + if (buildTargetsInclude(Targets.HTML_FIXTURES)) { + timedExecOrDie('amp validate-html-fixtures'); + } + + if (buildTargetsInclude(Targets.LINT_RULES)) { + timedExecOrDie('amp lint'); + } else if (buildTargetsInclude(Targets.LINT)) { + timedExecOrDie('amp lint --local_changes'); + } + + if (buildTargetsInclude(Targets.PRETTIFY)) { + timedExecOrDie('amp prettify'); + } + + if (buildTargetsInclude(Targets.AVA)) { + timedExecOrDie('amp ava'); + } + + if (buildTargetsInclude(Targets.BUILD_SYSTEM)) { + timedExecOrDie('amp check-build-system'); + } + + if (buildTargetsInclude(Targets.BABEL_PLUGIN)) { + timedExecOrDie('amp babel-plugin-tests'); + } + + if (buildTargetsInclude(Targets.CACHES_JSON)) { + timedExecOrDie('amp caches-json'); + } + + if (buildTargetsInclude(Targets.DOCS)) { + timedExecOrDie('amp check-links --local_changes'); // only for PR builds + timedExecOrDie('amp markdown-toc'); + } + + if (buildTargetsInclude(Targets.DEV_DASHBOARD)) { + timedExecOrDie('amp dev-dashboard-tests'); + } + + if (buildTargetsInclude(Targets.OWNERS)) { + timedExecOrDie('amp check-owners --local_changes'); // only for PR builds + } + + if (buildTargetsInclude(Targets.PACKAGE_UPGRADE)) { + timedExecOrDie('amp check-exact-versions'); + } + + if (buildTargetsInclude(Targets.RENOVATE_CONFIG)) { + timedExecOrDie('amp check-renovate-config'); + } + + if (buildTargetsInclude(Targets.SERVER)) { + timedExecOrDie('amp server-tests'); + } + + if (buildTargetsInclude(Targets.AVA, Targets.RUNTIME)) { + timedExecOrDie('amp make-extension --name=t --test --cleanup'); + timedExecOrDie('amp make-extension --name=t --test --cleanup --bento'); + } + + if (buildTargetsInclude(Targets.RUNTIME)) { + timedExecOrDie('amp dep-check'); + timedExecOrDie('amp check-types'); + timedExecOrDie('amp check-sourcemaps'); + timedExecOrDie('amp performance-urls'); + timedExecOrDie('amp check-analytics-vendors-list'); + timedExecOrDie('amp check-video-interface-list'); + timedExecOrDie('amp get-zindex'); + } } -main(); +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/ci-job.js b/build-system/pr-check/ci-job.js new file mode 100644 index 0000000000000..a3f7352b4ed5b --- /dev/null +++ b/build-system/pr-check/ci-job.js @@ -0,0 +1,61 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const { + abortTimedJob, + printChangeSummary, + startTimer, + stopTimer, +} = require('./utils'); +const { + getLoggingPrefix, + logWithoutTimestamp, + setLoggingPrefix, +} = require('../common/logging'); +const {determineBuildTargets} = require('./build-targets'); +const {isPullRequestBuild} = require('../common/ci'); +const {red} = require('../common/colors'); +const {updatePackages} = require('../common/update-packages'); + +/** + * Helper used by all CI job scripts. Runs the PR / push build workflow. + * @param {string} jobName + * @param {function} pushBuildWorkflow + * @param {function} prBuildWorkflow + */ +async function runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow) { + setLoggingPrefix(jobName); + const startTime = startTimer(jobName); + try { + updatePackages(); + if (isPullRequestBuild()) { + printChangeSummary(); + determineBuildTargets(); + await prBuildWorkflow(); + } else { + await pushBuildWorkflow(); + } + stopTimer(jobName, startTime); + } catch (err) { + logWithoutTimestamp(getLoggingPrefix(), red('ERROR:'), err); + abortTimedJob(jobName, startTime); + } +} + +module.exports = { + runCiJob, +}; diff --git a/build-system/pr-check/cross-browser-tests.js b/build-system/pr-check/cross-browser-tests.js new file mode 100644 index 0000000000000..f2dae4e261aaa --- /dev/null +++ b/build-system/pr-check/cross-browser-tests.js @@ -0,0 +1,152 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Script that builds and tests on Linux, macOS, and Windows during CI. + */ + +const {cyan, red} = require('../common/colors'); +const {log} = require('../common/logging'); +const {reportAllExpectedTests} = require('../tasks/report-test-status'); +const {runCiJob} = require('./ci-job'); +const {skipDependentJobs, timedExecOrDie} = require('./utils'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'cross-browser-tests.js'; + +/** + * Helper that runs platform-specific integration tests + */ +function runIntegrationTestsForPlatform() { + switch (process.platform) { + case 'linux': + timedExecOrDie( + 'amp integration --nobuild --compiled --headless --firefox' + ); + break; + case 'darwin': + timedExecOrDie('amp integration --nobuild --compiled --safari'); + break; + case 'win32': + timedExecOrDie('amp integration --nobuild --compiled --headless --edge'); + timedExecOrDie('amp integration --nobuild --compiled --ie'); + break; + default: + log( + red('ERROR:'), + 'Cannot run cross-browser integration tests on', + cyan(process.platform) + '.' + ); + } +} + +/** + * Helper that runs platform-specific E2E tests + */ +function runE2eTestsForPlatform() { + switch (process.platform) { + case 'linux': + timedExecOrDie('amp e2e --nobuild --compiled --browsers=firefox'); + break; + case 'darwin': + timedExecOrDie('amp e2e --nobuild --compiled --browsers=safari'); + break; + case 'win32': + break; + default: + log( + red('ERROR:'), + 'Cannot run cross-browser E2E tests on', + cyan(process.platform) + '.' + ); + } +} + +/** + * Helper that runs platform-specific unit tests + */ +function runUnitTestsForPlatform() { + switch (process.platform) { + case 'linux': + timedExecOrDie('amp unit --headless --firefox'); + break; + case 'darwin': + timedExecOrDie('amp unit --safari'); + break; + case 'win32': + timedExecOrDie('amp unit --headless --edge'); + break; + default: + log( + red('ERROR:'), + 'Cannot run cross-browser unit tests on', + cyan(process.platform) + '.' + ); + } +} +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + runUnitTestsForPlatform(); + timedExecOrDie('amp dist --fortesting'); + runIntegrationTestsForPlatform(); +} + +/** + * Steps to run during PR builds. + * @return {Promise} + */ +async function prBuildWorkflow() { + if (process.platform == 'linux') { + await reportAllExpectedTests(); // Only once is sufficient. + } + if ( + !buildTargetsInclude( + Targets.RUNTIME, + Targets.UNIT_TEST, + Targets.E2E_TEST, + Targets.INTEGRATION_TEST + ) + ) { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime, unit tests, integration tests, or end-to-end tests' + ); + return; + } + if (buildTargetsInclude(Targets.RUNTIME, Targets.UNIT_TEST)) { + runUnitTestsForPlatform(); + } + if ( + buildTargetsInclude( + Targets.RUNTIME, + Targets.INTEGRATION_TEST, + Targets.E2E_TEST + ) + ) { + timedExecOrDie('amp dist --fortesting'); + } + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { + runIntegrationTestsForPlatform(); + } + if (buildTargetsInclude(Targets.RUNTIME, Targets.E2E_TEST)) { + runE2eTestsForPlatform(); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/dist-bundle-size.js b/build-system/pr-check/dist-bundle-size.js deleted file mode 100644 index a0d95a462b398..0000000000000 --- a/build-system/pr-check/dist-bundle-size.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -/** - * @fileoverview - * This script builds the minified AMP runtime and runs the bundle size check. - * This is run during the CI stage = build; job = dist, bundle size. - */ - -const colors = require('ansi-colors'); -const { - printChangeSummary, - processAndUploadDistOutput, - startTimer, - stopTimer, - stopTimedJob, - timedExecWithError, - timedExecOrDie: timedExecOrDieBase, - uploadDistOutput, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); -const {runYarnChecks} = require('./yarn-checks'); -const {signalDistUpload} = require('../tasks/pr-deploy-bot-utils'); - -const FILENAME = 'dist-bundle-size.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -async function main() { - const startTime = startTimer(FILENAME, FILENAME); - if (!runYarnChecks(FILENAME)) { - stopTimedJob(FILENAME, startTime); - return; - } - - if (!isTravisPullRequestBuild()) { - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp dist --fortesting'); - timedExecOrDie('gulp bundle-size --on_push_build'); - uploadDistOutput(FILENAME); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('INTEGRATION_TEST') || - buildTargets.has('E2E_TEST') || - buildTargets.has('VISUAL_DIFF') || - buildTargets.has('UNIT_TEST') - ) { - timedExecOrDie('gulp update-packages'); - - const process = timedExecWithError('gulp dist --fortesting', FILENAME); - if (process.error) { - console.log(colors.red('ERROR'), colors.yellow(process.error.message)); - await signalDistUpload('errored'); - stopTimedJob(FILENAME, startTime); - return; - } - - timedExecOrDie('gulp bundle-size --on_pr_build'); - await processAndUploadDistOutput(FILENAME); - } else { - timedExecOrDie('gulp bundle-size --on_skipped_build'); - await signalDistUpload('skipped'); - - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Dist, Bundle Size'), - 'because this commit does not affect the runtime, flag configs,', - 'integration tests, end-to-end tests, or visual diff tests.' - ); - } - } - - stopTimer(FILENAME, FILENAME, startTime); -} - -main(); diff --git a/build-system/pr-check/dist-tests.js b/build-system/pr-check/dist-tests.js deleted file mode 100644 index c5e83fa41dab0..0000000000000 --- a/build-system/pr-check/dist-tests.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2020 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. - */ -'use strict'; - -/** - * @fileoverview - * This script runs the unit and integration tests against minified code - * on a local Travis VM. - * This is run during the CI stage = test; job = dist tests. - */ - -const colors = require('ansi-colors'); -const { - downloadDistOutput, - printChangeSummary, - startTimer, - stopTimer, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); - -const FILENAME = 'dist-tests.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -function main() { - const startTime = startTimer(FILENAME, FILENAME); - - if (!isTravisPullRequestBuild()) { - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp integration --nobuild --headless --compiled'); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - !buildTargets.has('RUNTIME') && - !buildTargets.has('FLAG_CONFIG') && - !buildTargets.has('INTEGRATION_TEST') - ) { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Dist Tests'), - 'because this commit not affect the runtime, flag configs,', - 'or integration tests.' - ); - stopTimer(FILENAME, FILENAME, startTime); - return; - } - - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('INTEGRATION_TEST') - ) { - timedExecOrDie('gulp integration --nobuild --headless --compiled'); - } - } - - stopTimer(FILENAME, FILENAME, startTime); -} - -main(); diff --git a/build-system/pr-check/e2e-tests.js b/build-system/pr-check/e2e-tests.js index 5b34a16d80bff..53a5ffb8eb11b 100644 --- a/build-system/pr-check/e2e-tests.js +++ b/build-system/pr-check/e2e-tests.js @@ -16,54 +16,48 @@ 'use strict'; /** - * @fileoverview - * This script runs end to end tests. - * This is run during the CI stage = test; job = e2e tests. + * @fileoverview Script that runs the end-to-end tests during CI. */ -const colors = require('ansi-colors'); const { - downloadDistOutput, - printChangeSummary, - startTimer, - stopTimer, - timedExecOrDie: timedExecOrDieBase, + skipDependentJobs, + timedExecOrDie, + timedExecOrThrow, } = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); -const FILENAME = 'e2e-tests.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); +const jobName = 'e2e-tests.js'; -async function main() { - const startTime = startTimer(FILENAME, FILENAME); - - if (!isTravisPullRequestBuild()) { - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp e2e --nobuild --headless --compiled'); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('E2E_TEST') - ) { - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp e2e --nobuild --headless --compiled'); - } else { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('End to End Tests'), - 'because this commit does not affect the runtime, flag configs,', - 'or end-to-end tests' - ); +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + try { + timedExecOrThrow( + 'amp e2e --nobuild --headless --compiled --report', + 'End-to-end tests failed!' + ); + } catch (e) { + if (e.status) { + process.exitCode = e.status; } + } finally { + timedExecOrDie('amp test-report-upload'); } - stopTimer(FILENAME, FILENAME, startTime); } -main(); +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME, Targets.E2E_TEST)) { + timedExecOrDie('amp e2e --nobuild --headless --compiled'); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or end-to-end tests' + ); + } +} +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/experiment-build.js b/build-system/pr-check/experiment-build.js new file mode 100644 index 0000000000000..56bf47bfebbdd --- /dev/null +++ b/build-system/pr-check/experiment-build.js @@ -0,0 +1,65 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview Script that builds the experiment A/B/C runtime during CI. + */ + +const { + skipDependentJobs: skipDependentJobs, + storeExperimentBuildToWorkspace, + timedExecOrDie, +} = require('./utils'); +const {experiment} = require('minimist')(process.argv.slice(2)); +const {getExperimentConfig} = require('../common/utils'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = `${experiment}-build.js`; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + // Note that if config is invalid, this build would have been skipped by CircleCI. + const config = getExperimentConfig(experiment); + const defineFlag = `--define_experiment_constant ${config.define_experiment_constant}`; + timedExecOrDie(`amp dist --fortesting ${defineFlag}`); + storeExperimentBuildToWorkspace(experiment); +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if ( + buildTargetsInclude( + Targets.RUNTIME, + Targets.INTEGRATION_TEST, + Targets.E2E_TEST + ) + ) { + pushBuildWorkflow(); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime, integration tests, or end-to-end tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/experiment-tests.js b/build-system/pr-check/experiment-tests.js index bd39d39cbcdaa..6d5541ec694f0 100644 --- a/build-system/pr-check/experiment-tests.js +++ b/build-system/pr-check/experiment-tests.js @@ -16,67 +16,75 @@ 'use strict'; /** - * @fileoverview - * This script builds an experiment binary and runs local tests. - * This is run during the CI stage = test; job = experiments tests. + * @fileoverview Script that runs the experiment A/B/C tests during CI. */ -const colors = require('ansi-colors'); -const experimentsConfig = require('../global-configs/experiments-config.json'); const { - startTimer, - stopTimer, - timedExecOrDie: timedExecOrDieBase, + skipDependentJobs, + timedExecOrDie, + timedExecOrThrow, } = require('./utils'); const {experiment} = require('minimist')(process.argv.slice(2)); -const FILENAME = `${experiment}-tests.js`; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); +const {getExperimentConfig} = require('../common/utils'); +const {isPushBuild} = require('../common/ci'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); -function getConfig_() { - const config = experimentsConfig[experiment]; +const jobName = `${experiment}-tests.js`; - if (!config) { - return; - } - - if (!config.name || !config.command) { - return; - } - - if (new Date(config['expiration_date_utc']) < Date.now) { - return; +/** + * Runs tests for the given configuration and reports results for push builds. + * @param {!Object} config + */ +function runExperimentTests(config) { + try { + const defineFlag = `--define_experiment_constant ${config.define_experiment_constant}`; + const experimentFlag = `--experiment ${experiment}`; + const reportFlag = isPushBuild() ? '--report' : ''; + timedExecOrThrow( + `amp integration --nobuild --compiled --headless ${experimentFlag} ${defineFlag} ${reportFlag}` + ); + timedExecOrThrow( + `amp e2e --nobuild --compiled --headless ${experimentFlag} ${defineFlag} ${reportFlag}` + ); + } catch (e) { + if (e.status) { + process.exitCode = e.status; + } + } finally { + if (isPushBuild()) { + timedExecOrDie('amp test-report-upload'); + } } - - return config; -} - -function build_(config) { - const command = config.command.replace('gulp dist', 'gulp dist --fortesting'); - timedExecOrDie('gulp clean'); - timedExecOrDie('gulp update-packages'); - timedExecOrDie(command); } -function test_() { - timedExecOrDie('gulp integration --nobuild --compiled --headless'); - timedExecOrDie('gulp e2e --nobuild --compiled --headless'); +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + // Note that if config is invalid, this build would have been skipped by CircleCI. + const config = getExperimentConfig(experiment); + runExperimentTests(config); } -function main() { - const startTime = startTimer(FILENAME, FILENAME); - const config = getConfig_(); - if (config) { - build_(config); - test_(); +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if ( + buildTargetsInclude( + Targets.RUNTIME, + Targets.INTEGRATION_TEST, + Targets.E2E_TEST + ) + ) { + pushBuildWorkflow(); } else { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan(`${experiment} Tests`), - `because ${experiment} is expired or does not exist.` + skipDependentJobs( + jobName, + 'this PR does not affect the runtime, integration tests, or end-to-end tests' ); } - stopTimer(FILENAME, FILENAME, startTime); } -main(); +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/local-tests.js b/build-system/pr-check/local-tests.js deleted file mode 100644 index 91d20f92e6b62..0000000000000 --- a/build-system/pr-check/local-tests.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -/** - * @fileoverview - *This script runs the unit and integration tests on a local Travis VM. - * This is run during the CI stage = test; job = local tests. - */ - -const colors = require('ansi-colors'); -const { - downloadBuildOutput, - printChangeSummary, - startTimer, - stopTimer, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); - -const FILENAME = 'local-tests.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -function main() { - const startTime = startTimer(FILENAME, FILENAME); - - if (!isTravisPullRequestBuild()) { - downloadBuildOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp integration --nobuild --headless --coverage'); - timedExecOrDie('gulp unit --nobuild --headless --coverage'); - timedExecOrDie('gulp codecov-upload'); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - !buildTargets.has('RUNTIME') && - !buildTargets.has('FLAG_CONFIG') && - !buildTargets.has('UNIT_TEST') && - !buildTargets.has('INTEGRATION_TEST') - ) { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Local Tests'), - 'because this commit not affect the runtime, flag configs,', - 'unit tests, or integration tests.' - ); - stopTimer(FILENAME, FILENAME, startTime); - return; - } - - downloadBuildOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - - if (buildTargets.has('RUNTIME') || buildTargets.has('UNIT_TEST')) { - timedExecOrDie('gulp unit --nobuild --headless --local_changes'); - } - - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('INTEGRATION_TEST') - ) { - timedExecOrDie('gulp integration --nobuild --headless --coverage'); - } - - if (buildTargets.has('RUNTIME') || buildTargets.has('UNIT_TEST')) { - timedExecOrDie('gulp unit --nobuild --headless --coverage'); - } - - if (buildTargets.has('RUNTIME')) { - timedExecOrDie('gulp codecov-upload'); - } - } - - stopTimer(FILENAME, FILENAME, startTime); -} - -main(); diff --git a/build-system/pr-check/module-build.js b/build-system/pr-check/module-build.js new file mode 100644 index 0000000000000..74e047d006cdb --- /dev/null +++ b/build-system/pr-check/module-build.js @@ -0,0 +1,58 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Script that builds the module AMP runtime during CI. + */ + +const { + skipDependentJobs, + storeModuleBuildToWorkspace, + timedExecOrDie, +} = require('./utils'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'module-build.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp dist --esm --fortesting'); + storeModuleBuildToWorkspace(); +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + // TODO(#31102): This list must eventually match the same buildTargets check + // found in pr-check/nomodule-build.js as we turn on the systems that + // run against the module build. (ex. visual diffs, e2e, etc.) + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { + timedExecOrDie('amp dist --esm --fortesting'); + storeModuleBuildToWorkspace(); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or integration tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/module-dist-bundle-size.js b/build-system/pr-check/module-dist-bundle-size.js deleted file mode 100644 index 4cafe63ea0858..0000000000000 --- a/build-system/pr-check/module-dist-bundle-size.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright 2020 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. - */ -'use strict'; - -/** - * @fileoverview - * This script builds the esm minified AMP runtime and runs the bundle size check. - * This is run during the CI stage = build; job = module dist, bundle size. - */ - -const colors = require('ansi-colors'); -const { - printChangeSummary, - startTimer, - stopTimer, - stopTimedJob, - timedExecOrDie: timedExecOrDieBase, - uploadEsmDistOutput, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); -const {runYarnChecks} = require('./yarn-checks'); - -const FILENAME = 'module-dist-bundle-size.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -function main() { - const startTime = startTimer(FILENAME, FILENAME); - if (!runYarnChecks(FILENAME)) { - stopTimedJob(FILENAME, startTime); - return; - } - - if (!isTravisPullRequestBuild()) { - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp dist --esm --fortesting'); - // TODO(#27703): Run bundle size check (--on_push_build) - uploadEsmDistOutput(FILENAME); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if (buildTargets.has('RUNTIME') || buildTargets.has('FLAG_CONFIG')) { - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp dist --esm --fortesting'); - // TODO(#27703): Run bundle size check (--on_pr_build) - uploadEsmDistOutput(FILENAME); - } else { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Module Dist, Bundle Size'), - 'because this commit does not affect the runtime or flag configs.' - ); - } - } - - stopTimer(FILENAME, FILENAME, startTime); -} - -main(); diff --git a/build-system/pr-check/module-tests.js b/build-system/pr-check/module-tests.js new file mode 100644 index 0000000000000..1ec4a199b59be --- /dev/null +++ b/build-system/pr-check/module-tests.js @@ -0,0 +1,68 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Script that tests the module AMP runtime during CI. + */ + +const argv = require('minimist')(process.argv.slice(2)); +const {MINIFIED_TARGETS} = require('../tasks/helpers'); +const {runCiJob} = require('./ci-job'); +const {skipDependentJobs, timedExecOrDie} = require('./utils'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'module-tests.js'; + +/** + * Adds a canary or prod config string to all esm and non-esm minified targets. + */ +function prependConfig() { + const targets = MINIFIED_TARGETS.flatMap((target) => [ + `dist/${target}.js`, + `dist/${target}.mjs`, + ]).join(','); + timedExecOrDie( + `amp prepend-global --${argv.config} --local_dev --fortesting --derandomize --target=${targets}` + ); +} + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + prependConfig(); + timedExecOrDie('amp integration --nobuild --compiled --headless --esm'); +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { + prependConfig(); + timedExecOrDie( + `amp integration --nobuild --compiled --headless --esm --config=${argv.config}` + ); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or integration tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/nomodule-build.js b/build-system/pr-check/nomodule-build.js new file mode 100644 index 0000000000000..a920209f8ac62 --- /dev/null +++ b/build-system/pr-check/nomodule-build.js @@ -0,0 +1,91 @@ +/** + * Copyright 2019 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. + */ +'use strict'; + +/** + * @fileoverview Script that builds the nomodule AMP runtime during CI. + */ + +const atob = require('atob'); +const { + abortTimedJob, + processAndStoreBuildToArtifacts, + skipDependentJobs, + startTimer, + storeNomoduleBuildToWorkspace, + timedExecOrDie, + timedExecWithError, +} = require('./utils'); +const {log} = require('../common/logging'); +const {red, yellow} = require('../common/colors'); +const {runCiJob} = require('./ci-job'); +const {signalPrDeployUpload} = require('../tasks/pr-deploy-bot-utils'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'nomodule-build.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp dist --fortesting'); + storeNomoduleBuildToWorkspace(); +} + +/** + * Steps to run during PR builds. + * @return {Promise} + */ +async function prBuildWorkflow() { + const startTime = startTimer(jobName); + if ( + buildTargetsInclude( + Targets.RUNTIME, + Targets.INTEGRATION_TEST, + Targets.E2E_TEST, + Targets.VISUAL_DIFF + ) + ) { + const process = timedExecWithError('amp dist --fortesting'); + if (process.status !== 0) { + const message = process?.error + ? process.error.message + : 'Unknown error, check logs'; + log(red('ERROR'), yellow(message)); + await signalPrDeployUpload('errored'); + return abortTimedJob(jobName, startTime); + } + timedExecOrDie('amp storybook --build'); + await processAndStoreBuildToArtifacts(); + await signalPrDeployUpload('success'); + storeNomoduleBuildToWorkspace(); + } else { + await signalPrDeployUpload('skipped'); + + // Special case for visual diffs - Percy is a required check and must pass, + // but we just called `skipDependentJobs` so the Visual Diffs job will not + // run. Instead, we create an empty, passing check on Percy here. + process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); + timedExecOrDie('amp visual-diff --empty'); + + skipDependentJobs( + jobName, + 'this PR does not affect the runtime, integration tests, end-to-end tests, or visual diff tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/nomodule-tests.js b/build-system/pr-check/nomodule-tests.js new file mode 100644 index 0000000000000..6685b54ccc4b8 --- /dev/null +++ b/build-system/pr-check/nomodule-tests.js @@ -0,0 +1,82 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @fileoverview Script that tests the nomodule AMP runtime during CI. + */ + +const argv = require('minimist')(process.argv.slice(2)); +const { + skipDependentJobs, + timedExecOrDie, + timedExecOrThrow, +} = require('./utils'); +const {MINIFIED_TARGETS} = require('../tasks/helpers'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'nomodule-tests.js'; + +/** + * Adds a canary or prod config string to all non-esm minified targets. + */ +function prependConfig() { + const targets = MINIFIED_TARGETS.flatMap((target) => [ + `dist/${target}.js`, + ]).join(','); + timedExecOrDie( + `amp prepend-global --${argv.config} --local_dev --fortesting --derandomize --target=${targets}` + ); +} + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + prependConfig(); + try { + timedExecOrThrow( + `amp integration --nobuild --headless --compiled --report --config=${argv.config}`, + 'Integration tests failed!' + ); + } catch (e) { + if (e.status) { + process.exitCode = e.status; + } + } finally { + timedExecOrDie('amp test-report-upload'); + } +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { + prependConfig(); + timedExecOrDie( + `amp integration --nobuild --compiled --headless --config=${argv.config}` + ); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or integration tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/performance-tests.js b/build-system/pr-check/performance-tests.js index 0de9504ca0985..ff6c40fe2657e 100644 --- a/build-system/pr-check/performance-tests.js +++ b/build-system/pr-check/performance-tests.js @@ -16,28 +16,19 @@ 'use strict'; /** - * @fileoverview - * This script runs performance tests. - * This is run during the CI stage = experiment; job = performance tests. + * @fileoverview Script that runs the performance tests during CI. */ -const { - downloadDistOutput, - startTimer, - stopTimer, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const FILENAME = 'performance-tests.js'; -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); +const {runCiJob} = require('./ci-job'); +const {timedExecOrDie} = require('./utils'); -async function main() { - const startTime = startTimer(FILENAME, FILENAME); +const jobName = 'performance-tests.js'; - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp performance --nobuild --quiet --headless'); - - stopTimer(FILENAME, FILENAME, startTime); +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp performance --nobuild --quiet --headless'); } -main(); +runCiJob(jobName, pushBuildWorkflow, () => {}); diff --git a/build-system/pr-check/remote-tests.js b/build-system/pr-check/remote-tests.js deleted file mode 100644 index c8e769583e9c9..0000000000000 --- a/build-system/pr-check/remote-tests.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -/** - * @fileoverview - * This script kicks off the unit and integration tests on Sauce Labs. - * This is run during the CI stage = test; job = remote tests. - */ - -const colors = require('ansi-colors'); -const { - downloadDistOutput, - printChangeSummary, - startTimer, - stopTimer, - startSauceConnect, - stopSauceConnect, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); - -const FILENAME = 'remote-tests.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); - -async function main() { - const startTime = startTimer(FILENAME, FILENAME); - - if (!isTravisPullRequestBuild()) { - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - - await startSauceConnect(FILENAME); - timedExecOrDie('gulp unit --nobuild --saucelabs'); - timedExecOrDie( - 'gulp integration --nobuild --compiled --saucelabs --stable' - ); - timedExecOrDie('gulp integration --nobuild --compiled --saucelabs --beta'); - - stopSauceConnect(FILENAME); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - !buildTargets.has('RUNTIME') && - !buildTargets.has('FLAG_CONFIG') && - !buildTargets.has('UNIT_TEST') && - !buildTargets.has('INTEGRATION_TEST') - ) { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Remote (Sauce Labs) Tests'), - 'because this commit does not affect the runtime, flag configs,', - 'unit tests, or integration tests.' - ); - stopTimer(FILENAME, FILENAME, startTime); - return; - } - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - await startSauceConnect(FILENAME); - - if (buildTargets.has('RUNTIME') || buildTargets.has('UNIT_TEST')) { - timedExecOrDie('gulp unit --nobuild --saucelabs'); - } - - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('INTEGRATION_TEST') - ) { - timedExecOrDie( - 'gulp integration --nobuild --compiled --saucelabs --stable' - ); - timedExecOrDie( - 'gulp integration --nobuild --compiled --saucelabs --beta' - ); - } - stopSauceConnect(FILENAME); - } - - stopTimer(FILENAME, FILENAME, startTime); -} - -main(); diff --git a/build-system/pr-check/unit-tests.js b/build-system/pr-check/unit-tests.js new file mode 100644 index 0000000000000..e9e3a82cd04f0 --- /dev/null +++ b/build-system/pr-check/unit-tests.js @@ -0,0 +1,70 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview Script that runs the unit tests during CI. + */ + +const { + skipDependentJobs, + timedExecOrDie, + timedExecOrThrow, +} = require('./utils'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'unit-tests.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + try { + timedExecOrThrow( + 'amp unit --headless --coverage --report', + 'Unit tests failed!' + ); + timedExecOrThrow( + 'amp codecov-upload', + 'Failed to upload code coverage to Codecov!' + ); + } catch (e) { + if (e.status) { + process.exitCode = e.status; + } + } finally { + timedExecOrDie('amp test-report-upload'); + } +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME, Targets.UNIT_TEST)) { + timedExecOrDie('amp unit --headless --local_changes'); + timedExecOrDie('amp unit --headless --coverage'); + timedExecOrDie('amp codecov-upload'); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or unit tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/unminified-build.js b/build-system/pr-check/unminified-build.js new file mode 100644 index 0000000000000..539dfe87daa48 --- /dev/null +++ b/build-system/pr-check/unminified-build.js @@ -0,0 +1,55 @@ +/** + * Copyright 2019 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. + */ +'use strict'; + +/** + * @fileoverview Script that builds the unminified AMP runtime during CI. + */ + +const { + skipDependentJobs, + storeUnminifiedBuildToWorkspace, + timedExecOrDie, +} = require('./utils'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'unminified-build.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp build --fortesting'); + storeUnminifiedBuildToWorkspace(); +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { + timedExecOrDie('amp build --fortesting'); + storeUnminifiedBuildToWorkspace(); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or integration tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/unminified-tests.js b/build-system/pr-check/unminified-tests.js new file mode 100644 index 0000000000000..8102e1f52c027 --- /dev/null +++ b/build-system/pr-check/unminified-tests.js @@ -0,0 +1,69 @@ +/** + * Copyright 2019 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. + */ +'use strict'; + +/** + * @fileoverview Script that tests the unminified AMP runtime during CI. + */ + +const { + skipDependentJobs, + timedExecOrDie, + timedExecOrThrow, +} = require('./utils'); +const {runCiJob} = require('./ci-job'); +const {Targets, buildTargetsInclude} = require('./build-targets'); + +const jobName = 'unminified-tests.js'; + +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + try { + timedExecOrThrow( + 'amp integration --nobuild --headless --coverage --report', + 'Integration tests failed!' + ); + timedExecOrThrow( + 'amp codecov-upload', + 'Failed to upload code coverage to Codecov!' + ); + } catch (e) { + if (e.status) { + process.exitCode = e.status; + } + } finally { + timedExecOrDie('amp test-report-upload'); + } +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { + timedExecOrDie('amp integration --nobuild --headless --coverage'); + timedExecOrDie('amp codecov-upload'); + } else { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or integration tests' + ); + } +} + +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/utils.js b/build-system/pr-check/utils.js index 1bc980ce85b3d..52f6216421be3 100644 --- a/build-system/pr-check/utils.js +++ b/build-system/pr-check/utils.js @@ -15,401 +15,309 @@ */ 'use strict'; -const colors = require('ansi-colors'); -const requestPromise = require('request-promise'); +const fs = require('fs-extra'); +const { + ciPullRequestSha, + circleciBuildNumber, + isCiBuild, + isCircleciBuild, +} = require('../common/ci'); const { gitBranchCreationPoint, gitBranchName, + gitCiMainBaseline, gitCommitHash, gitDiffCommitLog, - gitDiffStatMaster, - gitTravisMasterBaseline, + gitDiffStatMain, shortSha, } = require('../common/git'); -const { - isTravisBuild, - travisBuildNumber, - travisPullRequestSha, -} = require('../common/travis'); -const {execOrDie, execWithError, exec} = require('../common/exec'); -const {replaceUrls, signalDistUpload} = require('../tasks/pr-deploy-bot-utils'); - -const BUILD_OUTPUT_FILE = isTravisBuild() - ? `amp_build_${travisBuildNumber()}.zip` - : ''; -const DIST_OUTPUT_FILE = isTravisBuild() - ? `amp_dist_${travisBuildNumber()}.zip` - : ''; -const ESM_DIST_OUTPUT_FILE = isTravisBuild() - ? `amp_esm_dist_${travisBuildNumber()}.zip` - : ''; - -const BUILD_OUTPUT_DIRS = 'build/ dist/ dist.3p/'; -const APP_SERVING_DIRS = 'dist.tools/ examples/ test/manual/'; - -const OUTPUT_STORAGE_LOCATION = 'gs://amp-travis-builds'; -const OUTPUT_STORAGE_KEY_FILE = 'sa-travis-key.json'; -const OUTPUT_STORAGE_PROJECT_ID = 'amp-travis-build-storage'; -const OUTPUT_STORAGE_SERVICE_ACCOUNT = - 'sa-travis@amp-travis-build-storage.iam.gserviceaccount.com'; +const {cyan, green, yellow} = require('../common/colors'); +const {exec, execOrDie, execOrThrow, execWithError} = require('../common/exec'); +const {getLoggingPrefix, logWithoutTimestamp} = require('../common/logging'); +const {replaceUrls} = require('../tasks/pr-deploy-bot-utils'); + +const UNMINIFIED_CONTAINER_DIRECTORY = 'unminified'; +const NOMODULE_CONTAINER_DIRECTORY = 'nomodule'; +const MODULE_CONTAINER_DIRECTORY = 'module'; + +const ARTIFACT_FILE_NAME = '/tmp/artifacts/amp_nomodule_build.tar.gz'; + +const BUILD_OUTPUT_DIRS = ['build', 'dist', 'dist.3p']; +const APP_SERVING_DIRS = [ + ...BUILD_OUTPUT_DIRS, + 'dist.tools', + 'examples', + 'test/manual', + 'test/fixtures/e2e', +]; const GIT_BRANCH_URL = - 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-e2e.md#create-a-git-branch'; + 'https://github.com/ampproject/amphtml/blob/main/docs/getting-started-e2e.md#create-a-git-branch'; /** * Prints a summary of files changed by, and commits included in the PR. - * @param {string} fileName */ -function printChangeSummary(fileName) { - const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); +function printChangeSummary() { + const loggingPrefix = getLoggingPrefix(); let commitSha; - if (isTravisBuild()) { - console.log( - `${fileLogPrefix} Latest commit from ${colors.cyan('master')} included ` + - `in this build: ${colors.cyan(shortSha(gitTravisMasterBaseline()))}` + if (isCiBuild()) { + logWithoutTimestamp( + `${loggingPrefix} Latest commit from ${cyan('main')} included ` + + `in this build: ${cyan(shortSha(gitCiMainBaseline()))}` ); - commitSha = travisPullRequestSha(); + commitSha = ciPullRequestSha(); } else { commitSha = gitCommitHash(); } - console.log( - `${fileLogPrefix} Testing the following changes at commit ` + - `${colors.cyan(shortSha(commitSha))}` + logWithoutTimestamp( + `${loggingPrefix} Testing the following changes at commit ` + + `${cyan(shortSha(commitSha))}` ); - const filesChanged = gitDiffStatMaster(); - console.log(filesChanged); + const filesChanged = gitDiffStatMain(); + logWithoutTimestamp(filesChanged); const branchCreationPoint = gitBranchCreationPoint(); if (branchCreationPoint) { - console.log( - `${fileLogPrefix} Commit log since branch`, - `${colors.cyan(gitBranchName())} was forked from`, - `${colors.cyan('master')} at`, - `${colors.cyan(shortSha(branchCreationPoint))}:` + logWithoutTimestamp( + `${loggingPrefix} Commit log since branch`, + `${cyan(gitBranchName())} was forked from`, + `${cyan('main')} at`, + `${cyan(shortSha(branchCreationPoint))}:` ); - console.log(gitDiffCommitLog() + '\n'); + logWithoutTimestamp(gitDiffCommitLog() + '\n'); } else { - console.error( - fileLogPrefix, - colors.yellow('WARNING:'), + logWithoutTimestamp( + loggingPrefix, + yellow('WARNING:'), 'Could not find a common ancestor for', - colors.cyan(gitBranchName()), + cyan(gitBranchName()), 'and', - colors.cyan('master') + '. (This can happen with older PR branches.)' + cyan('main') + '. (This can happen with older PR branches.)' ); - console.error( - fileLogPrefix, - colors.yellow('NOTE 1:'), + logWithoutTimestamp( + loggingPrefix, + yellow('NOTE 1:'), 'If this causes unexpected test failures, try rebasing the PR branch on', - colors.cyan('master') + '.' + cyan('main') + '.' ); - console.error( - fileLogPrefix, - colors.yellow('NOTE 2:'), + logWithoutTimestamp( + loggingPrefix, + yellow('NOTE 2:'), "If rebasing doesn't work, you may have to recreate the branch. See", - colors.cyan(GIT_BRANCH_URL) + '.\n' + cyan(GIT_BRANCH_URL) + '.\n' ); } } /** - * Starts connection to Sauce Labs after getting account credentials - * @param {string} functionName + * Signal to dependent jobs that they should be skipped. Uses an identifier that + * corresponds to the current job to eliminate conflicts if a parallel job also + * signals the same thing. + * + * Currently only relevant for CircleCI builds. */ -async function startSauceConnect(functionName) { - process.env['SAUCE_USERNAME'] = 'amphtml'; - const response = await requestPromise( - 'https://amphtml-sauce-token-dealer.appspot.com/getJwtToken' - ); - process.env['SAUCE_ACCESS_KEY'] = response.trim(); - const startScCmd = 'build-system/sauce_connect/start_sauce_connect.sh'; - const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - console.log( - '\n' + fileLogPrefix, - 'Starting Sauce Connect Proxy:', - colors.cyan(startScCmd) - ); - execOrDie(startScCmd); +function signalGracefulHalt() { + if (isCircleciBuild()) { + const loggingPrefix = getLoggingPrefix(); + const sentinelFile = `/tmp/workspace/.CI_GRACEFULLY_HALT_${circleciBuildNumber()}`; + fs.closeSync(fs.openSync(sentinelFile, 'w')); + logWithoutTimestamp( + `${loggingPrefix} Created ${cyan(sentinelFile)} to signal graceful halt.` + ); + } } /** - * Stops connection to Sauce Labs - * @param {string} functionName + * Prints a message indicating why a job was skipped and mark its dependent jobs + * for skipping. + * @param {string} jobName + * @param {string} skipReason */ -function stopSauceConnect(functionName) { - const stopScCmd = 'build-system/sauce_connect/stop_sauce_connect.sh'; - const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - console.log( - '\n' + fileLogPrefix, - 'Stopping Sauce Connect Proxy:', - colors.cyan(stopScCmd) +function skipDependentJobs(jobName, skipReason) { + const loggingPrefix = getLoggingPrefix(); + logWithoutTimestamp( + `${loggingPrefix} Skipping ${cyan(jobName)} because ${skipReason}.` ); - execOrDie(stopScCmd); + signalGracefulHalt(); } /** - * Starts a timer to measure the execution time of the given function. - * @param {string} functionName - * @param {string} fileName + * Starts a timer to measure the execution time of the given job / command. + * @param {string} jobNameOrCmd * @return {DOMHighResTimeStamp} */ -function startTimer(functionName, fileName) { +function startTimer(jobNameOrCmd) { const startTime = Date.now(); - const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - console.log( - '\n' + fileLogPrefix, + const loggingPrefix = getLoggingPrefix(); + logWithoutTimestamp( + '\n' + loggingPrefix, 'Running', - colors.cyan(functionName) + '...' + cyan(jobNameOrCmd) + '...' ); return startTime; } /** - * Stops the timer for the given function and prints the execution time. - * @param {string} functionName - * @param {string} fileName + * Stops the timer for the given job / command and prints the execution time. + * @param {string} jobNameOrCmd * @param {DOMHighResTimeStamp} startTime - * @return {number} */ -function stopTimer(functionName, fileName, startTime) { +function stopTimer(jobNameOrCmd, startTime) { const endTime = Date.now(); const executionTime = endTime - startTime; const mins = Math.floor(executionTime / 60000); const secs = Math.floor((executionTime % 60000) / 1000); - const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - console.log( - fileLogPrefix, + const loggingPrefix = getLoggingPrefix(); + logWithoutTimestamp( + loggingPrefix, 'Done running', - colors.cyan(functionName), + cyan(jobNameOrCmd), 'Total time:', - colors.green(mins + 'm ' + secs + 's') + green(mins + 'm ' + secs + 's') ); } /** - * Stops the Node process and timer - * @param {string} fileName - * @param {startTime} startTime + * Aborts the process after stopping the timer for a given job + * @param {string} jobName + * @param {number} startTime */ -function stopTimedJob(fileName, startTime) { - stopTimer(fileName, fileName, startTime); +function abortTimedJob(jobName, startTime) { + stopTimer(jobName, startTime); process.exitCode = 1; } +/** + * Wraps an exec helper in a timer. Returns the result of the helper. + * @param {function(string, string=): ?} execFn + * @return {function(string, string=): ?} + */ +function timedExecFn(execFn) { + return (cmd, ...rest) => { + const startTime = startTimer(cmd); + const p = execFn(cmd, ...rest); + stopTimer(cmd, startTime); + return p; + }; +} + /** * Executes the provided command and times it. Errors, if any, are printed. + * @function * @param {string} cmd - * @param {string} fileName * @return {!Object} Node process */ -function timedExec(cmd, fileName = 'utils.js') { - const startTime = startTimer(cmd, fileName); - const p = exec(cmd); - stopTimer(cmd, fileName, startTime); - return p; -} +const timedExec = timedExecFn(exec); /** * Executes the provided command and times it. Errors, if any, are returned. + * @function * @param {string} cmd - * @param {string} fileName * @return {!Object} Node process */ -function timedExecWithError(cmd, fileName = 'utils.js') { - const startTime = startTimer(cmd, fileName); - const p = execWithError(cmd); - stopTimer(cmd, fileName, startTime); - return p; -} +const timedExecWithError = timedExecFn(execWithError); /** * Executes the provided command and times it. The program terminates in case of * failure. + * @function * @param {string} cmd - * @param {string} fileName */ -function timedExecOrDie(cmd, fileName = 'utils.js') { - const startTime = startTimer(cmd, fileName); - execOrDie(cmd); - stopTimer(cmd, fileName, startTime); -} +const timedExecOrDie = timedExecFn(execOrDie); /** - * Download output helper - * @param {string} functionName - * @param {string} outputFileName - * @param {string} outputDirs - * @private + * Executes the provided command and times it. The program throws on error in + * case of failure. + * @function + * @param {string} cmd */ -function downloadOutput_(functionName, outputFileName, outputDirs) { - const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - const buildOutputDownloadUrl = `${OUTPUT_STORAGE_LOCATION}/${outputFileName}`; - const dirsToUnzip = outputDirs.split(' '); - - console.log( - `${fileLogPrefix} Downloading build output from ` + - colors.cyan(buildOutputDownloadUrl) + - '...' - ); - exec('echo travis_fold:start:download_results && echo'); - authenticateWithStorageLocation_(); - execOrDie(`gsutil cp ${buildOutputDownloadUrl} ${outputFileName}`); - exec('echo travis_fold:end:download_results'); - - console.log( - `${fileLogPrefix} Extracting ` + colors.cyan(outputFileName) + '...' - ); - exec('echo travis_fold:start:unzip_results && echo'); - dirsToUnzip.forEach((dir) => { - execOrDie(`unzip ${outputFileName} '${dir.replace('/', '/*')}'`); - }); - exec('echo travis_fold:end:unzip_results'); - - console.log(fileLogPrefix, 'Verifying extracted files...'); - exec('echo travis_fold:start:verify_unzip_results && echo'); - execOrDie(`ls -laR ${outputDirs}`); - exec('echo travis_fold:end:verify_unzip_results'); -} +const timedExecOrThrow = timedExecFn(execOrThrow); /** - * Upload output helper - * @param {string} functionName - * @param {string} outputFileName - * @param {string} outputDirs + * Stores build files to the CI workspace. + * @param {string} containerDirectory * @private */ -function uploadOutput_(functionName, outputFileName, outputDirs) { - const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); - - console.log( - `\n${fileLogPrefix} Compressing ` + - colors.cyan(outputDirs.split(' ').join(', ')) + - ' into ' + - colors.cyan(outputFileName) + - '...' - ); - exec('echo travis_fold:start:zip_results && echo'); - execOrDie(`zip -r ${outputFileName} ${outputDirs}`); - exec('echo travis_fold:end:zip_results'); - - console.log( - `${fileLogPrefix} Uploading ` + - colors.cyan(outputFileName) + - ' to ' + - colors.cyan(OUTPUT_STORAGE_LOCATION) + - '...' - ); - exec('echo travis_fold:start:upload_results && echo'); - authenticateWithStorageLocation_(); - execOrDie(`gsutil -m cp -r ${outputFileName} ${OUTPUT_STORAGE_LOCATION}`); - exec('echo travis_fold:end:upload_results'); -} - -function authenticateWithStorageLocation_() { - decryptTravisKey_(); - execOrDie( - 'gcloud auth activate-service-account ' + - `--key-file ${OUTPUT_STORAGE_KEY_FILE}` - ); - execOrDie(`gcloud config set account ${OUTPUT_STORAGE_SERVICE_ACCOUNT}`); - execOrDie('gcloud config set pass_credentials_to_gsutil true'); - execOrDie(`gcloud config set project ${OUTPUT_STORAGE_PROJECT_ID}`); - execOrDie('gcloud config list'); -} - -/** - * Downloads and unzips build output from storage - * @param {string} functionName - */ -function downloadBuildOutput(functionName) { - downloadOutput_(functionName, BUILD_OUTPUT_FILE, BUILD_OUTPUT_DIRS); +function storeBuildToWorkspace_(containerDirectory) { + if (isCircleciBuild()) { + fs.ensureDirSync(`/tmp/workspace/builds/${containerDirectory}`); + for (const outputDir of BUILD_OUTPUT_DIRS) { + fs.moveSync( + `${outputDir}/`, + `/tmp/workspace/builds/${containerDirectory}/${outputDir}` + ); + } + } } /** - * Downloads and unzips dist output from storage - * @param {string} functionName + * Stores unminified build files to the CI workspace. */ -function downloadDistOutput(functionName) { - downloadOutput_(functionName, DIST_OUTPUT_FILE, BUILD_OUTPUT_DIRS); +function storeUnminifiedBuildToWorkspace() { + storeBuildToWorkspace_(UNMINIFIED_CONTAINER_DIRECTORY); } /** - * Downloads and unzips esm dist output from storage - * @param {string} functionName + * Stores nomodule build files to the CI workspace. */ -function downloadEsmDistOutput(functionName) { - downloadOutput_(functionName, ESM_DIST_OUTPUT_FILE, BUILD_OUTPUT_DIRS); +function storeNomoduleBuildToWorkspace() { + storeBuildToWorkspace_(NOMODULE_CONTAINER_DIRECTORY); } /** - * Zips and uploads the build output to a remote storage location - * @param {string} functionName + * Stores module build files to the CI workspace. */ -function uploadBuildOutput(functionName) { - uploadOutput_(functionName, BUILD_OUTPUT_FILE, BUILD_OUTPUT_DIRS); +function storeModuleBuildToWorkspace() { + storeBuildToWorkspace_(MODULE_CONTAINER_DIRECTORY); } /** - * Zips and uploads the dist output to a remote storage location - * @param {string} functionName + * Stores an experiment's build files to the CI workspace. + * @param {string} exp one of 'experimentA', 'experimentB', or 'experimentC'. */ -function uploadDistOutput(functionName) { - const distOutputDirs = `${BUILD_OUTPUT_DIRS} ${APP_SERVING_DIRS}`; - uploadOutput_(functionName, DIST_OUTPUT_FILE, distOutputDirs); +function storeExperimentBuildToWorkspace(exp) { + storeBuildToWorkspace_(exp); } /** - * Zips and uploads the esm dist output to a remote storage location - * @param {string} functionName + * Replaces URLS in HTML files, compresses and stores nomodule build in CI artifacts. */ -function uploadEsmDistOutput(functionName) { - const esmDistOutputDirs = `${BUILD_OUTPUT_DIRS} ${APP_SERVING_DIRS}`; - uploadOutput_(functionName, ESM_DIST_OUTPUT_FILE, esmDistOutputDirs); -} +async function processAndStoreBuildToArtifacts() { + if (!isCircleciBuild()) { + return; + } -/** - * Replaces URLS in HTML files, zips and uploads dist output, - * and signals to the AMP PR Deploy bot that the upload is complete. - * @param {string} functionName - */ -async function processAndUploadDistOutput(functionName) { await replaceUrls('test/manual'); await replaceUrls('examples'); - uploadDistOutput(functionName); - await signalDistUpload('success'); -} -/** - * Decrypts key used by storage service account - */ -function decryptTravisKey_() { - // -md sha256 is required due to encryption differences between - // openssl 1.1.1a, which was used to encrypt the key, and - // openssl 1.0.2g, which is used by Travis to decrypt. - execOrDie( - `openssl aes-256-cbc -md sha256 -k ${process.env.GCP_TOKEN} -in ` + - `build-system/common/sa-travis-key.json.enc -out ` + - `${OUTPUT_STORAGE_KEY_FILE} -d` + const loggingPrefix = getLoggingPrefix(); + + logWithoutTimestamp( + `\n${loggingPrefix} Compressing ` + + cyan(APP_SERVING_DIRS.join(', ')) + + ' into ' + + cyan(ARTIFACT_FILE_NAME) + + '...' ); + execOrDie(`tar -czf ${ARTIFACT_FILE_NAME} ${APP_SERVING_DIRS.join('/ ')}/`); + execOrDie(`du -sh ${ARTIFACT_FILE_NAME}`); } module.exports = { - downloadBuildOutput, - downloadDistOutput, - downloadEsmDistOutput, + abortTimedJob, printChangeSummary, - processAndUploadDistOutput, + skipDependentJobs, startTimer, stopTimer, - startSauceConnect, - stopSauceConnect, - stopTimedJob, timedExec, timedExecOrDie, timedExecWithError, - uploadBuildOutput, - uploadDistOutput, - uploadEsmDistOutput, + timedExecOrThrow, + storeUnminifiedBuildToWorkspace, + storeNomoduleBuildToWorkspace, + storeModuleBuildToWorkspace, + storeExperimentBuildToWorkspace, + processAndStoreBuildToArtifacts, }; diff --git a/build-system/pr-check/validator-tests.js b/build-system/pr-check/validator-tests.js index f5154a0a2735b..291ad06af946f 100644 --- a/build-system/pr-check/validator-tests.js +++ b/build-system/pr-check/validator-tests.js @@ -16,76 +16,53 @@ 'use strict'; /** - * @fileoverview - * This script runs validator tests. - * This is run during the CI stage = build; job = validator. + * @fileoverview Script that runs the validator tests during CI. */ -const colors = require('ansi-colors'); -const { - printChangeSummary, - startTimer, - stopTimer, - stopTimedJob, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); -const {runYarnChecks} = require('./yarn-checks'); +const {runCiJob} = require('./ci-job'); +const {skipDependentJobs, timedExecOrDie} = require('./utils'); +const {Targets, buildTargetsInclude} = require('./build-targets'); -const FILENAME = 'validator-tests.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); +const jobName = 'validator-tests.js'; -function main() { - const startTime = startTimer(FILENAME, FILENAME); - if (!runYarnChecks(FILENAME)) { - stopTimedJob(FILENAME, startTime); +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + timedExecOrDie('amp validator-webui'); + timedExecOrDie('amp validator'); + timedExecOrDie('amp validator-cpp'); +} + +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + if ( + !buildTargetsInclude( + Targets.RUNTIME, + Targets.VALIDATOR, + Targets.VALIDATOR_WEBUI + ) + ) { + skipDependentJobs( + jobName, + 'this PR does not affect the runtime, validator, or validator web UI' + ); return; } - if (!isTravisPullRequestBuild()) { - timedExecOrDie('gulp validator'); - // #27786: Java validator is not guaranteed to be in sync with AMP code. - // #28497: Java Validator tests are broken due to Ubuntu keyserver outage. - // timedExec('gulp validator-java'); - timedExecOrDie('gulp validator-webui'); - } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - if ( - !buildTargets.has('RUNTIME') && - !buildTargets.has('VALIDATOR') && - !buildTargets.has('VALIDATOR_WEBUI') && - !buildTargets.has('VALIDATOR_JAVA') - ) { - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Validator Tests'), - 'because this commit does not affect the runtime, validator,', - 'or validator web UI.' - ); - stopTimer(FILENAME, FILENAME, startTime); - return; - } - - if (buildTargets.has('RUNTIME') || buildTargets.has('VALIDATOR')) { - timedExecOrDie('gulp validator'); - } - - // #28497: Java Validator tests are broken due to Ubuntu keyserver outage. - // if (buildTargets.has('VALIDATOR_JAVA')) { - // timedExecOrDie('gulp validator-java'); - // } else if (buildTargets.has('RUNTIME')) { - // timedExec('gulp validator-java'); - // } + if (buildTargetsInclude(Targets.VALIDATOR_WEBUI)) { + timedExecOrDie('amp validator-webui'); + } - if (buildTargets.has('VALIDATOR_WEBUI')) { - timedExecOrDie('gulp validator-webui'); - } + if (buildTargetsInclude(Targets.RUNTIME, Targets.VALIDATOR)) { + timedExecOrDie('amp validator'); } - stopTimer(FILENAME, FILENAME, startTime); + if (buildTargetsInclude(Targets.VALIDATOR)) { + timedExecOrDie('amp validator-cpp'); + } } -main(); +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/visual-diff-tests.js b/build-system/pr-check/visual-diff-tests.js index ac9e7ddd2329c..2499c7d4dbd27 100644 --- a/build-system/pr-check/visual-diff-tests.js +++ b/build-system/pr-check/visual-diff-tests.js @@ -16,59 +16,38 @@ 'use strict'; /** - * @fileoverview - * This script runs the visual diff tests. - * This is run during the CI stage = test; job = visual diff tests. + * @fileoverview Script that runs the visual diff tests during CI. */ const atob = require('atob'); -const colors = require('ansi-colors'); -const { - downloadDistOutput, - printChangeSummary, - startTimer, - stopTimer, - timedExecOrDie: timedExecOrDieBase, -} = require('./utils'); -const {determineBuildTargets} = require('./build-targets'); -const {isTravisPullRequestBuild} = require('../common/travis'); +const {runCiJob} = require('./ci-job'); +const {skipDependentJobs, timedExecOrDie} = require('./utils'); +const {Targets, buildTargetsInclude} = require('./build-targets'); -const FILENAME = 'visual-diff-tests.js'; -const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); -const timedExecOrDie = (cmd) => timedExecOrDieBase(cmd, FILENAME); +const jobName = 'visual-diff-tests.js'; -function main() { - const startTime = startTimer(FILENAME, FILENAME); +/** + * Steps to run during push builds. + */ +function pushBuildWorkflow() { + process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); + timedExecOrDie('amp visual-diff --nobuild --main'); +} - if (!isTravisPullRequestBuild()) { - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); - timedExecOrDie('gulp visual-diff --compiled --nobuild --master'); +/** + * Steps to run during PR builds. + */ +function prBuildWorkflow() { + process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); + if (buildTargetsInclude(Targets.RUNTIME, Targets.VISUAL_DIFF)) { + timedExecOrDie('amp visual-diff --nobuild'); } else { - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('VISUAL_DIFF') - ) { - downloadDistOutput(FILENAME); - timedExecOrDie('gulp update-packages'); - timedExecOrDie('gulp visual-diff --compiled --nobuild'); - } else { - timedExecOrDie('gulp visual-diff --empty'); - console.log( - `${FILELOGPREFIX} Skipping`, - colors.cyan('Visual Diff Tests'), - 'because this commit does not affect the runtime, flag configs,', - 'or visual diff tests.' - ); - } + timedExecOrDie('amp visual-diff --empty'); + skipDependentJobs( + jobName, + 'this PR does not affect the runtime or visual diff tests' + ); } - - stopTimer(FILENAME, FILENAME, startTime); } -main(); +runCiJob(jobName, pushBuildWorkflow, prBuildWorkflow); diff --git a/build-system/pr-check/yarn-checks.js b/build-system/pr-check/yarn-checks.js deleted file mode 100644 index 772f105aa9b05..0000000000000 --- a/build-system/pr-check/yarn-checks.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -/** - * @fileoverview - * This script makes sure that package.json and yarn.lock - * are in sync and up to date. - */ - -const colors = require('ansi-colors'); -const {getStderr} = require('../common/exec'); -const {gitDiffColor, gitDiffNameOnly} = require('../common/git'); - -/** - * Makes sure package.json and yarn.lock are in sync. - * @param {string} fileName - * @return {boolean} - */ -function isYarnLockFileInSync(fileName = 'yarn-checks.js') { - const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - const yarnIntegrityCheck = getStderr('yarn check --integrity').trim(); - if (yarnIntegrityCheck.includes('error')) { - console.error( - fileLogPrefix, - colors.red('ERROR:'), - 'Found the following', - colors.cyan('yarn'), - 'errors:\n' + colors.cyan(yarnIntegrityCheck) - ); - console.error( - fileLogPrefix, - colors.red('ERROR:'), - 'Updates to', - colors.cyan('package.json'), - 'must be accompanied by a corresponding update to', - colors.cyan('yarn.lock') - ); - console.error( - fileLogPrefix, - colors.yellow('NOTE:'), - 'To update', - colors.cyan('yarn.lock'), - 'after changing', - colors.cyan('package.json') + ',', - 'run', - '"' + colors.cyan('yarn install') + '"', - 'and include the updated', - colors.cyan('yarn.lock'), - 'in your PR.' - ); - return false; - } - return true; -} - -/** - * Makes sure that yarn.lock was properly updated. - * @param {string} fileName - * @return {boolean} - */ -function isYarnLockFileProperlyUpdated(fileName = 'yarn-checks.js') { - const filesChanged = gitDiffNameOnly(); - const fileLogPrefix = colors.bold(colors.yellow(`${fileName}:`)); - - if (filesChanged.includes('yarn.lock')) { - console.error( - fileLogPrefix, - colors.red('ERROR:'), - 'This PR did not properly update', - colors.cyan('yarn.lock') + '.' - ); - console.error( - fileLogPrefix, - colors.yellow('NOTE:'), - 'To fix this, sync your branch to', - colors.cyan('upstream/master') + ', run', - colors.cyan('gulp update-packages') + - ', and push a new commit containing the changes.' - ); - console.error(fileLogPrefix, 'Expected changes:'); - console.log(gitDiffColor()); - return false; - } - return true; -} - -/** - * Runs both yarn checks, and returns false if either one fails. - * @param {string} filename - * @return {boolean} - */ -function runYarnChecks(filename) { - return ( - isYarnLockFileInSync(filename) && isYarnLockFileProperlyUpdated(filename) - ); -} - -module.exports = { - runYarnChecks, -}; diff --git a/build-system/runner/OWNERS b/build-system/runner/OWNERS deleted file mode 100644 index 6e9c5e237735f..0000000000000 --- a/build-system/runner/OWNERS +++ /dev/null @@ -1,14 +0,0 @@ -// For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example - -{ - rules: [ - { - owners: [ - {name: 'ampproject/wg-infra'}, - {name: 'erwinmombay', notify: true}, - {name: 'rsimha', notify: true}, - ], - }, - ], -} diff --git a/build-system/runner/build.xml b/build-system/runner/build.xml deleted file mode 100644 index e6ea19fa3ff9d..0000000000000 --- a/build-system/runner/build.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build-system/runner/src/org/ampproject/AmpCodingConvention.java b/build-system/runner/src/org/ampproject/AmpCodingConvention.java deleted file mode 100644 index ce4fb72edeaf7..0000000000000 --- a/build-system/runner/src/org/ampproject/AmpCodingConvention.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2016 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. - */ - -package org.ampproject; - -import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; -import com.google.javascript.jscomp.ClosureCodingConvention; -import com.google.javascript.jscomp.CodingConvention; -import com.google.javascript.jscomp.CodingConventions; -import com.google.javascript.rhino.jstype.JSType; -import com.google.javascript.rhino.jstype.JSTypeNative; - -import java.util.ArrayList; -import java.util.Collection; - - -/** - * A coding convention for AMP. - */ -public final class AmpCodingConvention extends CodingConventions.Proxy { - - /** By default, decorate the ClosureCodingConvention. */ - public AmpCodingConvention() { - this(new ClosureCodingConvention()); - } - - /** Decorates a wrapped CodingConvention. */ - public AmpCodingConvention(CodingConvention convention) { - super(convention); - } - - /** - * {@inheritDoc} - * Because AMP objects can travel between compilation units, we consider - * non-private methods exported. - * Should we decide to do full-program compilation (for version bound JS - * delivery), this could go away there. - */ - @Override public boolean isExported(String name, boolean local) { - if (local) { - return false; - } - // This is a special case, of compiler generated super globals. - // Because we otherwise use ES6 modules throughout, we don't - // have any other similar variables. - if (name.startsWith("JSCompiler_")) { - return false; - } - // ES6 generated module names are not exported. - if (name.contains("$")) { - return false; - } - // Starting with _ explicitly exports a name. - if (name.startsWith("_")) { - return true; - } - return !name.endsWith("_") && !name.endsWith("ForTesting"); - } - - /** - * {@inheritDoc} - * We cannot rename properties we treat as exported, because they may travel - * between compilation unit. - */ - @Override public boolean blockRenamingForProperty(String name) { - return isExported(name, false); - } -} diff --git a/build-system/runner/src/org/ampproject/AmpCommandLineRunner.java b/build-system/runner/src/org/ampproject/AmpCommandLineRunner.java deleted file mode 100644 index 613190d3322ef..0000000000000 --- a/build-system/runner/src/org/ampproject/AmpCommandLineRunner.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2016 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. - */ -package org.ampproject; - -import com.google.common.collect.ImmutableSet; -import com.google.javascript.jscomp.CommandLineRunner; -import com.google.javascript.jscomp.CompilerOptions; -import com.google.javascript.jscomp.FlagUsageException; -import com.google.javascript.jscomp.PropertyRenamingPolicy; -import com.google.javascript.jscomp.VariableRenamingPolicy; - -import java.io.IOException; - - -/** - * Sets custom closure compiler flags that cannot be set via the command line. - */ -public class AmpCommandLineRunner extends CommandLineRunner { - - /** - * Identifies if the runner only needs to do type checking. - */ - private boolean typecheck_only = false; - - private boolean pseudo_names = false; - - protected AmpCommandLineRunner(String[] args) { - super(args); - } - - @Override protected CompilerOptions createOptions() { - if (typecheck_only) { - return createTypeCheckingOptions(); - } - CompilerOptions options = super.createOptions(); - options.setCollapsePropertiesLevel(CompilerOptions.PropertyCollapseLevel.ALL); - options.setDevirtualizeMethods(true); - options.setExtractPrototypeMemberDeclarations(true); - options.setSmartNameRemoval(true); - options.optimizeCalls = true; - // Have to turn this off because we cannot know whether sub classes - // might override a method. In the future this might be doable - // with using a more complete extern file instead. - options.setRemoveUnusedPrototypeProperties(false); - options.setComputeFunctionSideEffects(false); - // Property renaming. Relies on AmpCodingConvention to be safe. - options.setRenamingPolicy(VariableRenamingPolicy.ALL, - PropertyRenamingPolicy.ALL_UNQUOTED); - options.setGeneratePseudoNames(pseudo_names); - return options; - } - - @Override protected void setRunOptions(CompilerOptions options) - throws IOException, FlagUsageException { - super.setRunOptions(options); - options.setCodingConvention(new AmpCodingConvention()); - } - - /** - * Create the most basic CompilerOptions instance with type checking turned on. - */ - protected CompilerOptions createTypeCheckingOptions() { - CompilerOptions options = super.createOptions(); - options.setCheckTypes(true); - options.setInferTypes(true); - return options; - } - - public static void main(String[] args) { - AmpCommandLineRunner runner = new AmpCommandLineRunner(args); - - for (String arg : args) { - if (arg.contains("TYPECHECK_ONLY=true")) { - runner.typecheck_only = true; - } else if (arg.contains("PSEUDO_NAMES=true")) { - runner.pseudo_names = true; - } - } - - if (runner.shouldRunCompiler()) { - runner.run(); - } - if (runner.hasErrors()) { - System.exit(-1); - } - } -} diff --git a/build-system/sauce_connect/OWNERS b/build-system/sauce_connect/OWNERS deleted file mode 100644 index 2dba3e48ce020..0000000000000 --- a/build-system/sauce_connect/OWNERS +++ /dev/null @@ -1,14 +0,0 @@ -// For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example - -{ - rules: [ - { - owners: [ - {name: 'ampproject/wg-infra'}, - {name: 'rsimha', notify: true}, - {name: 'danielrozenberg', notify: true}, - ], - }, - ], -} diff --git a/build-system/sauce_connect/start_sauce_connect.sh b/build-system/sauce_connect/start_sauce_connect.sh deleted file mode 100755 index 5520f4c7dc79b..0000000000000 --- a/build-system/sauce_connect/start_sauce_connect.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/bin/bash -# -# Copyright 2018 The AMP HTML Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the license. -# -# This script starts the sauce connect proxy, and waits for a successful -# connection. Works on Linux and Mac OS. - -CYAN() { echo -e "\033[0;36m$1\033[0m"; } -YELLOW() { echo -e "\033[1;33m$1\033[0m"; } -GREEN() { echo -e "\033[0;32m$1\033[0m"; } -RED() { echo -e "\033[0;31m$1\033[0m"; } - -SC_VERSION="sc-4.5.1" -AUTHENTICATED_STATUS_URL="https://$SAUCE_USERNAME:$SAUCE_ACCESS_KEY@saucelabs.com/rest/v1/info/status" -STATUS_URL="https://saucelabs.com/rest/v1/info/status" -DOWNLOAD_DIR="sauce_connect" -PID_FILE="sauce_connect_pid" -LOG_FILE="sauce_connect_log" -OUTPUT_FILE="sauce_connect_output" -READY_FILE="sauce_connect_ready" -READY_DELAY_SECS=120 -LOG_PREFIX=$(YELLOW "start_sauce_connect.sh") - -if [[ -z "$SAUCE_USERNAME" || -z "$SAUCE_ACCESS_KEY" ]]; then - echo "$LOG_PREFIX The $(CYAN "SAUCE_USERNAME") and $(CYAN "SAUCE_ACCESS_KEY") environment variables must be set." - exit 1 -fi - -if [[ "$OSTYPE" == "linux-gnu" ]]; then - DOWNLOAD_URL="https://saucelabs.com/downloads/$SC_VERSION-linux.tar.gz" - ARCHIVE_FILE="$DOWNLOAD_DIR/$SC_VERSION-linux.tar.gz" - BINARY_FILE="$SC_VERSION-linux/bin/sc" -elif [[ "$OSTYPE" == "darwin"* ]]; then - DOWNLOAD_URL="https://saucelabs.com/downloads/$SC_VERSION-osx.zip" - ARCHIVE_FILE="$DOWNLOAD_DIR/$SC_VERSION-osx.zip" - BINARY_FILE="$SC_VERSION-osx/bin/sc" -else - echo "$LOG_PREFIX Sauce Connect Proxy launcher script does not support the $(CYAN "$OSTYPE") platform." - exit 1 -fi - -# Start a timer to track how long setup took -START_TIME=$( date +%s ) -echo "$LOG_PREFIX Starting $(CYAN "start_sauce_connect.sh")..." - -# Check the status of the Sauce Labs service -echo "$LOG_PREFIX Fetching current Sauce Labs service status from $(CYAN "$STATUS_URL")..." -SAUCE_STATUS="$(curl -s "$AUTHENTICATED_STATUS_URL")" -SERVICE_OPERATIONAL="$(echo "$SAUCE_STATUS" | jq '.service_operational')" -STATUS_MESSAGE="$(echo "$SAUCE_STATUS" | jq '.status_message')" -ERROR_MESSAGE="$(echo "$SAUCE_STATUS" | jq '.message')" -if [[ "$STATUS_MESSAGE" = "null" ]]; then - echo "$LOG_PREFIX $(RED "ERROR:") Could not fetch Sauce Labs Service status. Message: $(CYAN "$ERROR_MESSAGE")" - echo "$LOG_PREFIX Attempting to connect anyway..." -else - if [[ $SERVICE_OPERATIONAL = "true" ]]; then - echo "$LOG_PREFIX $(GREEN "SUCCESS:") Sauce Labs is operational. Status: $(CYAN "$STATUS_MESSAGE")" - else - echo "$LOG_PREFIX $(RED "ERROR:") Sauce Labs does not appear to be operational. Status: $(CYAN "$STATUS_MESSAGE")" - echo "$LOG_PREFIX Attempting to connect anyway..." - fi -fi - -# Download the sauce connect proxy binary (if needed) and unpack it. -if [[ -f $ARCHIVE_FILE ]]; then - echo "$LOG_PREFIX Using cached Sauce Connect binary $(CYAN "$ARCHIVE_FILE")" -else - echo "$LOG_PREFIX Downloading $(CYAN "$DOWNLOAD_URL")" - if [[ "$OSTYPE" == "linux-gnu" ]]; then - wget -q "$DOWNLOAD_URL" -P "$DOWNLOAD_DIR" - elif [[ "$OSTYPE" == "darwin"* ]]; then - curl -s --create-dirs -o "$ARCHIVE_FILE" -O "$DOWNLOAD_URL" - fi -fi -echo "$LOG_PREFIX Unpacking $(CYAN "$ARCHIVE_FILE")" -if [[ "$OSTYPE" == "linux-gnu" ]]; then - tar -xzf "$ARCHIVE_FILE" -elif [[ "$OSTYPE" == "darwin"* ]]; then - unzip -qu "$ARCHIVE_FILE" -fi - -# Clean up old files, if any. -if [[ -f "$LOG_FILE" ]]; then - echo "$LOG_PREFIX Deleting old log file $(CYAN "$LOG_FILE")" - rm "$LOG_FILE" -fi -if [[ -f $PID_FILE ]]; then - echo "$LOG_PREFIX Deleting old pid file $(CYAN "$PID_FILE")" - rm "$PID_FILE" -fi -if [[ -f "$OUTPUT_FILE" ]]; then - echo "$LOG_PREFIX Deleting old output file $(CYAN "$OUTPUT_FILE")" - rm "$OUTPUT_FILE" -fi - -# Establish the tunnel identifier (job number on Travis / username during local dev). -if [[ -z "$TRAVIS_JOB_NUMBER" ]]; then - TUNNEL_IDENTIFIER="$(git log -1 --pretty=format:"%ae")" -else - TUNNEL_IDENTIFIER="$TRAVIS_JOB_NUMBER" -fi - - -# Launch proxy and wait for a tunnel to be created. -echo "$LOG_PREFIX Launching $(CYAN "$BINARY_FILE")..." -"$BINARY_FILE" --verbose --tunnel-identifier "$TUNNEL_IDENTIFIER" --readyfile "$READY_FILE" --pidfile "$PID_FILE" --logfile "$LOG_FILE" 1>"$OUTPUT_FILE" 2>&1 & -count=0 -while [ $count -lt $READY_DELAY_SECS ] -do - if [ -e "$READY_FILE" ] - then - # Print confirmation. - PID="$(cat "$PID_FILE")" - if [[ "$OSTYPE" == "linux-gnu" ]]; then - TUNNEL_ID="$(grep -oP "Tunnel ID: \K.*$" "$OUTPUT_FILE")" - elif [[ "$OSTYPE" == "darwin"* ]]; then - TUNNEL_ID="$(grep -o "Tunnel ID: .*$" "$OUTPUT_FILE" | cut -d' ' -f3)" - fi - echo "$LOG_PREFIX Sauce Connect Proxy with tunnel ID $(CYAN "$TUNNEL_ID") and identifier $(CYAN "$TUNNEL_IDENTIFIER") is now running as pid $(CYAN "$PID")" - END_TIME=$( date +%s ) - TOTAL_TIME=$(( END_TIME - START_TIME )) - echo "$LOG_PREFIX Done running $(CYAN "start_sauce_connect.sh") Total time: $(CYAN "$TOTAL_TIME"s)" - echo "$LOG_PREFIX To stop the proxy, run $(CYAN "stop_sauce_connect.sh") from the same directory" - break - else - # Continue waiting. - sleep 1 - (( count++ )) - if [ $count -eq $READY_DELAY_SECS ] - then - # Print the error logs. - echo "$LOG_PREFIX Sauce Connect Proxy has not started after $(CYAN "$READY_DELAY_SECS") seconds." - echo "$LOG_PREFIX Console output:" - cat "$OUTPUT_FILE" - echo "$LOG_PREFIX Log file contents:" - cat "$LOG_FILE" - exit 1 - fi - fi -done diff --git a/build-system/sauce_connect/stop_sauce_connect.sh b/build-system/sauce_connect/stop_sauce_connect.sh deleted file mode 100755 index 345894b55fc0a..0000000000000 --- a/build-system/sauce_connect/stop_sauce_connect.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# -# Copyright 2018 The AMP HTML Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the license. -# -# This script stops the sauce connect proxy, and waits for a clean exit. - -CYAN() { echo -e "\033[0;36m$1\033[0m"; } -YELLOW() { echo -e "\033[1;33m$1\033[0m"; } - -PID_FILE="sauce_connect_pid" -LOG_FILE="sauce_connect_log" -LOG_PREFIX=$(YELLOW "stop_sauce_connect.sh") - -# Early exit if there's no proxy running. -if [[ ! -f "$PID_FILE" ]]; then - echo "$LOG_PREFIX Sauce Connect Proxy is not running" - exit 0 -fi - -# Stop the sauce connect proxy. -PID="$(cat "$PID_FILE")" -echo "$LOG_PREFIX Stopping Sauce Connect Proxy pid $(CYAN "$PID")" -kill "$PID" - -# Clean up files. -if [[ -f "$LOG_FILE" ]]; then - echo "$LOG_PREFIX Cleaning up log file $(CYAN "$LOG_FILE")" - rm "$LOG_FILE" -fi -if [[ -f "$PID_FILE" ]]; then - echo "$LOG_PREFIX Cleaning up pid file $(CYAN "$PID_FILE")" - rm "$PID_FILE" -fi - -# Done. -echo "$LOG_PREFIX Successfully stopped Sauce Connect Proxy" diff --git a/build-system/server/OWNERS b/build-system/server/OWNERS index 7aeff9752cc98..1cebb95e96e93 100644 --- a/build-system/server/OWNERS +++ b/build-system/server/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/server/amp-cors.js b/build-system/server/amp-cors.js index f731c300802c6..e66ca850fbd82 100644 --- a/build-system/server/amp-cors.js +++ b/build-system/server/amp-cors.js @@ -24,6 +24,12 @@ const ORIGIN_REGEX = new RegExp( '^https?://localhost:8000|^https?://.+\\.localhost:8000' ); +/** + * @param {*} req require('express').Request + * @param {*} res require('express').Response + * @param {['POST'|'GET']} opt_validMethods + * @param {string[]=} opt_exposeHeaders + */ function assertCors(req, res, opt_validMethods, opt_exposeHeaders) { // Allow disable CORS check (iframe fixtures have origin 'about:srcdoc'). if (req.query.cors == '0') { @@ -60,6 +66,12 @@ function assertCors(req, res, opt_validMethods, opt_exposeHeaders) { enableCors(req, res, origin, opt_exposeHeaders); } +/** + * @param {*} req require('express').Request + * @param {*} res require('express').Response + * @param {string=} origin + * @param {string[]=} opt_exposeHeaders + */ function enableCors(req, res, origin, opt_exposeHeaders) { res.setHeader('Access-Control-Allow-Credentials', 'true'); @@ -76,6 +88,10 @@ function enableCors(req, res, origin, opt_exposeHeaders) { ); } +/** + * @param {*} req require('express').Request + * @return {string} + */ function getUrlPrefix(req) { return req.protocol + '://' + req.headers.host; } diff --git a/build-system/server/amp4test.js b/build-system/server/amp4test.js index 0709f20a7715a..be31d7d435ba8 100644 --- a/build-system/server/amp4test.js +++ b/build-system/server/amp4test.js @@ -21,10 +21,9 @@ const minimist = require('minimist'); const argv = minimist(process.argv.slice(2)); const path = require('path'); const upload = require('multer')(); +const {getServeMode, replaceUrls} = require('./app-utils'); const {renderShadowViewer} = require('./shadow-viewer'); -const {replaceUrls, getServeMode} = require('./app-utils'); -const KARMA_SERVER_PORT = 9876; const CUSTOM_TEMPLATES = ['amp-mustache']; const SERVE_MODE = getServeMode(); @@ -95,7 +94,6 @@ app.use('/compose-shadow', function (req, res) { const {docUrl} = req.query; const viewerHtml = renderShadowViewer({ src: docUrl.replace(/^\//, ''), - port: KARMA_SERVER_PORT, baseHref: path.dirname(req.url), }); res.send(replaceUrls(SERVE_MODE, viewerHtml)); @@ -233,11 +231,11 @@ app.get('/a4a/:bid', (req, res) => { }); /** - * @param {{body: string, css: string|undefined, extensions: Array|undefined, head: string|undefined, spec: string|undefined}} config + * @param {{body: string, css?: string|undefined, extensions: Array|undefined, head?: string|undefined, spec?: string|undefined, mode?: string|undefined}} config * @return {string} */ function composeDocument(config) { - const {body, css, extensions, head, spec, mode} = config; + const {body, css, extensions, head, mode, spec} = config; const m = mode || SERVE_MODE; const cdn = m === 'cdn'; diff --git a/build-system/server/app-index/OWNERS b/build-system/server/app-index/OWNERS index e3778bb45e24d..cded1b6997e2b 100644 --- a/build-system/server/app-index/OWNERS +++ b/build-system/server/app-index/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/server/app-index/amphtml-helpers.js b/build-system/server/app-index/amphtml-helpers.js index 8d5acbd257eda..ae0b8e5663da2 100644 --- a/build-system/server/app-index/amphtml-helpers.js +++ b/build-system/server/app-index/amphtml-helpers.js @@ -32,7 +32,7 @@ const containsByRegex = (str, re) => str.search(re) > -1; // TODO(alanorozco): Expand const formTypes = ['input', 'select', 'form']; -const ExtensionScript = ({name, version, isTemplate}) => +const ExtensionScript = ({isTemplate, name, version}) => html` `, - body: html`
Hola
`, - })); - }); + body: html`
Hola
`, + }) + ); + }).timeout(5000); }); describe('ampStateKey', () => { it('concats arguments', () => { expect(ampStateKey('foo', 'bar')).to.equal('foo.bar'); - expect(ampStateKey('tacos', 'al', 'pastor')) - .to.equal('tacos.al.pastor'); + expect(ampStateKey('tacos', 'al', 'pastor')).to.equal( + 'tacos.al.pastor' + ); }); - }); describe('ternaryExpr', () => { @@ -109,13 +110,15 @@ describe('devdash', () => { describe('containsExpr', () => { it('creates expression with literals', () => { - expect(containsExpr('\'a\'', '\'b\'', '\'c\'', '\'d\'')) - .to.equal('\'a\'.indexOf(\'b\') > -1 ? \'c\' : \'d\''); + expect(containsExpr("'a'", "'b'", "'c'", "'d'")).to.equal( + "'a'.indexOf('b') > -1 ? 'c' : 'd'" + ); }); it('creates expression with vars', () => { - expect(containsExpr('a', 'b', 'c', 'd')) - .to.equal('a.indexOf(b) > -1 ? c : d'); + expect(containsExpr('a', 'b', 'c', 'd')).to.equal( + 'a.indexOf(b) > -1 ? c : d' + ); }); }); @@ -132,16 +135,18 @@ describe('devdash', () => { const {firstElementChild} = root; expect(firstElementChild.tagName).to.equal('SCRIPT'); - expect(firstElementChild.getAttribute('type')) - .to.equal('application/json'); + expect(firstElementChild.getAttribute('type')).to.equal( + 'application/json' + ); }); it('renders json object', () => { const id = 'whatever'; const state = {foo: 'bar', baz: {yes: 'no'}}; - const {textContent} = - parseHtmlChunk(AmpState(id, state)).firstElementChild; + const {textContent} = parseHtmlChunk( + AmpState(id, state) + ).firstElementChild; expect(JSON.parse(textContent)).to.deep.equal(state); }); @@ -150,8 +155,9 @@ describe('devdash', () => { const id = 'whatever'; const state = 'foo'; - const {textContent} = - parseHtmlChunk(AmpState(id, state)).firstElementChild; + const {textContent} = parseHtmlChunk( + AmpState(id, state) + ).firstElementChild; expect(JSON.parse(textContent)).to.equal(state); }); @@ -160,41 +166,44 @@ describe('devdash', () => { const id = 'whatever'; const state = ['foo', 'bar', 'baz']; - const {textContent} = - parseHtmlChunk(AmpState(id, state)).firstElementChild; + const {textContent} = parseHtmlChunk( + AmpState(id, state) + ).firstElementChild; expect(JSON.parse(textContent)).to.deep.equal(state); }); }); describe('addRequiredExtensionsToHead', () => { - function containsExtension(scripts, expectedExtension) { - return scripts.some(s => - s.getAttribute('custom-element') == expectedExtension && - s.getAttribute('custom-template') == null); + return scripts.some( + (s) => + s.getAttribute('custom-element') == expectedExtension && + s.getAttribute('custom-template') == null + ); } function containsTemplate(scripts, expectedTemplate) { - return scripts.some(s => - s.getAttribute('custom-template') == expectedTemplate && - s.getAttribute('custom-extension') == null); + return scripts.some( + (s) => + s.getAttribute('custom-template') == expectedTemplate && + s.getAttribute('custom-extension') == null + ); } it('renders ok', () => { - const rawStr = html` - - - - - - `; + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + `; expect(new JSDOM(addRequiredExtensionsToHead(rawStr))).to.be.ok; }); it('adds mixed', () => { - const expectedExtensions = [ 'amp-foo', 'amp-bar', @@ -205,97 +214,103 @@ describe('devdash', () => { const expectedTemplates = ['amp-mustache']; - const rawStr = html` - - - - - - + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + +
+
- -
- - Text content - -
- - - + + Text content +
- - `; + + + +
+ + `; - const {document} = - (new JSDOM(addRequiredExtensionsToHead(rawStr))).window; + const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) + .window; - const scripts = - Array.from(document.head.getElementsByTagName('script')); + const scripts = Array.from( + document.head.getElementsByTagName('script') + ); expect(scripts).to.have.length( - expectedExtensions.length + expectedTemplates.length); + expectedExtensions.length + expectedTemplates.length + ); - scripts.forEach(script => { + scripts.forEach((script) => { expect(script.getAttribute('src')).to.be.ok; expect(script.getAttribute('async')).to.equal(''); }); - expectedExtensions.forEach(expectedScript => { - expect(scripts).to.satisfy(scripts => - containsExtension(scripts, expectedScript)) + expectedExtensions.forEach((expectedScript) => { + expect(scripts).to.satisfy((scripts) => + containsExtension(scripts, expectedScript) + ); }); - expectedTemplates.forEach(expectedScript => { - expect(scripts).to.satisfy(scripts => - containsTemplate(scripts, expectedScript)) + expectedTemplates.forEach((expectedScript) => { + expect(scripts).to.satisfy((scripts) => + containsTemplate(scripts, expectedScript) + ); }); }); it('adds extensions', () => { - const expected = ['amp-foo', 'amp-bar', 'amp-foo-bar-baz']; - const rawStr = html` - - - - - - + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + +
+
- -
- - Text content - -
+ + Text content +
- - `; +
+ + `; - const {document} = - (new JSDOM(addRequiredExtensionsToHead(rawStr))).window; + const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) + .window; - const scripts = - Array.from(document.head.getElementsByTagName('script')); + const scripts = Array.from( + document.head.getElementsByTagName('script') + ); expect(scripts).to.have.length(expected.length); - scripts.forEach(script => { - expect(script.getAttribute('src')).to.be.ok + scripts.forEach((script) => { + expect(script.getAttribute('src')).to.be.ok; expect(script.getAttribute('async')).to.equal(''); expect(script.getAttribute('custom-template')).to.be.null; }); - expected.forEach(expectedScript => { - expect(scripts).to.satisfy(scripts => - containsExtension(scripts, expectedScript)); + expected.forEach((expectedScript) => { + expect(scripts).to.satisfy((scripts) => + containsExtension(scripts, expectedScript) + ); }); }); it('adds template', () => { const expected = 'amp-mustache'; + // eslint-disable-next-line local/html-template const rawStr = html` @@ -309,8 +324,8 @@ describe('devdash', () => { `; - const {document} = - (new JSDOM(addRequiredExtensionsToHead(rawStr))).window; + const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) + .window; const scripts = document.head.getElementsByTagName('script'); @@ -325,19 +340,18 @@ describe('devdash', () => { }); it('adds per
', () => { - const expected = 'amp-form'; - const rawStr = html` - - - -
- - `; + // eslint-disable-next-line local/html-template + const rawStr = html` + + +
+ + `; - const {document} = - (new JSDOM(addRequiredExtensionsToHead(rawStr))).window; + const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) + .window; const scripts = document.head.getElementsByTagName('script'); @@ -352,21 +366,20 @@ describe('devdash', () => { }); it('adds per ', () => { - const expected = 'amp-form'; - const rawStr = html` - - - - - - - - `; + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + + + `; - const {document} = - (new JSDOM(addRequiredExtensionsToHead(rawStr))).window; + const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) + .window; const scripts = document.head.getElementsByTagName('script'); @@ -381,19 +394,18 @@ describe('devdash', () => { }); it('adds per - - `; + // eslint-disable-next-line local/html-template + const rawStr = html` + + + + + `; - const {document} = - (new JSDOM(addRequiredExtensionsToHead(rawStr))).window; + const {document} = new JSDOM(addRequiredExtensionsToHead(rawStr)) + .window; const scripts = document.head.getElementsByTagName('script'); @@ -406,8 +418,6 @@ describe('devdash', () => { expect(script.getAttribute('async')).to.equal(''); expect(script.getAttribute('custom-element')).to.equal(expected); }); - }); }); - }); diff --git a/build-system/server/app-index/test/test-file-list.js b/build-system/server/app-index/test/test-file-list.js index cebe113597821..b5c04fe6f3810 100644 --- a/build-system/server/app-index/test/test-file-list.js +++ b/build-system/server/app-index/test/test-file-list.js @@ -18,17 +18,16 @@ const {expect} = require('chai'); const {FileList} = require('../file-list'); const {getBoundAttr, parseHtmlChunk} = require('./helpers'); - describe('devdash', () => { - describe('FileList', () => { - it('wraps', () => { - const root = parseHtmlChunk(FileList({ - basepath: 'basepath', - fileSet: [], - selectModePrefix: '/', - })); + const root = parseHtmlChunk( + FileList({ + basepath: 'basepath', + fileSet: [], + selectModePrefix: '/', + }) + ); expect(root.className).to.equal('file-list-container'); @@ -38,11 +37,13 @@ describe('devdash', () => { }); it('creates amp-list', () => { - const root = parseHtmlChunk(FileList({ - basepath: 'basepath', - fileSet: [], - selectModePrefix: '/', - })); + const root = parseHtmlChunk( + FileList({ + basepath: 'basepath', + fileSet: [], + selectModePrefix: '/', + }) + ); const {length} = root.getElementsByTagName('amp-list'); expect(length).to.equal(1); @@ -51,11 +52,13 @@ describe('devdash', () => { it('creates placeholder inside amp-list with rendered data', () => { const fileSet = ['foo.bar', 'tacos.al.pastor']; - const root = parseHtmlChunk(FileList({ - fileSet, - basepath: 'basepath', - selectModePrefix: '/', - })); + const root = parseHtmlChunk( + FileList({ + fileSet, + basepath: 'basepath', + selectModePrefix: '/', + }) + ); const els = root.querySelectorAll('amp-list > [placeholder]'); @@ -75,11 +78,13 @@ describe('devdash', () => { const fileSet = ['asada.html', 'adobada.html', 'pastor.html']; const basepath = '/examples/'; - const root = parseHtmlChunk(FileList({ - fileSet, - basepath, - selectModePrefix: '/', - })); + const root = parseHtmlChunk( + FileList({ + fileSet, + basepath, + selectModePrefix: '/', + }) + ); const els = root.querySelectorAll('amp-list [role=listitem] > a[href]'); @@ -95,11 +100,13 @@ describe('devdash', () => { const fileSet = ['asada.html', 'adobada.html', 'pastor.html']; const basepath = '/potato/'; - const root = parseHtmlChunk(FileList({ - fileSet, - basepath, - selectModePrefix: '/', - })); + const root = parseHtmlChunk( + FileList({ + fileSet, + basepath, + selectModePrefix: '/', + }) + ); const els = root.querySelectorAll('amp-list [role=listitem] > a[href]'); @@ -116,11 +123,13 @@ describe('devdash', () => { const notBound = ['chabbuddy.g', 'dj.beats', 'mc.grindah']; const basepath = '/examples/'; - const root = parseHtmlChunk(FileList({ - fileSet: [...bound, ...notBound], - basepath, - selectModePrefix: '/', - })); + const root = parseHtmlChunk( + FileList({ + fileSet: [...bound, ...notBound], + basepath, + selectModePrefix: '/', + }) + ); const els = root.querySelectorAll('amp-list [role=listitem] > a[href]'); @@ -138,7 +147,5 @@ describe('devdash', () => { expect(el.getAttribute('href')).to.equal(basepath + expectedHref); }); }); - }); - }); diff --git a/build-system/server/app-index/test/test-html.js b/build-system/server/app-index/test/test-html.js index 7ab9f443dab82..fa1a3d671055b 100644 --- a/build-system/server/app-index/test/test-html.js +++ b/build-system/server/app-index/test/test-html.js @@ -17,19 +17,15 @@ const {expect} = require('chai'); const {html, joinFragments} = require('../html'); - describe('devdash', () => { describe('html helpers', () => { describe('joinFragments', () => { - it('joins simple fragments', () => { expect(joinFragments(['a', 'b', 'c'])).to.equal('abc'); }); - it('joins mapped fragments', () => { - expect(joinFragments([1, 2, 3], a => a + 1)).to.equal('234'); + expect(joinFragments([1, 2, 3], (a) => a + 1)).to.equal('234'); }); - }); describe('html', () => { @@ -38,8 +34,9 @@ describe('devdash', () => { }); it('concatenates interpolated args', () => { - expect(html`quesadilla ${'de'} chicharrón ${'con'} queso`) - .to.equal('quesadilla de chicharrón con queso'); + // eslint-disable-next-line local/html-template + const interpolated = html`quesadilla ${'de'} chicharrón ${'con'} queso`; + expect(interpolated).to.equal('quesadilla de chicharrón con queso'); }); }); }); diff --git a/build-system/server/app-index/test/test-self.js b/build-system/server/app-index/test/test-self.js index 47c4241fe4cb8..937d5191356bc 100644 --- a/build-system/server/app-index/test/test-self.js +++ b/build-system/server/app-index/test/test-self.js @@ -17,41 +17,31 @@ const amphtmlValidator = require('amphtml-validator'); const fs = require('fs'); const path = require('path'); +const {expectValidAmphtml, getBoundAttr, parseHtmlChunk} = require('./helpers'); const {expect} = require('chai'); -const { - expectValidAmphtml, - getBoundAttr, - parseHtmlChunk, -} = require('./helpers'); - describe('devdash', () => { - describe('Test helpers', () => { - describe('parseHtmlChunk', () => { - it('returns firstElementChild', () => { const {tagName} = parseHtmlChunk(''); expect(tagName).to.equal(tagName); }); it('fails with multiple children', () => { - expect(() => parseHtmlChunk('')).to.throw; + expect(() => parseHtmlChunk('')).to.throw(); }); it('fails with text node as content', () => { - expect(() => parseHtmlChunk('text content')).to.throw; + expect(() => parseHtmlChunk('text content')).to.throw(); }); it('fails on empty string', () => { - expect(() => parseHtmlChunk('')).to.throw; + expect(() => parseHtmlChunk('')).to.throw(); }); - }); describe('getBoundAttr', () => { - it('returns bound attr set with other bound attrs', () => { const fakeEl = {outerHTML: '
'}; expect(getBoundAttr(fakeEl, 'myHref')).to.equal('a'); @@ -94,36 +84,36 @@ describe('devdash', () => { }); it('returns value with double quote', () => { - const fakeEl = {outerHTML: '
'}; + const fakeEl = {outerHTML: "
"}; expect(getBoundAttr(fakeEl, 'foo')).to.equal('ab"cd ef'); }); it('returns value with single quote', () => { const fakeEl = {outerHTML: '
'}; - expect(getBoundAttr(fakeEl, 'foo')).to.equal('ab\'cd ef'); + expect(getBoundAttr(fakeEl, 'foo')).to.equal("ab'cd ef"); }); - }); describe('expectValidAmphtml', () => { - - it('passes with minimum valid doc', async() => { - const validDocPath = path.join(__dirname, - '../../../../validator/testdata/feature_tests/minimum_valid_amp.html'); + it('passes with minimum valid doc', async () => { + const validDocPath = path.join( + __dirname, + '../../../../validator/testdata/feature_tests/minimum_valid_amp.html' + ); const validDoc = fs.readFileSync(validDocPath).toString(); expectValidAmphtml(await amphtmlValidator.getInstance(), validDoc); }); - it('fails with invalid doc', async() => { + it('fails with invalid doc', async () => { const invalidDoc = ''; const validator = await amphtmlValidator.getInstance(); expect(() => { expectValidAmphtml(validator, invalidDoc); - }).to.throw; + }).to.throw(); }); it('ignores errors with severity ≠ ERROR', () => { @@ -135,9 +125,6 @@ describe('devdash', () => { }; expectValidAmphtml(mockValidator, 'any string'); }); - }); - }); - }); diff --git a/build-system/server/app-index/test/test-template.js b/build-system/server/app-index/test/test-template.js index 395833075732b..2974931b84d9e 100644 --- a/build-system/server/app-index/test/test-template.js +++ b/build-system/server/app-index/test/test-template.js @@ -20,25 +20,26 @@ const {expectValidAmphtml} = require('./helpers'); const {renderTemplate} = require('../template'); describe('template', () => { - describe('renderTemplate', () => { - - it('renders valid doc', async() => { - expectValidAmphtml(await amphtmlValidator.getInstance(), renderTemplate({ - basepath: '/examples/', - css: 'body{}', - isMainPage: true, - fileSet: ['tacos.al.pastor'], - serveMode: 'default', - selectModePrefix: '/', - })); + it('renders valid doc', async () => { + expectValidAmphtml( + await amphtmlValidator.getInstance(), + renderTemplate({ + basepath: '/examples/', + css: 'body{}', + isMainPage: true, + fileSet: ['tacos.al.pastor'], + serveMode: 'default', + selectModePrefix: '/', + }) + ); }); - it('renders valid doc with empty/default values', async() => { - expectValidAmphtml(await amphtmlValidator.getInstance(), - renderTemplate()); + it('renders valid doc with empty/default values', async () => { + expectValidAmphtml( + await amphtmlValidator.getInstance(), + renderTemplate() + ); }); - }); - }); diff --git a/build-system/server/app-index/test/test.js b/build-system/server/app-index/test/test.js index 1b2615e13d56f..a2b8a3c682a11 100644 --- a/build-system/server/app-index/test/test.js +++ b/build-system/server/app-index/test/test.js @@ -20,13 +20,10 @@ const {serveIndexForTesting} = require('../index'); const NOOP = () => {}; describe('devdash', () => { - describe('express middleware', () => { - - it('renders HTML', async() => { + it('renders HTML', async () => { const renderedHtml = await serveIndexForTesting({url: '/'}, {end: NOOP}); expect(renderedHtml).to.be.ok; }); - }); }); diff --git a/build-system/server/app-index/util/listing.js b/build-system/server/app-index/util/listing.js index 7a560033642ed..ce6e693ba8e5d 100644 --- a/build-system/server/app-index/util/listing.js +++ b/build-system/server/app-index/util/listing.js @@ -18,10 +18,20 @@ const fs = require('fs'); const {join, normalize, sep} = require('path'); +/** + * @param {string} path + * @param {string} rootPath + * @return {boolean} + */ function isMaliciousPath(path, rootPath) { return (path + sep).substr(0, rootPath.length) !== rootPath; } +/** + * @param {string} rootPath + * @param {string} basepath + * @return {Promise} + */ async function getListing(rootPath, basepath) { const path = normalize(join(rootPath, basepath)); @@ -43,6 +53,10 @@ async function getListing(rootPath, basepath) { } } +/** + * @param {string} url + * @return {boolean} + */ function isMainPageFromUrl(url) { return url == '/'; } diff --git a/build-system/server/app-utils.js b/build-system/server/app-utils.js index f7f836a4d9727..0d9bae98b5e27 100644 --- a/build-system/server/app-utils.js +++ b/build-system/server/app-utils.js @@ -14,9 +14,9 @@ * limitations under the License. */ -const log = require('fancy-log'); const minimist = require('minimist'); -const {cyan, green} = require('ansi-colors'); +const {cyan, green} = require('../common/colors'); +const {log} = require('../common/logging'); let serveMode = 'default'; @@ -49,9 +49,7 @@ function setServeMode(modeOptions) { if (isRtvMode(rtv)) { serveMode = rtv; } else { - const err = new Error(`Invalid rtv: ${rtv}. (Must be 15 digits long.)`); - err.showStack = false; - throw err; + throw new Error(`Invalid rtv: ${rtv}. (Must be 15 digits long.)`); } } } @@ -87,19 +85,11 @@ const isRtvMode = (serveMode) => { * @param {string} file * @param {string=} hostName * @param {boolean=} inabox - * @param {boolean=} storyV1 * @return {string} */ -const replaceUrls = (mode, file, hostName, inabox, storyV1) => { +const replaceUrls = (mode, file, hostName, inabox) => { hostName = hostName || ''; if (mode == 'default') { - // TODO:(ccordry) remove this when story 0.1 is deprecated - if (storyV1) { - file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/amp-story-0\.1\.js/g, - hostName + '/dist/v0/amp-story-1.0.max.js' - ); - } file = file.replace( /https:\/\/cdn\.ampproject\.org\/v0\.js/g, hostName + '/dist/amp.js' @@ -127,28 +117,28 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { } } else if (mode == 'compiled') { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\.js/g, - hostName + '/dist/v0.js' + /https:\/\/cdn\.ampproject\.org\/v0\.(m?js)/g, + hostName + '/dist/v0.$1' ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/shadow-v0\.js/g, - hostName + '/dist/shadow-v0.js' + /https:\/\/cdn\.ampproject\.org\/shadow-v0\.(m?js)/g, + hostName + '/dist/shadow-v0.$1' ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.js/g, - hostName + '/dist/amp4ads-v0.js' + /https:\/\/cdn\.ampproject\.org\/amp4ads-v0\.(m?js)/g, + hostName + '/dist/amp4ads-v0.$1' ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.js/g, - hostName + '/dist/video-iframe-integration-v0.js' + /https:\/\/cdn\.ampproject\.org\/video-iframe-integration-v0\.(m?js)/g, + hostName + '/dist/video-iframe-integration-v0.$1' ); file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\/(.+?).js/g, - hostName + '/dist/v0/$1.js' + /https:\/\/cdn\.ampproject\.org\/v0\/(.+?)\.(m?js)/g, + hostName + '/dist/v0/$1.$2' ); file = file.replace( - /\/dist\/v0\/examples\/(.*)\.max.js/g, - '/dist/v0/examples/$1.js' + /\/dist\/v0\/examples\/(.*)\.max\.(m?js)/g, + '/dist/v0/examples/$1.$2' ); file = file.replace( /\/dist.3p\/current\/(.*)\.max.html/g, @@ -156,7 +146,7 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { ); if (inabox) { - file = file.replace(/\/dist\/v0\.js/g, '/dist/amp4ads-v0.js'); + file = file.replace(/\/dist\/v0\.(m?js)/g, '/dist/amp4ads-v0.$1'); } } else if (isRtvMode(mode)) { hostName = `https://cdn.ampproject.org/rtv/${mode}/`; @@ -164,14 +154,14 @@ const replaceUrls = (mode, file, hostName, inabox, storyV1) => { if (inabox) { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/rtv\/\d{15}\/v0\.js/g, - hostName + 'amp4ads-v0.js' + /https:\/\/cdn\.ampproject\.org\/rtv\/\d{15}\/v0\.(m?js)/g, + hostName + 'amp4ads-v0.$1' ); } } else if (inabox) { file = file.replace( - /https:\/\/cdn\.ampproject\.org\/v0\.js/g, - 'https://cdn.ampproject.org/amp4ads-v0.js' + /https:\/\/cdn\.ampproject\.org\/v0\.(m?js)/g, + 'https://cdn.ampproject.org/amp4ads-v0.$1' ); } return file; diff --git a/build-system/server/app.js b/build-system/server/app.js index 43ef5d8453ed7..d0a8f57d89281 100644 --- a/build-system/server/app.js +++ b/build-system/server/app.js @@ -17,22 +17,23 @@ /** * @fileoverview Creates an http server to handle static - * files and list directories for use with the gulp live server + * files and list directories for use with the amp live server */ -const app = require('express')(); const argv = require('minimist')(process.argv.slice(2)); const bacon = require('baconipsum'); const bodyParser = require('body-parser'); const cors = require('./amp-cors'); const devDashboard = require('./app-index/index'); +const express = require('express'); +const fetch = require('node-fetch'); const formidable = require('formidable'); const fs = require('fs'); const jsdom = require('jsdom'); const path = require('path'); -const request = require('request'); const upload = require('multer')(); const pc = process; const autocompleteEmailData = require('./autocomplete-test-data'); +const header = require('connect-header'); const runVideoTestBench = require('./app-video-testbench'); const { getVariableRequest, @@ -45,18 +46,46 @@ const { recaptchaRouter, } = require('./recaptcha-router'); const {getServeMode} = require('./app-utils'); +const {isRtvMode, replaceUrls} = require('./app-utils'); +const {logWithoutTimestamp} = require('../common/logging'); +const {log} = require('../common/logging'); +const {red} = require('../common/colors'); const {renderShadowViewer} = require('./shadow-viewer'); -const {replaceUrls, isRtvMode} = require('./app-utils'); +/** + * Respond with content received from a URL when SERVE_MODE is "cdn". + * @param {express.Response} res + * @param {string} cdnUrl + * @return {Promise} + */ +async function passthroughServeModeCdn(res, cdnUrl) { + if (SERVE_MODE !== 'cdn') { + return false; + } + try { + const response = await fetch(cdnUrl); + res.status(response.status); + res.send(await response.text()); + } catch (e) { + log(red('ERROR:'), e); + res.status(500); + res.end(); + } + return true; +} + +const app = express(); const TEST_SERVER_PORT = argv.port || 8000; let SERVE_MODE = getServeMode(); +app.use(bodyParser.json()); app.use(bodyParser.text()); // Middleware is executed in order, so this must be at the top. // TODO(#24333): Migrate all server URL handlers to new-server/router and -// deprecate this file. -if (argv.new_server) { +// deprecate app.js. +// TODO(erwinmombay, #32865): Make visual diff tests use the new server +if (!argv._.includes('visual-diff')) { app.use(require('./new-server/router')); } @@ -65,6 +94,12 @@ app.use('/amp4test', require('./amp4test').app); app.use('/analytics', require('./routes/analytics')); app.use('/list/', require('./routes/list')); app.use('/test', require('./routes/test')); +if (argv.coverage) { + app.use('/coverage', require('istanbul-middleware').createHandler()); +} + +// Built binaries should be fetchable from other origins, i.e. Storybook. +app.use(header({'Access-Control-Allow-Origin': '*'})); // Append ?csp=1 to the URL to turn on the CSP header. // TODO: shall we turn on CSP all the time? @@ -78,6 +113,11 @@ app.use((req, res, next) => { next(); }); +/** + * + * @param {string} serveMode + * @return {boolean} + */ function isValidServeMode(serveMode) { return ( ['default', 'compiled', 'cdn', 'esm'].includes(serveMode) || @@ -85,6 +125,10 @@ function isValidServeMode(serveMode) { ); } +/** + * + * @param {string} serveMode + */ function setServeMode(serveMode) { SERVE_MODE = serveMode; } @@ -153,7 +197,7 @@ app.get('/proxy', async (req, res, next) => { const proxyUrl = `${modePrefix}/proxy/s/${ampdocUrlSuffix}`; res.redirect(proxyUrl); } catch ({message}) { - console.log(`ERROR: ${message}`); + logWithoutTimestamp(`ERROR: ${message}`); next(); } }); @@ -165,32 +209,22 @@ app.get('/proxy', async (req, res, next) => { * @param {string=} protocol 'https' or 'http'. 'https' retries using 'http'. * @return {!Promise} */ -function requestAmphtmlDocUrl(urlSuffix, protocol = 'https') { +async function requestAmphtmlDocUrl(urlSuffix, protocol = 'https') { const defaultUrl = `${protocol}://${urlSuffix}`; - console.log(`Fetching URL: ${defaultUrl}`); - return new Promise((resolve, reject) => { - request(defaultUrl, (error, response, body) => { - if ( - error || - (response && (response.statusCode < 200 || response.statusCode >= 300)) - ) { - if (protocol == 'https') { - return requestAmphtmlDocUrl(urlSuffix, 'http'); - } - return reject(new Error(error || `Status: ${response.statusCode}`)); - } - const {window} = new jsdom.JSDOM(body); - const linkRelAmphtml = window.document.querySelector('link[rel=amphtml]'); - if (!linkRelAmphtml) { - return resolve(defaultUrl); - } - const amphtmlUrl = linkRelAmphtml.getAttribute('href'); - if (!amphtmlUrl) { - return resolve(defaultUrl); - } - return resolve(amphtmlUrl); - }); - }); + logWithoutTimestamp(`Fetching URL: ${defaultUrl}`); + + const response = await fetch(defaultUrl); + if (!response.ok) { + if (protocol == 'https') { + return requestAmphtmlDocUrl(urlSuffix, 'http'); + } + throw new Error(`Status: ${response.status}`); + } + + const {window} = new jsdom.JSDOM(await response.text()); + const linkRelAmphtml = window.document.querySelector('link[rel=amphtml]'); + const amphtmlUrl = linkRelAmphtml && linkRelAmphtml.getAttribute('href'); + return amphtmlUrl || defaultUrl; } /* @@ -207,7 +241,6 @@ app.get( '/examples/*.(min|max).html', '/test/manual/*.(min|max).html', '/test/fixtures/e2e/*/*.(min|max).html', - '/dist/cache-sw.(min|max).html', ], (req, res) => { const filePath = req.url; @@ -248,13 +281,13 @@ app.use('/pwa', (req, res) => { }); }); -app.use('/api/show', (req, res) => { +app.use('/api/show', (_req, res) => { res.json({ showNotification: true, }); }); -app.use('/api/dont-show', (req, res) => { +app.use('/api/dont-show', (_req, res) => { res.json({ showNotification: false, }); @@ -269,7 +302,7 @@ app.use('/api/echo/post', (req, res) => { res.end(req.body); }); -app.use('/api/ping', (req, res) => { +app.use('/api/ping', (_req, res) => { res.status(204).end(); }); @@ -277,7 +310,7 @@ app.use('/form/html/post', (req, res) => { cors.assertCors(req, res, ['POST']); const form = new formidable.IncomingForm(); - form.parse(req, (err, fields) => { + form.parse(req, (_err, fields) => { res.setHeader('Content-Type', 'text/html'); if (fields['email'] == 'already@subscribed.com') { res.statusCode = 500; @@ -320,7 +353,7 @@ app.use('/form/echo-json/post', (req, res) => { } fields[realName].push(value); }); - form.parse(req, (unusedErr) => { + form.parse(req, () => { res.setHeader('Content-Type', 'application/json; charset=utf-8'); if (fields['email'] == 'already@subscribed.com') { res.statusCode = 500; @@ -362,7 +395,6 @@ app.use('/form/json/poll1', (req, res) => { app.post('/form/json/upload', upload.fields([{name: 'myFile'}]), (req, res) => { cors.assertCors(req, res, ['POST']); - /** @type {!Array|undefined} */ const myFile = req.files['myFile']; if (!myFile) { @@ -375,7 +407,7 @@ app.post('/form/json/upload', upload.fields([{name: 'myFile'}]), (req, res) => { res.json({message: contents}); }); -app.use('/form/search-html/get', (req, res) => { +app.use('/form/search-html/get', (_req, res) => { res.setHeader('Content-Type', 'text/html'); res.end(`

Here's results for your search

@@ -421,7 +453,7 @@ app.use('/form/autocomplete/query', (req, res) => { } }); -app.use('/form/autocomplete/error', (req, res) => { +app.use('/form/autocomplete/error', (_req, res) => { res.status(500).end(); }); @@ -441,7 +473,7 @@ app.use('/form/mention/query', (req, res) => { app.use('/form/verify-search-json/post', (req, res) => { cors.assertCors(req, res, ['POST']); const form = new formidable.IncomingForm(); - form.parse(req, (err, fields) => { + form.parse(req, (_err, fields) => { res.setHeader('Content-Type', 'application/json; charset=utf-8'); const errors = []; @@ -479,62 +511,60 @@ app.use('/form/verify-search-json/post', (req, res) => { }); }); -app.use('/share-tracking/get-outgoing-fragment', (req, res) => { - res.json({ - fragment: '54321', - }); -}); - -// Fetches an AMP document from the AMP proxy and replaces JS -// URLs, so that they point to localhost. -function proxyToAmpProxy(req, res, mode) { +/** + * Fetches an AMP document from the AMP proxy and replaces JS + * URLs, so that they point to localhost. + * + * @param {express.Request} req + * @param {express.Response} res + * @param {string} mode + */ +async function proxyToAmpProxy(req, res, mode) { const url = 'https://cdn.ampproject.org/' + (req.query['amp_js_v'] ? 'v' : 'c') + req.url; - console.log('Fetching URL: ' + url); - request(url, function (error, response, body) { + logWithoutTimestamp('Fetching URL: ' + url); + const urlResponse = await fetch(url); + let body = await urlResponse.text(); + body = body + // Unversion URLs. + .replace( + /https\:\/\/cdn\.ampproject\.org\/rtv\/\d+\//g, + 'https://cdn.ampproject.org/' + ) + // href pointing to the proxy, so that images, etc. still work. + .replace('', ''); + const inabox = req.query['inabox']; + const urlPrefix = getUrlPrefix(req); + if (req.query['mraid']) { body = body - // Unversion URLs. .replace( - /https\:\/\/cdn\.ampproject\.org\/rtv\/\d+\//g, - 'https://cdn.ampproject.org/' + '', + '' + + '' ) - // href pointing to the proxy, so that images, etc. still work. - .replace('', ''); - const inabox = req.query['inabox']; - // TODO(ccordry): Remove this when story v01 is depricated. - const storyV1 = req.query['story_v'] === '1'; - const urlPrefix = getUrlPrefix(req); - if (req.query['mraid']) { - body = body - .replace( - '', - '' + - '' - ) - // Change cdnUrl from the default so amp-mraid requests the (mock) - // mraid.js from the local server. In a real environment this doesn't - // matter as the local environment would intercept this request. - .replace( - '', - ' ' + - ' ' - ); - } - body = replaceUrls(mode, body, urlPrefix, inabox, storyV1); - if (inabox) { - // Allow CORS requests for A4A. - const origin = req.headers.origin || urlPrefix; - cors.enableCors(req, res, origin); - } - res.status(response.statusCode).send(body); - }); + // Change cdnUrl from the default so amp-mraid requests the (mock) + // mraid.js from the local server. In a real environment this doesn't + // matter as the local environment would intercept this request. + .replace( + '', + ' ' + + ' ' + ); + } + body = replaceUrls(mode, body, urlPrefix, inabox); + if (inabox) { + // Allow CORS requests for A4A. + const origin = req.headers.origin || urlPrefix; + cors.enableCors(req, res, origin); + } + res.status(urlResponse.status).send(body); } let itemCtr = 2; @@ -558,7 +588,7 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { } if (!liveListDoc) { const liveListUpdateFullPath = `${pc.cwd()}${req.baseUrl}`; - console.log('liveListUpdateFullPath', liveListUpdateFullPath); + logWithoutTimestamp('liveListUpdateFullPath', liveListUpdateFullPath); const liveListFile = fs.readFileSync(liveListUpdateFullPath); liveListDoc = liveListDocs[req.baseUrl] = new jsdom.JSDOM( liveListFile @@ -608,27 +638,41 @@ app.use('/examples/live-list-update(-reverse)?.amp.html', (req, res, next) => { res.send(`${doctype}${outerHTML}`); }); +/** + * @param {Element} item + */ function liveListReplace(item) { - item.setAttribute('data-update-time', Date.now()); + item.setAttribute('data-update-time', Date.now().toString()); const itemContents = item.querySelectorAll('.content'); - itemContents[0].textContent = Math.floor(Math.random() * 10); - itemContents[1].textContent = Math.floor(Math.random() * 10); + itemContents[0].textContent = Math.floor(Math.random() * 10).toString(); + itemContents[1].textContent = Math.floor(Math.random() * 10).toString(); } +/** + * @param {Element} liveList + * @param {Element} node + */ function liveListInsert(liveList, node) { const iterCount = Math.floor(Math.random() * 2) + 1; - console.log(`inserting ${iterCount} item(s)`); + logWithoutTimestamp(`inserting ${iterCount} item(s)`); for (let i = 0; i < iterCount; i++) { - const child = node.cloneNode(true); + /** + * TODO(#28387) this type cast may be hiding a bug. + * @type {Element} + */ + const child = /** @type {*} */ (node.cloneNode(true)); child.setAttribute('id', `list-item-${itemCtr++}`); child.setAttribute('data-sort-time', Date.now()); - liveList.querySelector('[items]').appendChild(child); + liveList.querySelector('[items]')?.appendChild(child); } } +/** + * @param {Element} liveList + */ function liveListTombstone(liveList) { const tombstoneId = Math.floor(Math.random() * itemCtr); - console.log(`trying to tombstone #list-item-${tombstoneId}`); + logWithoutTimestamp(`trying to tombstone #list-item-${tombstoneId}`); // We can tombstone any list item except item-1 since we always do a // replace example on item-1. if (tombstoneId != 1) { @@ -639,8 +683,14 @@ function liveListTombstone(liveList) { } } -// Generate a random number between min and max -// Value is inclusive of both min and max values. +/** + * Generate a random number between min and max + * Value is inclusive of both min and max values. + * + * @param {number} min + * @param {number} max + * @return {number} + */ function range(min, max) { const values = Array.apply(null, new Array(max - min + 1)).map( (_, i) => min + i @@ -648,11 +698,18 @@ function range(min, max) { return values[Math.round(Math.random() * (max - min))]; } -// Returns the result of a coin flip, true or false +/** + * Returns the result of a coin flip, true or false + * + * @return {boolean} + */ function flip() { return !!Math.floor(Math.random() * 2); } +/** + * @return {string} + */ function getLiveBlogItem() { const now = Date.now(); // Generate a 3 to 7 worded headline @@ -691,7 +748,9 @@ function getLiveBlogItem() {

PublisherName News Reporter

${Date(now).replace(/ GMT.*$/, '')}

+ itemprop="Date"> + ${new Date(now).toString().replace(/ GMT.*$/, '')} +

${body}
@@ -708,6 +767,9 @@ function getLiveBlogItem() { `; } +/** + * @return {string} + */ function getLiveBlogItemWithBindAttributes() { const now = Date.now(); // Generate a 3 to 7 worded headline @@ -795,6 +857,7 @@ app.post('/get-consent-v1/', (req, res) => { cors.assertCors(req, res, ['POST']); const body = { 'promptIfUnknown': true, + 'purposeConsentRequired': ['purpose-foo', 'purpose-bar'], 'forcePromptOnNext': forcePromptOnNext, 'sharedData': { 'tfua': true, @@ -824,20 +887,24 @@ app.post('/get-consent-no-prompt/', (req, res) => { app.post('/check-consent', (req, res) => { cors.assertCors(req, res, ['POST']); - res.json({ + const response = { 'consentRequired': req.query.consentRequired === 'true', 'consentStateValue': req.query.consentStateValue, + 'consentString': req.query.consentString, 'expireCache': req.query.expireCache === 'true', - }); + }; + if (req.query.consentMetadata) { + response['consentMetadata'] = JSON.parse( + req.query.consentMetadata.replace(/'/g, '"') + ); + } + res.json(response); }); // Proxy with local JS. // Example: // http://localhost:8000/proxy/s/www.washingtonpost.com/amphtml/news/post-politics/wp/2016/02/21/bernie-sanders-says-lower-turnout-contributed-to-his-nevada-loss-to-hillary-clinton/ -app.use('/proxy/', (req, res) => { - const mode = SERVE_MODE; - proxyToAmpProxy(req, res, mode); -}); +app.use('/proxy/', (req, res) => proxyToAmpProxy(req, res, SERVE_MODE)); // Nest the response in an iframe. // Example: @@ -914,7 +981,7 @@ app.get('/iframe-echo-message', (req, res) => { * */ -app.use(['/dist/v0/amp-*.js', '/dist/amp*.js'], (req, res, next) => { +app.use(['/dist/v0/amp-*.(m?js)', '/dist/amp*.(m?js)'], (req, _res, next) => { const sleep = parseInt(req.query.sleep || 0, 10) * 1000; setTimeout(next, sleep); }); @@ -922,7 +989,7 @@ app.use(['/dist/v0/amp-*.js', '/dist/amp*.js'], (req, res, next) => { /** * Disable caching for extensions if the --no_caching_extensions flag is used. */ -app.get(['/dist/v0/amp-*.js'], (req, res, next) => { +app.get(['/dist/v0/amp-*.(m?js)'], (_req, res, next) => { if (argv.no_caching_extensions) { res.header('Cache-Control', 'no-store'); } @@ -946,6 +1013,7 @@ app.get( const mode = SERVE_MODE; const inabox = req.query['inabox']; const stream = Number(req.query['stream']); + const componentVersion = req.query['componentVersion']; const urlPrefix = getUrlPrefix(req); fs.promises .readFile(pc.cwd() + filePath, 'utf8') @@ -972,6 +1040,9 @@ app.get( ); } file = file.replace(/__TEST_SERVER_PORT__/g, TEST_SERVER_PORT); + if (componentVersion) { + file = file.replace(/-latest.js/g, `-${componentVersion}.js`); + } if (inabox && req.headers.origin) { // Allow CORS requests for A4A. @@ -990,9 +1061,10 @@ app.get( // Extract amp-ad for the given 'type' specified in URL query. if (req.path.indexOf('/examples/ads.amp.html') == 0 && req.query.type) { - const ads = file.match( - elementExtractor('(amp-ad|amp-embed)', req.query.type) - ); + const ads = + file.match( + elementExtractor('(amp-ad|amp-embed)', req.query.type) + ) ?? []; file = file.replace( /[\s\S]+<\/body>/m, '' + ads.join('') + '' @@ -1004,9 +1076,8 @@ app.get( req.path.indexOf('/examples/analytics-vendors.amp.html') == 0 && req.query.type ) { - const analytics = file.match( - elementExtractor('amp-analytics', req.query.type) - ); + const analytics = + file.match(elementExtractor('amp-analytics', req.query.type)) ?? []; file = file.replace( /
[\s\S]+<\/div>/m, '
' + analytics.join('') + '
' @@ -1018,9 +1089,8 @@ app.get( req.path.indexOf('/examples/amp-consent/cmp-vendors.amp.html') == 0 && req.query.type ) { - const consent = file.match( - elementExtractor('amp-consent', req.query.type) - ); + const consent = + file.match(elementExtractor('amp-consent', req.query.type)) ?? []; file = file.replace( /
[\s\S]+<\/div>/m, '
' + consent.join('') + '
' @@ -1054,10 +1124,19 @@ app.get( } ); +/** + * @param {string} string + * @return {string} + */ function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } +/** + * @param {string} tagName + * @param {string} type + * @return {RegExp} + */ function elementExtractor(tagName, type) { type = escapeRegExp(type); return new RegExp( @@ -1139,20 +1218,78 @@ app.use('/bind/ecommerce/sizes', (req, res) => { }, 1000); // Simulate network delay. }); -// Simulated subscription entitlement +/** + * Simulates a publisher's metering state store. + * (amp-subscriptions) + * @type {{[ampReaderId: string]: {}}} + */ +const meteringStateStore = {}; + +// Simulate a publisher's entitlements API. +// (amp-subscriptions) app.use('/subscription/:id/entitlements', (req, res) => { cors.assertCors(req, res, ['GET']); - res.json({ - source: 'local' + req.params.id, - granted: true, - grantedReason: 'NOT_SUBSCRIBED', + + // Create entitlements response. + const source = 'local' + req.params.id; + const granted = req.params.id > 0; + const grantReason = granted ? 'SUBSCRIBER' : 'NOT_SUBSCRIBER'; + const decryptedDocumentKey = decryptDocumentKey(req.query.crypt); + const response = { + source, + granted, + grantReason, data: { login: true, }, - decryptedDocumentKey: decryptDocumentKey(req.query.crypt), + decryptedDocumentKey, + }; + + // Store metering state, if possible. + const ampReaderId = req.query.rid; + if (ampReaderId && req.query.meteringState) { + // Parse metering state from encoded Base64 string. + const encodedMeteringState = req.query.meteringState; + const decodedMeteringState = Buffer.from( + encodedMeteringState, + 'base64' + ).toString(); + const meteringState = JSON.parse(decodedMeteringState); + + // Store metering state. + meteringStateStore[ampReaderId] = meteringState; + } + + // Add metering state to response, if possible. + if (meteringStateStore[ampReaderId]) { + response.metering = { + state: meteringStateStore[ampReaderId], + }; + } + + res.json(response); +}); + +// Simulate a publisher's SKU map API. +// (amp-subscriptions) +app.use('/subscriptions/skumap', (req, res) => { + cors.assertCors(req, res, ['GET']); + res.json({ + 'subscribe.google.com': { + 'subscribeButtonSimple': { + 'sku': 'basic', + }, + 'subscribeButtonCarousel': { + 'carouselOptions': { + 'skus': ['basic', 'premium_monthly'], + }, + }, + }, }); }); +// Simulate a publisher's pingback API. +// (amp-subscriptions) app.use('/subscription/pingback', (req, res) => { cors.assertCors(req, res, ['POST']); res.json({ @@ -1160,6 +1297,65 @@ app.use('/subscription/pingback', (req, res) => { }); }); +/* + Simulate a publisher's account registration API. + + The `amp-subscriptions-google` extension sends this API a POST request. + The request body looks like: + + { + "googleSignInDetails": { + // This signed JWT contains information from Google Sign-In + "idToken": "...JWT from Google Sign-In...", + // Some useful fields from the `idToken`, pre-parsed for convenience + "name": "Jane Smith", + "givenName": "Jane", + "familyName": "Smith", + "imageUrl": "https://imageurl", + "email": "janesmith@example.com" + }, + // Associate this ID with the registration. Use it to look up metering state + // for future entitlements requests + // https://github.com/ampproject/amphtml/blob/main/extensions/amp-subscriptions/amp-subscriptions.md#combining-the-amp-reader-id-with-publisher-cookies + "ampReaderId": "amp-s0m31d3nt1f13r" + } + + (amp-subscriptions-google) +*/ +app.use('/subscription/register', (req, res) => { + cors.assertCors(req, res, ['POST']); + + // Generate a new ID for this metering state. + const meteringStateId = 'ppid' + Math.round(Math.random() * 99999999); + + // Define registration timestamp. + // + // For demo purposes, set timestamp to 30 seconds ago. + // This causes Metering Toast to show immediately, + // which helps engineers test metering. + const registrationTimestamp = Math.round(Date.now() / 1000) - 30000; + + // Store metering state. + // + // For demo purposes, just save this in memory. + // Production systems should persist this. + meteringStateStore[req.body.ampReaderId] = { + id: meteringStateId, + standardAttributes: { + // eslint-disable-next-line google-camelcase/google-camelcase + registered_user: { + timestamp: registrationTimestamp, // In seconds. + }, + }, + }; + + res.json({ + metering: { + state: meteringStateStore[req.body.ampReaderId], + }, + }); +}); + // Simulated adzerk ad server and AMP cache CDN. app.get('/adzerk/*', (req, res) => { cors.assertCors(req, res, ['GET'], ['AMP-template-amp-creative']); @@ -1185,31 +1381,27 @@ app.get('/adzerk/*', (req, res) => { }); }); +app.get('/dist/*.mjs', (req, res, next) => { + // Allow CORS access control explicitly for mjs files + cors.enableCors(req, res); + next(); +}); + /* * Serve extension scripts and their source maps. */ app.get( - ['/dist/rtv/*/v0/*.js', '/dist/rtv/*/v0/*.js.map'], - (req, res, next) => { + ['/dist/rtv/*/v0/*.(m?js)', '/dist/rtv/*/v0/*.(m?js).map'], + async (req, res, next) => { const mode = SERVE_MODE; const fileName = path.basename(req.path).replace('.max.', '.'); let filePath = 'https://cdn.ampproject.org/v0/' + fileName; - if (mode == 'cdn') { - // This will not be useful until extension-location.js change in prod - // Require url from cdn - request(filePath, (error, response) => { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); + if (await passthroughServeModeCdn(res, filePath)) { return; } const isJsMap = filePath.endsWith('.map'); if (isJsMap) { - filePath = filePath.replace(/\.js\.map$/, '.js'); + filePath = filePath.replace(/\.(m?js)\.map$/, '.$1'); } filePath = replaceUrls(mode, filePath); req.url = filePath + (isJsMap ? '.map' : ''); @@ -1217,42 +1409,49 @@ app.get( } ); -/** - * Serve entry point script url - */ -app.get( - ['/dist/sw.js', '/dist/sw-kill.js', '/dist/ww.js'], - (req, res, next) => { - // Special case for entry point script url. Use compiled for testing - const mode = SERVE_MODE; - const fileName = path.basename(req.path); - if (mode == 'cdn') { - // This will not be useful until extension-location.js change in prod - // Require url from cdn - const filePath = 'https://cdn.ampproject.org/' + fileName; - request(filePath, function (error, response) { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); - return; - } - if (mode == 'default') { - req.url = req.url.replace(/\.js$/, '.max.js'); - } - next(); +if (argv.coverage === 'live') { + app.get('/dist/amp.js', async (req, res) => { + const ampJs = await fs.promises.readFile(`${pc.cwd()}${req.path}`); + res.setHeader('Content-Type', 'text/javascript'); + res.setHeader('Access-Control-Allow-Origin', '*'); + // Append an unload handler that reports coverage information each time you + // leave a page. + res.end(`${ampJs}; +window.addEventListener('beforeunload', (evt) => { + const COV_REPORT_URL = 'http://localhost:${TEST_SERVER_PORT}/coverage/client'; + console.info('POSTing code coverage to', COV_REPORT_URL); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', COV_REPORT_URL, true); + xhr.setRequestHeader('Content-type', 'application/json'); + xhr.send(JSON.stringify(window.__coverage__)); + + // Required by Chrome + evt.returnValue = ''; + return null; +});`); + }); +} + +app.get('/dist/ww.(m?js)', async (req, res, next) => { + // Special case for entry point script url. Use compiled for testing + const mode = SERVE_MODE; + const fileName = path.basename(req.path); + if (await passthroughServeModeCdn(res, fileName)) { + return; } -); + if (mode == 'default') { + req.url = req.url.replace(/\.(m?js)$/, '.max.$1'); + } + next(); +}); -app.get('/dist/iframe-transport-client-lib.js', (req, res, next) => { +app.get('/dist/iframe-transport-client-lib.(m?js)', (req, _res, next) => { req.url = req.url.replace(/dist/, 'dist.3p/current'); next(); }); -app.get('/dist/amp-inabox-host.js', (req, res, next) => { +app.get('/dist/amp-inabox-host.(m?js)', (req, _res, next) => { const mode = SERVE_MODE; if (mode != 'default') { req.url = req.url.replace('amp-inabox-host', 'amp4ads-host-v0'); @@ -1260,115 +1459,7 @@ app.get('/dist/amp-inabox-host.js', (req, res, next) => { next(); }); -/* - * Start Cache SW LOCALDEV section - */ -app.get('/dist/sw(.max)?.js', (req, res, next) => { - const filePath = req.path; - fs.promises - .readFile(pc.cwd() + filePath, 'utf8') - .then((file) => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= - (n.getMinutes() % 5) * 1000 * 60 + - n.getSeconds() * 1000 + - n.getMilliseconds(); - file = - 'self.AMP_CONFIG = {v: "99' + - n + - '",' + - 'cdnUrl: "http://localhost:8000/dist"};' + - file; - res.setHeader('Content-Type', 'application/javascript'); - res.setHeader('Date', new Date().toUTCString()); - res.setHeader('Cache-Control', 'no-cache;max-age=150'); - res.end(file); - }) - .catch(next); -}); - -app.get('/dist/rtv/9[89]*/*.js', (req, res, next) => { - res.setHeader('Content-Type', 'application/javascript'); - res.setHeader('Date', new Date().toUTCString()); - res.setHeader('Cache-Control', 'no-cache;max-age=31536000'); - - setTimeout(() => { - // Cause a delay, to show the "stale-while-revalidate" - if (req.path.includes('v0.js')) { - const path = req.path.replace(/rtv\/\d+/, ''); - return fs.promises - .readFile(pc.cwd() + path, 'utf8') - .then((file) => { - res.end(file); - }) - .catch(next); - } - - res.end(` - const li = document.createElement('li'); - li.textContent = '${req.path}'; - loaded.appendChild(li); - `); - }, 2000); -}); - -app.get(['/dist/cache-sw.html'], (req, res, next) => { - const filePath = '/test/manual/cache-sw.html'; - fs.promises - .readFile(pc.cwd() + filePath, 'utf8') - .then((file) => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= - (n.getMinutes() % 5) * 1000 * 60 + - n.getSeconds() * 1000 + - n.getMilliseconds(); - const percent = parseFloat(req.query.canary) || 0.01; - let env = '99'; - if (Math.random() < percent) { - env = '98'; - n += 5 * 1000 * 60; - } - file = file.replace(/dist\/v0/g, `dist/rtv/${env}${n}/v0`); - file = file.replace(/CURRENT_RTV/, env + n); - - res.setHeader('Content-Type', 'text/html'); - res.end(file); - }) - .catch(next); -}); - -app.get('/dist/diversions', (req, res) => { - let n = new Date(); - // Round down to the nearest 5 minutes. - n -= - (n.getMinutes() % 5) * 1000 * 60 + - n.getSeconds() * 1000 + - n.getMilliseconds(); - n += 5 * 1000 * 60; - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Date', new Date().toUTCString()); - res.setHeader('Cache-Control', 'no-cache;max-age=150'); - res.end(JSON.stringify(['98' + n])); -}); - -/* - * End Cache SW LOCALDEV section - */ - -/** - * Web worker binary. - */ -app.get('/dist/ww(.max)?.js', (req, res) => { - fs.promises.readFile(pc.cwd() + req.path).then((file) => { - res.setHeader('Content-Type', 'text/javascript'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.end(file); - }); -}); - -app.get('/mraid.js', (req, res, next) => { +app.get('/mraid.js', (req, _res, next) => { req.url = req.url.replace('mraid.js', 'examples/mraid/mraid.js'); next(); }); @@ -1402,12 +1493,12 @@ app.use('/mraid/', (req, res) => { }); /** - * @param {string} ampJsVersion + * @param {string} ampJsVersionString * @param {string} file * @return {string} */ -function addViewerIntegrationScript(ampJsVersion, file) { - ampJsVersion = parseFloat(ampJsVersion); +function addViewerIntegrationScript(ampJsVersionString, file) { + const ampJsVersion = parseFloat(ampJsVersionString); if (!ampJsVersion) { return file; } @@ -1433,10 +1524,18 @@ function addViewerIntegrationScript(ampJsVersion, file) { return file; } +/** + * @param {express.Request} req + * @return {string} + */ function getUrlPrefix(req) { return req.protocol + '://' + req.headers.host; } +/** + * @param {string} filePath + * @return {string} + */ function generateInfo(filePath) { const mode = SERVE_MODE; filePath = filePath.substr(0, filePath.length - 9) + '.html'; @@ -1459,6 +1558,10 @@ function generateInfo(filePath) { ); } +/** + * @param {string} encryptedDocumentKey + * @return {?string} + */ function decryptDocumentKey(encryptedDocumentKey) { if (!encryptedDocumentKey) { return null; @@ -1477,36 +1580,29 @@ function decryptDocumentKey(encryptedDocumentKey) { } // serve local vendor config JSON files -app.use('(/dist)?/rtv/*/v0/analytics-vendors/:vendor.json', (req, res) => { - const {vendor} = req.params; - const serveMode = SERVE_MODE; - - if (serveMode === 'cdn') { - const vendorUrl = `https://cdn.ampproject.org/v0/analytics-vendors/${vendor}.json`; - request(vendorUrl, (error, response) => { - if (error) { - res.status(404); - res.end(); - } else { - res.send(response); - } - }); - return; - } +app.use( + '(/dist)?/rtv/*/v0/analytics-vendors/:vendor.json', + async (req, res) => { + const {vendor} = req.params; + const serveMode = SERVE_MODE; - const max = serveMode === 'default' ? '.max' : ''; - const localVendorConfigPath = `${pc.cwd()}/dist/v0/analytics-vendors/${vendor}${max}.json`; + const cdnUrl = `https://cdn.ampproject.org/v0/analytics-vendors/${vendor}.json`; + if (await passthroughServeModeCdn(res, cdnUrl)) { + return; + } - fs.promises - .readFile(localVendorConfigPath) - .then((file) => { + const max = serveMode === 'default' ? '.max' : ''; + const localPath = `${pc.cwd()}/dist/v0/analytics-vendors/${vendor}${max}.json`; + + try { + const file = await fs.promises.readFile(localPath); res.setHeader('Content-Type', 'application/json'); res.end(file); - }) - .catch(() => { + } catch (_) { res.status(404); - res.end('Not found: ' + localVendorConfigPath); - }); -}); + res.end('Not found: ' + localPath); + } + } +); module.exports = app; diff --git a/build-system/server/lazy-build.js b/build-system/server/lazy-build.js index 4bb1bf4fd9ca9..01671d2ce4d57 100644 --- a/build-system/server/lazy-build.js +++ b/build-system/server/lazy-build.js @@ -16,17 +16,24 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); +const { + doBuild3pVendor, + generateBundles, +} = require('../tasks/3p-vendor-helpers'); const { doBuildExtension, - maybeInitializeExtensions, getExtensionsToBuild, + maybeInitializeExtensions, } = require('../tasks/extension-helpers'); -const {doBuildJs} = require('../tasks/helpers'); +const {compileCoreRuntime, doBuildJs} = require('../tasks/helpers'); const {jsBundles} = require('../compile/bundles.config'); +const {VERSION} = require('../compile/internal-version'); const extensionBundles = {}; maybeInitializeExtensions(extensionBundles, /* includeLatest */ true); +const vendorBundles = generateBundles(); + /** * Gets the unminified name of the bundle if it can be lazily built. * @@ -53,10 +60,10 @@ function maybeGetUnminifiedName(bundles, name) { * required. * * @param {string} url - * @param {string} matcher + * @param {string|RegExp} matcher * @param {!Object} bundles - * @param {function()} buildFunc - * @param {function()} next + * @param {function(!Object, string, ?Object):Promise} buildFunc + * @param {function(): void} next */ async function lazyBuild(url, matcher, bundles, buildFunc, next) { const match = url.match(matcher); @@ -76,7 +83,8 @@ async function lazyBuild(url, matcher, bundles, buildFunc, next) { * * @param {!Object} bundles * @param {string} name - * @param {function()} buildFunc + * @param {function(!Object, string, ?Object):Promise} buildFunc + * @return {Promise} */ async function build(bundles, name, buildFunc) { const bundle = bundles[name]; @@ -104,10 +112,10 @@ async function build(bundles, name, buildFunc) { * Lazy builds the correct version of an extension when requested. * * @param {!Object} req - * @param {!Object} res - * @param {function()} next + * @param {!Object} _res + * @param {function(): void} next */ -async function lazyBuildExtensions(req, res, next) { +async function lazyBuildExtensions(req, _res, next) { const matcher = argv.compiled ? /\/dist\/v0\/([^\/]*)\.js/ // '/dist/v0/*.js' : /\/dist\/v0\/([^\/]*)\.max\.js/; // '/dist/v0/*.max.js' @@ -118,27 +126,42 @@ async function lazyBuildExtensions(req, res, next) { * Lazy builds a non-extension JS file when requested. * * @param {!Object} req - * @param {!Object} res - * @param {function()} next + * @param {!Object} _res + * @param {function(): void} next */ -async function lazyBuildJs(req, res, next) { +async function lazyBuildJs(req, _res, next) { const matcher = /\/.*\/([^\/]*\.js)/; await lazyBuild(req.url, matcher, jsBundles, doBuildJs, next); } +/** + * Lazy builds a 3p iframe vendor file when requested. + * + * @param {!Object} req + * @param {!Object} _res + * @param {function(): void} next + */ +async function lazyBuild3pVendor(req, _res, next) { + const matcher = argv.compiled + ? new RegExp(`\\/dist\\.3p\\/${VERSION}\\/vendor\\/([^\/]*)\\.js`) // '/dist.3p/21900000/vendor/*.js' + : /\/dist\.3p\/current\/vendor\/([^\/]*)\.max\.js/; // '/dist.3p/current/vendor/*.max.js' + await lazyBuild(req.url, matcher, vendorBundles, doBuild3pVendor, next); +} + /** * Pre-builds the core runtime and the JS files that it loads. */ async function preBuildRuntimeFiles() { - await build(jsBundles, 'amp.js', doBuildJs); - await build(jsBundles, 'ww.max.js', doBuildJs); + await build(jsBundles, 'amp.js', (_bundles, _name, options) => + compileCoreRuntime(options) + ); } /** * Pre-builds default extensions and ones requested via command line flags. */ async function preBuildExtensions() { - const extensions = getExtensionsToBuild(); + const extensions = getExtensionsToBuild(/* preBuild */ true); for (const extensionBundle in extensionBundles) { const extension = extensionBundles[extensionBundle].name; if (extensions.includes(extension) && !extensionBundle.endsWith('latest')) { @@ -150,6 +173,7 @@ async function preBuildExtensions() { module.exports = { lazyBuildExtensions, lazyBuildJs, + lazyBuild3pVendor, preBuildExtensions, preBuildRuntimeFiles, }; diff --git a/build-system/server/new-server/router.js b/build-system/server/new-server/router.js index c819f2e24987d..7122b2ed74506 100644 --- a/build-system/server/new-server/router.js +++ b/build-system/server/new-server/router.js @@ -15,12 +15,26 @@ */ const router = require('express').Router(); +// @ts-ignore const {transform} = require('./transforms/dist/transform'); router.get('/examples/*.html', async (req, res) => { - const transformed = await transform(process.cwd() + req.path); + let transformedHTML; + const filePath = `${process.cwd()}${req.path}`; + try { + transformedHTML = await transform(filePath); + } catch (e) { + console./*OK*/ log( + `${req.path} could not be transformed by the postHTML ` + + `pipeline.\n${e.message}` + ); + } res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.end(transformed); + if (req.query.__amp_source_origin) { + res.setHeader('Access-Control-Allow-Origin', req.query.__amp_source_origin); + res.setHeader('Access-Control-Allow-Credentials', 'true'); + } + res.end(transformedHTML); }); module.exports = router; diff --git a/build-system/server/new-server/transforms/README.md b/build-system/server/new-server/transforms/README.md index fb4e605e419eb..b12e80c0f228e 100644 --- a/build-system/server/new-server/transforms/README.md +++ b/build-system/server/new-server/transforms/README.md @@ -1,11 +1,12 @@ -## Custom transforms for `gulp serve`. +## Custom transforms for `amp serve`. **Main directory:** `build-system/server/new-server/transforms/` **Structure:** Each subdirectory should contain the following: -- A single `.ts` file that implements a specific transform (`*-transform.ts`) -- A `test/` subdirectory containing one or more pairs of input and output files (`test/*-[input|output].html`) +- A single `.ts` file that implements a specific transform (`*-transform.ts`) +- A `test/` subdirectory containing one or more subdirectories labelled by test name, with each subdirectory containing one pair of input and output files (`test/*/[input|output].html`) +- Optionally, in each individual test subdirectory, an `options.json` file for specific configurations for specific tests (`test/*/options.json`) **Example:** @@ -14,13 +15,17 @@ ├── foo │ ├── foo-transform.ts │ └── test - │ ├── testName-input.html - │ └── testName-output.html + │ └── testName + │ ├── input.html + │ ├── output.html + │ └── options.json ├── bar │ ├── bar-transform.ts │ └── test - │ ├── testName1-input.html - │ ├── testName1-output.html - │ ├── testName2-input.html - │ └── testName2-output.html + │ ├── testName1 + │ │ ├── input.html + │ │ └── output.html + │ └── testName2 + │ ├── input.html + │ └── output.html ``` diff --git a/build-system/server/new-server/transforms/css/css-transform.ts b/build-system/server/new-server/transforms/css/css-transform.ts new file mode 100644 index 0000000000000..7254f49519115 --- /dev/null +++ b/build-system/server/new-server/transforms/css/css-transform.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2020 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. + */ + +import minimist from 'minimist'; +import posthtml from 'posthtml'; +import {readFileSync} from 'fs'; +import {OptionSet} from '../utilities/option-set'; + +const argv = minimist(process.argv.slice(2)); +const isTestMode: boolean = argv._.includes('server-tests'); + +const testDir = 'build-system/server/new-server/transforms/css/test'; +const cwd = process.cwd(); + +const cssPath = isTestMode + ? `${cwd}/${testDir}/css.txt` + : `${cwd}/build/css/v0.css`; +const versionPath = `${cwd}/${testDir}/version.txt` + +const css = readFileSync(cssPath, 'utf8').toString().trim(); +const version = readFileSync(versionPath, 'utf8').toString().trim(); + +interface StyleNode extends posthtml.Node { + tag: 'style', + attrs: { + [key: string]: string | undefined + 'amp-runtime': string, + 'i-amphtml-version': string, + }, + content: string[] +} + +function isStyleNode(node: posthtml.Node | string): node is StyleNode { + return node !== undefined && typeof node !== 'string' && + (node as StyleNode).tag === 'style'; +} + +function prependAmpStyles(head: posthtml.Node): posthtml.Node { + const content = head.content || []; + + const firstStyleNode = content.filter(isStyleNode)[0]; + + // If 'amp-runtime' already exists bail out. + if (firstStyleNode?.attrs && 'amp-runtime' in firstStyleNode.attrs) { + return head; + } + + const styleNode: StyleNode = { + walk: head.walk, + match: head.match, + tag: 'style', + attrs: { + 'amp-runtime': '', + // Prefix 01 to simulate stable/prod version RTV prefix. + 'i-amphtml-version': `01${version}`, + }, + content: [css] + }; + content.unshift(styleNode); + return {...head, content}; +} + +/** + * Replace the src for every stories script tag. + */ +export default function (_options: OptionSet = {}): (tree: posthtml.Node) => void { + return function (tree: posthtml.Node) { + tree.match({tag: 'head'}, prependAmpStyles); + } +} diff --git a/build-system/server/new-server/transforms/css/test/css.txt b/build-system/server/new-server/transforms/css/test/css.txt new file mode 100644 index 0000000000000..efe9c34b60f1a --- /dev/null +++ b/build-system/server/new-server/transforms/css/test/css.txt @@ -0,0 +1 @@ +.i-amp-internal{background-color:red} diff --git a/build-system/server/new-server/transforms/css/test/esm-env/input.html b/build-system/server/new-server/transforms/css/test/esm-env/input.html new file mode 100644 index 0000000000000..5ebf21c535374 --- /dev/null +++ b/build-system/server/new-server/transforms/css/test/esm-env/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/css/test/esm-env/output.html b/build-system/server/new-server/transforms/css/test/esm-env/output.html new file mode 100644 index 0000000000000..81f40c67eaeb2 --- /dev/null +++ b/build-system/server/new-server/transforms/css/test/esm-env/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/css/test/no-insert/input.html b/build-system/server/new-server/transforms/css/test/no-insert/input.html new file mode 100644 index 0000000000000..cf1c30befa6d9 --- /dev/null +++ b/build-system/server/new-server/transforms/css/test/no-insert/input.html @@ -0,0 +1,4 @@ + + + + diff --git a/build-system/server/new-server/transforms/css/test/no-insert/output.html b/build-system/server/new-server/transforms/css/test/no-insert/output.html new file mode 100644 index 0000000000000..a91d5793addc6 --- /dev/null +++ b/build-system/server/new-server/transforms/css/test/no-insert/output.html @@ -0,0 +1,4 @@ + + + + diff --git a/build-system/server/new-server/transforms/css/test/version.txt b/build-system/server/new-server/transforms/css/test/version.txt new file mode 100644 index 0000000000000..a32a4347a4ec2 --- /dev/null +++ b/build-system/server/new-server/transforms/css/test/version.txt @@ -0,0 +1 @@ +1234567890 diff --git a/build-system/server/new-server/transforms/modules/modules-transform.ts b/build-system/server/new-server/transforms/modules/modules-transform.ts index b8b548502e4de..e5294766cc456 100644 --- a/build-system/server/new-server/transforms/modules/modules-transform.ts +++ b/build-system/server/new-server/transforms/modules/modules-transform.ts @@ -14,62 +14,76 @@ * limitations under the License. */ -import {PostHTML} from 'posthtml'; -import {URL} from 'url'; -import {isValidScript, ScriptNode} from '../utilities/script'; -import {CDNURLToLocalDistURL} from '../utilities/cdn'; +import posthtml from 'posthtml'; +import {isJsonScript, isValidScript, toExtension, ScriptNode, tryGetUrl} from '../utilities/script'; +import {OptionSet} from '../utilities/option-set'; /** - * Append a Module Script for a ScriptNode. * @param head * @param script */ -function appendModuleScript(head: PostHTML.Node, script: ScriptNode): void { - const modulePath = CDNURLToLocalDistURL( - new URL(script.attrs.src || ''), - undefined, - '.mjs' - ).toString(); - - const insert: ScriptNode = { - ...script, +function appendModuleScript(head: posthtml.Node, nomoduleScript: ScriptNode, options: OptionSet): void { + const modulePath = toExtension(tryGetUrl(nomoduleScript.attrs.src), '.mjs').toString(); + const moduleScript : ScriptNode = { + ...nomoduleScript, attrs: { - ...script.attrs, + ...nomoduleScript.attrs, src: modulePath, type: 'module', }, }; - delete insert.attrs.nomodule; + delete moduleScript.attrs.nomodule; - (head.content || []).push(insert); + const content = head.content || []; + const nomoduleIdx = content.indexOf(nomoduleScript); + // If we are testing and in esm mode, outright replace the nomodule script + // with the module script. This is so that we testing the module script in + // isolation without a fallback. + if (options.fortesting && options.esm) { + content.splice(nomoduleIdx, 1, moduleScript); + } else { + // Add the module script after the nomodule script. + content.splice(nomoduleIdx + 1, 0, '\n', moduleScript); + } } /** - * + * Returns a function that will transform script node sources into module/nomodule pair. + * @param options */ -export default function(tree: PostHTML.Node): void { - let head: PostHTML.Node | undefined = undefined; - const scripts: Array = []; - tree.walk(node => { - if (node.tag === 'head') { - head = node; - } - if (!isValidScript(node)) { - return node; - } +export default function(options: OptionSet = {}): (tree: posthtml.Node) => void { + return function(tree: posthtml.Node): void { + let head: posthtml.Node | undefined = undefined; + const scripts: Array = []; + tree.walk(node => { + if (node.tag === 'head') { + head = node; + } - // Mark the existing valid scripts with `nomodule` attributes. - node.attrs.nomodule = ''; - scripts.push(node); - return node; - }); + // Make sure that isJsonScript is used before `isValidScript`. We bail out + // early if the ScriptNofe is of type="application/json" since it wouldn't + // have any src url to modify. + if (isJsonScript(node)) { + return node; + } - if (head === undefined) { - console.log('Could not find a head element in the document'); - return; - } + if (!isValidScript(node, options.looseScriptSrcCheck)) { + return node; + } + + // Mark the existing valid scripts with `nomodule` attributes. + node.attrs.nomodule = ''; + scripts.push(node); + return node; + }); - for (const script of scripts) { - appendModuleScript(head, script); + if (head === undefined) { + console.log('Could not find a head element in the document'); + return; + } + + for (const script of scripts) { + appendModuleScript(head, script, options); + } } } diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-cdn-input/input.html b/build-system/server/new-server/transforms/modules/test/module-nomodule-cdn-input/input.html new file mode 100644 index 0000000000000..062973c40f0ac --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-cdn-input/input.html @@ -0,0 +1,4 @@ + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-cdn-input/output.html b/build-system/server/new-server/transforms/modules/test/module-nomodule-cdn-input/output.html new file mode 100644 index 0000000000000..113ced5111814 --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-cdn-input/output.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/input.html b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/input.html new file mode 100644 index 0000000000000..f6cf915a20481 --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/input.html @@ -0,0 +1,5 @@ + + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/options.json b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/options.json new file mode 100644 index 0000000000000..c0b206e741909 --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/options.json @@ -0,0 +1,3 @@ +{ + "looseScriptSrcCheck": true +} diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/output.html b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/output.html new file mode 100644 index 0000000000000..415b9646318ed --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-loose/output.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-strict/input.html b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-strict/input.html new file mode 100644 index 0000000000000..f6cf915a20481 --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-strict/input.html @@ -0,0 +1,5 @@ + + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-strict/output.html b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-strict/output.html new file mode 100644 index 0000000000000..4c78464b51c6d --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-nomodule-localhost-input-strict/output.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/input.html b/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/input.html new file mode 100644 index 0000000000000..062973c40f0ac --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/input.html @@ -0,0 +1,4 @@ + + + + diff --git a/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/options.json b/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/options.json new file mode 100644 index 0000000000000..e8d501419759b --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/options.json @@ -0,0 +1,4 @@ +{ + "esm": true, + "fortesting": true +} diff --git a/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/output.html b/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/output.html new file mode 100644 index 0000000000000..7d5fc87291ffd --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/module-replace-nomodule-script/output.html @@ -0,0 +1,4 @@ + + + + diff --git a/build-system/server/new-server/transforms/modules/test/skip-over-json-script/input.html b/build-system/server/new-server/transforms/modules/test/skip-over-json-script/input.html new file mode 100644 index 0000000000000..39a81ed2bddd3 --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/skip-over-json-script/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/modules/test/skip-over-json-script/output.html b/build-system/server/new-server/transforms/modules/test/skip-over-json-script/output.html new file mode 100644 index 0000000000000..39a81ed2bddd3 --- /dev/null +++ b/build-system/server/new-server/transforms/modules/test/skip-over-json-script/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/scripts-transform.ts b/build-system/server/new-server/transforms/scripts/scripts-transform.ts index 726275aef0905..6333e42710722 100644 --- a/build-system/server/new-server/transforms/scripts/scripts-transform.ts +++ b/build-system/server/new-server/transforms/scripts/scripts-transform.ts @@ -14,21 +14,32 @@ * limitations under the License. */ -import {PostHTML} from 'posthtml'; -import {URL} from 'url'; -import {isValidScript} from '../utilities/script'; -import {CDNURLToLocalDistURL} from '../utilities/cdn'; +import posthtml from 'posthtml'; +import {isJsonScript, isValidScript, tryGetUrl} from '../utilities/script'; +import {CDNURLToLocalHostRelativeAbsoluteDist} from '../utilities/cdn'; +import {OptionSet} from '../utilities/option-set'; +import {parse} from 'path'; /** * For any script, with a valid path to AMP Project CDN, replace it with a local value. * @param script */ -function modifySrc(script: PostHTML.Node): PostHTML.Node { - if (!isValidScript(script)) { +function modifySrc(script: posthtml.Node, options: OptionSet): posthtml.Node { + // Make sure that isJsonScript is used before `isValidScript`. We bail out + // early if the ScriptNode is of type="application/json" since it wouldn't + // have any src url to modify. + if (isJsonScript(script)) { return script; } - const src = CDNURLToLocalDistURL(new URL(script.attrs.src || '')).toString(); + if (!isValidScript(script, options.looseScriptSrcCheck)) { + return script; + } + + const url = tryGetUrl(script.attrs.src || ''); + const parsedPath = parse(url.pathname); + const src = CDNURLToLocalHostRelativeAbsoluteDist(url, [null, null], parsedPath.ext, options.port, options.useMaxNames) + .toString(); script.attrs.src = src; return script; } @@ -36,6 +47,10 @@ function modifySrc(script: PostHTML.Node): PostHTML.Node { /** * Replace the src for every script tag to the local value. */ -export default function(tree: PostHTML.Node): void { - tree.match({tag: 'script'}, modifySrc); +export default function(options: OptionSet = {}): (tree: posthtml.Node) => void { + return function(tree: posthtml.Node) { + tree.match({tag: 'script'}, (script) => { + return modifySrc(script, options); + }); + } } diff --git a/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/input.html b/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/input.html new file mode 100644 index 0000000000000..0048475e7832c --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/options.json b/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/options.json new file mode 100644 index 0000000000000..87590c5297de7 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/options.json @@ -0,0 +1,3 @@ +{ + "useMaxNames": true +} diff --git a/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/output.html b/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/output.html new file mode 100644 index 0000000000000..0faa308d14533 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/convert-to-max-names/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-input.html b/build-system/server/new-server/transforms/scripts/test/invalid-src-input.html deleted file mode 100644 index aaf619d9d243f..0000000000000 --- a/build-system/server/new-server/transforms/scripts/test/invalid-src-input.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/input.html b/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/input.html new file mode 100644 index 0000000000000..1aa22c9e7e316 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/options.json b/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/options.json new file mode 100644 index 0000000000000..c0b206e741909 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/options.json @@ -0,0 +1,3 @@ +{ + "looseScriptSrcCheck": true +} diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/output.html b/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/output.html new file mode 100644 index 0000000000000..8d5870a5df72f --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/invalid-src-loose/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-output.html b/build-system/server/new-server/transforms/scripts/test/invalid-src-output.html deleted file mode 100644 index 597560bff5c78..0000000000000 --- a/build-system/server/new-server/transforms/scripts/test/invalid-src-output.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-strict/input.html b/build-system/server/new-server/transforms/scripts/test/invalid-src-strict/input.html new file mode 100644 index 0000000000000..4bf60247f275b --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/invalid-src-strict/input.html @@ -0,0 +1,2 @@ + + diff --git a/build-system/server/new-server/transforms/scripts/test/invalid-src-strict/output.html b/build-system/server/new-server/transforms/scripts/test/invalid-src-strict/output.html new file mode 100644 index 0000000000000..fa8c4db7f4eac --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/invalid-src-strict/output.html @@ -0,0 +1,2 @@ + + diff --git a/build-system/server/new-server/transforms/scripts/test/multiple-output.html b/build-system/server/new-server/transforms/scripts/test/multiple-output.html deleted file mode 100644 index e10c2e42b28ce..0000000000000 --- a/build-system/server/new-server/transforms/scripts/test/multiple-output.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/build-system/server/new-server/transforms/scripts/test/multiple-input.html b/build-system/server/new-server/transforms/scripts/test/multiple/input.html similarity index 100% rename from build-system/server/new-server/transforms/scripts/test/multiple-input.html rename to build-system/server/new-server/transforms/scripts/test/multiple/input.html diff --git a/build-system/server/new-server/transforms/scripts/test/multiple/output.html b/build-system/server/new-server/transforms/scripts/test/multiple/output.html new file mode 100644 index 0000000000000..aa985dd86b500 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/multiple/output.html @@ -0,0 +1,2 @@ + + diff --git a/build-system/server/new-server/transforms/scripts/test/port-transform-loose/input.html b/build-system/server/new-server/transforms/scripts/test/port-transform-loose/input.html new file mode 100644 index 0000000000000..919055b8f300d --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/port-transform-loose/input.html @@ -0,0 +1,5 @@ + + + + + diff --git a/build-system/server/new-server/transforms/scripts/test/port-transform-loose/options.json b/build-system/server/new-server/transforms/scripts/test/port-transform-loose/options.json new file mode 100644 index 0000000000000..5c31eed2fdffd --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/port-transform-loose/options.json @@ -0,0 +1,4 @@ +{ + "port": 9876, + "looseScriptSrcCheck": true +} diff --git a/build-system/server/new-server/transforms/scripts/test/port-transform-loose/output.html b/build-system/server/new-server/transforms/scripts/test/port-transform-loose/output.html new file mode 100644 index 0000000000000..a9f50715da19a --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/port-transform-loose/output.html @@ -0,0 +1,5 @@ + + + + + diff --git a/build-system/server/new-server/transforms/scripts/test/port-transform-strict/input.html b/build-system/server/new-server/transforms/scripts/test/port-transform-strict/input.html new file mode 100644 index 0000000000000..919055b8f300d --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/port-transform-strict/input.html @@ -0,0 +1,5 @@ + + + + + diff --git a/build-system/server/new-server/transforms/scripts/test/port-transform-strict/options.json b/build-system/server/new-server/transforms/scripts/test/port-transform-strict/options.json new file mode 100644 index 0000000000000..bd8c0931fa96a --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/port-transform-strict/options.json @@ -0,0 +1,3 @@ +{ + "port": 9876 +} diff --git a/build-system/server/new-server/transforms/scripts/test/port-transform-strict/output.html b/build-system/server/new-server/transforms/scripts/test/port-transform-strict/output.html new file mode 100644 index 0000000000000..bd29048368725 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/port-transform-strict/output.html @@ -0,0 +1,5 @@ + + + + + diff --git a/build-system/server/new-server/transforms/scripts/test/retain-extension/input.html b/build-system/server/new-server/transforms/scripts/test/retain-extension/input.html new file mode 100644 index 0000000000000..d380685fe3c27 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/retain-extension/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/retain-extension/output.html b/build-system/server/new-server/transforms/scripts/test/retain-extension/output.html new file mode 100644 index 0000000000000..71f563c211cb3 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/retain-extension/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/runtime-input.html b/build-system/server/new-server/transforms/scripts/test/runtime-input.html deleted file mode 100644 index 6642aca11243f..0000000000000 --- a/build-system/server/new-server/transforms/scripts/test/runtime-input.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/build-system/server/new-server/transforms/scripts/test/runtime-output.html b/build-system/server/new-server/transforms/scripts/test/runtime-output.html deleted file mode 100644 index 0f836a55435bf..0000000000000 --- a/build-system/server/new-server/transforms/scripts/test/runtime-output.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/build-system/server/new-server/transforms/scripts/test/runtime/input.html b/build-system/server/new-server/transforms/scripts/test/runtime/input.html new file mode 100644 index 0000000000000..bbe65c8de9fbd --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/runtime/input.html @@ -0,0 +1,2 @@ + + diff --git a/build-system/server/new-server/transforms/scripts/test/runtime/output.html b/build-system/server/new-server/transforms/scripts/test/runtime/output.html new file mode 100644 index 0000000000000..6e16aeb53068e --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/runtime/output.html @@ -0,0 +1,2 @@ + + diff --git a/build-system/server/new-server/transforms/scripts/test/skip-over-json-script/input.html b/build-system/server/new-server/transforms/scripts/test/skip-over-json-script/input.html new file mode 100644 index 0000000000000..39a81ed2bddd3 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/skip-over-json-script/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/scripts/test/skip-over-json-script/output.html b/build-system/server/new-server/transforms/scripts/test/skip-over-json-script/output.html new file mode 100644 index 0000000000000..39a81ed2bddd3 --- /dev/null +++ b/build-system/server/new-server/transforms/scripts/test/skip-over-json-script/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/stories/stories-transform.ts b/build-system/server/new-server/transforms/stories/stories-transform.ts deleted file mode 100644 index 0ba8df2fda387..0000000000000 --- a/build-system/server/new-server/transforms/stories/stories-transform.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import {PostHTML} from 'posthtml'; -import {URL} from 'url'; -import {isValidScript} from '../utilities/script'; -import {CDNURLToLocalDistURL} from '../utilities/cdn'; - -function sidegradeStories(script: PostHTML.Node): PostHTML.Node { - if (!isValidScript(script)) { - return script; - } - - const originalSrc = new URL(script.attrs.src || ''); - const src = CDNURLToLocalDistURL(originalSrc, [ - 'amp-story-1.0.js', - 'amp-story-1.0.max.js', - ]).toString(); - script.attrs.src = src; - return script; -} - -/** - * Replace the src for every stories script tag. - */ -export default function(tree: PostHTML.Node): void { - tree.match({tag: 'script'}, sidegradeStories); -} diff --git a/build-system/server/new-server/transforms/sxg/sxg-transform.ts b/build-system/server/new-server/transforms/sxg/sxg-transform.ts new file mode 100644 index 0000000000000..184e2e8be38eb --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/sxg-transform.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2020 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. + */ + +import posthtml from 'posthtml'; +import {URL} from 'url'; +import {isJsonScript, isValidScript} from '../utilities/script'; +import {OptionSet} from '../utilities/option-set'; + +function sxgTransform(node: posthtml.Node, options: OptionSet = {}): posthtml.Node { + // Make sure that isJsonScript is used before `isValidScript`. We bail out + // early if the ScriptNode is of type="application/json" since it wouldn't + // have any src url to modify. + if (isJsonScript(node)) { + return node; + } + + if (!isValidScript(node)) { + return node; + } + + if (options.compiled) { + const src = node.attrs.src; + node.attrs.src = src.replace('.js', '.sxg.js'); + } else { + const url = new URL(node.attrs.src); + url.searchParams.append('f', 'sxg'); + node.attrs.src = url.toString(); + } + + return node; +} + +/** + * Returns a function that will transform script node sources into their sxg counterparts. + * @param options + */ +export default function(options: OptionSet = {}): (tree: posthtml.Node) => void { + return function(tree: posthtml.Node) { + tree.match({tag: 'script'}, (script) => { + return sxgTransform(script, options); + }); + } +} diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/input.html b/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/input.html new file mode 100644 index 0000000000000..729d094837859 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/options.json b/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/options.json new file mode 100644 index 0000000000000..cba50baae4d20 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/options.json @@ -0,0 +1,3 @@ +{ + "compiled": true +} diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/output.html b/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/output.html new file mode 100644 index 0000000000000..fd46a3fc83727 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-env-compiled/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-env/input.html b/build-system/server/new-server/transforms/sxg/test/sxg-env/input.html new file mode 100644 index 0000000000000..729d094837859 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-env/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-env/options.json b/build-system/server/new-server/transforms/sxg/test/sxg-env/options.json new file mode 100644 index 0000000000000..318b3f446efa4 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-env/options.json @@ -0,0 +1,3 @@ +{ + "compiled": false +} diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-env/output.html b/build-system/server/new-server/transforms/sxg/test/sxg-env/output.html new file mode 100644 index 0000000000000..cec1790a2a64f --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-env/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-query-param/input.html b/build-system/server/new-server/transforms/sxg/test/sxg-query-param/input.html new file mode 100644 index 0000000000000..985db936a8b95 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-query-param/input.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-query-param/options.json b/build-system/server/new-server/transforms/sxg/test/sxg-query-param/options.json new file mode 100644 index 0000000000000..318b3f446efa4 --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-query-param/options.json @@ -0,0 +1,3 @@ +{ + "compiled": false +} diff --git a/build-system/server/new-server/transforms/sxg/test/sxg-query-param/output.html b/build-system/server/new-server/transforms/sxg/test/sxg-query-param/output.html new file mode 100644 index 0000000000000..ff50be957145c --- /dev/null +++ b/build-system/server/new-server/transforms/sxg/test/sxg-query-param/output.html @@ -0,0 +1,3 @@ + + + diff --git a/build-system/server/new-server/transforms/transform.ts b/build-system/server/new-server/transforms/transform.ts index d8954495b15be..0192ac60aebdf 100644 --- a/build-system/server/new-server/transforms/transform.ts +++ b/build-system/server/new-server/transforms/transform.ts @@ -14,22 +14,45 @@ * limitations under the License. */ -import {promises as fsPromises} from 'fs'; +import fs from 'fs'; import minimist from 'minimist'; import posthtml from 'posthtml'; import transformModules from './modules/modules-transform'; import transformScriptPaths from './scripts/scripts-transform'; -import transformStories from './stories/stories-transform'; - -const transforms = [transformStories, transformScriptPaths]; +import transformCss from './css/css-transform'; const argv = minimist(process.argv.slice(2)); -if (argv.esm) { - transforms.unshift(transformModules); +const FOR_TESTING = argv._.includes('integration'); +// Use 9876 if running integration tests as this is the KARMA_SERVER_PORT +const PORT = FOR_TESTING ? 9876 : (argv.port ?? 8000); +const ESM = !!argv.esm; + +const defaultTransformConfig = { + esm: ESM, + port: PORT, + fortesting: FOR_TESTING, + useMaxNames: !argv.compiled, +}; + +const transforms = [ + transformScriptPaths(defaultTransformConfig), +]; + +if (ESM) { + transforms.unshift( + transformCss(), + transformModules(defaultTransformConfig), + ); } export async function transform(fileLocation: string): Promise { - const source = await fsPromises.readFile(fileLocation, 'utf8'); + const source = await fs.promises.readFile(fileLocation, 'utf8'); const result = await posthtml(transforms).process(source); return result.html; } + +export function transformSync(content: string): string { + // @ts-ignore We can only use posthtml's sync API in our Karma preprocessor. + // See https://github.com/posthtml/posthtml#api + return posthtml(transforms).process(content, {sync: true}).html; +} diff --git a/build-system/server/new-server/transforms/tsconfig.json b/build-system/server/new-server/transforms/tsconfig.json index e462d53846022..5e3e1f059d44a 100644 --- a/build-system/server/new-server/transforms/tsconfig.json +++ b/build-system/server/new-server/transforms/tsconfig.json @@ -1,7 +1,7 @@ { "compileOnSave": true, "compilerOptions": { - "lib": ["esnext"], + "lib": ["dom", "esnext"], "outDir": "dist", "sourceMap": true, "moduleResolution": "node", diff --git a/build-system/server/new-server/transforms/utilities/cdn.ts b/build-system/server/new-server/transforms/utilities/cdn.ts index d3a731634d3c0..a1c65ab192a5b 100644 --- a/build-system/server/new-server/transforms/utilities/cdn.ts +++ b/build-system/server/new-server/transforms/utilities/cdn.ts @@ -15,21 +15,48 @@ */ import {URL} from 'url'; -import {parse, format} from 'path'; +import {parse, format, basename} from 'path'; export const VALID_CDN_ORIGIN = 'https://cdn.ampproject.org'; +export const AMP_MAIN_BINARIES_RENAMES = new Map([ + ['v0', 'amp'], + ['f', 'integration'], + ['shadow-v0', 'amp-shadow'], + ['ampcontext-v0', 'ampcontext-lib'], + ['amp4ads-v0', 'amp-inabox'], + ['amp4ads-host-v0', 'amp-inabox-host'], + ['iframe-transport-client-v0', 'iframe-transform-client-lib'], + ['video-iframe-integration-v0', 'video-iframe-integration'], + ['amp-story-entry-point-v0', 'amp-story-entry-point'], + ['amp-story-player-v0', 'amp-story-player'], +]); + +/** + * @param minifiedBasename should be without extension + */ +function getMinifiedName(minifiedBasename: string): string { + const renamedBasename = AMP_MAIN_BINARIES_RENAMES.get(minifiedBasename); + if (renamedBasename) { + return renamedBasename; + } + + return `${minifiedBasename}.max`; +} + /** * Convert an existing URL to one from the local `serve` command. */ export function CDNURLToLocalDistURL( url: URL, pathnames: [string | null, string | null] = [null, null], - extension: string = '.js' + extension: string = '.js', + port: number = 8000, + useMaxNames = false, ): URL { url.protocol = 'http'; url.hostname = 'localhost'; - url.port = '8000'; + url.port = String(port); const [overwriteablePathname, newPathname] = pathnames; if (url.pathname === overwriteablePathname && newPathname !== null) { @@ -37,15 +64,30 @@ export function CDNURLToLocalDistURL( } const parsedPath = parse('/dist' + url.pathname); + let curBasename = basename(parsedPath.base, parsedPath.ext); + if (useMaxNames) { + curBasename = getMinifiedName(curBasename); + } if (parsedPath.ext !== extension) { - parsedPath.base = parsedPath.base.replace(parsedPath.ext, extension); parsedPath.ext = extension; } + parsedPath.base = `${curBasename}${parsedPath.ext}`; url.pathname = format(parsedPath); return url; } +export function CDNURLToLocalHostRelativeAbsoluteDist( + url: URL, + pathnames: [string | null, string | null] = [null, null], + extension: string = '.js', + port: number = 8000, + useMaxNames = false, +): string { + const newUrl = CDNURLToLocalDistURL(url, pathnames, extension, port, useMaxNames); + return `${newUrl.pathname}${newUrl.search}${newUrl.hash}`; +} + /** * Convert an existing URL to one from a specific RTV. */ @@ -70,3 +112,4 @@ export function CDNURLToRTVURL( return url; } + diff --git a/build-system/server/new-server/transforms/utilities/option-set.ts b/build-system/server/new-server/transforms/utilities/option-set.ts new file mode 100644 index 0000000000000..7ac6f2fd84766 --- /dev/null +++ b/build-system/server/new-server/transforms/utilities/option-set.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2020 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. + */ + + /** + * A list of options to correspond with options.json for testing purposes. + * To add an option, add the corresponding key-value pair into the + * options.json, then add the field to this interface. + */ +export interface OptionSet { + compiled?: boolean; + esm?: boolean; + port?: number; + fortesting?: boolean; + looseScriptSrcCheck?: boolean; + useMaxNames?: boolean; +} diff --git a/build-system/server/new-server/transforms/utilities/script.ts b/build-system/server/new-server/transforms/utilities/script.ts index a902f2a6fe46c..1234f957b6ad5 100644 --- a/build-system/server/new-server/transforms/utilities/script.ts +++ b/build-system/server/new-server/transforms/utilities/script.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import {PostHTML} from 'posthtml'; +import posthtml from 'posthtml'; import {URL} from 'url'; import {extname} from 'path'; import {VALID_CDN_ORIGIN} from './cdn'; +import {parse, format} from 'path'; -export interface ScriptNode extends PostHTML.Node { +export interface ScriptNode extends posthtml.Node { tag: 'script'; attrs: { [key: string]: string | undefined; @@ -27,16 +28,54 @@ export interface ScriptNode extends PostHTML.Node { }; } +const VALID_SCRIPT_EXTENSIONS = ['.js', '.mjs']; + +function isValidScriptExtension(url: URL): boolean { + return VALID_SCRIPT_EXTENSIONS.includes(extname(url.pathname)); +} + /** * Determines if a Node is really a ScriptNode. * @param node */ -export function isValidScript(node: PostHTML.Node): node is ScriptNode { +export function isValidScript(node: posthtml.Node, looseScriptSrcCheck?: boolean): node is ScriptNode { if (node.tag !== 'script') { return false; } const attrs = node.attrs || {}; - const src = new URL(attrs.src || ''); - return src.origin === VALID_CDN_ORIGIN && extname(src.pathname) === '.js'; + const url = tryGetUrl(attrs.src || ''); + if (looseScriptSrcCheck) { + return isValidScriptExtension(url); + } + return url.origin === VALID_CDN_ORIGIN && isValidScriptExtension(url); +} + +export function isJsonScript(node: posthtml.Node): boolean { + if (node.tag !== 'script') { + return false; + } + const attrs = node.attrs || {}; + const type = attrs.type || ''; + return type.toLowerCase() === 'application/json'; +} + +/** + * Transforms a url's extension type to the desired type. + * ex. v0.js -> v0.mjs + */ +export function toExtension(url: URL, extension: string): URL { + const parsedPath = parse(url.pathname); + parsedPath.base = parsedPath.base.replace(parsedPath.ext, extension); + parsedPath.ext = extension; + url.pathname = format(parsedPath); + return url; +} + +/** + * This is a temporary measure to allow for a relaxed parsing of our + * fixture files' src urls before they are all fixed accordingly. + */ +export function tryGetUrl(src: string, host: string = '0.0.0.0', port: number = 8000): URL { + return new URL(src, `http://${host}:${port}`); } diff --git a/build-system/server/recaptcha-router.js b/build-system/server/recaptcha-router.js index 9f4946b09abe4..126edfc6727c2 100644 --- a/build-system/server/recaptcha-router.js +++ b/build-system/server/recaptcha-router.js @@ -44,7 +44,7 @@ const recaptchaFrameRequestHandler = (req, res, next) => { } }; -recaptchaRouter.get('/mock.js', (req, res) => { +recaptchaRouter.get('/mock.js', (_req, res) => { res.end(recaptchaMock); }); diff --git a/build-system/server/routes/a4a-envelopes.js b/build-system/server/routes/a4a-envelopes.js index 0912d73298458..ac097498467a1 100644 --- a/build-system/server/routes/a4a-envelopes.js +++ b/build-system/server/routes/a4a-envelopes.js @@ -14,89 +14,93 @@ * limitations under the License. */ -const app = require('express').Router(); +const express = require('express'); +const fetch = require('node-fetch'); const fs = require('fs'); -const log = require('fancy-log'); -const request = require('request'); const {getServeMode, replaceUrls} = require('../app-utils'); -const {red} = require('ansi-colors'); +const {log} = require('../../common/logging'); +const {red} = require('../../common/colors'); + +const app = express.Router(); // In-a-box envelope. // Examples: // http://localhost:8000/inabox/examples/animations.amp.html // http://localhost:8000/inabox/proxy/s/www.washingtonpost.com/amphtml/news/post-politics/wp/2016/02/21/bernie-sanders-says-lower-turnout-contributed-to-his-nevada-loss-to-hillary-clinton/ -app.use(['/inabox', '/inabox-mraid'], (req, res) => { +app.use(['/inabox', '/inabox-mraid'], async (req, res) => { const templatePath = process.cwd() + '/build-system/server/server-inabox-template.html'; - fs.promises.readFile(templatePath, 'utf8').then((template) => { - template = template.replace(/SOURCE/g, 'AD_URL'); - if (req.baseUrl == '/inabox-mraid') { - // MRAID does not load amp4ads-host-v0.js - template = template.replace('INABOX_ADS_TAG_INTEGRATION', ''); - } - const url = getInaboxUrl(req); - res.end(fillTemplate(template, url.href, req.query)); - }); + let template = await fs.promises.readFile(templatePath, 'utf8'); + template = template.replace(/SOURCE/g, 'AD_URL'); + if (req.baseUrl == '/inabox-mraid') { + // MRAID does not load amp4ads-host-v0.js + template = template.replace('INABOX_ADS_TAG_INTEGRATION', ''); + } + const url = getInaboxUrl(req); + res.end(fillTemplate(template, url.href, req.query)); }); // In-a-box friendly iframe and safeframe envelope. // Examples: // http://localhost:8000/inabox-friendly/examples/animations.amp.html // http://localhost:8000/inabox-friendly/proxy/s/www.washingtonpost.com/amphtml/news/post-politics/wp/2016/02/21/bernie-sanders-says-lower-turnout-contributed-to-his-nevada-loss-to-hillary-clinton/ -app.use('/inabox-(friendly|safeframe)', (req, res) => { +app.use('/inabox-(friendly|safeframe)', async (req, res) => { const templatePath = '/build-system/server/server-inabox-template.html'; - fs.promises - .readFile(process.cwd() + templatePath, 'utf8') - .then((template) => { - const url = getInaboxUrl(req); - if (req.baseUrl == '/inabox-friendly') { - template = template - .replace('SRCDOC_ATTRIBUTE', 'srcdoc="BODY"') - .replace('INABOX_ADS_TAG_INTEGRATION', ''); - } else { - template = template - .replace( - /NAME/g, - '1-0-31;LENGTH;BODY{"uid":"test"}' - ) - .replace( - /SOURCE/g, - url.origin + '/test/fixtures/served/iframe-safeframe.html' - ); - } - return requestFromUrl(template, url.href, req.query); - }) - .then((result) => { - res.end(result); - }) - .catch((err) => { - log(red('Error:'), err); - res.status(500); - res.end(); - }); + try { + let template = await fs.promises.readFile( + process.cwd() + templatePath, + 'utf8' + ); + + const url = getInaboxUrl(req); + if (req.baseUrl == '/inabox-friendly') { + template = template + .replace('SRCDOC_ATTRIBUTE', 'srcdoc="BODY"') + .replace('INABOX_ADS_TAG_INTEGRATION', ''); + } else { + template = template + .replace( + /NAME/g, + '1-0-31;LENGTH;BODY{"uid":"test"}' + ) + .replace( + /SOURCE/g, + url.origin + '/test/fixtures/served/iframe-safeframe.html' + ); + } + const result = await requestFromUrl(template, url.href, req.query); + res.end(result); + } catch (err) { + log(red('Error:'), err); + res.status(500); + res.end(); + } }); // A4A envelope. // Examples: // http://localhost:8000/a4a[-3p]/examples/animations.amp.html // http://localhost:8000/a4a[-3p]/proxy/s/www.washingtonpost.com/amphtml/news/post-politics/wp/2016/02/21/bernie-sanders-says-lower-turnout-contributed-to-his-nevada-loss-to-hillary-clinton/ -app.use('/a4a(|-3p)/', (req, res) => { +app.use('/a4a(|-3p)/', async (req, res) => { const force3p = req.baseUrl.startsWith('/a4a-3p'); const templatePath = '/build-system/server/server-a4a-template.html'; const url = getInaboxUrl(req); - fs.promises - .readFile(process.cwd() + templatePath, 'utf8') - .then((template) => { - const content = fillTemplate(template, url.href, req.query) - .replace(/CHECKSIG/g, force3p || '') - .replace(/DISABLE3PFALLBACK/g, !force3p); - res.end(replaceUrls(getServeMode(), content)); - }); + const template = await fs.promises.readFile( + process.cwd() + templatePath, + 'utf8' + ); + const branchLevelExperiments = req.query.eid; + + const content = fillTemplate(template, url.href, req.query) + .replace(/CHECKSIG/g, force3p || '') + .replace(/DATAEXPERIMENTIDS/, branchLevelExperiments || '') + .replace(/DISABLE3PFALLBACK/g, (!force3p).toString()); + res.end(replaceUrls(getServeMode(), content)); }); /** - * @param {Request} req - * @param {string|undefined} extraExperiment + * @param {express.Request} req + * @param {string=} extraExperiment * @return {!URL} */ function getInaboxUrl(req, extraExperiment) { @@ -137,23 +141,15 @@ function getInaboxUrl(req, extraExperiment) { * @param {Object} query * @return {!Promise} */ -function requestFromUrl(template, url, query) { - return new Promise((resolve, reject) => { - request(url, (error, response, body) => { - if (error) { - reject(error); - return; - } - if ( - !response.headers['content-type'] || - response.headers['content-type'].startsWith('text/html') - ) { - resolve(fillTemplate(template, url, query, body)); - } else { - resolve(null); - } - }); - }); +async function requestFromUrl(template, url, query) { + const response = await fetch(url); + if ( + !response.headers.has('Content-Type') || + response.headers.get('Content-Type').startsWith('text/html') + ) { + return fillTemplate(template, url, query, await response.text()); + } + return null; } /** @@ -161,7 +157,7 @@ function requestFromUrl(template, url, query) { * @param {string} template * @param {string} url * @param {Object} query - * @param {string|undefined} body + * @param {string=} body * @return {string} */ function fillTemplate(template, url, query, body) { @@ -178,8 +174,8 @@ function fillTemplate(template, url, query, body) { } return ( template - .replace(/BODY/g, newBody) - .replace(/LENGTH/g, length) + .replace(/BODY/g, newBody ?? '') + .replace(/LENGTH/g, length.toString()) .replace(/AD_URL/g, url) .replace(/OFFSET/g, query.offset || '0px') .replace(/AD_WIDTH/g, query.width || '300') diff --git a/build-system/server/routes/list.js b/build-system/server/routes/list.js index 33d064e22b32a..964820635c78b 100644 --- a/build-system/server/routes/list.js +++ b/build-system/server/routes/list.js @@ -175,7 +175,7 @@ router.get('/infinite-scroll-state', function (req, res) { res.json(results); }); -router.get('/ecommerce-nested-menu', function (req, res) { +router.get('/ecommerce-nested-menu', function (_req, res) { res.json({ 'menu': [ { diff --git a/build-system/server/routes/test.js b/build-system/server/routes/test.js index 98a09876d0165..e1dec034813af 100644 --- a/build-system/server/routes/test.js +++ b/build-system/server/routes/test.js @@ -24,7 +24,11 @@ router.use('/form/post/success', function (req, res) { }); }); -router.use('/date-picker/config.json', (req, res) => { +router.use('/date-picker/config.json', (_req, res) => { + /** + * @param {Date} date + * @return {string} + */ function getISO8601Date(date) { const year = date.toLocaleString('en-US', {year: 'numeric'}); const month = date.toLocaleString('en-US', {month: '2-digit'}); diff --git a/build-system/server/server-a4a-template.html b/build-system/server/server-a4a-template.html index f2044243e18a5..4b45e95f2d461 100644 --- a/build-system/server/server-a4a-template.html +++ b/build-system/server/server-a4a-template.html @@ -7,10 +7,16 @@ +

A4A Envelope

-
url: AD_URL
+
url: AD_URL
size: AD_WIDTHxAD_HEIGHT
scroll down to see the ad
@@ -22,6 +28,7 @@

A4A Envelope

a4a-conversion="true" checksig="CHECKSIG" disable3pfallback="DISABLE3PFALLBACK" + data-experiment-id="DATAEXPERIMENTIDS" src="AD_URL"> diff --git a/build-system/server/shadow-viewer.js b/build-system/server/shadow-viewer.js index 198419bea1222..03a1c3478beb0 100644 --- a/build-system/server/shadow-viewer.js +++ b/build-system/server/shadow-viewer.js @@ -14,7 +14,6 @@ * limitations under the License. */ /* eslint-disable local/html-template */ -/* eslint-disable indent */ const {html} = require('./app-index/html'); @@ -144,7 +143,7 @@ const SCRIPT = ` }; `; -const renderShadowViewer = ({src, baseHref}) => +const renderShadowViewer = ({baseHref, src}) => html` diff --git a/build-system/server/test-server.js b/build-system/server/test-server.js index 86bfddab756cd..cf39a5ac53316 100644 --- a/build-system/server/test-server.js +++ b/build-system/server/test-server.js @@ -19,11 +19,18 @@ * @fileoverview Creates an http server to handle responses for different test * cases. */ -const app = require('express')(); const bodyParser = require('body-parser'); +const express = require('express'); +const app = express(); app.use(bodyParser.json()); +/** + * + * @param {express.Request} req + * @param {express.Response} res + * @param {express.NextFunction} next + */ function setCorsHeaders(req, res, next) { res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); @@ -44,11 +51,11 @@ app.use('/redirect-to', function (req, res) { res.redirect(302, req.query.url); }); -app.use('/status/404', function (req, res) { +app.use('/status/404', function (_req, res) { res.status(404).end(); }); -app.use('/status/500', function (req, res) { +app.use('/status/500', function (_req, res) { res.status(500).end(); }); @@ -101,7 +108,7 @@ app.use('/form/post', function (req, res) { }); }); -app.use('/form/verify-error', function (req, res) { +app.use('/form/verify-error', function (_req, res) { res.status(400).json({ verifyErrors: [{name: 'email', message: 'That email is already taken.'}], }); diff --git a/build-system/server/typescript-compile.js b/build-system/server/typescript-compile.js new file mode 100644 index 0000000000000..6a4b9e3309933 --- /dev/null +++ b/build-system/server/typescript-compile.js @@ -0,0 +1,68 @@ +/** + * Copyright 2020 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. + */ +const esbuild = require('esbuild'); +const globby = require('globby'); +const path = require('path'); +const {cyan, green} = require('../common/colors'); +const {endBuildStep} = require('../tasks/helpers'); +const {exec} = require('../common/exec'); +const {log} = require('../common/logging'); + +const SERVER_TRANSFORM_PATH = 'build-system/server/new-server/transforms'; +const CONFIG_PATH = `${SERVER_TRANSFORM_PATH}/tsconfig.json`; + +/** + * Builds the new server by converting typescript transforms to JS. This JS + * output is not type-checked as part of `amp check-build-system`. + * @return {Promise} + */ +async function buildNewServer() { + log( + green('Building'), + cyan('AMP Server'), + green('at'), + cyan(`${SERVER_TRANSFORM_PATH}/dist`) + green('...') + ); + const entryPoints = globby.sync(`${SERVER_TRANSFORM_PATH}/**/*.ts`); + const startTime = Date.now(); + await esbuild.build({ + entryPoints, + outdir: path.join(SERVER_TRANSFORM_PATH, 'dist'), + bundle: false, + banner: {js: '// @ts-nocheck'}, + tsconfig: CONFIG_PATH, + format: 'cjs', + }); + endBuildStep('Built', 'AMP Server', startTime); +} + +/** + * Checks all types in the generated output after running server transforms. + */ +function typecheckNewServer() { + const cmd = `npx -p typescript tsc --noEmit -p ${CONFIG_PATH}`; + const result = exec(cmd, {'stdio': ['inherit', 'inherit', 'pipe']}); + + if (result.status != 0) { + throw new Error(`Typechecking AMP Server failed.`); + } +} + +module.exports = { + buildNewServer, + typecheckNewServer, + SERVER_TRANSFORM_PATH, +}; diff --git a/build-system/server/variable-substitution.js b/build-system/server/variable-substitution.js index 0dae1fbfc80db..9a5130af14c73 100644 --- a/build-system/server/variable-substitution.js +++ b/build-system/server/variable-substitution.js @@ -18,6 +18,10 @@ let url; let variableSubstitution; let variables; +/** + * @param {*} req require('express').Request + * @param {*} res require('express').Response + */ function saveVariables(req, res) { const requestVariables = {}; // For when a JSON is entered @@ -30,7 +34,7 @@ function saveVariables(req, res) { AMP Analytics - +

Error:

${e} @@ -52,6 +56,10 @@ function saveVariables(req, res) { return; } +/** + * @param {*} req require('express').Request + * @param {*} res require('express').Response + */ function runVariableSubstitution(req, res) { variables = variables || {}; // Don't include the incremented number sent in to make a new request @@ -70,9 +78,9 @@ function runVariableSubstitution(req, res) { - + - +

'' request:

${ testParameters @@ -105,6 +113,10 @@ function runVariableSubstitution(req, res) { `); } +/** + * @param {*} req require('express').Request + * @param {*} res require('express').Response + */ function saveVariableRequest(req, res) { res.setHeader('Access-Control-Allow-Credentials', true); res.setHeader('Access-Control-Allow-Origin', '*'); @@ -114,7 +126,11 @@ function saveVariableRequest(req, res) { url = req.originalUrl; } -function getVariableRequest(req, res) { +/** + * @param {*} _req require('express').Request + * @param {*} res require('express').Response + */ +function getVariableRequest(_req, res) { res.json({'Results': variableSubstitution, 'URL': url}); return; } diff --git a/build-system/task-runner/OWNERS b/build-system/task-runner/OWNERS new file mode 100644 index 0000000000000..002922bb9cc93 --- /dev/null +++ b/build-system/task-runner/OWNERS @@ -0,0 +1,10 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [{name: 'ampproject/wg-infra'}, {name: 'rsimha', notify: true}], + }, + ], +} diff --git a/build-system/task-runner/amp-cli-runner.js b/build-system/task-runner/amp-cli-runner.js new file mode 100755 index 0000000000000..e3bcdfc166ad0 --- /dev/null +++ b/build-system/task-runner/amp-cli-runner.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview This is the `amp` CLI runner executable that is installed to + * the global node directory. It invokes the repo-local `amp` task runner, and + * makes it possible for multiple local repo copies to share one globally + * installed runner executable. + */ + +const childProcess = require('child_process'); +const path = require('path'); + +/** + * Returns the current git repo's root directory if we are inside one. + * @return {string|undefined} + */ +function getRepoRoot() { + const repoRootCmd = 'git rev-parse --show-toplevel'; + const spawnOptions = { + shell: process.platform == 'win32' ? 'cmd' : '/bin/bash', + encoding: 'utf-8', + }; + const result = childProcess.spawnSync(repoRootCmd, spawnOptions); + return result.status == 0 ? result.stdout.trim() : undefined; +} + +/** + * Invokes the repo-local `amp` task runner if we are inside a git repository. + */ +function invokeAmpTaskRunner() { + const repoRoot = getRepoRoot(); + if (repoRoot) { + require(path.join(repoRoot, 'amp.js')); + } else { + console.log('\x1b[31mERROR:\x1b[0m Not inside a git repo'); + } +} + +invokeAmpTaskRunner(); diff --git a/build-system/task-runner/amp-task-runner.js b/build-system/task-runner/amp-task-runner.js new file mode 100644 index 0000000000000..9e0a394577a22 --- /dev/null +++ b/build-system/task-runner/amp-task-runner.js @@ -0,0 +1,250 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview This is a lightweight task runner with a one-file implentation + * based on the commander npm package. + */ + +const argv = require('minimist')(process.argv.slice(2)); +const commander = require('commander'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); +const { + updatePackages, + updateSubpackages, +} = require('../common/update-packages'); +const {cyan, green, magenta, red} = require('../common/colors'); +const {isCiBuild} = require('../common/ci'); +const {log} = require('../common/logging'); + +/** + * Special-case constant that indicates if `amp --help` was invoked. + */ +const isHelpTask = argv._.length == 0 && argv.hasOwnProperty('help'); + +/** + * Calculates and formats the duration for which an AMP task ran. + * @param {DOMHighResTimeStamp} start + * @return {string} + */ +function getTime(start) { + const endTime = Date.now(); + const executionTime = endTime - start; + const mins = Math.floor(executionTime / 60000); + const secs = Math.floor((executionTime % 60000) / 1000); + const msecs = executionTime % 1000; + return mins !== 0 + ? `${mins}m ${secs}s` + : secs != 0 + ? `${secs}s` + : `${msecs}ms`; +} + +/** + * Switches the current directory to the repo root if needed. This is done so + * that hard-coded paths within tasks can work predictably. + */ +function startAtRepoRoot() { + const repoRoot = path.resolve(__dirname, '..', '..'); + if (repoRoot != process.cwd()) { + process.chdir(repoRoot); + log('Working directory changed to', magenta(path.basename(repoRoot))); + } +} + +/** + * Updates task-specific subpackages if there are any. + * @param {string} taskSourceFilePath + * @return {Promise} + */ +async function maybeUpdateSubpackages(taskSourceFilePath) { + const packageFile = path.join(taskSourceFilePath, 'package.json'); + const hasSubpackages = await fs.pathExists(packageFile); + if (hasSubpackages) { + await updateSubpackages(taskSourceFilePath); + } +} + +/** + * Runs an AMP task with logging and timing after installing its subpackages. + * @param {string} taskName + * @param {string} taskSourceFileName + * @param {Function()} taskFunc + * @return {Promise} + */ +async function runTask(taskName, taskSourceFileName, taskFunc) { + const taskFile = path.relative(os.homedir(), 'amp.js'); + log('Using task file', magenta(taskFile)); + const start = Date.now(); + try { + log(`Starting '${cyan(taskName)}'...`); + await maybeUpdateSubpackages(getTaskSourceFilePath(taskSourceFileName)); + await taskFunc(); + log('Finished', `'${cyan(taskName)}'`, 'after', magenta(getTime(start))); + } catch (err) { + log(`'${cyan(taskName)}'`, red('errored after'), magenta(getTime(start))); + log(err); + process.exit(1); + } +} + +/** + * Prints an error if the task file and / or function are invalid, and exits. + * @param {string} taskSourceFileName + * @param {string?} taskFuncName + */ +function handleInvalidTaskError(taskSourceFileName, taskFuncName) { + log( + red('ERROR:'), + 'Could not find' + (taskFuncName ? ` ${cyan(taskFuncName + '()')} in` : ''), + cyan(path.join('build-system', 'tasks', taskSourceFileName)) + '.' + ); + log( + '⤷ Please check the arguments to', + cyan('createTask()'), + 'in', + cyan('amp.js') + '.' + ); + process.exit(1); +} + +/** + * Returns a task's source file path after making sure it is either a valid JS + * file or a valid dir. + * @param {string} taskSourceFileName + * @return {string} + */ +function getTaskSourceFilePath(taskSourceFileName) { + const tasksDir = path.join(__dirname, '..', 'tasks'); + const taskSourceFilePath = path.join(tasksDir, taskSourceFileName); + const isValidSourceFilePath = + fs.pathExistsSync(`${taskSourceFilePath}.js`) || // Task lives in a JS file. + fs.pathExistsSync(taskSourceFilePath); // Task lives in a directory. + if (!isValidSourceFilePath) { + handleInvalidTaskError(taskSourceFileName); + } + return taskSourceFilePath; +} + +/** + * Returns a task function after making sure it is valid. + * @param {string} taskSourceFileName + * @param {string} taskFuncName + * @return {Function():any} + */ +function getTaskFunc(taskSourceFileName, taskFuncName) { + const taskSourceFilePath = getTaskSourceFilePath(taskSourceFileName); + const taskFunc = require(taskSourceFilePath)[taskFuncName]; + const isValidFunc = typeof taskFunc == 'function'; + if (!isValidFunc) { + handleInvalidTaskError(taskSourceFileName, taskFuncName); + } + return taskFunc; +} + +/** + * Helper that creates the tasks in AMP's toolchain based on the invocation: + * - For `amp --help`, load all task descriptions so a list can be printed. + * - For `amp --help`, load and print just the task description + flags. + * - When a task is actually run, update root packages, load the entry point, + * validate usage, update task-specific packages, and run the task. + * @param {string} taskName + * @param {string=} taskFuncName + * @param {string=} taskSourceFileName + */ +function createTask( + taskName, + taskFuncName = taskName, + taskSourceFileName = taskName +) { + const isInvokedTask = argv._.includes(taskName); // `amp ` + const isDefaultTask = + argv._.length === 0 && taskName == 'default' && !isHelpTask; // `amp` + const isTaskLevelHelp = + (isInvokedTask || isDefaultTask) && argv.hasOwnProperty('help'); // `amp --help` + + if (isHelpTask) { + const taskFunc = getTaskFunc(taskSourceFileName, taskFuncName); + const task = commander.command(cyan(taskName)); + task.description(taskFunc.description); + } + if (isInvokedTask || isDefaultTask) { + startAtRepoRoot(); + if (!isTaskLevelHelp && !isCiBuild()) { + updatePackages(); + } + const taskFunc = getTaskFunc(taskSourceFileName, taskFuncName); + const task = commander.command(taskName, {isDefault: isDefaultTask}); + task.description(green(taskFunc.description)); + task.allowUnknownOption(); // Fall through to validateUsage() + task.helpOption('--help', 'Print this list of flags'); + task.usage(''); + for (const [flag, description] of Object.entries(taskFunc.flags ?? {})) { + task.option(`--${cyan(flag)}`, description); + } + task.action(async () => { + validateUsage(task, taskName, taskFunc); + await runTask(taskName, taskSourceFileName, taskFunc); + }); + } +} + +/** + * Validates usage by examining task and flag invocation. + * @param {Object} task + * @param {string} taskName + * @param {function} taskFunc + */ +function validateUsage(task, taskName, taskFunc) { + const tasks = argv._; + const invalidTasks = tasks.filter((task) => task != taskName); + + const flags = Object.keys(argv).slice(1); // Everything after '_' + const validFlags = taskFunc.flags ? Object.keys(taskFunc.flags) : []; + const invalidFlags = flags.filter((flag) => !validFlags.includes(flag)); + + if (invalidTasks.length > 0 || invalidFlags.length > 0) { + task.addHelpText('before', red('ERROR: ') + 'Invalid usage'); + task.help({error: true}); + } +} + +/** + * Finalizes the task runner by doing special-case setup for `amp --help`, + * parsing the invoked command, and printing an error message if an unknown task + * was called. + */ +function finalizeRunner() { + commander.addHelpCommand(false); // We already have `amp --help` and `amp --help` + if (isHelpTask) { + commander.helpOption('--help', 'Print this list of tasks'); + commander.usage(' '); + } + commander.on('command:*', (args) => { + log(red('ERROR:'), 'Unknown task', cyan(args.join(' '))); + log('⤷ Run', cyan('amp --help'), 'for a full list of tasks.'); + log('⤷ Run', cyan('amp --help'), 'for help with a specific task.'); + process.exitCode = 1; + }); + commander.parse(); +} + +module.exports = { + createTask, + finalizeRunner, +}; diff --git a/build-system/task-runner/install-amp-task-runner.js b/build-system/task-runner/install-amp-task-runner.js new file mode 100644 index 0000000000000..8a3198b806235 --- /dev/null +++ b/build-system/task-runner/install-amp-task-runner.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview Installs the amp task runner. + */ + +const fs = require('fs-extra'); +const path = require('path'); +const {cyan, green, yellow} = require('../common/colors'); +const {getStdout} = require('../common/process'); +const {logWithoutTimestamp: log} = require('../common/logging'); + +const ampCliRunner = 'build-system/task-runner/amp-cli-runner.js'; + +/** + * Installs the `amp` task runner to the npm bin directory if it hasn't already + * been installed. Ensures that the binary exists and is a node runner script. + */ +async function installAmpTaskRunner() { + const npmBinDir = getStdout('npm bin --global').trim(); + const ampBinary = path.join(npmBinDir, 'amp'); + const ampBinaryExists = await fs.pathExists(ampBinary); + if (ampBinaryExists) { + const ampBinaryIsAScript = !(await fs.lstat(ampBinary)).isSymbolicLink(); + if (ampBinaryIsAScript) { + log(green('Detected'), cyan('amp'), green('task runner.')); + return; + } + } + log(yellow('Installing'), cyan('amp'), yellow('task runner...')); + await fs.remove(ampBinary); + await fs.copy(ampCliRunner, ampBinary); + log(green('Installed'), cyan('amp'), green('task runner.\n')); +} + +installAmpTaskRunner(); diff --git a/build-system/tasks/3p-vendor-helpers.js b/build-system/tasks/3p-vendor-helpers.js new file mode 100644 index 0000000000000..71617e1419a19 --- /dev/null +++ b/build-system/tasks/3p-vendor-helpers.js @@ -0,0 +1,145 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const debounce = require('debounce'); +const globby = require('globby'); +const {compileJsWithEsbuild} = require('./helpers'); +const {cyan, red} = require('../common/colors'); +const {endBuildStep} = require('./helpers'); +const {VERSION} = require('../compile/internal-version'); +const {watchDebounceDelay} = require('./helpers'); +const {watch} = require('chokidar'); + +const SRCPATH = ['3p/vendors/*.js']; + +/** + * Entry point for 'amp ad-vendor-configs' + * Compile all the vendor configs and drop in the dist folder + * @param {!Object} options + * @return {!Promise} + */ +async function buildVendorConfigs(options) { + options = options || {}; + + const destPath = 'dist.3p/'; + + if (options.watch) { + // Do not set watchers again when we get called by the watcher. + const copyOptions = {...options, watch: false, calledByWatcher: true}; + const watchFunc = () => { + buildVendorConfigs(copyOptions); + }; + watch(SRCPATH).on('change', debounce(watchFunc, watchDebounceDelay)); + } + + const startTime = Date.now(); + const bundles = generateBundles(); + + await Promise.all( + Object.values(bundles).map((bundle) => + compileJsWithEsbuild( + bundle.srcDir, + bundle.srcFilename, + options.minify ? bundle.minifiedDestDir : bundle.destDir, + {...bundle.options, ...options} + ) + ) + ); + + endBuildStep( + (options.minify ? 'Minified' : 'Compiled') + + ' all 3p iframe vendor configs into', + destPath, + startTime + ); +} + +/** + * Build the JavaScript for the vendor specified for lazy building. + * + * @param {!Object} jsBundles + * @param {string} name + * @param {?Object} options + * @return {!Promise} + */ +async function doBuild3pVendor(jsBundles, name, options) { + const target = jsBundles[name]; + if (target) { + return compileJsWithEsbuild( + target.srcDir, + target.srcFilename, + options.minify ? target.minifiedDestDir : target.destDir, + {...target.options, ...options} + ); + } else { + return Promise.reject( + [red('Error:'), 'Could not find', cyan(name)].join(' ') + ); + } +} + +/** + * Generate bundles for all 3p vendors to be built. + * @return {Object} + */ +function generateBundles() { + const bundles = {}; + listVendors().forEach((vendor) => { + bundles[`${vendor}`] = { + srcDir: './3p/vendors/', + srcFilename: `${vendor}.js`, + destDir: './dist.3p/current/vendor/', + minifiedDestDir: `./dist.3p/${VERSION}/vendor/`, + options: { + include3pDirectories: true, + includePolyfills: true, + externs: ['./ads/ads.extern.js'], + toName: `${vendor}.max.js`, + minifiedName: `${vendor}.js`, + }, + }; + }); + return bundles; +} + +/** + * Return all 3p iframe vendors' names. + * @return {!Array} + */ +function listVendors() { + const filesToBuild = globby.sync(SRCPATH); + const srcMatcher = /^3p\/vendors\/(.*)\.js/; + const results = []; + + for (const index in filesToBuild) { + const src = filesToBuild[index]; + const match = src.match(srcMatcher); + if (!match || match.length != 2) { + throw new Error(`${src} is not a valid 3p vendor path`); + } + + // Extract vendor file name + const name = match[1]; + results.push(name); + } + return results; +} + +module.exports = { + buildVendorConfigs, + doBuild3pVendor, + generateBundles, +}; diff --git a/build-system/tasks/OWNERS b/build-system/tasks/OWNERS index 32c5ea6d75438..48c410b7bfc7c 100644 --- a/build-system/tasks/OWNERS +++ b/build-system/tasks/OWNERS @@ -1,10 +1,14 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [{name: 'ampproject/wg-infra'}], }, + { + pattern: 'check-types.js', + owners: [{name: 'ampproject/wg-performance'}], + }, ], } diff --git a/build-system/tasks/a4a.js b/build-system/tasks/a4a.js deleted file mode 100644 index 257ff3e0a0071..0000000000000 --- a/build-system/tasks/a4a.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const { - maybePrintArgvMessages, - shouldNotRun, -} = require('./runtime-test/helpers'); -const { - RuntimeTestRunner, - RuntimeTestConfig, -} = require('./runtime-test/runtime-test-base'); -const {css} = require('./css'); - -class Runner extends RuntimeTestRunner { - constructor(config) { - super(config); - } - - /** @override */ - async maybeBuild() { - if (argv.nobuild) { - return; - } - - await css(); - } -} - -async function a4a() { - if (shouldNotRun()) { - return; - } - - maybePrintArgvMessages(); - - const config = new RuntimeTestConfig('a4a'); - const runner = new Runner(config); - - await runner.setup(); - await runner.run(); - await runner.teardown(); -} - -module.exports = { - a4a, -}; - -a4a.description = 'Runs a4a tests'; -a4a.flags = { - 'chrome_canary': ' Runs tests on Chrome Canary', - 'chrome_flags': ' Uses the given flags to launch Chrome', - 'firefox': ' Runs tests on Firefox', - 'files': ' Runs tests for specific files', - 'grep': ' Runs tests that match the pattern', - 'headless': ' Run tests in a headless Chrome window', - 'ie': ' Runs tests on IE', - 'nobuild': ' Skips build step', - 'nohelp': ' Silence help messages that are printed prior to test run', - 'safari': ' Runs tests on Safari', - 'testnames': ' Lists the name of each test being run', - 'verbose': ' With logging enabled', - 'watch': ' Watches for changes in files, runs corresponding test(s)', -}; diff --git a/build-system/tasks/analytics-vendor-configs.js b/build-system/tasks/analytics-vendor-configs.js new file mode 100644 index 0000000000000..b33a3da1add9b --- /dev/null +++ b/build-system/tasks/analytics-vendor-configs.js @@ -0,0 +1,95 @@ +/** + * Copyright 2019 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. + */ + +const minimist = require('minimist'); +const argv = minimist(process.argv.slice(2)); +const debounce = require('debounce'); +const fs = require('fs-extra'); +const globby = require('globby'); +const jsonminify = require('jsonminify'); +const {basename, dirname, extname, join} = require('path'); +const {endBuildStep} = require('./helpers'); +const {watchDebounceDelay} = require('./helpers'); +const {watch} = require('chokidar'); + +/** + * Entry point for 'amp analytics-vendor-configs' + * Compile all the vendor configs and drop in the dist folder + * @param {Object=} opt_options + * @return {!Promise} + */ +async function analyticsVendorConfigs(opt_options) { + const options = opt_options || {}; + + const srcPath = ['extensions/amp-analytics/0.1/vendors/*.json']; + const destPath = 'dist/v0/analytics-vendors/'; + + // ignore test json if not fortesting or build. + if (!(argv.fortesting || options.fortesting || argv._.includes('build'))) { + srcPath.push('!extensions/amp-analytics/0.1/vendors/_fake_.json'); + } + + if (options.watch) { + // Do not set watchers again when we get called by the watcher. + const copyOptions = {...options, watch: false, calledByWatcher: true}; + const watchFunc = () => { + analyticsVendorConfigs(copyOptions); + }; + watch(srcPath).on('change', debounce(watchFunc, watchDebounceDelay)); + } + + const startTime = Date.now(); + + const srcFiles = globby.sync(srcPath); + await fs.ensureDir(destPath); + for (const srcFile of srcFiles) { + let destFile = join(destPath, basename(srcFile)); + let contents = await fs.readFile(srcFile, 'utf-8'); + if (options.minify) { + contents = jsonminify(contents); + } + // Report any parsing errors + try { + JSON.parse(contents); + } catch (err) { + // Only fail if not in watcher, so watch is not interrupted + if (!options.calledByWatcher) { + throw err; + } + } + // If not minifying, append .max to filename + if (!options.minify) { + const extension = extname(destFile); + const base = basename(destFile, extension); + const dir = dirname(destFile); + destFile = join(dir, `${base}.max${extension}`); + } + await fs.writeFile(destFile, contents, 'utf-8'); + } + if (globby.sync(srcPath).length > 0) { + endBuildStep( + 'Compiled all analytics vendor configs into', + destPath, + startTime + ); + } +} + +module.exports = { + analyticsVendorConfigs, +}; + +analyticsVendorConfigs.description = 'Compile analytics vendor configs to dist'; diff --git a/build-system/tasks/ava.js b/build-system/tasks/ava.js index 48edce57a51d5..afd59ac017295 100644 --- a/build-system/tasks/ava.js +++ b/build-system/tasks/ava.js @@ -15,32 +15,37 @@ */ 'use strict'; -const gulp = require('gulp'); -const gulpAva = require('gulp-ava'); -const {isTravisBuild} = require('../common/travis'); +const argv = require('minimist')(process.argv.slice(2)); +const {execOrDie} = require('../common/exec'); /** * Runs ava tests. - * @return {!Vinyl} */ async function ava() { - return gulp - .src([ - require.resolve('./csvify-size/test.js'), - require.resolve('./get-zindex/test.js'), - require.resolve('./prepend-global/test.js'), - ]) - .pipe( - gulpAva({ - 'concurrency': 5, - 'failFast': true, - 'silent': isTravisBuild(), - }) - ); + // These need equivalents for CI in build-system/pr-check/build-targets.js + // (see targetMatchers[Targets.AVA]) + const testFiles = [ + 'build-system/tasks/get-zindex/get-zindex.test.js', + 'build-system/tasks/make-extension/test/test.js', + 'build-system/tasks/markdown-toc/test/test.js', + 'build-system/tasks/prepend-global/prepend-global.test.js', + ]; + execOrDie( + [ + 'npx ava', + ...testFiles, + '--color --fail-fast', + argv.watch ? '--watch' : '', + ].join(' ') + ); } module.exports = { ava, }; -ava.description = 'Runs ava tests for gulp tasks'; +ava.description = "Runs ava tests for AMP's tasks"; + +ava.flags = { + 'watch': 'Watches for changes', +}; diff --git a/build-system/tasks/babel-plugin-tests.js b/build-system/tasks/babel-plugin-tests.js index 09d22b9f8dce8..790efd3087dc0 100644 --- a/build-system/tasks/babel-plugin-tests.js +++ b/build-system/tasks/babel-plugin-tests.js @@ -16,10 +16,10 @@ 'use strict'; const jest = require('@jest/core'); -const {isTravisBuild} = require('../common/travis'); +const {isCiBuild} = require('../common/ci'); /** - * Entry point for `gulp babel-plugin-tests`. Runs the jest-based tests for + * Entry point for `amp babel-plugin-tests`. Runs the jest-based tests for * AMP's custom babel plugins. */ async function babelPluginTests() { @@ -27,15 +27,28 @@ async function babelPluginTests() { const options = { automock: false, coveragePathIgnorePatterns: ['/node_modules/'], + detectOpenHandles: true, modulePathIgnorePatterns: ['/test/fixtures/', '/build/'], - reporters: [isTravisBuild() ? 'jest-silent-reporter' : 'jest-dot-reporter'], + reporters: [ + isCiBuild() ? 'jest-silent-reporter' : 'jest-progress-bar-reporter', + ], setupFiles: ['./build-system/babel-plugins/testSetupFile.js'], testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/'], testRegex: '/babel-plugins/[^/]+/test/.+\\.m?js$', transformIgnorePatterns: ['/node_modules/'], }; - await jest.runCLI(options, projects); + + // The `jest.runCLI` command is undocumented. See the types file for object shape: + // https://github.com/facebook/jest/blob/bd76829f66c5c0f3c6907b80010f19893cb0fc8c/packages/jest-test-result/src/types.ts#L74-L91. + const aggregatedResults = await jest.runCLI( + // TODO(#23582) the typing for jest.Config.Argv seems to be a little off. + /** @type {*} */ (options), + projects + ); + if (!aggregatedResults.results.success) { + throw new Error('See the logs above for details.'); + } } module.exports = { diff --git a/build-system/tasks/build.js b/build-system/tasks/build.js index 7cf5e6ece7af0..523bcecc70827 100644 --- a/build-system/tasks/build.js +++ b/build-system/tasks/build.js @@ -14,7 +14,6 @@ * limitations under the License. */ -const log = require('fancy-log'); const { bootstrapThirdPartyFrames, compileAllJs, @@ -27,51 +26,25 @@ const { exitCtrlcHandler, } = require('../common/ctrlcHandler'); const {buildExtensions} = require('./extension-helpers'); +const {buildVendorConfigs} = require('./3p-vendor-helpers'); const {compileCss} = require('./css'); -const {compileJison} = require('./compile-jison'); -const {cyan, green, yellow} = require('ansi-colors'); -const {maybeUpdatePackages} = require('./update-packages'); const {parseExtensionFlags} = require('./extension-helpers'); const argv = require('minimist')(process.argv.slice(2)); -/** - * Deprecated. Use `gulp build --watch` or `gulp dist --watch`. - * - * TODO(rsimha, #27471): Remove this after several weeks. - */ -async function watch() { - log(yellow('WARNING:'), cyan('gulp watch'), 'has been deprecated.'); - log( - green('INFO:'), - 'Use', - cyan('gulp build --watch'), - 'or', - cyan('gulp dist --watch'), - 'instead.' - ); - log( - green('INFO:'), - 'Run', - cyan('gulp help'), - 'for a full list of commands and flags.' - ); -} - /** * Perform the prerequisite steps before starting the unminified build. - * Used by `gulp` and `gulp build`. + * Used by `amp` and `amp build`. * * @param {!Object} options + * @return {Promise} */ async function runPreBuildSteps(options) { - await compileCss(options); - await compileJison(); - await bootstrapThirdPartyFrames(options); + return Promise.all([compileCss(options), bootstrapThirdPartyFrames(options)]); } /** - * Unminified build. Entry point for `gulp build`. + * Unminified build. Entry point for `amp build`. */ async function build() { await doBuild(); @@ -83,7 +56,6 @@ async function build() { * @param {Object=} extraArgs */ async function doBuild(extraArgs = {}) { - maybeUpdatePackages(); const handlerProcess = createCtrlcHandler('build'); process.env.NODE_ENV = 'development'; const options = { @@ -92,14 +64,18 @@ async function doBuild(extraArgs = {}) { watch: argv.watch, }; printNobuildHelp(); - printConfigHelp('gulp build'); + printConfigHelp('amp build'); parseExtensionFlags(); await runPreBuildSteps(options); if (argv.core_runtime_only) { await compileCoreRuntime(options); } else { await compileAllJs(options); - await buildExtensions(options); + } + await buildExtensions(options); + + if (!argv.core_runtime_only) { + await buildVendorConfigs(options); } if (!argv.watch) { exitCtrlcHandler(handlerProcess); @@ -110,24 +86,22 @@ module.exports = { build, doBuild, runPreBuildSteps, - watch, }; /* eslint "google-camelcase/google-camelcase": 0 */ build.description = 'Builds the AMP library'; build.flags = { - config: ' Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', - fortesting: ' Builds the AMP library for local testing', - extensions: ' Builds only the listed extensions.', - extensions_from: ' Builds only the extensions from the listed AMP(s).', - noextensions: ' Builds with no extensions.', - core_runtime_only: ' Builds only the core runtime.', - coverage: ' Adds code coverage instrumentation to JS files using istanbul.', - version_override: ' Overrides the version written to AMP_CONFIG', - watch: ' Watches for changes in files, re-builds when detected', + config: 'Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', + fortesting: 'Builds the AMP library for local testing', + extensions: 'Builds only the listed extensions.', + extensions_from: 'Builds only the extensions from the listed AMP(s).', + noextensions: 'Builds with no extensions.', + core_runtime_only: 'Builds only the core runtime.', + coverage: 'Adds code coverage instrumentation to JS files using istanbul.', + version_override: 'Overrides the version written to AMP_CONFIG', + watch: 'Watches for changes in files, re-builds when detected', + esm: 'Do not transpile down to ES5', define_experiment_constant: - ' Builds runtime with the EXPERIMENT constant set to true', + 'Builds runtime with the EXPERIMENT constant set to true', }; - -watch.description = 'Deprecated. Use gulp build --watch or gulp dist --watch'; diff --git a/build-system/tasks/bundle-size/APPROVERS.json b/build-system/tasks/bundle-size/APPROVERS.json index d64fb815b5168..638d36c63c1b5 100644 --- a/build-system/tasks/bundle-size/APPROVERS.json +++ b/build-system/tasks/bundle-size/APPROVERS.json @@ -1,22 +1,22 @@ { - "dist/v0.js": { - "approvers": ["ampproject/wg-performance", "ampproject/wg-runtime"], + "dist/v0.?(m)js": { + "approvers": ["ampproject/wg-performance"], "threshold": 0.1 }, - "dist/amp4ads-v0.js": { - "approvers": ["ampproject/wg-ads"], + "dist/amp4ads-v0.?(m)js": { + "approvers": ["ampproject/wg-monetization", "ampproject/wg-performance"], "threshold": 0.1 }, - "dist/v0/amp-analytics-0.1.js": { - "approvers": ["ampproject/wg-analytics"], + "dist/v0/amp-analytics-?.?.?(m)js": { + "approvers": ["ampproject/wg-analytics", "ampproject/wg-performance"], "threshold": 0.1 }, - "dist/v0/amp-story-0.1.js": { - "approvers": ["ampproject/wg-stories"], + "dist/v0/amp-story-?.?.?(m)js": { + "approvers": ["ampproject/wg-stories", "ampproject/wg-performance"], "threshold": 0.5 }, - "dist/v0/amp-story-1.0.js": { - "approvers": ["ampproject/wg-stories"], - "threshold": 0.5 + "dist.3p/current-min/vendor/*.?(m)js": { + "approvers": ["ampproject/wg-components", "ampproject/wg-monetization"], + "threshold": 0.1 } } diff --git a/build-system/tasks/bundle-size/README.md b/build-system/tasks/bundle-size/README.md index 6c2d263587d83..77debc5960be3 100644 --- a/build-system/tasks/bundle-size/README.md +++ b/build-system/tasks/bundle-size/README.md @@ -1,21 +1,20 @@ # Bundle Size checks The `APPROVERS.json` file is a listing of compiled AMP runtime and extensions -files. Each rule has two parts: +files in `extglob` syntax. Each rule has two parts: -- `approvers`: list of GitHub teams whose members may approve pull requests that - fail the check -- `threshold`: number of kilobytes by which the brotli-compressed bundle size - can increase before failing the check +- `approvers`: list of GitHub teams whose members may approve pull requests that + fail the check +- `threshold`: number of kilobytes by which the brotli-compressed bundle size + can increase before failing the check Approval from any single member of any of the file's approval-teams is enough to satisfy the check. -This file is dynamically fetched by the [Bundle-Size GitHub App](https://github.com/ampproject/amp-github-apps/tree/master/bundle-size) +This file is dynamically fetched by the [Bundle-Size GitHub App](https://github.com/ampproject/amp-github-apps/tree/main/bundle-size) If a pull request requires a bundle-size approval from more than one _set_ of approvers, it will fall back to the default set of approvers -(`@ampproject/wg-runtime` and `@ampproject/wg-performance`). Members of -`@ampproject/wg-infra` can also approve all bundle size increases or app -failures, but this should only be done when the cause for the failure is an -infrastructure issue. +(`@ampproject/wg-performance`). Members of `@ampproject/wg-infra` can also +approve all bundle size increases or app failures, but this should only be +done when the cause for the failure is an infrastructure issue. diff --git a/build-system/tasks/bundle-size/filesize.json b/build-system/tasks/bundle-size/filesize.json index d0803d63e8ded..b0ca1b72c7453 100644 --- a/build-system/tasks/bundle-size/filesize.json +++ b/build-system/tasks/bundle-size/filesize.json @@ -4,7 +4,11 @@ "dist/*.js", "dist/v0/*-?.?.js", "dist/*.mjs", - "dist/v0/*-?.?.mjs" + "dist/v0/*-?.?.mjs", + "dist.3p/current-min/vendor/*.js", + "dist.3p/current-min/vendor/*.mjs", + "extensions/*/?.?/dist/*.js", + "extensions/*/?.?/dist/*.mjs" ], "trackFormat": ["brotli"] } diff --git a/build-system/tasks/bundle-size/index.js b/build-system/tasks/bundle-size/index.js index a44b7aea54ddc..1a7c865d81f49 100644 --- a/build-system/tasks/bundle-size/index.js +++ b/build-system/tasks/bundle-size/index.js @@ -16,29 +16,28 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); +const fetch = require('node-fetch'); const globby = require('globby'); -const log = require('fancy-log'); const path = require('path'); const url = require('url'); -const util = require('util'); const { + ciPushBranch, + ciRepoSlug, + circleciPrMergeCommit, + isPullRequestBuild, + isPushBuild, +} = require('../../common/ci'); +const { + gitCiMainBaseline, gitCommitHash, - gitTravisMasterBaseline, shortSha, } = require('../../common/git'); -const { - isTravisPullRequestBuild, - isTravisPushBuild, - travisPushBranch, - travisRepoSlug, -} = require('../../common/travis'); const { VERSION: internalRuntimeVersion, } = require('../../compile/internal-version'); -const {cyan, green, red, yellow} = require('ansi-colors'); -const {report, Report} = require('@ampproject/filesize'); - -const requestPost = util.promisify(require('request').post); +const {cyan, red, yellow} = require('../../common/colors'); +const {log, logWithoutTimestamp} = require('../../common/logging'); +const {NoTTYReport, report} = require('@ampproject/filesize'); const filesizeConfigPath = require.resolve('./filesize.json'); const fileGlobs = require(filesizeConfigPath).filesize.track; @@ -51,52 +50,82 @@ const replacementExpression = new RegExp(internalRuntimeVersion, 'g'); /** * Get the brotli bundle sizes of the current build after normalizing the RTV number. * - * @return {Map} the bundle size in KB rounded to 2 decimal + * @return {Promise>} the bundle size in KB rounded to 2 decimal * points. */ async function getBrotliBundleSizes() { + /** @type {Object} */ const bundleSizes = {}; - - log(cyan('brotli'), 'bundle sizes are:'); - await report( + const sizes = await report( filesizeConfigPath, (content) => content.replace(replacementExpression, normalizedRtvNumber), - class extends Report { - update(context) { - const completed = super.getUpdated(context); - for (const complete of completed) { - const [filePath, sizeMap] = complete; - const relativePath = path.relative('.', filePath); - const reportedSize = parseFloat((sizeMap[0][0] / 1024).toFixed(2)); - log(' ', cyan(relativePath) + ':', green(reportedSize + 'KB')); - bundleSizes[relativePath] = reportedSize; - } - } - } + NoTTYReport, + /* silent */ false ); - + for (const size of sizes) { + const [filePath, sizeMap] = size; + const relativePath = path.relative('.', filePath); + const reportedSize = parseFloat((sizeMap[0][0] / 1024).toFixed(2)); + bundleSizes[relativePath] = reportedSize; + } return bundleSizes; } /** - * Store the bundle size of a commit hash in the build artifacts storage + * Checks the response of an operation. Throws if there's an error, and prints + * success messages if not. + * @param {!Response} response + * @param {...string} successMessages + */ +async function checkResponse(response, ...successMessages) { + if (!response.ok) { + throw new Error( + `${response.status} ${response.statusText}: ${await response.text()}` + ); + } else { + log(...successMessages); + } +} + +/** + * Does a JSON POST request. + * @param {string} url + * @param {*} body + * @param {?Object=} options + * @return {Promise} + */ +async function postJson(url, body, options) { + return fetch(url, { + ...options, + headers: { + ...(options && options.headers), + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify(body), + }); +} + +/** + * Store the bundle sizes for a commit hash in the build artifacts storage * repository to the passed value. */ async function storeBundleSize() { - if (!isTravisPushBuild() || travisPushBranch() !== 'master') { + if (!isPushBuild() || ciPushBranch() !== 'main') { log( yellow('Skipping'), cyan('--on_push_build') + ':', - 'this action can only be performed on `master` push builds on Travis' + 'this action can only be performed on main branch push builds during CI' ); return; } - if (travisRepoSlug() !== expectedGitHubRepoSlug) { + if (ciRepoSlug() !== expectedGitHubRepoSlug) { log( yellow('Skipping'), cyan('--on_push_build') + ':', - 'this action can only be performed on Travis builds on the', + 'this action can only be performed during CI builds on the', cyan(expectedGitHubRepoSlug), 'repository' ); @@ -104,61 +133,57 @@ async function storeBundleSize() { } const commitHash = gitCommitHash(); + log('Storing bundle sizes for commit', cyan(shortSha(commitHash)) + '...'); try { - const response = await requestPost({ - uri: url.resolve( + const response = await postJson( + url.resolve( bundleSizeAppBaseUrl, path.join('commit', commitHash, 'store') ), - json: true, - body: { + { token: process.env.BUNDLE_SIZE_TOKEN, bundleSizes: await getBrotliBundleSizes(), - }, - }); - if (response.statusCode < 200 || response.statusCode >= 300) { - throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + response.body - ); - } + } + ); + await checkResponse(response, 'Successfully stored bundle sizes.'); } catch (error) { - log(red('Could not store the bundle size')); - log(red(error)); - process.exitCode = 1; + log(yellow('WARNING:'), 'Could not store bundle sizes'); + logWithoutTimestamp(error); return; } } /** - * Mark a pull request on Travis as skipped, via the AMP bundle-size GitHub App. + * Mark a pull request as skipped, via the AMP bundle-size GitHub App. */ async function skipBundleSize() { - if (isTravisPullRequestBuild()) { + if (isPullRequestBuild()) { const commitHash = gitCommitHash(); + log( + 'Skipping bundle size reporting for commit', + cyan(shortSha(commitHash)) + '...' + ); try { - const response = await requestPost( + const response = await fetch( url.resolve( bundleSizeAppBaseUrl, path.join('commit', commitHash, 'skip') - ) + ), + {method: 'POST'} + ); + await checkResponse( + response, + 'Successfully skipped bundle size reporting.' ); - if (response.statusCode < 200 || response.statusCode >= 300) { - throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + response.body - ); - } } catch (error) { - log(red('Could not report a skipped pull request')); - log(red(error)); - process.exitCode = 1; + log(yellow('WARNING:'), 'Could not skip bundle size reporting'); + logWithoutTimestamp(error); return; } } else { log( - yellow( - 'Not marking this pull request to skip because that can only be ' + - 'done on Travis' - ) + yellow('WARNING'), + 'Pull requests can be marked as skipped only during CI builds' ); } } @@ -167,51 +192,59 @@ async function skipBundleSize() { * Report the size to the bundle-size GitHub App, to determine size changes. */ async function reportBundleSize() { - if (isTravisPullRequestBuild()) { - const baseSha = gitTravisMasterBaseline(); - const commitHash = gitCommitHash(); + if (isPullRequestBuild()) { + const headSha = gitCommitHash(); + const baseSha = gitCiMainBaseline(); + const mergeSha = circleciPrMergeCommit(); + log( + 'Reporting bundle sizes for commit', + cyan(shortSha(headSha)), + 'using baseline commit', + cyan(shortSha(baseSha)), + 'and merge commit', + cyan(shortSha(mergeSha)) + '...' + ); try { - const response = await requestPost({ - uri: url.resolve( + const response = await postJson( + url.resolve( bundleSizeAppBaseUrl, - path.join('commit', commitHash, 'report') + path.join('commit', headSha, 'report') ), - json: true, - body: { + { baseSha, + mergeSha, bundleSizes: await getBrotliBundleSizes(), - }, - }); - if (response.statusCode < 200 || response.statusCode >= 300) { - throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + response.body - ); - } + } + ); + await checkResponse(response, 'Successfully reported bundle sizes.'); } catch (error) { - log(red('Could not report the bundle size of this pull request')); - log(red(error)); - process.exitCode = 1; + log( + yellow('WARNING:'), + 'Could not report the bundle sizes for this pull request' + ); + logWithoutTimestamp(error); return; } } else { log( - yellow( - 'Not reporting the bundle size of this pull request because ' + - 'that can only be done on Travis' - ) + yellow('WARNING:'), + 'Bundle sizes from pull requests can be reported only during CI builds' ); } } +/** + * @return {Promise} + */ async function getLocalBundleSize() { if (globby.sync(fileGlobs).length === 0) { log('Could not find runtime files.'); - log('Run', cyan('gulp dist --noextensions'), 'and re-run this task.'); + log('Run', cyan('amp dist --noextensions'), 'and re-run this task.'); process.exitCode = 1; return; } else { log( - 'Computing bundle size for version', + 'Computing bundle sizes for version', cyan(internalRuntimeVersion), 'at commit', cyan(shortSha(gitCommitHash())) + '.' @@ -220,17 +253,20 @@ async function getLocalBundleSize() { await getBrotliBundleSizes(); } +/** + * @return {Promise} + */ async function bundleSize() { if (argv.on_skipped_build) { - return await skipBundleSize(); + return skipBundleSize(); } else if (argv.on_push_build) { - return await storeBundleSize(); + return storeBundleSize(); } else if (argv.on_pr_build) { - return await reportBundleSize(); + return reportBundleSize(); } else if (argv.on_local_build) { - return await getLocalBundleSize(); + return getLocalBundleSize(); } else { - log(red('Called'), cyan('gulp bundle-size'), red('with no task.')); + log(red('Called'), cyan('amp bundle-size'), red('with no task.')); process.exitCode = 1; } } @@ -243,11 +279,10 @@ bundleSize.description = 'Checks if the minified AMP binary has exceeded its size cap'; bundleSize.flags = { 'on_push_build': - ' Store bundle size in AMP build artifacts repo ' + - '(also implies --on_pr_build)', - 'on_pr_build': ' Report the bundle size of this pull request to GitHub', + 'Store bundle sizes in the AMP build artifacts repo for main branch builds', + 'on_pr_build': 'Report the bundle sizes for this pull request to GitHub', 'on_skipped_build': - " Set the status of this pull request's bundle " + + "Set the status of this pull request's bundle " + 'size check in GitHub to `skipped`', - 'on_local_build': ' Compute the bundle size of the locally built runtime', + 'on_local_build': 'Compute bundle sizes for the locally built runtime', }; diff --git a/build-system/tasks/caches-json.js b/build-system/tasks/caches-json.js index 346b9ee33fbcc..3ad0212b68f06 100644 --- a/build-system/tasks/caches-json.js +++ b/build-system/tasks/caches-json.js @@ -15,57 +15,42 @@ */ 'use strict'; -const colors = require('ansi-colors'); -const gulp = require('gulp'); -const log = require('fancy-log'); -const through2 = require('through2'); +const path = require('path'); +const {cyan, green, red} = require('../common/colors'); +const {log, logLocalDev} = require('../common/logging'); -const expectedCaches = ['google']; - -const cachesJsonPath = 'build-system/global-configs/caches.json'; +const expectedCaches = ['google', 'bing']; +const cachesJsonPath = '../global-configs/caches.json'; /** - * Fail if build-system/global-configs/caches.json is missing some expected - * caches. - * @return {!Promise} + * Entry point for amp caches-jason. */ async function cachesJson() { - return gulp.src([cachesJsonPath]).pipe( - through2.obj(function (file) { - let obj; - try { - obj = JSON.parse(file.contents.toString()); - } catch (e) { - log( - colors.yellow( - `Could not parse ${cachesJsonPath}. ` + - 'This is most likely a fatal error that ' + - 'will be found by checkValidJson' - ) - ); - return; - } - const foundCaches = []; - for (const foundCache of obj.caches) { - foundCaches.push(foundCache.id); - } - for (const cache of expectedCaches) { - if (!foundCaches.includes(cache)) { - log( - colors.red( - 'Missing expected cache "' + cache + `" in ${cachesJsonPath}` - ) - ); - process.exitCode = 1; - } - } - }) - ); + const filename = path.basename(cachesJsonPath); + let jsonContent; + try { + jsonContent = require(cachesJsonPath); + } catch (e) { + log(red('ERROR:'), 'Could not parse', cyan(filename)); + process.exitCode = 1; + return; + } + const foundCaches = []; + for (const foundCache of jsonContent.caches) { + foundCaches.push(foundCache.id); + } + for (const cache of expectedCaches) { + if (foundCaches.includes(cache)) { + logLocalDev(green('✔'), 'Found', cyan(cache), 'in', cyan(filename)); + } else { + log(red('✖'), 'Missing', cyan(cache), 'in', cyan(filename)); + process.exitCode = 1; + } + } } module.exports = { cachesJson, }; -cachesJson.description = - 'Check that some expected caches are included in caches.json.'; +cachesJson.description = 'Check that caches.json contains all expected caches.'; diff --git a/build-system/tasks/check-analytics-vendors-list.js b/build-system/tasks/check-analytics-vendors-list.js new file mode 100644 index 0000000000000..321d1c3ce9bb1 --- /dev/null +++ b/build-system/tasks/check-analytics-vendors-list.js @@ -0,0 +1,105 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const globby = require('globby'); +const {basename} = require('path'); +const {readFile} = require('fs-extra'); +const {writeDiffOrFail} = require('../common/diff'); + +/** + * Ensures that this Markdown file contains a section for every matching vendor. + */ +const filepath = 'extensions/amp-analytics/analytics-vendors-list.md'; + +/** + * Vendor JSON files are found here. + * Their basename without extension is their type listed in their block. + */ +const vendorsGlob = 'extensions/amp-analytics/0.1/vendors/*.json'; + +const blockHeading = (heading, name) => + `### ${heading}\n\nType attribute value: \`${name}\``; + +const blockRegExp = (name) => + new RegExp( + `()?`, + 'm' + ); + +/** + * Checks or updates analytics vendors list. + */ +async function checkAnalyticsVendorsList() { + const vendors = globby + .sync(vendorsGlob) + .map((path) => basename(path, '.json')) + .sort(); + + // Keeps list sorted if so, allows: + // - arbitrary sections in-between. + // - intentionally commented out blocks + let tentative = await readFile(filepath, 'utf-8'); + let previousBlock; + for (const vendor of vendors) { + const match = tentative.match(blockRegExp(vendor)); + if (match) { + previousBlock = match[0].trim(); + continue; + } + // "d* n*t s*bmit" has to be split to prevent this file from blocking CI + // (unlike the resulting change, which should block it if unaddressed). + const block = + `${blockHeading(vendor, vendor)}\n\nDO NOT` + + ` SUBMIT: Add a paragraph to describe ${vendor}.`; + // If there's no previously found block, the name is lexicographically lower, + // so inserting the new block at the beginning keeps the list sorted. + tentative = previousBlock + ? tentative.replace(previousBlock, `${previousBlock}\n\n${block}`) + : tentative.replace(blockRegExp('.+'), `${block}\n\n\$&`); + previousBlock = block; + } + + // Remove those no longer on vendor-requests.json + let match; + const anyVendorRegExp = new RegExp(blockRegExp('(.+)').source, 'gm'); + while ((match = anyVendorRegExp.exec(tentative)) !== null) { + const fullMatch = match[0]; + /** @type {string} */ + const nameMatch = /** @type {*} */ (match[2]); + const name = nameMatch + .split(/[,\s]+/) + .shift() + .replace(/[`"']/g, ''); + if (!vendors.includes(name)) { + tentative = tentative.replace(fullMatch, ''); + } + } + + await writeDiffOrFail('check-analytics-vendors-list', filepath, tentative); +} + +module.exports = { + checkAnalyticsVendorsList, +}; + +checkAnalyticsVendorsList.description = `Checks or updates list on ${filepath}`; + +checkAnalyticsVendorsList.flags = { + 'fix': 'Write to file', +}; diff --git a/build-system/tasks/check-asserts.js b/build-system/tasks/check-asserts.js new file mode 100644 index 0000000000000..431f7f9b03a1a --- /dev/null +++ b/build-system/tasks/check-asserts.js @@ -0,0 +1,87 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const fs = require('fs').promises; +const {cyan, green, red} = require('../common/colors'); +const {log} = require('../common/logging'); + +const DEV_ASSERT_SENTINEL = '__devAssert_sentinel__'; +const PURE_ASSERT_SENTINEL = 'Assertion failed'; +const UNMINIFIED_JS = './dist/amp.js'; +const MINIFIED_JS = './dist/v0.js'; + +/** + * Checks that a provided sentinel is/is not contained in a file. + * @param {string} filePath JS binary to check + * @param {Record} sentinels map from sentinels to whether or not + * they should be present + * @throws if a sentinel isn't/is present when it should/shouldn't be + */ +async function checkSentinels(filePath, sentinels) { + const fileContents = await fs.readFile(filePath, 'utf8'); + + for (const [sentinel, shouldBePresent] of Object.entries(sentinels)) { + const isPresent = fileContents.includes(sentinel); + + if (isPresent != shouldBePresent) { + log( + red('ERROR:'), + cyan(filePath), + shouldBePresent ? 'does not contain' : 'contains', + `${cyan(sentinel)}.`, + 'Something may be wrong with assertions or compilation.' + ); + throw new Error('Assertion sentinel check failed'); + } + + log( + green('SUCCESS:'), + cyan(sentinel), + shouldBePresent ? 'found in' : 'not found in', + cyan(filePath) + ); + } +} + +/** + * Checks that the file at the provided path does not include devAssert. + * This works as follows: + * - The devAssert function includes a sentinel string message behind a + * conditional that is always false, but not DCE-able. + * - In minified code, devAssert should be removed entirely, so the sentinel + * will not be present. + * - In unminified code, it should remain present but never execute. + * - Even when devAssert is DCE'd, pureAssert still includes the base assertion + * logic, so the 'Assertion failed' string will be present. + */ +async function checkAsserts() { + await checkSentinels(UNMINIFIED_JS, { + [PURE_ASSERT_SENTINEL]: true, + [DEV_ASSERT_SENTINEL]: true, + }); + await checkSentinels(MINIFIED_JS, { + [PURE_ASSERT_SENTINEL]: true, + [DEV_ASSERT_SENTINEL]: false, + }); +} + +module.exports = { + checkAsserts, +}; + +checkAsserts.description = + "Checks amp.js and v0.js to validate that assertions are DCE'd correctly"; diff --git a/build-system/tasks/check-build-system.js b/build-system/tasks/check-build-system.js new file mode 100644 index 0000000000000..4a43ffd976271 --- /dev/null +++ b/build-system/tasks/check-build-system.js @@ -0,0 +1,54 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const globby = require('globby'); +const path = require('path'); +const {cyan, green} = require('../common/colors'); +const {execOrThrow} = require('../common/exec'); +const {log} = require('../common/logging'); +const {updateSubpackages} = require('../common/update-packages'); + +/** + * Helper that updates build-system subpackages so their types can be verified. + * Skips NPM checks during CI (already done while running each task). + */ +async function updateBuildSystemSubpackages() { + const packageFiles = globby.sync('build-system/tasks/*/package.json'); + for (const packageFile of packageFiles) { + const packageDir = path.dirname(packageFile); + await updateSubpackages(packageDir, /* skipNpmChecks */ true); + } +} + +/** + * Performs type checking on the /build-system directory using TypeScript. + * Configuration is defined in /build-system/tsconfig.json. + */ +async function checkBuildSystem() { + await updateBuildSystemSubpackages(); + log('Checking types in', cyan('build-system') + '...'); + execOrThrow( + 'npx -p typescript tsc --project ./build-system/tsconfig.json', + 'Type checking failed' + ); + log(green('SUCCESS:'), 'No type errors in', cyan('build-system') + '.'); +} + +checkBuildSystem.description = + 'Check source code in build-system/ for JS type errors.'; + +module.exports = { + checkBuildSystem, +}; diff --git a/build-system/tasks/check-exact-versions.js b/build-system/tasks/check-exact-versions.js index c42e935413cdc..44cf1447a0b30 100644 --- a/build-system/tasks/check-exact-versions.js +++ b/build-system/tasks/check-exact-versions.js @@ -15,57 +15,61 @@ */ 'use strict'; -const colors = require('ansi-colors'); -const log = require('fancy-log'); -const {getStderr} = require('../common/exec'); -const {gitDiffFileMaster} = require('../common/git'); -const {isTravisBuild} = require('../common/travis'); +const fs = require('fs-extra'); +const globby = require('globby'); +const semver = require('semver'); +const {cyan, green, red} = require('../common/colors'); +const {gitDiffFileMain} = require('../common/git'); +const {log, logLocalDev, logWithoutTimestamp} = require('../common/logging'); -const PACKAGE_JSON_PATHS = [ - 'package.json', - 'build-system/tasks/e2e/package.json', - 'build-system/tasks/visual-diff/package.json', - 'build-system/tasks/storybook/package.json', -]; +/** + * @param {string} file + * @return {boolean} + */ +function check(file) { + const json = fs.readJsonSync(file, 'utf8'); + + // We purposfully ignore peerDependencies here, because that's that's for the + // consumer to decide. + const keys = ['dependencies', 'devDependencies', 'optionalDependencies']; -const checkerExecutable = 'npx npm-exact-versions'; + for (const key of keys) { + const deps = json[key]; + for (const dep in deps) { + const version = deps[dep]; + if (!semver.clean(version)) { + return false; + } + } + } + return true; +} /** * Makes sure all package.json files in the repo use exact versions. * @return {!Promise} */ async function checkExactVersions() { - let success = true; - PACKAGE_JSON_PATHS.forEach((file) => { - const checkerCmd = `${checkerExecutable} --path ${file}`; - const err = getStderr(checkerCmd); - if (err) { + const packageJsonFiles = globby.sync(['**/package.json', '!**/node_modules']); + packageJsonFiles.forEach((file) => { + if (check(file)) { + logLocalDev( + green('SUCCESS:'), + 'All packages in', + cyan(file), + 'have exact versions.' + ); + } else { log( - colors.red('ERROR:'), + red('ERROR:'), 'One or more packages in', - colors.cyan(file), + cyan(file), 'do not have an exact version.' ); - console.log(gitDiffFileMaster(file)); - success = false; - } else { - if (!isTravisBuild()) { - log( - colors.green('SUCCESS:'), - 'All packages in', - colors.cyan(file), - 'have exact versions.' - ); - } + logWithoutTimestamp(gitDiffFileMain(file)); + throw new Error('Check failed'); } }); - if (success) { - return Promise.resolve(); - } else { - const reason = new Error('Check failed'); - reason.showStack = false; - return Promise.reject(reason); - } } module.exports = { @@ -73,4 +77,4 @@ module.exports = { }; checkExactVersions.description = - 'Checks that all packages in package.json use exact versions.'; + 'Checks that all package.json files in the repo use exact versions.'; diff --git a/build-system/tasks/check-invalid-whitespaces.js b/build-system/tasks/check-invalid-whitespaces.js new file mode 100644 index 0000000000000..9232e6b48892a --- /dev/null +++ b/build-system/tasks/check-invalid-whitespaces.js @@ -0,0 +1,70 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'using strict'; + +const {getFilesToCheck} = require('../common/utils'); +const {getStdout} = require('../common/process'); +const {green, red} = require('../common/colors'); +const {invalidWhitespaceGlobs} = require('../test-configs/config'); +const {log, logLocalDev, logWithoutTimestamp} = require('../common/logging'); + +/** + * Runs the check on the given list of files and prints results. Uses the + * built-in functionality of `git diff --check` to generate results. Checks + * entire files by comparing their contents against an empty commit. + * + * @param {Array} filesToCheck + */ +function runCheck(filesToCheck) { + logLocalDev(green('Checking files for invalid whitespaces...')); + const fileList = filesToCheck.join(' '); + const emptyCommit = getStdout('git hash-object -t tree /dev/null').trim(); + const checkFileCmd = `git -c color.ui=always diff ${emptyCommit} --check ${fileList}`; + const result = getStdout(checkFileCmd).trim(); + if (result.length) { + logWithoutTimestamp(result); + log(red('ERROR:'), 'Please fix the files listed above.'); + process.exitCode = 1; + } else { + log(green('SUCCESS:'), 'No invalid whitespaces found.'); + } +} + +/** + * Checks multiple kinds of files for invalid whitespaces. + */ +function checkInvalidWhitespaces() { + const filesToCheck = getFilesToCheck( + invalidWhitespaceGlobs, + {dot: true}, + '.gitignore' + ); + if (filesToCheck.length == 0) { + return; + } + runCheck(filesToCheck); +} + +checkInvalidWhitespaces.description = + 'Checks different types of files for invalid whitespaces.'; +checkInvalidWhitespaces.flags = { + 'files': 'Checks just the specified files', + 'local_changes': 'Checks just the files changed in the local branch', +}; + +module.exports = { + checkInvalidWhitespaces, +}; diff --git a/build-system/tasks/check-links.js b/build-system/tasks/check-links.js index 2f8284f8ed319..4b502d1efabd7 100644 --- a/build-system/tasks/check-links.js +++ b/build-system/tasks/check-links.js @@ -16,18 +16,16 @@ 'use strict'; const fs = require('fs-extra'); -const log = require('fancy-log'); const markdownLinkCheck = require('markdown-link-check'); const path = require('path'); +const {cyan, green, red, yellow} = require('../common/colors'); const {getFilesToCheck, usesFilesOrLocalChanges} = require('../common/utils'); -const {gitDiffAddedNameOnlyMaster} = require('../common/git'); -const {green, cyan, red, yellow} = require('ansi-colors'); -const {isTravisBuild} = require('../common/travis'); +const {gitDiffAddedNameOnlyMain} = require('../common/git'); const {linkCheckGlobs} = require('../test-configs/config'); -const {maybeUpdatePackages} = require('./update-packages'); +const {log, logLocalDev} = require('../common/logging'); const LARGE_REFACTOR_THRESHOLD = 20; -const GITHUB_BASE_PATH = 'https://github.com/ampproject/amphtml/blob/master/'; +const GITHUB_BASE_PATH = 'https://github.com/ampproject/amphtml/blob/main/'; let filesIntroducedByPr; @@ -35,11 +33,10 @@ let filesIntroducedByPr; * Checks for dead links in .md files passed in via --files or --local_changes. */ async function checkLinks() { - maybeUpdatePackages(); if (!usesFilesOrLocalChanges('check-links')) { return; } - const filesToCheck = getFilesToCheck(linkCheckGlobs); + const filesToCheck = getFilesToCheck(linkCheckGlobs, {dot: true}); if (filesToCheck.length == 0) { return; } @@ -47,10 +44,8 @@ async function checkLinks() { log(green('INFO:'), 'Skipping check because this is a large refactor.'); return; } - if (!isTravisBuild()) { - log(green('Starting checks...')); - } - filesIntroducedByPr = gitDiffAddedNameOnlyMaster(); + logLocalDev(green('Starting checks...')); + filesIntroducedByPr = gitDiffAddedNameOnlyMain(); const results = await Promise.all(filesToCheck.map(checkLinksInFile)); reportResults(results); } @@ -58,7 +53,7 @@ async function checkLinks() { /** * Reports results after all markdown files have been checked. * - * @param {!Array} results + * @param {!Array<{file: string, containsDeadLinks: boolean}>} results */ function reportResults(results) { const filesWithDeadLinks = results @@ -72,7 +67,7 @@ function reportResults(results) { ); log( yellow('NOTE 1:'), - "Valid links that don't resolve on Travis can be ignored via", + "Valid links that don't resolve during CI can be ignored via", cyan('ignorePatterns'), 'in', cyan('build-system/tasks/check-links.js') + '.' @@ -107,7 +102,7 @@ function isLinkToFileIntroducedByPR(link) { * Checks a given markdown file for dead links. * * @param {string} file - * @return {!Promise} + * @return {!Promise<{file: string, containsDeadLinks: boolean}>} */ function checkLinksInFile(file) { let markdown = fs.readFileSync(file).toString(); @@ -120,10 +115,18 @@ function checkLinksInFile(file) { // Relative links start at the markdown file's path. baseUrl: 'file://' + path.dirname(path.resolve(file)), ignorePatterns: [ - // Localhost links don't work unless a `gulp` server is running. + // Localhost links don't work unless a `amp` server is running. {pattern: /localhost/}, + // codepen returns a 503 for these link checks + {pattern: /https:\/\/codepen.*/}, + // GitHub PRs and Issues can be assumed to exist + { + pattern: + /https:\/\/github.com\/ampproject\/amphtml\/(pull|issue)\/d+.*/, + }, // Templated links are merely used to generate other markdown files. {pattern: /\$\{[a-z]*\}/}, + {pattern: /https:.*?__component_name\w*__/}, ], }; @@ -149,14 +152,10 @@ function checkLinksInFile(file) { } switch (status) { case 'alive': - if (!isTravisBuild()) { - log(`[${green('✔')}] ${link}`); - } + logLocalDev(`[${green('✔')}] ${link}`); break; case 'ignored': - if (!isTravisBuild()) { - log(`[${yellow('•')}] ${link}`); - } + logLocalDev(`[${yellow('•')}] ${link}`); break; case 'dead': containsDeadLinks = true; @@ -180,6 +179,6 @@ module.exports = { checkLinks.description = 'Detects dead links in markdown files'; checkLinks.flags = { - 'files': ' Checks only the specified files', - 'local_changes': ' Checks just the files changed in the local branch', + 'files': 'Checks only the specified files', + 'local_changes': 'Checks just the files changed in the local branch', }; diff --git a/build-system/tasks/check-owners.js b/build-system/tasks/check-owners.js index 435a48a729db3..4a46b6eb1e51e 100644 --- a/build-system/tasks/check-owners.js +++ b/build-system/tasks/check-owners.js @@ -15,37 +15,33 @@ */ /** - * @fileoverview This file implements the `gulp check-owners` task, which checks + * @fileoverview This file implements the `amp check-owners` task, which checks * all OWNERS files in the repo for correctness, as determined by the parsing * API provided by the AMP owners bot. */ 'use strict'; +const fetch = require('node-fetch'); const fs = require('fs-extra'); const JSON5 = require('json5'); -const log = require('fancy-log'); -const request = require('request'); -const util = require('util'); -const {cyan, red, green} = require('ansi-colors'); +const {cyan, green, red} = require('../common/colors'); const {getFilesToCheck, usesFilesOrLocalChanges} = require('../common/utils'); -const {isTravisBuild} = require('../common/travis'); - -const requestPost = util.promisify(request.post); +const {log, logLocalDev} = require('../common/logging'); const OWNERS_SYNTAX_CHECK_URI = 'http://ampproject-owners-bot.appspot.com/v0/syntax'; /** * Checks OWNERS files for correctness using the owners bot API. - * The cumulative result is returned to the `gulp` process via process.exitCode + * The cumulative result is returned to the `amp` process via process.exitCode * so that all OWNERS files can be checked / fixed. */ async function checkOwners() { if (!usesFilesOrLocalChanges('check-owners')) { return; } - const filesToCheck = getFilesToCheck('**/OWNERS'); + const filesToCheck = getFilesToCheck(['**/OWNERS']); for (const file of filesToCheck) { await checkFile(file); } @@ -65,29 +61,30 @@ async function checkFile(file) { const contents = fs.readFileSync(file, 'utf8').toString(); try { JSON5.parse(contents); - if (!isTravisBuild()) { - log(green('SUCCESS:'), 'No errors in', cyan(file)); - } + logLocalDev(green('SUCCESS:'), 'No errors in', cyan(file)); } catch { log(red('FAILURE:'), 'Found errors in', cyan(file)); process.exitCode = 1; } try { - const response = await requestPost({ - uri: OWNERS_SYNTAX_CHECK_URI, - json: true, - body: {path: file, contents}, + const response = await fetch(OWNERS_SYNTAX_CHECK_URI, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({path: file, contents}), }); - if (response.statusCode < 200 || response.statusCode >= 300) { + if (!response.ok) { log(red('ERROR:'), 'Could not reach the owners syntax check API'); throw new Error( - `${response.statusCode} ${response.statusMessage}: ` + response.body + `${response.status} ${response.statusText}: ${await response.text()}` ); } - const {requestErrors, fileErrors, rules} = response.body; + const {fileErrors, requestErrors, rules} = await response.json(); if (requestErrors) { requestErrors.forEach((err) => log(red(err))); @@ -103,7 +100,7 @@ async function checkFile(file) { cyan(file), 'successfully; produced', cyan(rules.length), - 'rules.' + 'rule(s).' ); } catch (error) { log(red('FAILURE:'), error); @@ -117,6 +114,6 @@ module.exports = { checkOwners.description = 'Checks all OWNERS files in the repo for correctness'; checkOwners.flags = { - 'files': ' Checks only the specified OWNERS files', - 'local_changes': ' Checks just the OWNERS files changed in the local branch', + 'files': 'Checks only the specified OWNERS files', + 'local_changes': 'Checks just the OWNERS files changed in the local branch', }; diff --git a/build-system/tasks/check-renovate-config.js b/build-system/tasks/check-renovate-config.js index 14ba1792528f3..7d40baa9886d1 100644 --- a/build-system/tasks/check-renovate-config.js +++ b/build-system/tasks/check-renovate-config.js @@ -15,7 +15,7 @@ */ /** - * @fileoverview This file implements the `gulp check-renovate-config` task, + * @fileoverview This file implements the `amp check-renovate-config` task, * which validates the Renovate configuration file using Renovate's provided * config validator. The output of this built-in validator is shown below. * @@ -49,19 +49,19 @@ 'use strict'; -const log = require('fancy-log'); -const {cyan, red, green} = require('ansi-colors'); -const {getOutput} = require('../common/exec'); +const {cyan, green, red} = require('../common/colors'); +const {getOutput} = require('../common/process'); +const {log} = require('../common/logging'); /** * Checks Renovate config for correctness using the validator provided by the * `renovate` package. - * The cumulative result is returned to the `gulp` process via process.exitCode + * The cumulative result is returned to the `amp` process via process.exitCode * so that all OWNERS files can be checked / fixed. */ async function checkRenovateConfig() { const {status, stdout} = getOutput( - 'node_modules/renovate/dist/config-validator.js' + 'npx -q -p renovate renovate-config-validator' ); const [configFile] = stdout.match(/(?<=Validating )\S+/); diff --git a/build-system/tasks/check-sourcemaps.js b/build-system/tasks/check-sourcemaps.js index c624054cd4d30..1d9d22987f81f 100644 --- a/build-system/tasks/check-sourcemaps.js +++ b/build-system/tasks/check-sourcemaps.js @@ -17,33 +17,25 @@ const argv = require('minimist')(process.argv.slice(2)); const fs = require('fs'); -const log = require('fancy-log'); -const {cyan, green, red} = require('ansi-colors'); +const {cyan, green, red} = require('../common/colors'); const {decode} = require('sourcemap-codec'); const {execOrDie} = require('../common/exec'); +const {log} = require('../common/logging'); // Compile related constants -const distWithSourcemapsCmd = 'gulp dist --core_runtime_only --full_sourcemaps'; +const distWithSourcemapsCmd = 'amp dist --core_runtime_only --full_sourcemaps'; const v0JsMap = 'dist/v0.js.map'; +const distEsmWithSourcemapsCmd = + 'amp dist --core_runtime_only --full_sourcemaps --esm'; +const v0MjsMap = 'dist/v0.mjs.map'; // Sourcemap URL related constants const sourcemapUrlMatcher = 'https://raw.githubusercontent.com/ampproject/amphtml/\\d{13}/'; // Mapping related constants -const expectedFirstLineFile = 'src/internal-version.js'; -const expectedFirstLineCode = 'export function internalRuntimeVersion() {'; - -/** - * Throws an error with the given message - * - * @param {string} message - */ -function throwError(message) { - const err = new Error(message); - err.showStack = false; - throw err; -} +const expectedFirstLineFile = 'src/polyfills/abort-controller.js'; // First file that is compiled into v0.js. +const expectedFirstLineCode = 'class AbortController {'; // First line of code in that file. /** * Build runtime with sourcemaps if needed. @@ -52,36 +44,39 @@ function maybeBuild() { if (!argv.nobuild) { log('Compiling', cyan('v0.js'), 'with full sourcemaps...'); execOrDie(distWithSourcemapsCmd, {'stdio': 'ignore'}); + log('Compiling', cyan('v0.mjs'), 'with full sourcemaps...'); + execOrDie(distEsmWithSourcemapsCmd, {'stdio': 'ignore'}); } } /** * Verifies that the sourcemap file exists, and returns its contents. - * + * @param {string} map The map filepath to check * @return {!Object} */ -function getSourcemapJson() { - if (!fs.existsSync(v0JsMap)) { - log(red('ERROR:'), 'Could not find', cyan(v0JsMap)); - throwError('Could not find sourcemap file'); +function getSourcemapJson(map) { + if (!fs.existsSync(map)) { + log(red('ERROR:'), 'Could not find', cyan(map)); + throw new Error(`Could not find sourcemap file '${map}'`); } - return JSON.parse(fs.readFileSync(v0JsMap, 'utf8')); + return JSON.parse(fs.readFileSync(map, 'utf8')); } /** * Verifies that a correctly formatted sourcemap URL is present in v0.js.map. * * @param {!Object} sourcemapJson + * @param {string} map The map filepath to check */ -function checkSourcemapUrl(sourcemapJson) { - log('Inspecting', cyan('sourceRoot'), 'in', cyan(v0JsMap) + '...'); +function checkSourcemapUrl(sourcemapJson, map) { + log('Inspecting', cyan('sourceRoot'), 'in', cyan(map) + '...'); if (!sourcemapJson.sourceRoot) { log(red('ERROR:'), 'Could not find', cyan('sourceRoot')); - throwError('Could not find sourcemap URL'); + throw new Error('Could not find sourcemap URL'); } if (!sourcemapJson.sourceRoot.match(sourcemapUrlMatcher)) { log(red('ERROR:'), cyan(sourcemapJson.sourceRoot), 'is badly formatted'); - throwError('Badly formatted sourcemap URL'); + throw new Error('Badly formatted sourcemap URL'); } } @@ -89,13 +84,15 @@ function checkSourcemapUrl(sourcemapJson) { * Verifies all the paths in the sources field are as expected. * * @param {!Object} sourcemapJson + * @param {string} map The map filepath to check */ -function checkSourcemapSources(sourcemapJson) { - log('Inspecting', cyan('sources'), 'in', cyan(v0JsMap) + '...'); +function checkSourcemapSources(sourcemapJson, map) { + log('Inspecting', cyan('sources'), 'in', cyan(map) + '...'); if (!sourcemapJson.sources) { log(red('ERROR:'), 'Could not find', cyan('sources')); - throwError('Could not find sources array'); + throw new Error('Could not find sources array'); } + /** @type {string[]} */ const invalidSources = sourcemapJson.sources .filter((source) => !source.match(/\[.*\]/)) // Ignore non-path sources '[...]' .filter((source) => !fs.existsSync(source)); // All source paths should exist @@ -106,7 +103,7 @@ function checkSourcemapSources(sourcemapJson) { cyan('sources') + ':', cyan(invalidSources.join(', ')) ); - throwError('Invalid paths in sources array'); + throw new Error('Invalid paths in sources array'); } } @@ -114,68 +111,73 @@ function checkSourcemapSources(sourcemapJson) { * Performs a sanity check on the mappings field in the sourcemap file. * * Today, the first line of amp.js after resolving imports comes from - * src/internal-version.js. (The import chain is src/amp.js -> src/polyfills.js - * -> src/mode.js -> src/internal-version.js.) This sequence is unlikely to - * change, so we can use it as a sentinel value. Here is the process: + * src/polyfills/array-includes.js. (The import chain is src/amp.js -> + * src/polyfills/index.js -> src/polyfills/array-includes.js.) This sequence + * changes rarely, so we can use it as a sentinel value. Here is the process: * * 1. Decode the 'mappings' field into a 3d array using 'sourcemap-codec'. * 2. Extract the mapping for the first line of code in minified v0.js. * 3. Compute the name of the source file that corresponds to this line. * 4. Read the source file and extract the corresponding line of code. - * 5. Check if the filename and the line of code match expected sentinel values. + * 5. Check if the filename, line of code, and column match expected sentinel values. * * @param {!Object} sourcemapJson + * @param {string} map The map filepath to check */ -function checkSourcemapMappings(sourcemapJson) { - log('Inspecting', cyan('mappings'), 'in', cyan(v0JsMap) + '...'); +function checkSourcemapMappings(sourcemapJson, map) { + log('Inspecting', cyan('mappings'), 'in', cyan(map) + '...'); if (!sourcemapJson.mappings) { log(red('ERROR:'), 'Could not find', cyan('mappings')); - throwError('Could not find mappings array'); + throw new Error('Could not find mappings array'); } // Zeroth sub-array corresponds to ';' and has no mappings. // See https://www.npmjs.com/package/sourcemap-codec#usage const firstLineMapping = decode(sourcemapJson.mappings)[1][0]; - const [ - generatedCodeColumn, - sourceIndex, - sourceCodeLine, - sourceCodeColumn, - ] = firstLineMapping; + const [, sourceIndex = 0, sourceCodeLine = 0, sourceCodeColumn] = + firstLineMapping; const firstLineFile = sourcemapJson.sources[sourceIndex]; const contents = fs.readFileSync(firstLineFile, 'utf8').split('\n'); - const firstLineCode = contents[sourceCodeLine]; + const firstLineCode = contents[sourceCodeLine].slice(sourceCodeColumn); + const helpMessage = + 'If this change is intentional, update the mapping related constants in ' + + cyan('build-system/tasks/check-sourcemaps.js') + + '.'; if (firstLineFile != expectedFirstLineFile) { log(red('ERROR:'), 'Found mapping for incorrect file.'); log('Actual:', cyan(firstLineFile)); log('Expected:', cyan(expectedFirstLineFile)); - throwError('Found mapping for incorrect file'); + log(helpMessage); + throw new Error('Found mapping for incorrect file'); } if (firstLineCode != expectedFirstLineCode) { log(red('ERROR:'), 'Found mapping for incorrect code.'); log('Actual:', cyan(firstLineCode)); log('Expected:', cyan(expectedFirstLineCode)); - throwError('Found mapping for incorrect code'); - } - if (generatedCodeColumn != 0 || sourceCodeColumn != 0) { - log(red('ERROR:'), 'Found mapping for incorrect (non-zero) column.'); - log('generatedCodeColumn:', cyan(generatedCodeColumn)); - log('sourceCodeColumn:', cyan(sourceCodeColumn)); - throwError('Found mapping for incorrect column'); + log(helpMessage); + throw new Error('Found mapping for incorrect code'); } } +/** + * @param {string} map The map filepath to check + */ +function checkSourceMap(map) { + const sourcemapJson = getSourcemapJson(map); + checkSourcemapUrl(sourcemapJson, map); + checkSourcemapSources(sourcemapJson, map); + checkSourcemapMappings(sourcemapJson, map); +} + /** * Checks sourcemaps generated during minified compilation for correctness. - * Entry point for `gulp check-sourcemaps`. + * Entry point for `amp check-sourcemaps`. */ async function checkSourcemaps() { maybeBuild(); - const sourcemapJson = getSourcemapJson(); - checkSourcemapUrl(sourcemapJson); - checkSourcemapSources(sourcemapJson); - checkSourcemapMappings(sourcemapJson); + checkSourceMap(v0JsMap); + checkSourceMap(v0MjsMap); log(green('SUCCESS:'), 'All sourcemaps checks passed.'); } @@ -186,5 +188,5 @@ module.exports = { checkSourcemaps.description = 'Checks sourcemaps generated during minified compilation for correctness.'; checkSourcemaps.flags = { - 'nobuild': ' Skips building the runtime (checks previously built code)', + 'nobuild': 'Skips building the runtime (checks previously built code)', }; diff --git a/build-system/tasks/check-types.js b/build-system/tasks/check-types.js index 98fdf317b25b4..3bfce07dc30df 100644 --- a/build-system/tasks/check-types.js +++ b/build-system/tasks/check-types.js @@ -14,116 +14,330 @@ * limitations under the License. */ -const log = require('fancy-log'); -const { - checkTypesNailgunPort, - startNailgunServer, - stopNailgunServer, -} = require('./nailgun'); +const argv = require('minimist')(process.argv.slice(2)); +const globby = require('globby'); const { createCtrlcHandler, exitCtrlcHandler, } = require('../common/ctrlcHandler'); +const { + displayLifecycleDebugging, +} = require('../compile/debug-compilation-lifecycle'); const {cleanupBuildDir, closureCompile} = require('../compile/compile'); const {compileCss} = require('./css'); +const {compileJison} = require('./compile-jison'); +const {cyan, green, red, yellow} = require('../common/colors'); const {extensions, maybeInitializeExtensions} = require('./extension-helpers'); -const {maybeUpdatePackages} = require('./update-packages'); +const {logClosureCompilerError} = require('../compile/closure-compile'); +const {log} = require('../common/logging'); +const {typecheckNewServer} = require('../server/typescript-compile'); + +/** + * Files that pass type-checking but don't belong to a passing directory target. + * Note: This is a TEMPORARY holding point during the transition to type-safety. + * @type {!Array} + */ +const PRIDE_FILES_GLOBS = [ + // Core + 'src/core/**/*.js', + + // Runtime + 'build/amp-loader-0.1.css.js', + 'build/ampdoc.css.js', + 'build/ampshared.css.js', + 'src/config.js', + 'src/document-ready.js', + 'src/dom.js', + 'src/exponential-backoff.js', + 'src/format.js', + 'src/history.js', + 'src/internal-version.js', + 'src/json.js', + 'src/log.js', + 'src/mode.js', + 'src/resolved-promise.js', + 'src/time.js', + 'src/types.js', + 'src/url-parse-query-string.js', + 'src/url-try-decode-uri-component.js', + 'src/utils/bytes.js', + 'src/utils/img.js', + + // Third Party + 'third_party/css-escape/css-escape.js', + 'third_party/webcomponentsjs/ShadowCSS.js', + 'node_modules/promise-pjs/package.json', + 'node_modules/promise-pjs/promise.mjs', +]; + +// We provide glob lists for core src/externs since any other targets are +// allowed to depend on core. +const CORE_SRCS_GLOBS = [ + 'src/core/**/*.js', + + // Needed for CSS escape polyfill + 'third_party/css-escape/css-escape.js', +]; + +/** + * Generates a list of source file paths for extensions to type-check + * Must be run after `maybeInitializeExtensions` + * @function + * @return {!Array} + */ +const getExtensionSrcPaths = () => + Object.values(extensions) + .filter((ext) => !ext.noTypeCheck) + .map(({name, version}) => `extensions/${name}/${version}/${name}.js`) + .sort(); + +/** + * The main configuration location to add/edit targets for type checking. + * Properties besides `entryPoints` are passed on to `closureCompile` as + * options. * Values may be objects or functions, as some require initialization + * or filesystem access and shouldn't be run until needed. + * + * When updating type-check targets, `srcGlobs` is the primary value you care + * about. This is a list of source files to include in type-checking. For any + * glob pattern ending in *.js, externs are picked up following the same pattern + * but ending in *.extern.js. Note this only applies to *.js globs, and not + * specific filenames. If just an array of strings is provided instead of an + * object, it is treated as srcGlobs. + * + * @type {Object|Object|function():Object>} + */ +const TYPE_CHECK_TARGETS = { + // Below are targets containing individual directories which are fully passing + // type-checking. Do not remove or disable anything on this list. + // Goal: Remove 'QUIET' from all of them. + // To test a target locally: + // `amp check-types --target=src-foo-bar --warning_level=verbose` + 'src-amp-story-player': { + srcGlobs: ['src/amp-story-player/**/*.js'], + warningLevel: 'QUIET', + }, + 'src-context': ['src/context/**/*.js', ...CORE_SRCS_GLOBS], + 'src-core': CORE_SRCS_GLOBS, + 'src-examiner': ['src/examiner/**/*.js'], + 'src-experiments': ['src/experiments/**/*.js', ...CORE_SRCS_GLOBS], + 'src-inabox': { + srcGlobs: ['src/inabox/**/*.js'], + warningLevel: 'QUIET', + }, + 'src-polyfills': [ + 'src/polyfills/**/*.js', + // Exclude fetch its dependencies are cleaned up/extracted to core. + '!src/polyfills/fetch.js', + ...CORE_SRCS_GLOBS, + ], + 'src-preact': { + srcGlobs: ['src/preact/**/*.js', 'src/context/**/*.js', ...CORE_SRCS_GLOBS], + warningLevel: 'QUIET', + }, + 'src-purifier': { + srcGlobs: ['src/purifier/**/*.js'], + warningLevel: 'QUIET', + }, + 'src-service': { + srcGlobs: ['src/service/**/*.js'], + warningLevel: 'QUIET', + }, + 'src-utils': { + srcGlobs: ['src/utils/**/*.js'], + warningLevel: 'QUIET', + }, + 'src-web-worker': { + srcGlobs: ['src/web-worker/**/*.js'], + warningLevel: 'QUIET', + }, + + // Opposite of `shame.extern.js`. This target is a catch-all for files that + // are currently passing, but whose parent directories are not fully passing. + // Adding a file or glob here will cause CI to fail if type errors are + // introduced. It is okay to remove a file from this list only when fixing a + // bug for cherry-pick. + 'pride': { + srcGlobs: PRIDE_FILES_GLOBS, + externGlobs: ['build-system/externs/*.extern.js'], + }, + + // Ensures that all files in src and extensions pass the specified set of + // errors. + 'low-bar': { + entryPoints: ['src/amp.js'], + extraGlobs: ['{src,extensions}/**/*.js'], + onError(msg) { + const lowBarErrors = [ + 'JSC_BAD_JSDOC_ANNOTATION', + 'JSC_INVALID_PARAM', + 'JSC_TYPE_PARSE_ERROR', + ]; + const lowBarRegex = new RegExp(lowBarErrors.join('|')); + + const targetErrors = msg + .split('\n') + .filter((s) => lowBarRegex.test(s)) + .join('\n') + .trim(); + + if (targetErrors.length) { + logClosureCompilerError(targetErrors); + throw new Error(`Type-checking failed for target ${cyan('low-bar')}`); + } + }, + }, + + // TODO(#33631): Targets below this point are not expected to pass. + // They can possibly be removed? + 'src': { + entryPoints: [ + 'src/amp.js', + 'src/amp-shadow.js', + 'src/inabox/amp-inabox.js', + 'ads/alp/install-alp.js', + 'ads/inabox/inabox-host.js', + 'src/web-worker/web-worker.js', + ], + extraGlobs: ['src/inabox/*.js', '!node_modules/preact'], + warningLevel: 'QUIET', + }, + 'extensions': () => ({ + entryPoints: getExtensionSrcPaths(), + extraGlobs: ['src/inabox/*.js', '!node_modules/preact'], + warningLevel: 'QUIET', + }), + 'integration': { + entryPoints: '3p/integration.js', + externs: ['ads/ads.extern.js'], + warningLevel: 'QUIET', + }, + 'ampcontext': { + entryPoints: '3p/ampcontext-lib.js', + externs: ['ads/ads.extern.js'], + warningLevel: 'QUIET', + }, + 'iframe-transport-client': { + entryPoints: '3p/iframe-transport-client-lib.js', + externs: ['ads/ads.extern.js'], + warningLevel: 'QUIET', + }, +}; /** - * Dedicated type check path. - * @return {!Promise} + * Produces a list of extern glob patterns from a list of source glob patterns. + * ex. ['src/core/** /*.js'] => ['src/core/** /*.extern.js'] + * @param {!Array} srcGlobs + * @return {!Array} + */ +function externGlobsFromSrcGlobs(srcGlobs) { + return srcGlobs + .filter((glob) => glob.endsWith('*.js')) + .map((glob) => glob.replace(/\*\.js$/, '*.extern.js')); +} + +/** + * Performs closure type-checking on the target provided. + * @param {string} targetName key in TYPE_CHECK_TARGETS + * @return {!Promise} + */ +async function typeCheck(targetName) { + let target = TYPE_CHECK_TARGETS[targetName]; + // Allow targets to be dynamically evaluated + if (typeof target == 'function') { + target = target(); + } + // Allow targets to be specified as just an array of source globs + if (Array.isArray(target)) { + target = {srcGlobs: target}; + } + + if (!target) { + log( + red('ERROR:'), + 'No type-check configuration defined for target', + cyan(targetName) + ); + throw new Error( + `No type-check configuration defined for target ${targetName}` + ); + } + + const {entryPoints = [], srcGlobs = [], externGlobs = [], ...opts} = target; + externGlobs.push(...externGlobsFromSrcGlobs(srcGlobs)); + + // If srcGlobs and externGlobs are defined, determine the externs/extraGlobs + if (srcGlobs.length || externGlobs.length) { + opts.externs = externGlobs.flatMap(globby.sync); + + // Included globs should explicitly exclude any externs + const excludedExterns = externGlobs.map((glob) => `!${glob}`); + opts.extraGlobs = srcGlobs.concat(excludedExterns); + } + + // If no entry point is defined, we want to scan the globs provided without + // injecting extra dependencies. + const noAddDeps = !entryPoints.length; + // If the --warning_level flag is passed explicitly, it takes precedence. + opts.warningLevel = argv.warning_level || opts.warningLevel || 'VERBOSE'; + + // For type-checking, QUIET suppresses all warnings and can't affect the + // resulting status, so there's no point in doing it. + if (opts.warningLevel == 'QUIET') { + log( + yellow('WARNING:'), + 'Warning level for target', + cyan(targetName), + `is set to ${cyan('QUIET')}; skipping` + ); + return; + } + + let errorMsg; + if (target.onError) { + // If an onError handler is defined, steal the output and let onError handle + // logging + opts.logger = (m) => (errorMsg = m); + } + + await closureCompile(entryPoints, './dist', `${targetName}-check-types.js`, { + noAddDeps, + include3pDirectories: !noAddDeps, + includePolyfills: !noAddDeps, + typeCheckOnly: true, + ...opts, + }).catch((error) => { + if (!target.onError) { + throw error; + } + target.onError(errorMsg); + }); + log(green('SUCCESS:'), 'Type-checking passed for target', cyan(targetName)); +} + +/** + * Runs closure compiler's type checker against all AMP code. + * @return {!Promise} */ async function checkTypes() { - maybeUpdatePackages(); const handlerProcess = createCtrlcHandler('check-types'); + + // Prepare build environment process.env.NODE_ENV = 'production'; cleanupBuildDir(); maybeInitializeExtensions(); - const compileSrcs = [ - './src/amp.js', - './src/amp-shadow.js', - './src/inabox/amp-inabox.js', - './ads/alp/install-alp.js', - './ads/inabox/inabox-host.js', - './src/web-worker/web-worker.js', - ]; - const extensionValues = Object.keys(extensions).map(function (key) { - return extensions[key]; - }); - const extensionSrcs = extensionValues - .filter(function (extension) { - return !extension.noTypeCheck; - }) - .map(function (extension) { - return ( - './extensions/' + - extension.name + - '/' + - extension.version + - '/' + - extension.name + - '.js' - ); - }) - .sort(); - return compileCss() - .then(async () => { - await startNailgunServer(checkTypesNailgunPort, /* detached */ false); - }) - .then(() => { - log('Checking types...'); - return Promise.all([ - closureCompile( - compileSrcs.concat(extensionSrcs), - './dist', - 'check-types.js', - { - include3pDirectories: true, - includePolyfills: true, - extraGlobs: ['src/inabox/*.js', '!node_modules/preact'], - typeCheckOnly: true, - } - ), - // Type check 3p/ads code. - closureCompile( - ['./3p/integration.js'], - './dist', - 'integration-check-types.js', - { - externs: ['ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - typeCheckOnly: true, - } - ), - closureCompile( - ['./3p/ampcontext-lib.js'], - './dist', - 'ampcontext-check-types.js', - { - externs: ['ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - typeCheckOnly: true, - } - ), - closureCompile( - ['./3p/iframe-transport-client-lib.js'], - './dist', - 'iframe-transport-client-check-types.js', - { - externs: ['ads/ads.extern.js'], - include3pDirectories: true, - includePolyfills: true, - typeCheckOnly: true, - } - ), - ]); - }) - .then(async () => { - await stopNailgunServer(checkTypesNailgunPort); - }) - .then(() => exitCtrlcHandler(handlerProcess)); + typecheckNewServer(); + await Promise.all([compileCss(), compileJison()]); + + // Use the list of targets if provided, otherwise check all targets + const targets = argv.targets + ? argv.targets.split(/,/) + : Object.keys(TYPE_CHECK_TARGETS); + + log(`Checking types for targets: ${targets.map(cyan).join(', ')}`); + displayLifecycleDebugging(); + + await Promise.all(targets.map(typeCheck)); + exitCtrlcHandler(handlerProcess); } module.exports = { @@ -134,7 +348,9 @@ module.exports = { checkTypes.description = 'Check source code for JS type errors'; checkTypes.flags = { - closure_concurrency: ' Sets the number of concurrent invocations of closure', - disable_nailgun: - " Doesn't use nailgun to invoke closure compiler (much slower)", + closure_concurrency: 'Sets the number of concurrent invocations of closure', + debug: 'Outputs the file contents during compilation lifecycles', + targets: 'Comma-delimited list of targets to type-check', + warning_level: + "Optionally sets closure's warning level to one of [quiet, default, verbose]", }; diff --git a/build-system/tasks/check-video-interface-list.js b/build-system/tasks/check-video-interface-list.js new file mode 100644 index 0000000000000..8e5fb1d77990f --- /dev/null +++ b/build-system/tasks/check-video-interface-list.js @@ -0,0 +1,87 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const globby = require('globby'); +const {getStdout} = require('../common/process'); +const {readFile} = require('fs-extra'); +const {writeDiffOrFail} = require('../common/diff'); + +/** Checks or updates 3rd party video player list on this Markdown file. */ +const filepath = 'docs/spec/amp-video-interface.md'; + +/** Excludes these extensions since they're on a separate list. */ +const excludeGeneric = ['amp-video', 'amp-video-iframe']; + +/** Determines whether a file belongs to an extension that should be listed. */ +const grepJsContent = '@implements {.*VideoInterface}'; + +/** Finds extension files here. */ +const grepJsFiles = 'extensions/**/*.js'; + +/** + * Returns a formatted list entry. + * @param {string} name + * @return {string} + */ +const entry = (name) => + `- [${name}](https://amp.dev/documentation/components/${name}.md)\n`; + +/** + * Generates Markdown list by finding matching extensions. + * @return {string} + */ +const generateList = () => + getStdout( + ['grep -lr', `"${grepJsContent}"`, ...globby.sync(grepJsFiles)].join(' ') + ) + .trim() + .split('\n') + .reduce((list, path) => { + const name = path.substr('extensions/'.length).split('/').shift() ?? ''; + return list + (excludeGeneric.includes(name) ? '' : entry(name)); + }, ''); + +/** + * Returns a RegExp that matches the existing list. + * @return {RegExp} + */ +const getListRegExp = () => + new RegExp( + `(${entry('NAME') + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/\NAME/g, `((?!${excludeGeneric.join('|')})[a-z0-9-]+)`)})+`, + 'gim' + ); + +/** + * Checks or updates 3rd party video player list. + */ +async function checkVideoInterfaceList() { + const current = await readFile(filepath, 'utf-8'); + const tentative = current.replace(getListRegExp(), generateList()); + if (current !== tentative) { + await writeDiffOrFail('check-video-interface-list', filepath, tentative); + } +} + +module.exports = { + checkVideoInterfaceList, +}; + +checkVideoInterfaceList.description = `Checks or updates 3rd party video player list on ${filepath}`; + +checkVideoInterfaceList.flags = { + 'fix': 'Write to file', +}; diff --git a/build-system/tasks/cherry-pick.js b/build-system/tasks/cherry-pick.js index 2f4c0e6338985..83eb7e7835113 100644 --- a/build-system/tasks/cherry-pick.js +++ b/build-system/tasks/cherry-pick.js @@ -16,36 +16,22 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const log = require('fancy-log'); -const {getOutput} = require('../common/exec'); -const {green, cyan, red, yellow} = require('ansi-colors'); - -/** - * Executes a shell command, and logs an error message if the command fails. - * - * @param {string} cmd - * @param {string} msg - * @return {!Object} - */ -function execOrThrow(cmd, msg) { - const result = getOutput(cmd); - if (result.status) { - log(yellow('ERROR:'), msg); - throw new Error(result.stderr); - } - - return result; -} +const {cyan, green, red, yellow} = require('../common/colors'); +const {execOrThrow} = require('../common/exec'); +const {getOutput} = require('../common/process'); +const {log} = require('../common/logging'); /** * Determines the name of the cherry-pick branch. * * @param {string} version + * @param {number} numCommits * @return {string} */ -function cherryPickBranchName(version) { +function cherryPickBranchName(version, numCommits) { const timestamp = version.slice(0, -3); - const suffix = String(Number(version.slice(-3)) + 1).padStart(3, '0'); + const suffixNumber = Number(version.slice(-3)) + numCommits; + const suffix = String(suffixNumber).padStart(3, '0'); return `amp-release-${timestamp}${suffix}`; } @@ -53,27 +39,15 @@ function cherryPickBranchName(version) { * Updates tags from the remote and creates a branch at the release commit. * * @param {string} ref - * @param {!Array} commits * @param {string} branch * @param {string} remote */ -function prepareBranch(ref, commits, branch, remote) { - const needsFetch = [ref] - .concat(commits) - .some((r) => getOutput(`git rev-parse ${r}`).status); - - if (needsFetch) { - log(green('INFO:'), 'Fetching latest tags and commits from', cyan(remote)); - execOrThrow( - `git fetch ${remote}`, - `Failed to fetch updates from remote ${cyan(remote)}` - ); - } else { - log( - green('INFO:'), - 'Identified tag and all commits available in local repository' - ); - } +function prepareBranch(ref, branch, remote) { + log(green('INFO:'), 'Pulling latest from', cyan(remote)); + execOrThrow( + `git pull ${remote}`, + `Failed to pull latest from remote ${cyan(remote)}` + ); execOrThrow( `git checkout -b ${branch} ${ref}`, @@ -102,20 +76,19 @@ function performCherryPick(sha) { } } -function cherryPick() { +/** + * @return {Promise} + */ +async function cherryPick() { const {push, remote = 'origin'} = argv; const commits = (argv.commits || '').split(',').filter(Boolean); let onto = String(argv.onto || ''); if (!commits.length) { - log(red('ERROR:'), 'Must provide commit list with --commits'); - process.exitCode = 1; - return; + throw new Error('Must provide commit list with --commits'); } if (!onto) { - log(red('ERROR:'), 'Must provide 13-digit AMP version with --onto'); - process.exitCode = 1; - return; + throw new Error('Must provide 13-digit AMP version with --onto'); } if (onto.length === 15) { log( @@ -127,14 +100,12 @@ function cherryPick() { onto = onto.substr(2); } if (onto.length !== 13) { - log(red('ERROR:'), 'Expected 13-digit AMP version; got', cyan(onto)); - process.exitCode = 1; - return; + throw new Error('Expected 13-digit AMP version'); } - const branch = cherryPickBranchName(onto); + const branch = cherryPickBranchName(onto, commits.length); try { - prepareBranch(onto, commits, branch, remote); + prepareBranch(onto, branch, remote); commits.forEach(performCherryPick); if (push) { @@ -155,12 +126,11 @@ function cherryPick() { green('SUCCESS:'), `Cherry-picked ${commits.length} commits onto release ${onto}` ); - process.exitCode = 0; } catch (e) { log(red('ERROR:'), e.message); log('Deleting branch', cyan(branch)); - getOutput(`git checkout master && git branch -d ${branch}`); - process.exitCode = 1; + getOutput(`git checkout main && git branch -d ${branch}`); + throw e; } } @@ -168,8 +138,8 @@ module.exports = {cherryPick}; cherryPick.description = 'Cherry-picks one or more commits onto a new branch'; cherryPick.flags = { - 'commits': ' Comma-delimited list of commit SHAs to cherry-pick', - 'push': ' If set, will push the created branch to the remote', - 'remote': ' Remote to refresh tags from (default: origin)', - 'onto': ' 13-digit AMP version to cherry-pick onto', + 'commits': 'Comma-delimited list of commit SHAs to cherry-pick', + 'push': 'If set, will push the created branch to the remote', + 'remote': 'Remote to refresh tags from (default: origin)', + 'onto': '13-digit AMP version to cherry-pick onto', }; diff --git a/build-system/tasks/clean.js b/build-system/tasks/clean.js index 9ec0e46d987a2..b9ba0c1ae3d8e 100644 --- a/build-system/tasks/clean.js +++ b/build-system/tasks/clean.js @@ -15,33 +15,78 @@ */ 'use strict'; +const argv = require('minimist')(process.argv.slice(2)); const del = require('del'); +const path = require('path'); +const {cyan} = require('../common/colors'); +const {log} = require('../common/logging'); + +const ROOT_DIR = path.resolve(__dirname, '../../'); /** - * Clean up the build artifacts - * @return {!Promise} + * Cleans up various cache and output directories. Optionally cleans up inner + * node_modules package directories, or excludes some directories from deletion. */ async function clean() { - return del([ + const pathsToDelete = [ + // Local cache directories + // Keep this list in sync with .gitignore, .eslintignore, and .prettierignore + '.babel-cache', + '.css-cache', + '.pre-closure-cache', + + // Output directories + // Keep this list in sync with .gitignore, .eslintignore, and .prettierignore + '.amp-dep-check', + 'build', + 'build-system/dist', + 'build-system/server/new-server/transforms/dist', + 'build-system/tasks/performance/cache', + 'build-system/tasks/performance/results.json', + 'build-system/global-configs/custom-config.json', 'dist', 'dist.3p', 'dist.tools', - 'build', - '.amp-build', - '.karma-cache', - 'deps.txt', - 'build-system/runner/build', - 'build-system/runner/dist', - 'build-system/server/new-server/transforms/dist', - 'validator/java/target', - 'validator/java/src/main/resources', - 'validator/java/src/main/java/dev/amp/validator/ValidatorProtos.java', - 'validator/java/bazel-*', - ]); + 'export', + 'examples/storybook', + 'extensions/**/dist', + 'release', + 'result-reports', + 'src/purifier/dist', + 'test/coverage', + 'test/coverage-e2e', + 'validator/**/dist', + 'validator/export', + ]; + if (argv.include_subpackages) { + pathsToDelete.push('**/node_modules', '!node_modules'); + } + if (argv.exclude) { + const excludes = argv.exclude.split(','); + for (const exclude of excludes) { + pathsToDelete.push(`!${exclude}`); + } + } + const deletedPaths = await del(pathsToDelete, { + expandDirectories: false, + dryRun: argv.dry_run, + }); + if (deletedPaths.length > 0) { + log(argv.dry_run ? "Paths that would've been deleted:" : 'Deleted paths:'); + deletedPaths.forEach((deletedPath) => { + log('\t' + cyan(path.relative(ROOT_DIR, deletedPath))); + }); + } } module.exports = { clean, }; -clean.description = 'Removes build output'; +clean.description = 'Cleans up various cache and output directories'; +clean.flags = { + 'dry_run': 'Does a dry run without actually deleting anything', + 'include_subpackages': + 'Also cleans up inner node_modules package directories', + 'exclude': 'Comma separated list of directories to exclude from deletion', +}; diff --git a/build-system/tasks/codecov-upload.js b/build-system/tasks/codecov-upload.js index ccc1bd6f164db..c6696f1b3b2bb 100644 --- a/build-system/tasks/codecov-upload.js +++ b/build-system/tasks/codecov-upload.js @@ -15,19 +15,20 @@ */ 'use strict'; -const colors = require('ansi-colors'); +const colors = require('../common/colors'); const fs = require('fs-extra'); -const log = require('fancy-log'); -const { - isTravisBuild, - isTravisPullRequestBuild, - travisCommitSha, - travisPullRequestSha, -} = require('../common/travis'); -const {getStdout} = require('../common/exec'); +const {ciBuildSha, isCiBuild} = require('../common/ci'); +const {getStdout} = require('../common/process'); +const {log} = require('../common/logging'); const {shortSha} = require('../common/git'); -const {green, yellow, cyan} = colors; +const {cyan, green, yellow} = colors; +const CODECOV_EXEC = './node_modules/.bin/codecov'; +const COVERAGE_REPORTS = { + 'unit_tests': 'test/coverage/lcov-unit.info', + 'integration_tests': 'test/coverage/lcov-integration.info', + 'e2e_tests': 'test/coverage-e2e/lcov.info', +}; /** * Uploads a single report @@ -35,8 +36,7 @@ const {green, yellow, cyan} = colors; * @param {string} flags */ function uploadReport(file, flags) { - const codecovExecutable = './node_modules/.bin/codecov'; - const codecovCmd = `${codecovExecutable} --file=${file} --flags=${flags}`; + const codecovCmd = `${CODECOV_EXEC} --file=${file} --flags=${flags}`; const output = getStdout(codecovCmd); const viewReportPrefix = 'View report at: '; const viewReport = output.match(`${viewReportPrefix}.*`); @@ -52,33 +52,27 @@ function uploadReport(file, flags) { } /** - * Uploads code coverage reports for unit and integration tests during Travis - * jobs. + * Uploads code coverage reports for unit / integration tests during CI builds. */ async function codecovUpload() { - if (!isTravisBuild()) { + if (!isCiBuild()) { log( yellow('WARNING:'), - 'Code coverage reports can only be uploaded by Travis builds.' + 'Code coverage reports can only be uploaded by CI builds.' ); return; } - const commitSha = shortSha( - isTravisPullRequestBuild() ? travisPullRequestSha() : travisCommitSha() - ); + + const commitSha = shortSha(ciBuildSha()); log( green('INFO:'), 'Uploading coverage reports to', cyan(`https://codecov.io/gh/ampproject/amphtml/commit/${commitSha}`) ); - const unitTestsReport = 'test/coverage/lcov-unit.info'; - const integrationTestsReport = 'test/coverage/lcov-integration.info'; - if (fs.existsSync(unitTestsReport)) { - uploadReport(unitTestsReport, 'unit_tests'); - } - if (fs.existsSync(integrationTestsReport)) { - uploadReport(integrationTestsReport, 'integration_tests'); - } + + Object.entries(COVERAGE_REPORTS) + .filter(([, reportFile]) => fs.existsSync(reportFile)) + .forEach(([testType, reportFile]) => uploadReport(reportFile, testType)); } module.exports = { @@ -86,4 +80,4 @@ module.exports = { }; codecovUpload.description = - 'Uploads code coverage reports to codecov.io during Travis builds.'; + 'Uploads code coverage reports to codecov.io during CI builds.'; diff --git a/build-system/tasks/compile-jison.js b/build-system/tasks/compile-jison.js index ad4da510578d1..ea041ac245549 100644 --- a/build-system/tasks/compile-jison.js +++ b/build-system/tasks/compile-jison.js @@ -16,11 +16,10 @@ 'use strict'; const fs = require('fs-extra'); -const glob = require('glob'); +const globby = require('globby'); const jison = require('jison'); const path = require('path'); -const {endBuildStep} = require('./helpers'); -const {jisonPaths} = require('../test-configs/config'); +const {jisonPath} = require('../test-configs/config'); // set imports for each parser from directory build/parsers/. const imports = new Map([ @@ -31,7 +30,7 @@ const imports = new Map([ [ 'bindParser', "import {AstNode, AstNodeType} from '../../extensions/amp-bind/0.1/bind-expr-defines';\n" + - "import {tryParseJson} from '../../src/json';", + "import {tryParseJson} from '../../src/core/types/object/json';", ], ]); @@ -39,23 +38,21 @@ const imports = new Map([ * Builds parsers for extensions with *.jison files. * Uses jison file path to name the parser to export. * For example, css-expr-impl.jison creates `cssParser`. - * @return {!Promise} + * + * @param {string} searchDir - directory to compile jison files within. + * @return {!Promise} */ -async function compileJison() { +async function compileJison(searchDir = jisonPath) { fs.mkdirSync('build/parsers', {recursive: true}); - const startTime = Date.now(); - const promises = []; - jisonPaths.forEach((jisonPath) => { - glob.sync(jisonPath).forEach((jisonFile) => { - const jsFile = path.basename(jisonFile, '.jison'); - const extension = jsFile.replace('-expr-impl', ''); - const parser = extension + 'Parser'; - const newFilePath = `build/parsers/${jsFile}.js`; - promises.push(compileExpr(jisonFile, parser, newFilePath)); - }); + + const promises = globby.sync(searchDir).map((jisonFile) => { + const jsFile = path.basename(jisonFile, '.jison'); + const extension = jsFile.replace('-expr-impl', ''); + const parser = extension + 'Parser'; + const newFilePath = `build/parsers/${jsFile}.js`; + return compileExpr(jisonFile, parser, newFilePath); }); - await Promise.all(promises); - endBuildStep('Compiled Jison parsers into', 'build/parsers/', startTime); + return Promise.all(promises); } /** diff --git a/build-system/tasks/coverage-map/index.js b/build-system/tasks/coverage-map/index.js new file mode 100644 index 0000000000000..8770b22af5eb1 --- /dev/null +++ b/build-system/tasks/coverage-map/index.js @@ -0,0 +1,183 @@ +/** + * Copyright 2020 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. + */ +const argv = require('minimist')(process.argv.slice(2)); +const fs = require('fs').promises; +const {buildNewServer} = require('../../server/typescript-compile'); +const {cyan} = require('../../common/colors'); +const {dist} = require('../dist'); +const {log} = require('../../common/logging'); +const {startServer, stopServer} = require('../serve'); + +const coverageJsonName = argv.json || 'coverage.json'; +const serverPort = argv.port || 8000; +const outHtml = argv.outputhtml || 'coverage.html'; +const inputHtml = argv.inputhtml || 'everything.amp.html'; +let testUrl = `http://localhost:${serverPort}/examples/${inputHtml}`; +let inputJs = argv.file || 'v0.js'; + +/** + * @return {Promise} + */ +async function collectCoverage() { + const puppeteer = require('puppeteer'); + log('Opening browser and navigating to', cyan(`${testUrl}`) + '...'); + const browser = await puppeteer.launch({ + defaultViewport: {width: 1200, height: 800}, + }); + const page = await browser.newPage(); + + // Enable JavaScript coverage + await page.coverage.startJSCoverage(); + // Navigate to page + await page.goto(testUrl); + await page.waitFor( + () => + !!document.querySelector('style[amp-runtime]') && + !!document.body && + getComputedStyle(document.body).visibility === 'visible' + ); + log('Scrolling to the end of the page...'); + await autoScroll(page); + log('Testing completed.'); + const jsCoverage = await page.coverage.stopJSCoverage(); + const data = JSON.stringify(jsCoverage); + log( + 'Writing to', + cyan(`${coverageJsonName}`), + 'in', + cyan(`dist/${coverageJsonName}`) + '...' + ); + await fs.writeFile(`dist/${coverageJsonName}`, data); + await browser.close(); +} + +/** + * Source: https://github.com/chenxiaochun/blog/issues/38s + * TODO(#28387) change the first parameter to puppeteer.Page once puppeteer is consistently + * imported. + * @param {*} page + * @return {Promise} + */ +async function autoScroll(page) { + await page.evaluate(async () => { + await /** @type {Promise} */ ( + new Promise((resolve) => { + let totalHeight = 0; + const scrollDistance = 100; + const distance = scrollDistance; + const timer = setInterval(() => { + const {scrollHeight} = document.body; + window./*OK*/ scrollBy(0, distance); + totalHeight += distance; + + if (totalHeight >= scrollHeight) { + clearInterval(timer); + resolve(); + } + }, 100); + }) + ); + }); +} + +/** + * @return {Promise} + */ +async function htmlTransform() { + // @ts-ignore + const {transform} = require('../../server/new-server/transforms/dist/transform'); // prettier-ignore + log('Transforming', cyan(`${inputHtml}`) + '...'); + const transformed = await transform(`examples/${inputHtml}`); + const transformedName = `transformed.${inputHtml}`; + await fs.mkdir('dist/transformed', {recursive: true}); + await fs.writeFile(`dist/transformed/${transformedName}`, transformed); + log( + 'Transformation complete. It can be found at', + cyan(`dist/transformed/${transformedName}`) + '.' + ); + testUrl = `http://localhost:${serverPort}/dist/transformed/${transformedName}`; +} + +/** + * @return {Promise} + */ +async function generateMap() { + const {explore} = require('source-map-explorer'); + + // Change source map explorer to mjs file extension if needed + if (argv.esm && inputJs.includes('.js')) { + inputJs = inputJs.replace(/\.js/g, '.mjs'); + } + + log( + 'Generating heat map in', + cyan(`dist/${outHtml}`), + 'of', + cyan(`${inputJs}`), + 'based on', + cyan(`${coverageJsonName}`) + '...' + ); + await explore( + {code: `dist/${inputJs}`, map: `dist/${inputJs}.map`}, + { + output: {format: 'html', filename: `dist/${outHtml}`}, + coverage: `dist/${coverageJsonName}`, + onlyMapped: true, + } + ); +} + +/** + * @return {Promise} + */ +async function coverageMap() { + await buildNewServer(); + + if (!argv.nobuild) { + await dist(); + } + + if (argv.esm || argv.sxg) { + await htmlTransform(); + } + + await startServer( + {host: 'localhost', port: serverPort}, + {quiet: true}, + {compiled: true} + ); + await collectCoverage(); + await generateMap(); + await stopServer(); +} + +module.exports = { + coverageMap, +}; + +coverageMap.description = + 'Generates a code coverage heat map for v0.js via source map explorer'; + +coverageMap.flags = { + json: 'JSON output filename [default: out.json]', + inputhtml: 'Input HTML file under "examples/" [default: everything.amp.html]', + outputhtml: 'Output HTML file [default: out.html]', + nobuild: 'Skips dist build.', + port: 'Port number for AMP server [default: 8000]', + file: 'Output file(s) relative to dist/. Accepts .js, .mjs, and wildcards. [default: v0.js]', + esm: 'Generate coverage in ESM mode. Triggers an extra HTML transformation.', + sxg: 'Generate in SxG mode. Triggers an extra HTML transformation.', +}; diff --git a/build-system/tasks/coverage-map/package-lock.json b/build-system/tasks/coverage-map/package-lock.json new file mode 100644 index 0000000000000..13ea1ac8106be --- /dev/null +++ b/build-system/tasks/coverage-map/package-lock.json @@ -0,0 +1,844 @@ +{ + "name": "amp-coverage-map", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", + "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", + "dev": true, + "optional": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "dev": true + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "ejs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "dev": true, + "requires": { + "jake": "^10.6.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "filelist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dev": true, + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "puppeteer": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", + "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-explorer": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.2.tgz", + "integrity": "sha512-gBwOyCcHPHcdLbgw6Y6kgoH1uLKL6hN3zz0xJcNI2lpnElZliIlmSYAjUVwAWnc7+HscoTyh1ScR7ITtFuEnxg==", + "dev": true, + "requires": { + "btoa": "^1.2.1", + "chalk": "^4.1.0", + "convert-source-map": "^1.7.0", + "ejs": "^3.1.5", + "escape-html": "^1.0.3", + "glob": "^7.1.6", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "open": "^7.3.1", + "source-map": "^0.7.3", + "temp": "^0.9.4", + "yargs": "^16.2.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/build-system/tasks/coverage-map/package.json b/build-system/tasks/coverage-map/package.json new file mode 100644 index 0000000000000..6600a41878eb6 --- /dev/null +++ b/build-system/tasks/coverage-map/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "name": "amp-coverage-map", + "version": "0.1.0", + "description": "amp coverage map.", + "devDependencies": { + "puppeteer": "9.1.1", + "source-map-explorer": "2.5.2" + } +} diff --git a/build-system/tasks/create-golden-css/css/main.css b/build-system/tasks/create-golden-css/css/main.css index e53155ad016e4..eac098a2b6da7 100644 --- a/build-system/tasks/create-golden-css/css/main.css +++ b/build-system/tasks/create-golden-css/css/main.css @@ -45,7 +45,7 @@ html.i-amphtml-fie { /** * Margin:0 is currently needed for iOS viewer embeds. * See: - * https://github.com/ampproject/amphtml/blob/master/spec/amp-html-layout.md + * https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-html-layout.md */ body { margin: 0 !important; @@ -621,21 +621,10 @@ amp-live-list > [update] { /** * Display none elements */ -amp-experiment, amp-share-tracking { +amp-experiment { display: none; } -.i-amphtml-jank-meter { - position: fixed; - background-color: rgba(232,72,95,.5); - bottom: 0; - right: 0; - color: #fff; - font-size: 16px; - z-index: 1000; - padding: 5px; -} - /** * Animated equalizer icon for muted autoplaying videos. */ diff --git a/build-system/tasks/create-golden-css/index.js b/build-system/tasks/create-golden-css/index.js deleted file mode 100644 index b9335a359dfe9..0000000000000 --- a/build-system/tasks/create-golden-css/index.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2017 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. - */ -'use strict'; -const fs = require('fs-extra'); -const {transformCss} = require('../jsify-css'); - -async function createGoldenCss() { - return transformCss('./build-system/tasks/create-golden-css/css/main.css', { - normalizeWhitespace: false, - discardComments: false, - }).then(function (result) { - fs.writeFileSync('./test/golden-files/main.css', result); - }); -} - -module.exports = { - createGoldenCss, -}; - -createGoldenCss.description = 'Creates a golden file for untransformed css'; diff --git a/build-system/tasks/css.js b/build-system/tasks/css.js deleted file mode 100644 index 8cb7cfd969d3e..0000000000000 --- a/build-system/tasks/css.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright 2019 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. - */ - -const debounce = require('debounce'); -const file = require('gulp-file'); -const fs = require('fs-extra'); -const gulp = require('gulp'); -const gulpWatch = require('gulp-watch'); -const { - endBuildStep, - mkdirSync, - toPromise, - watchDebounceDelay, -} = require('./helpers'); -const {buildExtensions} = require('./extension-helpers'); -const {jsifyCssAsync} = require('./jsify-css'); -const {maybeUpdatePackages} = require('./update-packages'); - -/** - * Entry point for 'gulp css' - * @return {!Promise} - */ -async function css() { - maybeUpdatePackages(); - return compileCss(); -} - -const cssEntryPoints = [ - { - path: 'ampdoc.css', - outJs: 'ampdoc.css.js', - outCss: 'v0.css', - }, - { - path: 'ampshared.css', - outJs: 'ampshared.css.js', - outCss: 'v0.css', - append: true, - }, - { - path: 'video-autoplay.css', - outJs: 'video-autoplay.css.js', - // When the .css.js files are imported, the .js extension is omitted - // e.g. '../../build/file.css' attempts to load 'build/file.css.js' - // but if a file which matches without the .js extension, it will - // be preferred. We should rename the out.css to have a different name - // than the JS file to avoid loading CSS as JS - outCss: 'video-autoplay-out.css', - }, - { - // Publisher imported CSS for `src/amp-story-player/amp-story-player.js`. - path: 'amp-story-player.css', - outJs: 'amp-story-player.css.js', - outCss: 'amp-story-player-v0.css', - }, - { - // Internal CSS used for the iframes inside `src/amp-story-player/amp-story-player.js`. - path: 'amp-story-player-iframe.css', - outJs: 'amp-story-player-iframe.css.js', - outCss: 'amp-story-player-iframe-v0.css', - }, -]; - -/** - * Compile all the css and drop in the build folder - * - * @param {Object=} options - * @return {!Promise} - */ -function compileCss(options = {}) { - if (options.watch) { - const watchFunc = () => { - compileCss(); - }; - gulpWatch('css/**/*.css', debounce(watchFunc, watchDebounceDelay)); - } - - /** - * Writes CSS to build folder - * - * @param {string} css - * @param {string} jsFilename - * @param {string} cssFilename - * @param {boolean} append append CSS to existing file - * @return {Promise} - */ - function writeCss(css, jsFilename, cssFilename, append) { - return toPromise( - file( - jsFilename, - '/** @noinline */ export const cssText = ' + JSON.stringify(css), - { - src: true, - } - ) - .pipe(gulp.dest('build')) - .on('end', function () { - mkdirSync('build'); - mkdirSync('build/css'); - if (append) { - fs.appendFileSync(`build/css/${cssFilename}`, css); - } else { - fs.writeFileSync(`build/css/${cssFilename}`, css); - } - }) - ); - } - - /** - * @param {string} path - * @param {string} outJs - * @param {string} outCss - * @param {boolean} append - * @return {!Promise} - */ - function writeCssEntryPoint(path, outJs, outCss, append) { - return jsifyCssAsync(`css/${path}`).then((css) => - writeCss(css, outJs, outCss, append) - ); - } - - const startTime = Date.now(); - - let promise = Promise.resolve(); - - cssEntryPoints.forEach((entryPoint) => { - const {path, outJs, outCss, append} = entryPoint; - promise = promise.then(() => - writeCssEntryPoint(path, outJs, outCss, append) - ); - }); - - return promise - .then(() => buildExtensions({compileOnlyCss: true})) - .then(() => { - endBuildStep('Recompiled all CSS files into', 'build/', startTime); - }); -} - -module.exports = { - css, - compileCss, - cssEntryPoints, -}; - -css.description = 'Recompile css to build directory'; diff --git a/build-system/tasks/css/index.js b/build-system/tasks/css/index.js new file mode 100644 index 0000000000000..6d334219bb3a2 --- /dev/null +++ b/build-system/tasks/css/index.js @@ -0,0 +1,163 @@ +/** + * Copyright 2019 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. + */ + +const debounce = require('debounce'); +const fs = require('fs-extra'); +const globby = require('globby'); +const path = require('path'); +const {buildExtensions} = require('../extension-helpers'); +const {endBuildStep, watchDebounceDelay} = require('../helpers'); +const {jsifyCssAsync} = require('./jsify-css'); +const {watch} = require('chokidar'); + +/** + * Entry point for 'amp css' + */ +async function css() { + await compileCss(); +} + +const cssEntryPoints = [ + { + path: 'ampdoc.css', + outJs: 'ampdoc.css.js', + outCss: 'v0.css', + append: false, + }, + { + path: 'ampshared.css', + outJs: 'ampshared.css.js', + outCss: 'v0.css', + append: true, + }, + { + path: 'video-autoplay.css', + outJs: 'video-autoplay.css.js', + // When the .css.js files are imported, the .js extension is omitted + // e.g. '../../build/file.css' attempts to load 'build/file.css.js' + // but if a file which matches without the .js extension, it will + // be preferred. We should rename the out.css to have a different name + // than the JS file to avoid loading CSS as JS + outCss: 'video-autoplay-out.css', + append: false, + }, + { + path: 'amp-story-entry-point.css', + outJs: 'amp-story-entry-point.css.js', + outCss: 'amp-story-entry-point-v0.css', + append: false, + }, + { + // Publisher imported CSS for `src/amp-story-player/amp-story-player.js`. + path: 'amp-story-player.css', + outJs: 'amp-story-player.css.js', + outCss: 'amp-story-player-v0.css', + append: false, + }, + { + // Internal CSS used for the iframes inside `src/amp-story-player/amp-story-player.js`. + path: 'amp-story-player-iframe.css', + outJs: 'amp-story-player-iframe.css.js', + outCss: 'amp-story-player-iframe-v0.css', + append: false, + }, + { + path: 'amp-ima-video-iframe.css', + outJs: 'amp-ima-video-iframe.css.js', + outCss: 'amp-ima-video-iframe-v0.css', + append: false, + }, +]; + +/** + * Copies the css from the build folder to the dist folder + */ +async function copyCss() { + const startTime = Date.now(); + await fs.ensureDir('dist/v0'); + for (const {outCss} of cssEntryPoints) { + await fs.copy(`build/css/${outCss}`, `dist/${outCss}`); + } + const cssFiles = globby.sync('build/css/amp-*.css'); + await Promise.all( + cssFiles.map((cssFile) => { + return fs.copy(cssFile, `dist/v0/${path.basename(cssFile)}`); + }) + ); + endBuildStep('Copied', 'build/css/*.css to dist/v0/*.css', startTime); +} + +/** + * Writes CSS to build folder + * + * @param {string} css + * @param {string} jsFilename + * @param {string} cssFilename + * @param {boolean} append append CSS to existing file + */ +async function writeCss(css, jsFilename, cssFilename, append) { + await fs.ensureDir('build/css'); + const jsContent = 'export const cssText = ' + JSON.stringify(css); + await fs.writeFile(`build/${jsFilename}`, jsContent); + if (append) { + await fs.appendFile(`build/css/${cssFilename}`, css); + } else { + await fs.writeFile(`build/css/${cssFilename}`, css); + } +} + +/** + * @param {string} path + * @param {string} outJs + * @param {string} outCss + * @param {boolean} append + */ +async function writeCssEntryPoint(path, outJs, outCss, append) { + const css = await jsifyCssAsync(`css/${path}`); + await writeCss(css, outJs, outCss, append); +} + +/** + * Compile all the css and drop in the build folder + * + * @param {Object=} options + * @return {!Promise} + */ +async function compileCss(options = {}) { + if (options.watch) { + watch('css/**/*.css').on( + 'change', + debounce(compileCss, watchDebounceDelay) + ); + } + + const startTime = Date.now(); + // Must be in order because some iterations write while others append. + for (const {append, outCss, outJs, path} of cssEntryPoints) { + await writeCssEntryPoint(path, outJs, outCss, append); + } + await buildExtensions({compileOnlyCss: true}); + endBuildStep('Recompiled all CSS files into', 'build/', startTime); +} + +module.exports = { + css, + compileCss, + copyCss, + cssEntryPoints, +}; + +css.description = 'Recompile css to build directory'; diff --git a/build-system/tasks/css/init-sync.js b/build-system/tasks/css/init-sync.js new file mode 100644 index 0000000000000..644945159d8a1 --- /dev/null +++ b/build-system/tasks/css/init-sync.js @@ -0,0 +1,31 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const {transformCssString} = require('./jsify-css'); + +/** + * Wrapper for the asynchronous transformCssString that is used by transformCssSync() + * in build-system/tasks/css/jsify-css-sync.js. + * + * @return {function(string, !Object=, !Object=): ReturnType} + */ +function init() { + return function (cssStr, opt_filename) { + return Promise.resolve(transformCssString(cssStr, opt_filename)); + }; +} +module.exports = init; diff --git a/build-system/tasks/css/jsify-css-sync.js b/build-system/tasks/css/jsify-css-sync.js new file mode 100644 index 0000000000000..ef60a7717e341 --- /dev/null +++ b/build-system/tasks/css/jsify-css-sync.js @@ -0,0 +1,40 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * Lazily instantiate the transformer. + */ +let syncTransformer; + +/** + * Synchronously transforms a css string using postcss. + + * @param {string} cssStr the css text to transform + * @param {!Object=} opt_cssnano cssnano options + * @param {!Object=} opt_filename the filename of the file being transformed. Used for sourcemaps generation. + * @return {Object} The transformation result + */ +function transformCssSync(cssStr, opt_cssnano, opt_filename) { + if (!syncTransformer) { + syncTransformer = require('sync-rpc')(__dirname + '/init-sync.js'); + } + return syncTransformer(cssStr, opt_cssnano, opt_filename); +} + +module.exports = { + transformCssSync, +}; diff --git a/build-system/tasks/css/jsify-css.js b/build-system/tasks/css/jsify-css.js new file mode 100644 index 0000000000000..906a265e9c86e --- /dev/null +++ b/build-system/tasks/css/jsify-css.js @@ -0,0 +1,171 @@ +/** + * Copyright 2016 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. + */ +'use strict'; + +const cssnano = require('cssnano'); +const fs = require('fs'); +const path = require('path'); +const postcss = require('postcss'); +const postcssImport = require('postcss-import'); +const { + TransformCache, + batchedRead, + md5, +} = require('../../common/transform-cache'); +const {log} = require('../../common/logging'); +const {red} = require('../../common/colors'); + +// NOTE: see https://github.com/ai/browserslist#queries for `browsers` list +const browsersList = { + overrideBrowserslist: [ + 'last 5 ChromeAndroid versions', + 'last 5 iOS versions', + 'last 3 FirefoxAndroid versions', + 'last 5 Android versions', + 'last 2 ExplorerMobile versions', + 'last 2 OperaMobile versions', + 'last 2 OperaMini versions', + ], +}; + +// See https://cssnano.co/docs/what-are-optimisations for full list. +// We try and turn off any optimization that is marked unsafe. +const cssNanoDefaultOptions = { + autoprefixer: false, + convertValues: false, + discardUnused: false, + cssDeclarationSorter: false, + // `mergeIdents` this is only unsafe if you rely on those animation names in + // JavaScript. + mergeIdents: true, + reduceIdents: false, + reduceInitial: false, + zindex: false, + svgo: { + encode: true, + }, +}; + +const packageJsonPath = path.join(__dirname, '..', '..', '..', 'package.json'); + +let environmentHash = null; + +/** @return {Promise} */ +function getEnvironmentHash() { + if (environmentHash) { + return environmentHash; + } + + // We want to set environmentHash to a promise synchronously s.t. + // we never end up with multiple calculations at the same time. + environmentHash = Promise.resolve().then(async () => { + const packageJsonHash = md5(await fs.promises.readFile(packageJsonPath)); + const cssOptions = JSON.stringify({cssNanoDefaultOptions, browsersList}); + return md5(packageJsonHash, cssOptions); + }); + return environmentHash; +} + +/** + * Used to cache css transforms done by postcss. + * @const {TransformCache} + */ +let transformCache; + +/** + * @typedef {{css: string, warnings: string[]}}: + */ +let CssTransformResultDef; + +/** + * Transform a css string using postcss. + + * @param {string} contents the css text to transform + * @param {!Object=} opt_filename the filename of the file being transformed. Used for sourcemaps generation. + * @return {!Promise} that resolves with the css content after + * processing + */ +async function transformCssString(contents, opt_filename) { + const hash = md5(contents); + return transformCss(contents, hash, opt_filename); +} + +/** + * 'Jsify' a CSS file - Adds vendor specific css prefixes to the css file, + * compresses the file, removes the copyright comment, and adds the sourceURL + * to the stylesheet + * + * @param {string} filename css file + * @return {!Promise} that resolves with the css content after + * processing + */ +async function jsifyCssAsync(filename) { + const {contents, hash: filehash} = await batchedRead(filename); + const hash = md5(filehash, await getEnvironmentHash()); + const result = await transformCss(contents, hash, filename); + + result.warnings.forEach((warn) => log(red(warn))); + return result.css + '\n/*# sourceURL=/' + filename + '*/'; +} + +/** + * @param {string} contents + * @param {string} hash + * @param {string=} opt_filename + * @return {Promise} + */ +async function transformCss(contents, hash, opt_filename) { + if (!transformCache) { + transformCache = new TransformCache('.css-cache', '.css'); + } + const cached = transformCache.get(hash); + if (cached) { + return JSON.parse((await cached).toString()); + } + + const transformed = transform(contents, opt_filename); + transformCache.set( + hash, + transformed.then((r) => JSON.stringify(r)) + ); + return transformed; +} + +/** + * @param {string} contents + * @param {string=} opt_filename + * @return {Promise} + */ +async function transform(contents, opt_filename) { + const cssnanoTransformer = cssnano({ + preset: ['default', cssNanoDefaultOptions], + }); + const {default: autoprefixer} = await import('autoprefixer'); // Lazy-imported to speed up task loading. + const cssprefixer = autoprefixer(browsersList); + const transformers = [postcssImport, cssprefixer, cssnanoTransformer]; + return postcss + .default(transformers) + .process(contents, {'from': opt_filename}) + .then((result) => ({ + css: result.css, + warnings: result.warnings().map((warning) => warning.toString()), + })); +} + +module.exports = { + jsifyCssAsync, + transformCssString, +}; diff --git a/build-system/tasks/csvify-size/index.js b/build-system/tasks/csvify-size/index.js deleted file mode 100644 index c429943cab6aa..0000000000000 --- a/build-system/tasks/csvify-size/index.js +++ /dev/null @@ -1,281 +0,0 @@ -/** - * Copyright 2016 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. - */ -'use strict'; - -const childProcess = require('child_process'); -const colors = require('ansi-colors'); -const fs = require('fs'); -const log = require('fancy-log'); -const util = require('util'); - -const exec = util.promisify(childProcess.exec); - -const prettyBytesUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - -/** - * @typedef {!Array} - */ -let TablesDef; - -/** - * @typedef {{ - * name: string, - * dateTime: string, - * size: string - * }} - */ -let FieldsDef; - -const filePath = 'test/size.txt'; - -const tableHeaders = [['"datetime"']]; - -const dateTimes = []; - -/** - * @param {string} format - * @return {!Array} - */ -function getLog(format) { - return exec(`git log --format="${format}" ${filePath}`).then((logs) => - logs.trim().split('\n') - ); -} - -/** - * @param {string} file - * @return {!TablesDef} - */ -function parseSizeFile(file) { - const lines = file.trim().split('\n'); - const headers = lines[0] - .trim() - .split('|') - .map((x) => x.trim()); - let minPos = -1; - // Find the "min" column which is the closure compiled or the "size" column - // which was previously babelify compiled file. - for (let i = 0; i < headers.length; i++) { - if (headers[i] == 'min' || headers[i] == 'size') { - minPos = i; - break; - } - } - - // Remove headers - lines.shift(); - // Remove separator - lines.shift(); - - return lines - .map((line) => { - const columns = line.split('|').map((x) => x.trim()); - let name = columns[columns.length - 1]; - - // Older size.txt files contained duplicate entries of the same "entity", - // for example a file had an entry for its .min and its .max file. - const shouldSkip = - (name.endsWith('max.js') && - !name.endsWith('alp.max.js') && - !/\s\/\s/.test(name)) || - name == 'current/integration.js' || - name == 'amp.js' || - name == 'cc.js' || - name.endsWith('-latest.js'); - - if (shouldSkip) { - return null; - } - - // Normalize names. We made mistakes at some point with duplicate entries - // or renamed entries so we make sure to identify these entities - // and put then into the same column. - if (name == 'v0.js / amp.js' || name == 'current-min/v0.js') { - name = 'v0.js'; - } else if ( - name == 'current-min/f.js / current/integration.js' || - name == 'current-min/f.js' - ) { - name = 'f.js'; - } else if ( - name == 'alp.max.js' || - name == 'alp.js / install-alp.js' || - name == 'alp.js / alp.max.js' - ) { - name = 'alp.js'; - } else if (name == 'sw.js / sw.max.js') { - name = 'sw.js'; - } else if (name == 'sw-kill.js / sw-kill.max.js') { - name = 'sw-kill.js'; - } else if (name == 'a4a-host-v0.js / amp-inabox-host.js') { - name = 'amp4ads-host-v0.js / amp-inabox-host.js'; - } else if (name == 'a4a-v0.js / amp-inabox.js') { - name = 'amp4ads-v0.js / amp-inabox.js'; - } - - return { - name: `"${name}"`, - size: `"${reversePrettyBytes(columns[minPos])}"`, - }; - }) - .filter((x) => !!x); -} - -/** - * @param {!Array} dateTimes - * @param {!TablesDef} tables - * @return {!Array>} - */ -function mergeTables(dateTimes, tables) { - // Where key is filename - /** @typedef {!Object>} */ - const obj = Object.create(null); - const rows = []; - - // Aggregate all fields with same file name into an array - tables.forEach((table) => { - table.forEach((field) => { - const {name} = field; - if (!obj[name]) { - obj[name] = []; - } - obj[name].push({ - size: field.size, - dateTime: field.dateTime, - }); - }); - }); - - // Populate the headers array with unique file names for row 1 - Object.keys(obj) - .sort() - .forEach((fileName) => { - // TODO(erwinm): figure out where this is occurring. - if (fileName.trim() == '""') { - return; - } - tableHeaders[0].push(fileName); - }); - - // Populate column A with all the dates we've seen and then - // populate all other columns with their respective file size if any. - dateTimes.forEach((dateTime) => { - // Seed array with empty string values - const row = Array.apply(null, Array(tableHeaders[0].length)).map( - () => '""' - ); - rows.push(row); - row[0] = dateTime; - // Exclude the datetime column - tableHeaders[0].slice(1).forEach((fileName, colIdx) => { - colIdx = colIdx + 1; - let curField = null; - for (let i = 0; i < obj[fileName].length; i++) { - curField = obj[fileName][i]; - if (curField.dateTime == dateTime) { - row[colIdx] = curField.size; - break; - } - } - }); - }); - return rows; -} - -/** - * @param {string} prettyBytes - * @return {number} - */ -function reversePrettyBytes(prettyBytes) { - const triple = prettyBytes.match( - /(\d+(?:\.\d+)?)\s+(B|kB|MB|GB|TB|PB|EB|ZB|YB)/ - ); - if (!triple) { - throw new Error('No matching bytes data found'); - } - const value = triple[1]; - const unit = triple[2]; - - if (!(value && unit)) { - return 0; - } - const exponent = prettyBytesUnits.indexOf(unit); - return (Number(value) * Math.pow(1000, exponent)).toFixed(3); -} - -/** - * Iterates through the commits and tries to checkout the file - * @param {!Array} logs - * @return {!Promise} - */ -function serializeCheckout(logs) { - const tables = []; - const promise = logs.reduce((acc, cur, i) => { - const parts = logs[i].split(' '); - const sha = parts.shift(); - const dateTime = parts.join(' '); - - return acc.then((tables) => { - // We checkout all the known commits for the file and accumulate - // all the tables. - return exec(`git checkout ${sha} ${filePath}`) - .then(() => { - return fs.promises.readFile(`${filePath}`); - }) - .then((file) => { - const quotedDateTime = `"${dateTime}"`; - dateTimes.push(quotedDateTime); - // We convert the read file string into an Table objects - const fields = parseSizeFile(file.toString()).map((field) => { - field.dateTime = quotedDateTime; - return field; - }); - tables.push(fields); - return tables; - }) - .catch((e) => { - // Ignore if pathspec error. This can happen if the file was - // deleted in git. - if (/error: pathspec/.test(e.message)) { - tables.push([]); - return tables; - } - log(colors.red(e.message)); - }); - }); - }, Promise.resolve(tables)); - return promise.then(mergeTables.bind(null, dateTimes)); -} - -async function csvifySize() { - const shaAndDate = '%H %ai'; - return getLog(shaAndDate).then((logs) => { - // Reverse it from oldest to newest - return serializeCheckout(logs.reverse()).then((rows) => { - rows.unshift.apply(rows, tableHeaders); - const tbl = rows.map((row) => row.join(',')).join('\n'); - return fs.promises.writeFile('test/size.csv', `${tbl}\n`); - }); - }); -} - -module.exports = { - csvifySize, - parseSizeFile, - mergeTables, -}; - -csvifySize.description = 'Creates a CSV file out of the size.txt file'; diff --git a/build-system/tasks/csvify-size/test.js b/build-system/tasks/csvify-size/test.js deleted file mode 100644 index 0df1efd4a2847..0000000000000 --- a/build-system/tasks/csvify-size/test.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2016 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. - */ -'use strict'; - -const m = require('./'); -const test = require('ava'); - -/*eslint "max-len": 0*/ -test('sync - parse size.txt', (t) => { - t.plan(2); - const sizeFiles = [ - ` max | min | gzip | brotli | file - -- | --- | --- | --- | --- - 12.04 kB | 5.5 kB | 3.2 kB | 1.12 kB | v0.js / amp.js - 100.46 kB | 60.11 kB | 23.22 kB | 10.11 kB | current-min/f.js / current/integration.js - `, - ` max | size | file - -- | --- | --- - 13 B | 12 B | v0.js / amp.js - 120.46 kB | 70.11 kB | current-min/f.js - `, - ]; - const table1 = m.parseSizeFile(sizeFiles[0]); - t.deepEqual(table1, [ - {name: '"v0.js"', size: '"5500.000"'}, - {name: '"f.js"', size: '"60110.000"'}, - ]); - const table2 = m.parseSizeFile(sizeFiles[1]); - t.deepEqual(table2, [ - {name: '"v0.js"', size: '"12.000"'}, - {name: '"f.js"', size: '"70110.000"'}, - ]); -}); - -test('sync - parse table typedef', (t) => { - t.plan(1); - const dateTimes = ['"0"', '"1"', '"2"']; - const tables = [ - [{name: '"v0.js"', size: '"5.5"', dateTime: '"0"'}], - [ - {name: '"v0.js"', size: '"8.5"', dateTime: '"1"'}, - {name: '"f.js"', size: '"70.11"', dateTime: '"1"'}, - ], - [ - {name: '"v0.js"', size: '"8.53"', dateTime: '"2"'}, - {name: '"f.js"', size: '"71.11"', dateTime: '"2"'}, - ], - ]; - const csv = m.mergeTables(dateTimes, tables); - t.deepEqual(csv, [ - ['"0"', '""', '"5.5"'], - ['"1"', '"70.11"', '"8.5"'], - ['"2"', '"71.11"', '"8.53"'], - ]); -}); diff --git a/build-system/tasks/default-task.js b/build-system/tasks/default-task.js index 6b3fa98fed92e..df759ed4ae2ef 100644 --- a/build-system/tasks/default-task.js +++ b/build-system/tasks/default-task.js @@ -15,21 +15,20 @@ */ const argv = require('minimist')(process.argv.slice(2)); -const log = require('fancy-log'); const {createCtrlcHandler} = require('../common/ctrlcHandler'); -const {cyan, green} = require('ansi-colors'); +const {cyan, green} = require('../common/colors'); const {doServe} = require('./serve'); -const {maybeUpdatePackages} = require('./update-packages'); +const {log} = require('../common/logging'); const {parseExtensionFlags} = require('./extension-helpers'); const {printConfigHelp} = require('./helpers'); const {runPreBuildSteps} = require('./build'); const {runPreDistSteps} = require('./dist'); /** - * Prints a useful help message prior to the default gulp task + * Prints a useful help message prior to the default amp task */ function printDefaultTaskHelp() { - log(green('Running the default ') + cyan('gulp ') + green('task.')); + log(green('Running the default ') + cyan('amp ') + green('task.')); log( green( '⤷ JS and extensions will be lazily built when requested from the server.' @@ -38,15 +37,14 @@ function printDefaultTaskHelp() { } /** - * The default task run when `gulp` is executed + * The default task run when `amp` is executed * * @return {!Promise} */ async function defaultTask() { - maybeUpdatePackages(); - createCtrlcHandler('gulp'); + createCtrlcHandler('amp'); process.env.NODE_ENV = 'development'; - printConfigHelp('gulp'); + printConfigHelp('amp'); printDefaultTaskHelp(); parseExtensionFlags(/* preBuild */ true); if (argv.compiled) { @@ -67,26 +65,25 @@ module.exports = { defaultTask.description = 'Starts the dev server, lazily builds JS and extensions when requested, and watches them for changes'; defaultTask.flags = { - compiled: ' Compiles and serves minified binaries', + compiled: 'Compiles and serves minified binaries', pseudo_names: - ' Compiles with readable names. ' + + 'Compiles with readable names. ' + 'Great for profiling and debugging production code.', pretty_print: - ' Outputs compiled code with whitespace. ' + + 'Outputs compiled code with whitespace. ' + 'Great for debugging production code.', - fortesting: ' Compiles production binaries for local testing', - noconfig: ' Compiles production binaries without applying AMP_CONFIG', - config: ' Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', - closure_concurrency: ' Sets the number of concurrent invocations of closure', - extensions: ' Pre-builds the given extensions, lazily builds the rest.', + fortesting: 'Compiles production binaries for local testing', + noconfig: 'Compiles production binaries without applying AMP_CONFIG', + config: 'Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', + closure_concurrency: 'Sets the number of concurrent invocations of closure', + extensions: 'Pre-builds the given extensions, lazily builds the rest.', extensions_from: - ' Pre-builds the extensions used by the provided example page.', - full_sourcemaps: ' Includes source code content in sourcemaps', - disable_nailgun: - " Doesn't use nailgun to invoke closure compiler (much slower)", - version_override: ' Overrides the version written to AMP_CONFIG', - host: ' Host to serve the project on. localhost by default.', - port: ' Port to serve the project on. 8000 by default.', + 'Pre-builds the extensions used by the provided example page.', + full_sourcemaps: 'Includes source code content in sourcemaps', + version_override: 'Overrides the version written to AMP_CONFIG', + host: 'Host to serve the project on. localhost by default.', + port: 'Port to serve the project on. 8000 by default.', + https: 'Use https server. http by default.', define_experiment_constant: - ' Builds runtime with the EXPERIMENT constant set to true', + 'Builds runtime with the EXPERIMENT constant set to true', }; diff --git a/build-system/tasks/dep-check.js b/build-system/tasks/dep-check.js index ce95bfee97cf4..fbd41472eeac8 100644 --- a/build-system/tasks/dep-check.js +++ b/build-system/tasks/dep-check.js @@ -15,32 +15,25 @@ */ 'use strict'; -const babelify = require('babelify'); -const browserify = require('browserify'); const depCheckConfig = require('../test-configs/dep-check-config'); +const esbuild = require('esbuild'); const fs = require('fs-extra'); -const gulp = require('gulp'); -const log = require('fancy-log'); const minimatch = require('minimatch'); const path = require('path'); -const source = require('vinyl-source-stream'); -const through = require('through2'); const { createCtrlcHandler, exitCtrlcHandler, } = require('../common/ctrlcHandler'); const {compileJison} = require('./compile-jison'); const {css} = require('./css'); -const {cyan, red, yellow} = require('ansi-colors'); -const {isTravisBuild} = require('../common/travis'); - -const root = process.cwd(); -const absPathRegExp = new RegExp(`^${root}/`); +const {cyan, green, red, yellow} = require('../common/colors'); +const {getEsbuildBabelPlugin} = require('../common/esbuild-babel'); +const {log, logLocalDev} = require('../common/logging'); /** * @typedef {{ * name: string, - * deps: ?Array + * deps: ?Array> * }} */ let ModuleDef; @@ -55,6 +48,22 @@ let GlobDef; */ let GlobsDef; +/** + * - type - Is assumed to be "forbidden" if not provided. + * - filesMatching - Is assumed to be all files if not provided. + * - mustNotDependOn - If type is "forbidden" (default) then the files + * matched must not match the glob(s) provided. + * - allowlist - Skip rule if this particular dependency is found. + * Syntax: fileAGlob->fileB where -> reads "depends on" + * @typedef {{ + * type?: (string|undefined), + * filesMatching?: (string|!Array|undefined), + * mustNotDependOn?: (string|!Array|undefined), + * allowlist?: (string|!Array|undefined), + * }} + */ +let RuleConfigDef; + /** * @constructor @final @struct * @param {!RuleConfigDef} config @@ -156,90 +165,63 @@ Rule.prototype.matchBadDeps = function (moduleName, deps) { const rules = depCheckConfig.rules.map((config) => new Rule(config)); /** - * Returns a list of entryPoint modules. - * extensions/{$extension}/{$version}/{$extension}.js - * src/amp.js - * 3p/integration.js - * - * @return {!Promise>} + * Returns a single module that contains a list of entry points to these files: + * - extensions/{$extension}/{$version}/{$extension}.js + * - src/amp.js + * - 3p/integration.js + * @return {Promise} */ -function getSrcs() { - return fs - .readdir('extensions') - .then((dirItems) => { - // Look for extension entry points - return flatten( - dirItems - .map((x) => `extensions/${x}`) - .filter((x) => fs.statSync(x).isDirectory()) - .map(getEntryModule) - // Concat the core binary and integration binary as entry points. - .concat('src/amp.js', '3p/integration.js') - ); - }) - .then((files) => { - // Write all the entry modules into a single file so they can be processed - // together. - fs.mkdirpSync('./.amp-build'); - const filename = './.amp-build/gulp-dep-check-collection.js'; - fs.writeFileSync( - filename, - files - .map((file) => { - return `import '../${file}';`; - }) - .join('\n') - ); - return [filename]; - }); +async function getEntryPointModule() { + const coreBinaries = ['src/amp.js', '3p/integration.js']; + const extensions = await fs.promises.readdir('extensions'); + const extensionEntryPoints = extensions + .map((x) => `extensions/${x}`) + .filter((x) => fs.statSync(x).isDirectory()) + .map(getEntryPoint); + const allEntryPoints = flatten(extensionEntryPoints).concat(coreBinaries); + const entryPointData = allEntryPoints + .map((file) => `import './${file}';`) + .join('\n'); + return entryPointData; } /** - * @param {string} entryModule - * @return {!Promise} + * @param {string} entryPointModule + * @return {!Promise} */ -function getGraph(entryModule) { - let resolve; - const promise = new Promise((r) => { - resolve = r; +async function getModuleGraph(entryPointModule) { + const plugin = getEsbuildBabelPlugin('unminified', /* enableCache */ true); + const result = await esbuild.build({ + stdin: { + contents: entryPointModule, + resolveDir: '.', + }, + bundle: true, + write: false, + metafile: true, + plugins: [plugin], }); - const module = Object.create(null); - module.name = entryModule; - module.deps = []; - - // TODO(erwinm): Try and work this in with `gulp build` so that - // we're not running browserify twice on travis. - const bundler = browserify(entryModule, { - debug: true, - fast: true, - }).transform(babelify, {caller: {name: 'dep-check'}, global: true}); - - bundler.pipeline.get('deps').push( - through.obj(function (row, enc, next) { - module.deps.push({ - name: row.file.replace(absPathRegExp, ''), - deps: row.deps, - }); - this.push(row); - next(); - }) - ); - bundler - .bundle() - .pipe(source(entryModule)) - // Unfortunately we need to write the files out. - .pipe(gulp.dest('./.amp-build')) - .on('end', () => { - resolve(module); + + const entryPoints = result.metafile.inputs; + const moduleGraph = Object.create(null); + moduleGraph.name = entryPointModule; + moduleGraph.deps = []; + + for (const entryPoint in entryPoints) { + moduleGraph.deps.push({ + name: entryPoint, + deps: entryPoints[entryPoint].imports.map((dep) => dep.path), }); - return promise; + } + logLocalDev('Extracted module graph'); + return moduleGraph; } /** * @param {string} extensionFolder * @return {!Array} */ -function getEntryModule(extensionFolder) { +function getEntryPoint(extensionFolder) { const extension = path.basename(extensionFolder); return fs .readdirSync(extensionFolder) @@ -251,25 +233,17 @@ function getEntryModule(extensionFolder) { } /** - * Flattens the graph to easily run through the Rules. Original graph - * would be nested ModuleDef's wherein the top level are entry points - * with nested dependencies. We flatten it so all we have are individual - * modules and their imports as well as making the entries unique. + * Flattens the module dependency graph and makes its entries unique. This + * serves as the input on which all rules are tested. * - * @param {!Array} entryPoints + * @param {!ModuleDef} entryPoints * @return {!ModuleDef} */ function flattenGraph(entryPoints) { - // Flatten the graph by just getting all the deps from all - // the entry points. - entryPoints = entryPoints.map((entryPoint) => entryPoint.deps); - // Now make the graph have unique entries - return flatten(entryPoints).reduce((acc, cur) => { + return flatten(entryPoints.deps).reduce((acc, cur) => { const {name} = cur; if (!acc[name]) { - acc[name] = Object.keys(cur.deps) - // Get rid of the absolute path for minimatch'ing - .map((x) => cur.deps[x].replace(absPathRegExp, '')); + acc[name] = Object.values(cur.deps); } return acc; }, Object.create(null)); @@ -278,12 +252,12 @@ function flattenGraph(entryPoints) { /** * Run Module dependency graph against the rules. * - * @param {!ModuleDef} modules + * @param {!ModuleDef} moduleGraph * @return {boolean} true if violations were discovered. */ -function runRules(modules) { +function runRules(moduleGraph) { const errors = []; - Object.entries(modules).forEach(([moduleName, deps]) => { + Object.entries(moduleGraph).forEach(([moduleName, deps]) => { // Run Rules against the modules and flatten for reporting. const results = rules.flatMap((rule) => rule.run(moduleName, deps)); errors.push(...results); @@ -302,35 +276,28 @@ function runRules(modules) { return errors.length > 0; } +/** + * @return {Promise} + */ async function depCheck() { const handlerProcess = createCtrlcHandler('dep-check'); await css(); await compileJison(); - if (!isTravisBuild()) { - log('Checking dependencies...'); + logLocalDev('Checking dependencies...'); + const entryPointModule = await getEntryPointModule(); + const moduleGraph = await getModuleGraph(entryPointModule); + const errorsFound = runRules(flattenGraph(moduleGraph)); + if (errorsFound) { + log( + yellow('NOTE:'), + 'Invalid dependencies must be removed, while valid ones must be added to', + cyan('build-system/test-configs/dep-check-config.js') + ); + throw new Error('Dependency checks failed'); + } else { + logLocalDev(green('SUCCESS:'), 'Checked all dependencies.'); } - return getSrcs() - .then((entryPoints) => { - // This check is for extension folders that actually dont have - // an extension entry point module yet. - entryPoints = entryPoints.filter((x) => fs.existsSync(x)); - return Promise.all(entryPoints.map(getGraph)); - }) - .then(flattenGraph) - .then(runRules) - .then((errorsFound) => { - if (errorsFound) { - log( - yellow('NOTE:'), - 'Valid dependencies should be added whereas unused ones should be deleted. Please fix', - cyan('build-system/test-configs/dep-check-config.js') - ); - const reason = new Error('Dependency checks failed'); - reason.showStack = false; - return Promise.reject(reason); - } - }) - .then(() => exitCtrlcHandler(handlerProcess)); + exitCtrlcHandler(handlerProcess); } /** diff --git a/build-system/tasks/dev-dashboard-tests.js b/build-system/tasks/dev-dashboard-tests.js index b8d2a858fd781..04a0709dbd4c4 100644 --- a/build-system/tasks/dev-dashboard-tests.js +++ b/build-system/tasks/dev-dashboard-tests.js @@ -18,7 +18,7 @@ const config = require('../test-configs/config'); const globby = require('globby'); const Mocha = require('mocha'); -const {isTravisBuild} = require('../common/travis'); +const {isCiBuild} = require('../common/ci'); /** * Run all the dev dashboard tests @@ -26,7 +26,7 @@ const {isTravisBuild} = require('../common/travis'); */ async function devDashboardTests() { const mocha = new Mocha({ - reporter: isTravisBuild() ? 'mocha-silent-reporter' : 'spec', + reporter: isCiBuild() ? 'mocha-silent-reporter' : 'spec', }); // Add our files diff --git a/build-system/tasks/dist.js b/build-system/tasks/dist.js index 7501e55300a7c..1e0a42fbb833d 100644 --- a/build-system/tasks/dist.js +++ b/build-system/tasks/dist.js @@ -14,11 +14,10 @@ * limitations under the License. */ -const colors = require('ansi-colors'); -const file = require('gulp-file'); +const colors = require('../common/colors'); const fs = require('fs-extra'); -const gulp = require('gulp'); -const log = require('fancy-log'); +const globby = require('globby'); +const path = require('path'); const { bootstrapThirdPartyFrames, compileAllJs, @@ -26,11 +25,13 @@ const { compileJs, endBuildStep, maybeToEsmName, - mkdirSync, printConfigHelp, printNobuildHelp, - toPromise, } = require('./helpers'); +const { + cleanupBuildDir, + printClosureConcurrency, +} = require('../compile/compile'); const { createCtrlcHandler, exitCtrlcHandler, @@ -38,20 +39,15 @@ const { const { displayLifecycleDebugging, } = require('../compile/debug-compilation-lifecycle'); -const { - distNailgunPort, - startNailgunServer, - stopNailgunServer, -} = require('./nailgun'); const {buildExtensions, parseExtensionFlags} = require('./extension-helpers'); -const {cleanupBuildDir} = require('../compile/compile'); -const {compileCss, cssEntryPoints} = require('./css'); +const {buildVendorConfigs} = require('./3p-vendor-helpers'); +const {compileCss, copyCss} = require('./css'); const {compileJison} = require('./compile-jison'); const {formatExtractedMessages} = require('../compile/log-messages'); -const {maybeUpdatePackages} = require('./update-packages'); +const {log} = require('../common/logging'); const {VERSION} = require('../compile/internal-version'); -const {green, cyan} = colors; +const {cyan, green} = colors; const argv = require('minimist')(process.argv.slice(2)); /** @@ -73,7 +69,7 @@ const WEB_PUSH_PUBLISHER_VERSIONS = ['0.1']; const hostname = argv.hostname || 'cdn.ampproject.org'; /** - * Prints a useful help message prior to the gulp dist task + * Prints a useful help message prior to the amp dist task * * @param {!Object} options */ @@ -82,7 +78,7 @@ function printDistHelp(options) { throw new Error('--sanitize_vars_for_diff requires --pseudo_names'); } - let cmd = 'gulp dist'; + let cmd = 'amp dist'; if (options.fortesting) { cmd = cmd + ' --fortesting'; } @@ -99,7 +95,7 @@ function printDistHelp(options) { /** * Perform the prerequisite steps before starting the minified build. - * Used by `gulp` and `gulp dist`. + * Used by `amp` and `amp dist`. * * @param {!Object} options */ @@ -107,16 +103,15 @@ async function runPreDistSteps(options) { cleanupBuildDir(); await prebuild(); await compileCss(options); - await compileJison(); await copyCss(); + await compileJison(); await copyParsers(); await bootstrapThirdPartyFrames(options); - await startNailgunServer(distNailgunPort, /* detached */ false); displayLifecycleDebugging(); } /** - * Minified build. Entry point for `gulp dist`. + * Minified build. Entry point for `amp dist`. */ async function dist() { await doDist(); @@ -128,7 +123,6 @@ async function dist() { * @param {Object=} extraArgs */ async function doDist(extraArgs = {}) { - maybeUpdatePackages(); const handlerProcess = createCtrlcHandler('dist'); process.env.NODE_ENV = 'production'; const options = { @@ -136,11 +130,12 @@ async function doDist(extraArgs = {}) { minify: true, watch: argv.watch, }; + printClosureConcurrency(); printNobuildHelp(); printDistHelp(options); await runPreDistSteps(options); - // Steps that use closure compiler. Small ones before large (parallel) ones. + // These steps use closure compiler. Small ones before large (parallel) ones. if (argv.core_runtime_only) { await compileCoreRuntime(options); } else { @@ -148,16 +143,24 @@ async function doDist(extraArgs = {}) { await buildLoginDone('0.1'); await buildWebPushPublisherFiles(); await compileAllJs(options); - await buildExtensions(options); - } - if (!argv.watch) { - await stopNailgunServer(distNailgunPort); } - if (!argv.core_runtime_only) { - await formatExtractedMessages(); - await generateFileListing(); + // This step internally parses the various extension* flags. + await buildExtensions(options); + + // This step is to be run only during a full `amp dist`. + if ( + !argv.core_runtime_only && + !argv.extensions && + !argv.extensions_from && + !argv.noextensions + ) { + await buildVendorConfigs(options); } + + // This step is required no matter which binaries are built. + await formatExtractedMessages(); + if (!argv.watch) { exitCtrlcHandler(handlerProcess); } @@ -165,11 +168,9 @@ async function doDist(extraArgs = {}) { /** * Build AMP experiments.js. - * - * @return {!Promise} */ -function buildExperiments() { - return compileJs( +async function buildExperiments() { + await compileJs( './build/experiments/', 'experiments.max.js', './dist.tools/experiments/', @@ -178,7 +179,6 @@ function buildExperiments() { minify: true, includePolyfills: true, minifiedName: maybeToEsmName('experiments.js'), - esmPassCompilation: argv.esm || false, } ); } @@ -190,20 +190,19 @@ function buildExperiments() { * @return {!Promise} */ function buildLoginDone(version) { - const buildDir = `build/all/amp-access-${version}/`; + const buildDir = `build/all/amp-access-${version}`; const builtName = `amp-login-done-${version}.max.js`; const minifiedName = `amp-login-done-${version}.js`; const latestName = 'amp-login-done-latest.js'; - return compileJs('./' + buildDir, builtName, './dist/v0/', { + return compileJs(`./${buildDir}`, builtName, './dist/v0/', { watch: argv.watch, includePolyfills: true, minify: true, minifiedName, latestName, - esmPassCompilation: argv.esm || false, extraGlobs: [ - buildDir + 'amp-login-done-0.1.max.js', - buildDir + 'amp-login-done-dialog.js', + `${buildDir}/amp-login-done-0.1.max.js`, + `${buildDir}/amp-login-done-dialog.js`, ], }); } @@ -213,212 +212,142 @@ function buildLoginDone(version) { */ async function buildWebPushPublisherFiles() { const distDir = 'dist/v0'; - const promises = []; - WEB_PUSH_PUBLISHER_VERSIONS.forEach((version) => { - WEB_PUSH_PUBLISHER_FILES.forEach((fileName) => { - const tempBuildDir = `build/all/amp-web-push-${version}/`; - const builtName = fileName + '.js'; - const minifiedName = maybeToEsmName(fileName + '.js'); - const p = compileJs('./' + tempBuildDir, builtName, './' + distDir, { + for (const version of WEB_PUSH_PUBLISHER_VERSIONS) { + for (const fileName of WEB_PUSH_PUBLISHER_FILES) { + const tempBuildDir = `build/all/amp-web-push-${version}`; + const builtName = `${fileName}.js`; + const minifiedName = maybeToEsmName(builtName); + await compileJs(`./${tempBuildDir}`, builtName, `./${distDir}`, { watch: argv.watch, includePolyfills: true, minify: true, - esmPassCompilation: argv.esm || false, minifiedName, - extraGlobs: [tempBuildDir + '*.js'], + extraGlobs: [`${tempBuildDir}/*.js`], }); - promises.push(p); - }); - }); - await Promise.all(promises); + } + } await postBuildWebPushPublisherFilesVersion(); } +/** + * @return {Promise} + */ async function prebuild() { await preBuildExperiments(); await preBuildLoginDone(); await preBuildWebPushPublisherFiles(); } -/** - * Copies the css from the build folder to the dist folder - * @return {!Promise} - */ -function copyCss() { - const startTime = Date.now(); - - cssEntryPoints.forEach(({outCss}) => { - fs.copySync(`build/css/${outCss}`, `dist/${outCss}`); - }); - - return toPromise( - gulp - .src('build/css/amp-*.css', {base: 'build/css/'}) - .pipe(gulp.dest('dist/v0')) - ).then(() => { - endBuildStep('Copied', 'build/css/*.css to dist/v0/*.css', startTime); - }); -} - /** * Copies parsers from the build folder to the dist folder - * @return {!Promise} - */ -function copyParsers() { - const startTime = Date.now(); - return fs.copy('build/parsers', 'dist/v0').then(() => { - endBuildStep('Copied', 'build/parsers/ to dist/v0', startTime); - }); -} - -/** - * Obtain a recursive file listing of a directory - * @param {string} dest - Directory to be scanned - * @return {Array} - All files found in directory */ -async function walk(dest) { - const filelist = []; - const files = await fs.readdir(dest); - - for (let i = 0; i < files.length; i++) { - const file = `${dest}/${files[i]}`; - - fs.statSync(file).isDirectory() - ? Array.prototype.push.apply(filelist, await walk(file)) - : filelist.push(file); - } - - return filelist; -} - -/** - * Generate a listing of all files in dist/ and save as dist/files.txt - */ -async function generateFileListing() { +async function copyParsers() { const startTime = Date.now(); - const distDir = 'dist'; - const filesOut = `${distDir}/files.txt`; - fs.writeFileSync(filesOut, ''); - const files = (await walk(distDir)).map((f) => f.replace(`${distDir}/`, '')); - fs.writeFileSync(filesOut, files.join('\n') + '\n'); - endBuildStep('Generated', filesOut, startTime); + await fs.copy('build/parsers', 'dist/v0'); + endBuildStep('Copied', 'build/parsers/ to dist/v0', startTime); } /** * Build amp-web-push publisher files HTML page. - * - * @return {!Promise} */ async function preBuildWebPushPublisherFiles() { - mkdirSync('dist'); - mkdirSync('dist/v0'); - const promises = []; - - WEB_PUSH_PUBLISHER_VERSIONS.forEach((version) => { - WEB_PUSH_PUBLISHER_FILES.forEach((fileName) => { - const basePath = `extensions/amp-web-push/${version}/`; - const tempBuildDir = `build/all/amp-web-push-${version}/`; + for (const version of WEB_PUSH_PUBLISHER_VERSIONS) { + for (const fileName of WEB_PUSH_PUBLISHER_FILES) { + const srcPath = `extensions/amp-web-push/${version}`; + const destPath = `build/all/amp-web-push-${version}`; // Build Helper Frame JS - const js = fs.readFileSync(basePath + fileName + '.js', 'utf8'); - const builtName = fileName + '.js'; - const promise = toPromise( - gulp - .src(basePath + '/*.js', {base: basePath}) - .pipe(file(builtName, js)) - .pipe(gulp.dest(tempBuildDir)) + const js = await fs.readFile(`${srcPath}/${fileName}.js`, 'utf8'); + const builtName = `${fileName}.js`; + await fs.outputFile(`${destPath}/${builtName}`, js); + const jsFiles = globby.sync(`${srcPath}/*.js`); + await Promise.all( + jsFiles.map((jsFile) => { + return fs.copy(jsFile, `${destPath}/${path.basename(jsFile)}`); + }) ); - promises.push(promise); - }); - }); - return Promise.all(promises); + } + } } /** * post Build amp-web-push publisher files HTML page. */ -function postBuildWebPushPublisherFilesVersion() { +async function postBuildWebPushPublisherFilesVersion() { const distDir = 'dist/v0'; - WEB_PUSH_PUBLISHER_VERSIONS.forEach((version) => { - const basePath = `extensions/amp-web-push/${version}/`; - WEB_PUSH_PUBLISHER_FILES.forEach((fileName) => { - const minifiedName = maybeToEsmName(fileName + '.js'); - if (!fs.existsSync(distDir + '/' + minifiedName)) { - throw new Error(`Cannot find ${distDir}/${minifiedName}`); + for (const version of WEB_PUSH_PUBLISHER_VERSIONS) { + const basePath = `extensions/amp-web-push/${version}`; + for (const fileName of WEB_PUSH_PUBLISHER_FILES) { + const minifiedName = maybeToEsmName(`${fileName}.js`); + const minifiedFile = `${distDir}/${minifiedName}`; + if (!fs.existsSync(minifiedFile)) { + throw new Error(`Cannot find ${minifiedFile}`); } // Build Helper Frame HTML - let fileContents = fs.readFileSync(basePath + fileName + '.html', 'utf8'); - fileContents = fileContents.replace( - '', - '' + const html = await fs.readFile(`${basePath}/${fileName}.html`, 'utf8'); + const js = await fs.readFile(minifiedFile, 'utf8'); + const minifiedHtml = html.replace( + ``, + `` ); - - fs.writeFileSync('dist/v0/' + fileName + '.html', fileContents); - }); - }); + await fs.outputFile(`dist/v0/${fileName}.html`, minifiedHtml); + } + } } /** * Precompilation steps required to build experiment js binaries. - * @return {!Promise} */ async function preBuildExperiments() { - const path = 'tools/experiments'; - const htmlPath = path + '/experiments.html'; - const jsPath = path + '/experiments.js'; + const expDir = 'tools/experiments'; + const htmlDestDir = 'dist.tools/experiments'; + const htmlSrcPath = `${expDir}/experiments.html`; + const jsSrcPath = `${expDir}/experiments.js`; // Build HTML. - const html = fs.readFileSync(htmlPath, 'utf8'); + const html = await fs.readFile(htmlSrcPath, 'utf8'); const minHtml = html .replace( '/dist.tools/experiments/experiments.js', `https://${hostname}/v0/experiments.js` ) .replace(/\$internalRuntimeVersion\$/g, VERSION); - - await toPromise( - gulp - .src(htmlPath) - .pipe(file('experiments.cdn.html', minHtml)) - .pipe(gulp.dest('dist.tools/experiments/')) - ); + await fs.outputFile(`${htmlDestDir}/experiments.cdn.html`, minHtml); + await fs.copy(htmlSrcPath, `${htmlDestDir}/${path.basename(htmlSrcPath)}`); // Build JS. - const js = fs.readFileSync(jsPath, 'utf8'); + const jsDir = 'build/experiments/'; + const js = await fs.readFile(jsSrcPath, 'utf8'); const builtName = 'experiments.max.js'; - return toPromise( - gulp - .src(path + '/*.js') - .pipe(file(builtName, js)) - .pipe(gulp.dest('build/experiments/')) + await fs.outputFile(`${jsDir}/${builtName}`, js); + const jsFiles = globby.sync(`${expDir}/*.js`); + await Promise.all( + jsFiles.map((jsFile) => { + return fs.copy(jsFile, `${jsDir}/${path.basename(jsFile)}`); + }) ); } /** * Build "Login Done" page. - * @return {!Promise} */ -function preBuildLoginDone() { - return preBuildLoginDoneVersion('0.1'); +async function preBuildLoginDone() { + await preBuildLoginDoneVersion('0.1'); } /** * Build "Login Done" page for the specified version. - * * @param {string} version - * @return {!Promise} */ -function preBuildLoginDoneVersion(version) { - const path = `extensions/amp-access/${version}/`; - const buildDir = `build/all/amp-access-${version}/`; - const htmlPath = path + 'amp-login-done.html'; - const jsPath = path + 'amp-login-done.js'; +async function preBuildLoginDoneVersion(version) { + const srcDir = `extensions/amp-access/${version}`; + const buildDir = `build/all/amp-access-${version}`; + const htmlPath = `${srcDir}/amp-login-done.html`; + const jsPath = `${srcDir}/amp-login-done.js`; // Build HTML. - const html = fs.readFileSync(htmlPath, 'utf8'); + const html = await fs.readFile(htmlPath, 'utf8'); const minJs = `https://${hostname}/v0/amp-login-done-${version}.js`; const minHtml = html .replace(`../../../dist/v0/amp-login-done-${version}.max.js`, minJs) @@ -426,20 +355,17 @@ function preBuildLoginDoneVersion(version) { if (minHtml.indexOf(minJs) == -1) { throw new Error('Failed to correctly set JS in login-done.html'); } - - mkdirSync('dist'); - mkdirSync('dist/v0'); - - fs.writeFileSync('dist/v0/amp-login-done-' + version + '.html', minHtml); + await fs.outputFile(`dist/v0/amp-login-done-${version}.html`, minHtml); // Build JS. - const js = fs.readFileSync(jsPath, 'utf8'); - const builtName = 'amp-login-done-' + version + '.max.js'; - return toPromise( - gulp - .src(path + '/*.js', {base: path}) - .pipe(file(builtName, js)) - .pipe(gulp.dest(buildDir)) + const js = await fs.readFile(jsPath, 'utf8'); + const builtName = `amp-login-done-${version}.max.js`; + await fs.outputFile(`${buildDir}/${builtName}`, js); + const jsFiles = globby.sync(`${srcDir}/*.js`); + await Promise.all( + jsFiles.map((jsFile) => { + return fs.copy(jsFile, `${buildDir}/${path.basename(jsFile)}`); + }) ); } @@ -455,30 +381,32 @@ dist.description = 'Compiles AMP production binaries and applies AMP_CONFIG to runtime files'; dist.flags = { pseudo_names: - ' Compiles with readable names. ' + + 'Compiles with readable names. ' + 'Great for profiling and debugging production code.', pretty_print: - ' Outputs compiled code with whitespace. ' + + 'Outputs compiled code with whitespace. ' + 'Great for debugging production code.', - fortesting: ' Compiles production binaries for local testing', - noconfig: ' Compiles production binaries without applying AMP_CONFIG', - config: ' Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', - extensions: ' Builds only the listed extensions.', - extensions_from: ' Builds only the extensions from the listed AMP(s).', - noextensions: ' Builds with no extensions.', - core_runtime_only: ' Builds only the core runtime.', - full_sourcemaps: ' Includes source code content in sourcemaps', - disable_nailgun: - " Doesn't use nailgun to invoke closure compiler (much slower)", - sourcemap_url: ' Sets a custom sourcemap URL with placeholder {version}', - type: ' Points sourcemap to fetch files from the correct GitHub tag', - esm: ' Does not transpile down to ES5', - version_override: ' Override the version written to AMP_CONFIG', - watch: ' Watches for changes in files, re-compiles when detected', - closure_concurrency: ' Sets the number of concurrent invocations of closure', - debug: ' Outputs the file contents during compilation lifecycles', + fortesting: 'Compiles production binaries for local testing', + noconfig: 'Compiles production binaries without applying AMP_CONFIG', + config: 'Sets the runtime\'s AMP_CONFIG to one of "prod" or "canary"', + coverage: 'Instruments compiled code for collecting coverage information', + extensions: 'Builds only the listed extensions.', + extensions_from: 'Builds only the extensions from the listed AMP(s).', + noextensions: 'Builds with no extensions.', + core_runtime_only: 'Builds only the core runtime.', + full_sourcemaps: 'Includes source code content in sourcemaps', + sourcemap_url: 'Sets a custom sourcemap URL with placeholder {version}', + type: 'Points sourcemap to fetch files from the correct GitHub tag', + esm: 'Does not transpile down to ES5', + version_override: 'Override the version written to AMP_CONFIG', + watch: 'Watches for changes in files, re-compiles when detected', + closure_concurrency: 'Sets the number of concurrent invocations of closure', + debug: 'Outputs the file contents during compilation lifecycles', define_experiment_constant: - ' Builds runtime with the EXPERIMENT constant set to true', + 'Builds runtime with the EXPERIMENT constant set to true', sanitize_vars_for_diff: - ' Sanitize the output to diff build results. Requires --pseudo_names', + 'Sanitize the output to diff build results. Requires --pseudo_names', + sxg: 'Outputs the compiled code for the SxG build', + warning_level: + "Optionally sets closure's warning level to one of [quiet, default, verbose]", }; diff --git a/build-system/tasks/e2e/DEBUGGING.md b/build-system/tasks/e2e/DEBUGGING.md index c08f927b4e926..a280203cfe726 100644 --- a/build-system/tasks/e2e/DEBUGGING.md +++ b/build-system/tasks/e2e/DEBUGGING.md @@ -1,8 +1,8 @@ ## Debugging AMP E2E tests ```sh -node --inspect-brk $(which gulp) e2e -node --inspect-brk $(which gulp) e2e --nobuild # ... etc +node --inspect-brk $(which amp) e2e +node --inspect-brk $(which amp) e2e --nobuild # ... etc ``` Include any flags after `e2e` like normal. diff --git a/build-system/tasks/e2e/OWNERS b/build-system/tasks/e2e/OWNERS index d3a1b00d7fa5f..d7634080ca7f9 100644 --- a/build-system/tasks/e2e/OWNERS +++ b/build-system/tasks/e2e/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/tasks/e2e/README.md b/build-system/tasks/e2e/README.md index e7308a9f87094..235cbb5686456 100644 --- a/build-system/tasks/e2e/README.md +++ b/build-system/tasks/e2e/README.md @@ -2,22 +2,30 @@ AMP contributors embrace testing to maintain confidence that their code is executing correctly during development and maintenance of features and fixes. End-to-end (or E2E) tests aim to closely reproduce how a user would interact with a document as possible. -- [What is an end-to-end test?](#what-is-an-end-to-end-test) -- [Choosing which features to test](#choosing-which-features-to-test) -- [Writing E2E tests](#writing-e2e-tests) -- [Debugging E2E tests](#debugging-e2e-tests) + + + + +- [What is an end-to-end test?](#what-is-an-end-to-end-test) +- [Choosing which features to test](#choosing-which-features-to-test) +- [Writing E2E tests](#writing-e2e-tests) +- [Debugging E2E tests](#debugging-e2e-tests) This document is a usage guide. For full test command documentation, consult the following resource: -- [Information on executing tests](../../../contributing/TESTING.md) +- [Information on executing tests](../../../docs/testing.md) ## What is an end-to-end test? Let's compare the test types available to AMP contributors: -- Unit tests -- Integration tests -- End-to-end tests +- Unit tests +- Integration tests +- End-to-end tests Unit tests are useful for testing individual behaviors of a feature or fix. They are cheap to execute since they use mocks heavily to eliminate dependencies that need to also be executed during testing. These should be the most common type of test for a feature. These should fail rarely for reasons other than bugs. @@ -35,13 +43,13 @@ End-to-end tests are able to test a full page as the user's browser would load i End-to-end tests should verify the most important user flows of a component. Prioritize tests for behaviors that would make the page appear very obviously broken if there was a failure. For example: -- The component's initial render -- Primary user interactions - - e.g. clicking the next button on a carousel -- Features with heavy usage by a large number of AMP publishers - - e.g. loading more content at the bottom of an amp page -- Important behaviors that are frequently broken - - e.g. browser updates often break video autoplay behavior +- The component's initial render +- Primary user interactions + - e.g. clicking the next button on a carousel +- Features with heavy usage by a large number of AMP publishers + - e.g. loading more content at the bottom of an amp page +- Important behaviors that are frequently broken + - e.g. browser updates often break video autoplay behavior ## Writing E2E tests @@ -152,7 +160,7 @@ E2E test code runs in `node` and the code under test runs in the browser with th ### node inspect ```sh -node --inspect-brk $(which gulp) e2e --nobuild --testnames --files=extensions/amp-foo/0.1/test-e2e/test-amp-foo-basic.js +node --inspect-brk $(which amp) e2e --nobuild --testnames --files=extensions/amp-foo/0.1/test-e2e/test-amp-foo-basic.js ``` Open Chrome DevTools and click the Node logo in the top left. @@ -185,4 +193,4 @@ In the Node debugger, the `repl` global provides a reference to the test control ### watch mode -Debug tests in `watch` mode with the `--watch` flag. This will allow you to make changes to test files without having to rerun the `gulp e2e` task. +Debug tests in `watch` mode with the `--watch` flag. This will allow you to make changes to test files without having to rerun the `amp e2e` task. diff --git a/build-system/tasks/e2e/amp-driver.js b/build-system/tasks/e2e/amp-driver.js index 1b7727e69be82..9a4c26ed3aa3a 100644 --- a/build-system/tasks/e2e/amp-driver.js +++ b/build-system/tasks/e2e/amp-driver.js @@ -18,6 +18,7 @@ const AmpdocEnvironment = { SINGLE: 'single', VIEWER_DEMO: 'viewer-demo', + EMAIL_DEMO: 'email-demo', SHADOW_DEMO: 'shadow-demo', // AMPHTML ads environments @@ -32,7 +33,7 @@ const HOST = 'http://localhost:8000'; const EnvironmentBehaviorMap = { [AmpdocEnvironment.SINGLE]: { - ready(unusedController) { + ready() { return Promise.resolve(); }, @@ -49,21 +50,18 @@ const EnvironmentBehaviorMap = { }, url(url) { - const defaultCaps = [ - 'a2a', - 'focus-rect', - 'foo', - 'keyboard', - 'swipe', - 'iframeScroll', - ]; - // Correctly append extra params in original url - url = url.replace('#', '&'); - // TODO(estherkim): somehow allow non-8000 port and domain - return ( - `http://localhost:8000/test/fixtures/e2e/amp-viewer-integration/viewer.html#href=${url}` + - `&caps=${defaultCaps.join(',')}` - ); + return getViewerUrl(url); + }, + }, + + [AmpdocEnvironment.EMAIL_DEMO]: { + ready(controller) { + return controller + .findElement('#viewer[data-loaded]') + .then((frame) => controller.switchToFrame(frame)); + }, + url(url) { + return getViewerUrl(url, {isEmail: true}); }, }, @@ -135,12 +133,36 @@ const EnvironmentBehaviorMap = { }, }; +/** + * @param {string} url + * @param {{isEmail: boolean}=} opts + * @return {string} + */ +function getViewerUrl(url, {isEmail} = {isEmail: false}) { + const defaultCaps = [ + 'a2a', + 'focus-rect', + 'foo', + 'keyboard', + 'swipe', + 'iframeScroll', + ]; + // Correctly append extra params in original url + url = url.replace('#', '&'); + // TODO(estherkim): somehow allow non-8000 port and domain + return ( + `http://localhost:8000/test/fixtures/e2e/amp-viewer-integration/viewer.html#href=${url}` + + `&caps=${defaultCaps.join(',')}` + + `&isEmail=${isEmail}` + ); +} + /** * Provides AMP-related utilities for E2E Functional Tests. */ class AmpDriver { /** - * @param {!../functional-test-controller.FunctionalTestController} controller + * @param {!*} controller */ constructor(controller) { /** @private @const */ diff --git a/build-system/tasks/e2e/controller-promise.js b/build-system/tasks/e2e/controller-promise.js index 8d4964e3c5091..0fdf7f865f584 100644 --- a/build-system/tasks/e2e/controller-promise.js +++ b/build-system/tasks/e2e/controller-promise.js @@ -22,14 +22,19 @@ * the new values that come from the browser. * * @template TYPE - * @extends {Promise} + * @extends {Promise} */ -class ControllerPromise { +class ControllerPromise extends Promise { /** - * @param {function(function(?TYPE):void, function(*):void):void|!Promise} executorOrPromise - * @param {function(TYPE,function(TYPE): ?TYPE): !Promise=} opt_waitForValue + * @param {Promise|function(function(?TYPE):void, function(*):void):void} executorOrPromise + * @param {undefined|function(TYPE,function(TYPE): ?TYPE): Promise} opt_waitForValue */ constructor(executorOrPromise, opt_waitForValue) { + if (executorOrPromise instanceof Promise) { + super(executorOrPromise.then); + } else { + super(executorOrPromise); + } this.promise_ = typeof executorOrPromise == 'function' ? new Promise(executorOrPromise) @@ -62,9 +67,8 @@ class ControllerPromise { /** @override */ then(opt_onFulfilled, opt_onRejected) { - opt_onFulfilled = opt_onFulfilled || ((x) => x); // Allow this and future `then`s to update the wait value. - let wrappedWait = null; + let wrappedWait; if (this.waitForValue) { wrappedWait = wrapWait(this.waitForValue, opt_onFulfilled); } @@ -80,14 +84,18 @@ class ControllerPromise { * Wrap the given wait function with the given mutation function, * while still allowing it to be mutated again in the future by * the inner opt_mutate function. - * @param {function(TYPE,function(TYPE): ?TYPE): !Promise=} wait - * @param {function(TYPE): TYPE} mutate - * @return {!Promise} - * @template TYPE + * @param {function(CONDITION, function(VALUE): ?DERIVED): Promise} wait + * @param {function(VALUE): MUTANT} mutate + * @return {function(CONDITION, function(MUTANT): ?DERIVED): Promise} + * @template CONDITION + * @template MUTANT + * @template DERIVED + * @template VALUE + * @template RES */ function wrapWait(wait, mutate) { return (condition, opt_mutate) => { - opt_mutate = opt_mutate || ((x) => x); + opt_mutate = opt_mutate || ((x) => /** @type {*} */ (x)); return wait(condition, (value) => opt_mutate(mutate(value))); }; } diff --git a/build-system/tasks/e2e/describes-e2e.js b/build-system/tasks/e2e/describes-e2e.js index 06d49b92c517e..4c0b965a81209 100644 --- a/build-system/tasks/e2e/describes-e2e.js +++ b/build-system/tasks/e2e/describes-e2e.js @@ -14,12 +14,14 @@ * limitations under the License. */ // import to install chromedriver and geckodriver -require('chromedriver'); // eslint-disable-line no-unused-vars -require('geckodriver'); // eslint-disable-line no-unused-vars +require('chromedriver'); +require('geckodriver'); +const argv = require('minimist')(process.argv.slice(2)); const chrome = require('selenium-webdriver/chrome'); +const fetch = require('node-fetch'); const firefox = require('selenium-webdriver/firefox'); -const puppeteer = require('puppeteer'); +const selenium = require('selenium-webdriver'); const { clearLastExpectError, getLastExpectError, @@ -29,10 +31,10 @@ const { SeleniumWebDriverController, } = require('./selenium-webdriver-controller'); const {AmpDriver, AmpdocEnvironment} = require('./amp-driver'); -const {Builder, Capabilities, logging} = require('selenium-webdriver'); +const {HOST, PORT} = require('../serve'); const {installRepl, uninstallRepl} = require('./repl'); -const {isTravisBuild} = require('../../common/travis'); -const {PuppeteerController} = require('./puppeteer-controller'); +const {isCiBuild} = require('../../common/ci'); +const {Builder, Capabilities, logging} = selenium; /** Should have something in the name, otherwise nothing is shown. */ const SUB = ' '; @@ -40,42 +42,28 @@ const TEST_TIMEOUT = 40000; const SETUP_TIMEOUT = 30000; const SETUP_RETRIES = 3; const DEFAULT_E2E_INITIAL_RECT = {width: 800, height: 600}; +const COV_REPORT_PATH = '/coverage/client'; const supportedBrowsers = new Set(['chrome', 'firefox', 'safari']); -/** - * TODO(cvializ): Firefox now experimentally supports puppeteer. - * When it's more mature we might want to support it. - * {@link https://github.com/GoogleChrome/puppeteer/blob/master/experimental/puppeteer-firefox/README.md} - */ -const PUPPETEER_BROWSERS = new Set(['chrome']); /** - * Engine types for e2e testing. - * @enum {string} + * Load coverage middleware only if needed. */ -const EngineType = { - SELENIUM: 'selenium', - PUPPETEER: 'puppeteer', -}; +let istanbulMiddleware; +if (argv.coverage) { + istanbulMiddleware = require('istanbul-middleware/lib/core'); +} /** * @typedef {{ * browsers: string, * headless: boolean, - * engine: string, * }} */ let DescribesConfigDef; /** * @typedef {{ - * headless: boolean, - * }} - */ -let PuppeteerConfigDef; - -/** - * @typedef {{ - * headless: boolean, + * headless?: boolean, * }} */ let SeleniumConfigDef; @@ -110,35 +98,20 @@ function getConfig() { return describesConfig; } -/** - * Configure and launch a Puppeteer instance - * @param {!PuppeteerConfigDef=} opt_config - * @return {!Promise} - */ -async function createPuppeteer(opt_config = {}) { - const browser = await puppeteer.launch({ - headless: opt_config.headless || false, - devtools: false, - defaultViewport: null, - timeout: 0, - }); - return browser; -} - /** * Configure and launch a Selenium instance * @param {string} browserName * @param {!SeleniumConfigDef=} args - * @param {?string} deviceName - * @return {!WebDriver} + * @param {string=} deviceName + * @return {!selenium.WebDriver} */ function createSelenium(browserName, args = {}, deviceName) { switch (browserName) { case 'safari': // Safari's only option is setTechnologyPreview - return createDriver(browserName, []); + return createDriver(browserName, [], deviceName); case 'firefox': - return createDriver(browserName, getFirefoxArgs(args)); + return createDriver(browserName, getFirefoxArgs(args), deviceName); case 'chrome': default: return createDriver(browserName, getChromeArgs(args), deviceName); @@ -148,9 +121,9 @@ function createSelenium(browserName, args = {}, deviceName) { /** * * @param {string} browserName - * @param {!SeleniumConfigDef=} args - * @param {?string} deviceName - * @return {!WebDriver} + * @param {!string[]} args + * @param {string=} deviceName + * @return {!selenium.WebDriver} */ function createDriver(browserName, args, deviceName) { const capabilities = Capabilities[browserName](); @@ -181,6 +154,8 @@ function createDriver(browserName, args, deviceName) { //which is also when `Server terminated early with status 1` began appearing. Coincidence? Maybe. driver.onQuit = null; return driver; + case 'safari': + return new Builder().forBrowser(browserName).build(); } } @@ -222,13 +197,30 @@ function getFirefoxArgs(config) { * @typedef {{ * browsers: (!Array|undefined), * environments: (!Array|undefined), - * testUrl: string, - * initialRect: ({{width: number, height:number}}|undefined), + * experiments: (!Array|undefined), + * testUrl: string|undefined, + * fixture: string, + * initialRect: ({width: number, height:number}|undefined), * deviceName: string|undefined, + * version: string|undefined, * }} */ let TestSpec; +/** + * @typedef {{ + * browsers: (!Array|undefined), + * environments: (!Array|undefined), + * testUrl: string|undefined, + * fixture: string, + * initialRect: ({width: number, height:number}|undefined), + * deviceName: string|undefined, + * versions: {[version: string]: TestSpec}, + * version: string|undefined + * }} + */ +let RootSpec; + /** * An end2end test using Selenium Web Driver or Puppeteer */ @@ -246,6 +238,10 @@ const EnvironmentVariantMap = { name: 'Viewer environment', value: {environment: 'viewer-demo'}, }, + [AmpdocEnvironment.EMAIL_DEMO]: { + name: 'Email environment (viewer)', + value: {environment: 'email-demo'}, + }, [AmpdocEnvironment.SHADOW_DEMO]: { name: 'Shadow environment', value: {environment: 'shadow-demo'}, @@ -293,32 +289,53 @@ envPresets['ampdoc-amp4ads-preset'] = envPresets['ampdoc-preset'].concat( * it.configure().skipViewerDemo().skipShadowDemo().run('Should ...', ...); */ class ItConfig { + /** + * @param {function} it + * @param {Object} env + */ constructor(it, env) { - this.it = it; + this.it = /** @type {Mocha.it} */ (it); this.env = env; this.skip = false; } + /** + * @return {ItConfig} + */ skipShadowDemo() { this.skip = this.skip ? this.skip : this.env.environment == 'shadow-demo'; return this; } + /** + * @return {ItConfig} + */ skipSingle() { this.skip = this.skip ? this.skip : this.env.environment == 'single'; return this; } + /** + * @return {ItConfig} + */ skipViewerDemo() { this.skip = this.skip ? this.skip : this.env.environment == 'viewer-demo'; return this; } + /** + * @return {ItConfig} + */ skipA4aFie() { this.skip = this.skip ? this.skip : this.env.environment == 'a4a-fie'; return this; } + /** + * @param {string} name + * @param {function(): void} fn + * @return {void|Mocha.Test} + */ run(name, fn) { if (this.skip) { return this.it.skip(name, fn); @@ -330,20 +347,45 @@ class ItConfig { } } +/** + * Extracts code coverage data from the page and aggregates it with other tests. + * @param {!Object} env e2e driver environment. + * @return {Promise} + */ +async function updateCoverage(env) { + const coverage = await env.controller.evaluate(() => window.__coverage__); + if (coverage) { + istanbulMiddleware.mergeClientCoverage(coverage); + } +} + +/** + * Reports code coverage data to an aggregating endpoint. + * @return {Promise} + */ +async function reportCoverage() { + const coverage = istanbulMiddleware.getCoverageObject(); + await fetch(`https://${HOST}:${PORT}${COV_REPORT_PATH}`, { + method: 'POST', + body: JSON.stringify(coverage), + headers: {'Content-type': 'application/json'}, + }); +} + /** * Returns a wrapped version of Mocha's describe(), it() and only() methods * that also sets up the provided fixtures and returns the corresponding * environment objects of each fixture to the test method. - * @param {function(!Object):!Array} factory - * @return {function()} + * @param {function(!TestSpec): EndToEndFixture} factory + * @return {function(string, RootSpec, function(!Object): void): void} */ function describeEnv(factory) { /** * @param {string} suiteName * @param {!Object} spec - * @param {function(!Object)} fn - * @param {function(string, function())} describeFunc - * @return {function()} + * @param {function(!Object): void} fn + * @param {function(string, function(): void): void} describeFunc + */ const templateFunc = function (suiteName, spec, fn, describeFunc) { const fixture = factory(spec); @@ -365,9 +407,11 @@ function describeEnv(factory) { spec.browsers = ['chrome']; } + /** + * Initializes the describe object for all applicable browsers. + */ function createBrowserDescribe() { const allowedBrowsers = getAllowedBrowsers(); - spec.browsers .filter((x) => allowedBrowsers.has(x)) .forEach((browserName) => { @@ -377,24 +421,16 @@ function describeEnv(factory) { }); } + /** + * @return {Set} + */ function getAllowedBrowsers() { - const {engine, browsers} = getConfig(); + const {browsers} = getConfig(); const allowedBrowsers = browsers ? new Set(browsers.split(',').map((x) => x.trim())) : supportedBrowsers; - if (engine === EngineType.PUPPETEER) { - const result = intersect(allowedBrowsers, PUPPETEER_BROWSERS); - if (result.size === 0) { - const browsersList = Array.from(allowedBrowsers).join(','); - throw new Error( - `browsers ${browsersList} not supported by Puppeteer` - ); - } - return result; - } - if (process.platform !== 'darwin' && allowedBrowsers.has('safari')) { // silently skip safari tests allowedBrowsers.delete('safari'); @@ -403,6 +439,9 @@ function describeEnv(factory) { return allowedBrowsers; } + /** + * @param {string} browserName + */ function createVariantDescribe(browserName) { for (const name in variants) { it.configure = function () { @@ -415,28 +454,40 @@ function describeEnv(factory) { } } - return describeFunc(suiteName, function () { + describeFunc(suiteName, function () { createBrowserDescribe(); }); - - function doTemplate(name, variant, browserName) { + return; + + /** + * + * @param {string} _name + * @param {Object} variant + * @param {string} browserName + */ + function doTemplate(_name, variant, browserName) { const env = Object.create(variant); + // @ts-ignore this.timeout(TEST_TIMEOUT); beforeEach(async function () { this.timeout(SETUP_TIMEOUT); await fixture.setup(env, browserName, SETUP_RETRIES); // don't install for CI - if (!isTravisBuild()) { + if (!isCiBuild()) { installRepl(global, env); } }); afterEach(async function () { + if (argv.coverage) { + await updateCoverage(env); + } + // If there is an async expect error, throw it in the final state. const lastExpectError = getLastExpectError(); if (lastExpectError) { - this.test.error(lastExpectError); + /** @type {any} */ (this.test).error(lastExpectError); clearLastExpectError(); } @@ -445,11 +496,17 @@ function describeEnv(factory) { delete env[key]; } - if (!isTravisBuild()) { + if (!isCiBuild()) { uninstallRepl(); } }); + after(async () => { + if (argv.coverage) { + await reportCoverage(); + } + }); + describe(SUB, function () { fn.call(this, env); }); @@ -458,26 +515,53 @@ function describeEnv(factory) { /** * @param {string} name - * @param {!Object} spec - * @param {function(!Object)} fn - * @return {function()} + * @param {!RootSpec} spec + * @param {function(!Object): void} fn */ const mainFunc = function (name, spec, fn) { - return templateFunc(name, spec, fn, describe); + const {versions, ...baseSpec} = spec; + if (!versions) { + // If a version is provided, add a prefix to the test suite name. + const {version} = spec; + templateFunc( + version ? `[v${version}] ${name}` : name, + spec, + fn, + describe + ); + } else { + // A root `describes.endtoend` spec may contain a `versions` object, where + // the key represents the version number and the value is an object with + // test specs for that version. This allows specs to share test fixtures, + // browsers, and other settings. + Object.entries(versions).forEach(([version, versionSpec]) => { + const fullSpec = { + ...baseSpec, + ...versionSpec, + version, + }; + templateFunc(`[v${version}] ${name}`, fullSpec, fn, describe); + }); + } }; /** * @param {string} name * @param {!Object} spec - * @param {function(!Object)} fn - * @return {function()} + * @param {function(!Object): void} fn */ mainFunc.only = function (name, spec, fn) { - return templateFunc(name, spec, fn, describe./*OK*/ only); + templateFunc(name, spec, fn, describe./*OK*/ only); + return; }; + /** + * @param {string} name + * @param {!Object} variants + * @param {function(!Object): void} fn + */ mainFunc.skip = function (name, variants, fn) { - return templateFunc(name, variants, fn, describe.skip); + templateFunc(name, variants, fn, describe.skip); }; return mainFunc; @@ -487,7 +571,7 @@ class EndToEndFixture { /** @param {!TestSpec} spec */ constructor(spec) { /** @const */ - this.spec = spec; + this.spec = this.setTestUrl(spec); } /** @@ -498,13 +582,11 @@ class EndToEndFixture { async setup(env, browserName, retries = 0) { const config = getConfig(); const driver = getDriver(config, browserName, this.spec.deviceName); - const controller = - config.engine == EngineType.PUPPETEER - ? new PuppeteerController(driver) - : new SeleniumWebDriverController(driver); + const controller = new SeleniumWebDriverController(driver); const ampDriver = new AmpDriver(controller); env.controller = controller; env.ampDriver = ampDriver; + env.version = this.spec.version; installBrowserAssertions(controller.networkLogger); @@ -513,7 +595,7 @@ class EndToEndFixture { // Set env props that require the fixture to be set up. if (env.environment === AmpdocEnvironment.VIEWER_DEMO) { env.receivedMessages = await controller.evaluate(() => { - return window.parent.viewer.receivedMessages; + return window.parent.viewer?.receivedMessages; }); } } catch (ex) { @@ -525,6 +607,10 @@ class EndToEndFixture { } } + /** + * @param {!Object} env + * @return {Promise} + */ async teardown(env) { const {controller} = env; if (controller && controller.driver) { @@ -532,34 +618,60 @@ class EndToEndFixture { await controller.dispose(); } } + + /** + * Translate relative fixture specs into localhost test URL. + * @param {!TestSpec} spec + * @return {!TestSpec} + */ + setTestUrl(spec) { + const {fixture, testUrl} = spec; + + if (testUrl) { + throw new Error( + 'Setting `testUrl` directly is no longer permitted in e2e tests; please use `fixture` instead' + ); + } + + return { + ...spec, + testUrl: `http://localhost:8000/test/fixtures/e2e/${fixture}`, + }; + } } /** * Get the driver for the configured engine. * @param {!DescribesConfigDef} describesConfig * @param {string} browserName - * @param {?string} deviceName - * @return {!ThenableWebDriver} + * @param {string|undefined} deviceName + * @return {!selenium.WebDriver} */ -function getDriver( - {engine = EngineType.SELENIUM, headless = false}, - browserName, - deviceName -) { - if (engine == EngineType.PUPPETEER) { - return createPuppeteer({headless}); - } - - if (engine == EngineType.SELENIUM) { - return createSelenium(browserName, {headless}, deviceName); - } +function getDriver({headless = false}, browserName, deviceName) { + return createSelenium(browserName, {headless}, deviceName); } +/** + * @param {{ + * environment: *, + * ampDriver: *, + * controller: *, + * }} param0 + * @param {TestSpec} param1 + * @return {Promise} + */ async function setUpTest( - {environment, ampDriver, controller}, - {testUrl, experiments = [], initialRect} + {ampDriver, controller, environment}, + {testUrl = '', version, experiments = [], initialRect} ) { const url = new URL(testUrl); + + // When a component version is specified in the e2e spec, provide it as a + // request param. + if (version) { + url.searchParams.set('componentVersion', version); + } + if (experiments.length > 0) { if (environment.includes('inabox')) { // inabox experiments are toggled at server side using tag @@ -571,7 +683,7 @@ async function setUpTest( } if (initialRect) { - const {width, height} = initialRect; + const {height, width} = initialRect; await controller.setWindowRect({width, height}); } @@ -593,18 +705,8 @@ async function toggleExperiments(ampDriver, testUrl, experiments) { } } -/** - * Intersection of two sets - * @param {Set} a - * @param {Set} b - * @return {Set} - * @template T - */ -function intersect(a, b) { - return new Set(Array.from(a).filter((aItem) => b.has(aItem))); -} - module.exports = { + RootSpec, TestSpec, endtoend, configure, diff --git a/build-system/tasks/e2e/driver/query-xpath.js b/build-system/tasks/e2e/driver/query-xpath.js index d42b61aff98b5..9d621a2305226 100644 --- a/build-system/tasks/e2e/driver/query-xpath.js +++ b/build-system/tasks/e2e/driver/query-xpath.js @@ -57,7 +57,7 @@ function queryXpath(xpathString, context) { // Add an ID to every element in the tree so we can reference them later. // This allows us to correlate nodes from the cloned tree to the // original tree. - for (const {item, index} of createIndexedInterator( + for (const {index, item} of createIndexedInterator( createElementIterator(context) )) { setData(item, index); diff --git a/build-system/tasks/e2e/e2e-types.js b/build-system/tasks/e2e/e2e-types.js new file mode 100644 index 0000000000000..ab82eb52c5e8b --- /dev/null +++ b/build-system/tasks/e2e/e2e-types.js @@ -0,0 +1,99 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A wrapper class that allows client code to own references to + * framework-specific element handles, but which does not expose any of the + * framework-specific methods on that element. + * + * @template T + * @public + */ +class ElementHandle { + /** + * Wrap the framework-specific element handle object. + * @param {!T} element + * @package + */ + constructor(element) { + /** @private */ + this.element_ = element; + } + + /** + * Unwrap the framework-specific element handle object. + * @return {!T} + * @package + */ + getElement() { + return this.element_; + } +} + +/** + * Key codes used to trigger actions. + * @enum {string} + */ +const Key = { + 'ArrowDown': 'ArrowDown', + 'ArrowLeft': 'ArrowLeft', + 'ArrowRight': 'ArrowRight', + 'ArrowUp': 'ArrowUp', + 'Enter': 'Enter', + 'Escape': 'Escape', + 'Tab': 'Tab', + 'CtrlV': 'CtrlV', +}; + +/** + * @typedef {{ + * width: number, + * height: number + * }} WindowRectDef + */ +let WindowRectDef; + +/** + * @typedef {{ + * x: number, + * y: number, + * top: number, + * bottom: number, + * left: number, + * right: number, + * width: number, + * height: number + * }} + */ +let DOMRectDef; + +/** + * @typedef {{ + * left: number, + * top: number, + * behavior: ScrollBehavior + * }} + * {@link https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions} + */ +let ScrollToOptionsDef; + +module.exports = { + ElementHandle, + Key, + WindowRectDef, + DOMRectDef, + ScrollToOptionsDef, +}; diff --git a/build-system/tasks/e2e/expect.js b/build-system/tasks/e2e/expect.js index 3ae0368663e9b..2673841bb1e33 100644 --- a/build-system/tasks/e2e/expect.js +++ b/build-system/tasks/e2e/expect.js @@ -22,11 +22,15 @@ let installed; let lastExpectError; let networkLogger; +/** + * Clears previous expected error state. + */ function clearLastExpectError() { lastExpectError = null; } /** + * Retrieves the expected error state. * @return {?Error} */ function getLastExpectError() { @@ -36,7 +40,7 @@ function getLastExpectError() { /** * @param {*} actual * @param {string=} opt_message - * @return {!ExpectStatic} + * @return {!Chai.Assertion} */ function expect(actual, opt_message) { if (!installed) { @@ -62,7 +66,7 @@ const ChaiType = { * Not all chai properties need to be overwritten, like those that set * flags or are only language chains e.g. `not` or 'to' * See the Chai implementation for the original definitions: - * {@link https://github.com/chaijs/chai/blob/master/lib/chai/core/assertions.js} + * {@link https://github.com/chaijs/chai/blob/main/lib/chai/core/assertions.js} */ const chaiMethodsAndProperties = [ {name: 'a', type: ChaiType.CHAINABLE_METHOD}, @@ -135,11 +139,16 @@ const chaiMethodsAndProperties = [ {name: 'within', type: ChaiType.METHOD}, ]; +/** + * @param {Chai.ChaiStatic} chai + * @param {Chai.ChaiUtils} utils + */ function installWrappers(chai, utils) { - const {METHOD, PROPERTY, CHAINABLE_METHOD} = ChaiType; + const {CHAINABLE_METHOD, METHOD, PROPERTY} = ChaiType; const {Assertion} = chai; for (const {name, type, unsupported} of chaiMethodsAndProperties) { + /** @type {function(Chai.AssertionStatic): void} */ const overwrite = unsupported ? overwriteUnsupported : overwriteAlwaysUseSuper(utils); @@ -149,13 +158,14 @@ function installWrappers(chai, utils) { Assertion.overwriteMethod(name, overwrite); break; case PROPERTY: - Assertion.overwriteProperty(name, overwrite); + // TODO(#28387) cleanup this type. + Assertion.overwriteProperty(name, /** @type {*} */ (overwrite)); break; case CHAINABLE_METHOD: Assertion.overwriteChainableMethod( name, overwrite, - inheritChainingBehavior + /** @type {() => any} */ (inheritChainingBehavior) ); break; default: @@ -164,21 +174,31 @@ function installWrappers(chai, utils) { } } +/** + * @param {Chai.ChaiUtils} utils + * @return {function(Chai.AssertionStatic): function(): any} + */ function overwriteAlwaysUseSuper(utils) { const {flag} = utils; + /** + * @param {Chai.AssertionStatic} _super + * @return {function(): ReturnType} + */ return function (_super) { return function () { - const obj = this._obj; + // @ts-ignore + const that = this; + const obj = that._obj; const isControllerPromise = obj instanceof ControllerPromise; if (!isControllerPromise) { - return _super.apply(this, arguments); + return _super.apply(that, arguments); } const {waitForValue} = obj; if (!waitForValue) { return obj.then((result) => { - flag(this, 'object', result); - return _super.apply(this, arguments); + flag(that, 'object', result); + return _super.apply(that, arguments); }); } @@ -193,10 +213,10 @@ function overwriteAlwaysUseSuper(utils) { const valueSatisfiesExpectation = (value) => { try { // Tell chai to use value as the subject of the expect chain. - flag(this, 'object', value); + flag(that, 'object', value); // Run the code that checks the condition. - _super.apply(this, arguments); + _super.apply(that, arguments); clearLastExpectError(); // Let waitForValue know we are done. @@ -208,59 +228,84 @@ function overwriteAlwaysUseSuper(utils) { lastExpectError = e; return false; } finally { - flag(this, 'object', resultPromise); + flag(that, 'object', resultPromise); } }; const resultPromise = waitForValue(valueSatisfiesExpectation); - flag(this, 'object', resultPromise); + flag(that, 'object', resultPromise); return resultPromise; }; }; } +/** + * @param {Chai.AssertionStatic} _super + * @return {function(): *} + */ function inheritChainingBehavior(_super) { return function () { + // @ts-ignore _super.apply(this, arguments); }; } +/** + * @param {Chai.AssertionStatic} _super + * @return {function(): *} + */ function overwriteUnsupported(_super) { return function () { - const obj = this._obj; + // @ts-ignore + const that = this; + const obj = that._obj; const isControllerPromise = obj instanceof ControllerPromise; if (isControllerPromise) { throw new Error( 'ControllerPromise used with unsupported expectation. Await the Promise and expect the value.' ); } - return _super.apply(this, arguments); + return _super.apply(that, arguments); }; } +/** + * @param {*} _networkLogger + */ function installBrowserAssertions(_networkLogger) { networkLogger = _networkLogger; chai.use(installBrowserWrappers); } +/** + * @param {Chai.ChaiStatic} chai + * @param {Chai.ChaiUtils} utils + */ function installBrowserWrappers(chai, utils) { const {Assertion} = chai; // Assert that a request with a testUrl was sent // Example usage: await expect(testUrl).to.have.been.sent; utils.addProperty(Assertion.prototype, 'sent', async function () { - const url = this._obj; + // @ts-ignore + const that = this; + const url = that._obj; const requests = await networkLogger.getSentRequests(url); - this.assert(0 < requests.length, 'expected #{this} to have been sent'); + that.assert(0 < requests.length, 'expected #{this} to have been sent'); }); - Assertion.overwriteProperty('sent', overwriteAlwaysUseSuper(utils)); + Assertion.overwriteProperty( + 'sent', + /** @type {any} */ (overwriteAlwaysUseSuper(utils)) + ); // Assert that a request was sent n number of times // Example usage: await expect(testUrl).to.have.sentCount(n); utils.addMethod(Assertion.prototype, 'sentCount', async function (count) { - const url = this._obj; + // @ts-ignore + const that = this; + const url = that._obj; const requests = await networkLogger.getSentRequests(url); - this.assert( + that.assert( count === requests.length, `expected #{this} to have been sent ${ count == 1 ? 'once' : count + ' times' diff --git a/build-system/tasks/e2e/functional-test-controller.js b/build-system/tasks/e2e/functional-test-controller.js deleted file mode 100644 index 934e21081d4f7..0000000000000 --- a/build-system/tasks/e2e/functional-test-controller.js +++ /dev/null @@ -1,469 +0,0 @@ -/** - * Copyright 2019 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. - */ - -/** - * A wrapper class that allows client code to own references to - * framework-specific element handles, but which does not expose any of the - * framework-specific methods on that element. - * - * @template T - * @public - */ -class ElementHandle { - /** - * Wrap the framework-specific element handle object. - * @param {!T} element - * @param {!T} unusedController - * @package - */ - constructor(element, unusedController) { - /** @private */ - this.element_ = element; - } - - /** - * Unwrap the framework-specific element handle object. - * @return {!T} - * @package - */ - getElement() { - return this.element_; - } -} - -/** - * Key to send to the FunctionalTestController#type method to trigger - * actions instead of text. - * @enum {string} - */ -const Key = { - 'ArrowDown': 'ArrowDown', - 'ArrowLeft': 'ArrowLeft', - 'ArrowRight': 'ArrowRight', - 'ArrowUp': 'ArrowUp', - 'Enter': 'Enter', - 'Escape': 'Escape', - 'Tab': 'Tab', - 'CtrlV': 'CtrlV', -}; - -/** @interface */ -class FunctionalTestController { - /** - * Navigates to the given URL. - * {@link https://www.w3.org/TR/webdriver1/#navigate-to} - - * @param {string} unusedUrl - * @return {!Promise} - */ - async navigateTo(unusedUrl) {} - - /** - * Retrieves the URL for the current page. - * {@link https://www.w3.org/TR/webdriver1/#get-current-url} - * - * @return {!ControllerPromise} - */ - async getCurrentUrl() {} - - /** - * Returns the document title. - * {@link https://www.w3.org/TR/webdriver1/#get-title} - * - * @return {!ControllerPromise} - */ - async getTitle() {} - - /** - * Select the given window. - * {@link https://www.w3.org/TR/webdriver1/#dfn-switch-to-window} - * - * @param {string} unusedString - * @return {!Promise} - */ - async switchToWindow(unusedString) {} - - /** - * Selects the current top-level browsing context or a child browsing context - * of the current browsing context to use as the current browsing context for - * subsequent commands. - * {@link https://www.w3.org/TR/webdriver1/#switch-to-frame} - * - * @param {!ElementHandle} unusedHandle - * @return {!Promise} - */ - async switchToFrame(unusedHandle) {} - - /** - * Selects the current top-level browsing context or a child browsing context - * of the current browsing context to use as the current browsing context for - * subsequent commands. - * {@link https://www.w3.org/TR/webdriver1/#switch-to-frame} - * - * @return {!Promise} - */ - async switchToParent() {} - - /** - * Selects the body of a subtree inside a ShadowDOM ShadowRoot to use as the current - * browsing context for subsequent commands. - * {@link https://github.com/w3c/webdriver/pull/1320} - * https://github.com/SeleniumHQ/selenium/issues/5869 - * - * @param {!ElementHandle} unusedHandle - * @return {!Promise} - */ - async switchToShadow(unusedHandle) {} - - /** - * Selects a subtree inside a ShadowDOM ShadowRoot to use as the current - * browsing context for subsequent commands. - * {@link https://github.com/w3c/webdriver/pull/1320} - * https://github.com/SeleniumHQ/selenium/issues/5869 - * - * @param {!ElementHandle} unusedHandle - * @return {!Promise} - */ - async switchToShadowRoot(unusedHandle) {} - - /** - * Selects the main top-level DOM tree to use as the current - * browsing context for subsequent commands. - * {@link https://github.com/w3c/webdriver/pull/1320} - * https://github.com/SeleniumHQ/selenium/issues/5869 - * - * @return {!Promise} - */ - async switchToLight() {} - - /** - * Gets the active element of the current browsing context’s document element. - * {@link https://www.w3.org/TR/webdriver1/#get-active-element} - * - * @return {!ControllerPromise} - */ - async getActiveElement() {} - - /** - * Gets the root of the current document, for use in scrolling e.g. - * @return {!Promise} - */ - async getDocumentElement() {} - - /** - * The Find Element command is used to find the first element matching the - * given selector in the current browsing context that can be used as the - * web element context for future element-centric commands. - * {@link https://www.w3.org/TR/webdriver1/#find-element} - * - * @param {string} unusedSelector - * @param {number=} unusedTimeout - * @return {!Promise} - */ - async findElement(unusedSelector, unusedTimeout) {} - - /** - * The Find Elements command is used to find all elements matching the - * given selector in the current browsing context that can be used as the - * web element context for future element-centric commands. - * {@link https://www.w3.org/TR/webdriver1/#find-elements} - * - * @param {string} unusedSelector - * @return {!Promise>} - */ - async findElements(unusedSelector) {} - - /** - * Note: Use findElement instead where possible. CSS selectors are more - * familiar to developers and easier to reason about. If you need to use - * this method, a comment explaining the xpath query can be helpful. - * The Find Element command is used to find the first element matching the - * given xpath in the current browsing context that can be used as the - * web element context for future element-centric commands. - * {@link https://www.w3.org/TR/webdriver1/#xpath} - * {@link https://www.w3.org/TR/webdriver1/#find-element} - * - * @param {string} unusedXpath - * @return {!Promise} - */ - async findElementXPath(unusedXpath) {} - - /** - * Note: Use findElements instead where possible. CSS selectors are more - * familiar to developers and easier to reason about. If you need to use - * this method, a comment explaining the xpath query can be helpful. - * The Find Elements command is used to find all elements matching the - * given xpath in the current browsing context that can be used as the - * web element context for future element-centric commands. - * {@link https://www.w3.org/TR/webdriver1/#xpath} - * {@link https://www.w3.org/TR/webdriver1/#find-elements} - * - * @param {string} unusedXpath - * @return {!Promise>} - */ - async findElementsXPath(unusedXpath) {} - - /** - * The Find Element From Element command is used to find the first element - * that is a descendent of the given element matching the given selector in - * the current browsing context that can be used as the web element context - * for future element-centric commands. - * {@link https://www.w3.org/TR/webdriver1/#find-element-from-element} - * - * @param {!ElementHandle} unusedHandle - * @param {string} unusedSelector - * @return {!Promise} - */ - async findElementFromElement(unusedHandle, unusedSelector) {} - - /** - * The Find Elements command is used to find all elements that are descendents - * of the given element matching the given selector in the current - * browsing context that can be used as the web element context for future - * element-centric commands. - * {@link https://www.w3.org/TR/webdriver1/#find-elements-from-element} - * - * @param {!ElementHandle} unusedHandle - * @param {string} unusedSelector - * @return {!Promise>} - */ - async findElementsFromElement(unusedHandle, unusedSelector) {} - - /** - * Determines if the referenced element is elected or not. - * This operation only makes sense on input elements of the Checkbox- and - * Radio Button states, or on option elements. - * {@link https://www.w3.org/TR/webdriver1/#is-element-selected} - * - * @param {!ElementHandle} unusedHandle - * @return {!ControllerPromise} - */ - async isElementSelected(unusedHandle) {} - - /** - * Return the value of the given attribute name on the given element. - * Note: for boolean attributes, the value returned is "true". - * {@link https://www.w3.org/TR/webdriver1/#get-element-attribute} - * - * @param {!ElementHandle} unusedHandle - * @param {string} unusedAttribute - * @return {!ControllerPromise} - */ - async getElementAttribute(unusedHandle, unusedAttribute) {} - - /** - * Return the value of the given property name on the given element. - * {@link https://www.w3.org/TR/webdriver1/#get-element-property} - * - * @param {!ElementHandle} unusedHandle - * @param {string} unusedProperty - * @return {!ControllerPromise} - */ - async getElementProperty(unusedHandle, unusedProperty) {} - - /** - * Return the value of the given CSS value on the given element. - * {@link https://www.w3.org/TR/webdriver1/#get-element-css-value} - * - * @param {!ElementHandle} unusedHandle - * @param {string} unusedStyleProperty - * @return {!ControllerPromise} styleProperty - */ - async getElementCssValue(unusedHandle, unusedStyleProperty) {} - - /** - * The Get Element Text command intends to return an element’s text - * “as rendered”. An element’s rendered text is also used for locating `` - * elements by their link text and partial link text. - * {@link https://www.w3.org/TR/webdriver1/#get-element-text} - * - * @param {!ElementHandle} unusedHandle - * @return {!ControllerPromise} - */ - async getElementText(unusedHandle) {} - - /** - * Return the value of the tag name for the given element. - * {@link https://www.w3.org/TR/webdriver1/#get-element-tag-name} - * - * @param {!ElementHandle} unusedHandle - * @return {!Promise} - */ - async getElementTagName(unusedHandle) {} - - /** - * The Get Element Rect command returns the dimensions and coordinates of - * the given web element. Unlike the webdriver version, this also returns - * the left, right, top and bottom properties. - * {@link https://www.w3.org/TR/webdriver1/#get-element-rect} - * - * @param {!ElementHandle} unusedHandle - * @return {!ControllerPromise} - */ - async getElementRect(unusedHandle) {} - - /** - * Return the enabled state of the given element. i.e. `false` if the - * given element has the "disabled" attribute. - * {@link https://www.w3.org/TR/webdriver1/#is-element-enabled} - * - * @param {!ElementHandle} unusedHandle - * @return {!ControllerPromise} - */ - async isElementEnabled(unusedHandle) {} - - /** - * Return an array of handles consisting of all windows in the browser - * session. - * {@link https://www.w3.org/TR/webdriver1/#get-window-handles} - * - * @return {!Promise>} - */ - async getAllWindows() {} - - /** - * The Set Window Rect command alters the size and the position of the - * operating system window corresponding to the current top-level browsing - * context. - * {@link https://www.w3.org/TR/webdriver1/#set-window-rect} - * - * @param {!WindowRectDef} unusedRect - * @return {!Promise} - */ - async setWindowRect(unusedRect) {} - - /** - * The Take Screenshot command takes a screenshot of the - * visible region encompassed by the bounding rectangle of the window. - * {@link https://www.w3.org/TR/webdriver1/#take-screenshot} - * @param {string} unusedPath - * @return {!Promise} - */ - async takeScreenshot(unusedPath) {} - - /** - * The Take Element Screenshot command takes a screenshot of the visible - * region encompassed by the bounding rectangle of an element. - * {@link https://www.w3.org/TR/webdriver1/#take-element-screenshot} - * - * @param {!ElementHandle} unusedHandle - * @param {string} unusedPath - * @return {!Promise} - */ - async takeElementScreenshot(unusedHandle, unusedPath) {} - - /** - * Clicks the given element in its center point. - * {@link https://www.w3.org/TR/webdriver1/#element-click} - * - * @param {!ElementHandle} unusedHandle - * @return {!Promise} - */ - async click(unusedHandle) {} - - /** - * Sends the provided keys to the given form control element. If an element - * is not provided, the active element receives the keys. - * {@link https://www.w3.org/TR/webdriver1/#element-send-keys} - * - * @param {?ElementHandle} unusedHandle - * @param {string|Key} unusedKeys - * @return {!Promise} - */ - async type(unusedHandle, unusedKeys) {} - - /** - * Pastes from the clipboard by perfoming the keyboard shortcut. - * {@link https://stackoverflow.com/a/41046276} - * - * @return {!Promise} - */ - async pasteFromClipboard() {} - - /** - * Scrolls the given element using the scroll options if provided. - * If the element cannot scroll, then no scroll will be performed. - * Note: This is not yet spec'd in the W3C WebDriver spec, but there are plans - * to include it. - * {@link https://github.com/w3c/webdriver/issues/1005#issuecomment-436629425} - * - * @param {!ElementHandle} unusedHandle - * @param {!ScrollToOptionsDef=} opt_scrollToOptions - * @return {!Promise} - */ - async scroll(unusedHandle, opt_scrollToOptions) {} - - /** - * Evaluate the given function - * @param {function()} unusedFn - * @param {...*} unusedArgs - * @return {!Promise} - * @package - */ - async evaluate(unusedFn, ...unusedArgs) {} - - /** - * Cleanup any resources - * @return {!Promise} - */ - async dispose() {} -} - -/** - * @typedef {{ - * width: number, - * height: number - * }} WindowRectDef - */ -let WindowRectDef; - -/** - * @typedef {{ - * x: number, - * y: number, - * top: number, - * bottom: number, - * left: number, - * right: number, - * width: number, - * height: number - * }} - */ -let DOMRectDef; - -/** @enum {string} */ -// export let ScrollBehavior = { -// AUTO: 'auto', -// SMOOTH: 'smooth', -// } - -/** @typedef {{ - * left: number, - * top: number, - * behavior: ScrollBehavior - * }} - * {@link https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions} - */ -let ScrollToOptionsDef; - -module.exports = { - ElementHandle, - FunctionalTestController, - Key, - WindowRectDef, - DOMRectDef, - ScrollToOptionsDef, -}; diff --git a/build-system/tasks/e2e/index.js b/build-system/tasks/e2e/index.js index 80dc6430d0f37..a8426d473c817 100644 --- a/build-system/tasks/e2e/index.js +++ b/build-system/tasks/e2e/index.js @@ -10,133 +10,211 @@ * 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 + * See the License for the specific lan``guage governing permissions and * limitations under the License. */ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const ciReporter = require('../mocha-ci-reporter'); +const ciReporter = require('./mocha-ci-reporter'); const config = require('../../test-configs/config'); +const dotsReporter = require('./mocha-dots-reporter'); +const fs = require('fs'); const glob = require('glob'); -const log = require('fancy-log'); +const http = require('http'); const Mocha = require('mocha'); const path = require('path'); const { - buildRuntime, - getFilesFromArgv, - installPackages, -} = require('../../common/utils'); -const {cyan} = require('ansi-colors'); -const {isTravisBuild} = require('../../common/travis'); + createCtrlcHandler, + exitCtrlcHandler, +} = require('../../common/ctrlcHandler'); +const {buildRuntime, getFilesFromArgv} = require('../../common/utils'); +const {cyan} = require('../../common/colors'); +const {execOrDie} = require('../../common/exec'); +const {HOST, PORT, startServer, stopServer} = require('../serve'); +const {isCiBuild, isCircleciBuild} = require('../../common/ci'); +const {log} = require('../../common/logging'); +const {maybePrintCoverageMessage} = require('../helpers'); const {reportTestStarted} = require('../report-test-status'); -const {startServer, stopServer} = require('../serve'); -const {watch} = require('gulp'); +const {watch} = require('chokidar'); -const HOST = 'localhost'; -const PORT = 8000; const SLOW_TEST_THRESHOLD_MS = 2500; -const TEST_RETRIES = isTravisBuild() ? 2 : 0; +const TEST_RETRIES = isCiBuild() ? 2 : 0; -async function launchWebServer_() { - await startServer( +const COV_DOWNLOAD_PATH = '/coverage/download'; +const COV_OUTPUT_DIR = './test/coverage-e2e'; +const COV_OUTPUT_HTML = path.resolve(COV_OUTPUT_DIR, 'lcov-report/index.html'); + +/** + * Set up the e2e testing environment. + * @return {!Promise} + */ +async function setUpTesting_() { + require('@babel/register')({caller: {name: 'test'}}); + const {describes} = require('./helper'); + describes.configure({ + browsers: argv.browsers, + headless: argv.headless, + }); + + // build runtime + if (!argv.nobuild) { + await buildRuntime(); + } + + // start up web server + return startServer( {host: HOST, port: PORT}, {quiet: !argv.debug}, {compiled: argv.compiled} ); } -async function cleanUp_() { - await stopServer(); -} - +/** + * Creates a mocha test instance with configuration determined by CLI args. + * @return {!Mocha} + */ function createMocha_() { - const mocha = new Mocha({ + let reporter; + if (argv.testnames || argv.watch) { + reporter = ''; + } else if (argv.report || isCircleciBuild()) { + // TODO(#28387) clean up this typing. + reporter = /** @type {*} */ (ciReporter); + } else { + reporter = dotsReporter; + } + + return new Mocha({ // e2e tests have a different standard for when a test is too slow, // so we set a non-default threshold. slow: SLOW_TEST_THRESHOLD_MS, - reporter: argv.testnames || argv.watch ? '' : ciReporter, + reporter, retries: TEST_RETRIES, fullStackTrace: true, + reporterOptions: isCiBuild() + ? { + mochaFile: 'result-reports/e2e.xml', + } + : null, }); - - return mocha; } -async function e2e() { - // install e2e-specific modules - installPackages(__dirname); +/** + * Refreshes require cache and adds file to a Mocha instance. + * @param {!Mocha} mocha Mocha test instance. + * @param {string} file relative path to test file to add. + */ +function addMochaFile_(mocha, file) { + delete require.cache[path.resolve(file)]; + mocha.addFile(file); +} - // set up promise to return to gulp.task() - let resolver; - const deferred = new Promise((resolverIn) => { - resolver = resolverIn; - }); +/** + * Fetch aggregated coverage data from server. + * @param {string} outDir relative path to coverage files directory. + * @return {Promise} + */ +async function fetchCoverage_(outDir) { + // Note: We could access the coverage UI directly through the server started + // for the e2e tests, but then that coverage data would vanish once that + // server instance was closed. This method will persist the coverage data so + // it can be accessed separately. + + // Clear out previous coverage data. + fs.rmdirSync(outDir, {recursive: true}); + fs.mkdirSync(outDir); + + const zipFilename = path.join(outDir, 'coverage.zip'); + const zipFile = fs.createWriteStream(zipFilename); + + await /** @type {Promise} */ ( + new Promise((resolve, reject) => { + http + .get( + { + host: HOST, + port: PORT, + path: COV_DOWNLOAD_PATH, + }, + (response) => { + response.pipe(zipFile); + zipFile.on('finish', () => { + zipFile.close(); + resolve(); + }); + } + ) + .on('error', (err) => { + fs.unlinkSync(zipFilename); + reject(err); + }); + }) + ); + execOrDie(`unzip -o ${zipFilename} -d ${outDir}`); +} - require('@babel/register')({caller: {name: 'test'}}); - const {describes} = require('./helper'); - describes.configure({ - browsers: argv.browsers, - engine: argv.engine, - headless: argv.headless, - }); +/** + * Runs e2e tests on all files under test. + * @return {!Promise} + */ +async function runTests_() { + const mocha = createMocha_(); + const addFile = addMochaFile_.bind(null, mocha); - // build runtime - if (!argv.nobuild) { - await buildRuntime(); + // specify tests to run + if (argv.files) { + getFilesFromArgv().forEach(addFile); + } else { + config.e2eTestPaths.forEach((path) => { + glob.sync(path).forEach(addFile); + }); } - // start up web server - await launchWebServer_(); - - // run tests - if (!argv.watch) { - log('Running tests...'); - const mocha = createMocha_(); - - // specify tests to run - if (argv.files) { - getFilesFromArgv().forEach((file) => { - delete require.cache[file]; - mocha.addFile(file); - }); - } else { - config.e2eTestPaths.forEach((path) => { - glob.sync(path).forEach((file) => { - delete require.cache[file]; - mocha.addFile(file); - }); - }); - } + await reportTestStarted(); - await reportTestStarted(); + // return promise to amp that resolves when there's an error. + return new Promise((resolve) => { mocha.run(async (failures) => { - // end web server - await cleanUp_(); - - // end task + if (argv.coverage) { + await fetchCoverage_(COV_OUTPUT_DIR); + maybePrintCoverageMessage(COV_OUTPUT_HTML); + } + await stopServer(); process.exitCode = failures ? 1 : 0; - await resolver(); - }); - } else { - const filesToWatch = argv.files - ? getFilesFromArgv() - : [config.e2eTestPaths]; - const watcher = watch(filesToWatch); - log('Watching', cyan(filesToWatch), 'for changes...'); - watcher.on('change', (file) => { - log('Detected a change in', cyan(file)); - log('Running tests...'); - // clear file from node require cache if running test again - delete require.cache[path.resolve(file)]; - const mocha = createMocha_(); - mocha.files = [file]; - mocha.run(); + resolve(); }); - } + }); +} + +/** + * Watches files a under test, running affected e2e tests on changes. + * @return {!Promise} + */ +async function runWatch_() { + const filesToWatch = argv.files ? getFilesFromArgv() : config.e2eTestPaths; - return deferred; + log('Watching', cyan(filesToWatch), 'for changes...'); + watch(filesToWatch).on('change', (file) => { + log('Detected a change in', cyan(file)); + const mocha = createMocha_(); + addMochaFile_(mocha, file); + mocha.run(); + }); + + // return non-resolving promise to amp. + return new Promise(() => {}); +} + +/** + * Entry-point to run e2e tests. + */ +async function e2e() { + const handlerProcess = createCtrlcHandler('e2e'); + await setUpTesting_(); + argv.watch ? await runWatch_() : await runTests_(); + exitCtrlcHandler(handlerProcess); } module.exports = { @@ -146,21 +224,22 @@ module.exports = { e2e.description = 'Runs e2e tests'; e2e.flags = { 'browsers': - ' Run only the specified browser tests. Options are ' + + 'Run only the specified browser tests. Options are ' + '`chrome`, `firefox`, `safari`.', 'config': - ' Sets the runtime\'s AMP_CONFIG to one of "prod" (default) or "canary"', - 'core_runtime_only': ' Builds only the core runtime.', - 'nobuild': - ' Skips building the runtime via `gulp (build|dist) --fortesting`', - 'extensions': ' Builds only the listed extensions.', - 'compiled': ' Runs tests against minified JS', - 'files': ' Run tests found in a specific path (ex: **/test-e2e/*.js)', - 'testnames': ' Lists the name of each test being run', - 'watch': ' Watches for changes in files, runs corresponding test(s)', - 'engine': - ' The automation engine that orchestrates the browser. ' + - 'Options are `puppeteer` or `selenium`. Default: `selenium`', - 'headless': ' Runs the browser in headless mode', - 'debug': ' Prints debugging information while running tests', + 'Sets the runtime\'s AMP_CONFIG to one of "prod" (default) or "canary"', + 'core_runtime_only': 'Builds only the core runtime.', + 'nobuild': 'Skips building the runtime via `amp (build|dist) --fortesting`', + 'define_experiment_constant': + 'Transforms tests with the EXPERIMENT constant set to true', + 'experiment': 'Experiment being tested (used for status reporting)', + 'extensions': 'Builds only the listed extensions.', + 'compiled': 'Runs tests against minified JS', + 'files': 'Run tests found in a specific path (ex: **/test-e2e/*.js)', + 'testnames': 'Lists the name of each test being run', + 'watch': 'Watches for changes in files, runs corresponding test(s)', + 'headless': 'Runs the browser in headless mode', + 'debug': 'Prints debugging information while running tests', + 'report': 'Write test result report to a local file', + 'coverage': 'Collect coverage data from instrumented code', }; diff --git a/build-system/tasks/e2e/mocha-ci-reporter.js b/build-system/tasks/e2e/mocha-ci-reporter.js new file mode 100644 index 0000000000000..f0d6464c2ce61 --- /dev/null +++ b/build-system/tasks/e2e/mocha-ci-reporter.js @@ -0,0 +1,38 @@ +/** + * Copyright 2020 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. + */ + +const JsonReporter = require('./mocha-custom-json-reporter'); +const mocha = require('mocha'); +const MochaDotsReporter = require('./mocha-dots-reporter'); +const MochaJUnitReporter = require('mocha-junit-reporter'); +const {Base} = mocha.reporters; + +/** + * @param {*} runner + * @param {*} options + * @return {MochaDotsReporter} + */ +function ciReporter(runner, options) { + Base.call(this, runner, options); + this._mochaDotsReporter = new MochaDotsReporter(runner); + this._jsonReporter = new JsonReporter(runner); + this._mochaJunitReporter = new MochaJUnitReporter(runner, options); + // TODO(#28387) clean up this typing. + return /** @type {*} */ (this); +} +ciReporter.prototype.__proto__ = Base.prototype; + +module.exports = ciReporter; diff --git a/build-system/tasks/e2e/mocha-custom-json-reporter.js b/build-system/tasks/e2e/mocha-custom-json-reporter.js new file mode 100644 index 0000000000000..bc4bddc549212 --- /dev/null +++ b/build-system/tasks/e2e/mocha-custom-json-reporter.js @@ -0,0 +1,94 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +const fs = require('fs-extra'); +const mocha = require('mocha'); +const {Base} = require('mocha').reporters; +const {inherits} = require('mocha').utils; + +const { + EVENT_RUN_END, + EVENT_SUITE_BEGIN, + EVENT_SUITE_END, + EVENT_TEST_FAIL, + EVENT_TEST_PASS, + EVENT_TEST_PENDING, +} = mocha.Runner.constants; +/** + * @param {Object} output + * @param {string} filename + * @return {Promise} + */ +async function writeOutput(output, filename) { + try { + await fs.outputJson(filename, output, {spaces: 4}); + } catch (error) { + process.stdout.write( + Base.color( + 'fail', + `Could not write test result report to file '${filename}': ${error}` + ) + ); + } +} + +/** + * Custom Mocha reporter for CI builds. + * Mimics the structured karma reporter, but for Mocha. + * @param {*} runner + */ +function JsonReporter(runner) { + // @ts-ignore + Base.call(this, runner); + const testEvents = []; + let suiteList = []; + + runner.on(EVENT_SUITE_BEGIN, function (suite) { + suiteList.push(suite.title); + }); + + runner.on(EVENT_SUITE_END, function () { + // We need a fresh copy every time we make a new suite, + // so we can't use pop here (or the suiteList info of previous + // tests would be changed) + suiteList = suiteList.slice(0, -1); + }); + + [EVENT_TEST_PASS, EVENT_TEST_FAIL, EVENT_TEST_PENDING].forEach((event) => { + runner.on(event, (test) => { + testEvents.push({test, suiteList, event}); + }); + }); + + runner.on(EVENT_RUN_END, async function () { + const results = testEvents.map(({event, suiteList, test}) => ({ + description: test.title, + suite: suiteList, + success: event === EVENT_TEST_PASS, + skipped: event === EVENT_TEST_PENDING, + time: test.duration, // in milliseconds + })); + + // Apparently we'll need to add a --no-exit flag when calling this + // to allow for the asynchronous reporter. + // See https://github.com/mochajs/mocha/issues/812 + await writeOutput({browsers: [{results}]}, `result-reports/e2e.json`); + }); +} + +inherits(JsonReporter, Base); +module.exports = JsonReporter; diff --git a/build-system/tasks/e2e/mocha-dots-reporter.js b/build-system/tasks/e2e/mocha-dots-reporter.js new file mode 100644 index 0000000000000..ebbff15434451 --- /dev/null +++ b/build-system/tasks/e2e/mocha-dots-reporter.js @@ -0,0 +1,80 @@ +/** + * Copyright 2019 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. + */ +'use strict'; + +const Mocha = require('mocha'); +const {log, logWithoutTimestamp} = require('../../common/logging'); +const { + EVENT_RUN_BEGIN, + EVENT_RUN_END, + EVENT_TEST_FAIL, + EVENT_TEST_PASS, + EVENT_TEST_PENDING, +} = Mocha.Runner.constants; +const {Base} = Mocha.reporters; +const {green, red, yellow} = require('../../common/colors'); +const {icon, nbDotsPerLine} = + require('../../test-configs/karma.conf').superDotsReporter; +const {reportTestFinished} = require('../report-test-status'); + +/** + * Custom Mocha reporter for CI builds. + * Mimics the style of the Karma super-dots reporter. + * @param {*} runner + */ +class MochaDotsReporter extends Base { + /** + * @param {*} runner + */ + constructor(runner) { + super(runner); + + let wrapCounter = 0; + const printDot = (dot) => { + process.stdout.write(dot); + if (++wrapCounter >= nbDotsPerLine) { + wrapCounter = 0; + process.stdout.write('\n'); + } + }; + + runner + .once(EVENT_RUN_BEGIN, () => { + log('Running tests...'); + }) + .on(EVENT_TEST_PASS, () => { + printDot(green(icon.success)); + }) + .on(EVENT_TEST_FAIL, () => { + printDot(red(icon.failure)); + }) + .on(EVENT_TEST_PENDING, () => { + printDot(yellow(icon.ignore)); + }) + .once(EVENT_RUN_END, () => { + Base.list(this.failures); + const {failures, passes, pending, tests} = runner.stats; + logWithoutTimestamp( + `Executed ${failures + passes} of ${tests}`, + `(Skipped ${pending})`, + failures == 0 ? green('SUCCESS') : red(`${failures} FAILED`) + ); + reportTestFinished(passes, failures); + }); + } +} + +module.exports = MochaDotsReporter; diff --git a/build-system/tasks/e2e/network-logger.js b/build-system/tasks/e2e/network-logger.js index 28bcbaa979858..e5d0559e23564 100644 --- a/build-system/tasks/e2e/network-logger.js +++ b/build-system/tasks/e2e/network-logger.js @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {logging} from 'selenium-webdriver'; +import selenium from 'selenium-webdriver'; + +const {logging} = selenium; /** @enum {string} */ const PerformanceMethods = { @@ -26,13 +28,17 @@ const PerformanceMethods = { 'WINDOW_OPEN': 'Network.windowOpen', }; -class NetworkLogger { - /** @param {!WebDriver} driver */ +export class NetworkLogger { + /** @param {!selenium.WebDriver} driver */ constructor(driver) { - /** @type {WebDriver} */ + /** @type {selenium.WebDriver} */ this.driver_ = driver; } + /** + * @param {PerformanceMethods} networkMethod + * @return {Promise<*>} + */ async getEntries_(networkMethod) { const entries = await this.driver_ .manage() @@ -49,7 +55,7 @@ class NetworkLogger { /** * Gets sent requests with an optional url to filter by. * @param {string=} url - * @return {Array} + * @return {Promise>} */ async getSentRequests(url) { const entries = await this.getEntries_( @@ -61,7 +67,3 @@ class NetworkLogger { return entries; } } - -module.exports = { - NetworkLogger, -}; diff --git a/build-system/tasks/e2e/package-lock.json b/build-system/tasks/e2e/package-lock.json new file mode 100644 index 0000000000000..a47580d3011bb --- /dev/null +++ b/build-system/tasks/e2e/package-lock.json @@ -0,0 +1,1177 @@ +{ + "name": "amp-e2e", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/register": { + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.13.16.tgz", + "integrity": "sha512-dh2t11ysujTwByQjXNgJ48QZ2zcXKQVdV8s0TbeMI0flmtGWCdTwK9tJiACHXPLmncm5+ktNn/diojA45JE4jg==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.0", + "source-map-support": "^0.5.16" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, + "@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@testim/chrome-version": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.0.7.tgz", + "integrity": "sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw==", + "dev": true + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.3.tgz", + "integrity": "sha512-/WbxFeBU+0F79z9RdEOXH4CsDga+ibi5M8uEYr91u3CkT/pdWcV8MCook+4wDPnZBexRdwWS+PiVZ2xJviAzcQ==", + "dev": true + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dev": true, + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "babel-regenerator-runtime": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.5.0.tgz", + "integrity": "sha1-DkHNHJ+ARCRm8BXHSf/4upj44RA=", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chromedriver": { + "version": "90.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-90.0.1.tgz", + "integrity": "sha512-jvyhin0I/Bacxfet7eg29B1j+5mKR35XwWGbgcCOUALeE3mqcCKJY8xUW9cVrqVqTK9/iUOq8/kar7qrTVshPA==", + "dev": true, + "requires": { + "@testim/chrome-version": "^1.0.7", + "axios": "^0.21.1", + "del": "^6.0.0", + "extract-zip": "^2.0.1", + "https-proxy-agent": "^5.0.0", + "proxy-from-env": "^1.1.0", + "tcp-port-used": "^1.0.1" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", + "dev": true + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "geckodriver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-2.0.0.tgz", + "integrity": "sha512-qcL58vKCwaUufE9y2viDydvvynxUzeITc0CblefAo0jWdxNMbQmY6wVt6MKJuBHOQ71QRer5iq9CSRZHppuytg==", + "dev": true, + "requires": { + "adm-zip": "0.5.5", + "bluebird": "3.7.2", + "got": "11.8.2", + "https-proxy-agent": "5.0.0", + "tar": "6.1.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dev": true, + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "is2": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz", + "integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "jszip": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "resolve-alpn": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", + "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==", + "dev": true + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "selenium-webdriver": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-beta.3.tgz", + "integrity": "sha512-R0mGHpQkSKgIWiPgcKDcckh4A6aaK0KTyWxs5ieuiI7zsXQ+Kb6neph+dNoeqq3jSBGyv3ONo2w3oohoL4D/Rg==", + "dev": true, + "requires": { + "jszip": "^3.5.0", + "rimraf": "^2.7.1", + "tmp": "^0.2.1", + "ws": "^7.3.1" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "requires": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/build-system/tasks/e2e/package.json b/build-system/tasks/e2e/package.json index c0521e41eb233..712545a0ab02d 100644 --- a/build-system/tasks/e2e/package.json +++ b/build-system/tasks/e2e/package.json @@ -1,17 +1,13 @@ { "private": true, - "name": "gulp-e2e", + "name": "amp-e2e", "version": "0.1.0", - "description": "Gulp e2e", + "description": "amp e2e", "devDependencies": { - "@babel/register": "7.10.1", + "@babel/register": "7.13.16", "babel-regenerator-runtime": "6.5.0", - "chromedriver": "83.0.0", - "puppeteer": "3.3.0", - "geckodriver": "1.19.1", - "selenium-webdriver": "4.0.0-alpha.7" - }, - "resolutions": { - "**/**/minimist": "^1.2.3" + "chromedriver": "90.0.1", + "geckodriver": "2.0.0", + "selenium-webdriver": "4.0.0-beta.3" } } diff --git a/build-system/tasks/e2e/puppeteer-controller.js b/build-system/tasks/e2e/puppeteer-controller.js deleted file mode 100644 index 263ac2868e48c..0000000000000 --- a/build-system/tasks/e2e/puppeteer-controller.js +++ /dev/null @@ -1,731 +0,0 @@ -/** - * Copyright 2019 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. - */ - -const fs = require('fs'); -const { - Browser, // eslint-disable-line no-unused-vars - JSHandle, // eslint-disable-line no-unused-vars - Page, // eslint-disable-line no-unused-vars - ElementHandle: PuppeteerHandle, // eslint-disable-line no-unused-vars -} = require('puppeteer'); -const { - DOMRectDef, - ElementHandle, - FunctionalTestController, // eslint-disable-line no-unused-vars - Key, -} = require('./functional-test-controller'); -const {ControllerPromise} = require('./controller-promise'); -const {dirname, join} = require('path'); - -/** - * For a list of all possible key strings, see - * {@link https://github.com/GoogleChrome/puppeteer/blob/master/lib/USKeyboardLayout.js} - * @enum {string} - */ -const KeyToPuppeteerMap = { - [Key.ArrowDown]: 'ArrowDown', - [Key.ArrowLeft]: 'ArrowLeft', - [Key.ArrowRight]: 'ArrowRight', - [Key.ArrowUp]: 'ArrowUp', - [Key.Enter]: 'Enter', - [Key.Escape]: 'Escape', - [Key.Tab]: 'Tab', -}; - -const DEFAULT_WAIT_TIMEOUT = 10000; - -/** - * Make the test runner wait until the value returned by the valueFn matches - * the given condition. - * @param {!Page} page - * @param {function(): !Promise} valueFn - * @param {!IArrayLike<*>} args - * @param {function(T):boolean} condition - * @param {function(T):T} opt_mutate - * @return {!Promise} - * @template T - */ -async function waitFor(page, valueFn, args, condition, opt_mutate) { - const handle = await evaluate(page, valueFn, ...args); - let value = await unboxHandle(handle); - if (opt_mutate) { - value = await opt_mutate(value); - } - - while (!condition(value)) { - const handle = await page.waitForFunction( - valueFn, - {timeout: DEFAULT_WAIT_TIMEOUT}, - ...args - ); - value = await unboxHandle(handle); - if (opt_mutate) { - value = await opt_mutate(value); - } - } - return value; -} - -/** - * Remove the jsonValue wrapper from a PuppeteerHandle and - * remove an outer object wrapper if present. - * @param {!PuppeteerHandle} handle - * @return {T} - * @template T - */ -async function unboxHandle(handle) { - const prop = await handle.jsonValue(); - return 'value' in prop ? prop.value : prop; -} - -/** - * Evaluate the given function and its arguments in the context of the document. - * @param {!Frame} frame - * @param {function(...*):*} fn - * @return {!Promise} - */ -function evaluate(frame, fn) { - const args = Array.prototype.slice.call(arguments, 2); - return frame.evaluateHandle(fn, ...args); -} - -/** @implements {FunctionalTestController} */ -class PuppeteerController { - /** - * @param {!Browser} browser - */ - constructor(browser) { - /** @private @const */ - this.browser_ = browser; - - /** @private */ - this.page_ = null; - - /** @private */ - this.currentFrame_ = null; - - /** @private */ - this.shadowRoot_ = null; - - /** @private */ - this.isXpathInstalled_ = false; - } - - /** - * Get the current page object. Create the object if it does not exist. - * @return {!Promise} - */ - async getPage_() { - if (!this.page_) { - this.page_ = await this.browser_.newPage(); - await this.setWindowRect({width: 800, height: 600}); - } - return this.page_; - } - - /** - * Get the current page object. Create the object if it does not exist. - * @return {!Promise} - */ - async getCurrentFrame_() { - if (!this.currentFrame_) { - const page = await this.getPage_(); - this.currentFrame_ = page.mainFrame(); - } - - return this.currentFrame_; - } - - /** - * Return a wait function. When called, the function will cause the test - * runner to wait until the given value matches the expected value. - * @param {function(): !Promise} valueFn - * @param {...*} args - * @return {function(T,T): !Promise} - * @template T - */ - getWaitFn_(valueFn, ...args) { - return async (condition, opt_mutate) => { - const frame = await this.getCurrentFrame_(); - return waitFor(frame, valueFn, args, condition, opt_mutate); - }; - } - - /** - * Evaluate a function in the context of the document. - * @param {function(...*):T} fn - * @param {...*} args - * @return {!Promise} - * @template T - */ - async evaluate(fn, ...args) { - const frame = await this.getCurrentFrame_(); - return await evaluate(frame, fn, ...args); - } - - /** - * Gets the result of a value function which is boxed in an object. - * @param {*} fn - * @param {...any} args - */ - async evaluateValue_(fn, ...args) { - const value = await this.evaluate(fn, ...args); - const json = await value.jsonValue(); - return json.value; - } - - /** - * @param {string} selector - * @param {number=} timeout - * @return {!Promise>} - * @override - */ - async findElement(selector, timeout = DEFAULT_WAIT_TIMEOUT) { - const frame = await this.getCurrentFrame_(); - const root = await this.getRoot_(); - const jsHandle = await frame.waitForFunction( - (root, selector) => { - return root./*OK*/ querySelector(selector); - }, - {timeout}, - root, - selector - ); - const elementHandle = jsHandle.asElement(); - return new ElementHandle(elementHandle); - } - - /** - * @param {string} selector - * @return {!Promise>>} - * @override - */ - async findElements(selector) { - const frame = await this.getCurrentFrame_(); - const root = await this.getRoot_(); - const nodeListHandle = await frame.waitForFunction( - (root, selector) => { - const nodeList = root./*OK*/ querySelectorAll(selector); - return nodeList.length > 0 ? Array.from(nodeList) : null; - }, - {timeout: DEFAULT_WAIT_TIMEOUT}, - root, - selector - ); - - const lengthHandle = await nodeListHandle.getProperty('length'); - const length = await lengthHandle.jsonValue(); - - const elementHandles = []; - for (let i = 0; i < length; i++) { - const elementHandle = await nodeListHandle.getProperty(`${i}`); - elementHandles.push(new ElementHandle(elementHandle)); - } - - return elementHandles; - } - - /** - * @param {string} xpath - * @return {!Promise>} - * @override - */ - async findElementXPath(xpath) { - await this.maybeInstallXpath_(); - const frame = await this.getCurrentFrame_(); - - const root = await this.getRoot_(); - const jsHandle = await frame.waitForFunction( - (xpath, root) => { - const results = window.queryXpath(xpath, root); - return results && results[0]; - }, - {timeout: DEFAULT_WAIT_TIMEOUT}, - xpath, - root - ); - return new ElementHandle(jsHandle.asElement()); - } - - /** - * @param {string} xpath - * @return {!Promise>>} - * @override - */ - async findElementsXPath(xpath) { - this.maybeInstallXpath_(); - - const frame = await this.getCurrentFrame_(); - const root = await this.getRoot_(); - - const arrayHandle = await frame.waitForFunction( - (xpath, root) => { - return window.queryXpath(xpath, root); - }, - {timeout: DEFAULT_WAIT_TIMEOUT}, - xpath, - root - ); - - const lengthHandle = await arrayHandle.getProperty('length'); - const length = await lengthHandle.jsonValue(); - - const elementHandles = []; - for (let i = 0; i < length; i++) { - const elementHandle = await arrayHandle.getProperty(`${i}`); - elementHandles.push(new ElementHandle(elementHandle)); - } - return elementHandles; - } - - /** - * Install a third-party XPath library if it is not already installed. - * @return {!Promise} - */ - async maybeInstallXpath_() { - if (this.isXpathInstalled_) { - return; - } - this.isXpathInstalled_ = true; - - const scripts = await Promise.all([ - fs.promises.readFile('third_party/wgxpath/wgxpath.js', 'utf8'), - fs.promises.readFile( - 'build-system/tasks/e2e/driver/query-xpath.js', - 'utf8' - ), - ]); - const frame = await this.getCurrentFrame_(); - await frame.evaluate(scripts.join('\n\n')); - } - - /** - * @return {!Promise>} - * @override - */ - async getDocumentElement() { - const root = await this.getRoot_(); - const getter = (root) => root.ownerDocument.documentElement; - const element = await this.evaluate(getter, root); - return new ElementHandle(element); - } - - /** - * @param {string} url - * @return {!Promise} - * @override - */ - async navigateTo(url) { - const frame = await this.getCurrentFrame_(); - await frame.goto(url, {waitUntil: 'domcontentloaded'}); - } - - /** - * @param {!ElementHandle} handle - * @param {string|Key} keys - * @return {!Promise} - * @override - */ - async type(handle, keys) { - const targetElement = handle - ? handle.getElement() - : (await this.getActiveElement()).getElement(); - - const key = KeyToPuppeteerMap[keys]; - if (key) { - await targetElement.press(key); - return; - } - - await targetElement.type(keys); - } - - /** - * @param {!ElementHandle} handle - * @return {!Promise} - * @override - */ - getElementText(handle) { - const element = handle.getElement(); - const getter = (element) => ({value: element./*OK*/ innerText.trim()}); - return new ControllerPromise( - this.evaluateValue_(getter, element), - this.getWaitFn_(getter, element) - ); - } - - /** - * @param {!ElementHandle} handle - * @param {string} styleProperty - * @return {!Promise} styleProperty - * @override - */ - getElementCssValue(handle, styleProperty) { - const element = handle.getElement(); - const getter = (element, styleProperty) => { - const value = window /*OK*/['getComputedStyle'](element)[styleProperty]; - return {value}; - }; - return new ControllerPromise( - this.evaluateValue_(getter, element, styleProperty), - this.getWaitFn_(getter, element, styleProperty) - ); - } - - /** - * @param {!ElementHandle} handle - * @param {string} attribute - * @return {!Promise} - * @override - */ - getElementAttribute(handle, attribute) { - const element = handle.getElement(); - const getter = (element, attribute) => ({ - value: element.getAttribute(attribute), - }); - return new ControllerPromise( - this.evaluateValue_(getter, element, attribute), - this.getWaitFn_(getter, element, attribute) - ); - } - - /** - * @param {!ElementHandle} handle - * @param {string} property - * @return {!Promise} - * @override - */ - getElementProperty(handle, property) { - const element = handle.getElement(); - const getter = (element, property) => { - let value = element[property]; - if (typeof value !== 'string' && typeof value.length === 'number') { - value = Array.from(value); - } - - return {value}; - }; - return new ControllerPromise( - this.evaluateValue_(getter, element, property), - this.getWaitFn_(getter, element, property) - ); - } - - /** - * @param {!ElementHandle} handle - * @return {!Promise} - * @override - */ - getElementRect(handle) { - const element = handle.getElement(); - const getter = (element) => { - // Extracting the values seems to perform better than returning - // the raw ClientRect from the element, in terms of flakiness. - // The raw ClientRect also has hundredths of a pixel. We round to int. - const { - x, - y, - width, - height, - top, - bottom, - left, - right, - } = element./*OK*/ getBoundingClientRect(); - return { - x: Math.round(x), - y: Math.round(y), - width: Math.round(width), - height: Math.round(height), - top: Math.round(top), - bottom: Math.round(bottom), - left: Math.round(left), - right: Math.round(right), - }; - }; - return new ControllerPromise( - this.evaluate(getter, element).then((handle) => handle.jsonValue()), - this.getWaitFn_(getter, element) - ); - } - - /** - * @param {!WindowRectDef} rect - * @return {!Promise} - * @override - */ - async setWindowRect(rect) { - const {width, height} = rect; - await this.resizeWindow_(width, height); - } - - /** - * Resize the window and the viewport. The `page.setViewport` method only - * changes the size of the rendered area of the browser, not the window size. - * `page.setViewport` also does not relayout the page. - * To resize the window to test code that makes decisions on resize, we must - * change the viewport and the window bounds. - * We also pass any scrollbars adding width or tab bar adding height as - * the chromePadding* parameters. - * {@link https://github.com/GoogleChrome/puppeteer/issues/1183#issuecomment-348615375} - * @param {number} width - * @param {number} height - * @private - */ - async resizeWindow_(width, height) { - const browser = this.browser_; - const page = await this.getPage_(); - - const { - outerWidth, - outerHeight, - clientWidth, - clientHeight, - } = await this.evaluate(() => { - const {style} = document.documentElement; - const saved = style.overflow; - style.overflow = 'scroll'; - const {outerWidth, outerHeight} = window; - const {clientWidth, clientHeight} = document.documentElement; - style.overflow = saved; - return { - outerWidth: Math.round(outerWidth), - outerHeight: Math.round(outerHeight), - clientWidth: Math.round(clientWidth), - clientHeight: Math.round(clientHeight), - }; - }); - const chromePaddingWidth = outerWidth > 0 ? outerWidth - clientWidth : 0; - const chromePaddingHeight = - outerHeight > 0 ? outerHeight - clientHeight : 0; - - await page.setViewport({ - width: width + chromePaddingWidth, - height: height + chromePaddingHeight, - hasTouch: true, - }); - - // Any tab. - const { - targetInfos: [{targetId}], - } = await browser._connection.send('Target.getTargets'); - - // Tab window. - try { - const {windowId} = await browser._connection.send( - 'Browser.getWindowForTarget', - { - targetId, - } - ); - - // Resize. - await browser._connection.send('Browser.setWindowBounds', { - bounds: { - width: width + chromePaddingWidth, - height: height + chromePaddingHeight, - }, - windowId, - }); - } catch (e) { - // Catch if we're in headless. - if ( - !e.toString().includes('Protocol error (Browser.getWindowForTarget)') - ) { - throw e; - } - } - } - - /** - * @override - */ - getTitle() { - const title = this.getCurrentFrame_().then((frame) => frame.title()); - return new ControllerPromise(title, () => - this.getCurrentFrame_().then((frame) => frame.title()) - ); - } - - /** - * @return {!Promise} - * @override - */ - async getActiveElement() { - const root = await this.getRoot_(); - const getter = (root) => - root.activeElement || root.ownerDocument.activeElement; - const element = await this.evaluate(getter, root); - return new ElementHandle(element); - } - - /** - * TODO(cvializ): decide if we need to waitForNavigation on click and keypress - * @param {!ElementHandle} handle - * @return {!Promise} - * @override - */ - click(handle) { - return handle.getElement().click(); - } - - /** - * @param {!ElementHandle} handle - * @param {!ScrollToOptionsDef=} opt_scrollToOptions - * @return {!Promise} - * @override - */ - async scrollTo(handle, opt_scrollToOptions) { - const element = handle.getElement(); - await this.evaluate( - (element, opt_scrollToOptions) => { - element./*OK*/ scrollTo(opt_scrollToOptions); - }, - element, - opt_scrollToOptions - ); - } - - /** - * @param {!ElementHandle} handle - * @param {!ScrollToOptionsDef=} opt_scrollToOptions - * @return {!Promise} - * @override - */ - async scrollBy(handle, opt_scrollToOptions) { - const element = handle.getElement(); - await this.evaluate( - (element, opt_scrollToOptions) => { - element./*OK*/ scrollBy(opt_scrollToOptions); - }, - element, - opt_scrollToOptions - ); - } - - /** - * For some reason page.screenshot fails, so we use the html element. - * @param {string} path - * @return {!Promise} An encoded string representing the image data - * @override - */ - async takeScreenshot(path) { - const root = new ElementHandle(await this.getRoot_()); - return await this.takeElementScreenshot(root, path); - } - - /** - * @param {!ElementHandle} handle - * @param {string} path The path relative to the build-system/tasks/e2e root. - * @return {!Promise} An encoded string representing the image data - * @override - */ - async takeElementScreenshot(handle, path) { - const relative = join(__dirname, path); - try { - await fs.mkdirAsync(dirname(relative), {recursive: true}); - } catch (err) { - if (err.code != 'EEXIST') { - throw err; - } - } - - const options = { - path: relative, - type: 'png', - }; - const element = handle.getElement(); - return await element.screenshot(options); - } - - /** - * @param {?ElementHandle} handle - * @return {!Promise} - */ - async switchToFrame(handle) { - const element = handle.getElement(); - this.currentFrame_ = await element.contentFrame(); - } - - /** - * @return {!Promise} - */ - async switchToParent() { - let frame = await this.currentFrame_.parentFrame(); - if (!frame) { - const page = await this.getPage_(); - frame = page.mainFrame(); - } - this.currentFrame_ = frame; - } - - /** - * Switch controller to shadowRoot body hosted by given element. - * @param {!ElementHandle shadowHost.shadowRoot.body; - return this.switchToShadowInternal_(handle, getter); - } - - /** - * Switch controller to shadowRoot hosted by given element. - * @param {!ElementHandle} handle - * @return {!Promise} - */ - switchToShadowRoot(handle) { - const getter = (shadowHost) => shadowHost.shadowRoot; - return this.switchToShadowInternal_(handle, getter); - } - - /**. - * @param {!ElementHandle} handle - * @param {!Function} getter - */ - async switchToShadowInternal_(handle, getter) { - const shadowHost = handle.getElement(); - const shadowRootBodyHandle = await this.evaluate(getter, shadowHost); - this.shadowRoot_ = shadowRootBodyHandle.asElement(); - } - - async switchToLight() { - this.shadowRoot_ = null; - } - - /** - * Get the current root - * @return {!Promise} - */ - async getRoot_() { - if (this.shadowRoot_) { - return this.shadowRoot_; - } - - return await this.evaluate(() => document.documentElement); - } - - /** - * @override - */ - dispose() { - return this.browser_.close(); - } -} - -module.exports = { - PuppeteerController, -}; diff --git a/build-system/tasks/e2e/repl.js b/build-system/tasks/e2e/repl.js index 808ab56ba0ee2..9e5be8d841665 100644 --- a/build-system/tasks/e2e/repl.js +++ b/build-system/tasks/e2e/repl.js @@ -26,8 +26,8 @@ const REPL_INFINITE_TIMEOUT = 86400000; // milliseconds in a day * This will cause the test to wait indefinitely when you want to execute * commands in the DevTools console. * You may have to change `async() => {}` to `async function() {}` - * 2. Run gulp with Node debugging enabled: - * `node --inspect-brk $(which gulp) e2e ...` + * 2. Run amp with Node debugging enabled: + * `node --inspect-brk $(which amp) e2e ...` * 3. Open Chrome DevTools and open the Node debugger * 4. Wait for the `READY_MESSAGE` to appear in the console * 5. You are now free to execute code in the console using the controller API. @@ -65,6 +65,9 @@ function installRepl(global, env) { return replPromise; }; + /** + * Continues execution while debugging. + */ function replContinue() { if (!replResolve) { return; @@ -81,6 +84,9 @@ function installRepl(global, env) { } } +/** + * Ends the debugging session. + */ function uninstallRepl() { delete global.repl; } diff --git a/build-system/tasks/e2e/selenium-webdriver-controller.js b/build-system/tasks/e2e/selenium-webdriver-controller.js index ae55755ded7ab..c16164019498c 100644 --- a/build-system/tasks/e2e/selenium-webdriver-controller.js +++ b/build-system/tasks/e2e/selenium-webdriver-controller.js @@ -15,16 +15,19 @@ */ const fs = require('fs'); +const selenium = require('selenium-webdriver'); const { DOMRectDef, ElementHandle, Key, -} = require('./functional-test-controller'); -const {By, Condition, Key: SeleniumKey, error} = require('selenium-webdriver'); + ScrollToOptionsDef, +} = require('./e2e-types'); const {ControllerPromise} = require('./controller-promise'); const {expect} = require('chai'); const {NetworkLogger} = require('./network-logger'); +const {By, Condition, Key: SeleniumKey, error} = selenium; + const {NoSuchElementError} = error; const ELEMENT_WAIT_TIMEOUT = 5000; @@ -41,11 +44,12 @@ const KeyToSeleniumMap = { }; /** - * @param {function(): !Promise} valueFn - * @param {function(T):boolean} condition - * @param {T} opt_mutate + * @param {function(): !Promise} valueFn + * @param {function(T2): boolean} condition + * @param {function(T1): T2} opt_mutate * @return {!Condition} - * @template T + * @template T1 + * @template T2 */ function expectCondition(valueFn, condition, opt_mutate) { opt_mutate = opt_mutate || ((x) => x); @@ -59,54 +63,54 @@ function expectCondition(valueFn, condition, opt_mutate) { /** * Make the test runner wait until the value returned by the valueFn matches * the given condition. - * @param {!WebDriver} driver - * @param {function(): !Promise} valueFn - * @param {function(T): ?T} condition - * @param {T} opt_mutate - * @return {!Promise} - * @template T + * @param {!selenium.WebDriver} driver + * @param {function(): !Promise} valueFn + * @param {function(T2): ?T1} condition + * @param {function(T1): T2} opt_mutate + * @return {!Promise} + * @template T1 + * @template T2 */ -function waitFor(driver, valueFn, condition, opt_mutate) { +async function waitFor(driver, valueFn, condition, opt_mutate) { const conditionValue = (value) => { // Box the value in an object, so values that are present but falsy // (like "") do not cause driver.wait to continue waiting. - return condition(value) ? {value} : null; + return Boolean(condition(value)); }; - return driver - .wait(expectCondition(valueFn, conditionValue, opt_mutate)) - .then((result) => result.value); // Unbox the value. + + const result = await driver.wait( + expectCondition(valueFn, conditionValue, opt_mutate) + ); + + return result.value; // Unbox the value. } -/** @implements {FunctionalTestController} */ class SeleniumWebDriverController { /** - * @param {!WebDriver} driver + * @param {!selenium.WebDriver} driver */ constructor(driver) { this.driver = driver; this.networkLogger = new NetworkLogger(driver); - /** @private {?WebElement} */ + /** @private {?selenium.WebElement} */ this.shadowRoot_ = null; /** @private {boolean} */ this.isXpathInstalled_ = false; } + // * @return {function(T1,T1): !Promise} /** * Return a wait function. When called, the function will cause the test * runner to wait until the given value matches the expected value. - * @param {function(): !Promise} valueFn - * @return {function(T,T): !Promise} - * @template T + * @param {function(): !Promise} valueFn + * @return {function(function(T2): ?T1, function(T1): T2): !Promise} + * @template T1 + * @template T2 */ getWaitFn_(valueFn) { - /** - * @param {function(T): ?T} condition - * @param {T} opt_mutate - * @return {!Promise} - */ return (condition, opt_mutate) => { return waitFor(this.driver, valueFn, condition, opt_mutate); }; @@ -118,8 +122,7 @@ class SeleniumWebDriverController { * {@link https://github.com/SeleniumHQ/selenium/blob/6a717f20/javascript/node/selenium-webdriver/lib/until.js#L237} * @param {string} selector * @param {number=} timeout - * @return {!Promise>} - * @override + * @return {!Promise>} */ async findElement(selector, timeout = ELEMENT_WAIT_TIMEOUT) { const bySelector = By.css(selector); @@ -141,7 +144,7 @@ class SeleniumWebDriverController { } }); const webElement = await this.driver.wait(condition, timeout); - return new ElementHandle(webElement, this); + return new ElementHandle(webElement); } /** @@ -149,8 +152,7 @@ class SeleniumWebDriverController { * until.js#elementsLocated * {@link https://github.com/SeleniumHQ/selenium/blob/6a717f20/javascript/node/selenium-webdriver/lib/until.js#L258} * * @param {string} selector - * @return {!Promise>>} - * @override + * @return {!Promise>>} */ async findElements(selector) { const bySelector = By.css(selector); @@ -169,13 +171,12 @@ class SeleniumWebDriverController { } }); const webElements = await this.driver.wait(condition, ELEMENT_WAIT_TIMEOUT); - return webElements.map((webElement) => new ElementHandle(webElement, this)); + return webElements.map((webElement) => new ElementHandle(webElement)); } /** * @param {string} xpath - * @return {!Promise>} - * @override + * @return {!Promise>} */ async findElementXPath(xpath) { await this.maybeInstallXpath_(); @@ -195,13 +196,12 @@ class SeleniumWebDriverController { }), ELEMENT_WAIT_TIMEOUT ); - return new ElementHandle(webElement, this); + return new ElementHandle(webElement); } /** * @param {string} xpath - * @return {!Promise>>} - * @override + * @return {!Promise>>} */ async findElementsXPath(xpath) { await this.maybeInstallXpath_(); @@ -220,7 +220,7 @@ class SeleniumWebDriverController { }), ELEMENT_WAIT_TIMEOUT ); - return webElements.map((webElement) => new ElementHandle(webElement, this)); + return webElements.map((webElement) => new ElementHandle(webElement)); } /** @@ -244,8 +244,7 @@ class SeleniumWebDriverController { } /** - * @return {!Promise>} - * @override + * @return {!Promise>} */ async getActiveElement() { const root = await this.getRoot_(); @@ -256,8 +255,7 @@ class SeleniumWebDriverController { } /** - * @return {!Promise>} - * @override + * @return {!Promise>} */ async getDocumentElement() { const root = await this.getRoot_(); @@ -267,8 +265,7 @@ class SeleniumWebDriverController { } /** - * @return {!ControllerPromise} - * @override + * @return {!ControllerPromise} */ getCurrentUrl() { return new ControllerPromise( @@ -280,17 +277,15 @@ class SeleniumWebDriverController { /** * @param {string} location * @return {!Promise} - * @override */ async navigateTo(location) { - return await this.driver.get(location); + return this.driver.get(location); } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @param {string|Key} keys * @return {!Promise} - * @override */ async type(handle, keys) { const targetElement = handle @@ -298,22 +293,21 @@ class SeleniumWebDriverController { : await this.driver.switchTo().activeElement(); if (keys === Key.CtrlV) { - return await this.pasteFromClipboard(); + return this.pasteFromClipboard(); } const key = KeyToSeleniumMap[keys]; if (key) { - return await targetElement.sendKeys(key); + return targetElement.sendKeys(key); } - return await targetElement.sendKeys(keys); + return targetElement.sendKeys(keys); } /** * Pastes from the clipboard by perfoming the keyboard shortcut. * https://stackoverflow.com/a/41046276 * @return {!Promise} - * @override */ pasteFromClipboard() { return this.driver @@ -326,9 +320,8 @@ class SeleniumWebDriverController { } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @return {!Promise} - * @override */ getElementText(handle) { const webElement = handle.getElement(); @@ -339,9 +332,8 @@ class SeleniumWebDriverController { } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @return {!Promise} - * @override */ getElementTagName(handle) { const webElement = handle.getElement(); @@ -349,10 +341,9 @@ class SeleniumWebDriverController { } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @param {string} attribute * @return {!Promise} - * @override */ getElementAttribute(handle, attribute) { const webElement = handle.getElement(); @@ -366,10 +357,9 @@ class SeleniumWebDriverController { /** * Gets the element property. Note that this is different * than getElementAttribute() - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @param {string} property * @return {!Promise} - * @override */ getElementProperty(handle, property) { const webElement = handle.getElement(); @@ -384,9 +374,8 @@ class SeleniumWebDriverController { } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @return {!Promise} - * @override */ getElementRect(handle) { const webElement = handle.getElement(); @@ -394,14 +383,8 @@ class SeleniumWebDriverController { // Extracting the values seems to perform better than returning // the raw ClientRect from the element, in terms of flakiness. // The raw ClientRect also has hundredths of a pixel. We round to int. - const { - width, - height, - top, - bottom, - left, - right, - } = element./*OK*/ getBoundingClientRect(); + const {bottom, height, left, right, top, width} = + element./*OK*/ getBoundingClientRect(); return { x: Math.round(left), y: Math.round(top), @@ -423,7 +406,6 @@ class SeleniumWebDriverController { * @param {!ElementHandle} handle * @param {string} styleProperty * @return {!Promise} styleProperty - * @override */ getElementCssValue(handle, styleProperty) { const webElement = handle.getElement(); @@ -436,7 +418,6 @@ class SeleniumWebDriverController { /** * @param {!ElementHandle} handle * @return {!Promise} - * @override */ isElementEnabled(handle) { const webElement = handle.getElement(); @@ -447,16 +428,14 @@ class SeleniumWebDriverController { } /** * @return {!Promise>} - * @override */ async getAllWindows() { - return await this.driver.getAllWindowHandles(); + return this.driver.getAllWindowHandles(); } /** * @param {!ElementHandle} handle * @return {!Promise} - * @override */ isElementSelected(handle) { const webElement = handle.getElement(); @@ -468,12 +447,11 @@ class SeleniumWebDriverController { /** * Sets width/height of the browser area. - * @param {!WindowRectDef} rect + * @param {!selenium.WindowRectDef} rect * @return {!Promise} - * @override */ async setWindowRect(rect) { - const {width, height} = rect; + const {height, width} = rect; await this.driver.manage().window().setRect({ x: 0, @@ -537,7 +515,6 @@ class SeleniumWebDriverController { /** * Get the title of the current document. * @return {!Promise} - * @override */ getTitle() { const getTitle = () => document.title; @@ -550,19 +527,17 @@ class SeleniumWebDriverController { /** * - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @return {!Promise} - * @override */ async click(handle) { - return await handle.getElement().click(); + return handle.getElement().click(); } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @param {!ScrollToOptionsDef=} opt_scrollToOptions * @return {!Promise} - * @override */ async scrollBy(handle, opt_scrollToOptions) { const webElement = handle.getElement(); @@ -570,18 +545,13 @@ class SeleniumWebDriverController { element./*OK*/ scrollBy(opt_scrollToOptions); }; - return await this.driver.executeScript( - scrollBy, - webElement, - opt_scrollToOptions - ); + return this.driver.executeScript(scrollBy, webElement, opt_scrollToOptions); } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @param {!ScrollToOptionsDef=} opt_scrollToOptions * @return {!Promise} - * @override */ async scrollTo(handle, opt_scrollToOptions) { const webElement = handle.getElement(); @@ -589,17 +559,12 @@ class SeleniumWebDriverController { element./*OK*/ scrollTo(opt_scrollToOptions); }; - return await this.driver.executeScript( - scrollTo, - webElement, - opt_scrollToOptions - ); + return this.driver.executeScript(scrollTo, webElement, opt_scrollToOptions); } /** * @param {string} path - * @return {!Promise} An encoded string representing the image data - * @override + * @return {!Promise} An encoded string representing the image data */ async takeScreenshot(path) { const imageString = await this.driver.takeScreenshot(); @@ -608,10 +573,9 @@ class SeleniumWebDriverController { /** * Evaluate the given function - * @param {function()} fn + * @param {function(): any} fn * @param {...*} args - * @return {!ControllerPromise} - * @override + * @return {!Promise<*>} */ evaluate(fn, ...args) { return this.driver.executeScript(fn, ...args); @@ -620,8 +584,7 @@ class SeleniumWebDriverController { /** * @param {!ElementHandle} handle * @param {string} path - * @return {!Promise} An encoded string representing the image data - * @override + * @return {!Promise} An encoded string representing the image data */ async takeElementScreenshot(handle, path) { // TODO(cvializ): Errors? Or maybe ChromeDriver hasn't yet implemented @@ -634,14 +597,13 @@ class SeleniumWebDriverController { /** * @param {string} handle * @return {!Promise} - * @override */ async switchToWindow(handle) { await this.driver.switchTo().window(handle); } /** - * @param {!ElementHandle} handle + * @param {!ElementHandle} handle * @return {!Promise} */ async switchToFrame(handle) { @@ -664,8 +626,8 @@ class SeleniumWebDriverController { /** * Switch controller to shadowRoot body hosted by given element. - * @param {!ElementHandle} handle - * @return {!Promise} + * @param {!ElementHandle} handle + * @return {!Promise} */ async switchToShadow(handle) { const getter = (shadowHost) => shadowHost.shadowRoot.body; @@ -674,8 +636,8 @@ class SeleniumWebDriverController { /** * Switch controller to shadowRoot hosted by given element. - * @param {!ElementHandle} handle - * @return {!Promise} + * @param {!ElementHandle} handle + * @return {!Promise} */ async switchToShadowRoot(handle) { const getter = (shadowHost) => shadowHost.shadowRoot; @@ -683,8 +645,9 @@ class SeleniumWebDriverController { } /**. - * @param {!ElementHandle} handle - * @param {!Function} getter + * @param {!ElementHandle} handle + * @param {function(): any} getter + * @return {!Promise} */ async switchToShadowInternal_(handle, getter) { const shadowHost = handle.getElement(); @@ -692,13 +655,16 @@ class SeleniumWebDriverController { this.shadowRoot_ = shadowRootBody; } + /** + * @return {Promise} + */ async switchToLight() { this.shadowRoot_ = null; } /** * Get the current root - * @return {!Promise} + * @return {!Promise} */ getRoot_() { if (this.shadowRoot_) { @@ -708,7 +674,10 @@ class SeleniumWebDriverController { return this.evaluate(() => document.documentElement); } - /** @override */ + /** + * Shutdown the driver. + * @return {void} + */ dispose() { return this.driver.quit(); } diff --git a/build-system/tasks/e2e/yarn.lock b/build-system/tasks/e2e/yarn.lock deleted file mode 100644 index be9e7d3d9a972..0000000000000 --- a/build-system/tasks/e2e/yarn.lock +++ /dev/null @@ -1,1111 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/register@7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.10.1.tgz#b6567c5cb5049f44bbf8c35d6ff68ca3c43238ed" - integrity sha512-sl96+kB3IA2B9EzpwwBmYadOT14vw3KaXOknGDbJaZCOj52GDA4Tivudq9doCJcB+bEIKCEARZYwRgBBsCGXyg== - dependencies: - find-cache-dir "^2.0.0" - lodash "^4.17.13" - make-dir "^2.1.0" - pirates "^4.0.0" - source-map-support "^0.5.16" - -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== - dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== - dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" - -"@testim/chrome-version@^1.0.7": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9" - integrity sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw== - -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== - -"@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/node@*": - version "12.6.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c" - integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg== - -"@types/yauzl@^2.9.1": - version "2.9.1" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" - integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== - dependencies: - "@types/node" "*" - -adm-zip@0.4.11: - version "0.4.11" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" - integrity sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA== - -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - -aggregate-error@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" - integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - -babel-regenerator-runtime@6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/babel-regenerator-runtime/-/babel-regenerator-runtime-6.5.0.tgz#0e41cd1c9f80442466f015c749fff8ba98f8e110" - integrity sha1-DkHNHJ+ARCRm8BXHSf/4upj44RA= - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -bl@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" - integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bluebird@3.4.6: - version "3.4.6" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f" - integrity sha1-AdqNgh2HgT0ViWfnQ9X+bGLPjA8= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer@^5.2.1, buffer@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== - -chownr@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chromedriver@83.0.0: - version "83.0.0" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-83.0.0.tgz#75d7d838e58014658c3990089464166fef951926" - integrity sha512-AePp9ykma+z4aKPRqlbzvVlc22VsQ6+rgF+0aL3B5onHOncK18dWSkLrSSJMczP/mXILN9ohGsvpuTwoRSj6OQ== - dependencies: - "@testim/chrome-version" "^1.0.7" - axios "^0.19.2" - del "^5.1.0" - extract-zip "^2.0.0" - mkdirp "^1.0.4" - tcp-port-used "^1.0.1" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -create-error-class@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= - dependencies: - capture-stack-trace "^1.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== - dependencies: - ms "^2.1.1" - -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -deep-is@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -del@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" - integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== - dependencies: - globby "^10.0.1" - graceful-fs "^4.2.2" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.1" - p-map "^3.0.0" - rimraf "^3.0.0" - slash "^3.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -duplexer2@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= - dependencies: - readable-stream "^2.0.2" - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -error-ex@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es6-promise@^4.0.3: - version "4.2.6" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" - integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - -extract-zip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" - integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -fast-glob@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" - integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - -fastq@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" - integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== - dependencies: - reusify "^1.0.0" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-cache-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== - dependencies: - minipass "^2.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -geckodriver@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-1.19.1.tgz#556f95fd6451b553cec89f81f81abbefce10d6e5" - integrity sha512-xWL/+eEhQ6+t98rc1c+xVM3hshDJibXtZf9WJA3sshxq4k5L1PBwfmswyBmmlKUfBr4xuC256gLVC2RxFhiCsQ== - dependencies: - adm-zip "0.4.11" - bluebird "3.4.6" - got "5.6.0" - https-proxy-agent "3.0.0" - tar "4.4.2" - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - -glob-parent@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globby@^10.0.1: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - -got@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf" - integrity sha1-ux1+4WO3gIK7yOuDbz85UATqb78= - dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" - is-plain-obj "^1.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^2.0.0" - unzip-response "^1.0.0" - url-parse-lax "^1.0.0" - -graceful-fs@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - -https-proxy-agent@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz#0106efa5d63d6d6f3ab87c999fa4877a3fd1ff97" - integrity sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -ignore@^5.1.1: - version "5.1.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" - integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= - -is-retry-allowed@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" - integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= - -is-stream@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-url@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - -is2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a" - integrity sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA== - dependencies: - deep-is "^0.1.3" - ip-regex "^2.1.0" - is-url "^1.2.2" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -jszip@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d" - integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - set-immediate-shim "~1.0.1" - -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -lodash@^4.17.13: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -make-dir@^2.0.0, make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -merge2@^1.2.3, merge2@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" - integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== - -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime@^2.0.3: - version "2.4.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" - integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8, minimist@^1.2.3: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -minipass@^2.2.1, minipass@^2.2.4: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mkdirp-classic@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" - integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== - -mkdirp@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -node-modules-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" - integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= - -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - integrity sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8= - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -os-tmpdir@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -p-limit@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" - integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@~1.0.2: - version "1.0.8" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4" - integrity sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA== - -parse-json@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -picomatch@^2.0.5: - version "2.2.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" - integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -pirates@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.0.tgz#850b18781b4ac6ec58a43c9ed9ec5fe6796addbd" - integrity sha512-8t5BsXy1LUIjn3WWOlOuFDuKswhQb/tkak641lvBgmPOBUQHXveORtlMCp6OdPV1dtuTaEahKA8VNz6uLfKBtA== - dependencies: - node-modules-regexp "^1.0.0" - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== - -progress@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -puppeteer@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7" - integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw== - dependencies: - debug "^4.1.0" - extract-zip "^2.0.0" - https-proxy-agent "^4.0.0" - mime "^2.0.3" - progress "^2.0.1" - proxy-from-env "^1.0.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" - -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - integrity sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po= - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" - -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -reusify@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== - -safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -selenium-webdriver@4.0.0-alpha.7: - version "4.0.0-alpha.7" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" - integrity sha512-D4qnTsyTr91jT8f7MfN+OwY0IlU5+5FmlO5xlgRUV6hDEV8JyYx2NerdTEqDDkNq7RZDYc4VoPALk8l578RBHw== - dependencies: - jszip "^3.2.2" - rimraf "^2.7.1" - tmp "0.0.30" - -semver@^5.6.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== - -set-immediate-shim@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@^0.5.16: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -tar-fs@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" - integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.0.0" - -tar-stream@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" - integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== - dependencies: - bl "^4.0.1" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.2.tgz#60685211ba46b38847b1ae7ee1a24d744a2cd462" - integrity sha512-BfkE9CciGGgDsATqkikUHrQrraBCO+ke/1f6SFAEMnxyyfN9lxC+nW1NFWMpqH865DhHIy9vQi682gk1X7friw== - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.2.4" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - -tcp-port-used@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tcp-port-used/-/tcp-port-used-1.0.1.tgz#46061078e2d38c73979a2c2c12b5a674e6689d70" - integrity sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q== - dependencies: - debug "4.1.0" - is2 "2.0.1" - -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -timed-out@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" - integrity sha1-84sK6B03R9YoAB9B2vxlKs5nHAo= - -tmp@0.0.30: - version "0.0.30" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed" - integrity sha1-ckGdSovn1s51FI/YsyTlk6cRwu0= - dependencies: - os-tmpdir "~1.0.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -unbzip2-stream@^1.3.3: - version "1.4.1" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.1.tgz#151b104af853df3efdaa135d8b1eca850a44b426" - integrity sha512-sgDYfSDPMsA4Hr2/w7vOlrJBlwzmyakk1+hW8ObLvxSp0LA36LcL2XItGvOT3OSblohSdevMuT8FQjLsqyy4sA== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - -unzip-response@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - integrity sha1-uYTwh3/AqJwsdzzB73tbIytbBv4= - -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= - dependencies: - prepend-http "^1.0.1" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -ws@^7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== - -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" diff --git a/build-system/tasks/extension-generator/OWNERS b/build-system/tasks/extension-generator/OWNERS deleted file mode 100644 index ae36b3a522e6b..0000000000000 --- a/build-system/tasks/extension-generator/OWNERS +++ /dev/null @@ -1,17 +0,0 @@ -// For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example - -{ - rules: [ - { - owners: [ - // Outreach group maintains extension boilerplate/template. - {name: 'ampproject/wg-outreach'}, - - // Most new extensions are reviewed by this group. It's in their - // interest to maintain the extension generator. - {name: 'ampproject/wg-ui-and-a11y'}, - ], - }, - ], -} diff --git a/build-system/tasks/extension-generator/extension-doc.template.md b/build-system/tasks/extension-generator/extension-doc.template.md deleted file mode 100644 index 22ac2514bca7b..0000000000000 --- a/build-system/tasks/extension-generator/extension-doc.template.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -\$category@: presentation -formats: - - websites - - email -teaser: - text: Fill this in with teaser text to improve SEO. Use the component description. ---- - - - - - -# `${name}` - - - -## Usage - -One to three paragraphs explaining the component usage. List important functionality. Explain why developers care about it. - -[filter formats=“websites”] - -Below is an example for websites. - -[example preview="inline" playground="true" imports="${name}"] - -```html -<${name} required-attribute> - I am a hello world inline executable code sample for websites! - -``` - -[/example][/filter] - - - -[filter formats=“ads”] - -Below is an example for ads. - -[example preview=“inline” playground=“true” imports="${name}"] - -```html -<${name} required-attribute> - I am a hello world inline executable code sample for ads! - -``` - -[/example][/filter] - -### Behavior users should be aware of (optional) - -What to do if they want behavior. How to work around it. - -```html -<${name} required-attribute> - Code sample of behavior or behavior workaround. - -``` - -### Behavior restrictions - -What is allowed, what isn't. - -## Attributes - -### `attribute-name` - -Description of attribute. Use cases for this attribute. - -- `attribute-value-option-one` (default): `attribute-option-one-value` does this to `${name}`. -- `attribute-value-option-two`: `attribute-option-two-value` does this to `${name}`. - -### `optional-attribute-name` (optional) - -Here, I write what `optional-attribute-name` will do to `${name}`. - -## Actions (optional) - -### `action-name` - -Description of action. Use cases of `action-name`. Include all the nuances, such as: `${name}` needs to be identified with an `id` to work. - -## Events (optional) - -### `event-name` - -Description of event. Use cases of event-name. Include all the nuances, such as: `${name}` needs to be identified with an `id` to work. - -[example preview=”top-frame” playground=”true”] - -```html - - - - - <${name} - required-attribute - on="event-name: my-button.show" - > - Hello World! - - - -``` - -[/example] - -## Styling (optional) - -Explain how to style the element. - -## Analytics (optional) - -Explain analytics. - -```html -"configuration": {} -``` - -## Accessibility (optional) - -Accessibility information related to `${name}`. - -## Version notes (optional) - -Information on version differences and migration notes. - -## Validation - -See [\${name} rules](https://github.com/ampproject/amphtml/blob/master/extensions/${name}/validator-${name}.protoascii) in the AMP validator specification. diff --git a/build-system/tasks/extension-generator/index.js b/build-system/tasks/extension-generator/index.js deleted file mode 100644 index 838db7a959a34..0000000000000 --- a/build-system/tasks/extension-generator/index.js +++ /dev/null @@ -1,259 +0,0 @@ -/** - * Copyright 2017 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. - */ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); -const fs = require('fs-extra'); -const log = require('fancy-log'); - -const year = new Date().getFullYear(); - -/*eslint "max-len": 0*/ - -function pascalCase(str) { - return ( - str[0].toUpperCase() + - str.slice(1).replace(/-([a-z])/g, function (g) { - return g[1].toUpperCase(); - }) - ); -} - -function getValidatorFile(name) { - return `# -# Copyright ${year} 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. -# - -tags: { # ${name} - html_format: AMP - tag_name: "SCRIPT" - extension_spec: { - name: "${name}" - version: "0.1" - version: "latest" - } - attr_lists: "common-extension-attrs" -} -tags: { # <${name}> - html_format: AMP - tag_name: "${name.toUpperCase()}" - requires_extension: "${name}" - attr_lists: "extended-amp-global" - spec_url: "https://amp.dev/documentation/components/${name}" - amp_layout: { - supported_layouts: RESPONSIVE - } -} -`; -} - -const getMarkdownDocFile = async (name) => - (await fs.readFile(`${__dirname}/extension-doc.template.md`)) - .toString('utf-8') - .replace(/\\\$category/g, '$category') - .replace(/\\?\${name}/g, name) - .replace(/\\?\${year}/g, year); - -function getJsTestExtensionFile(name) { - return `/** - * Copyright ${year} 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. - */ - -import '../${name}'; -import {createElementWithAttributes} from '../../../../src/dom'; - -describes.realWin( - '${name}', - { - amp: { - runtimeOn: true, - extensions: ['${name}'], - }, - }, - (env) => { - let win; - let element; - - beforeEach(() => { - win = env.win; - element = createElementWithAttributes(win.document, '${name}', { - layout: 'responsive', - }); - win.document.body.appendChild(element); - }); - - it('should contain "hello world" when built', async () => { - await element.whenBuilt(); - expect(element.querySelector('div').textContent).to.equal('hello world'); - }); - } -); -`; -} - -function getJsExtensionFile(name) { - const className = pascalCase(name); - return `/** - * Copyright ${year} 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. - */ - -import {Layout} from '../../../src/layout'; - -export class ${className} extends AMP.BaseElement { - - /** @param {!AmpElement} element */ - constructor(element) { - super(element); - - /** @private {string} */ - this.myText_ = 'hello world'; - - /** @private {?Element} */ - this.container_ = null; - } - - /** @override */ - buildCallback() { - this.container_ = this.element.ownerDocument.createElement('div'); - this.container_.textContent = this.myText_; - this.element.appendChild(this.container_); - this.applyFillContent(this.container_, /* replacedContent */ true); - } - - /** @override */ - isLayoutSupported(layout) { - return layout == Layout.RESPONSIVE; - } -} - -AMP.extension('${name}', '0.1', AMP => { - AMP.registerElement('${name}', ${className}); -}); -`; -} - -function getExamplesFile(name) { - return ` - - - - ${name} example - - - - - - - - - <${name} layout="responsive" width="150" height="80"> - - -`; -} - -async function makeExtension() { - if (!argv.name) { - log(colors.red('Error! Please pass in the "--name" flag with a value')); - } - const {name} = argv; - const examplesFile = getExamplesFile(name); - - fs.mkdirpSync(`extensions/${name}/0.1/test`); - fs.writeFileSync( - `extensions/${name}/${name}.md`, - await getMarkdownDocFile(name) - ); - fs.writeFileSync( - `extensions/${name}/validator-${name}.protoascii`, - getValidatorFile(name) - ); - fs.writeFileSync( - `extensions/${name}/0.1/${name}.js`, - getJsExtensionFile(name) - ); - fs.writeFileSync( - `extensions/${name}/0.1/test/test-${name}.js`, - getJsTestExtensionFile(name) - ); - fs.writeFileSync( - `extensions/${name}/0.1/test/validator-${name}.html`, - examplesFile - ); - - const examplesFileValidatorOut = examplesFile - .trim() - .split('\n') - .map((line) => `| ${line}`) - .join('\n'); - - fs.writeFileSync( - `extensions/${name}/0.1/test/validator-${name}.out`, - ['PASS', examplesFileValidatorOut].join('\n') - ); - - fs.writeFileSync(`examples/${name}.amp.html`, examplesFile); -} - -module.exports = { - makeExtension, -}; - -makeExtension.description = 'Create an extension skeleton'; -makeExtension.flags = { - name: ' The name of the extension. Preferable prefixed with `amp-*`', -}; diff --git a/build-system/tasks/extension-helpers.js b/build-system/tasks/extension-helpers.js index 7c6823d087cf8..8370e3a63618c 100644 --- a/build-system/tasks/extension-helpers.js +++ b/build-system/tasks/extension-helpers.js @@ -14,25 +14,38 @@ * limitations under the License. */ -const colors = require('ansi-colors'); +const argv = require('minimist')(process.argv.slice(2)); const debounce = require('debounce'); const fs = require('fs-extra'); -const log = require('fancy-log'); -const watch = require('gulp-watch'); +const path = require('path'); const wrappers = require('../compile/compile-wrappers'); +const { + compileJs, + compileJsWithEsbuild, + doBuildJs, + endBuildStep, + maybeToEsmName, + maybeToNpmEsmName, + mkdirSync, + watchDebounceDelay, +} = require('./helpers'); const { extensionAliasBundles, extensionBundles, + jsBundles, verifyExtensionBundles, } = require('../compile/bundles.config'); -const {endBuildStep, watchDebounceDelay} = require('./helpers'); -const {isTravisBuild} = require('../common/travis'); -const {jsifyCssAsync} = require('./jsify-css'); -const {maybeToEsmName, compileJs, mkdirSync} = require('./helpers'); -const {vendorConfigs} = require('./vendor-configs'); - -const {green, red, cyan} = colors; -const argv = require('minimist')(process.argv.slice(2)); +const { + VERSION: internalRuntimeVersion, +} = require('../compile/internal-version'); +const {analyticsVendorConfigs} = require('./analytics-vendor-configs'); +const {compileJison} = require('./compile-jison'); +const {cyan, green, red} = require('../common/colors'); +const {isCiBuild} = require('../common/ci'); +const {jsifyCssAsync} = require('./css/jsify-css'); +const {log} = require('../common/logging'); +const {parse: pathParse} = require('path'); +const {watch} = require('chokidar'); /** * Extensions to build when `--extensions=inabox`. @@ -72,15 +85,27 @@ const DEFAULT_EXTENSION_SET = ['amp-loader', 'amp-auto-lightbox']; /** * @typedef {{ - * name: ?string, - * version: ?string, - * hasCss: ?boolean, - * loadPriority: ?string, - * cssBinaries: ?Array, - * extraGlobs: ?Array, + * name?: string, + * version?: string, + * hasCss?: boolean, + * loadPriority?: string, + * cssBinaries?: Array, + * extraGlobs?: Array, + * binaries?: Array, + * npm?: boolean, + * }} + */ +const ExtensionOptionDef = {}; + +/** + * @typedef {{ + * entryPoint: string, + * outfile: string, + * external?: Array + * remap?: Record * }} */ -const ExtensionOption = {}; // eslint-disable-line no-unused-vars +const ExtensionBinaryDef = {}; // All declared extensions. const extensions = {}; @@ -95,7 +120,7 @@ const adVendors = []; * @param {string} name * @param {string|!Array} version E.g. 0.1 or [0.1, 0.2] * @param {string} latestVersion E.g. 0.1 - * @param {!ExtensionOption} options extension options object. + * @param {!ExtensionOptionDef|undefined} options extension options object. * @param {!Object} extensionsObject * @param {boolean} includeLatest */ @@ -107,7 +132,7 @@ function declareExtension( extensionsObject, includeLatest ) { - const defaultOptions = {hasCss: false}; + const defaultOptions = {hasCss: false, npm: undefined}; const versions = Array.isArray(version) ? version : [version]; versions.forEach((v) => { extensionsObject[`${name}-${v}`] = { @@ -130,10 +155,10 @@ function declareExtension( } /** - * Initializes all extensions from build-system/compile/bundles.config.js if not - * already done and populates the given extensions object. + * Initializes all extensions from build-system/compile/bundles.config.extensions.json + * if not already done and populates the given extensions object. * @param {?Object} extensionsObject - * @param {?boolean} includeLatest + * @param {boolean=} includeLatest */ function maybeInitializeExtensions( extensionsObject = extensions, @@ -156,7 +181,7 @@ function maybeInitializeExtensions( /** * Set the extensions to build from example documents - * (for internal use by `gulp performance`) + * (for internal use by `amp performance`) * * @param {Array} examples Path to example documents */ @@ -175,10 +200,7 @@ function setExtensionsToBuildFromDocuments(examples) { * @return {!Array} */ function getExtensionsToBuild(preBuild = false) { - if (extensionsToBuild) { - return extensionsToBuild; - } - extensionsToBuild = DEFAULT_EXTENSION_SET; + extensionsToBuild = argv.core_runtime_only ? [] : DEFAULT_EXTENSION_SET; if (argv.extensions) { if (typeof argv.extensions !== 'string') { log(red('ERROR:'), 'Missing list of extensions.'); @@ -194,7 +216,11 @@ function getExtensionsToBuild(preBuild = false) { extensionsToBuild = dedupe(extensionsToBuild.concat(extensionsFrom)); } if ( - !(preBuild || argv.noextensions || argv.extensions || argv.extensions_from) + !preBuild && + !argv.noextensions && + !argv.extensions && + !argv.extensions_from && + !argv.core_runtime_only ) { const allExtensions = []; for (const extension in extensions) { @@ -213,7 +239,7 @@ function getExtensionsToBuild(preBuild = false) { * @param {boolean=} preBuild */ function parseExtensionFlags(preBuild = false) { - if (isTravisBuild()) { + if (isCiBuild()) { return; } @@ -221,7 +247,7 @@ function parseExtensionFlags(preBuild = false) { const coreRuntimeOnlyMessage = green('⤷ Use ') + cyan('--core_runtime_only ') + - green('to build just the core runtime.'); + green('to build just the core runtime and skip other JS targets.'); const noExtensionsMessage = green('⤷ Use ') + cyan('--noextensions ') + @@ -241,7 +267,7 @@ function parseExtensionFlags(preBuild = false) { cyan('foo.amp.html') + green('.'); - if (argv.core_runtime_only) { + if (argv.core_runtime_only && !(argv.extensions || argv.extensions_from)) { log(green('Building just the core runtime.')); } else if (preBuild) { log( @@ -278,7 +304,7 @@ function parseExtensionFlags(preBuild = false) { */ function getExtensionsFromArg(examples) { if (!examples) { - return; + return []; } const extensions = []; @@ -339,7 +365,7 @@ async function buildExtensions(options) { } } await Promise.all(results); - if (!options.compileOnlyCss) { + if (!options.compileOnlyCss && results.length > 0) { endBuildStep( options.minify ? 'Minified all' : 'Compiled all', 'extensions', @@ -370,30 +396,42 @@ async function doBuildExtension(extensions, extension, options) { } /** - * Watches the contents of an extension directory. When a file in the given path - * changes, the extension is rebuilt. + * Watches for non-JS changes within an extensions directory to trigger + * recompilation. * - * @param {string} path + * @param {string} extDir * @param {string} name * @param {string} version * @param {string} latestVersion * @param {boolean} hasCss * @param {?Object} options */ -function watchExtension(path, name, version, latestVersion, hasCss, options) { - const watchFunc = function () { - const bundleComplete = buildExtension( - name, - version, - latestVersion, - hasCss, - {...options, continueOnError: true} - ); - if (options.onWatchBuild) { - options.onWatchBuild(bundleComplete); - } - }; - watch(path + '/**/*', debounce(watchFunc, watchDebounceDelay)); +async function watchExtension( + extDir, + name, + version, + latestVersion, + hasCss, + options +) { + /** + * Steps to run when a watched file is modified. + */ + function watchFunc() { + buildExtension(name, version, latestVersion, hasCss, { + ...options, + continueOnError: true, + isRebuild: true, + watch: false, + }); + } + + const cssDeps = `${extDir}/**/*.css`; + const jisonDeps = `${extDir}/**/*.jison`; + watch([cssDeps, jisonDeps]).on( + 'change', + debounce(watchFunc, watchDebounceDelay) + ); } /** @@ -429,42 +467,52 @@ async function buildExtension( if (options.compileOnlyCss && !hasCss) { return; } - const path = 'extensions/' + name + '/' + version; + const extDir = 'extensions/' + name + '/' + version; - // Use a separate watcher for extensions to copy / inline CSS and compile JS - // instead of relying on the watchers used by compileUnminifiedJs and - // compileMinifiedJs, which only recompile JS. + // Use a separate watcher for css and jison compilation. + // The watcher within compileJs recompiles the JS. if (options.watch) { - options.watch = false; - watchExtension(path, name, version, latestVersion, hasCss, options); - // When an ad network extension is being watched, also watch amp-a4a. - if (name.match(/amp-ad-network-.*-impl/)) { - const a4aPath = `extensions/amp-a4a/${version}`; - watchExtension(a4aPath, name, version, latestVersion, hasCss, options); - } + await watchExtension(extDir, name, version, latestVersion, hasCss, options); } + if (hasCss) { mkdirSync('build'); mkdirSync('build/css'); - await buildExtensionCss(path, name, version, options); + await buildExtensionCss(extDir, name, version, options); if (options.compileOnlyCss) { return; } } + + await compileJison(path.join(extDir, '**', '*.jison')); + if (name === 'amp-bind') { + await doBuildJs(jsBundles, 'ww.max.js', options); + } + if (options.npm) { + await buildNpmBinaries(extDir, options); + } + if (options.binaries) { + await buildBinaries(extDir, options.binaries, options); + } if (name === 'amp-analytics') { - await vendorConfigs(options); + await analyticsVendorConfigs(options); } - await buildExtensionJs(path, name, version, latestVersion, options); + + if (options.isRebuild) { + return; + } + + await buildExtensionJs(extDir, name, version, latestVersion, options); } /** - * @param {string} path + * @param {string} extDir * @param {string} name * @param {string} version * @param {!Object} options * @return {!Promise} */ -function buildExtensionCss(path, name, version, options) { +function buildExtensionCss(extDir, name, version, options) { /** * Writes CSS binaries * @@ -482,7 +530,7 @@ function buildExtensionCss(path, name, version, options) { const isAliased = aliasBundle && aliasBundle.version == version; const promises = []; - const mainCssBinary = jsifyCssAsync(path + '/' + name + '.css').then( + const mainCssBinary = jsifyCssAsync(extDir + '/' + name + '.css').then( (mainCss) => { writeCssBinaries(`${name}-${version}.css`, mainCss); if (isAliased) { @@ -495,7 +543,7 @@ function buildExtensionCss(path, name, version, options) { promises.push.apply( promises, options.cssBinaries.map(function (name) { - return jsifyCssAsync(`${path}/${name}.css`).then((css) => { + return jsifyCssAsync(`${extDir}/${name}.css`).then((css) => { writeCssBinaries(`${name}-${version}.css`, css); if (isAliased) { writeCssBinaries(`${name}-${aliasBundle.aliasedVersion}.css`, css); @@ -508,10 +556,87 @@ function buildExtensionCss(path, name, version, options) { return Promise.all(promises); } +/** + * @param {string} extDir + * @param {!Object} options + * @return {!Promise} + */ +function buildNpmBinaries(extDir, options) { + let {npm} = options; + if (npm === true) { + // Default to the standard/expected entrypoint + npm = { + 'component.js': { + 'preact': 'component-preact.js', + 'react': 'component-react.js', + }, + }; + } + const keys = Object.keys(npm); + const promises = keys.flatMap((entryPoint) => { + const {preact, react} = npm[entryPoint]; + const binaries = []; + if (preact) { + binaries.push({ + entryPoint, + outfile: preact, + external: ['preact', 'preact/dom', 'preact/compat', 'preact/hooks'], + remap: {'preact/dom': 'preact'}, + }); + } + if (react) { + binaries.push({ + entryPoint, + outfile: react, + external: ['react', 'react-dom'], + remap: { + 'preact': 'react', + 'preact/compat': 'react', + 'preact/hooks': 'react', + 'preact/dom': 'react-dom', + }, + }); + } + return buildBinaries(extDir, binaries, options); + }); + return Promise.all(promises); +} + +/** + * @param {string} extDir + * @param {!Array} binaries + * @param {!Object} options + * @return {!Promise} + */ +function buildBinaries(extDir, binaries, options) { + mkdirSync(`${extDir}/dist`); + + const promises = binaries.map((binary) => { + const {entryPoint, external, outfile, remap} = binary; + const {name} = pathParse(outfile); + const esm = argv.esm || argv.sxg || false; + return compileJsWithEsbuild( + extDir + '/', + entryPoint, + `${extDir}/dist`, + Object.assign(options, { + toName: maybeToNpmEsmName(`${name}.max.js`), + minifiedName: maybeToNpmEsmName(`${name}.js`), + latestName: '', + outputFormat: esm ? 'esm' : 'cjs', + wrapper: '', + externalDependencies: external, + remapDependencies: remap, + }) + ); + }); + return Promise.all(promises); +} + /** * Build the JavaScript for the extension specified * - * @param {string} path Path to the extensions directory + * @param {string} extDir Path to the extension's directory * @param {string} name Name of the extension. Must be the sub directory in * the extensions directory and the name of the JS and optional CSS file. * @param {string} version Version of the extension. Must be identical to @@ -520,17 +645,17 @@ function buildExtensionCss(path, name, version, options) { * @param {!Object} options * @return {!Promise} */ -async function buildExtensionJs(path, name, version, latestVersion, options) { +async function buildExtensionJs(extDir, name, version, latestVersion, options) { const filename = options.filename || name + '.js'; + const latest = version === latestVersion; await compileJs( - path + '/', + extDir + '/', filename, './dist/v0', Object.assign(options, { toName: `${name}-${version}.max.js`, minifiedName: `${name}-${version}.js`, - latestName: version === latestVersion ? `${name}-latest.js` : '', - esmPassCompilation: argv.esm || false, + latestName: latest ? `${name}-latest.js` : '', // Wrapper that either registers the extension or schedules it for // execution after the main binary comes back. // The `function` is wrapped in `()` to avoid lazy parsing it, @@ -538,7 +663,13 @@ async function buildExtensionJs(path, name, version, latestVersion, options) { // See https://github.com/ampproject/amphtml/issues/3977 wrapper: options.noWrapper ? '' - : wrappers.extension(name, options.loadPriority), + : wrappers.extension( + name, + version, + latest, + argv.esm, + options.loadPriority + ), }) ); @@ -561,14 +692,101 @@ async function buildExtensionJs(path, name, version, latestVersion, options) { } if (name === 'amp-script') { - // Copy @ampproject/worker-dom/dist/amp/worker/worker.js to dist/ folder. - const dir = 'node_modules/@ampproject/worker-dom/dist/amp/worker/'; - const file = `dist/v0/amp-script-worker-${version}`; - // The "js" output is minified and transpiled to ES5. - fs.copyFileSync(dir + 'worker.js', `${file}.js`); - // The "mjs" output is unminified ES6 and has debugging flags enabled. - fs.copyFileSync(dir + 'worker.mjs', `${file}.max.js`); + await copyWorkerDomResources(version); + await buildSandboxedProxyIframe(options.minify); + } +} + +/** + * Builds and writes the HTML file used for sandboxed mode. + * @param {boolean} minify + */ +async function buildSandboxedProxyIframe(minify) { + await doBuildJs(jsBundles, 'amp-script-proxy-iframe.js', {minify}); + const dist3pDir = path.join( + 'dist.3p', + minify ? `${internalRuntimeVersion}` : 'current' + ); + const fileExt = argv.esm ? '.mjs' : '.js'; + const proxyScript = await fs.readFile( + path.join(dist3pDir, 'amp-script-proxy-iframe' + fileExt) + ); + const proxyIframe = ``; + await fs.outputFile( + path.join(dist3pDir, 'amp-script-proxy-iframe.html'), + proxyIframe + ); +} + +/** + * Copies the required resources from @ampproject/worker-dom and renames + * them accordingly. + * + * @param {string} version + */ +async function copyWorkerDomResources(version) { + const startTime = Date.now(); + const workerDomDir = 'node_modules/@ampproject/worker-dom'; + const targetDir = 'dist/v0'; + const dir = `${workerDomDir}/dist`; + const workerFilesToDeploy = new Map([ + ['amp-production/worker/worker.js', `amp-script-worker-${version}.js`], + [ + 'amp-production/worker/worker.nodom.js', + `amp-script-worker-nodom-${version}.js`, + ], + ['amp-production/worker/worker.mjs', `amp-script-worker-${version}.mjs`], + [ + 'amp-production/worker/worker.nodom.mjs', + `amp-script-worker-nodom-${version}.mjs`, + ], + [ + 'amp-production/worker/worker.js.map', + `amp-script-worker-${version}.js.map`, + ], + [ + 'amp-production/worker/worker.nodom.js.map', + `amp-script-worker-nodom-${version}.js.map`, + ], + [ + 'amp-production/worker/worker.mjs.map', + `amp-script-worker-${version}.mjs.map`, + ], + [ + 'amp-production/worker/worker.nodom.mjs.map', + `amp-script-worker-nodom-${version}.mjs.map`, + ], + ['amp-debug/worker/worker.js', `amp-script-worker-${version}.max.js`], + [ + 'amp-debug/worker/worker.nodom.js', + `amp-script-worker-nodom-${version}.max.js`, + ], + ['amp-debug/worker/worker.mjs', `amp-script-worker-${version}.max.mjs`], + [ + 'amp-debug/worker/worker.nodom.mjs', + `amp-script-worker-nodom-${version}.max.mjs`, + ], + [ + 'amp-debug/worker/worker.js.map', + `amp-script-worker-${version}.max.js.map`, + ], + [ + 'amp-debug/worker/worker.nodom.js.map', + `amp-script-worker-nodom-${version}.max.js.map`, + ], + [ + 'amp-debug/worker/worker.mjs.map', + `amp-script-worker-${version}.max.mjs.map`, + ], + [ + 'amp-debug/worker/worker.nodom.mjs.map', + `amp-script-worker-nodom-${version}.max.mjs.map`, + ], + ]); + for (const [src, dest] of workerFilesToDeploy) { + await fs.copy(`${dir}/${src}`, `${targetDir}/${dest}`); } + endBuildStep('Copied', '@ampproject/worker-dom resources', startTime); } module.exports = { diff --git a/build-system/tasks/firebase.js b/build-system/tasks/firebase.js index 970564b3616a4..2e7f0a0889579 100644 --- a/build-system/tasks/firebase.js +++ b/build-system/tasks/firebase.js @@ -14,14 +14,18 @@ * limitations under the License. */ const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); const fs = require('fs-extra'); -const log = require('fancy-log'); const path = require('path'); const {clean} = require('./clean'); const {doBuild} = require('./build'); const {doDist} = require('./dist'); +const {green} = require('../common/colors'); +const {log} = require('../common/logging'); +/** + * @param {string} dest + * @return {Promise} + */ async function walk(dest) { const filelist = []; const files = await fs.readdir(dest); @@ -37,6 +41,11 @@ async function walk(dest) { return filelist; } +/** + * @param {string} src + * @param {string} dest + * @return {Promise} + */ async function copyAndReplaceUrls(src, dest) { await fs.copy(src, dest, {overwrite: true}); // Recursively gets all the files within the directory and its children. @@ -47,6 +56,9 @@ async function copyAndReplaceUrls(src, dest) { await Promise.all(promises); } +/** + * @return {Promise} + */ async function firebase() { if (!argv.nobuild) { await clean(); @@ -58,32 +70,32 @@ async function firebase() { } await fs.mkdirp('firebase'); if (argv.file) { - log(colors.green(`Processing file: ${argv.file}.`)); - log(colors.green('Writing file to firebase.index.html.')); - await fs.copyFile(/*src*/ argv.file, 'firebase/index.html', { - overwrite: true, - }); + log(green(`Processing file: ${argv.file}.`)); + log(green('Writing file to firebase.index.html.')); + await fs.copyFile(/*src*/ argv.file, 'firebase/index.html'); await replaceUrls('firebase/index.html'); } else { - log(colors.green('Copying test/manual and examples folders.')); + log(green('Copying test/manual and examples folders.')); await Promise.all([ copyAndReplaceUrls('test/manual', 'firebase/manual'), copyAndReplaceUrls('examples', 'firebase/examples'), ]); } - log(colors.green('Copying local amp files from dist folder.')); + log(green('Copying local amp files from dist folder.')); await Promise.all([ fs.copy('dist', 'firebase/dist', {overwrite: true}), fs.copy('dist.3p/current', 'firebase/dist.3p/current', {overwrite: true}), ]); await Promise.all([ - fs.copyFile('firebase/dist/ww.max.js', 'firebase/dist/ww.js', { - overwrite: true, - }), + fs.copyFile('firebase/dist/ww.max.js', 'firebase/dist/ww.js'), ]); } +/** + * @param {string} filePath + * @return {Promise} + */ async function replaceUrls(filePath) { const data = await fs.readFile(filePath, 'utf8'); let result = data.replace( @@ -110,9 +122,9 @@ module.exports = { firebase.description = 'Generates firebase folder for deployment'; firebase.flags = { - 'file': ' File to deploy to firebase as index.html', - 'compiled': ' Deploy from minified files', - 'nobuild': ' Skips the gulp build|dist step.', + 'file': 'File to deploy to firebase as index.html', + 'compiled': 'Deploy from minified files', + 'nobuild': 'Skips the amp build|dist step.', 'fortesting': - ' Expects an env var AMP_TESTING_HOST and writes this to AMP_CONFIG', + 'Expects an env var AMP_TESTING_HOST and writes this to AMP_CONFIG', }; diff --git a/build-system/tasks/generate-runner.js b/build-system/tasks/generate-runner.js deleted file mode 100644 index a4020ac2c4d66..0000000000000 --- a/build-system/tasks/generate-runner.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -const fs = require('fs-extra'); -const log = require('fancy-log'); -const {cyan, green, red} = require('ansi-colors'); -const {getOutput} = require('../common/exec'); -const {gitCommitHash, gitDiffPath} = require('../common/git'); -const {isTravisBuild} = require('../common/travis'); - -const antExecutable = 'third_party/ant/bin/ant'; -const runnerDir = 'build-system/runner'; -const buildFile = `${runnerDir}/build.xml`; -const runnerDistDir = `${runnerDir}/dist`; -const generatedAtCommitFile = 'GENERATED_AT_COMMIT'; -const setupInstructionsUrl = - 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-e2e.md#building-amp-and-starting-a-local-server'; - -/** - * Determines if runner.jar needs to be regenerated - * @param {string} subDir - * @return {boolean} - */ -function shouldGenerateRunner(subDir) { - const runnerJarDir = `${runnerDistDir}/${subDir}`; - const runnerJar = `${runnerJarDir}/runner.jar`; - const generatedAtCommitPath = `${runnerJarDir}/${generatedAtCommitFile}`; - - // Always generate on Travis - if (isTravisBuild()) { - return true; - } - - // The binary hasn't been generated - if (!fs.existsSync(runnerJar)) { - return true; - } - - // We don't know when the binary was last generated - if (!fs.existsSync(generatedAtCommitPath)) { - return true; - } - - // The binary was generated at a different commit - const currentCommit = gitCommitHash(); - const generatedAtCommit = fs - .readFileSync(generatedAtCommitPath, 'utf8') - .toString(); - if (currentCommit != generatedAtCommit) { - return true; - } - - // There are local changes in the build-system/runner directory - const localRunnerChanges = gitDiffPath(runnerDir, gitCommitHash()); - if (localRunnerChanges) { - return true; - } - - return false; -} - -/** - * Generates runner.jar if required - * @param {string} subDir - */ -async function maybeGenerateRunner(subDir) { - if (shouldGenerateRunner(subDir)) { - await generateRunner(subDir); - } -} - -/** - * Writes a file to the runner dir to indicate when it was last generated - * @param {string} runnerJarDir - */ -function writeGeneratedAtCommitFile(runnerJarDir) { - let generatedAtCommit = gitCommitHash(); - const localRunnerChanges = gitDiffPath(runnerDir, gitCommitHash()); - if (localRunnerChanges) { - generatedAtCommit += '+local'; - } - fs.ensureDirSync(runnerJarDir); - fs.writeFileSync( - `${runnerJarDir}/${generatedAtCommitFile}`, - generatedAtCommit - ); -} - -/** - * Generates the custom closure compiler binary (runner.jar) and drops it in the - * given subdirectory of build-system/runner/dist/ (to enable concurrent usage) - * @param {string} subDir - * @return {!Promise} - */ -async function generateRunner(subDir) { - const generateCmd = `${antExecutable} -buildfile ${buildFile} -Ddist.dir dist/${subDir} jar`; - const runnerJarDir = `${runnerDistDir}/${subDir}`; - writeGeneratedAtCommitFile(runnerJarDir); - const result = getOutput(generateCmd); - if (0 !== result.status) { - log( - red('ERROR:'), - 'Could not generate custom closure compiler', - cyan(`${runnerJarDir}/runner.jar`) - ); - console.error(red(result.stdout), red(result.stderr)); - log( - green('INFO:'), - 'If the errors above are in java execution, see', - cyan(`${setupInstructionsUrl}`) - ); - const reason = new Error('Compiler generation failed'); - reason.showStack = false; - return Promise.reject(reason); - } else { - log( - 'Generated custom closure compiler', - cyan(`${runnerJarDir}/runner.jar`) - ); - } -} - -module.exports = { - maybeGenerateRunner, -}; diff --git a/build-system/tasks/get-zindex/get-zindex.test.js b/build-system/tasks/get-zindex/get-zindex.test.js new file mode 100644 index 0000000000000..45379a2cf1512 --- /dev/null +++ b/build-system/tasks/get-zindex/get-zindex.test.js @@ -0,0 +1,73 @@ +/** + * Copyright 2016 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. + */ +'use strict'; + +const test = require('ava'); +const {createTable, getZindexChainsInJs, getZindexSelectors} = require('.'); + +const cssResult = { + 'test.css': { + '.selector-1': '1', + '.selector-2': '0', + '.selector-3': '99', + }, + 'test-2.css': { + '.selector-4': '80', + '.selector-5': 'initial', + '.selector-6': 'auto', + }, +}; + +const jsResult = { + 'test-0.js': [ + ['
', 'initial'], + ['assignment', 'auto'], + ['declarator', 0], + ['setStyle', 15], + ['setStyles', 9999], + ], + 'test-1.js': [['', 12345]], +}; + +test('collects selectors', async (t) => { + const data = await getZindexSelectors('*.css', __dirname); + t.deepEqual(data, cssResult); +}); + +test('collects chains from js', async (t) => { + const data = await getZindexChainsInJs('*.js', __dirname); + t.deepEqual(data, jsResult); +}); + +test('sync - create array of arrays with z index order', (t) => { + t.plan(1); + const table = createTable({...cssResult, ...jsResult}); + const expected = [ + ['`assignment`', 'auto', '[test-0.js](/test-0.js)'], + ['`.selector-6`', 'auto', '[test-2.css](/test-2.css)'], + ['`
`', 'initial', '[test-0.js](/test-0.js)'], + ['`.selector-5`', 'initial', '[test-2.css](/test-2.css)'], + ['``', 12345, '[test-1.js](/test-1.js)'], + ['`setStyles`', 9999, '[test-0.js](/test-0.js)'], + ['`.selector-3`', '99', '[test.css](/test.css)'], + ['`.selector-4`', '80', '[test-2.css](/test-2.css)'], + ['`setStyle`', 15, '[test-0.js](/test-0.js)'], + ['`.selector-1`', '1', '[test.css](/test.css)'], + ['`declarator`', 0, '[test-0.js](/test-0.js)'], + ['`.selector-2`', '0', '[test.css](/test.css)'], + ]; + t.deepEqual(table, expected); +}); diff --git a/build-system/tasks/get-zindex/index.js b/build-system/tasks/get-zindex/index.js index 10635943082bb..549abe5fe6a02 100644 --- a/build-system/tasks/get-zindex/index.js +++ b/build-system/tasks/get-zindex/index.js @@ -16,14 +16,25 @@ 'use strict'; const fs = require('fs'); -const gulp = require('gulp'); -const PluginError = require('plugin-error'); -const postcss = require('postcss'); -const table = require('text-table'); -const through = require('through2'); +const globby = require('globby'); +const path = require('path'); +const Postcss = require('postcss'); +const prettier = require('prettier'); +const textTable = require('text-table'); +const { + getJscodeshiftReport, + jscodeshiftAsync, +} = require('../../test-configs/jscodeshift'); +const {getStdout} = require('../../common/process'); +const {gray, magenta} = require('../../common/colors'); +const {logLocalDev, logOnSameLineLocalDev} = require('../../common/logging'); +const {writeDiffOrFail} = require('../../common/diff'); + +/** @type {Postcss.default} */ +const postcss = /** @type {*} */ (Postcss); const tableHeaders = [ - ['selector', 'z-index', 'file'], + ['context', 'z-index', 'file'], ['---', '---', '---'], ]; @@ -32,113 +43,219 @@ const tableOptions = { hsep: ' | ', }; +const preamble = ` +**Run \`amp get-zindex --fix\` to generate this file.** + + +`.trim(); + +const logChecking = (filename) => + logOnSameLineLocalDev(gray(path.basename(filename))); + +const sortedByEntryKey = (a, b) => a[0].localeCompare(b[0]); + /** - * @param {!Object} acc accumulator object for selectors - * @param {!Rules} css post css rules object + * @param {!Object} acc accumulator object for selectors + * @param {!Postcss.Rule} css post css rules object */ function zIndexCollector(acc, css) { css.walkRules((rule) => { rule.walkDecls((decl) => { // Split out multi selector rules - let selectorNames = rule.selector.replace('\n', ''); - selectorNames = selectorNames.split(','); if (decl.prop == 'z-index') { - selectorNames.forEach((selector) => { - // If multiple redeclaration of a selector and z index - // are done in a single file, this will get overridden. - acc[selector] = decl.value; - }); + rule.selector + .replace('\n', '') + .split(',') + .forEach((selector) => { + // If multiple redeclaration of a selector and z index + // are done in a single file, this will get overridden. + acc[selector] = decl.value; + }); } }); }); } /** - * @param {!Vinyl} file vinyl fs object - * @param {string} enc encoding value - * @param {function(err: ?Object, data: !Vinyl|string)} cb chunk data through - */ -function onFileThrough(file, enc, cb) { - if (file.isNull()) { - cb(null, file); - return; - } - - if (file.isStream()) { - cb(new PluginError('size', 'Stream not supported')); - return; - } - - const selectors = Object.create(null); - - postcss([zIndexCollector.bind(null, selectors)]) - .process(file.contents.toString(), { - from: file.relative, - }) - .then(() => { - cb(null, {name: file.relative, selectors}); - }); -} - -/** - * @param {!Object} filesData + * @param {!Object>>} filesData * accumulation of files and the rules and z index values. * @return {!Array>} */ function createTable(filesData) { const rows = []; - Object.keys(filesData) - .sort() - .forEach((fileName) => { - const selectors = filesData[fileName]; - Object.keys(selectors) - .sort() - .forEach((selectorName) => { - const zIndex = selectors[selectorName]; - const row = [selectorName, zIndex, fileName]; - rows.push(row); - }); - }); + for (const filename of Object.keys(filesData).sort()) { + // JS entries are Arrays of Arrays since they can have duplicate contexts + // like [['context', 9999]] + // CSS entries are Obejcts since they should not have duplicate selectors + // like {'.selector': 9999} + const entry = Array.isArray(filesData[filename]) + ? filesData[filename] + : Object.entries(filesData[filename]).sort(sortedByEntryKey); + for (const [context, zIndex] of entry) { + rows.push([`\`${context}\``, zIndex, `[${filename}](/${filename})`]); + } + } rows.sort((a, b) => { const aZIndex = parseInt(a[1], 10); const bZIndex = parseInt(b[1], 10); - return aZIndex - bZIndex; + // Word values sorted lexicographically. + if (isNaN(aZIndex) && isNaN(bZIndex)) { + return a[1].localeCompare(b[1]); + } + // Word values before length values. + if (isNaN(aZIndex)) { + return -1; + } + if (isNaN(bZIndex)) { + return 1; + } + // By length descending. + return bZIndex - aZIndex; }); return rows; } /** - * @param {string} glob - * @return {!Stream} + * Extract z-index selectors from all files matching the given glob starting at + * the given working directory + * @param {string|Array} glob + * @param {string=} cwd + * @return {Promise} */ -function getZindexStream(glob) { - return gulp.src(glob).pipe(through.obj(onFileThrough)); +async function getZindexSelectors(glob, cwd = '.') { + const filesData = Object.create(null); + const files = globby.sync(glob, {cwd}); + for (const file of files) { + const contents = await fs.promises.readFile(path.join(cwd, file), 'utf-8'); + const selectors = Object.create(null); + const plugins = [zIndexCollector.bind(null, selectors)]; + logChecking(file); + await postcss(plugins).process(contents, {from: file}); + if (Object.keys(selectors).length) { + filesData[file] = selectors; + } + } + return filesData; } /** - * @param {function()} cb + * @param {string|Array} glob + * @param {string=} cwd + * @return {!Promise} */ -function getZindex(cb) { - const filesData = Object.create(null); - // Don't return the stream here since we do a `writeFileSync` - getZindexStream('{css,src,extensions}/**/*.css') - .on('data', (chunk) => { - filesData[chunk.name] = chunk.selectors; - }) - .on('end', () => { - const rows = createTable(filesData); - rows.unshift.apply(rows, tableHeaders); - const tbl = table(rows, tableOptions); - fs.writeFileSync('css/Z_INDEX.md', tbl); - cb(); +function getZindexChainsInJs(glob, cwd = '.') { + return new Promise((resolve) => { + const files = globby.sync(glob, {cwd}).map((file) => path.join(cwd, file)); + + const filesIncludingString = getStdout( + ['grep -irl "z-*index"', ...files].join(' ') + ) + .trim() + .split('\n'); + + const result = {}; + + const {stderr, stdout} = jscodeshiftAsync([ + '--dry', + '--no-babel', + `--transform=${__dirname}/jscodeshift/collect-zindex.js`, + ...filesIncludingString, + ]); + + stderr.on('data', (data) => { + throw new Error(data.toString()); + }); + + stdout.on('data', (data) => { + const reportLine = getJscodeshiftReport(data.toString()); + + if (!reportLine) { + return; + } + + const [filename, report] = reportLine; + const relative = path.relative(cwd, filename); + + logChecking(filename); + + try { + const reportParsed = JSON.parse(report); + + if (reportParsed.length) { + result[relative] = reportParsed.sort(sortedByEntryKey); + } + } catch (_) {} }); + + stdout.on('close', () => { + resolve(result); + }); + }); +} + +/** + * Entry point for amp get-zindex + */ +async function getZindex() { + logLocalDev('...'); + + const filesData = Object.assign( + {}, + ...(await Promise.all([ + getZindexSelectors('{css,src,extensions}/**/*.css'), + getZindexChainsInJs([ + '{3p,src,extensions}/**/*.js', + '!extensions/**/test/**/*.js', + '!extensions/**/storybook/**/*.js', + ]), + ])) + ); + + logOnSameLineLocalDev( + 'Generating z-index table from', + magenta(`${Object.keys(filesData).length} files`) + ); + + const filename = 'css/Z_INDEX.md'; + const rows = [...tableHeaders, ...createTable(filesData)]; + const table = textTable(rows, tableOptions); + const output = await prettierFormat(filename, `${preamble}\n\n${table}`); + + await writeDiffOrFail( + 'get-zindex', + filename, + output, + /* gitDiffFlags */ [ + '-U1', + // Rows are formatted to align, so rows with unchanged content may change + // in whitespace, forcing the diff to contain the entire table. + '--ignore-space-change', + ] + ); +} + +/** + * @param {string} filename + * @param {string} output + * @return {Promise} + */ +async function prettierFormat(filename, output) { + return prettier.format(output, { + ...(await prettier.resolveConfig(filename)), + parser: 'markdown', + }); } module.exports = { createTable, getZindex, - getZindexStream, + getZindexSelectors, + getZindexChainsInJs, }; getZindex.description = 'Runs through all css files of project to gather z-index values'; + +getZindex.flags = { + 'fix': 'Write to file', +}; diff --git a/build-system/tasks/get-zindex/jscodeshift/collect-zindex.js b/build-system/tasks/get-zindex/jscodeshift/collect-zindex.js new file mode 100644 index 0000000000000..d076d474883c0 --- /dev/null +++ b/build-system/tasks/get-zindex/jscodeshift/collect-zindex.js @@ -0,0 +1,131 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const zIndexRegExp = /^z-?index$/i; + +/** + * @param {Node} node + * @return {string} + */ +function getCallExpressionZIndexValue(node) { + for (let i = 1; i < node.arguments.length; i++) { + const argument = node.arguments[i]; + const previous = node.arguments[i - 1]; + if ( + argument.type.endsWith('Literal') && + argument.value !== '' && + zIndexRegExp.test(previous.value) + ) { + return argument.value; + } + } +} + +/** + * @param {string} file + * @param {Node} node + * @return {string} + */ +function source(file, node) { + const {end, start} = node; + return file.source.substr(start, end - start); +} + +/** + * @param {string} file + * @param {string} path + * @return {string} + */ +function chainId(file, path) { + let propertyChain = ''; + let at = path; + + while (at && at.value && at.value.type.endsWith('Property')) { + const part = source(file, at.value.key); + if (at.value.key.type === 'Identifier') { + propertyChain = `.${part}` + propertyChain; + } else { + propertyChain = `[${part}]` + propertyChain; + } + at = at.parent && at.parent.parent; + } + + while ( + at && + at.parent && + at.value.type !== 'CallExpression' && + at.value.type !== 'VariableDeclarator' && + at.value.type !== 'AssignmentExpression' && + at.value.type !== 'JSXAttribute' && + at.value.type !== 'Program' + ) { + at = at.parent; + } + + if (at.value.type === 'JSXAttribute') { + const openingElement = source(file, at.parent.value.name); + return `<${openingElement} />`; + } + + if (at.value.type === 'CallExpression') { + return source(file, at.value.callee); + } + + if (at.value.type === 'AssignmentExpression') { + return source(file, at.value.left) + propertyChain; + } + + if (at.value.type === 'VariableDeclarator') { + return source(file, at.value.id) + propertyChain; + } + + return '(unknown)'; +} + +module.exports = function (file, api) { + const j = api.jscodeshift; + + const report = []; + + j(file.source) + .find( + j.ObjectProperty, + (node) => + node.key && + node.value.type.endsWith('Literal') && + node.value.value !== '' && + zIndexRegExp.test(node.key.value || node.key.name) + ) + .forEach((path) => { + report.push([chainId(file, path.parent.parent), path.value.value.value]); + }); + + j(file.source) + .find( + j.CallExpression, + (node) => getCallExpressionZIndexValue(node) != null + ) + .forEach((path) => { + report.push([ + chainId(file, path), + getCallExpressionZIndexValue(path.value), + ]); + }); + + api.report(JSON.stringify(report)); + + return file.source; +}; diff --git a/build-system/tasks/get-zindex/test-0.js b/build-system/tasks/get-zindex/test-0.js new file mode 100644 index 0000000000000..917cc10b22210 --- /dev/null +++ b/build-system/tasks/get-zindex/test-0.js @@ -0,0 +1,34 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable */ + +const declarator = { + zIndex: 0, +}; + +assignment = { + 'z-index': 'auto', +}; + +const emptyZindexIsIgnored = { + zIndex: '', +}; + +setStyle(foo, 'z-index', 15); +setStyles(bar, {foo: 'bar', 'z-index': 9999}); + +
; diff --git a/build-system/tasks/get-zindex/test-1.js b/build-system/tasks/get-zindex/test-1.js new file mode 100644 index 0000000000000..1c98aea8e10e9 --- /dev/null +++ b/build-system/tasks/get-zindex/test-1.js @@ -0,0 +1,20 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable */ + +; +; diff --git a/build-system/tasks/get-zindex/test-2.css b/build-system/tasks/get-zindex/test-2.css index 8db19a008a8d3..646d0ed6fa162 100644 --- a/build-system/tasks/get-zindex/test-2.css +++ b/build-system/tasks/get-zindex/test-2.css @@ -28,3 +28,11 @@ .selector-4 { z-index: 80; } + +.selector-5 { + z-index: initial; +} + +.selector-6 { + z-index: auto; +} diff --git a/build-system/tasks/get-zindex/test.js b/build-system/tasks/get-zindex/test.js deleted file mode 100644 index 3b63946c1325e..0000000000000 --- a/build-system/tasks/get-zindex/test.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2016 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. - */ -'use strict'; - -const m = require('./'); -const test = require('ava'); - -const result = { - 'test.css': { - '.selector-1': '1', - '.selector-2': '0', - '.selector-3': '99', - }, - 'test-2.css': { - '.selector-4': '80', - }, -}; - -test.cb('collects selectors', (t) => { - const data = Object.create(null); - const testFiles = `${__dirname}/*.css`; - m.getZindexStream(testFiles) - .on('data', (chunk) => { - data[chunk.name] = chunk.selectors; - }) - .on('end', () => { - t.deepEqual(data, result); - t.end(); - }); -}); - -test('sync - create array of arrays with z index order', (t) => { - t.plan(1); - const table = m.createTable(result); - const expected = [ - ['.selector-2', '0', 'test.css'], - ['.selector-1', '1', 'test.css'], - ['.selector-4', '80', 'test-2.css'], - ['.selector-3', '99', 'test.css'], - ]; - t.deepEqual(table, expected); -}); diff --git a/build-system/tasks/helpers.js b/build-system/tasks/helpers.js index 909eeca9863b0..835dc54e9e2b3 100644 --- a/build-system/tasks/helpers.js +++ b/build-system/tasks/helpers.js @@ -15,36 +15,35 @@ */ const argv = require('minimist')(process.argv.slice(2)); -const babelify = require('babelify'); -const browserify = require('browserify'); -const buffer = require('vinyl-buffer'); const debounce = require('debounce'); -const del = require('del'); -const file = require('gulp-file'); +const esbuild = require('esbuild'); +/** @type {Object} */ +const experimentDefines = require('../global-configs/experiments-const.json'); const fs = require('fs-extra'); -const gulp = require('gulp'); -const gulpIf = require('gulp-if'); -const gulpWatch = require('gulp-watch'); -const istanbul = require('gulp-istanbul'); -const log = require('fancy-log'); +const magicstring = require('magic-string'); +const open = require('open'); const path = require('path'); -const regexpSourcemaps = require('gulp-regexp-sourcemaps'); -const rename = require('gulp-rename'); -const source = require('vinyl-source-stream'); -const sourcemaps = require('gulp-sourcemaps'); -const watchify = require('watchify'); +const Remapping = require('@ampproject/remapping'); +const terser = require('terser'); const wrappers = require('../compile/compile-wrappers'); const { VERSION: internalRuntimeVersion, } = require('../compile/internal-version'); const {applyConfig, removeConfig} = require('./prepend-global/index.js'); const {closureCompile} = require('../compile/compile'); -const {EventEmitter} = require('events'); -const {green, red, cyan} = require('ansi-colors'); -const {isTravisBuild} = require('../common/travis'); +const {cyan, green, red} = require('../common/colors'); +const {getEsbuildBabelPlugin} = require('../common/esbuild-babel'); +const {isCiBuild} = require('../common/ci'); const {jsBundles} = require('../compile/bundles.config'); +const {log, logLocalDev} = require('../common/logging'); const {thirdPartyFrames} = require('../test-configs/config'); -const {transpileTs} = require('../compile/typescript'); +const {watch} = require('chokidar'); + +/** @type {Remapping.default} */ +const remapping = /** @type {*} */ (Remapping); + +/** @type {magicstring.default} */ +const MagicString = /** @type {*} */ (magicstring); /** * Tasks that should print the `--nobuild` help text. @@ -67,6 +66,10 @@ const EXTENSION_BUNDLE_MAP = { 'third_party/vega/vega.js', ], 'amp-inputmask.js': ['third_party/inputmask/bundle.js'], + 'amp-date-picker.js': ['third_party/react-dates/bundle.js'], + 'amp-shadow-dom-polyfill.js': [ + 'node_modules/@webcomponents/webcomponentsjs/bundles/webcomponents-sd.install.js', + ], }; /** @@ -76,8 +79,6 @@ const UNMINIFIED_TARGETS = ['alp.max', 'amp-inabox', 'amp-shadow', 'amp']; /** * List of minified targets to which AMP_CONFIG should be written - * Note: keep this list in sync with release script. Contact @ampproject/wg-infra - * for details. */ const MINIFIED_TARGETS = ['alp', 'amp4ads-v0', 'shadow-v0', 'v0']; @@ -91,6 +92,12 @@ const hostname3p = argv.hostname3p || '3p.ampproject.net'; */ const watchDebounceDelay = 1000; +/** + * Stores esbuild's watch mode rebuilders. + * @private @const {!Map}>} + */ +const watchedTargets = new Map(); + /** * @param {!Object} jsBundles * @param {string} name @@ -107,7 +114,9 @@ function doBuildJs(jsBundles, name, extraOptions) { {...target.options, ...extraOptions} ); } else { - return Promise.reject(red('Error:'), 'Could not find', cyan(name)); + return Promise.reject( + [red('Error:'), 'Could not find', cyan(name)].join(' ') + ); } } @@ -118,25 +127,25 @@ function doBuildJs(jsBundles, name, extraOptions) { */ async function bootstrapThirdPartyFrames(options) { const startTime = Date.now(); - const promises = []; - const {watch, minify} = options; - thirdPartyFrames.forEach((frameObject) => { - promises.push( - thirdPartyBootstrap(frameObject.max, frameObject.min, options) - ); - }); - if (watch) { + if (options.watch) { thirdPartyFrames.forEach((frameObject) => { - const watchFunc = () => { - thirdPartyBootstrap(frameObject.max, frameObject.min, options); + const watchFunc = async () => { + await thirdPartyBootstrap(frameObject.max, frameObject.min, options); }; - gulpWatch(frameObject.max, debounce(watchFunc, watchDebounceDelay)); + watch(frameObject.max).on( + 'change', + debounce(watchFunc, watchDebounceDelay) + ); }); } - await Promise.all(promises); + await Promise.all( + thirdPartyFrames.map(async (frameObject) => { + await thirdPartyBootstrap(frameObject.max, frameObject.min, options); + }) + ); endBuildStep( 'Bootstrapped 3p frames into', - `dist.3p/${minify ? internalRuntimeVersion : 'current'}/`, + `dist.3p/${options.minify ? internalRuntimeVersion : 'current'}/`, startTime ); } @@ -145,6 +154,7 @@ async function bootstrapThirdPartyFrames(options) { * Compile and optionally minify the core runtime. * * @param {!Object} options + * @return {Promise} */ async function compileCoreRuntime(options) { await doBuildJs(jsBundles, 'amp.js', options); @@ -162,20 +172,20 @@ async function compileAllJs(options) { if (minify) { log('Minifying multi-pass JS with', cyan('closure-compiler') + '...'); } else { - log('Compiling JS with', cyan('browserify') + '...'); + log('Compiling JS with', cyan('esbuild'), 'and', cyan('babel') + '...'); } const startTime = Date.now(); await Promise.all([ minify ? Promise.resolve() : doBuildJs(jsBundles, 'polyfills.js', options), doBuildJs(jsBundles, 'alp.max.js', options), doBuildJs(jsBundles, 'examiner.max.js', options), - doBuildJs(jsBundles, 'ww.max.js', options), doBuildJs(jsBundles, 'integration.js', options), doBuildJs(jsBundles, 'ampcontext-lib.js', options), doBuildJs(jsBundles, 'iframe-transport-client-lib.js', options), doBuildJs(jsBundles, 'recaptcha.js', options), doBuildJs(jsBundles, 'amp-viewer-host.max.js', options), doBuildJs(jsBundles, 'video-iframe-integration.js', options), + doBuildJs(jsBundles, 'amp-story-entry-point.js', options), doBuildJs(jsBundles, 'amp-story-player.js', options), doBuildJs(jsBundles, 'amp-inabox-host.js', options), doBuildJs(jsBundles, 'amp-shadow.js', options), @@ -190,44 +200,105 @@ async function compileAllJs(options) { } /** - * Allows (ap|pre)pending to the already compiled, minified JS file - * + * Allows pending inside the compile wrapper to the already compiled, minified JS file. * @param {string} srcFilename Name of the JS source file * @param {string} destFilePath File path to the compiled JS file + * @param {?Object} options */ -function appendToCompiledFile(srcFilename, destFilePath) { +function combineWithCompiledFile(srcFilename, destFilePath, options) { const bundleFiles = EXTENSION_BUNDLE_MAP[srcFilename]; - if (bundleFiles) { - const newSource = concatFilesToString(bundleFiles.concat([destFilePath])); - fs.writeFileSync(destFilePath, newSource, 'utf8'); - } else if (srcFilename == 'amp-date-picker.js') { - // For amp-date-picker, we inject the react-dates bundle after compile - // to avoid CC from messing with browserify's module boilerplate. - const file = fs.readFileSync(destFilePath, 'utf8'); - const firstLineBreak = file.indexOf('\n'); - const wrapperOpen = file.substr(0, firstLineBreak + 1); - const reactDates = fs.readFileSync( - 'third_party/react-dates/bundle.js', - 'utf8' - ); - // Inject the bundle inside the standard AMP wrapper (after the first line). - const newSource = [ - wrapperOpen, - reactDates, - file.substr(firstLineBreak + 1), - ].join('\n'); - fs.writeFileSync(destFilePath, newSource, 'utf8'); + if (!bundleFiles) { + return; } + const bundle = new MagicString.Bundle({ + separator: '\n', + }); + // We need to inject the code _inside_ the extension wrapper + const destFileName = path.basename(destFilePath); + /** + * TODO (rileyajones) This should be import('magic-string').MagicStringOptions but + * is invalid until https://github.com/Rich-Harris/magic-string/pull/183 + * is merged. + * @type {Object} + */ + const mapMagicStringOptions = {filename: destFileName}; + const contents = new MagicString( + fs.readFileSync(destFilePath, 'utf8'), + mapMagicStringOptions + ); + const map = JSON.parse(fs.readFileSync(`${destFilePath}.map`, 'utf8')); + const {sourceRoot} = map; + map.sourceRoot = undefined; + + // The wrapper may have been minified further. Search backwards from the + // expected <%=contents%> location to find the start of the `{` in the + // wrapping function. + const wrapperIndex = options.wrapper.indexOf('<%= contents %>'); + const index = contents.original.lastIndexOf('{', wrapperIndex) + 1; + + const wrapperOpen = contents.snip(0, index); + const remainingContents = contents.snip(index, contents.length()); + + bundle.addSource(wrapperOpen); + for (const bundleFile of bundleFiles) { + const contents = fs.readFileSync(bundleFile, 'utf8'); + /** + * TODO (rileyajones) This should be import('magic-string').MagicStringOptions but + * is invalid until https://github.com/Rich-Harris/magic-string/pull/183 + * is merged. + * @type {Object} + */ + const bundleMagicStringOptions = {filename: bundleFile}; + bundle.addSource(new MagicString(contents, bundleMagicStringOptions)); + bundle.append(MODULE_SEPARATOR); + } + bundle.addSource(remainingContents); + + const bundledMap = bundle.generateDecodedMap({ + file: destFileName, + hires: true, + }); + + const remapped = remapping( + bundledMap, + (file) => { + if (file === destFileName) { + return map; + } + return null; + }, + !argv.full_sourcemaps + ); + remapped.sourceRoot = sourceRoot; + + fs.writeFileSync(destFilePath, bundle.toString(), 'utf8'); + fs.writeFileSync(`${destFilePath}.map`, remapped.toString(), 'utf8'); } +/** + * @param {string} name + * @return {string} + */ function toEsmName(name) { return name.replace(/\.js$/, '.mjs'); } +/** + * @param {string} name + * @return {string} + */ function maybeToEsmName(name) { return argv.esm ? toEsmName(name) : name; } +/** + * @param {string} name + * @return {string} + */ +function maybeToNpmEsmName(name) { + return argv.esm ? name.replace(/\.js$/, '.module.js') : name; +} + /** * Minifies a given JavaScript file entry point. * @param {string} srcDir @@ -241,64 +312,47 @@ async function compileMinifiedJs(srcDir, srcFilename, destDir, options) { const entryPoint = path.join(srcDir, srcFilename); const minifiedName = maybeToEsmName(options.minifiedName); - if (options.watch) { - const watchFunc = async () => { - const compileComplete = await doCompileMinifiedJs( - /* continueOnError */ true - ); - if (options.onWatchBuild) { - options.onWatchBuild(compileComplete); - } - }; - gulpWatch(entryPoint, debounce(watchFunc, watchDebounceDelay)); + options.errored = false; + await closureCompile(entryPoint, destDir, minifiedName, options, timeInfo); + // If an incremental watch build fails, simply return. + if (options.watch && options.errored) { + return; } - async function doCompileMinifiedJs(continueOnError) { - options.continueOnError = continueOnError; - options.errored = false; - await closureCompile(entryPoint, destDir, minifiedName, options, timeInfo); - - // If an incremental watch build fails, simply return. - if (options.errored) { - return; - } - - const destPath = path.join(destDir, minifiedName); - appendToCompiledFile(srcFilename, destPath); - fs.writeFileSync(path.join(destDir, 'version.txt'), internalRuntimeVersion); - if (options.latestName) { - fs.copySync( - destPath, - path.join(destDir, maybeToEsmName(options.latestName)) - ); - } + const destPath = path.join(destDir, minifiedName); + combineWithCompiledFile(srcFilename, destPath, options); + fs.writeFileSync(path.join(destDir, 'version.txt'), internalRuntimeVersion); + if (options.latestName) { + fs.copySync( + destPath, + path.join(destDir, maybeToEsmName(options.latestName)) + ); + } - let name = minifiedName; - if (options.latestName) { - name += ` → ${maybeToEsmName(options.latestName)}`; - } - endBuildStep('Minified', name, timeInfo.startTime); - - const target = path.basename(minifiedName, path.extname(minifiedName)); - if (!argv.noconfig && MINIFIED_TARGETS.includes(target)) { - await applyAmpConfig( - maybeToEsmName(`${destDir}/${minifiedName}`), - /* localDev */ options.fortesting, - /* fortesting */ options.fortesting - ); - } + let name = minifiedName; + if (options.latestName) { + name += ` → ${maybeToEsmName(options.latestName)}`; + } + endBuildStep('Minified', name, timeInfo.startTime); + + const target = path.basename(minifiedName, path.extname(minifiedName)); + if (!argv.noconfig && MINIFIED_TARGETS.includes(target)) { + await applyAmpConfig( + maybeToEsmName(`${destDir}/${minifiedName}`), + /* localDev */ options.fortesting, + /* fortesting */ options.fortesting + ); } - await doCompileMinifiedJs(options.continueOnError); } /** - * Handles a browserify bundling error + * Handles a bundling error * @param {Error} err * @param {boolean} continueOnError * @param {string} destFilename */ function handleBundleError(err, continueOnError, destFilename) { - let message = err; + let message = err.toString(); if (err.stack) { // Drop the node_modules call stack, which begins with ' at'. message = err.stack.replace(/ at[^]*/, '').trim(); @@ -308,144 +362,289 @@ function handleBundleError(err, continueOnError, destFilename) { if (continueOnError) { log(red('ERROR:'), reasonMessage); } else { - const reason = new Error(reasonMessage); - reason.showStack = false; - new EventEmitter().emit('error', reason); + throw new Error(reasonMessage); } } /** - * Performs the final steps after Browserify bundles a JS file + * Performs the final steps after a JS file is bundled and optionally minified + * with esbuild and babel. * @param {string} srcFilename * @param {string} destDir * @param {string} destFilename * @param {?Object} options + * @param {number} startTime */ -function finishBundle(srcFilename, destDir, destFilename, options) { - appendToCompiledFile(srcFilename, path.join(destDir, destFilename)); +async function finishBundle( + srcFilename, + destDir, + destFilename, + options, + startTime +) { + combineWithCompiledFile( + srcFilename, + path.join(destDir, destFilename), + options + ); - if (options.latestName) { - // "amp-foo-latest.js" -> "amp-foo-latest.max.js" - const latestMaxName = options.latestName.split('.js')[0] + '.max.js'; - // Copy amp-foo-0.1.js to amp-foo-latest.max.js. + const logPrefix = options.minify ? 'Minified' : 'Compiled'; + let {latestName} = options; + if (latestName) { + if (!options.minify) { + latestName = latestName.replace(/\.js$/, '.max.js'); + } fs.copySync( path.join(destDir, options.toName), - path.join(destDir, latestMaxName) + path.join(destDir, latestName) + ); + endBuildStep(logPrefix, `${destFilename} → ${latestName}`, startTime); + } else { + const loggingName = + options.npm && !destFilename.startsWith('amp-') + ? `${options.name} → ${destFilename}` + : destFilename; + endBuildStep(logPrefix, loggingName, startTime); + } + + const targets = options.minify ? MINIFIED_TARGETS : UNMINIFIED_TARGETS; + const target = path.basename(destFilename, path.extname(destFilename)); + if (targets.includes(target)) { + await applyAmpConfig( + path.join(destDir, destFilename), + /* localDev */ true, + /* fortesting */ options.fortesting ); } } /** - * Transforms a given JavaScript file entry point with browserify, and watches - * it for changes (if required). + * Transforms a given JavaScript file entry point with esbuild and babel, and + * watches it for changes (if required). * @param {string} srcDir * @param {string} srcFilename * @param {string} destDir * @param {?Object} options * @return {!Promise} */ -function compileUnminifiedJs(srcDir, srcFilename, destDir, options) { +async function compileUnminifiedJs(srcDir, srcFilename, destDir, options) { + const startTime = Date.now(); const entryPoint = path.join(srcDir, srcFilename); const destFilename = options.toName || srcFilename; - const wrapper = options.wrapper || wrappers.none; - const devWrapper = wrapper.replace('<%= contents %>', '$1'); - - // TODO: @jonathantyng remove browserifyOptions #22757 - const browserifyOptions = { - entries: entryPoint, - debug: true, - fast: true, - ...options.browserifyOptions, - }; + const destFile = path.join(destDir, destFilename); - let bundler = browserify(browserifyOptions).transform(babelify, { - caller: {name: 'unminified'}, - global: true, - }); - - if (options.watch) { - const watchFunc = () => { - const bundleComplete = performBundle(/* continueOnError */ true); - if (options.onWatchBuild) { - options.onWatchBuild(bundleComplete); - } - }; - bundler = watchify(bundler); - bundler.on('update', debounce(watchFunc, watchDebounceDelay)); + if (watchedTargets.has(entryPoint)) { + return watchedTargets.get(entryPoint).rebuild(); } /** - * @param {boolean} continueOnError - * @return {Promise} + * Splits up the wrapper to compute the banner and footer + * @return {Object} */ - function performBundle(continueOnError) { - let startTime; - return toPromise( - bundler - .bundle() - .once('readable', () => (startTime = Date.now())) - .on('error', (err) => - handleBundleError(err, continueOnError, destFilename) - ) - .pipe(source(srcFilename)) - .pipe(buffer()) - .pipe(gulpIf(argv.coverage, istanbul())) - .pipe(sourcemaps.init({loadMaps: true})) - .pipe( - regexpSourcemaps( - /\$internalRuntimeVersion\$/g, - internalRuntimeVersion, - 'runtime-version' + function splitWrapper() { + const wrapper = options.wrapper || wrappers.none; + const sentinel = '<%= contents %>'; + const start = wrapper.indexOf(sentinel); + return { + banner: {js: wrapper.slice(0, start)}, + footer: {js: wrapper.slice(start + sentinel.length)}, + }; + } + + const {banner, footer} = splitWrapper(); + const babelPlugin = getEsbuildBabelPlugin( + 'unminified', + /* enableCache */ true + ); + + const buildResult = await esbuild + .build({ + entryPoints: [entryPoint], + bundle: true, + sourcemap: true, + define: experimentDefines, + outfile: destFile, + plugins: [babelPlugin], + banner, + footer, + incremental: !!options.watch, + logLevel: 'silent', + }) + .then((result) => { + finishBundle(srcFilename, destDir, destFilename, options, startTime); + return result; + }) + .catch((err) => handleBundleError(err, !!options.watch, destFilename)); + + if (options.watch) { + watchedTargets.set(entryPoint, { + rebuild: async () => { + const time = Date.now(); + const {rebuild} = /** @type {Required} */ ( + buildResult + ); + + const buildPromise = rebuild() + .then(() => + finishBundle(srcFilename, destDir, destFilename, options, time) ) - ) - .pipe(regexpSourcemaps(/([^]+)/, devWrapper, 'wrapper')) - .pipe(rename(destFilename)) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest(destDir)) - .on('end', () => - finishBundle(srcFilename, destDir, destFilename, options) - ) - ) - .then(() => { - let name = destFilename; - if (options.latestName) { - const latestMaxName = options.latestName.split('.js')[0] + '.max.js'; - name = `${name} → ${latestMaxName}`; - } - endBuildStep('Compiled', name, startTime); - }) - .then(() => { - const target = path.basename(destFilename, path.extname(destFilename)); - if (UNMINIFIED_TARGETS.includes(target)) { - return applyAmpConfig( - `${destDir}/${destFilename}`, - /* localDev */ true, - /* fortesting */ options.fortesting + .catch((err) => + handleBundleError(err, /* continueOnError */ true, destFilename) ); - } + options?.onWatchBuild(buildPromise); + await buildPromise; + }, + }); + } +} + +/** + * Transforms a given JavaScript file entry point with esbuild and babel, and + * watches it for changes (if required). + * Used by 3p iframe vendors. + * @param {string} srcDir + * @param {string} srcFilename + * @param {string} destDir + * @param {?Object} options + * @return {!Promise} + */ +async function compileJsWithEsbuild(srcDir, srcFilename, destDir, options) { + const startTime = Date.now(); + const entryPoint = path.join(srcDir, srcFilename); + const fileName = options.minify ? options.minifiedName : options.toName; + const destFilename = options.npm ? fileName : maybeToEsmName(fileName); + const destFile = path.join(destDir, destFilename); + + if (watchedTargets.has(entryPoint)) { + return watchedTargets.get(entryPoint).rebuild(); + } + + const babelPlugin = getEsbuildBabelPlugin( + options.minify ? 'minified' : 'unminified', + /* enableCache */ true + ); + const plugins = [babelPlugin]; + + if (options.remapDependencies) { + plugins.unshift(remapDependenciesPlugin()); + } + + let result = null; + + /** + * @param {number} time + */ + async function build(time) { + if (!result) { + result = await esbuild.build({ + entryPoints: [entryPoint], + bundle: true, + sourcemap: true, + outfile: destFile, + plugins, + minify: options.minify, + format: options.outputFormat || undefined, + target: argv.esm ? 'es6' : 'es5', + incremental: !!options.watch, + logLevel: 'silent', + external: options.externalDependencies, }); + } else { + result = await result.rebuild(); + } + await minifyWithTerser(destDir, destFilename, options); + await finishBundle(srcFilename, destDir, destFilename, options, time); } - return performBundle(options.continueOnError); + /** + * Generates a plugin to remap the dependencies of a JS bundle. + * @return {Object} + */ + function remapDependenciesPlugin() { + const remapDependencies = {__proto__: null, ...options.remapDependencies}; + const external = options.externalDependencies; + return { + name: 'remap-dependencies', + setup(build) { + build.onResolve({filter: /.*/}, (args) => { + const dep = args.path; + const remap = remapDependencies[dep]; + if (remap) { + const isExternal = external.includes(remap); + return { + path: isExternal ? remap : require.resolve(remap), + external: isExternal, + }; + } + }); + }, + }; + } + + await build(startTime).catch((err) => + handleBundleError(err, !!options.watch, destFilename) + ); + + if (options.watch) { + watchedTargets.set(entryPoint, { + rebuild: async () => { + const time = Date.now(); + const buildPromise = build(time).catch((err) => + handleBundleError(err, !!options.watch, destFilename) + ); + if (options.onWatchBuild) { + options.onWatchBuild(buildPromise); + } + await buildPromise; + }, + }); + } } /** - * Transpiles from TypeScript into intermediary files before compilation and - * deletes them afterwards. + * Minify the code with Terser. Only used by the ESBuild. * - * @param {string} srcDir Path to the src directory - * @param {string} srcFilename Name of the JS source file - * @param {string} destDir Destination folder for output script + * @param {string} destDir + * @param {string} destFilename * @param {?Object} options * @return {!Promise} */ -async function compileTs(srcDir, srcFilename, destDir, options) { - options = options || {}; - await transpileTs(srcDir, srcFilename); - await compileJs(srcDir, srcFilename, destDir, options); - del.sync(path.join(srcDir, '**/*.js')); +async function minifyWithTerser(destDir, destFilename, options) { + if (!options.minify) { + return; + } + + const filename = path.join(destDir, destFilename); + const terserOptions = { + mangle: true, + compress: true, + output: { + beautify: !!argv.pretty_print, + comments: /\/*/, + // eslint-disable-next-line google-camelcase/google-camelcase + keep_quoted_props: true, + }, + sourceMap: true, + }; + const minified = await terser.minify( + fs.readFileSync(filename, 'utf8'), + terserOptions + ); + const remapped = remapping( + [minified.map, fs.readFileSync(`${filename}.map`, 'utf8')], + () => null, + !argv.full_sourcemaps + ); + fs.writeFileSync(filename, minified.code); + fs.writeFileSync(`${filename}.map`, remapped.toString()); } +/** + * The set of entrypoints currently watched by compileJs. + * @type {Set} + */ +const watchedEntryPoints = new Set(); + /** * Bundles (max) or compiles (min) a given JavaScript file entry point. * @@ -457,16 +656,40 @@ async function compileTs(srcDir, srcFilename, destDir, options) { */ async function compileJs(srcDir, srcFilename, destDir, options) { options = options || {}; - if (options.minify) { - return await compileMinifiedJs(srcDir, srcFilename, destDir, options); - } else { - return await compileUnminifiedJs(srcDir, srcFilename, destDir, options); + const entryPoint = path.join(srcDir, srcFilename); + if (watchedEntryPoints.has(entryPoint)) { + return; } + + if (options.watch) { + watchedEntryPoints.add(entryPoint); + const deps = await getDependencies(entryPoint, options); + const watchFunc = async () => { + await doCompileJs({...options, continueOnError: true}); + }; + watch(deps).on('change', debounce(watchFunc, watchDebounceDelay)); + } + + /** + * Actually performs the steps to compile the entry point. + * @param {Object} options + * @return {Promise} + */ + async function doCompileJs(options) { + const buildResult = options.minify + ? compileMinifiedJs(srcDir, srcFilename, destDir, options) + : compileUnminifiedJs(srcDir, srcFilename, destDir, options); + if (options.onWatchBuild) { + options.onWatchBuild(buildResult); + } + await buildResult; + } + + await doCompileJs(options); } /** - * Stops the timer for the given build step and prints the execution time, - * unless we are on Travis. + * Stops the timer for the given build step and prints the execution time. * @param {string} stepName Name of the action, like 'Compiled' or 'Minified' * @param {string} targetName Name of the target, like a filename or path * @param {DOMHighResTimeStamp} startTime Start time of build step @@ -500,15 +723,13 @@ function printConfigHelp(command) { cyan(argv.config === 'canary' ? 'canary' : 'prod'), green('AMP config.') ); - if (!isTravisBuild()) { - log( - green('⤷ Use'), - cyan('--config={canary|prod}'), - green('with your'), - cyan(command), - green('command to specify which config to apply.') - ); - } + logLocalDev( + green('⤷ Use'), + cyan('--config={canary|prod}'), + green('with your'), + cyan(command), + green('command to specify which config to apply.') + ); } /** @@ -516,7 +737,6 @@ function printConfigHelp(command) { */ function printNobuildHelp() { for (const task of NOBUILD_HELP_TASKS) { - // eslint-disable-line local/no-for-of-statement if (argv._.includes(task)) { log( green('To skip building during future'), @@ -524,7 +744,7 @@ function printNobuildHelp() { green('runs, use'), cyan('--nobuild'), green('with your'), - cyan(`gulp ${task}`), + cyan(`amp ${task}`), green('command.') ); return; @@ -532,9 +752,23 @@ function printNobuildHelp() { } } +/** + * @param {string=} covPath + * @return {!Promise} + */ +async function maybePrintCoverageMessage(covPath = '') { + if (!argv.coverage || isCiBuild()) { + return; + } + + const url = 'file://' + path.resolve(covPath); + log(green('INFO:'), 'Generated code coverage report at', cyan(url)); + await open(url, {wait: false}); +} + /** * Writes AMP_CONFIG to a runtime file. Optionally enables localDev mode and - * fortesting mode. Called by "gulp build" and "gulp dist" while building + * fortesting mode. Called by "amp build" and "amp dist" while building * various runtime files. * * @param {string} targetFile File to which the config is to be written. @@ -554,26 +788,12 @@ async function applyAmpConfig(targetFile, localDev, fortesting) { baseConfigFile, /* opt_localDev */ localDev, /* opt_localBranch */ true, - /* opt_branch */ false, + /* opt_branch */ undefined, /* opt_fortesting */ fortesting ); }); } -/** - * Synchronously concatenates the given files into a string. - * - * @param {Array} files A list of file paths. - * @return {string} The concatenated contents of the given files. - */ -function concatFilesToString(files) { - return files - .map(function (filePath) { - return fs.readFileSync(filePath, 'utf8'); - }) - .join(MODULE_SEPARATOR); -} - /** * Copies frame.html to output folder, replaces js references to minified * copies, and generates symlink to it. @@ -583,10 +803,14 @@ function concatFilesToString(files) { * @param {!Object} options * @return {!Promise} */ -function thirdPartyBootstrap(input, outputName, options) { - const {minify, fortesting} = options; +async function thirdPartyBootstrap(input, outputName, options) { + const {fortesting, minify} = options; + const destDir = `dist.3p/${minify ? internalRuntimeVersion : 'current'}`; + await fs.ensureDir(destDir); + if (!minify) { - return toPromise(gulp.src(input).pipe(gulp.dest('dist.3p/current'))); + await fs.copy(input, `${destDir}/${path.basename(input)}`); + return; } // By default we use an absolute URL, that is independent of the @@ -600,21 +824,12 @@ function thirdPartyBootstrap(input, outputName, options) { const html = fs .readFileSync(input, 'utf8') .replace(/\.\/integration\.js/g, integrationJs); - return toPromise( - file(outputName, html, {src: true}) - .pipe(gulp.dest('dist.3p/' + internalRuntimeVersion)) - .on('end', function () { - const aliasToLatestBuild = 'dist.3p/current-min'; - if (fs.existsSync(aliasToLatestBuild)) { - fs.unlinkSync(aliasToLatestBuild); - } - fs.symlinkSync( - './' + internalRuntimeVersion, - aliasToLatestBuild, - 'dir' - ); - }) - ); + await fs.writeFile(`${destDir}/${outputName}`, html); + const aliasToLatestBuild = 'dist.3p/current-min'; + if (fs.existsSync(aliasToLatestBuild)) { + fs.unlinkSync(aliasToLatestBuild); + } + fs.symlinkSync('./' + internalRuntimeVersion, aliasToLatestBuild, 'dir'); } /** @@ -633,30 +848,43 @@ function mkdirSync(path) { } /** - * Returns a promise for readable + * Returns the list of dependencies for a given JS entrypoint by having esbuild + * generate a metafile for it. Uses the set of babel plugins that would've been + * used to compile the entrypoint. * - * @param {*} readable - * @return {Promise} + * @param {string} entryPoint + * @param {!Object} options + * @return {Promise>} */ -function toPromise(readable) { - return new Promise(function (resolve, reject) { - readable.on('error', reject).on('end', resolve); +async function getDependencies(entryPoint, options) { + const caller = options.minify ? 'minified' : 'unminified'; + const babelPlugin = getEsbuildBabelPlugin(caller, /* enableCache */ true); + const result = await esbuild.build({ + entryPoints: [entryPoint], + bundle: true, + write: false, + metafile: true, + plugins: [babelPlugin], }); + return Object.keys(result.metafile?.inputs ?? {}); } module.exports = { + MINIFIED_TARGETS, applyAmpConfig, bootstrapThirdPartyFrames, compileAllJs, compileCoreRuntime, compileJs, - compileTs, + compileJsWithEsbuild, doBuildJs, endBuildStep, + compileUnminifiedJs, + maybePrintCoverageMessage, maybeToEsmName, + maybeToNpmEsmName, mkdirSync, printConfigHelp, printNobuildHelp, - toPromise, watchDebounceDelay, }; diff --git a/build-system/tasks/integration.js b/build-system/tasks/integration.js index 1a9caa93b3244..2414de6a7c588 100644 --- a/build-system/tasks/integration.js +++ b/build-system/tasks/integration.js @@ -17,34 +17,33 @@ const argv = require('minimist')(process.argv.slice(2)); const { - maybePrintArgvMessages, - shouldNotRun, -} = require('./runtime-test/helpers'); -const { - RuntimeTestRunner, RuntimeTestConfig, + RuntimeTestRunner, } = require('./runtime-test/runtime-test-base'); const {buildRuntime} = require('../common/utils'); +const {maybePrintArgvMessages} = require('./runtime-test/helpers'); class Runner extends RuntimeTestRunner { + /** + * @param {RuntimeTestConfig} config + */ constructor(config) { super(config); } /** @override */ async maybeBuild() { - if (argv.nobuild) { - return; + if (!argv.nobuild) { + await buildRuntime(); } - await buildRuntime(); } } +/** + * Entry point for the `amp integration` task. + * @return {Promise} + */ async function integration() { - if (shouldNotRun()) { - return; - } - maybePrintArgvMessages(); const config = new RuntimeTestConfig('integration'); @@ -61,27 +60,30 @@ module.exports = { integration.description = 'Runs integration tests'; integration.flags = { - 'chrome_canary': ' Runs tests on Chrome Canary', - 'chrome_flags': ' Uses the given flags to launch Chrome', - 'compiled': ' Runs tests against minified JS', + 'chrome_canary': 'Runs tests on Chrome Canary', + 'chrome_flags': 'Uses the given flags to launch Chrome', + 'compiled': 'Runs tests against minified JS', 'config': - ' Sets the runtime\'s AMP_CONFIG to one of "prod" (default) or "canary"', - 'coverage': ' Run tests in code coverage mode', + 'Sets the runtime\'s AMP_CONFIG to one of "prod" (default) or "canary"', + 'coverage': 'Run tests in code coverage mode', 'debug': - ' Allow debug statements by auto opening devtools. NOTE: This only ' + + 'Allow debug statements by auto opening devtools. NOTE: This only ' + 'works in non headless mode.', - 'firefox': ' Runs tests on Firefox', - 'files': ' Runs tests for specific files', - 'grep': ' Runs tests that match the pattern', - 'headless': ' Run tests in a headless Chrome window', - 'ie': ' Runs tests on IE', - 'nobuild': ' Skips build step', - 'nohelp': ' Silence help messages that are printed prior to test run', - 'safari': ' Runs tests on Safari', - 'saucelabs': ' Runs tests on Sauce Labs (requires setup)', - 'stable': ' Runs Sauce Labs tests on stable browsers', - 'beta': ' Runs Sauce Labs tests on beta browsers', - 'testnames': ' Lists the name of each test being run', - 'verbose': ' With logging enabled', - 'watch': ' Watches for changes in files, runs corresponding test(s)', + 'edge': 'Runs tests on Edge', + 'esm': 'Runs against module(esm) build', + 'define_experiment_constant': + 'Transforms tests with the EXPERIMENT constant set to true', + 'experiment': 'Experiment being tested (used for status reporting)', + 'firefox': 'Runs tests on Firefox', + 'files': 'Runs tests for specific files', + 'grep': 'Runs tests that match the pattern', + 'headless': 'Run tests in a headless Chrome window', + 'ie': 'Runs tests on IE', + 'nobuild': 'Skips build step', + 'nohelp': 'Silence help messages that are printed prior to test run', + 'report': 'Write test result report to a local file', + 'safari': 'Runs tests on Safari', + 'testnames': 'Lists the name of each test being run', + 'verbose': 'With logging enabled', + 'watch': 'Watches for changes in files, runs corresponding test(s)', }; diff --git a/build-system/tasks/jsify-css.js b/build-system/tasks/jsify-css.js deleted file mode 100644 index a6b0ffe45496d..0000000000000 --- a/build-system/tasks/jsify-css.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright 2016 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. - */ -'use strict'; - -const autoprefixer = require('autoprefixer'); -const colors = require('ansi-colors'); -const cssnano = require('cssnano'); -const fs = require('fs-extra'); -const log = require('fancy-log'); -const postcss = require('postcss'); -const postcssImport = require('postcss-import'); - -// NOTE: see https://github.com/ai/browserslist#queries for `browsers` list -const cssprefixer = autoprefixer({ - overrideBrowserslist: [ - 'last 5 ChromeAndroid versions', - 'last 5 iOS versions', - 'last 3 FirefoxAndroid versions', - 'last 5 Android versions', - 'last 2 ExplorerMobile versions', - 'last 2 OperaMobile versions', - 'last 2 OperaMini versions', - ], -}); - -const cssNanoDefaultOptions = { - autoprefixer: false, - convertValues: false, - discardUnused: false, - cssDeclarationSorter: false, - // `mergeIdents` this is only unsafe if you rely on those animation names in - // JavaScript. - mergeIdents: true, - reduceIdents: false, - reduceInitial: false, - zindex: false, - svgo: { - encode: true, - }, -}; - -/** - * Css transformations to target file using postcss. - - * @param {string} filename css file - * @param {!Object=} opt_cssnano cssnano options - * @return {!Promise} that resolves with the css content after - * processing - */ -function transformCss(filename, opt_cssnano) { - opt_cssnano = opt_cssnano || Object.create(null); - // See http://cssnano.co/optimisations/ for full list. - // We try and turn off any optimization that is marked unsafe. - const cssnanoOptions = Object.assign( - Object.create(null), - cssNanoDefaultOptions, - opt_cssnano - ); - const cssnanoTransformer = cssnano({preset: ['default', cssnanoOptions]}); - - const css = fs.readFileSync(filename, 'utf8'); - const transformers = [postcssImport, cssprefixer, cssnanoTransformer]; - return postcss(transformers).process(css.toString(), { - 'from': filename, - }); -} - -/** - * 'Jsify' a CSS file - Adds vendor specific css prefixes to the css file, - * compresses the file, removes the copyright comment, and adds the sourceURL - * to the stylesheet - * - * @param {string} filename css file - * @return {!Promise} that resolves with the css content after - * processing - */ -function jsifyCssAsync(filename) { - return transformCss(filename).then(function (result) { - result.warnings().forEach(function (warn) { - log(colors.red(warn.toString())); - }); - const {css} = result; - return css + '\n/*# sourceURL=/' + filename + '*/'; - }); -} - -module.exports = { - jsifyCssAsync, - transformCss, -}; diff --git a/build-system/tasks/karma.conf.js b/build-system/tasks/karma.conf.js deleted file mode 100644 index 7ebab0fb5ec78..0000000000000 --- a/build-system/tasks/karma.conf.js +++ /dev/null @@ -1,355 +0,0 @@ -/** - * Copyright 2015 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. - */ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const browserifyPersistFs = require('browserify-persist-fs'); -const crypto = require('crypto'); -const fs = require('fs'); -const globby = require('globby'); - -const {gitCommitterEmail} = require('../common/git'); -const {isTravisBuild, travisJobNumber} = require('../common/travis'); - -const TEST_SERVER_PORT = 8081; - -const COMMON_CHROME_FLAGS = [ - // Dramatically speeds up iframe creation time. - '--disable-extensions', - // Allows simulating user actions (e.g unmute) which otherwise will be denied. - '--autoplay-policy=no-user-gesture-required', -]; - -if (argv.debug) { - COMMON_CHROME_FLAGS.push('--auto-open-devtools-for-tabs'); -} - -// Reduces the odds of Sauce labs timing out during tests. See #16135 and #24286. -// Reference: https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts -const SAUCE_TIMEOUT_CONFIG = { - maxDuration: 30 * 60, - commandTimeout: 10 * 60, - idleTimeout: 30 * 60, -}; - -// Used by persistent browserify caching to further salt hashes with our -// environment state. Eg, when updating a babel-plugin, the environment hash -// must change somehow so that the cache busts and the file is retransformed. -const createHash = (input) => - crypto.createHash('sha1').update(input).digest('hex'); - -const persistentCache = browserifyPersistFs( - '.karma-cache', - { - deps: createHash(fs.readFileSync('./yarn.lock')), - build: globby - .sync([ - 'build-system/**/*.js', - '!build-system/eslint-rules', - '!**/test/**', - ]) - .map((f) => { - return createHash(fs.readFileSync(f)); - }), - }, - () => { - process.stdout.write('.'); - } -); - -persistentCache.gc( - { - maxAge: 1000 * 60 * 60 * 24 * 7, - }, - () => { - // swallow errors - } -); - -/** - * @param {!Object} config - */ -module.exports = { - frameworks: [ - 'fixture', - 'browserify', - 'mocha', - 'sinon-chai', - 'chai', - 'source-map-support', - ], - - preprocessors: { - './test/fixtures/*.html': ['html2js'], - './test/**/*.js': ['browserify'], - './ads/**/test/test-*.js': ['browserify'], - './extensions/**/test/**/*.js': ['browserify'], - './testing/**/*.js': ['browserify'], - }, - - // TODO(rsimha, #15510): Sauce labs on Safari doesn't reliably support - // 'localhost' addresses. See #14848 for more info. - // Details: https://support.saucelabs.com/hc/en-us/articles/115010079868 - hostname: 'localhost', - - browserify: { - watch: true, - debug: true, - fast: true, - basedir: __dirname + '/../../', - transform: [['babelify', {caller: {name: 'test'}, global: true}]], - // Prevent "cannot find module" errors on Travis. See #14166. - bundleDelay: isTravisBuild() ? 5000 : 1200, - - persistentCache, - }, - - reporters: ['super-dots', 'karmaSimpleReporter'], - - superDotsReporter: { - nbDotsPerLine: 100000, - color: { - success: 'green', - failure: 'red', - ignore: 'yellow', - }, - icon: { - success: '●', - failure: '●', - ignore: '○', - }, - }, - - specReporter: { - suppressPassed: true, - suppressSkipped: true, - suppressFailed: false, - suppressErrorSummary: true, - maxLogLines: 20, - }, - - mochaReporter: { - output: 'full', - colors: { - success: 'green', - error: 'red', - info: 'yellow', - }, - symbols: { - success: '●', - error: '●', - info: '○', - }, - }, - - port: 9876, - - colors: true, - - proxies: { - '/ads/': '/base/ads/', - '/dist/': '/base/dist/', - '/dist.3p/': '/base/dist.3p/', - '/examples/': '/base/examples/', - '/extensions/': '/base/extensions/', - '/src/': '/base/src/', - '/test/': '/base/test/', - }, - - // Can't import the Karma constant config.LOG_ERROR, so we hard code it here. - // Hopefully it'll never change. - logLevel: 'ERROR', - - autoWatch: true, - - browsers: [isTravisBuild() ? 'Chrome_travis_ci' : 'Chrome_no_extensions'], - - customLaunchers: { - /* eslint "google-camelcase/google-camelcase": 0*/ - Chrome_travis_ci: { - base: 'Chrome', - flags: ['--no-sandbox'].concat(COMMON_CHROME_FLAGS), - }, - Chrome_no_extensions: { - base: 'Chrome', - flags: COMMON_CHROME_FLAGS, - }, - Chrome_no_extensions_headless: { - base: 'ChromeHeadless', - flags: [ - // https://developers.google.com/web/updates/2017/04/headless-chrome#frontend - '--no-sandbox', - '--remote-debugging-port=9222', - // https://github.com/karma-runner/karma-chrome-launcher/issues/175 - "--proxy-server='direct://'", - '--proxy-bypass-list=*', - ].concat(COMMON_CHROME_FLAGS), - }, - // SauceLabs configurations. - // New configurations can be created here: - // https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/ - SL_Chrome: { - base: 'SauceLabs', - browserName: 'chrome', - platform: 'Windows 10', - version: 'latest', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Chrome_Beta: { - base: 'SauceLabs', - browserName: 'chrome', - platform: 'Windows 10', - version: 'beta', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Chrome_Android_7: { - base: 'SauceLabs', - appiumVersion: '1.8.1', - deviceName: 'Android GoogleAPI Emulator', - browserName: 'Chrome', - platformName: 'Android', - platformVersion: '7.1', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_iOS_12: { - base: 'SauceLabs', - appiumVersion: '1.9.1', - deviceName: 'iPhone X Simulator', - browserName: 'Safari', - platformName: 'iOS', - platformVersion: '12.0', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_iOS_11: { - base: 'SauceLabs', - appiumVersion: '1.9.1', - deviceName: 'iPhone X Simulator', - browserName: 'Safari', - platformName: 'iOS', - platformVersion: '11.3', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Firefox: { - base: 'SauceLabs', - browserName: 'firefox', - platform: 'Windows 10', - version: 'latest', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Firefox_Beta: { - base: 'SauceLabs', - browserName: 'firefox', - platform: 'Windows 10', - version: 'beta', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Safari_12: { - base: 'SauceLabs', - browserName: 'safari', - platform: 'macOS 10.13', - version: '12.1', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Safari_11: { - base: 'SauceLabs', - browserName: 'safari', - platform: 'macOS 10.13', - version: '11.1', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_Edge: { - base: 'SauceLabs', - browserName: 'MicrosoftEdge', - platform: 'Windows 10', - ...SAUCE_TIMEOUT_CONFIG, - }, - SL_IE: { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 10', - ...SAUCE_TIMEOUT_CONFIG, - }, - }, - - sauceLabs: { - testName: 'AMP HTML on Sauce', - // Identifier used in build-system/sauce_connect/start_sauce_connect.sh. - tunnelIdentifier: isTravisBuild() ? travisJobNumber() : gitCommitterEmail(), - startConnect: false, - connectOptions: { - noSslBumpDomains: 'all', - }, - }, - - client: { - mocha: { - reporter: 'html', - // Longer timeout on Travis; fail quickly during local runs. - timeout: isTravisBuild() ? 10000 : 2000, - // Run tests up to 3 times before failing them on Travis. - retries: isTravisBuild() ? 2 : 0, - }, - captureConsole: false, - verboseLogging: false, - testServerPort: TEST_SERVER_PORT, - }, - - singleRun: true, - captureTimeout: 4 * 60 * 1000, - failOnEmptyTestSuite: false, - - // Give a disconnected browser 2 minutes to reconnect with Karma. - // This allows a browser to retry 2 times per `browserDisconnectTolerance` - // on Travis before stalling out after 10 minutes. - browserDisconnectTimeout: 2 * 60 * 1000, - - // If there's no message from the browser, make Karma wait 2 minutes - // until it disconnects. - browserNoActivityTimeout: 2 * 60 * 1000, - - // IF YOU CHANGE THIS, DEBUGGING WILL RANDOMLY KILL THE BROWSER - browserDisconnectTolerance: isTravisBuild() ? 2 : 0, - - // Import our gulp webserver as a Karma server middleware - // So we instantly have all the custom server endpoints available - beforeMiddleware: ['custom'], - plugins: [ - 'karma-browserify', - 'karma-chai', - 'karma-chrome-launcher', - 'karma-edge-launcher', - 'karma-firefox-launcher', - 'karma-fixture', - 'karma-html2js-preprocessor', - 'karma-ie-launcher', - 'karma-mocha', - 'karma-mocha-reporter', - 'karma-safari-launcher', - 'karma-sauce-launcher', - 'karma-simple-reporter', - 'karma-sinon-chai', - 'karma-source-map-support', - 'karma-super-dots-reporter', - { - 'middleware:custom': [ - 'factory', - function () { - return require(require.resolve('../server/app.js')); - }, - ], - }, - ], -}; diff --git a/build-system/tasks/lint.js b/build-system/tasks/lint.js index d07a468cb6994..ce67fc6aa89d8 100644 --- a/build-system/tasks/lint.js +++ b/build-system/tasks/lint.js @@ -16,204 +16,142 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); -const config = require('../test-configs/config'); -const debounce = require('gulp-debounce'); -const eslint = require('gulp-eslint'); -const eslintIfFixed = require('gulp-eslint-if-fixed'); -const globby = require('globby'); -const gulp = require('gulp'); -const lazypipe = require('lazypipe'); -const log = require('fancy-log'); -const path = require('path'); -const watch = require('gulp-watch'); +const fs = require('fs'); const { - getFilesChanged, - getFilesFromArgv, + log, + logLocalDev, logOnSameLine, -} = require('../common/utils'); -const {gitDiffNameOnlyMaster} = require('../common/git'); -const {isTravisBuild} = require('../common/travis'); -const {maybeUpdatePackages} = require('./update-packages'); -const {watchDebounceDelay} = require('./helpers'); + logOnSameLineLocalDev, +} = require('../common/logging'); +const {cyan, green, red, yellow} = require('../common/colors'); +const {ESLint} = require('eslint'); +const {getFilesToCheck} = require('../common/utils'); +const {lintGlobs} = require('../test-configs/config'); -const rootDir = path.dirname(path.dirname(__dirname)); - -/** - * Initializes the linter stream based on globs - * - * @param {!Object} globs - * @param {!Object} streamOptions - * @return {!ReadableStream} - */ -function initializeStream(globs, streamOptions) { - let stream = gulp.src(globs, streamOptions); - if (argv.watch) { - const watcher = lazypipe().pipe(watch, globs); - stream = stream.pipe(watcher()).pipe(debounce({wait: watchDebounceDelay})); - } - return stream; -} +/** @type {ESLint.Options} */ +const options = { + fix: argv.fix, + reportUnusedDisableDirectives: 'error', +}; /** - * Runs the linter on the given stream using the given options. - * - * @param {!ReadableStream} stream - * @return {boolean} + * Runs the linter on the given set of files. + * @param {Array} filesToLint */ -function runLinter(stream) { - if (!isTravisBuild()) { - log(colors.green('Starting linter...')); - } - const options = { - fix: argv.fix, - quiet: argv.quiet, +async function runLinter(filesToLint) { + logLocalDev(green('Starting linter...')); + const eslint = new ESLint(options); + const results = { + errorCount: 0, + warningCount: 0, }; const fixedFiles = {}; - return stream - .pipe(eslint(options)) - .pipe( - eslint.formatEach('stylish', function (msg) { - logOnSameLine(msg.replace(`${rootDir}/`, '').trim() + '\n'); - }) - ) - .pipe(eslintIfFixed(rootDir)) - .pipe( - eslint.result(function (result) { - const relativePath = path.relative(rootDir, result.filePath); - if (!isTravisBuild()) { - logOnSameLine(colors.green('Linted: ') + relativePath); - } - if (options.fix && result.fixed) { - const status = - result.errorCount == 0 - ? colors.green('Fixed: ') - : colors.yellow('Partially fixed: '); - logOnSameLine(status + colors.cyan(relativePath)); - fixedFiles[relativePath] = status; - } - }) - ) - .pipe( - eslint.results(function (results) { - if (results.errorCount == 0 && results.warningCount == 0) { - if (!isTravisBuild()) { - logOnSameLine( - colors.green('SUCCESS: ') + 'No linter warnings or errors.' - ); - } - } else { - const prefix = - results.errorCount == 0 - ? colors.yellow('WARNING: ') - : colors.red('ERROR: '); - logOnSameLine( - prefix + - 'Found ' + - results.errorCount + - ' error(s) and ' + - results.warningCount + - ' warning(s).' - ); - if (!options.fix) { - log( - colors.yellow('NOTE 1:'), - 'You may be able to automatically fix some of these warnings ' + - '/ errors by running', - colors.cyan('gulp lint --local_changes --fix'), - 'from your local branch.' - ); - log( - colors.yellow('NOTE 2:'), - 'Since this is a destructive operation (that edits your files', - 'in-place), make sure you commit before running the command.' - ); - log( - colors.yellow('NOTE 3:'), - 'If you see any', - colors.cyan('prettier/prettier'), - 'errors, read', - colors.cyan( - 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-e2e.md#code-quality-and-style' - ) - ); - } - } - if (options.fix && Object.keys(fixedFiles).length > 0) { - log(colors.green('INFO: ') + 'Summary of fixes:'); - Object.keys(fixedFiles).forEach((file) => { - log(fixedFiles[file] + colors.cyan(file)); - }); - } - }) - ) - .pipe(eslint.failAfterError()); + for (const file of filesToLint) { + const text = fs.readFileSync(file, 'utf-8'); + const lintResult = await eslint.lintText(text, {filePath: file}); + const result = lintResult[0]; + if (!result) { + continue; // File was ignored + } + results.errorCount += result.errorCount; + results.warningCount += result.warningCount; + const formatter = await eslint.loadFormatter('stylish'); + const resultText = formatter + .format(lintResult) + .replace(`${process.cwd()}/`, '') + .trim(); + if (resultText.length) { + logOnSameLine(resultText); + } + if (argv.fix) { + await ESLint.outputFixes(lintResult); + } + logOnSameLineLocalDev(green('Linted: ') + file); + if (options.fix && result.output) { + const status = + result.errorCount == 0 ? green('Fixed: ') : yellow('Partially fixed: '); + logOnSameLine(status + cyan(file)); + fixedFiles[file] = status; + } + } + summarizeResults(results, fixedFiles); } /** - * Checks if there are eslint rule changes, in which case we must lint all - * files. - * - * @return {boolean} + * Summarize the results of linting all files. + * @param {Object} results + * @param {Object} fixedFiles */ -function eslintRulesChanged() { - return ( - gitDiffNameOnlyMaster().filter(function (file) { - return ( - path.basename(file).includes('.eslintrc.js') || - path.dirname(file) === 'build-system/eslint-rules' +function summarizeResults(results, fixedFiles) { + const {errorCount, warningCount} = results; + if (errorCount == 0 && warningCount == 0) { + logOnSameLineLocalDev(green('SUCCESS:'), 'No linter warnings or errors.'); + } else { + const prefix = errorCount == 0 ? yellow('WARNING: ') : red('ERROR: '); + logOnSameLine( + prefix + + 'Found ' + + errorCount + + ' error(s) and ' + + warningCount + + ' warning(s).' + ); + if (!options.fix) { + log( + yellow('NOTE 1:'), + 'You may be able to automatically fix some of these warnings ' + + '/ errors by running', + cyan('amp lint --local_changes --fix'), + 'from your local branch.' ); - }).length > 0 - ); -} - -/** - * Gets the list of files to be linted. - * - * @param {!Array} files - * @return {!Array} - */ -function getFilesToLint(files) { - const filesToLint = globby.sync(files, {gitignore: true}); - if (!isTravisBuild()) { - log(colors.green('INFO: ') + 'Running lint on the following files:'); - filesToLint.forEach((file) => { - log(colors.cyan(file)); + log( + yellow('NOTE 2:'), + 'Since this is a destructive operation (that edits your files', + 'in-place), make sure you commit before running the command.' + ); + log( + yellow('NOTE 3:'), + 'If you see any', + cyan('prettier/prettier'), + 'errors, read', + cyan( + 'https://github.com/ampproject/amphtml/blob/main/docs/getting-started-e2e.md#code-quality-and-style' + ) + ); + } + process.exitCode = 1; + } + if (options.fix && Object.keys(fixedFiles).length > 0) { + log(green('INFO:'), 'Summary of fixes:'); + Object.keys(fixedFiles).forEach((file) => { + log(fixedFiles[file] + cyan(file)); }); } - return filesToLint; } /** - * Run the eslinter on the src javascript and log the output - * - * @return {!ReadableStream} + * Checks files for formatting (and optionally fixes them) with Eslint. + * Explicitly makes sure the API doesn't check files in `.eslintignore`. */ -function lint() { - maybeUpdatePackages(); - let filesToLint = config.lintGlobs; - if (argv.files) { - filesToLint = getFilesToLint(getFilesFromArgv()); - } else if (!eslintRulesChanged() && argv.local_changes) { - const lintableFiles = getFilesChanged(config.lintGlobs); - if (lintableFiles.length == 0) { - log(colors.green('INFO: ') + 'No JS files in this PR'); - return Promise.resolve(); - } - filesToLint = getFilesToLint(lintableFiles); +async function lint() { + const filesToCheck = getFilesToCheck( + lintGlobs, + {gitignore: true}, + '.eslintignore' + ); + if (filesToCheck.length == 0) { + return; } - return runLinter(initializeStream(filesToLint, {base: rootDir})); + await runLinter(filesToCheck); } module.exports = { lint, }; -lint.description = 'Validates against Google Closure Linter'; +lint.description = 'Runs eslint checks against JS files'; lint.flags = { - 'watch': ' Watches for changes in files, validates against the linter', - 'fix': ' Fixes simple lint errors (spacing etc)', - 'files': ' Lints just the specified files', - 'local_changes': ' Lints just the files changed in the local branch', - 'quiet': ' Suppress warnings from outputting', + 'fix': 'Fixes simple lint errors (spacing etc)', + 'files': 'Lints just the specified files', + 'local_changes': 'Lints just the files changed in the local branch', }; diff --git a/build-system/tasks/make-extension/OWNERS b/build-system/tasks/make-extension/OWNERS new file mode 100644 index 0000000000000..5b93a992998a3 --- /dev/null +++ b/build-system/tasks/make-extension/OWNERS @@ -0,0 +1,18 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [ + // Outreach group maintains extension boilerplate/template. + {name: 'ampproject/wg-outreach'}, + + // Most new extensions are reviewed by these groups. It's in their + // interest to maintain the extension generator. + {name: 'ampproject/wg-components'}, + {name: 'ampproject/wg-bento'}, + ], + }, + ], +} diff --git a/build-system/tasks/make-extension/format.js b/build-system/tasks/make-extension/format.js new file mode 100644 index 0000000000000..28f40679018a5 --- /dev/null +++ b/build-system/tasks/make-extension/format.js @@ -0,0 +1,29 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {getOutput} = require('../../common/process'); + +/** + * @param {Array} files + * @return {Object} + */ +function format(files) { + return getOutput(`npx prettier --ignore-unknown --write ${files.join(' ')}`); +} + +module.exports = { + format, +}; diff --git a/build-system/tasks/make-extension/index.js b/build-system/tasks/make-extension/index.js new file mode 100644 index 0000000000000..e50c1bfe8f631 --- /dev/null +++ b/build-system/tasks/make-extension/index.js @@ -0,0 +1,444 @@ +/** + * Copyright 2017 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. + */ +'use strict'; + +const argv = require('minimist')(process.argv.slice(2), {string: ['version']}); +const del = require('del'); +const fs = require('fs-extra'); +const objstr = require('obj-str'); +const path = require('path'); +const {cyan, green, red, yellow} = require('../../common/colors'); +const {format} = require('./format'); +const {getOutput, getStdout} = require('../../common/process'); +const {log, logLocalDev, logWithoutTimestamp} = require('../../common/logging'); + +const extensionBundlesJson = + 'build-system/compile/bundles.config.extensions.json'; + +/** + * Convert dash-case-name to PascalCaseName. + * @param {string} name + * @return {string} + */ +function dashToPascalCase(name) { + return name.replace(/(?:-|^)([a-z])/g, (_, c) => c.toUpperCase()); +} + +/** + * Replaces from a map of keys/values. + * @param {string} inputText + * @param {Object} replacements + * @return {function(string): string} + */ +const replace = (inputText, replacements) => + Object.keys(replacements).reduce( + (text, key) => text.replace(new RegExp(key, 'g'), replacements[key]), + inputText + ); + +/** + * Generate a sequence of all files in a directory recursively. + * @param {string} dir + * @yields {string} + */ +async function* walkDir(dir) { + for (const f of await fs.readdir(dir)) { + const dirPath = path.join(dir, f); + + if ((await fs.stat(dirPath)).isDirectory()) { + yield* walkDir(dirPath); + } else { + yield path.join(dir, f); + } + } +} + +const getTemplateDir = (template) => + path.join(path.relative(process.cwd(), __dirname), 'template', template); + +/** + * @param {string} templateDir + * @param {Object} replacements + * @param {string=} destinationDir + * @return {Array} + */ +async function writeFromTemplateDir( + templateDir, + replacements, + destinationDir = '.' +) { + const destinationPath = (templatePath) => + path.join( + destinationDir, + replace(templatePath.substr(templateDir.length + 1), replacements) + ); + + const written = []; + for await (const templatePath of walkDir(templateDir)) { + const destination = destinationPath(templatePath); + + await fs.mkdirp(path.dirname(destination)); + + // Skip if the destination file already exists + let fileHandle; + try { + fileHandle = await fs.open(destination, 'wx'); + } catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } + if (!argv.overwrite) { + logLocalDev( + yellow('WARNING:'), + 'Skipping existing file', + cyan(destination) + ); + continue; + } + logLocalDev( + yellow('WARNING:'), + 'Overwriting existing file', + cyan(destination) + ); + } + + const template = await fs.readFile(templatePath, 'utf8'); + await fs.write(fileHandle, replace(template, replacements)); + await fs.close(fileHandle); + + logLocalDev(green('SUCCESS:'), 'Created', cyan(destination)); + + written.push(destination); + } + return written; +} + +/** + * Inserts an extension entry into bundles.config.extensions.json + * + * @param {{ + * name: string, + * version: string, + * latestVersion?: (string|undefined) + * options: ({hasCss: boolean}|undefined) + * }} bundle + * @param {string=} destination + */ +async function insertExtensionBundlesConfig( + bundle, + destination = extensionBundlesJson +) { + let extensionBundles = []; + try { + extensionBundles = await fs.readJson(destination, {throws: false}); + } catch (_) {} + + const existingOrNull = extensionBundles.find( + ({name}) => name === bundle.name + ); + + extensionBundles.push({ + ...bundle, + latestVersion: + (existingOrNull && existingOrNull.latestVersion) || + bundle.latestVersion || + bundle.version, + }); + + await fs.mkdirp(path.dirname(destination)); + + await fs.writeJson( + destination, + extensionBundles.sort((a, b) => { + if (!a.name) { + return 1; + } + if (!b.name) { + return -1; + } + return a.name.localeCompare(b.name); + }) + ); + + format([destination]); + + logLocalDev(green('SUCCESS:'), 'Wrote', cyan(path.basename(destination))); +} + +/** + * @typedef {{ + * bundleConfig: Object, + * modified: !Array, + * created: !Array, + * }} + */ +let MakeExtensionResultDef; + +/** + * @param {Array} templateDirs + * @param {string=} destinationDir + * @param {{ + * version: (string|undefined), + * bento: (boolean|undefined), + * name: (string|undefined), + * }} options + * @return {Promise} + */ +async function makeExtensionFromTemplates( + templateDirs, + destinationDir = '.', + options = argv +) { + const version = ( + options.version || (options.bento ? '1.0' : '0.1') + ).toString(); + const name = (options.name || '').replace(/^amp-/, ''); + if (!name) { + log(red('ERROR:'), 'Must specify component name with', cyan('--name')); + return null; + } + + const namePascalCase = dashToPascalCase(name); + + const replacements = { + '__current_year__': `${new Date().getFullYear()}`, + '__component_version__': version, + '__component_version_snakecase__': version.replace(/\./g, '_'), + '__component_name_hyphenated__': name, + '__component_name_hyphenated_capitalized__': name.toUpperCase(), + '__component_name_pascalcase__': namePascalCase, + // TODO(alanorozco): Remove __storybook_experiments...__ once we stop + // requiring the bento experiment. + '__storybook_experiments_do_not_add_trailing_comma__': + // Don't add a trailing comma in the template, instead we add it here. + // This is because the property added is optional, and a double comma would + // cause a syntax error. + options.bento ? "experiments: ['bento']," : '', + ...(!options.nocss + ? { + '__css_import__': `import {CSS} from '../../../build/amp-${name}-${version}.css'`, + '__css_id__': `CSS`, + '__register_element_args__': `TAG, Amp${namePascalCase}, CSS`, + } + : { + '__css_import__': '', + '__css_id__': '', + '__register_element_args__': `TAG, Amp${namePascalCase}`, + }), + ...(!options.nojss + ? { + '__jss_import_component_css__': `import {CSS as COMPONENT_CSS} from './component.jss'`, + '__jss_component_css__': 'COMPONENT_CSS', + '__jss_import_use_styles__': `import {useStyles} from './component.jss'`, + '__jss_styles_use_styles__': 'const styles = useStyles()', + '__jss_styles_example_or_placeholder__': + '`${styles.exampleContentHidden}`', + } + : { + '__jss_import_component_css_': '', + '__jss_component_css__': 'null', + '__jss_import_use_styles__': '', + '__jss_styles_use_styles__': '', + '__jss_styles_example_or_placeholder__': `'my-classname'`, + }), + // eslint-disable-next-line local/no-forbidden-terms + // This allows generated code to contain "DO NOT SUBMIT", which will cause + // PRs to fail CI if example code isn't removed from the PR. We can't + // actually write that out, here or in templates, without CI failing. + // eslint-disable-next-line local/no-forbidden-terms + '__do_not_submit__': 'DO NOT SUBMIT', + // A rule on OWNERS assigns all filenames starting with "/validator-*" to + // belong to wg-caching. We don't require this group to review these + // template files, so using the __validator__ placeholder helps us exclude + // them from ownership. + '__validator__': 'validator', + }; + + const created = ( + await Promise.all( + templateDirs.map((templateDirs) => + writeFromTemplateDir(templateDirs, replacements, destinationDir) + ) + ) + ).flat(); + + if (created.length > 0) { + format(created); + } + + const bundleConfig = { + name: `amp-${name}`, + version, + }; + + if (!options.nocss) { + bundleConfig.options = {hasCss: true}; + } + + await insertExtensionBundlesConfig( + bundleConfig, + path.join(destinationDir, extensionBundlesJson) + ); + + const findCreatedByRegex = (regex) => { + const filenames = created.filter((filename) => regex.test(filename)); + return filenames.length < 1 ? null : filenames; + }; + + const blurb = [ + `${green('FINISHED:')} Created extension ${cyan(``)}`, + `Boilerplate for your new component has been created in: + ${cyan(`extensions/amp-${name}/`)}`, + ]; + + const unitTestFiles = findCreatedByRegex(new RegExp('test/test-')); + if (unitTestFiles) { + blurb.push(`You can run tests on your new component with the following command: + ${cyan(`amp unit --files="${unitTestFiles.join(',')}"`)}`); + } + + if (findCreatedByRegex(new RegExp('/storybook/'))) { + blurb.push(`You may view the component during development in storybook: + ${cyan(`amp storybook`)}`); + } + + if (findCreatedByRegex(new RegExp('/validator-(.+)\\.html$'))) { + blurb.push(`You should generate accompanying validator test result files by running: + ${cyan(`amp validator --update_tests`)}`); + } + + logLocalDev(`${blurb.join('\n\n')}\n`); + + return { + bundleConfig, + created, + modified: [extensionBundlesJson], + }; +} + +/** + * @param {function(...*):?{modified: ?Array, created: ?Array}} fn + * @return {Promise} + */ +async function affectsWorkingTree(fn) { + const stashStdout = getStdout(`git stash push --keep-index`); + + const {created, modified} = (await fn()) || {}; + + if (created) { + await del(created); + } + + if (modified) { + const head = getStdout('git rev-parse HEAD').trim(); + getOutput(`git checkout ${head} ${modified.join(' ')}`); + } + + if (!stashStdout.startsWith('No local changes')) { + getOutput('git stash pop'); + } +} + +/** + * Generates an extension with the given name and runs all unit tests located in + * the generated extension directory. + * @param {string} name + * @return {!Promise{?string}} stderr if failing, null if passing + */ +async function runExtensionTests(name) { + for (const command of [ + `amp build --extensions=${name} --core_runtime_only`, + `amp unit --headless --files="extensions/${name}/**/test/test-*.js"`, + ]) { + log('Running', cyan(command) + '...'); + const result = getOutput(command); + if (result.status !== 0) { + return result.stderr || result.stdout; + } + } + return null; +} + +/** + * @return {Promise} + */ +async function makeExtension() { + let testError; + + const {bento, nocss, nojss} = argv; + + const templateDirs = objstr({ + shared: true, + bento, + classic: !bento, + css: !nocss, + jss: bento && !nojss, + }) + .split(/\s+/) + .map((name) => getTemplateDir(name)); + + const withCleanup = argv.cleanup ? affectsWorkingTree : (fn) => fn(); + await withCleanup(async () => { + const result = await makeExtensionFromTemplates(templateDirs); + if (!result) { + const warningOrError = 'Could not write extension files.'; + if (argv.test) { + testError = warningOrError; + } else { + log(yellow('WARNING:'), warningOrError); + } + return null; + } + const {bundleConfig, created, modified} = result; + if (argv.test) { + testError = await runExtensionTests(bundleConfig.name); + } + return {created, modified}; + }); + + if (testError) { + logWithoutTimestamp(testError); + throw new Error( + [ + 'Failed testing generated extension', + yellow('⤷ Try updating the template files located in:'), + ...templateDirs.map((dir) => '\t' + dir), + '', + ].join('\n') + ); + } +} + +module.exports = { + insertExtensionBundlesConfig, + makeExtension, + makeExtensionFromTemplates, + writeFromTemplateDir, +}; + +makeExtension.description = 'Create an extension skeleton'; +makeExtension.flags = { + name: 'The name of the extension. The prefix `amp-*` is added if necessary', + cleanup: 'Undo file changes before exiting. This is useful alongside --test', + bento: 'Generate a Bento component', + nocss: + 'Exclude extension-specific CSS. (If specifying --bento, JSS is still generated unless combined with --nojss)', + nojss: 'Exclude extension-specific JSS when specifying --bento.', + test: 'Build and test the generated extension', + version: 'Sets the version number (default: 0.1; or 1.0 with --bento)', + overwrite: + 'Overwrites existing files at the destination, if present. Otherwise skips them', +}; diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/OWNERS b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/OWNERS new file mode 100644 index 0000000000000..ddba9cfadb4b5 --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/OWNERS @@ -0,0 +1,10 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [{name: 'ampproject/wg-bento'}], + }, + ], +} diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js new file mode 100644 index 0000000000000..95f6ae994e19d --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js @@ -0,0 +1,51 @@ +/** + * Copyright __current_year__ 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. + */ + +import {BaseElement} from './base-element'; +__css_import__; +import {dict} from '../../../src/core/types/object'; +import {isExperimentOn} from '../../../src/experiments'; +import {userAssert} from '../../../src/log'; + +/** @const {string} */ +const TAG = 'amp-__component_name_hyphenated__'; + +class Amp__component_name_pascalcase__ extends BaseElement { + /** @override */ + init() { + // __do_not_submit__: This is example code only. + this.registerApiAction('exampleToggle', (api) => api./*OK*/exampleToggle()); + + return dict({ + // Extra props passed by wrapper AMP component + 'exampleTagNameProp': this.element.tagName, + }); + } + + /** @override */ + isLayoutSupported(layout) { + userAssert( + isExperimentOn(this.win, 'bento') || + isExperimentOn(this.win, 'bento-__component_name_hyphenated__'), + 'expected global "bento" or specific "bento-__component_name_hyphenated__" experiment to be enabled' + ); + return super.isLayoutSupported(layout); + } +} + +AMP.extension(TAG, '__component_version__', (AMP) => { + AMP.registerElement(__register_element_args__); +}); diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/base-element.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/base-element.js new file mode 100644 index 0000000000000..a1f70ab9843fa --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/base-element.js @@ -0,0 +1,44 @@ +/** + * Copyright __current_year__ 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. + */ + +__jss_import_component_css__; +import {__component_name_pascalcase__} from './component'; +import {PreactBaseElement} from '../../../src/preact/base-element'; + +export class BaseElement extends PreactBaseElement {} + +/** @override */ +BaseElement['Component'] = __component_name_pascalcase__; + +/** @override */ +BaseElement['props'] = { + 'children': {passthrough: true}, + // 'children': {passthroughNonEmpty: true}, + // 'children': {selector: '...'}, +}; + +/** @override */ +BaseElement['layoutSizeDefined'] = true; + +/** @override */ +BaseElement['usesShadowDom'] = true; + +// __do_not_submit__: If BaseElement['shadowCss'] is set to `null`, remove the +// following declaration. +// Otherwise, keep it when defined to an actual value like `COMPONENT_CSS`. +// Once addressed, remove this set of comments. +/** @override */ +BaseElement['shadowCss'] = __jss_component_css__; diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/component.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/component.js new file mode 100644 index 0000000000000..2bba7ac5b0078 --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/component.js @@ -0,0 +1,53 @@ +/** + * Copyright __current_year__ 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. + */ + +import * as Preact from '../../../src/preact'; +import {ContainWrapper} from '../../../src/preact/component'; +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from '../../../src/preact'; +__jss_import_use_styles__; + +/** + * @param {!__component_name_pascalcase__Def.Props} props + * @return {PreactDef.Renderable} + */ +export function __component_name_pascalcase__({exampleTagNameProp, ...rest}) { + // Examples of state and hooks + // __do_not_submit__: This is example code only. + const [exampleValue, setExampleValue] = useState(0); + const exampleRef = useRef(null); + __jss_styles_use_styles__; + + useCallback(() => {/* Do things */}, []) + useEffect(() => {/* Do things */}, []) + useLayoutEffect(() => {/* Do things */}, []) + useMemo(() => {/* Do things */}, []) + + return ( + + {{exampleTagNameProp}} +
+ This is hidden +
+
+ ); +} diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/component.type.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/component.type.js new file mode 100644 index 0000000000000..976d96583d86a --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/component.type.js @@ -0,0 +1,33 @@ +/** + * Copyright __current_year__ 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. + */ + +/** @externs */ + +/** @const */ +var __component_name_pascalcase__Def = {}; + +/** + * @typedef {{ + * exampleProperty: (string|undefined), (__do_not_submit__) + * }} + */ +__component_name_pascalcase__Def.Props; + +/** @interface */ +__component_name_pascalcase__Def.__component_name_pascalcase__Api = class { + /** Example: API method to toggle the component */ + exampleToggle() {} // __do_not_submit__ +}; diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/storybook/Basic.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/storybook/Basic.js new file mode 100644 index 0000000000000..58163723bb93b --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/storybook/Basic.js @@ -0,0 +1,37 @@ +/** + * Copyright __current_year__ 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. + */ + +import * as Preact from '../../../../src/preact'; +import {__component_name_pascalcase__} from '../component' +import {withKnobs} from '@storybook/addon-knobs'; + +export default { + title: '__component_name_pascalcase__', + component: __component_name_pascalcase__, + decorators: [withKnobs], +}; + +export const _default = () => { + // __do_not_submit__: This is example code only. + return ( + <__component_name_pascalcase__ + style={{width: 300, height: 200}} + example-property="example string property value" + > + This text is inside. + + ); +}; diff --git a/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/test/test-amp-__component_name_hyphenated__.js b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/test/test-amp-__component_name_hyphenated__.js new file mode 100644 index 0000000000000..69416d6defa3b --- /dev/null +++ b/build-system/tasks/make-extension/template/bento/extensions/amp-__component_name_hyphenated__/__component_version__/test/test-amp-__component_name_hyphenated__.js @@ -0,0 +1,50 @@ +/** + * Copyright __current_year__ 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. + */ + +import '../amp-__component_name_hyphenated__'; +import {htmlFor} from '../../../../src/static-template'; +import {toggleExperiment} from '../../../../src/experiments'; +import {waitFor} from '../../../../testing/test-helper'; + +describes.realWin( + 'amp-__component_name_hyphenated__-v__component_version__', + { + amp: { + extensions: ['amp-__component_name_hyphenated__:__component_version__'], + }, + }, + (env) => { + let win; + let doc; + let html; + + beforeEach(async () => { + win = env.win; + doc = win.document; + html = htmlFor(doc); + toggleExperiment(win, 'bento-__component_name_hyphenated__', true, true); + }); + + it('example test renders', async () => { + const element = html` + + `; + doc.body.appendChild(element); + await waitFor(() => element.isConnected, 'element connected'); + expect(element.parentNode).to.equal(doc.body); + }); + } +); diff --git a/build-system/tasks/make-extension/template/classic/examples/amp-__component_name_hyphenated__.html b/build-system/tasks/make-extension/template/classic/examples/amp-__component_name_hyphenated__.html new file mode 100644 index 0000000000000..2a5f590fe6814 --- /dev/null +++ b/build-system/tasks/make-extension/template/classic/examples/amp-__component_name_hyphenated__.html @@ -0,0 +1,40 @@ + + + + + + + amp-__component_name_hyphenated__ example + + + + + + + + + + + diff --git a/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/OWNERS b/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/OWNERS new file mode 100644 index 0000000000000..77a55ee52b285 --- /dev/null +++ b/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/OWNERS @@ -0,0 +1,10 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [{name: 'ampproject/wg-components'}], + }, + ], +} diff --git a/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js b/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js new file mode 100644 index 0000000000000..a97bd2f7415b6 --- /dev/null +++ b/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.js @@ -0,0 +1,51 @@ +/** + * Copyright __current_year__ 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. + */ + +__css_import__; +import {Layout} from '../../../src/layout'; + +const TAG = 'amp-__component_name_hyphenated__'; + +export class Amp__component_name_pascalcase__ extends AMP.BaseElement { + /** @param {!AmpElement} element */ + constructor(element) { + // __do_not_submit__: This is example code only. + super(element); + + /** @private {string} */ + this.myText_ = 'hello world'; + + /** @private {?Element} */ + this.container_ = null; + } + + /** @override */ + buildCallback() { + this.container_ = this.element.ownerDocument.createElement('div'); + this.container_.textContent = this.myText_; + this.element.appendChild(this.container_); + this.applyFillContent(this.container_, /* replacedContent */ true); + } + + /** @override */ + isLayoutSupported(layout) { + return layout == Layout.RESPONSIVE; + } +} + +AMP.extension(TAG, '__component_version__', AMP => { + AMP.registerElement(__register_element_args__); +}); diff --git a/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/__component_version__/test/test-amp-__component_name_hyphenated__.js b/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/__component_version__/test/test-amp-__component_name_hyphenated__.js new file mode 100644 index 0000000000000..8e4659796c59b --- /dev/null +++ b/build-system/tasks/make-extension/template/classic/extensions/amp-__component_name_hyphenated__/__component_version__/test/test-amp-__component_name_hyphenated__.js @@ -0,0 +1,53 @@ +/** + * Copyright __current_year__ 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. + */ + +import '../amp-__component_name_hyphenated__'; +import {htmlFor} from '../../../../src/static-template'; + +describes.realWin( + 'amp-__component_name_hyphenated__-v__component_version__', + { + amp: { + runtimeOn: true, + extensions: ['amp-__component_name_hyphenated__:__component_version__'], + }, + }, + (env) => { + let win; + let doc; + let html; + + beforeEach(() => { + win = env.win; + doc = win.document; + html = htmlFor(doc); + }); + + it('should contain "hello world" when built', async () => { + const element = html` + + + `; + doc.body.appendChild(element); + await element.whenBuilt(); + expect(element.querySelector('div').textContent).to.equal('hello world'); + }); + } +); diff --git a/build-system/tasks/make-extension/template/css/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.css b/build-system/tasks/make-extension/template/css/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.css new file mode 100644 index 0000000000000..c9023a0c4d348 --- /dev/null +++ b/build-system/tasks/make-extension/template/css/extensions/amp-__component_name_hyphenated__/__component_version__/amp-__component_name_hyphenated__.css @@ -0,0 +1,19 @@ +/** + * Copyright __current_year__ 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. + */ + +amp-__component_name_hyphenated__ { + /** Component styles */ +} diff --git a/build-system/tasks/make-extension/template/jss/extensions/amp-__component_name_hyphenated__/__component_version__/component.jss.js b/build-system/tasks/make-extension/template/jss/extensions/amp-__component_name_hyphenated__/__component_version__/component.jss.js new file mode 100644 index 0000000000000..a7c145b3d75e6 --- /dev/null +++ b/build-system/tasks/make-extension/template/jss/extensions/amp-__component_name_hyphenated__/__component_version__/component.jss.js @@ -0,0 +1,30 @@ +/** + * Copyright __current_year__ 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. + */ + +import {createUseStyles} from 'react-jss'; + +// __do_not_submit__: Example class used for styling +const exampleContentHidden = { + display: 'none', +}; + +const JSS = { + exampleContentHidden, +}; + +// useStyles gets replaced for AMP builds via `babel-plugin-transform-jss`. +// eslint-disable-next-line local/no-export-side-effect +export const useStyles = createUseStyles(JSS); diff --git a/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__component_version__/storybook/Basic.amp.js b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__component_version__/storybook/Basic.amp.js new file mode 100644 index 0000000000000..012923b938216 --- /dev/null +++ b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__component_version__/storybook/Basic.amp.js @@ -0,0 +1,48 @@ +/** + * Copyright __current_year__ 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. + */ + +import * as Preact from '../../../../src/preact'; +import {withAmp} from '@ampproject/storybook-addon'; +import {withKnobs} from '@storybook/addon-knobs'; + +export default { + title: 'amp-__component_name_hyphenated__-__component_version_snakecase__', + decorators: [withKnobs, withAmp], + + parameters: { + extensions: [ + {name: 'amp-__component_name_hyphenated__', version: '__component_version__'}, + ], + __storybook_experiments_do_not_add_trailing_comma__ + }, +}; + +// __do_not_submit__: This is example code only. +export const ExampleUseCase = () => { + return ( + + This text is inside. + + ); +}; + +ExampleUseCase.story = { + name: 'Example use case story' +}; diff --git a/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__component_version__/test/__validator__-amp-__component_name_hyphenated__.html b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__component_version__/test/__validator__-amp-__component_name_hyphenated__.html new file mode 100644 index 0000000000000..2a5f590fe6814 --- /dev/null +++ b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__component_version__/test/__validator__-amp-__component_name_hyphenated__.html @@ -0,0 +1,40 @@ + + + + + + + amp-__component_name_hyphenated__ example + + + + + + + + + + + diff --git a/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__validator__-amp-__component_name_hyphenated__.protoascii b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__validator__-amp-__component_name_hyphenated__.protoascii new file mode 100644 index 0000000000000..e52970efc3115 --- /dev/null +++ b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/__validator__-amp-__component_name_hyphenated__.protoascii @@ -0,0 +1,43 @@ +# +# Copyright __current_year__ 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. +# + +tags: { # amp-__component_name_hyphenated__ + html_format: AMP + tag_name: "SCRIPT" + extension_spec: { + name: "amp-__component_name_hyphenated__" + version: "__component_version__" + version: "latest" + } + attr_lists: "common-extension-attrs" +} +tags: { # + html_format: AMP + tag_name: "__component_name_hyphenated_capitalized__" + requires_extension: "amp-__component_name_hyphenated__" + attr_lists: "extended-amp-global" + spec_url: "https://amp.dev/documentation/components/amp-__component_name_hyphenated__/" + amp_layout: { + supported_layouts: CONTAINER + supported_layouts: FILL + supported_layouts: FIXED + supported_layouts: FIXED_HEIGHT + supported_layouts: FLEX_ITEM + supported_layouts: INTRINSIC + supported_layouts: NODISPLAY + supported_layouts: RESPONSIVE + } +} diff --git a/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/amp-__component_name_hyphenated__.md b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/amp-__component_name_hyphenated__.md new file mode 100644 index 0000000000000..0874528fb3f47 --- /dev/null +++ b/build-system/tasks/make-extension/template/shared/extensions/amp-__component_name_hyphenated__/amp-__component_name_hyphenated__.md @@ -0,0 +1,267 @@ +--- +$category@: presentation +formats: + - websites +teaser: + text: Fill this in with teaser text to improve SEO. Use the component description. +--- + + + + + +# amp-__component_name_hyphenated__ + + + +## Usage + +One to three paragraphs explaining the component usage. List important functionality. Explain why developers care about it. + +[filter formats=“websites”] + +Below is an example for websites. + +[example preview="inline" playground="true" imports="amp-__component_name_hyphenated__"] + +```html + + I am a hello world inline executable code sample for websites! + +``` + +[/example][/filter] + + + +[filter formats=“ads”] + +Below is an example for ads. + +[example preview=“inline” playground=“true” imports="amp-__component_name_hyphenated__"] + +```html + + I am a hello world inline executable code sample for ads! + +``` + +[/example][/filter] + +### Standalone use outside valid AMP documents (optional) + + + +Bento AMP allows you to use AMP components in non-AMP pages without needing to commit to fully valid AMP. You can take these components and place them in implementations with frameworks and CMSs that don't support AMP. Read more in our guide [Use AMP components in non-AMP pages](https://amp.dev/documentation/guides-and-tutorials/start/bento_guide/). + +#### Example + +The example below demonstrates `amp-__component_name_hyphenated__` component in standalone use. + +[example preview="top-frame" playground="false"] +``` + +... + + + +... + + + ... + + + +``` +[/example] + +#### Interactivity and API usage + +Bento enabled components in standalone use are highly interactive through their API. In Bento standalone use, the element's API replaces AMP Actions and events and [`amp-bind`](https://amp.dev/documentation/components/amp-bind/?format=websites). + +The `amp-__component_name_hyphenated__` component API is accessible by including the following script tag in your document: + +``` +await customElements.whenDefined('amp-__component_name_hyphenated__-component'); +const api = await __component_name_pascalcase__.getApi(); +``` + +The `amp-__component_name_hyphenated__` API allows you to register and respond to the following events: + +**event 1** +Explanation of event, proper syntax/arguments. + +``` +example +``` + +**event 2** +Explanation of event, proper syntax/arguments. + +``` +example +``` + +**action 1** +Explanation of action, proper syntax/arguments. + +``` +example +``` + +#### Layout and style + +Each Bento component has a small CSS library you must include to guarantee proper loading without [content shifts](https://web.dev/cls/). Because of order-based specificity, you must manually ensure that stylesheets are included before any custom styles. + +``` + +``` + +Fully valid AMP pages use the AMP layout system to infer sizing of elements to create a page structure before downloading any remote resources. However, Bento use imports components into less controlled environments and AMP's layout system is inaccessible. + +**Container type** + +The `amp-__component_name_hyphenated__` component has a container/non-container layout type. To ensure the component renders correctly, apply the following styles: + +```css +example +``` + +**style/layout guidelines 2 (optional)** + +Information on how to layout and style `amp-__component_name_hyphenated__`. + +``` +example +``` + +### Behavior users should be aware of (optional) + +What to do if they want behavior. How to work around it. + +```html + + Code sample of behavior or behavior workaround. + +``` + +### Behavior restrictions + +What is allowed, what isn't. + +## Attributes + +### `attribute-name` + +Description of attribute. Use cases for this attribute. + +- `attribute-value-option-one` (default): `attribute-option-one-value` does this to `amp-__component_name_hyphenated__`. +- `attribute-value-option-two`: `attribute-option-two-value` does this to `amp-__component_name_hyphenated__`. + +### `optional-attribute-name` (optional) + +Here, I write what `optional-attribute-name` will do to `amp-__component_name_hyphenated__`. + +## Actions (optional) + +### `action-name` + +Description of action. Use cases of `action-name`. Include all the nuances, such as: `amp-__component_name_hyphenated__` needs to be identified with an `id` to work. + +## Events (optional) + +### `event-name` + +Description of event. Use cases of event-name. Include all the nuances, such as: `amp-__component_name_hyphenated__` needs to be identified with an `id` to work. + +#### Valid AMP + +Syntax and argument details for use in fully valid AMP pages. + +[example preview=”top-frame” playground=”true”] + +```html + + + + + + Hello World! + + + +``` + +[/example] + +#### Bento mode + +Syntax and argument details for use in Bento mode. + +``` +Bento example +``` + +## Styling (optional) + +Explain how to style the element. + +## Analytics (optional) + +Explain analytics. + +```html +"configuration": {} +``` + +## Accessibility (optional) + +Accessibility information related to `amp-__component_name_hyphenated__`. + +## Version notes (optional) + +Information on version differences and migration notes. + +## Validation + +See [amp-__component_name_hyphenated__ rules](https://github.com/ampproject/amphtml/blob/main/extensions/amp-__component_name_hyphenated__/validator-amp-__component_name_hyphenated__.protoascii) in the AMP validator specification. diff --git a/build-system/tasks/make-extension/test/template/test-1/file-__baz__.txt b/build-system/tasks/make-extension/test/template/test-1/file-__baz__.txt new file mode 100644 index 0000000000000..c4957b5391e5b --- /dev/null +++ b/build-system/tasks/make-extension/test/template/test-1/file-__baz__.txt @@ -0,0 +1 @@ +Constant. diff --git a/build-system/tasks/make-extension/test/template/test-1/x-__foo__/__bar__.txt b/build-system/tasks/make-extension/test/template/test-1/x-__foo__/__bar__.txt new file mode 100644 index 0000000000000..3b51aadb12565 --- /dev/null +++ b/build-system/tasks/make-extension/test/template/test-1/x-__foo__/__bar__.txt @@ -0,0 +1 @@ +This file is generated with values __foo__, __bar__, __baz__. diff --git a/build-system/tasks/make-extension/test/template/test-2/from-test-2/__component_name_hyphenated__.txt b/build-system/tasks/make-extension/test/template/test-2/from-test-2/__component_name_hyphenated__.txt new file mode 100644 index 0000000000000..257cc5642cb1a --- /dev/null +++ b/build-system/tasks/make-extension/test/template/test-2/from-test-2/__component_name_hyphenated__.txt @@ -0,0 +1 @@ +foo diff --git a/build-system/tasks/make-extension/test/test.js b/build-system/tasks/make-extension/test/test.js new file mode 100644 index 0000000000000..dc46813e6470a --- /dev/null +++ b/build-system/tasks/make-extension/test/test.js @@ -0,0 +1,351 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const ava = require('ava'); +const path = require('path'); +const tempy = require('tempy'); +const {mkdirp, readFile, readJson, writeFile, writeJson} = require('fs-extra'); + +const stubbedCalls = {}; + +async function stubModule(path, name, fn, callsFake = () => {}) { + const imported = require(path); + const original = imported[name]; + imported[name] = (...args) => { + const calls = stubbedCalls[name] || (stubbedCalls[name] = []); + calls.push(args); + return callsFake(...args); + }; + await fn(); + imported.log = original; +} + +const test = (name, cb) => + ava(name, (t) => + // Disable logging since it clutters the test output. + stubModule('../../../common/logging', 'log', () => + stubModule('../../../common/logging', 'logLocalDev', () => + // Disable prettier formatting since it's slow. + stubModule('../format', 'format', () => + // Run test + cb(t) + ) + ) + ) + ); + +test('writeFromTemplateDir', (t) => + tempy.directory.task(async (dir) => { + const {writeFromTemplateDir} = require('..'); + await writeFromTemplateDir( + path.join(__dirname, 'template/test-1'), + { + '__foo__': 'value-of-foo', + '__bar__': 'bar-value', + '__baz__': 'bazzzzz', + }, + dir + ); + t.is( + await readFile(`${dir}/x-value-of-foo/bar-value.txt`, 'utf-8'), + 'This file is generated with values value-of-foo, bar-value, bazzzzz.\n' + ); + t.is(await readFile(`${dir}/file-bazzzzz.txt`, 'utf-8'), 'Constant.\n'); + })); + +test('writeFromTemplateDir skips existing files', (t) => + tempy.directory.task(async (dir) => { + const {writeFromTemplateDir} = require('..'); + await mkdirp(`${dir}/x-value-of-foo/`); + await writeFile(`${dir}/x-value-of-foo/bar-value.txt`, 'Original.\n'); + await writeFromTemplateDir( + path.join(__dirname, 'template/test-1'), + { + '__foo__': 'value-of-foo', + '__bar__': 'bar-value', + '__baz__': 'bazzzzz', + }, + dir + ); + t.is( + await readFile(`${dir}/x-value-of-foo/bar-value.txt`, 'utf-8'), + 'Original.\n' + ); + t.is(await readFile(`${dir}/file-bazzzzz.txt`, 'utf-8'), 'Constant.\n'); + })); + +test('makeExtensionFromTemplates merges multiple templates', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [ + path.join(__dirname, 'template/test-1'), + path.join(__dirname, 'template/test-2'), + ], + dir, + { + name: 'my-extension-name', + } + ); + + t.is( + await readFile(`${dir}/from-test-2/my-extension-name.txt`, 'utf-8'), + 'foo\n' + ); + + // Replacement keys for test-1 are placeholders and are not set by + // makeExtensionFromTemplates, so they remain in the generated files. + t.is( + await readFile(`${dir}/x-__foo__/__bar__.txt`, 'utf-8'), + 'This file is generated with values __foo__, __bar__, __baz__.\n' + ); + t.is(await readFile(`${dir}/file-__baz__.txt`, 'utf-8'), 'Constant.\n'); + })); + +test('makeExtensionFromTemplates does not print unit test blurb if a test file is not created', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [path.join(__dirname, 'template/test-1')], + dir, + {name: 'my-extension-name'} + ); + t.false( + !!stubbedCalls.logLocalDev.find((args) => + args.find((arg) => arg.includes('amp unit --files')) + ) + ); + })); + +test('makeExtensionFromTemplates prints unit test blurb if a test file is created', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [path.join(__dirname, '../template/bento')], + dir, + {name: 'my-extension-name'} + ); + t.true( + !!stubbedCalls.logLocalDev.find((args) => + args.find((arg) => arg.includes('amp unit --files')) + ) + ); + })); + +test('makeExtensionFromTemplates does not print Storybook blurb if a Storybook file is not created', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [path.join(__dirname, 'template/test-1')], + dir, + {name: 'my-extension-name'} + ); + t.false( + !!stubbedCalls.logLocalDev.find((args) => + args.find((arg) => arg.includes('amp storybook')) + ) + ); + })); + +test('makeExtensionFromTemplates prints Storybook blurb if a Storybook file is created', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [path.join(__dirname, '../template/bento')], + dir, + {name: 'my-extension-name'} + ); + t.true( + !!stubbedCalls.logLocalDev.find((args) => + args.find((arg) => arg.includes('amp storybook')) + ) + ); + })); + +test('makeExtensionFromTemplates does not print validator blurb if a validator-*.html file is not created', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [path.join(__dirname, 'template/test-1')], + dir, + {name: 'my-extension-name'} + ); + t.false( + !!stubbedCalls.logLocalDev.find((args) => + args.find((arg) => arg.includes('amp validator --update_tests')) + ) + ); + })); + +test('makeExtensionFromTemplates prints validator blurb if a validator-*.html file is created', (t) => + tempy.directory.task(async (dir) => { + const {makeExtensionFromTemplates} = require('..'); + await makeExtensionFromTemplates( + [path.join(__dirname, '../template/shared')], + dir, + {name: 'my-extension-name'} + ); + t.true( + !!stubbedCalls.logLocalDev.find((args) => + args.find((arg) => arg.includes('amp validator --update_tests')) + ) + ); + })); + +test('insertExtensionBundlesConfig inserts new entry', (t) => + tempy.file.task( + async (destination) => { + const {insertExtensionBundlesConfig} = require('..'); + await writeJson(destination, [ + { + name: '_', + }, + { + name: 'z', + }, + ]); + + await insertExtensionBundlesConfig( + { + name: 'a', + version: 'x', + options: {hasCss: true}, + }, + destination + ); + + t.deepEqual(await readJson(destination), [ + // inserted in lexicographical order by name: + { + name: '_', + }, + { + name: 'a', + version: 'x', + latestVersion: 'x', + options: {hasCss: true}, + }, + { + name: 'z', + }, + ]); + }, + {extension: 'json'} + )); + +test('insertExtensionBundlesConfig uses existing latestVersion', (t) => + tempy.file.task( + async (destination) => { + const {insertExtensionBundlesConfig} = require('..'); + await writeJson(destination, [ + { + name: 'foo', + version: 'existing version', + latestVersion: 'existing version', + }, + ]); + + await insertExtensionBundlesConfig( + { + name: 'foo', + version: 'new version', + }, + destination + ); + + t.deepEqual(await readJson(destination), [ + { + name: 'foo', + version: 'existing version', + latestVersion: 'existing version', + }, + { + name: 'foo', + version: 'new version', + latestVersion: 'existing version', + }, + ]); + }, + {extension: 'json'} + )); + +test('insertExtensionBundlesConfig uses passed latestVersion', (t) => + tempy.file.task( + async (destination) => { + const {insertExtensionBundlesConfig} = require('..'); + await writeJson(destination, [ + { + name: 'foo', + version: '_', + }, + ]); + + await insertExtensionBundlesConfig( + { + name: 'foo', + version: 'new version', + latestVersion: 'new version', + }, + destination + ); + + t.deepEqual(await readJson(destination), [ + { + name: 'foo', + version: '_', + }, + { + name: 'foo', + version: 'new version', + latestVersion: 'new version', + }, + ]); + }, + {extension: 'json'} + )); + +test('insertExtensionBundlesConfig uses version as latestVersion', (t) => + tempy.file.task( + async (destination) => { + const {insertExtensionBundlesConfig} = require('..'); + await writeJson(destination, [ + { + name: 'foo', + version: '_', + }, + ]); + + await insertExtensionBundlesConfig( + { + name: 'foo', + version: 'new version', + }, + destination + ); + + t.deepEqual(await readJson(destination), [ + { + name: 'foo', + version: '_', + }, + { + name: 'foo', + version: 'new version', + latestVersion: 'new version', + }, + ]); + }, + {extension: 'json'} + )); diff --git a/build-system/tasks/markdown-toc/OWNERS b/build-system/tasks/markdown-toc/OWNERS new file mode 100644 index 0000000000000..cded1b6997e2b --- /dev/null +++ b/build-system/tasks/markdown-toc/OWNERS @@ -0,0 +1,13 @@ +// For an explanation of the OWNERS rules and syntax, see: +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example + +{ + rules: [ + { + owners: [ + {name: 'ampproject/wg-infra'}, + {name: 'alanorozco', notify: true}, + ], + }, + ], +} diff --git a/build-system/tasks/markdown-toc/README.md b/build-system/tasks/markdown-toc/README.md new file mode 100644 index 0000000000000..82c49f71ee80f --- /dev/null +++ b/build-system/tasks/markdown-toc/README.md @@ -0,0 +1,77 @@ +# markdown-toc + +Ensures that Markdown files in this repository have updated Tables-Of-Content. + +``` +amp markdown-toc [--fix] +``` + +## Usage + +Files must contain the following header comment: + +```markdown + +``` + +Running the mentioned command inserts the TOC after the comment if necessary: + +```diff + # Hello + + + ++ - [Section](#section) + + ## Section + + Content. +``` + +These files are checked during Continuous Integration so they will stay up-to-date. + +## Options + +You may configure how you'd like to create and format a TOC by including a second comment including a JSON object. This object contains [`markdown-toc` options](https://github.com/jonschlinkert/markdown-toc#options). + +For example, the following options: + +```json +{"maxdepth": 1} +``` + +In this markdown file: + +```markdown +# Hello + + + + + +- [included header one](#included-header-one) +- [included header two](#included-header-two) + +## included header one + +### this header is not included + +because of `{"maxdepth": 1}` above + +## included header two +``` diff --git a/build-system/tasks/markdown-toc/index.js b/build-system/tasks/markdown-toc/index.js new file mode 100644 index 0000000000000..683df0d1ee39b --- /dev/null +++ b/build-system/tasks/markdown-toc/index.js @@ -0,0 +1,206 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const globby = require('globby'); +const path = require('path'); +const prettier = require('prettier'); +const toc = require('markdown-toc'); +const {getStdout} = require('../../common/process'); +const {green} = require('../../common/colors'); +const {logOnSameLineLocalDev} = require('../../common/logging'); +const {readFile} = require('fs-extra'); +const {writeDiffOrFail} = require('../../common/diff'); + +const task = 'markdown-toc'; + +const header = ``; + +// Case-insensitive, allows multiple newlines and arbitrary indentation. +const headerRegexp = new RegExp( + header + .split(/\n+/g) + .map( + (line) => + `\\s*${line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/^\s+/, '')}` + ) + .join('\n+'), + 'im' +); + +/** + * @param {string} content + * @return {?string} + */ +function getFrontmatter(content) { + const end = content.search(/(\n-|\n#)/m); + if (end < 0) { + return null; + } + const withWhitespace = content.substr(0, end); + if (withWhitespace.trim().length < 1) { + return null; + } + return withWhitespace; +} + +/** + * @param {?string} maybeComment + * @return {?Object} + */ +function isolateCommentJson(maybeComment) { + if (!maybeComment) { + return null; + } + + const json = maybeComment.replace('', '').trim(); + if (!json) { + return null; + } + + try { + const options = JSON.parse(json); + return options; + } catch (_) { + return null; + } +} + +/** + * @param {string} content + * @return {Promise} + */ +async function overrideToc(content) { + const headerMatch = content.match(headerRegexp); + + if (!headerMatch || !headerMatch.length || headerMatch.index === undefined) { + return null; + } + + const headerEnd = headerMatch.index + headerMatch[0].length; + + const afterHeader = content.substr(headerEnd); + + const frontmatter = getFrontmatter(afterHeader); + const frontmatterOptions = isolateCommentJson(frontmatter); + + const afterFrontmatter = frontmatter + ? afterHeader.substr(afterHeader.indexOf(frontmatter) + frontmatter.length) + : afterHeader; + + const listEnd = afterFrontmatter.search(/\n[^-\s]/m); + + if (listEnd < 0) { + return null; + } + + // https://github.com/jonschlinkert/markdown-toc#options + const options = { + firsth1: false, + ...frontmatterOptions, + // Don't allow to override `bullets` because it breaks assumptions. + bullets: '-', + }; + + const overridden = [ + content.substr(0, headerEnd), + frontmatter, + toc(content, options).content, + afterFrontmatter.substr(listEnd), + ].join('\n'); + + return prettier.format(overridden, { + ...(await prettier.resolveConfig('_.md')), + parser: 'markdown', + }); +} + +/** + * @param {string} cwd + * @return {Promise>} + */ +async function overrideTocGlob(cwd) { + const glob = [ + '**/*.md', + '!**/{node_modules,build,dist,dist.3p,dist.tools,.karma-cache}/**', + ]; + const files = globby.sync(glob, {cwd}).map((file) => path.join(cwd, file)); + const filesIncludingString = getStdout( + [`grep -irl "${task}"`, ...files].join(' ') + ) + .trim() + .split('\n'); + + /** @type {Object} */ + const result = {}; + + for (const filename of filesIncludingString) { + const content = await readFile(filename, 'utf-8'); + const tentative = await overrideToc(content); + + if (!tentative) { + continue; + } + + result[filename] = tentative === content ? null : tentative; + } + + return result; +} + +/** + * Entry point for the `amp markdown-toc` task. + * @return {Promise} + */ +async function markdownToc() { + const result = await overrideTocGlob('.'); + let errored = false; + for (const filename in result) { + if (filename.indexOf('markdown-toc/') >= 0) { + continue; + } + const tentative = result[filename]; + if (!tentative) { + logOnSameLineLocalDev(green('Checked: ') + filename); + } else { + try { + writeDiffOrFail('markdown-toc', filename, tentative); + } catch (_) { + errored = true; + } + } + } + if (errored) { + throw new Error('Files are outdated'); + } +} + +module.exports = { + headerRegExpForTesting: headerRegexp, + markdownToc, + overrideTocGlob, + overrideToc, +}; + +markdownToc.description = + 'Finds Markdown files that contain table of contents and updates them.'; + +markdownToc.flags = { + 'fix': 'Write to file', +}; diff --git a/build-system/tasks/markdown-toc/test/all-are-complete/allows-paragraph-after-list.md b/build-system/tasks/markdown-toc/test/all-are-complete/allows-paragraph-after-list.md new file mode 100644 index 0000000000000..ec0d908a5b446 --- /dev/null +++ b/build-system/tasks/markdown-toc/test/all-are-complete/allows-paragraph-after-list.md @@ -0,0 +1,16 @@ +# allows-paragraph-after-list.md + + + +- [section](#section) + +This paragraph should be preserved. + +## section + +content diff --git a/build-system/tasks/markdown-toc/test/all-are-complete/arbitrary-header-indentation.md b/build-system/tasks/markdown-toc/test/all-are-complete/arbitrary-header-indentation.md new file mode 100644 index 0000000000000..ff9b154e3da6e --- /dev/null +++ b/build-system/tasks/markdown-toc/test/all-are-complete/arbitrary-header-indentation.md @@ -0,0 +1,18 @@ +# arbitrary-header-indentation.md + +This TOC header has newlines and indentation that doesn't match the lookup string as defined, but should still be matched. + + + +- [section](#section) + +## section + +content diff --git a/build-system/tasks/markdown-toc/test/all-are-complete/complete.md b/build-system/tasks/markdown-toc/test/all-are-complete/complete.md new file mode 100644 index 0000000000000..c073203aace24 --- /dev/null +++ b/build-system/tasks/markdown-toc/test/all-are-complete/complete.md @@ -0,0 +1,27 @@ +# complete.md + + + +- [tacos](#tacos) + - [asada](#asada) + - [al pastor](#al-pastor) + - [veggie](#veggie) + +## tacos + +### asada + +asada + +### al pastor + +al pastor + +### veggie + +veggie diff --git a/build-system/tasks/markdown-toc/test/all-are-complete/ignores-unparsable-options.md b/build-system/tasks/markdown-toc/test/all-are-complete/ignores-unparsable-options.md new file mode 100644 index 0000000000000..121b038c3f583 --- /dev/null +++ b/build-system/tasks/markdown-toc/test/all-are-complete/ignores-unparsable-options.md @@ -0,0 +1,18 @@ +# ignores-unparsable-options.md + +This file has unparsable TOC options, but should still have a TOC and should not fail. + + + + + +- [section](#section) + +## section + +content diff --git a/build-system/tasks/markdown-toc/test/all-are-complete/uses-options.md b/build-system/tasks/markdown-toc/test/all-are-complete/uses-options.md new file mode 100644 index 0000000000000..af94515431f8d --- /dev/null +++ b/build-system/tasks/markdown-toc/test/all-are-complete/uses-options.md @@ -0,0 +1,21 @@ +# uses-options.md + + + + + +- [included header one](#included-header-one) +- [included header two](#included-header-two) + +## included header one + +### this header is not included + +because of `{"maxdepth": 1}` above + +## included header two diff --git a/build-system/tasks/markdown-toc/test/some-are-incomplete/complete.md b/build-system/tasks/markdown-toc/test/some-are-incomplete/complete.md new file mode 100644 index 0000000000000..d4f7208478c1e --- /dev/null +++ b/build-system/tasks/markdown-toc/test/some-are-incomplete/complete.md @@ -0,0 +1,14 @@ +# some-are-incomplete + + + +- [What's this?](#whats-this) + +## What's this? + +This file is either `complete.md` or `incomplete.md`. One of them lacks a TOC. diff --git a/build-system/tasks/markdown-toc/test/some-are-incomplete/incomplete.md b/build-system/tasks/markdown-toc/test/some-are-incomplete/incomplete.md new file mode 100644 index 0000000000000..6b9bd037c0a7d --- /dev/null +++ b/build-system/tasks/markdown-toc/test/some-are-incomplete/incomplete.md @@ -0,0 +1,12 @@ +# some-are-incomplete + + + +## What's this? + +This file is either `complete.md` or `incomplete.md`. One of them lacks a TOC. diff --git a/build-system/tasks/markdown-toc/test/some-are-incomplete/should-not-appear-in-result.md b/build-system/tasks/markdown-toc/test/some-are-incomplete/should-not-appear-in-result.md new file mode 100644 index 0000000000000..57a2d7694e8bb --- /dev/null +++ b/build-system/tasks/markdown-toc/test/some-are-incomplete/should-not-appear-in-result.md @@ -0,0 +1 @@ +This file should be ignored because it lacks a TOC comment header. diff --git a/build-system/tasks/markdown-toc/test/test.js b/build-system/tasks/markdown-toc/test/test.js new file mode 100644 index 0000000000000..b4ce616b9132b --- /dev/null +++ b/build-system/tasks/markdown-toc/test/test.js @@ -0,0 +1,56 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const globby = require('globby'); +const path = require('path'); +const test = require('ava'); +const {headerRegExpForTesting, overrideToc, overrideTocGlob} = require('../'); +const {readFile} = require('fs-extra'); + +const dirname = path.relative(process.cwd(), __dirname); + +test('README.md includes correct header', async (t) => { + const expectedFoundTimes = 3; + + const filename = path.join(dirname, '../README.md'); + const content = await readFile(filename, 'utf-8'); + + const {length} = content.match( + new RegExp(headerRegExpForTesting.source, 'gim') + ); + t.is( + length, + expectedFoundTimes, + `${filename} should include TOC header comment ${expectedFoundTimes} times` + ); +}); + +test('overrideToc ./all-are-complete', async (t) => { + for (const filename of globby.sync(`${dirname}/all-are-complete/**/*.md`)) { + const content = await readFile(filename, 'utf-8'); + t.deepEqual(await overrideToc(content), content); + } +}); + +test('overrideTocGlob ./some-are-incomplete', async (t) => { + const dir = `${dirname}/some-are-incomplete`; + const expected = { + [`${dir}/complete.md`]: null, + [`${dir}/incomplete.md`]: + // Has exact same content but with TOC + await readFile(`${dir}/complete.md`, 'utf-8'), + }; + t.deepEqual(await overrideTocGlob(dir), expected); +}); diff --git a/build-system/tasks/mocha-ci-reporter.js b/build-system/tasks/mocha-ci-reporter.js deleted file mode 100644 index a67c32d44188a..0000000000000 --- a/build-system/tasks/mocha-ci-reporter.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -const {Base} = require('mocha').reporters; -const {inherits} = require('mocha').utils; -const {reportTestFinished} = require('./report-test-status'); -const {symbols} = require('./karma.conf').mochaReporter; - -/** - * Custom Mocha reporter for CI builds. - * Mimics the style of the Karma reporter on Travis. - * @param {*} runner - */ -function ciReporter(runner) { - Base.call(this, runner); - const self = this; - - runner.on('pass', function () { - process.stdout.write(Base.color('green', symbols.success)); - }); - - runner.on('pending', function () { - process.stdout.write(Base.color('bright yellow', symbols.info)); - }); - - runner.on('fail', function () { - process.stdout.write(Base.color('fail', symbols.error)); - }); - - runner.on('end', function () { - epilogue(); - }); - - function epilogue() { - const {failures, stats} = self; - reportTestFinished(stats.passes, stats.failures); - - Base.list(failures); - process.stdout.write( - `Executed ${stats.failures + stats.passes} of ${stats.tests} ` + - `(Skipped ${stats.pending}) ` - ); - if (stats.failures == 0) { - process.stdout.write(Base.color('green', 'SUCCESS \n')); - } else { - process.stdout.write(Base.color('fail', `${stats.failures} FAILED \n`)); - } - } -} - -inherits(ciReporter, Base); -module.exports = ciReporter; diff --git a/build-system/tasks/nailgun-compile b/build-system/tasks/nailgun-compile deleted file mode 100755 index 83391c7454a5a..0000000000000 --- a/build-system/tasks/nailgun-compile +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# -# Copyright 2019 The AMP HTML Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the license. -# -# This script is a wrapper around third-party/nailgun/nailgun-runner. -# -# Usage: -# ⤷ To start a server: gulp nailgun-start -# ⤷ To compile code: build-system/tasks/nailgun-compile -# ⤷ To stop the server: gulp nailgun-stop - -SCRIPT=${BASH_SOURCE[0]} -TASKS_DIR=$(dirname "$SCRIPT") - -eval $TASKS_DIR/../../third_party/nailgun/nailgun-runner org.ampproject.AmpCommandLineRunner -- "$@" diff --git a/build-system/tasks/nailgun.js b/build-system/tasks/nailgun.js deleted file mode 100644 index cec3cd459ebda..0000000000000 --- a/build-system/tasks/nailgun.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright 2019 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. - */ -'use strict'; - -const argv = require('minimist')(process.argv.slice(2)); -const colors = require('ansi-colors'); -const log = require('fancy-log'); -const sleep = require('sleep-promise'); -const {exec, execScriptAsync, getStdout} = require('../common/exec'); -const {green, red, cyan, yellow} = colors; -const {maybeGenerateRunner} = require('./generate-runner'); - -// Used to start and stop the Closure nailgun server -let nailgunRunnerReplacer; -const nailgunRunner = require.resolve( - '../../third_party/nailgun/nailgun-runner' -); -const nailgunServer = require.resolve( - '../../third_party/nailgun/nailgun-server.jar' -); -const DEFAULT_NAILGUN_PORT = '2113'; -const CHECK_TYPES_NAILGUN_PORT = '2114'; -const DIST_NAILGUN_PORT = '2115'; -const NAILGUN_STARTUP_TIMEOUT_MS = 5 * 1000; -const NAILGUN_STOP_TIMEOUT_MS = 5 * 1000; - -/** - * Replaces the default compiler binary with nailgun on linux and macos - * @return {?NodeRequire} - */ -function maybeReplaceDefaultCompiler() { - if (process.platform == 'darwin') { - return require('require-hijack') - .replace('google-closure-compiler-osx') - .with(nailgunRunner); - } else if (process.platform == 'linux') { - return require('require-hijack') - .replace('google-closure-compiler-linux') - .with(nailgunRunner); - } else { - log( - yellow('WARNING:'), - 'Cannot run', - cyan('nailgun-server.jar'), - 'on', - cyan(process.platform) - ); - log( - yellow('WARNING:'), - 'Closure compiler will be significantly slower than on', - cyan('macos'), - 'or', - cyan('linux') - ); - return null; - } -} - -/** - * Starts a nailgun server (provides a fast-running closure compiler instance) - * @param {string} port - * @param {boolean} detached - */ -async function startNailgunServer(port, detached) { - await maybeGenerateRunner(port); - - if (argv.disable_nailgun) { - return; - } - - nailgunRunnerReplacer = maybeReplaceDefaultCompiler(); - if (!nailgunRunnerReplacer) { - return; - } - - // Start up the nailgun server after cleaning up old instances (if any) - const customRunner = require.resolve(`../runner/dist/${port}/runner.jar`); - const startNailgunServerCmd = - 'java -XX:+TieredCompilation -server -cp ' + - `${nailgunServer}:${customRunner} ` + - `com.facebook.nailgun.NGServer ${port}`; - const stopNailgunServerCmd = `${nailgunRunner} --nailgun-port ${port} ng-stop`; - const getVersionCmd = - `${nailgunRunner} --nailgun-port ${port} ` + - 'org.ampproject.AmpCommandLineRunner -- --version'; - exec(stopNailgunServerCmd, {stdio: 'pipe'}); - const nailgunServerProcess = execScriptAsync(startNailgunServerCmd, { - stdio: detached ? 'ignore' : 'pipe', - detached, - }); - if (detached) { - nailgunServerProcess.unref(); - } - - // Ensure that the nailgun server is up and running - const end = Date.now() + NAILGUN_STARTUP_TIMEOUT_MS; - while (Date.now() < end) { - try { - const version = getStdout(getVersionCmd).trim(); - if (/Version/.test(version)) { - log('Started', cyan('nailgun-server.jar'), 'on port', cyan(port)); - return; - } - } catch (e) { - await sleep(1000); - } - } - log( - red('ERROR:'), - 'Could not start', - cyan('nailgun-server.jar'), - 'on port', - cyan(port) + '...' - ); - process.exit(1); -} - -/** - * Stops the nailgun server if it's running, and restores the binary used by - * google-closure-compiler - * @param {string} port - */ -async function stopNailgunServer(port) { - if (argv.disable_nailgun) { - return; - } - - if (nailgunRunnerReplacer) { - nailgunRunnerReplacer.restore(); - } - if (process.platform == 'darwin' || process.platform == 'linux') { - const stopNailgunServerCmd = `${nailgunRunner} --nailgun-port ${port} ng-stop`; - const stopped = exec(stopNailgunServerCmd, { - stdio: 'pipe', - timeout: NAILGUN_STOP_TIMEOUT_MS, - }); - if (stopped.status == 0) { - log('Stopped', cyan('nailgun-server.jar'), 'on port', cyan(port)); - } else { - log( - yellow('WARNING:'), - 'Could not stop', - cyan('nailgun-server.jar'), - 'on port', - cyan(port) - ); - log(red(stopped.stderr)); - } - } -} - -async function nailgunStart() { - log(green('Usage:')); - log('⤷ To start a server:', cyan('gulp nailgun-start')); - log('⤷ To compile code:', cyan('build-system/tasks/nailgun-compile ')); - log('⤷ To stop the server:', cyan('gulp nailgun-stop')); - await startNailgunServer(DEFAULT_NAILGUN_PORT, /* detached */ true); -} - -async function nailgunStop() { - await stopNailgunServer(DEFAULT_NAILGUN_PORT); -} - -module.exports = { - checkTypesNailgunPort: CHECK_TYPES_NAILGUN_PORT, - distNailgunPort: DIST_NAILGUN_PORT, - nailgunStart, - nailgunStop, - startNailgunServer, - stopNailgunServer, -}; - -nailgunStart.description = 'Starts up a nailgun server for closure compiler'; -nailgunStop.description = 'Stops an already running nailgun server'; diff --git a/build-system/tasks/performance-urls.js b/build-system/tasks/performance-urls.js index 5945a519a0fd8..dac3fff46ef56 100644 --- a/build-system/tasks/performance-urls.js +++ b/build-system/tasks/performance-urls.js @@ -14,64 +14,43 @@ * limitations under the License. */ -const colors = require('ansi-colors'); const fs = require('fs'); -const gulp = require('gulp'); -const log = require('fancy-log'); const path = require('path'); -const through2 = require('through2'); +const {cyan, green, red} = require('../common/colors'); +const {log} = require('../common/logging'); -const CONFIG_PATH = 'build-system/tasks/performance/config.json'; +const CONFIG_PATH = './performance/config.json'; const LOCAL_HOST_URL = 'http://localhost:8000/'; /** - * Throws an error with the given message. Duplicate function - * located in check-sourcemaps.js - * - * @param {string} message - */ -function throwError(message) { - const err = new Error(message); - err.showStack = false; - throw err; -} - -/** - * Entry point for 'gulp performance-urls' + * Entry point for 'amp performance-urls' * Check if all localhost urls in performance/config.json exist - * @return {!Promise} */ async function performanceUrls() { - return gulp.src([CONFIG_PATH]).pipe( - through2.obj(function (file) { - let obj; - try { - obj = JSON.parse(file.contents.toString()); - } catch (e) { - log(colors.yellow(`Could not parse ${CONFIG_PATH}. `)); - throwError(`Could not parse ${CONFIG_PATH}. `); - return; - } - const filepaths = obj.handlers.flatMap((handler) => - handler.urls - .filter((url) => url.startsWith(LOCAL_HOST_URL)) - .map((url) => - path.join(__dirname, '../../', url.split(LOCAL_HOST_URL)[1]) - ) - ); - for (const filepath of filepaths) { - if (!fs.existsSync(filepath)) { - log(colors.red(filepath + ' does not exist.')); - throwError(`${filepath} does not exist.`); - return; - } - } - log( - colors.green('SUCCESS:'), - 'All local performance task urls are valid.' - ); - }) + let jsonContent; + try { + jsonContent = require(CONFIG_PATH); + } catch (e) { + log(red('ERROR:'), 'Could not parse', cyan(CONFIG_PATH)); + process.exitCode = 1; + return; + } + /** @type {string[]} */ + const filepaths = jsonContent.handlers.flatMap((handler) => + handler.urls + .filter((url) => url.startsWith(LOCAL_HOST_URL)) + .map((url) => + path.join(__dirname, '../../', url.split(LOCAL_HOST_URL)[1]) + ) ); + for (const filepath of filepaths) { + if (!fs.existsSync(filepath)) { + log(red('ERROR:'), cyan(filepath), 'does not exist'); + process.exitCode = 1; + return; + } + } + log(green('SUCCESS:'), 'All local performance task urls are valid.'); } module.exports = { diff --git a/build-system/tasks/performance/README.md b/build-system/tasks/performance/README.md index 85eb9dbc0561c..35dac7674114e 100644 --- a/build-system/tasks/performance/README.md +++ b/build-system/tasks/performance/README.md @@ -1,7 +1,7 @@ # Performance test -Use `gulp performance` to run this test. It measures performance for the current branch compared to the current release. By default, the command first needs to compile minified runtime and components from the current branch by executing the `gulp dist` task. To skip this step, use the `--nobuild` flag, but the task will throw an error if the dist files are missing. Only the runtime and components used by documents from the configured URLs are compiled. +Use `amp performance` to run this test. It measures performance for the current branch compared to the current release. By default, the command first needs to compile minified runtime and components from the current branch by executing the `amp dist` task. To skip this step, use the `--nobuild` flag, but the task will throw an error if the dist files are missing. Only the runtime and components used by documents from the configured URLs are compiled. -The `config.json` file contains settings. The default value for `runs` is 50, which takes ~10 minutes to run, and generates results with ~5% margin of error. Differences larger than this should be investigated. The `defaultHandler` within the `handlers` list measures the basic pageview metrics (visible, first paint, etc...). The other handlers within the list can track different metrics (analytics request time, amount of analytics requests failed, etc...). Each handler within the handlers list must have a `handlerName` as well as a `urls` array (can be empty). +The `config.json` file contains settings. The default value for `runs` is 50, which takes ~10 minutes to run, and generates results with ~5% margin of error. Differences larger than this should be investigated. The `defaultHandler` within the `handlers` list measures the basic pageview metrics. The other handlers within the list can track different metrics (analytics request time, amount of analytics requests failed, etc...). Each handler within the handlers list must have a `handlerName` as well as a `urls` array (can be empty). -This test measures the following metrics: `visible`, `first paint`, `first contentful paint`, `largest contentful paint`, `time to interactive`, `max first input delay`, `cumulative layout shift`, `analytics request delay`, and `failed percentage of analytics requests`. +This test measures the Core Web Vitals metrics: `largest contentful paint`, `max first input delay`, and `cumulative layout shift`. For ads, the test also measures `analytics request delay` and `failed percentage of analytics requests`. diff --git a/build-system/tasks/performance/analytics-handler.js b/build-system/tasks/performance/analytics-handler.js index 501bb702d1e69..059fae321817c 100644 --- a/build-system/tasks/performance/analytics-handler.js +++ b/build-system/tasks/performance/analytics-handler.js @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * + * @param {!Array} handlersList + * @param {?Object} handlerOptions + * @param {!function} resolve + */ function setupAnalyticsHandler(handlersList, handlerOptions, resolve) { const {extraUrlParam} = handlerOptions; const analyticsParam = Object.keys(extraUrlParam) @@ -108,12 +114,8 @@ async function maybeHandleAnalyticsRequest( * @return {!Object} */ function getAnalyticsMetrics(analyticsHandlerOptions) { - const { - expectedRequests, - firstRequestTime, - requests, - startTime, - } = analyticsHandlerOptions; + const {expectedRequests, firstRequestTime, requests, startTime} = + analyticsHandlerOptions; const analyticsMetrics = {}; // If there is no firstRequestTime, that means that request didn't fire. // `percentRequestsFailed` because we take the mean rather than sum diff --git a/build-system/tasks/performance/cache-documents.js b/build-system/tasks/performance/cache-documents.js index 9786cad174979..b0dc910bf6c16 100644 --- a/build-system/tasks/performance/cache-documents.js +++ b/build-system/tasks/performance/cache-documents.js @@ -14,7 +14,7 @@ * limitations under the License. */ -const {CONTROL, downloadToDisk, EXPERIMENT} = require('./helpers'); +const {CONTROL, EXPERIMENT, downloadToDisk} = require('./helpers'); const {startServer, stopServer} = require('../serve'); const HOST = 'localhost'; const PORT = 8000; diff --git a/build-system/tasks/performance/copy-images.js b/build-system/tasks/performance/copy-images.js index 69a331daf60e3..5a1662c21946e 100644 --- a/build-system/tasks/performance/copy-images.js +++ b/build-system/tasks/performance/copy-images.js @@ -16,16 +16,17 @@ const fs = require('fs'); const {CONTROL, maybeCopyImageToCache, urlToCachePath} = require('./helpers'); -const {JSDOM} = require('jsdom'); /** * Lookup URL from cache. Inspect tags that could use images. * * @param {string} url + * @return {Promise} */ -function copyImagesFromTags(url) { +async function copyImagesFromTags(url) { const cachePath = urlToCachePath(url, CONTROL); const document = fs.readFileSync(cachePath); + const {JSDOM} = await import('jsdom'); // Lazy-imported to speed up task loading. const dom = new JSDOM(document); copyImagesFromAmpImg(url, dom); @@ -79,9 +80,10 @@ function copyImagesFromAmpVideo(url, dom) { * Copy locally stored images found in the markup to cache. * * @param {!Array} urls + * @return {Promise} */ -function copyLocalImages(urls) { - urls.forEach(copyImagesFromTags); +async function copyLocalImages(urls) { + await Promise.all(urls.map(copyImagesFromTags)); } module.exports = copyLocalImages; diff --git a/build-system/tasks/performance/helpers.js b/build-system/tasks/performance/helpers.js index 7781f5b96b973..1a1cdd080e7cb 100644 --- a/build-system/tasks/performance/helpers.js +++ b/build-system/tasks/performance/helpers.js @@ -93,16 +93,15 @@ function getLocalPathFromExtension(extension) { * @param {string} version * @return {!Promise} Resolves with relative path to file */ -function downloadToDisk(url, version = CONTROL) { +async function downloadToDisk(url, version = CONTROL) { touchDirs(); - return fetch(url) - .then((response) => response.text()) - .then((document) => { - const filepath = urlToCachePath(url, version); - fs.writeFileSync(filepath, document); - return filepath.split(`performance/cache/${version}/`)[1]; - }); + const response = await fetch(url); + const document = await response.text(); + const filepath = urlToCachePath(url, version); + fs.writeFileSync(filepath, document); + + return filepath.split(`performance/cache/${version}/`)[1]; } /** @@ -112,7 +111,7 @@ function downloadToDisk(url, version = CONTROL) { * @param {string} version * @return {!Promise} Resolves with relative path to file */ -function copyToCache(filePath, version = EXPERIMENT) { +async function copyToCache(filePath, version = EXPERIMENT) { touchDirs(); const fromPath = path.join(__dirname, '../../../dist/', filePath); @@ -122,7 +121,7 @@ function copyToCache(filePath, version = EXPERIMENT) { fs.copyFileSync(fromPath, destPath); - return Promise.resolve(filePath); + return filePath; } /** @@ -172,8 +171,8 @@ function getLocalVendorConfig(vendor) { * @param {string} filePath * @return {!Promise} Resolves with relative path to file */ -function getFileFromAbsolutePath(filePath) { - return Promise.resolve(fs.readFileSync(filePath)); +async function getFileFromAbsolutePath(filePath) { + return fs.readFileSync(filePath, 'utf-8'); } /** diff --git a/build-system/tasks/performance/index.js b/build-system/tasks/performance/index.js index cec30c723a966..f644d5f611bcd 100644 --- a/build-system/tasks/performance/index.js +++ b/build-system/tasks/performance/index.js @@ -22,7 +22,6 @@ const loadConfig = require('./load-config'); const rewriteAnalyticsTags = require('./rewrite-analytics-tags'); const rewriteScriptTags = require('./rewrite-script-tags'); const runTests = require('./run-tests'); -const {installPackages} = require('../../common/utils'); const {printReport} = require('./print-report'); /** @@ -34,13 +33,12 @@ async function performance() { resolver = resolverIn; }); - installPackages(__dirname); const config = new loadConfig(); const urls = Object.keys(config.urlToHandlers); const urlsAndAdsUrls = urls.concat(config.adsUrls || []); await cacheDocuments(urlsAndAdsUrls); await compileScripts(urlsAndAdsUrls); - copyLocalImages(urlsAndAdsUrls); + await copyLocalImages(urlsAndAdsUrls); await rewriteScriptTags(urlsAndAdsUrls); await rewriteAnalyticsTags(config.handlers); await getMetrics(urls, config); @@ -52,13 +50,13 @@ async function performance() { performance.description = 'Runs web performance test on current branch'; performance.flags = { - 'devtools': ' Run with devtools open', - 'headless': ' Run chromium headless', - 'nobuild': ' Does not compile minified runtime before running tests', + 'devtools': 'Run with devtools open', + 'headless': 'Run chromium headless', + 'nobuild': 'Does not compile minified runtime before running tests', 'threshold': - ' Fraction by which metrics are allowed to increase. Number between 0.0 and 1.0', - 'quiet': ' Does not log progress per page', - 'url': ' Page to test. Overrides urls set in config.json', + 'Fraction by which metrics are allowed to increase. Number between 0.0 and 1.0', + 'quiet': 'Does not log progress per page', + 'url': 'Page to test. Overrides urls set in config.json', }; module.exports = { diff --git a/build-system/tasks/performance/measure-documents.js b/build-system/tasks/performance/measure-documents.js index cb819b71c2dba..25e4a767285c6 100644 --- a/build-system/tasks/performance/measure-documents.js +++ b/build-system/tasks/performance/measure-documents.js @@ -16,28 +16,31 @@ const argv = require('minimist')(process.argv.slice(2)); const fs = require('fs'); -const log = require('fancy-log'); const { CDN_URL, CONTROL, DEFAULT_EXTENSIONS, EXPERIMENT, RESULTS_PATH, - urlToCachePath, getFileFromAbsolutePath, getLocalPathFromExtension, localFileToCachePath, + urlToCachePath, } = require('./helpers'); const { - setupAnalyticsHandler, getAnalyticsMetrics, + setupAnalyticsHandler, } = require('./analytics-handler'); -const {cyan, green} = require('ansi-colors'); +const {cyan, green} = require('../../common/colors'); +const {log} = require('../../common/logging'); const {setupAdRequestHandler} = require('./ads-handler'); -// Require Puppeteer dynamically to prevent throwing error in Travis +// Require Puppeteer dynamically to prevent throwing error during CI let puppeteer; +/** + * Lazy-requires the puppeteer module. + */ function requirePuppeteer_() { puppeteer = require('puppeteer'); } @@ -47,7 +50,7 @@ function requirePuppeteer_() { * observers need to be initialized before content begins to load to take * measurements. * - * @param {Puppeteer.page} page + * @param {puppeteer.page} page * @return {Promise} Resolves when script is evaluated */ const setupMeasurement = (page) => @@ -83,8 +86,8 @@ const setupMeasurement = (page) => }); /** - * Intecepts requests for default extensions made by runtime, - * and returns cached version (master and local). + * Intecepts requests for default extensions made by runtime and returns a + * cached version. * @param {Request} interceptedRequest * @param {string} version * @return {!Promise} @@ -142,14 +145,21 @@ const readMetrics = (page) => page.evaluate(() => { const entries = performance.getEntries(); + /** + * + * @param {string} name + * @return {nuber} + */ function getMetric(name) { const entry = entries.find((entry) => entry.name === name); return entry ? entry.startTime : 0; } - const firstPaint = getMetric('first-paint'); const firstContentfulPaint = getMetric('first-contentful-paint'); + /** + * @return {number} + */ function getMaxFirstInputDelay() { let longest = 0; @@ -165,16 +175,8 @@ const readMetrics = (page) => return longest; } - function getTimeToInteractive() { - return Date.now() - window.measureStarted; - } - return { - visible: getMetric('visible'), - firstPaint, - firstContentfulPaint, largestContentfulPaint: window.largestContentfulPaint, - timeToInteractive: getTimeToInteractive(), maxFirstInputDelay: getMaxFirstInputDelay(), cumulativeLayoutShift: window.cumulativeLayoutShift * 100, }; @@ -261,7 +263,7 @@ async function addHandlerMetric(handlerOptions, page) { return getAnalyticsMetrics(handlerOptions); case 'defaultHandler': default: - return await readMetrics(page); + return readMetrics(page); } } @@ -311,9 +313,8 @@ async function measureDocument(url, version, config) { const page = await browser.newPage(); const handlerOptionsForUrl = {...config.urlToHandlers[url]}; const handlersList = []; - const {timeoutPromise, resolve} = setupDelayBasedOnHandlerOptions( - handlerOptionsForUrl - ); + const {resolve, timeoutPromise} = + setupDelayBasedOnHandlerOptions(handlerOptionsForUrl); await page.setCacheEnabled(false); await page.setRequestInterception(true); setupDefaultHandlers(handlersList, version); @@ -366,6 +367,9 @@ async function measureDocuments(urls, config) { ); const startTime = Date.now(); + /** + * @return {number} + */ function timeLeft() { const elapsed = (Date.now() - startTime) / 1000; const secondsPerTask = elapsed / i; diff --git a/build-system/tasks/performance/package-lock.json b/build-system/tasks/performance/package-lock.json new file mode 100644 index 0000000000000..0f9177599f681 --- /dev/null +++ b/build-system/tasks/performance/package-lock.json @@ -0,0 +1,1152 @@ +{ + "name": "amp-performance", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", + "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", + "dev": true, + "optional": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "puppeteer": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", + "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/build-system/tasks/performance/package.json b/build-system/tasks/performance/package.json index adb64306fe6e7..51735906add9e 100644 --- a/build-system/tasks/performance/package.json +++ b/build-system/tasks/performance/package.json @@ -1,11 +1,11 @@ { "private": true, - "name": "gulp-performance", + "name": "amp-performance", "version": "0.1.0", - "description": "Dev dependencies used by `gulp performance`", + "description": "Dev dependencies used by `amp performance`", "devDependencies": { - "chai": "4.2.0", - "mocha": "8.0.1", - "puppeteer": "3.3.0" + "chai": "4.3.4", + "mocha": "8.4.0", + "puppeteer": "9.1.1" } } diff --git a/build-system/tasks/performance/print-report.js b/build-system/tasks/performance/print-report.js index ca4f936339f94..3454668c463d5 100644 --- a/build-system/tasks/performance/print-report.js +++ b/build-system/tasks/performance/print-report.js @@ -16,7 +16,7 @@ const fs = require('fs'); const {CONTROL, EXPERIMENT, RESULTS_PATH} = require('./helpers'); -const {cyan} = require('ansi-colors'); +const {cyan} = require('../../common/colors'); const {percent, trimmedMean} = require('./stats'); const HEADER_COLUMN = 26; @@ -64,8 +64,12 @@ function linesForMetric(metric, results) { ]; } +/** + * + * @param {string[]} urls + */ function printReport(urls) { - const results = JSON.parse(fs.readFileSync(RESULTS_PATH)); + const results = JSON.parse(fs.readFileSync(RESULTS_PATH, 'utf-8')); urls.forEach((url) => { const keys = Object.keys(results[url][CONTROL][0]); @@ -84,11 +88,20 @@ class PageMetrics { url; metrics; + /** + * @param {string} url + */ constructor(url) { this.url = url; this.metrics = new Map(); } + /** + * + * @param {string} metric + * @param {number} experiment + * @param {number} control + */ set(metric, experiment, control) { this.metrics.set(metric, {experiment, control}); } @@ -100,7 +113,7 @@ class PageMetrics { * @return {Array} report */ function getReport(urls) { - const raw = JSON.parse(fs.readFileSync(RESULTS_PATH)); + const raw = JSON.parse(fs.readFileSync(RESULTS_PATH, 'utf-8')); const report = []; urls.forEach((url) => { const results = raw[url]; diff --git a/build-system/tasks/performance/rewrite-analytics-tags.js b/build-system/tasks/performance/rewrite-analytics-tags.js index 7c180a9041f60..fad7005c79a14 100644 --- a/build-system/tasks/performance/rewrite-analytics-tags.js +++ b/build-system/tasks/performance/rewrite-analytics-tags.js @@ -21,13 +21,12 @@ const { getLocalVendorConfig, urlToCachePath, } = require('./helpers'); -const {JSDOM} = require('jsdom'); /** * Return local vendor config. * * @param {string} vendor - * @return {Object} + * @return {Promise} */ async function getVendorConfig(vendor) { return JSON.parse(await getLocalVendorConfig(vendor)); @@ -41,11 +40,11 @@ async function getVendorConfig(vendor) { * * @param {Element} tag * @param {Object} script - * @return {Object} + * @return {Promise} */ async function maybeMergeAndRemoveVendorConfig(tag, script) { - if (tag.hasAttribute('type')) { - const vendor = tag.getAttribute('type'); + const vendor = tag.getAttribute('type'); + if (vendor) { tag.removeAttribute('type'); const vendorConfig = await getVendorConfig(vendor); // TODO (micajuineho) replace with analytics/config.js merge objects @@ -65,6 +64,7 @@ async function maybeMergeAndRemoveVendorConfig(tag, script) { async function alterAnalyticsTags(url, version, extraUrlParams) { const cachePath = urlToCachePath(url, version); const document = fs.readFileSync(cachePath); + const {JSDOM} = await import('jsdom'); // Lazy-imported to speed up task loading. const dom = new JSDOM(document); const analyticsTags = Array.from( @@ -95,10 +95,10 @@ async function alterAnalyticsTags(url, version, extraUrlParams) { * @param {?Object} handlers * @return {Promise} */ -function rewriteAnalyticsConfig(handlers) { +async function rewriteAnalyticsConfig(handlers) { const handlerPromises = []; handlers.forEach((handler) => { - const {handlerName, adsUrls, urls, extraUrlParam} = handler; + const {adsUrls, extraUrlParam, handlerName, urls} = handler; if (handlerName !== 'analyticsHandler' && handlerName !== 'adsHandler') { return; } diff --git a/build-system/tasks/performance/rewrite-script-tags.js b/build-system/tasks/performance/rewrite-script-tags.js index b977e102101e2..7d764a1dfc86c 100644 --- a/build-system/tasks/performance/rewrite-script-tags.js +++ b/build-system/tasks/performance/rewrite-script-tags.js @@ -28,7 +28,6 @@ const { getLocalPathFromExtension, urlToCachePath, } = require('./helpers'); -const {JSDOM} = require('jsdom'); /** * Lookup URL from cache and rewrite URLs to build from working branch @@ -38,6 +37,7 @@ const {JSDOM} = require('jsdom'); async function useLocalScripts(url) { const cachePath = urlToCachePath(url, EXPERIMENT); const document = fs.readFileSync(cachePath); + const {JSDOM} = await import('jsdom'); // Lazy-imported to speed up task loading. const dom = new JSDOM(document); const scripts = Array.from(dom.window.document.querySelectorAll('script')); @@ -60,13 +60,15 @@ async function useLocalScripts(url) { } /** - * Lookup URL from cache and download scripts to cache and rewrite URLs to local copy + * Lookup URL from cache and download scripts to cache and rewrite URLs to local + * copy * * @param {string} url */ async function useRemoteScripts(url) { const cachePath = urlToCachePath(url, CONTROL); const document = fs.readFileSync(cachePath); + const {JSDOM} = await import('jsdom'); // Lazy-imported to speed up task loading. const dom = new JSDOM(document); const scripts = Array.from(dom.window.document.querySelectorAll('script')); @@ -88,8 +90,9 @@ async function useRemoteScripts(url) { } /** - * Download local and master version of default extension that - * are not explicility stated by script tags in the HTML. + * Download default extensions that are not explicility stated by script tags in + * the HTML. + * @return {Promise} */ async function downloadDefaultExtensions() { return Promise.all( diff --git a/build-system/tasks/performance/run-tests.js b/build-system/tasks/performance/run-tests.js index c518a7a9dada6..d80c1281ef698 100644 --- a/build-system/tasks/performance/run-tests.js +++ b/build-system/tasks/performance/run-tests.js @@ -18,6 +18,9 @@ const Mocha = require('mocha'); const path = require('path'); const TEST_SUITE_PATH = 'build-system/tasks/performance/test-suite.js'; +/** + * @param {function} resolver + */ function runTests(resolver) { const mocha = new Mocha(); mocha.addFile(path.join('./', TEST_SUITE_PATH)); diff --git a/build-system/tasks/performance/stats.js b/build-system/tasks/performance/stats.js index b96e9055d94d7..f18f816354c73 100644 --- a/build-system/tasks/performance/stats.js +++ b/build-system/tasks/performance/stats.js @@ -86,7 +86,7 @@ const q3 = (array) => { * * @param {number} a * @param {number} b - * @return {number|null} percentage change or null + * @return {?number} percentage change or null */ function percent(a, b) { if (a === 0) { diff --git a/build-system/tasks/performance/test-suite.js b/build-system/tasks/performance/test-suite.js index d2faa46baffb3..914c037c47fce 100644 --- a/build-system/tasks/performance/test-suite.js +++ b/build-system/tasks/performance/test-suite.js @@ -27,7 +27,7 @@ const reports = getReport(Object.keys(urlToHandlers)); reports.forEach((report) => { describe(`${report.url}`, () => { - report.metrics.forEach(({experiment, control}, name) => { + report.metrics.forEach(({control, experiment}, name) => { it(`${name}`, () => { expect(experiment).to.be.at.most(control * THRESHOLD); }); diff --git a/build-system/tasks/performance/yarn.lock b/build-system/tasks/performance/yarn.lock deleted file mode 100644 index 15be0997bcd39..0000000000000 --- a/build-system/tasks/performance/yarn.lock +++ /dev/null @@ -1,1088 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@types/node@*": - version "13.13.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz#160d82623610db590a64e8ca81784e11117e5a54" - integrity sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A== - -"@types/yauzl@^2.9.1": - version "2.9.1" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" - integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== - dependencies: - "@types/node" "*" - -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array.prototype.map@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.2.tgz#9a4159f416458a23e9483078de1106b2ef68f8ec" - integrity sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.4" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - -bl@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" - integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer@^5.2.1, buffer@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -chai@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - pathval "^1.1.0" - type-detect "^4.0.5" - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= - -chokidar@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.3.0" - optionalDependencies: - fsevents "~2.1.2" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -debug@3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== - dependencies: - type-detect "^4.0.0" - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -diff@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -es-abstract@^1.17.0-next.1: - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - -es-abstract@^1.17.4: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-get-iterator@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -extract-zip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" - integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -flat@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== - dependencies: - is-buffer "~2.0.3" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= - -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - -glob-parent@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob@7.1.6, glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" - integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== - -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== - dependencies: - has "^1.0.3" - -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -iterate-iterator@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" - integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw== - -iterate-value@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" - integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== - dependencies: - es-get-iterator "^1.0.2" - iterate-iterator "^1.0.1" - -js-yaml@3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -log-symbols@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" - -mime@^2.0.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== - -minimatch@3.0.4, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -mkdirp-classic@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" - integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== - -mocha@8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.0.1.tgz#fe01f0530362df271aa8f99510447bc38b88d8ed" - integrity sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.3.1" - debug "3.2.6" - diff "4.0.2" - escape-string-regexp "1.0.5" - find-up "4.1.0" - glob "7.1.6" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" - minimatch "3.0.4" - ms "2.1.2" - object.assign "4.1.0" - promise.allsettled "1.0.2" - serialize-javascript "3.0.0" - strip-json-comments "3.0.1" - supports-color "7.1.0" - which "2.0.2" - wide-align "1.1.3" - workerpool "6.0.0" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - -ms@2.1.2, ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@4.1.0, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -p-limit@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== - dependencies: - p-try "^2.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -picomatch@^2.0.4: - version "2.2.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" - integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== - -picomatch@^2.0.7: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -progress@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -promise.allsettled@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.2.tgz#d66f78fbb600e83e863d893e98b3d4376a9c47c9" - integrity sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg== - dependencies: - array.prototype.map "^1.0.1" - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - iterate-value "^1.0.0" - -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -puppeteer@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7" - integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw== - dependencies: - debug "^4.1.0" - extract-zip "^2.0.0" - https-proxy-agent "^4.0.0" - mime "^2.0.3" - progress "^2.0.1" - proxy-from-env "^1.0.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" - -readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== - dependencies: - picomatch "^2.0.7" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -serialize-javascript@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.0.0.tgz#492e489a2d77b7b804ad391a5f5d97870952548e" - integrity sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== - dependencies: - define-properties "^1.1.3" - function-bind "^1.1.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-json-comments@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== - -supports-color@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - dependencies: - has-flag "^4.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -tar-fs@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" - integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.0.0" - -tar-stream@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" - integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q== - dependencies: - bl "^4.0.1" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -unbzip2-stream@^1.3.3: - version "1.4.2" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz#84eb9e783b186d8fb397515fbb656f312f1a7dbf" - integrity sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -workerpool@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.0.tgz#85aad67fa1a2c8ef9386a1b43539900f61d03d58" - integrity sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA== - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -ws@^7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" - integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== - -yargs-parser@13.1.2, yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== - dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" - -yargs@13.3.2, yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" diff --git a/build-system/tasks/pr-check.js b/build-system/tasks/pr-check.js index 1eebe56245fa6..d90d1c1a3de2c 100644 --- a/build-system/tasks/pr-check.js +++ b/build-system/tasks/pr-check.js @@ -20,123 +20,133 @@ const { printChangeSummary, startTimer, stopTimer, - stopTimedJob, timedExec, } = require('../pr-check/utils'); -const {determineBuildTargets} = require('../pr-check/build-targets'); -const {runYarnChecks} = require('../pr-check/yarn-checks'); +const { + Targets, + buildTargetsInclude, + determineBuildTargets, +} = require('../pr-check/build-targets'); +const {runNpmChecks} = require('../common/npm-checks'); +const {setLoggingPrefix} = require('../common/logging'); -const FILENAME = 'pr-check.js'; +const jobName = 'pr-check.js'; /** * This file runs tests against the local workspace to mimic the CI build as * closely as possible. - * @param {Function} cb */ -async function prCheck(cb) { - const failTask = () => { - stopTimer(FILENAME, FILENAME, startTime); - const err = new Error('Local PR check failed. See logs above.'); - err.showStack = false; - cb(err); - }; - +async function prCheck() { const runCheck = (cmd) => { - const {status} = timedExec(cmd, FILENAME); + const {status} = timedExec(cmd); if (status != 0) { - failTask(); + stopTimer(jobName, startTime); + throw new Error('Local PR check failed. See logs above.'); } }; - const startTime = startTimer(FILENAME, FILENAME); - if (!runYarnChecks(FILENAME)) { - stopTimedJob(FILENAME, startTime); - return; + setLoggingPrefix(jobName); + const startTime = startTimer(jobName); + runNpmChecks(); + printChangeSummary(); + determineBuildTargets(); + + if (buildTargetsInclude(Targets.PRESUBMIT)) { + runCheck('amp presubmit'); + } + + if (buildTargetsInclude(Targets.INVALID_WHITESPACES)) { + runCheck('amp check-invalid-whitespaces --local_changes'); + } + + if (buildTargetsInclude(Targets.HTML_FIXTURES)) { + runCheck('amp validate-html-fixtures --local_changes'); } - printChangeSummary(FILENAME); - const buildTargets = determineBuildTargets(FILENAME); - runCheck('gulp lint --local_changes'); - runCheck('gulp prettify --local_changes'); - runCheck('gulp presubmit'); - runCheck('gulp check-exact-versions'); + if (buildTargetsInclude(Targets.LINT_RULES)) { + runCheck('amp lint'); + } else if (buildTargetsInclude(Targets.LINT)) { + runCheck('amp lint --local_changes'); + } + + if (buildTargetsInclude(Targets.PRETTIFY)) { + runCheck('amp prettify --local_changes'); + } + + if (buildTargetsInclude(Targets.AVA)) { + runCheck('amp ava'); + } + + if (buildTargetsInclude(Targets.BUILD_SYSTEM)) { + runCheck('amp check-build-system'); + } - if (buildTargets.has('AVA')) { - runCheck('gulp ava'); + if (buildTargetsInclude(Targets.BABEL_PLUGIN)) { + runCheck('amp babel-plugin-tests'); } - if (buildTargets.has('BABEL_PLUGIN')) { - runCheck('gulp babel-plugin-tests'); + if (buildTargetsInclude(Targets.CACHES_JSON)) { + runCheck('amp caches-json'); } - if (buildTargets.has('CACHES_JSON')) { - runCheck('gulp caches-json'); + if (buildTargetsInclude(Targets.DOCS)) { + runCheck('amp check-links --local_changes'); } - if (buildTargets.has('DOCS')) { - runCheck('gulp check-links --local_changes'); + if (buildTargetsInclude(Targets.DEV_DASHBOARD)) { + runCheck('amp dev-dashboard-tests'); } - if (buildTargets.has('DEV_DASHBOARD')) { - runCheck('gulp dev-dashboard-tests'); + if (buildTargetsInclude(Targets.OWNERS)) { + runCheck('amp check-owners'); } - if (buildTargets.has('OWNERS')) { - runCheck('gulp check-owners'); + if (buildTargetsInclude(Targets.PACKAGE_UPGRADE)) { + runCheck('amp check-exact-versions'); } - if (buildTargets.has('RENOVATE_CONFIG')) { - runCheck('gulp check-renovate-config'); + if (buildTargetsInclude(Targets.RENOVATE_CONFIG)) { + runCheck('amp check-renovate-config'); } - if (buildTargets.has('SERVER')) { - runCheck('gulp server-tests'); + if (buildTargetsInclude(Targets.SERVER)) { + runCheck('amp server-tests'); } - if (buildTargets.has('RUNTIME')) { - runCheck('gulp dep-check'); - runCheck('gulp check-types'); - runCheck('gulp check-sourcemaps'); + if (buildTargetsInclude(Targets.RUNTIME)) { + runCheck('amp dep-check'); + runCheck('amp check-types'); + runCheck('amp check-sourcemaps'); } - if (buildTargets.has('RUNTIME') || buildTargets.has('UNIT_TEST')) { - runCheck('gulp unit --local_changes --headless'); + if (buildTargetsInclude(Targets.RUNTIME, Targets.UNIT_TEST)) { + runCheck('amp unit --local_changes --headless'); } - if ( - buildTargets.has('RUNTIME') || - buildTargets.has('FLAG_CONFIG') || - buildTargets.has('INTEGRATION_TEST') - ) { + if (buildTargetsInclude(Targets.RUNTIME, Targets.INTEGRATION_TEST)) { if (!argv.nobuild) { - runCheck('gulp clean'); - runCheck('gulp dist --fortesting'); + runCheck('amp clean'); + runCheck('amp dist --fortesting'); } - runCheck('gulp integration --nobuild --compiled --headless'); + runCheck('amp integration --nobuild --compiled --headless'); } - if (buildTargets.has('RUNTIME') || buildTargets.has('VALIDATOR')) { - runCheck('gulp validator'); + if (buildTargetsInclude(Targets.RUNTIME, Targets.VALIDATOR)) { + runCheck('amp validator'); } - // #28497: Java Validator tests are broken due to Ubuntu keyserver outage. - // if (buildTargets.has('VALIDATOR_JAVA')) { - // runCheck('gulp validator-java'); - // } - - if (buildTargets.has('VALIDATOR_WEBUI')) { - runCheck('gulp validator-webui'); + if (buildTargetsInclude(Targets.VALIDATOR_WEBUI)) { + runCheck('amp validator-webui'); } - stopTimer(FILENAME, FILENAME, startTime); + stopTimer(jobName, startTime); } module.exports = { prCheck, }; -prCheck.description = - 'Runs a subset of the Travis CI checks against local changes.'; +prCheck.description = 'Runs a subset of the CI checks against local changes.'; prCheck.flags = { - 'nobuild': ' Skips building the runtime via `gulp dist`.', + 'nobuild': 'Skips building the runtime via `amp dist`.', }; diff --git a/build-system/tasks/pr-deploy-bot-utils.js b/build-system/tasks/pr-deploy-bot-utils.js index b37ba3a50af98..c01d19a4af4aa 100644 --- a/build-system/tasks/pr-deploy-bot-utils.js +++ b/build-system/tasks/pr-deploy-bot-utils.js @@ -15,17 +15,22 @@ */ 'use strict'; +const fetch = require('node-fetch'); const fs = require('fs-extra'); -const log = require('fancy-log'); const path = require('path'); -const request = require('request-promise'); -const {cyan, green} = require('ansi-colors'); -const {gitCommitHash} = require('../common/git'); +const {ciBuildSha, circleciBuildNumber} = require('../common/ci'); +const {cyan} = require('../common/colors'); +const {getLoggingPrefix, logWithoutTimestamp} = require('../common/logging'); const {replaceUrls: replaceUrlsAppUtil} = require('../server/app-utils'); -const {travisBuildNumber} = require('../common/travis'); const hostNamePrefix = 'https://storage.googleapis.com/amp-test-website-1'; +const prDeployBotBaseUrl = + 'https://amp-pr-deploy-bot.appspot.com/v0/pr-deploy/'; +/** + * @param {string} dest + * @return {Promise} + */ async function walk(dest) { const filelist = []; const files = await fs.readdir(dest); @@ -41,22 +46,30 @@ async function walk(dest) { return filelist; } +/** + * @return {string} + */ +function getBaseUrl() { + return `${hostNamePrefix}/amp_nomodule_${ciBuildSha()}`; +} + +/** + * @param {string} filePath + * @return {Promise} + */ async function replace(filePath) { const data = await fs.readFile(filePath, 'utf8'); - const hostName = `${hostNamePrefix}/amp_dist_${travisBuildNumber()}`; + const hostName = getBaseUrl(); const inabox = false; - const storyV1 = true; - const result = replaceUrlsAppUtil( - 'compiled', - data, - hostName, - inabox, - storyV1 - ); + const result = replaceUrlsAppUtil('compiled', data, hostName, inabox); await fs.writeFile(filePath, result, 'utf8'); } +/** + * @param {string} dir + * @return {Promise} + */ async function replaceUrls(dir) { const files = await walk(dir); const promises = files @@ -65,22 +78,25 @@ async function replaceUrls(dir) { await Promise.all(promises); } -async function signalDistUpload(result) { - const sha = gitCommitHash(); - const travisBuild = travisBuildNumber(); - const baseUrl = 'https://amp-pr-deploy-bot.appspot.com/v0/pr-deploy/'; - const url = `${baseUrl}travisbuilds/${travisBuild}/headshas/${sha}/${result}`; - - await request.post(url); - log( - green('INFO:'), - 'reported ', - cyan(`dist: ${result}`), - 'to the pr-deploy GitHub App' +/** + * @param {string} result + * @return {Promise} + */ +async function signalPrDeployUpload(result) { + const loggingPrefix = getLoggingPrefix(); + logWithoutTimestamp( + `${loggingPrefix} Reporting`, + cyan(result), + 'to the pr-deploy GitHub App...' ); + const sha = ciBuildSha(); + const maybeJobId = result == 'success' ? `/${circleciBuildNumber()}` : ''; + const url = `${prDeployBotBaseUrl}headshas/${sha}/${result}${maybeJobId}`; + await fetch(url, {method: 'POST'}); } module.exports = { + getBaseUrl, replaceUrls, - signalDistUpload, + signalPrDeployUpload, }; diff --git a/build-system/tasks/prepend-global/index.js b/build-system/tasks/prepend-global/index.js index e248b8483cfde..92aadcdb98ecd 100644 --- a/build-system/tasks/prepend-global/index.js +++ b/build-system/tasks/prepend-global/index.js @@ -17,18 +17,19 @@ const argv = require('minimist')(process.argv.slice(2)); const childProcess = require('child_process'); -const colors = require('ansi-colors'); +const colors = require('../../common/colors'); const fs = require('fs'); -const log = require('fancy-log'); const path = require('path'); const util = require('util'); +const {log} = require('../../common/logging'); const exec = util.promisify(childProcess.exec); -const {red, cyan} = colors; +const {cyan, red} = colors; -// custom-config.json overlays the active config. It is not part of checked-in source (.gitignore'd). See: -// https://github.com/ampproject/amphtml/blob/master/build-system/global-configs/README.md#custom-configjson +// custom-config.json overlays the active config. It is not part of checked-in +// source (.gitignore'd). See: +// https://github.com/ampproject/amphtml/blob/main/build-system/global-configs/README.md#custom-configjson const customConfigFile = 'build-system/global-configs/custom-config.json'; /** @@ -59,24 +60,16 @@ function sanityCheck(str) { /** * @param {string} filename File containing the config - * @param {string=} opt_localBranch Whether to use the local branch version + * @param {boolean=} opt_localBranch Whether to use the local branch version * @param {string=} opt_branch If not the local branch, which branch to use - * @return {!Promise} + * @return {!Promise} */ -function checkoutBranchConfigs(filename, opt_localBranch, opt_branch) { +async function fetchConfigFromBranch_(filename, opt_localBranch, opt_branch) { if (opt_localBranch) { - return Promise.resolve(); + return fs.promises.readFile(filename, 'utf8'); } - const branch = opt_branch || 'origin/master'; - // One bad path here will fail the whole operation. - return exec(`git checkout ${branch} ${filename}`).catch(function (e) { - // This means the files don't exist in master. Assume that it exists - // in the current branch. - if (/did not match any file/.test(e.message)) { - return; - } - throw e; - }); + const branch = opt_branch || 'origin/main'; + return (await exec(`git show ${branch}:${filename}`)).stdout; } /** @@ -95,13 +88,13 @@ function prependConfig(configString, fileString) { * @param {string} filename Destination filename * @param {string} fileString String to write * @param {boolean=} opt_dryrun If true, print the contents without writing them - * @return {!Promise} + * @return {!Promise} */ -function writeTarget(filename, fileString, opt_dryrun) { +async function writeTarget_(filename, fileString, opt_dryrun) { if (opt_dryrun) { log(cyan(`overwriting: ${filename}`)); log(fileString); - return Promise.resolve(); + return; } return fs.promises.writeFile(filename, fileString); } @@ -126,76 +119,79 @@ function valueOrDefault(value, defaultValue) { * @param {boolean=} opt_localBranch Whether to use the local branch version * @param {string=} opt_branch If not the local branch, which branch to use * @param {boolean=} opt_fortesting Whether to force getMode().test to be true - * @return {!Promise} + * @param {boolean=} opt_derandomize Whether to remove experiment randomization + * @return {!Promise} */ -function applyConfig( +async function applyConfig( config, target, filename, opt_localDev, opt_localBranch, opt_branch, - opt_fortesting + opt_fortesting, + opt_derandomize ) { - return checkoutBranchConfigs(filename, opt_localBranch, opt_branch) - .then(() => { - return Promise.all([ - fs.promises.readFile(filename, 'utf8'), - fs.promises.readFile(target, 'utf8'), - fs.promises.readFile(customConfigFile, 'utf8').catch(() => {}), - ]); - }) - .then(([configString, targetString, overlayString]) => { - let configJson; - try { - configJson = JSON.parse(configString); - } catch (e) { - log(red(`Error parsing config file: ${filename}`)); - throw e; - } - if (overlayString) { - try { - const overlayJson = JSON.parse(overlayString); - Object.assign(configJson, overlayJson); - log('Overlaid config with', cyan(path.basename(customConfigFile))); - } catch (e) { - log( - red('Could not apply overlay from'), - cyan(path.basename(customConfigFile)) - ); - } - } - if (opt_localDev) { - configJson = enableLocalDev(config, target, configJson); - } - if (opt_fortesting) { - configJson = {test: true, ...configJson}; - } - configString = JSON.stringify(configJson); - return prependConfig(configString, targetString); - }) - .then((fileString) => { - sanityCheck(fileString); - return writeTarget(target, fileString, argv.dryrun); - }) - .then(() => { - const details = - '(' + - cyan(config) + - (opt_localDev ? ', ' + cyan('localDev') : '') + - (opt_fortesting ? ', ' + cyan('test') : '') + - ')'; - log('Applied AMP config', details, 'to', cyan(path.basename(target))); - }); + const configString = await fetchConfigFromBranch_( + filename, + opt_localBranch, + opt_branch + ); + const [targetString, overlayString] = await Promise.all([ + fs.promises.readFile(target, 'utf8'), + fs.promises.readFile(customConfigFile, 'utf8').catch(() => {}), + ]); + + let configJson; + try { + configJson = JSON.parse(configString); + } catch (e) { + log(red(`Error parsing config file: ${filename}`)); + throw e; + } + if (overlayString) { + try { + const overlayJson = JSON.parse(overlayString); + Object.assign(configJson, overlayJson); + log('Overlaid config with', cyan(path.basename(customConfigFile))); + } catch (e) { + log( + red('Could not apply overlay from'), + cyan(path.basename(customConfigFile)) + ); + } + } + if (opt_localDev) { + configJson = enableLocalDev_(target, configJson); + } + if (opt_fortesting) { + configJson = {test: true, ...configJson}; + } + if (opt_derandomize) { + configJson = derandomize_(target, configJson); + } + const fileString = await prependConfig( + JSON.stringify(configJson), + targetString + ); + sanityCheck(fileString); + await writeTarget_(target, fileString, argv.dryrun); + const details = + '(' + + cyan(config) + + (opt_localDev ? ', ' + cyan('localDev') : '') + + (opt_fortesting ? ', ' + cyan('test') : '') + + (opt_derandomize ? ', ' + cyan('derandomized') : '') + + ')'; + log('Applied AMP config', details, 'to', cyan(path.basename(target))); } /** - * @param {string} config Prod or canary * @param {string} target File containing the AMP runtime (amp.js or v0.js) - * @param {string} configJson The json object in which to enable local dev - * @return {string} + * @param {!JSON} configJson The json object in which to enable local dev + * @return {!JSON} */ -function enableLocalDev(config, target, configJson) { +function enableLocalDev_(target, configJson) { let LOCAL_DEV_AMP_CONFIG = {localDev: true}; const TESTING_HOST = process.env.AMP_TESTING_HOST; if (typeof TESTING_HOST == 'string') { @@ -222,39 +218,49 @@ function enableLocalDev(config, target, configJson) { } /** - * @param {string} target Target file from which to remove the AMP config - * @return {!Promise} + * @param {string} target File containing the AMP runtime (amp.js or v0.js) + * @param {!JSON} configJson The json object in which to enable local dev + * @return {!JSON} */ -function removeConfig(target) { - return fs.promises.readFile(target).then((file) => { - let contents = file.toString(); - if (numConfigs(contents) == 0) { - return Promise.resolve(); +function derandomize_(target, configJson) { + for (const [key, value] of Object.entries(configJson)) { + if (typeof value == 'number') { + configJson[key] = Math.round(value); } - sanityCheck(contents); - const config = /self\.AMP_CONFIG\|\|\(self\.AMP_CONFIG=.*?\/\*AMP_CONFIG\*\//; - contents = contents.replace(config, ''); - return writeTarget(target, contents, argv.dryrun).then(() => { - log('Removed existing config from', cyan(target)); - }); - }); + } + log('Derandomized experiements in', cyan(target)); + return configJson; } -async function prependGlobal() { - const TESTING_HOST = process.env.AMP_TESTING_HOST; - const target = argv.target || TESTING_HOST; - - if (!target) { - log(red('Missing --target.')); +/** + * @param {string} target Target file from which to remove the AMP config + * @return {!Promise} + */ +async function removeConfig(target) { + const file = await fs.promises.readFile(target); + let contents = file.toString(); + if (numConfigs(contents) == 0) { return; } + sanityCheck(contents); + const config = /self\.AMP_CONFIG\|\|\(self\.AMP_CONFIG=.*?\/\*AMP_CONFIG\*\//; + contents = contents.replace(config, ''); + await writeTarget_(target, contents, argv.dryrun); + log('Removed existing config from', cyan(target)); +} - if (argv.remove) { - return removeConfig(target); +/** + * @return {Promise} + */ +async function prependGlobal() { + if (!argv.target) { + log(red('Missing --target.')); + return; } + const targets = argv.target.split(','); - if (!(argv.prod || argv.canary)) { - log(red('One of --prod or --canary should be provided.')); + if (Boolean(argv.prod) == Boolean(argv.canary)) { + log(red('Exactly one of --prod or --canary should be provided.')); return; } @@ -273,48 +279,48 @@ async function prependGlobal() { 'build-system/global-configs/prod-config.json' ); } - return removeConfig(target).then(() => { - return applyConfig( - config, - target, - filename, - argv.local_dev, - argv.local_branch, - argv.branch, - argv.fortesting - ); - }); + await Promise.all([...targets.map(removeConfig)]); + await Promise.all([ + ...targets.map((target) => + applyConfig( + config, + target, + filename, + argv.local_dev, + argv.local_branch, + argv.branch, + argv.fortesting, + argv.derandomize + ) + ), + ]); } module.exports = { applyConfig, - checkoutBranchConfigs, numConfigs, prependConfig, prependGlobal, removeConfig, sanityCheck, valueOrDefault, - writeTarget, }; prependGlobal.description = 'Prepends a json config to a target file'; prependGlobal.flags = { - 'target': ' The file to prepend the json config to.', + 'target': 'Comma separated list of files to prepend the json config to.', 'canary': - ' Prepend the default canary config. ' + + 'Prepend the default canary config. ' + 'Takes in an optional value for a custom canary config source.', 'prod': - ' Prepend the default prod config. ' + + 'Prepend the default prod config. ' + 'Takes in an optional value for a custom prod config source.', - 'local_dev': ' Enables runtime to be used for local development.', + 'local_dev': 'Enables runtime to be used for local development.', 'branch': - ' Switch to a git branch to get config source from. ' + - 'Uses master by default.', + 'Get config source from the given branch. Uses the main branch by default.', 'local_branch': - " Don't switch branches and use the config from the local branch.", - 'fortesting': ' Force the config to return true for getMode().test', - 'remove': - ' Removes previously prepended json config from the target ' + - 'file (if present).', + "Don't switch branches and use the config from the local branch.", + 'fortesting': 'Force the config to return true for getMode().test', + 'derandomize': + 'Rounds all experiment percentages to 0 or 1, whichever is closest.', }; diff --git a/build-system/tasks/prepend-global/prepend-global.test.js b/build-system/tasks/prepend-global/prepend-global.test.js new file mode 100644 index 0000000000000..bcdb60acf37a2 --- /dev/null +++ b/build-system/tasks/prepend-global/prepend-global.test.js @@ -0,0 +1,55 @@ +/** + * Copyright 2016 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. + */ +'use strict'; + +const m = require('.'); +const test = require('ava'); + +test('sync - prepends global config', (t) => { + t.plan(1); + const res = m.prependConfig('{"hello":"world"}', 'var x = 1 + 1;'); + t.is( + res, + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"});' + + '/*AMP_CONFIG*/var x = 1 + 1;' + ); +}); + +test('sync - valueOrDefault', (t) => { + t.plan(2); + let res = m.valueOrDefault(true, 'hello'); + t.is(res, 'hello'); + res = m.valueOrDefault('world', 'hello'); + t.is(res, 'world'); +}); + +test('sync - sanityCheck', (t) => { + t.plan(3); + const badStr = + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'var x = 1 + 1;'; + const badStr2 = 'var x = 1 + 1;'; + const goodStr = + 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + + '/*AMP_CONFIG*/' + + 'var x = 1 + 1;'; + t.false(m.numConfigs(badStr) == 1); + t.true(m.numConfigs(goodStr) == 1); + t.false(m.numConfigs(badStr2) == 1); +}); diff --git a/build-system/tasks/prepend-global/test.js b/build-system/tasks/prepend-global/test.js deleted file mode 100644 index 9146e7328323c..0000000000000 --- a/build-system/tasks/prepend-global/test.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2016 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. - */ -'use strict'; - -const m = require('./'); -const test = require('ava'); - -test('sync - prepends global config', (t) => { - t.plan(1); - const res = m.prependConfig('{"hello":"world"}', 'var x = 1 + 1;'); - t.is( - res, - 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"});' + - '/*AMP_CONFIG*/var x = 1 + 1;' - ); -}); - -test('sync - valueOrDefault', (t) => { - t.plan(2); - let res = m.valueOrDefault(true, 'hello'); - t.is(res, 'hello'); - res = m.valueOrDefault('world', 'hello'); - t.is(res, 'world'); -}); - -test('sync - sanityCheck', (t) => { - t.plan(3); - const badStr = - 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'var x = 1 + 1;'; - const badStr2 = 'var x = 1 + 1;'; - const goodStr = - 'self.AMP_CONFIG||(self.AMP_CONFIG={"hello":"world"})' + - '/*AMP_CONFIG*/' + - 'var x = 1 + 1;'; - t.false(m.numConfigs(badStr) == 1); - t.true(m.numConfigs(goodStr) == 1); - t.false(m.numConfigs(badStr2) == 1); -}); diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js deleted file mode 100644 index c081a31ffba54..0000000000000 --- a/build-system/tasks/presubmit-checks.js +++ /dev/null @@ -1,1455 +0,0 @@ -/** - * Copyright 2015 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. - */ -'use strict'; - -const colors = require('ansi-colors'); -const gulp = require('gulp'); -const log = require('fancy-log'); -const path = require('path'); -const srcGlobs = require('../test-configs/config').presubmitGlobs; -const through2 = require('through2'); - -const dedicatedCopyrightNoteSources = /(\.js|\.css|\.go)$/; - -const requiresReviewPrivacy = - 'Usage of this API requires dedicated review due to ' + - 'being privacy sensitive. Please file an issue asking for permission' + - ' to use if you have not yet done so.'; - -const privateServiceFactory = - 'This service should only be installed in ' + - 'the allowlisted files. Other modules should use a public function ' + - 'typically called serviceNameFor.'; - -const shouldNeverBeUsed = - 'Usage of this API is not allowed - only for internal purposes.'; - -const backwardCompat = - 'This method must not be called. It is only retained ' + - 'for backward compatibility during rollout.'; - -const realiasGetMode = - 'Do not re-alias getMode or its return so it can be ' + - 'DCE\'d. Use explicitly like "getMode().localDev" instead.'; - -// Terms that must not appear in our source files. -const forbiddenTerms = { - 'DO NOT SUBMIT': '', - 'whitelist|white-list': { - message: 'Please use the term allowlist instead', - }, - 'blacklist|black-list': { - message: 'Please use the term denylist instead', - }, - 'grandfather|grandfathered': { - message: 'Please use the term legacy instead', - }, - // TODO(dvoytenko, #8464): cleanup allowlist. - '(^-amp-|\\W-amp-)': { - message: 'Switch to new internal class form', - allowlist: [ - 'build-system/server/amp4test.js', - 'build-system/server/app-index/boilerplate.js', - 'build-system/server/variable-substitution.js', - 'build-system/tasks/extension-generator/index.js', - 'build-system/tasks/storybook/amp-env/decorator.js', - 'css/ampdoc.css', - 'css/ampshared.css', - 'extensions/amp-pinterest/0.1/amp-pinterest.css', - 'extensions/amp-pinterest/0.1/follow-button.js', - 'extensions/amp-pinterest/0.1/pin-widget.js', - 'extensions/amp-pinterest/0.1/save-button.js', - 'validator/engine/validator_test.js', - ], - }, - '(^i-amp-|\\Wi-amp-)': { - message: 'Switch to new internal ID form', - allowlist: [ - 'build-system/tasks/create-golden-css/css/main.css', - 'build-system/tasks/extension-generator/index.js', - 'build-system/tasks/storybook/amp-env/decorator.js', - 'css/ampdoc.css', - 'css/ampshared.css', - ], - }, - 'describe\\.only': '', - 'describes.*\\.only': '', - 'dev\\(\\)\\.assert\\(': 'Use the devAssert function instead.', - '[^.]user\\(\\)\\.assert\\(': 'Use the userAssert function instead.', - 'it\\.only': '', - 'Math.random[^;()]*=': 'Use Sinon to stub!!!', - 'gulp-util': { - message: - '`gulp-util` will be deprecated soon. See ' + - 'https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5 ' + - 'for a list of alternatives.', - }, - 'sinon\\.(spy|stub|mock)\\(': { - message: 'Use a sandbox instead to avoid repeated `#restore` calls', - }, - '(\\w*([sS]py|[sS]tub|[mM]ock|clock).restore)': { - message: 'Use a sandbox instead to avoid repeated `#restore` calls', - }, - 'sinon\\.useFake\\w+': { - message: 'Use a sandbox instead to avoid repeated `#restore` calls', - }, - 'sandbox\\.(spy|stub|mock)\\([^,\\s]*[iI]?frame[^,\\s]*,': { - message: - 'Do NOT stub on a cross domain iframe! #5359\n' + - ' If this is same domain, mark /*OK*/.\n' + - ' If this is cross domain, overwrite the method directly.', - }, - 'console\\.\\w+\\(': { - message: - 'If you run against this, use console/*OK*/.[log|error] to ' + - 'allowlist a legit case.', - allowlist: [ - 'build-system/common/check-package-manager.js', - 'build-system/pr-check/build.js', - 'build-system/pr-check/build-targets.js', - 'build-system/pr-check/checks.js', - 'build-system/pr-check/dist-bundle-size.js', - 'build-system/pr-check/dist-tests.js', - 'build-system/pr-check/module-dist-bundle-size.js', - 'build-system/pr-check/experiment-tests.js', - 'build-system/pr-check/e2e-tests.js', - 'build-system/pr-check/local-tests.js', - 'build-system/pr-check/performance-tests.js', - 'build-system/pr-check/remote-tests.js', - 'build-system/pr-check/utils.js', - 'build-system/pr-check/validator-tests.js', - 'build-system/pr-check/visual-diff-tests.js', - 'build-system/pr-check/yarn-checks.js', - 'build-system/server/app.js', - 'build-system/server/amp4test.js', - 'build-system/tasks/build.js', - 'build-system/tasks/check-exact-versions.js', - 'build-system/tasks/check-owners.js', - 'build-system/tasks/check-types.js', - 'build-system/tasks/dist.js', - 'build-system/tasks/dns-monitor.js', - 'build-system/tasks/generate-runner.js', - 'build-system/tasks/helpers.js', - 'build-system/tasks/prettify.js', - 'build-system/tasks/server-tests.js', - 'src/purifier/noop.js', - 'validator/nodejs/index.js', // NodeJs only. - 'validator/engine/parse-css.js', - 'validator/engine/validator-in-browser.js', - 'validator/engine/validator.js', - ], - checkInTestFolder: true, - }, - '\\bgetModeObject\\(': { - message: realiasGetMode, - allowlist: [ - 'src/mode-object.js', - 'src/iframe-attributes.js', - 'dist.3p/current/integration.js', - ], - }, - '(?:var|let|const) +IS_DEV +=': { - message: - 'IS_DEV local var only allowed in mode.js and ' + - 'dist.3p/current/integration.js', - allowlist: ['src/mode.js', 'dist.3p/current/integration.js'], - }, - '\\.prefetch\\(': { - message: 'Do not use preconnect.prefetch, use preconnect.preload instead.', - }, - 'iframePing': { - message: - 'This is only available in vendor config for temporary workarounds.', - allowlist: [ - 'build-system/server/routes/analytics.js', - 'extensions/amp-analytics/0.1/config.js', - 'extensions/amp-analytics/0.1/requests.js', - ], - }, - // Service factories that should only be installed once. - 'installActionServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/inabox/inabox-services.js', - 'src/service/action-impl.js', - 'src/service/core-services.js', - 'src/service/standard-actions-impl.js', - ], - }, - 'installActionHandler': { - message: privateServiceFactory, - allowlist: [ - 'src/service/action-impl.js', - 'extensions/amp-access/0.1/amp-access.js', - 'extensions/amp-form/0.1/amp-form.js', - 'extensions/amp-viewer-assistance/0.1/amp-viewer-assistance.js', - ], - }, - 'installActivityService': { - message: privateServiceFactory, - allowlist: [ - 'extensions/amp-analytics/0.1/activity-impl.js', - 'extensions/amp-analytics/0.1/amp-analytics.js', - ], - }, - 'cidServiceForDocForTesting': { - message: privateServiceFactory, - allowlist: ['src/service/cid-impl.js'], - }, - 'installCryptoService': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/service/core-services.js', - 'src/service/crypto-impl.js', - ], - }, - 'installDocService': { - message: privateServiceFactory, - allowlist: [ - 'src/amp.js', - 'src/amp-shadow.js', - 'src/inabox/amp-inabox.js', - 'src/service/ampdoc-impl.js', - 'testing/describes.js', - 'testing/iframe.js', - ], - }, - 'installMutatorServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/inabox/inabox-services.js', - 'src/service/core-services.js', - 'src/service/mutator-impl.js', - ], - }, - 'installPerformanceService': { - message: privateServiceFactory, - allowlist: [ - 'src/amp.js', - 'src/amp-shadow.js', - 'src/inabox/amp-inabox.js', - 'src/service/performance-impl.js', - ], - }, - 'installResourcesServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/inabox/inabox-services.js', - 'src/service/core-services.js', - 'src/service/resources-impl.js', - ], - }, - 'installStorageServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/service/core-services.js', - 'src/service/storage-impl.js', - ], - }, - 'installTemplatesService': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/service/core-services.js', - 'src/service/template-impl.js', - ], - }, - 'installUrlReplacementsServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/inabox/inabox-services.js', - 'src/service/core-services.js', - 'src/service/url-replacements-impl.js', - ], - }, - 'installViewerServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/inabox/inabox-services.js', - 'src/service/core-services.js', - 'src/service/viewer-impl.js', - ], - }, - 'installViewportServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/service/core-services.js', - 'src/service/viewport/viewport-impl.js', - ], - }, - 'installVsyncService': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/service/core-services.js', - 'src/service/resources-impl.js', - 'src/service/viewport/viewport-impl.js', - 'src/service/vsync-impl.js', - ], - }, - 'installXhrService': { - message: privateServiceFactory, - allowlist: [ - 'src/runtime.js', - 'src/service/core-services.js', - 'src/service/xhr-impl.js', - ], - }, - 'installPositionObserverServiceForDoc': { - message: privateServiceFactory, - allowlist: [ - // Please keep list alphabetically sorted. - 'extensions/amp-fx-collection/0.1/providers/fx-provider.js', - 'extensions/amp-list/0.1/amp-list.js', - 'extensions/amp-next-page/0.1/next-page-service.js', - 'extensions/amp-next-page/1.0/visibility-observer.js', - 'extensions/amp-position-observer/0.1/amp-position-observer.js', - 'extensions/amp-video-docking/0.1/amp-video-docking.js', - 'src/service/position-observer/position-observer-impl.js', - 'src/service/video-manager-impl.js', - 'src/service/video/autoplay.js', - ], - }, - 'getServiceForDoc': { - message: - 'Synchronous access to element services is unreliable (#22414). ' + - 'Use getServicePromiseForDoc() instead.', - allowlist: [ - // Do not allowlist additional "extensions/*" paths. - // TODO(#22414): Remove paths as they are migrated off of sync API. - 'extensions/amp-analytics/0.1/instrumentation.js', - 'extensions/amp-analytics/0.1/variables.js', - 'extensions/amp-fx-collection/0.1/providers/fx-provider.js', - 'src/chunk.js', - 'src/service.js', - 'src/service/cid-impl.js', - 'src/service/origin-experiments-impl.js', - 'src/services.js', - 'testing/test-helper.js', - ], - }, - 'initLogConstructor|setReportError': { - message: 'Should only be called from JS binary entry files.', - allowlist: [ - '3p/integration.js', - '3p/ampcontext-lib.js', - '3p/iframe-transport-client-lib.js', - '3p/recaptcha.js', - 'ads/alp/install-alp.js', - 'ads/inabox/inabox-host.js', - 'dist.3p/current/integration.js', - 'extensions/amp-access/0.1/amp-login-done.js', - 'extensions/amp-viewer-integration/0.1/examples/amp-viewer-host.js', - 'src/amp-story-player/amp-story-player-manager.js', - 'src/runtime.js', - 'src/log.js', - 'src/web-worker/web-worker.js', - 'tools/experiments/experiments.js', - ], - }, - 'parseUrlWithA': { - message: 'Use parseUrl instead.', - allowlist: [ - 'src/url.js', - 'src/service/navigation.js', - 'src/service/url-impl.js', - 'dist.3p/current/integration.js', - 'src/amp-story-player/amp-story-player-impl.js', - ], - }, - '\\.sendMessage\\(': { - message: 'Usages must be reviewed.', - allowlist: [ - // viewer-impl.sendMessage - 'src/error.js', - 'src/service/navigation.js', - 'src/service/viewer-impl.js', - 'src/service/viewport/viewport-impl.js', - 'src/service/performance-impl.js', - 'src/service/resources-impl.js', - 'extensions/amp-bind/0.1/bind-impl.js', - 'extensions/amp-app-banner/0.1/amp-app-banner.js', - 'extensions/amp-subscriptions/0.1/viewer-subscription-platform.js', - 'extensions/amp-viewer-integration/0.1/highlight-handler.js', - 'extensions/amp-consent/0.1/consent-ui.js', - 'extensions/amp-story/1.0/amp-story-viewer-messaging-handler.js', - - // iframe-messaging-client.sendMessage - '3p/iframe-messaging-client.js', - '3p/ampcontext.js', - '3p/ampcontext-integration.js', - '3p/recaptcha.js', - 'dist.3p/current/integration.js', // includes previous - ], - }, - '\\.sendMessageAwaitResponse\\(': { - message: 'Usages must be reviewed.', - allowlist: [ - 'extensions/amp-access/0.1/login-dialog.js', - 'extensions/amp-access/0.1/signin.js', - 'extensions/amp-story-education/0.1/amp-story-education.js', - 'extensions/amp-subscriptions/0.1/viewer-subscription-platform.js', - 'src/impression.js', - 'src/service/cid-impl.js', - 'src/service/history-impl.js', - 'src/service/storage-impl.js', - 'src/ssr-template-helper.js', - 'src/service/viewer-impl.js', - 'src/service/viewer-cid-api.js', - 'src/utils/xhr-utils.js', - ], - }, - // Privacy sensitive - 'cidForDoc|cidForDocOrNull': { - message: requiresReviewPrivacy, - allowlist: [ - // CID service is not allowed in amp4ads. No usage should there be - // in extensions listed in the amp4ads spec: - // https://amp.dev/documentation/guides-and-tutorials/learn/a4a_spec - 'src/ad-cid.js', - 'src/services.js', - 'src/service/cid-impl.js', - 'src/service/standard-actions-impl.js', - 'src/service/url-replacements-impl.js', - 'extensions/amp-access/0.1/amp-access.js', - 'extensions/amp-subscriptions/0.1/amp-subscriptions.js', - 'extensions/amp-experiment/0.1/variant.js', - 'extensions/amp-experiment/1.0/variant.js', - 'extensions/amp-user-notification/0.1/amp-user-notification.js', - 'extensions/amp-consent/0.1/consent-state-manager.js', - 'extensions/amp-story/1.0/amp-story-reaction.js', - ], - }, - 'getBaseCid': { - message: requiresReviewPrivacy, - allowlist: ['src/service/cid-impl.js', 'src/service/viewer-impl.js'], - }, - 'isTrustedViewer': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-bind/0.1/bind-impl.js', - 'src/error.js', - 'src/utils/xhr-utils.js', - 'src/service/navigation.js', - 'src/service/viewer-impl.js', - 'src/service/viewer-interface.js', - 'src/service/viewer-cid-api.js', - 'src/inabox/inabox-viewer.js', - 'src/service/cid-impl.js', - 'src/impression.js', - 'src/ssr-template-helper.js', - 'extensions/amp-viewer-assistance/0.1/amp-viewer-assistance.js', - ], - }, - 'prerenderSafe': { - message: requiresReviewPrivacy, - allowlist: [ - 'build-system/externs/amp.extern.js', - 'extensions/amp-subscriptions-google/0.1/amp-subscriptions-google.js', - 'src/utils/xhr-utils.js', - ], - }, - 'eval\\(': { - message: shouldNeverBeUsed, - allowlist: ['extension/amp-bind/0.1/test/test-bind-expr.js'], - }, - 'storageForDoc': { - message: - requiresReviewPrivacy + - ' Please refer to spec/amp-localstorage.md for more information on' + - ' the storage service usage.' + - ' Once approved, please also update the spec/amp-localstorage.md to' + - ' include your usage.', - allowlist: [ - // Storage service is not allowed in amp4ads. No usage should there be - // in extensions listed in the amp4ads spec: - // https://amp.dev/documentation/guides-and-tutorials/learn/a4a_spec - 'src/services.js', - 'src/service/cid-impl.js', - 'extensions/amp-ad-network-adsense-impl/0.1/responsive-state.js', - 'extensions/amp-app-banner/0.1/amp-app-banner.js', - 'extensions/amp-consent/0.1/consent-state-manager.js', - 'extensions/amp-user-notification/0.1/amp-user-notification.js', - ], - }, - 'localStorage': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-access/0.1/amp-access-iframe.js', - 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js', - 'extensions/amp-script/0.1/amp-script.js', - 'extensions/amp-story/1.0/history.js', - 'extensions/amp-web-push/0.1/amp-web-push-helper-frame.js', - 'extensions/amp-web-push/0.1/amp-web-push-permission-dialog.js', - 'src/experiments.js', - 'src/service/cid-impl.js', - 'src/service/storage-impl.js', - 'testing/fake-dom.js', - ], - }, - 'sessionStorage': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-access/0.1/amp-access-iframe.js', - 'extensions/amp-accordion/0.1/amp-accordion.js', - 'extensions/amp-script/0.1/amp-script.js', - 'testing/fake-dom.js', - ], - }, - 'indexedDB': { - message: requiresReviewPrivacy, - }, - 'openDatabase': requiresReviewPrivacy, - 'requestFileSystem': requiresReviewPrivacy, - 'webkitRequestFileSystem': requiresReviewPrivacy, - 'getAccessReaderId': { - message: requiresReviewPrivacy, - allowlist: [ - 'build-system/externs/amp.extern.js', - 'extensions/amp-access/0.1/amp-access.js', - 'extensions/amp-access/0.1/access-vars.js', - 'extensions/amp-access-scroll/0.1/scroll-impl.js', - 'extensions/amp-subscriptions/0.1/amp-subscriptions.js', - 'src/service/url-replacements-impl.js', - ], - }, - 'getAuthdataField': { - message: requiresReviewPrivacy, - allowlist: [ - 'build-system/externs/amp.extern.js', - 'extensions/amp-access/0.1/amp-access.js', - 'extensions/amp-access/0.1/access-vars.js', - 'extensions/amp-subscriptions/0.1/amp-subscriptions.js', - 'src/service/url-replacements-impl.js', - ], - }, - 'debugger': '', - // Overridden APIs. - '(doc.*)\\.referrer': { - message: 'Use Viewer.getReferrerUrl() instead.', - allowlist: [ - '3p/integration.js', - 'ads/google/a4a/utils.js', - 'dist.3p/current/integration.js', - 'src/inabox/inabox-viewer.js', - 'src/service/viewer-impl.js', - 'src/error.js', - 'src/window-interface.js', - ], - }, - 'getUnconfirmedReferrerUrl': { - message: 'Use Viewer.getReferrerUrl() instead.', - allowlist: [ - 'extensions/amp-dynamic-css-classes/0.1/amp-dynamic-css-classes.js', - 'src/3p-frame.js', - 'src/iframe-attributes.js', - 'src/service/viewer-impl.js', - 'src/service/viewer-interface.js', - 'src/inabox/inabox-viewer.js', - ], - }, - 'internalListenImplementation': { - message: - 'Use `listen()` in either `event-helper` or `3p-frame-messaging`' + - ', depending on your use case.', - allowlist: [ - 'src/3p-frame-messaging.js', - 'src/event-helper.js', - 'src/event-helper-listen.js', - 'dist.3p/current/integration.js', // includes previous - ], - }, - 'setTimeout.*throw': { - message: 'Use dev.error or user.error instead.', - allowlist: ['src/log.js'], - }, - '(dev|user)\\(\\)\\.(fine|info|warn|error)\\((?!\\s*([A-Z0-9-]+|[\'"`][A-Z0-9-]+[\'"`]))[^,)\n]*': { - // eslint-disable-line max-len - message: - 'Logging message require explicitly `TAG`, or an all uppercase' + - ' string as the first parameter', - allowlist: [ - 'build-system/babel-plugins/babel-plugin-transform-dev-methods/index.js', - ], - }, - '\\.schedulePass\\(': { - message: 'schedulePass is heavy, think twice before using it', - allowlist: ['src/service/mutator-impl.js', 'src/service/resources-impl.js'], - }, - '\\.requireLayout\\(': { - message: - 'requireLayout is restricted b/c it affects non-contained elements', // eslint-disable-line max-len - allowlist: [ - 'extensions/amp-animation/0.1/web-animations.js', - 'extensions/amp-lightbox-gallery/0.1/amp-lightbox-gallery.js', - 'src/service/resources-impl.js', - ], - }, - '\\.updateLayoutPriority\\(': { - message: 'updateLayoutPriority is a restricted API.', - allowlist: [ - 'extensions/amp-a4a/0.1/amp-a4a.js', - 'src/base-element.js', - 'src/service/resources-impl.js', - ], - }, - 'overrideVisibilityState': { - message: 'overrideVisibilityState is a restricted API.', - allowlist: [ - 'src/multidoc-manager.js', - 'src/service/ampdoc-impl.js', - 'src/service/viewer-impl.js', - ], - }, - '\\.scheduleLayoutOrPreload\\(': { - message: 'scheduleLayoutOrPreload is a restricted API.', - allowlist: ['src/service/owners-impl.js', 'src/service/resources-impl.js'], - }, - '(win|Win)(dow)?(\\(\\))?\\.open\\W': { - message: 'Use dom.openWindowDialog', - allowlist: ['src/dom.js'], - }, - '\\.getWin\\(': { - message: backwardCompat, - allowlist: [], - }, - '/\\*\\* @type \\{\\!Element\\} \\*/': { - message: 'Use assertElement instead of casting to !Element.', - allowlist: [ - 'src/log.js', // Has actual implementation of assertElement. - 'dist.3p/current/integration.js', // Includes the previous. - 'src/polyfills/custom-elements.js', - 'ads/google/imaVideo.js', // Required until #22277 is fixed. - '3p/twitter.js', // Runs in a 3p window context, so cannot import log.js. - ], - }, - 'startupChunk\\(': { - message: 'startupChunk( should only be used during startup', - allowlist: [ - 'src/amp.js', - 'src/chunk.js', - 'src/inabox/amp-inabox.js', - 'src/runtime.js', - 'src/custom-element.js', - 'src/service/resources-impl.js', - ], - }, - 'AMP_CONFIG': { - message: - 'Do not access AMP_CONFIG directly. Use isExperimentOn() ' + - 'and getMode() to access config', - allowlist: [ - 'build-system/externs/amp.extern.js', - 'build-system/server/app.js', - 'build-system/tasks/e2e/index.js', - 'build-system/tasks/firebase.js', - 'build-system/tasks/integration.js', - 'build-system/tasks/prepend-global/index.js', - 'build-system/tasks/prepend-global/test.js', - 'build-system/tasks/visual-diff/index.js', - 'build-system/tasks/build.js', - 'build-system/tasks/default-task.js', - 'build-system/tasks/dist.js', - 'build-system/tasks/helpers.js', - 'dist.3p/current/integration.js', - 'src/config.js', - 'src/experiments.js', - 'src/mode.js', - 'src/web-worker/web-worker.js', // Web worker custom error reporter. - 'tools/experiments/experiments.js', - 'build-system/server/amp4test.js', - ], - }, - 'data:image/svg(?!\\+xml;charset=utf-8,)[^,]*,': { - message: - 'SVG data images must use charset=utf-8: ' + - '"data:image/svg+xml;charset=utf-8,..."', - }, - 'new CustomEvent\\(': { - message: 'Use createCustomEvent() helper instead.', - allowlist: ['src/event-helper.js'], - }, - 'new FormData\\(': { - message: - 'Use createFormDataWrapper() instead and call ' + - 'formDataWrapper.getFormData() to get the native FormData object.', - allowlist: ['src/form-data-wrapper.js'], - }, - '([eE]xit|[eE]nter|[cC]ancel|[rR]equest)Full[Ss]creen\\(': { - message: 'Use fullscreenEnter() and fullscreenExit() from dom.js instead.', - allowlist: [ - 'ads/google/imaVideo.js', - 'dist.3p/current/integration.js', - 'src/video-iframe-integration.js', - 'extensions/amp-consent/0.1/amp-consent.js', - 'extensions/amp-consent/0.1/consent-ui.js', - ], - }, - '\\.defer\\(\\)': { - message: 'Promise.defer() is deprecated and should not be used.', - }, - '(dev|user)\\(\\)\\.assert(Element|String|Number)?\\(\\s*([A-Z][A-Z0-9-]*,)': { - // eslint-disable-line max-len - message: 'TAG is not an argument to assert(). Will cause false positives.', - }, - 'eslint no-unused-vars': { - message: 'Use a line-level "no-unused-vars" rule instead.', - allowlist: ['extensions/amp-access/0.1/iframe-api/access-controller.js'], - }, - 'this\\.skip\\(\\)': { - message: - 'Use of `this.skip()` is forbidden in test files. Use ' + - '`this.skipTest()` from within a `before()` block instead. See #17245.', - checkInTestFolder: true, - allowlist: ['test/_init_tests.js'], - }, - '[^\\.]makeBodyVisible\\(': { - message: - 'This is a protected function. If you are calling this to show ' + - 'body after an error please use `makeBodyVisibleRecovery`', - allowlist: [ - 'src/amp.js', - 'src/amp-shadow.js', - 'src/style-installer.js', - 'src/inabox/amp-inabox.js', - ], - }, - 'isBuildRenderBlocking': { - message: - 'This is a protected API. Please only override it the element is ' + - 'render blocking', - allowlist: [ - 'src/service/resources-impl.js', - 'src/service/resource.js', - 'src/custom-element.js', - 'src/base-element.js', - 'extensions/amp-experiment/0.1/amp-experiment.js', - 'extensions/amp-experiment/1.0/amp-experiment.js', - ], - }, - '^describe[\\.|\\(|$]': { - message: - 'Top-level "describe" blocks in test files have been deprecated. ' + - 'Use "describes.{realWin|sandboxed|fakeWin|integration}".', - allowlist: [ - // Non test files. These can remain. - 'build-system/server/app-index/test/test-amphtml-helpers.js', - 'build-system/server/app-index/test/test-file-list.js', - 'build-system/server/app-index/test/test-html.js', - 'build-system/server/app-index/test/test-self.js', - 'build-system/server/app-index/test/test-template.js', - 'build-system/server/app-index/test/test.js', - 'test/_init_tests.js', - 'test/e2e/test-controller-promise.js', - 'test/e2e/test-expect.js', - 'validator/engine/amp4ads-parse-css_test.js', - 'validator/engine/htmlparser_test.js', - 'validator/engine/keyframes-parse-css_test.js', - 'validator/engine/parse-css_test.js', - 'validator/engine/parse-srcset_test.js', - 'validator/engine/parse-url_test.js', - 'validator/engine/validator_test.js', - 'validator/gulpjs/test/validate.js', - // Test files. TODO(#24144): Fix these and remove from the allowlist. - 'ads/google/a4a/shared/test/test-content-recommendation.js', - 'ads/google/a4a/shared/test/test-url-builder.js', - 'ads/google/a4a/test/test-line-delimited-response-handler.js', - 'ads/google/a4a/test/test-traffic-experiments.js', - 'ads/google/a4a/test/test-utils.js', - 'ads/google/test/test-utils.js', - 'extensions/amp-a4a/0.1/test/test-a4a-integration.js', - 'extensions/amp-a4a/0.1/test/test-a4a-var-source.js', - 'extensions/amp-a4a/0.1/test/test-amp-a4a.js', - 'extensions/amp-a4a/0.1/test/test-amp-ad-utils.js', - 'extensions/amp-a4a/0.1/test/test-callout-vendors.js', - 'extensions/amp-a4a/0.1/test/test-refresh.js', - 'extensions/amp-access/0.1/test/test-access-expr.js', - 'extensions/amp-access/0.1/test/test-amp-login-done-dialog.js', - 'extensions/amp-access/0.1/test/test-jwt.js', - 'extensions/amp-ad-exit/0.1/test/filters/test-click-delay.js', - 'extensions/amp-ad/0.1/test/test-amp-ad-3p-impl.js', - 'extensions/amp-ad/0.1/test/test-amp-ad-custom.js', - 'extensions/amp-ad/0.1/test/test-amp-ad-xorigin-iframe-handler.js', - 'extensions/amp-addthis/0.1/test/addthis-utils/test-fragment.js', - 'extensions/amp-addthis/0.1/test/addthis-utils/test-rot13.js', - 'extensions/amp-analytics/0.1/test/test-crc32.js', - 'extensions/amp-analytics/0.1/test/test-iframe-transport-client.js', - 'extensions/amp-analytics/0.1/test/test-linker-manager.js', - 'extensions/amp-analytics/0.1/test/test-linker-reader.js', - 'extensions/amp-analytics/0.1/test/test-linker.js', - 'extensions/amp-analytics/0.1/test/test-transport-serializers.js', - 'extensions/amp-analytics/0.1/test/test-vendors.js', - 'extensions/amp-animation/0.1/test/test-css-expr.js', - 'extensions/amp-auto-ads/0.1/test/test-attributes.js', - 'extensions/amp-base-carousel/0.1/test/test-responsive-attributes.js', - 'extensions/amp-bind/0.1/test/test-bind-evaluator.js', - 'extensions/amp-bind/0.1/test/test-bind-expression.js', - 'extensions/amp-bind/0.1/test/test-bind-validator.js', - 'extensions/amp-dynamic-css-classes/0.1/test/test-dynamic-classes.js', - 'extensions/amp-form/0.1/test/test-form-submit-service.js', - 'extensions/amp-fx-collection/0.1/test/integration/test-amp-fx-fly-in.js', - 'extensions/amp-lightbox-gallery/0.1/test/integration/test-amp-lightbox-gallery.js', - 'extensions/amp-list/0.1/test/integration/test-amp-list.js', - 'extensions/amp-live-list/0.1/test/test-poller.js', - 'extensions/amp-next-page/0.1/test/test-config.js', - 'extensions/amp-script/0.1/test/unit/test-amp-script.js', - 'extensions/amp-sidebar/0.1/test/test-toolbar.js', - 'extensions/amp-truncate-text/0.1/test/test-binary-search.js', - 'extensions/amp-viewer-integration/0.1/test/test-findtext.js', - 'test/integration/test-3p-nameframe.js', - 'test/integration/test-amp-ad-3p.js', - 'test/integration/test-amp-ad-fake.js', - 'test/integration/test-amp-analytics.js', - 'test/integration/test-amp-pixel.js', - 'test/integration/test-amp-skimlinks.js', - 'test/integration/test-amphtml-ads.js', - 'test/integration/test-boilerplates.js', - 'test/integration/test-configuration.js', - 'test/integration/test-video-manager.js', - 'test/integration/test-video-players.js', - 'test/unit/3p/test-3p-messaging.js', - 'test/unit/3p/test-recaptcha.js', - 'test/unit/ads/test-unruly.js', - 'test/unit/test-3p-environment.js', - 'test/unit/test-3p.js', - 'test/unit/test-action.js', - 'test/unit/test-activity.js', - 'test/unit/test-ad-helper.js', - 'test/unit/test-ads-config.js', - 'test/unit/test-alp-handler.js', - 'test/unit/test-amp-context.js', - 'test/unit/test-amp-img.js', - 'test/unit/test-amp-inabox.js', - 'test/unit/test-animation.js', - 'test/unit/test-batched-json.js', - 'test/unit/test-chunk.js', - 'test/unit/test-cid.js', - 'test/unit/test-css.js', - 'test/unit/test-curve.js', - 'test/unit/test-describes.js', - 'test/unit/test-document-ready.js', - 'test/unit/test-element-service.js', - 'test/unit/test-error.js', - 'test/unit/test-event-helper.js', - 'test/unit/test-experiments.js', - 'test/unit/test-exponential-backoff.js', - 'test/unit/test-finite-state-machine.js', - 'test/unit/test-focus-history.js', - 'test/unit/test-gesture-recognizers.js', - 'test/unit/test-gesture.js', - 'test/unit/test-get-html.js', - 'test/unit/test-ie-media-bug.js', - 'test/unit/test-impression.js', - 'test/unit/test-input.js', - 'test/unit/test-integration.js', - 'test/unit/test-intersection-observer-polyfill.js', - 'test/unit/test-intersection-observer.js', - 'test/unit/test-json.js', - 'test/unit/test-layout-rect.js', - 'test/unit/test-layout.js', - 'test/unit/test-log.js', - 'test/unit/test-mode.js', - 'test/unit/test-motion.js', - 'test/unit/test-mustache.js', - 'test/unit/test-mutator.js', - 'test/unit/test-object.js', - 'test/unit/test-observable.js', - 'test/unit/test-pass.js', - 'test/unit/test-platform.js', - 'test/unit/test-polyfill-document-contains.js', - 'test/unit/test-polyfill-math-sign.js', - 'test/unit/test-polyfill-object-assign.js', - 'test/unit/test-polyfill-object-values.js', - 'test/unit/test-preconnect.js', - 'test/unit/test-pull-to-refresh.js', - 'test/unit/test-purifier.js', - 'test/unit/test-render-delaying-services.js', - 'test/unit/test-resource.js', - 'test/unit/test-resources.js', - 'test/unit/test-sanitizer.js', - 'test/unit/test-service.js', - 'test/unit/test-size-list.js', - 'test/unit/test-srcset.js', - 'test/unit/test-static-template.js', - 'test/unit/test-string.js', - 'test/unit/test-style-installer.js', - 'test/unit/test-style.js', - 'test/unit/test-task-queue.js', - 'test/unit/test-transition.js', - 'test/unit/test-types.js', - 'test/unit/test-url-rewrite.js', - 'test/unit/test-url.js', - 'test/unit/test-viewport.js', - 'test/unit/test-web-components.js', - 'test/unit/utils/test-array.js', - 'test/unit/utils/test-base64.js', - 'test/unit/utils/test-bytes.js', - 'test/unit/utils/test-lru-cache.js', - 'test/unit/utils/test-pem.js', - 'test/unit/utils/test-priority-queue.js', - 'test/unit/utils/test-rate-limit.js', - 'test/unit/web-worker/test-amp-worker.js', - ], - checkInTestFolder: true, - }, -}; - -const ThreePTermsMessage = - 'The 3p bootstrap iframe has no polyfills loaded' + - ' and can thus not use most modern web APIs.'; - -const forbidden3pTerms = { - // We need to forbid promise usage because we don't have our own polyfill - // available. This allowlisting of callNext is a major hack to allow one - // usage in babel's external helpers that is in a code path that we do - // not use. - '\\.then\\((?!callNext)': ThreePTermsMessage, -}; - -const bannedTermsHelpString = - 'Please review viewport service for helper ' + - 'methods or mark with `/*OK*/` or `/*REVIEW*/` and consult the AMP team. ' + - 'Most of the forbidden property/method access banned on the ' + - '`forbiddenTermsSrcInclusive` object can be found in ' + - '[What forces layout / reflow gist by Paul Irish]' + - '(https://gist.github.com/paulirish/5d52fb081b3570c81e3a). ' + - 'These properties/methods when read/used require the browser ' + - 'to have the up-to-date value to return which might possibly be an ' + - 'expensive computation and could also be triggered multiple times ' + - 'if we are not careful. Please mark the call with ' + - '`object./*OK*/property` if you explicitly need to read or update the ' + - 'forbidden property/method or mark it with `object./*REVIEW*/property` ' + - 'if you are unsure and so that it stands out in code reviews.'; - -const forbiddenTermsSrcInclusive = { - '\\.innerHTML(?!_)': bannedTermsHelpString, - '\\.outerHTML(?!_)': bannedTermsHelpString, - '\\.offsetLeft(?!_)': bannedTermsHelpString, - '\\.offsetTop(?!_)': bannedTermsHelpString, - '\\.offsetWidth(?!_)': bannedTermsHelpString, - '\\.offsetHeight(?!_)': bannedTermsHelpString, - '\\.offsetParent(?!_)': bannedTermsHelpString, - '\\.clientLeft(?!_)(?!_)': bannedTermsHelpString, - '\\.clientTop(?!_)': bannedTermsHelpString, - '\\.clientWidth(?!_)': bannedTermsHelpString, - '\\.clientHeight(?!_)': bannedTermsHelpString, - '\\.scrollWidth(?!_)': 'please use `getScrollWidth()` from viewport', - '\\.scrollHeight(?!_)': bannedTermsHelpString, - '\\.scrollTop(?!_)': bannedTermsHelpString, - '\\.scrollLeft(?!_)': bannedTermsHelpString, - '\\.computedRole(?!_)': bannedTermsHelpString, - '\\.computedName(?!_)': bannedTermsHelpString, - '\\.innerText(?!_)': bannedTermsHelpString, - '\\.scrollX(?!_)': bannedTermsHelpString, - '\\.scrollY(?!_)': bannedTermsHelpString, - '\\.pageXOffset(?!_)': bannedTermsHelpString, - '\\.pageYOffset(?!_)': bannedTermsHelpString, - '\\.innerWidth(?!_)': bannedTermsHelpString, - '\\.innerHeight(?!_)': bannedTermsHelpString, - '\\.scrollingElement(?!_)': bannedTermsHelpString, - '\\.computeCTM(?!_)': bannedTermsHelpString, - // Functions - '\\.applySize\\(': bannedTermsHelpString, - '\\.attemptChangeHeight\\(0\\)': 'please consider using `attemptCollapse()`', - '\\.collapse\\(': bannedTermsHelpString, - '\\.expand\\(': bannedTermsHelpString, - '\\.focus\\(': bannedTermsHelpString, - '\\.getBBox\\(': bannedTermsHelpString, - '\\.getBoundingClientRect\\(': bannedTermsHelpString, - '\\.getClientRects\\(': bannedTermsHelpString, - '\\.getMatchedCSSRules\\(': bannedTermsHelpString, - '\\.scrollBy\\(': bannedTermsHelpString, - '\\.scrollIntoView\\(': bannedTermsHelpString, - '\\.scrollIntoViewIfNeeded\\(': bannedTermsHelpString, - '\\.scrollTo\\(': bannedTermsHelpString, - '\\.webkitConvertPointFromNodeToPage\\(': bannedTermsHelpString, - '\\.webkitConvertPointFromPageToNode\\(': bannedTermsHelpString, - '\\.scheduleUnlayout\\(': bannedTermsHelpString, - '\\.postMessage\\(': { - message: bannedTermsHelpString, - allowlist: [ - 'extensions/amp-install-serviceworker/0.1/amp-install-serviceworker.js', - ], - }, - 'getComputedStyle\\(': { - message: - 'Due to various bugs in Firefox, you must use the computedStyle ' + - 'helper in style.js.', - allowlist: ['src/style.js', 'dist.3p/current/integration.js'], - }, - 'decodeURIComponent\\(': { - message: - 'decodeURIComponent throws for malformed URL components. Please ' + - 'use tryDecodeUriComponent from src/url.js', - allowlist: [ - '3p/integration.js', - 'dist.3p/current/integration.js', - 'examples/pwa/pwa.js', - 'validator/engine/parse-url.js', - 'validator/engine/validator.js', - 'validator/webui/webui.js', - 'extensions/amp-pinterest/0.1/util.js', - 'src/url.js', - 'src/url-try-decode-uri-component.js', - 'src/utils/bytes.js', - ], - }, - 'Text(Encoder|Decoder)\\(': { - message: - 'TextEncoder/TextDecoder is not supported in all browsers.' + - ' Please use UTF8 utilities from src/bytes.js', - allowlist: [ - 'ads/google/a4a/line-delimited-response-handler.js', - 'examples/pwa/pwa.js', - 'src/utils/bytes.js', - ], - }, - 'contentHeightChanged': { - message: bannedTermsHelpString, - allowlist: [ - 'src/inabox/inabox-viewport.js', - 'src/service/resources-impl.js', - 'src/service/viewport/viewport-binding-def.js', - 'src/service/viewport/viewport-binding-ios-embed-wrapper.js', - 'src/service/viewport/viewport-binding-natural.js', - 'src/service/viewport/viewport-impl.js', - 'src/service/viewport/viewport-interface.js', - ], - }, - 'preloadExtension': { - message: bannedTermsHelpString, - allowlist: [ - 'src/element-stub.js', - 'src/friendly-iframe-embed.js', - 'src/polyfillstub/intersection-observer-stub.js', - 'src/runtime.js', - 'src/service/extensions-impl.js', - 'src/service/lightbox-manager-discovery.js', - 'src/service/crypto-impl.js', - 'src/shadow-embed.js', - 'src/analytics.js', - 'src/extension-analytics.js', - 'src/services.js', - 'extensions/amp-ad/0.1/amp-ad.js', - 'extensions/amp-a4a/0.1/amp-a4a.js', - 'extensions/amp-a4a/0.1/template-validator.js', - 'extensions/amp-ad-network-adsense-impl/0.1/amp-ad-network-adsense-impl.js', // eslint-disable-line max-len - 'extensions/amp-ad-network-doubleclick-impl/0.1/amp-ad-network-doubleclick-impl.js', // eslint-disable-line max-len - 'extensions/amp-lightbox-gallery/0.1/amp-lightbox-gallery.js', - ], - }, - 'loadElementClass': { - message: bannedTermsHelpString, - allowlist: [ - 'src/runtime.js', - 'src/service/extensions-impl.js', - 'extensions/amp-ad/0.1/amp-ad.js', - 'extensions/amp-a4a/0.1/amp-a4a.js', - 'extensions/amp-auto-ads/0.1/amp-auto-ads.js', - 'extensions/amp-auto-ads/0.1/anchor-ad-strategy.js', - ], - }, - 'reject\\(\\)': { - message: - 'Always supply a reason in rejections. ' + - 'error.cancellation() may be applicable.', - }, - '[^.]loadPromise': { - message: 'Most users should use BaseElement…loadPromise.', - allowlist: [ - 'src/base-element.js', - 'src/event-helper.js', - 'src/friendly-iframe-embed.js', - 'src/service/performance-impl.js', - 'src/service/resources-impl.js', - 'src/service/url-replacements-impl.js', - 'src/service/variable-source.js', - 'src/validator-integration.js', - 'extensions/amp-ad/0.1/amp-ad-xorigin-iframe-handler.js', - 'extensions/amp-image-lightbox/0.1/amp-image-lightbox.js', - 'extensions/amp-analytics/0.1/transport.js', - 'extensions/amp-web-push/0.1/iframehost.js', - 'extensions/amp-recaptcha-input/0.1/amp-recaptcha-service.js', - 'dist.3p/current/integration.js', - ], - }, - '\\.getTime\\(\\)': { - message: 'Unless you do weird date math (allowlist), use Date.now().', - allowlist: [ - 'extensions/amp-timeago/0.1/amp-timeago.js', - 'extensions/amp-timeago/1.0/timeago.js', - ], - }, - '\\.expandStringSync\\(': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-form/0.1/amp-form.js', - 'src/service/url-replacements-impl.js', - ], - }, - '\\.expandStringAsync\\(': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-form/0.1/amp-form.js', - 'src/service/url-replacements-impl.js', - 'extensions/amp-analytics/0.1/config.js', - 'extensions/amp-analytics/0.1/cookie-writer.js', - 'extensions/amp-analytics/0.1/requests.js', - 'extensions/amp-analytics/0.1/variables.js', - ], - }, - '\\.expandInputValueSync\\(': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-form/0.1/amp-form.js', - 'src/service/url-replacements-impl.js', - ], - }, - '\\.expandInputValueAsync\\(': { - message: requiresReviewPrivacy, - allowlist: [ - 'extensions/amp-form/0.1/amp-form.js', - 'src/service/url-replacements-impl.js', - ], - }, - '\\.setNonBoolean\\(': { - message: requiresReviewPrivacy, - allowlist: [ - 'src/service/storage-impl.js', - 'extensions/amp-consent/0.1/consent-state-manager.js', - ], - }, - '(cdn|3p)\\.ampproject\\.': { - message: - 'The CDN domain should typically not be hardcoded in source ' + - 'code. Use a property of urls from src/config.js instead.', - allowlist: [ - 'ads/_a4a-config.js', - 'build-system/server/amp4test.js', - 'build-system/server/app-index/amphtml-helpers.js', - 'build-system/server/app-utils.js', - 'build-system/server/app-video-testbench.js', - 'build-system/server/app.js', - 'build-system/server/shadow-viewer.js', - 'build-system/server/variable-substitution.js', - 'build-system/tasks/check-links.js', - 'build-system/tasks/dist.js', - 'build-system/tasks/extension-generator/index.js', - 'build-system/tasks/helpers.js', - 'build-system/tasks/performance/helpers.js', - 'build-system/tasks/storybook/amp-env/decorator.js', - 'dist.3p/current/integration.js', - 'extensions/amp-iframe/0.1/amp-iframe.js', - 'src/3p-frame.js', - 'src/amp-story-player/amp-story-player-impl.js', - 'src/config.js', - 'testing/local-amp-chrome-extension/background.js', - 'tools/errortracker/errortracker.go', - 'tools/experiments/experiments.js', - 'validator/engine/validator-in-browser.js', - 'validator/engine/validator.js', - 'validator/nodejs/index.js', - 'validator/webui/serve-standalone.go', - ], - }, - '\\<\\<\\<\\<\\<\\<': { - message: 'Unresolved merge conflict.', - }, - '\\>\\>\\>\\>\\>\\>': { - message: 'Unresolved merge conflict.', - }, - '\\.indexOf\\([\'"][^)]+\\)\\s*===?\\s*0\\b': { - message: 'use startsWith helper in src/string.js', - allowlist: ['dist.3p/current/integration.js', 'build-system/server/app.js'], - }, - '\\.indexOf\\(.*===?.*\\.length': 'use endsWith helper in src/string.js', - '/url-parse-query-string': { - message: 'Import parseQueryString from `src/url.js`', - allowlist: ['src/url.js', 'src/mode.js', 'dist.3p/current/integration.js'], - }, - '\\.trim(Left|Right)\\(\\)': { - message: 'Unsupported on IE; use trim() or a helper instead.', - allowlist: ['validator/engine/validator.js'], - }, - "process\\.env(\\.TRAVIS|\\[\\'TRAVIS)": { - message: - 'Do not directly use process.env.TRAVIS. Instead, add a ' + - 'function to build-system/common/travis.js', - allowlist: [ - 'build-system/common/check-package-manager.js', - 'build-system/common/travis.js', - ], - }, - '\\.matches\\(': 'Please use matches() helper in src/dom.js', -}; - -// Terms that must appear in a source file. -const requiredTerms = { - 'Copyright 20(15|16|17|18|19|20) The AMP HTML Authors\\.': dedicatedCopyrightNoteSources, - 'Licensed under the Apache License, Version 2\\.0': dedicatedCopyrightNoteSources, - 'http\\://www\\.apache\\.org/licenses/LICENSE-2\\.0': dedicatedCopyrightNoteSources, -}; - -/** - * Check if root of path is test/ or file is in a folder named test. - * @param {string} path - * @return {boolean} - */ -function isInTestFolder(path) { - const dirs = path.split('/'); - return dirs.indexOf('test') >= 0; -} - -/** - * Check if file is inside the build-system/babel-plugins test/fixture folder. - * @param {string} filePath - * @return {boolean} - */ -function isInBuildSystemFixtureFolder(filePath) { - const folder = path.dirname(filePath); - return ( - folder.startsWith('build-system/babel-plugins') && - folder.includes('test/fixtures') - ); -} - -/** - * Strip Comments - * @param {string} contents - * @return {string} - */ -function stripComments(contents) { - // Multi-line comments - contents = contents.replace(/\/\*(?!.*\*\/)(.|\n)*?\*\//g, function (match) { - // Preserve the newlines - const newlines = []; - for (let i = 0; i < match.length; i++) { - if (match[i] === '\n') { - newlines.push('\n'); - } - } - return newlines.join(''); - }); - // Single line comments either on its own line or following a space, - // semi-colon, or closing brace - return contents.replace(/( |}|;|^) *\/\/.*/g, '$1'); -} - -/** - * Logs any issues found in the contents of file based on terms (regex - * patterns), and provides any possible fix information for matched terms if - * possible - * - * @param {!File} file a vinyl file object to scan for term matches - * @param {!Array} terms Pairs of regex patterns and possible - * fix messages. - * @return {boolean} true if any of the terms match the file content, - * false otherwise - */ -function matchTerms(file, terms) { - const contents = stripComments(file.contents.toString()); - const {relative} = file; - return Object.keys(terms) - .map(function (term) { - let fix; - const {allowlist, checkInTestFolder} = terms[term]; - // NOTE: we could do a glob test instead of exact check in the future - // if needed but that might be too permissive. - if ( - isInBuildSystemFixtureFolder(relative) || - (Array.isArray(allowlist) && - (allowlist.indexOf(relative) != -1 || - (isInTestFolder(relative) && !checkInTestFolder))) - ) { - return false; - } - // we can't optimize building the `RegExp` objects early unless we build - // another mapping of term -> regexp object to be able to get back to the - // original term to get the possible fix value. This is ok as the - // presubmit doesn't have to be blazing fast and this is most likely - // negligible. - const regex = new RegExp(term, 'gm'); - let index = 0; - let line = 1; - let column = 0; - let match; - let hasTerm = false; - - while ((match = regex.exec(contents))) { - hasTerm = true; - for (index; index < match.index; index++) { - if (contents[index] === '\n') { - line++; - column = 1; - } else { - column++; - } - } - - log( - colors.red( - 'Found forbidden: "' + - match[0] + - '" in ' + - relative + - ':' + - line + - ':' + - column - ) - ); - if (typeof terms[term] === 'string') { - fix = terms[term]; - } else { - fix = terms[term].message; - } - - // log the possible fix information if provided for the term. - if (fix) { - log(colors.blue(fix)); - } - log(colors.blue('==========')); - } - - return hasTerm; - }) - .some(function (hasAnyTerm) { - return hasAnyTerm; - }); -} - -/** - * Test if a file's contents match any of the - * forbidden terms - * - * @param {!File} file file is a vinyl file object - * @return {boolean} true if any of the terms match the file content, - * false otherwise - */ -function hasAnyTerms(file) { - const pathname = file.path; - const basename = path.basename(pathname); - let hasTerms = false; - let hasSrcInclusiveTerms = false; - let has3pTerms = false; - - hasTerms = matchTerms(file, forbiddenTerms); - - const isTestFile = - /^test-/.test(basename) || - /^_init_tests/.test(basename) || - /_test\.js$/.test(basename); - if (!isTestFile) { - hasSrcInclusiveTerms = matchTerms(file, forbiddenTermsSrcInclusive); - } - - const is3pFile = - /\/(3p|ads)\//.test(pathname) || - basename == '3p.js' || - basename == 'style.js'; - // Yet another reason to move ads/google/a4a somewhere else - const isA4A = /\/a4a\//.test(pathname); - const isRecaptcha = basename == 'recaptcha.js'; - if (is3pFile && !isRecaptcha && !isTestFile && !isA4A) { - has3pTerms = matchTerms(file, forbidden3pTerms); - } - - return hasTerms || hasSrcInclusiveTerms || has3pTerms; -} - -/** - * Test if a file's contents fail to match any of the required terms and log - * any missing terms - * - * @param {!File} file file is a vinyl file object - * @return {boolean} true if any of the terms are not matched in the file - * content, false otherwise - */ -function isMissingTerms(file) { - const contents = file.contents.toString(); - return Object.keys(requiredTerms) - .map(function (term) { - const filter = requiredTerms[term]; - if (!filter.test(file.path)) { - return false; - } - - const matches = contents.match(new RegExp(term)); - if (!matches) { - log( - colors.red( - 'Did not find required: "' + term + '" in ' + file.relative - ) - ); - log(colors.blue('==========')); - return true; - } - return false; - }) - .some(function (hasMissingTerm) { - return hasMissingTerm; - }); -} - -/** - * Check a file for all the required terms and - * any forbidden terms and log any errors found. - * @return {!Promise} - */ -function presubmit() { - let forbiddenFound = false; - let missingRequirements = false; - return gulp - .src(srcGlobs) - .pipe( - through2.obj(function (file, enc, cb) { - forbiddenFound = hasAnyTerms(file) || forbiddenFound; - missingRequirements = isMissingTerms(file) || missingRequirements; - cb(); - }) - ) - .on('end', function () { - if (forbiddenFound) { - log( - colors.blue( - 'Please remove these usages or consult with the AMP team.' - ) - ); - } - if (missingRequirements) { - log( - colors.blue( - 'Adding these terms (e.g. by adding a required LICENSE ' + - 'to the file)' - ) - ); - } - if (forbiddenFound || missingRequirements) { - process.exitCode = 1; - } - }); -} - -module.exports = { - presubmit, -}; - -presubmit.description = - 'Run validation against files to check for forbidden and required terms'; diff --git a/build-system/tasks/presubmit.js b/build-system/tasks/presubmit.js new file mode 100644 index 0000000000000..9b66d7d42a419 --- /dev/null +++ b/build-system/tasks/presubmit.js @@ -0,0 +1,140 @@ +/** + * Copyright 2015 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. + */ +'use strict'; + +const fs = require('fs'); +const globby = require('globby'); +const srcGlobs = require('../test-configs/config').presubmitGlobs; +const { + forbiddenTermsGlobal, + matchForbiddenTerms, +} = require('../test-configs/forbidden-terms'); +const {cyan, red, yellow} = require('../common/colors'); +const {log} = require('../common/logging'); + +/** + * If you're looking for the forbidden terms list, it's moved to: + * build-system/test-configs/forbidden-terms.js + */ + +const dedicatedCopyrightNoteSources = /(\.css|\.go)$/; + +// Terms that must appear in a source file. +const requiredTerms = { + 'Copyright 20(15|16|17|18|19|2\\d) The AMP HTML Authors\\.': + dedicatedCopyrightNoteSources, + 'Licensed under the Apache License, Version 2\\.0': + dedicatedCopyrightNoteSources, + 'http\\://www\\.apache\\.org/licenses/LICENSE-2\\.0': + dedicatedCopyrightNoteSources, +}; +// Exclude extension generator templates +const requiredTermsExcluded = new RegExp('/make-extension(/.+)?/template/'); + +/** + * Test if a file's contents match any of the forbidden terms + * @param {string} srcFile + * @return {boolean} true if any of the terms match the file content, + * false otherwise + */ +function hasForbiddenTerms(srcFile) { + const contents = fs.readFileSync(srcFile, 'utf-8'); + const terms = matchForbiddenTerms(srcFile, contents, forbiddenTermsGlobal); + for (const {loc, match, message} of terms) { + log( + red('ERROR:'), + 'Found forbidden', + cyan(`"${match}"`), + 'in', + cyan(`${srcFile}:${loc.start.line}:${loc.start.column}`) + ); + + // log the possible fix information if provided for the term. + if (message) { + log('⤷', yellow('To fix:'), message); + } + } + return terms.length > 0; +} + +/** + * Test if a file's contents fail to match any of the required terms and log + * any missing terms + * + * @param {string} srcFile + * @return {boolean} true if any of the terms are not matched in the file + * content, false otherwise + */ +function isMissingTerms(srcFile) { + const contents = fs.readFileSync(srcFile, 'utf-8'); + return Object.keys(requiredTerms) + .map(function (term) { + const filter = requiredTerms[term]; + if (!filter.test(srcFile) || requiredTermsExcluded.test(srcFile)) { + return false; + } + + const matches = contents.match(new RegExp(term)); + if (!matches) { + log( + red('ERROR:'), + 'Did not find required', + cyan(`"${term}"`), + 'in', + cyan(srcFile) + ); + return true; + } + return false; + }) + .some(function (hasMissingTerm) { + return hasMissingTerm; + }); +} + +/** + * Entry point for amp presubmit. + */ +async function presubmit() { + let forbiddenFound = false; + let missingRequirements = false; + const srcFiles = globby.sync(srcGlobs); + for (const srcFile of srcFiles) { + forbiddenFound = hasForbiddenTerms(srcFile) || forbiddenFound; + missingRequirements = isMissingTerms(srcFile) || missingRequirements; + } + if (forbiddenFound) { + log( + yellow('NOTE:'), + 'Please remove these usages or consult with the AMP team.' + ); + } + if (missingRequirements) { + log( + yellow('NOTE:'), + 'Please add these terms (e.g. a required LICENSE) to the files.' + ); + } + if (forbiddenFound || missingRequirements) { + process.exitCode = 1; + } +} + +module.exports = { + presubmit, +}; + +presubmit.description = 'Check source files for forbidden and required terms'; diff --git a/build-system/tasks/prettify.js b/build-system/tasks/prettify.js index 07ac81f15ab0a..22b59ad0c7aae 100644 --- a/build-system/tasks/prettify.js +++ b/build-system/tasks/prettify.js @@ -15,48 +15,57 @@ */ /** - * @fileoverview This file implements the `gulp prettify` task, which uses + * @fileoverview This file implements the `amp prettify` task, which uses * prettier to check (and optionally fix) the formatting in a variety of - * non-JS files in the repo. (JS files are separately checked by `gulp lint`, + * non-JS files in the repo. (JS files are separately checked by `amp lint`, * which uses eslint.) */ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); const fs = require('fs-extra'); -const gulp = require('gulp'); -const log = require('fancy-log'); const path = require('path'); -const prettier = require('gulp-prettier'); +const prettier = require('prettier'); const tempy = require('tempy'); +const { + log, + logLocalDev, + logOnSameLine, + logOnSameLineLocalDev, + logWithoutTimestamp, +} = require('../common/logging'); +const {cyan, green, red, yellow} = require('../common/colors'); const {exec} = require('../common/exec'); -const {getFilesToCheck, logOnSameLine} = require('../common/utils'); -const {green, cyan, red, yellow} = require('ansi-colors'); -const {isTravisBuild} = require('../common/travis'); -const {maybeUpdatePackages} = require('./update-packages'); +const {getFilesToCheck} = require('../common/utils'); const {prettifyGlobs} = require('../test-configs/config'); const rootDir = path.dirname(path.dirname(__dirname)); const tempDir = tempy.directory(); -const prettierCmd = 'node_modules/.bin/prettier'; - -// Message header printed by gulp-prettier before the list of files with errors. -// See https://github.com/bhargavrpatel/gulp-prettier/blob/master/index.js#L90 -const header = - 'Code style issues found in the following file(s). Forgot to run Prettier?'; /** * Checks files for formatting (and optionally fixes them) with Prettier. - * - * @return {!Promise} + * Explicitly makes sure the API doesn't check files in `.prettierignore`. */ -function prettify() { - maybeUpdatePackages(); - const filesToCheck = getFilesToCheck(prettifyGlobs, {dot: true}); +async function prettify() { + const filesToCheck = getFilesToCheck( + prettifyGlobs, + {dot: true}, + '.prettierignore' + ); if (filesToCheck.length == 0) { - return Promise.resolve(); + return; } - return runPrettify(filesToCheck); + await runPrettify(filesToCheck); +} + +/** + * Resolves the prettier config for the given file + * @param {string} file + * @return {Promise} + */ +async function getOptions(file) { + const config = await prettier.resolveConfig(file); + return {filepath: file, ...config}; } /** @@ -65,112 +74,85 @@ function prettify() { * * @param {string} file */ -function printErrorWithSuggestedFixes(file) { - console.log('\n'); +async function printErrorWithSuggestedFixes(file) { + logWithoutTimestamp('\n'); log(`Suggested fixes for ${cyan(file)}:`); + const options = await getOptions(file); + const original = fs.readFileSync(file).toString(); + const fixed = prettier.format(original, options); const fixedFile = `${tempDir}/${file}`; fs.ensureDirSync(path.dirname(fixedFile)); - exec(`${prettierCmd} ${file} > ${fixedFile}`); + fs.writeFileSync(fixedFile, fixed); const diffCmd = `git -c color.ui=always diff -U0 ${file} ${fixedFile} | tail -n +5`; exec(diffCmd); } /** - * Runs prettier on the given list of files with gulp-prettier. - * - * @param {!Array} filesToCheck - * @return {!Promise} + * Prints instructions for how to auto-fix errors. */ -function runPrettify(filesToCheck) { - if (!isTravisBuild()) { - log(green('Starting checks...')); - } - return new Promise((resolve, reject) => { - const onData = (data) => { - if (!isTravisBuild()) { - logOnSameLine(green('Checked: ') + path.relative(rootDir, data.path)); - } - }; - - const rejectWithReason = (reasonText) => { - const reason = new Error(reasonText); - reason.showStack = false; - reject(reason); - }; - - const printFixMessages = () => { - log( - yellow('NOTE 1:'), - "If you are using GitHub's web-UI to edit files,", - 'copy the suggested fixes printed above into your PR.' - ); - log( - yellow('NOTE 2:'), - 'If you are using the git command-line workflow, run', - cyan('gulp prettify --local_changes --fix'), - 'from your local branch.' - ); - log( - yellow('NOTE 3:'), - 'Since this is a destructive operation (that edits your files', - 'in-place), make sure you commit before running the command.' - ); - log( - yellow('NOTE 4:'), - 'For more information, read', - cyan( - 'https://github.com/ampproject/amphtml/blob/master/contributing/getting-started-e2e.md#code-quality-and-style\n' - ) - ); - }; +function printFixMessages() { + log( + yellow('NOTE 1:'), + "If you are using GitHub's web-UI to edit files,", + 'copy the suggested fixes printed above into your PR.' + ); + log( + yellow('NOTE 2:'), + 'If you are using the git command-line workflow, run', + cyan('amp prettify --local_changes --fix'), + 'from your local branch.' + ); + log( + yellow('NOTE 3:'), + 'Since this is a destructive operation (that edits your files', + 'in-place), make sure you commit before running the command.' + ); + log( + yellow('NOTE 4:'), + 'For more information, read', + cyan( + 'https://github.com/ampproject/amphtml/blob/main/docs/getting-started-e2e.md#code-quality-and-style\n' + ) + ); +} - const onError = (error) => { - if (error.message.startsWith(header)) { - const filesWithErrors = error.message - .replace(header, '') - .trim() - .split('\n'); - const reason = 'Found formatting errors in one or more files'; - logOnSameLine(red('ERROR: ') + reason); - filesWithErrors.forEach((file) => { - printErrorWithSuggestedFixes(file); - }); - printFixMessages(); - rejectWithReason(reason); - } else { - const reason = - 'Found an unrecoverable error in ' + - cyan(path.relative(rootDir, error.fileName)); - logOnSameLine(red('ERROR: ') + reason + ':'); - log(error.message); - rejectWithReason(reason); +/** + * Prettifies on the given list of files. + * @param {!Array} filesToCheck + */ +async function runPrettify(filesToCheck) { + logLocalDev(green('Starting checks...')); + const filesWithErrors = []; + for (const file of filesToCheck) { + const options = await getOptions(file); + const original = fs.readFileSync(file).toString(); + if (argv.fix) { + const fixed = prettier.format(original, options); + if (fixed != original) { + fs.writeFileSync(file, fixed); } - }; - - const onFinish = () => { - if (!isTravisBuild()) { - logOnSameLine('Checked ' + cyan(filesToCheck.length) + ' file(s)'); + if (!prettier.check(fixed, options)) { + filesWithErrors.push(file); } - resolve(); - }; - - if (argv.fix) { - return gulp - .src(filesToCheck) - .pipe(prettier()) - .on('data', onData) - .on('error', onError) - .pipe(gulp.dest((file) => file.base)) - .on('finish', onFinish); } else { - return gulp - .src(filesToCheck) - .pipe(prettier.check()) - .on('data', onData) - .on('error', onError) - .on('finish', onFinish); + if (!prettier.check(original, options)) { + filesWithErrors.push(file); + } } - }); + logOnSameLineLocalDev(green('Checked: ') + path.relative(rootDir, file)); + } + if (filesWithErrors.length) { + logOnSameLine( + red('ERROR:'), + 'Found formatting errors in one or more files' + ); + for (const file of filesWithErrors) { + await printErrorWithSuggestedFixes(file); + } + printFixMessages(); + process.exitCode = 1; + } + logOnSameLineLocalDev('Checked ' + cyan(filesToCheck.length) + ' file(s)'); } module.exports = { @@ -180,7 +162,7 @@ module.exports = { prettify.description = 'Checks several non-JS files in the repo for formatting using prettier'; prettify.flags = { - 'files': ' Checks only the specified files', - 'local_changes': ' Checks just the files changed in the local branch', - 'fix': ' Fixes formatting errors', + 'files': 'Checks only the specified files', + 'local_changes': 'Checks just the files changed in the local branch', + 'fix': 'Fixes formatting errors', }; diff --git a/build-system/tasks/process-3p-github-pr.js b/build-system/tasks/process-3p-github-pr.js deleted file mode 100644 index 6a1f9a4cd12a5..0000000000000 --- a/build-system/tasks/process-3p-github-pr.js +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Copyright 2018 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This script auto triage pull requests from 3P. - * It supports: - * 1. Triaging and 3P ad service integration PR. - */ - -'use strict'; -const argv = require('minimist')(process.argv.slice(2)); -const assert = require('assert'); -const colors = require('ansi-colors'); -const extend = require('util')._extend; -const log = require('fancy-log'); -const util = require('util'); - -const request = util.promisify(require('request')); - -const {GITHUB_ACCESS_TOKEN} = process.env; - -const isDryrun = argv.dryrun; - -let reviewer = ''; - -const REGEX_3P_INTEGRATION = new RegExp('3p/integration.js'); -const REGEX_3P_AD_JS = new RegExp('ads/[^/]+.js'); -const REGEX_3P_AD_MD = new RegExp('ads/[^/]+.md'); -const REGEX_3P_AD_CONFIG = new RegExp('ads/_config.js'); -const REGEX_3P_AD_EXAMPLE = new RegExp('examples/ads.amp.html'); -const REGEX_AD_MD = new RegExp('extensions/amp-ad/amp-ad.md'); - -const adIntegrationFileList = [ - REGEX_3P_INTEGRATION, - REGEX_3P_AD_JS, - REGEX_3P_AD_MD, - REGEX_3P_AD_CONFIG, - REGEX_3P_AD_EXAMPLE, - REGEX_AD_MD, -]; - -const internalContributors = [ - // Feel free to add your name here - // if you don't want your PR to be auto triaged. - 'bradfrizzell', - 'calebcordry', - 'glevitzky', - 'keithwrightbos', - 'lannka', - 'jasti', - 'rudygalfi', - 'zhouyx', -]; - -const reviewers = [ - // In rotation order - 'calebcordry', - 'zhouyx', - 'lannka', -]; - -const REF_DATE = new Date('May 13, 2018 00:00:00'); -const WEEK_DIFF = 604800000; - -const ANALYZE_OUTCOME = { - AD: 1, // Ad integration PR, ping the ad onduty person -}; - -const AD_COMMENT = - 'Dear contributor! Thank you for the pull request. ' + - 'It looks like this PR is trying to add support to an ad network. \n \n' + - 'If this is your first time adding support for ' + - 'a new third-party ad service, please make sure your follow our ' + - '[developer guideline](https://github.com/ampproject/amphtml/blob/master/' + - 'ads/README.md#developer-guidelines-for-a-pull-request). \n \n' + - 'If you have not implemented it, we also highly recommend implementing ' + - 'the [renderStart API](https://github.com/ampproject/amphtml/blob/master/' + - 'ads/README.md#available-apis) to provide better user experience. ' + - 'Please let us know if there is any question. \n \n'; - -const defaultOption = { - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - qs: { - 'access_token': GITHUB_ACCESS_TOKEN, - }, -}; - -// we need around 14 batches to get more than 1k issues -const NUM_BATCHES = 14; - -/** - * Calculate the reviewer this week, based on rotation calendar - * @return {string} - */ -function calculateReviewer() { - const now = Date.now(); - const diff = now - REF_DATE; - const week = diff / WEEK_DIFF; - const turn = Math.floor(week % 3); - return reviewers[turn]; -} - -/** - * Main function for auto triaging - * @return {!Promise|undefined} - */ -function process3pGithubPr() { - if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('You have not set the GITHUB_ACCESS_TOKEN env var.')); - log( - colors.green( - 'See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.' - ) - ); - return; - } - - reviewer = calculateReviewer(); - - const arrayPromises = []; - // we need to pull issues in batches - for (let batch = 1; batch < NUM_BATCHES; batch++) { - arrayPromises.push(getIssues(batch)); - } - return Promise.all(arrayPromises) - .then((requests) => [].concat.apply([], requests)) - .then((issues) => { - const allIssues = issues; - const allTasks = []; - allIssues.forEach(function (issue) { - allTasks.push(handleIssue(issue)); - }); - return Promise.all(allTasks); - }) - .then(() => { - log(colors.blue('auto triaging succeed!')); - }); -} - -function handleIssue(issue) { - return isQualifiedPR(issue).then((outcome) => { - return replyToPR(issue, outcome); - }); -} - -/** - * Fetches issues?page=${opt_page} - * - * @param {number=} opt_page - * @return {!Promise} - */ -function getIssues(opt_page) { - // We need to use the issue API because assignee is only available with it. - const options = extend({}, defaultOption); - options.url = 'https://api.github.com/repos/ampproject/amphtml/issues'; - options.qs = { - 'state': 'open', - 'assignee': 'none', - 'page': opt_page, - 'per_page': 100, - 'access_token': GITHUB_ACCESS_TOKEN, - }; - return request(options).then((res) => { - const issues = JSON.parse(res.body); - assert(Array.isArray(issues), 'issues must be an array.'); - return issues; - }); -} - -/** - * API call to get all changed files of a pull request. - * @param {!Object} pr - * @return {?Array} - */ -function getPullRequestFiles(pr) { - const options = extend({}, defaultOption); - const {number} = pr; - options.url = - 'https://api.github.com/repos/ampproject/amphtml/pulls/' + - `${number}/files`; - return request(options).then((res) => { - const files = JSON.parse(res.body); - if (!Array.isArray(files)) { - return null; - } - return files; - }); -} - -/** - * Determine the type of a give pull request - * @param {?Array} files - * @return {number|null|undefined} - */ -function analyzeChangedFiles(files) { - if (!files) { - return; - } - // Only support 3p ads integration files - const fileCount = files.length; - if (fileCount == 0) { - return null; - } - let matchFileCount = 0; - for (let i = 0; i < fileCount; i++) { - const fileName = files[i].filename; - for (let j = 0; j < adIntegrationFileList.length; j++) { - const regex = adIntegrationFileList[j]; - if (regex.test(fileName)) { - matchFileCount++; - continue; - } - } - } - const percentage = matchFileCount / fileCount; - if (percentage > 0.75 || matchFileCount >= 3) { - // Still need to check the matchFileCount because of incorrect rebase. - return ANALYZE_OUTCOME.AD; - } - return null; -} - -/** - * Determine if we need to reply to an issue - * @param {!Object} issue - * @return {!Promise} - */ -function isQualifiedPR(issue) { - // All issues are opened has no assignee - if (!issue.pull_request) { - // Is not a pull request - return Promise.resolve(null); - } - - const author = issue.user.login; - if (internalContributors.indexOf(author) > -1) { - // If it is a pull request from internal contributor - return Promise.resolve(null); - } - // get pull request reviewer API is not working as expected. Skip - - // Get changed files of this PR - return getPullRequestFiles(issue).then((files) => { - return analyzeChangedFiles(files); - }); -} - -/** - * Auto reply - * @param {!Object} pr - * @param {ANALYZE_OUTCOME} outcome - * @return {!Promise} - */ -function replyToPR(pr, outcome) { - let promise = Promise.resolve(); - if (outcome == ANALYZE_OUTCOME.AD) { - promise = promise - .then(() => { - // We should be good with rate limit given the number of - // 3p integration PRs today. - const comment = AD_COMMENT + `Thank you! Ping @${reviewer} for review`; - return applyComment(pr, comment); - }) - .then(() => { - return assignIssue(pr, [reviewer]); - }); - } - return promise; -} - -/** - * API call to comment on a give issue. - * @param {!Object} issue - * @param {string} comment - * @return {!Promise} - */ -function applyComment(issue, comment) { - const {number} = issue; - const options = extend( - { - url: - 'https://api.github.com/repos/ampproject/amphtml/issues/' + - `${number}/comments`, - method: 'POST', - body: JSON.stringify({ - 'body': comment, - }), - }, - defaultOption - ); - if (isDryrun) { - log(colors.blue(`apply comment to PR #${number}, comment is ${comment}`)); - return Promise.resolve(); - } - return request(options); -} - -/** - * API call to assign an issue with a list of assignees - * @param {!Object} issue - * @param {!Array} assignees - * @return {!Promise} - */ -function assignIssue(issue, assignees) { - const {number} = issue; - const options = extend( - { - url: - 'https://api.github.com/repos/ampproject/amphtml/issues/' + - `${number}/assignees`, - method: 'POST', - body: JSON.stringify({ - 'assignees': assignees, - }), - }, - defaultOption - ); - if (isDryrun) { - log(colors.blue(`assign PR #${number}, to ${assignees}`)); - return Promise.resolve(); - } - return request(options); -} - -module.exports = { - process3pGithubPr, -}; - -process3pGithubPr.description = 'Automatically triage 3P integration PRs'; -process3pGithubPr.flags = { - dryrun: " Generate process but don't push it out", -}; diff --git a/build-system/tasks/process-github-issues.js b/build-system/tasks/process-github-issues.js deleted file mode 100644 index 9ac22423a5397..0000000000000 --- a/build-system/tasks/process-github-issues.js +++ /dev/null @@ -1,485 +0,0 @@ -/** - * Copyright 2017 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. - */ -'use strict'; -const argv = require('minimist')(process.argv.slice(2)); -const assert = require('assert'); -const colors = require('ansi-colors'); -const extend = require('util')._extend; -const log = require('fancy-log'); -const util = require('util'); - -const request = util.promisify(require('request')); - -const {GITHUB_ACCESS_TOKEN} = process.env; - -const isDryrun = argv.dryrun; - -const issuesOptions = { - url: 'https://api.github.com/repos/ampproject/amphtml/issues', - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - qs: { - 'access_token': GITHUB_ACCESS_TOKEN, - }, -}; - -const milestoneOptions = { - url: 'https://api.github.com/repos/ampproject/amphtml/milestones', - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - qs: { - 'access_token': GITHUB_ACCESS_TOKEN, - }, -}; - -// 4 is the number for Milestone 'Backlog Bugs' -const MILESTONE_BACKLOG_BUGS = 4; -// 11 is the number for Milestone '3P Implementation' -const MILESTONE_3P_IMPLEMENTATION = 11; -// 12 is the number for Milestone 'Docs Updates' -const MILESTONE_DOCS_UPDATES = 12; -// By default we will assign 'Pending Triage' milestone, number 20 -const MILESTONE_PENDING_TRIAGE = 20; -// 22 is the number for Milestone 'Prioritized FRs' -const MILESTONE_PRIORITIZED_FRS = 22; -// 23 is the number for Milestone 'New FRs' -const MILESTONE_NEW_FRS = 23; -// 25 is the number for Milestone 'Good First Issues (GFI)' -const MILESTONE_GREAT_ISSUES = 25; -// days for biweekly updates -const BIWEEKLY_DAYS = 14; -// days for quarterly updates -const QUARTERLY_DAYS = 89; -// we need around 14 batches to get more than 1k issues -const NUM_BATCHES = 14; - -// We start processing the issues by checking token first -function processGithubIssues() { - if (!GITHUB_ACCESS_TOKEN) { - log(colors.red('You have not set the GITHUB_ACCESS_TOKEN env var.')); - log( - colors.green( - 'See https://help.github.com/articles/' + - 'creating-an-access-token-for-command-line-use/ ' + - 'for instructions on how to create a github access token. We only ' + - 'need `public_repo` scope.' - ) - ); - return; - } - return updateGitHubIssues().then(function () { - log(colors.blue('automation applied')); - }); -} -/** - * Fetches issues?page=${opt_page} - * - * @param {number=} opt_page - * @return {!Promise { - const issues = JSON.parse(res.body); - assert(Array.isArray(issues), 'issues must be an array.'); - return issues; - }); -} -/** - * Function goes through all the gitHub issues, - * gets all the Labels we are interested in, - * depending if missing milestone or label, - * tasks applied as per design go/ampgithubautomation - * @return {!Promise} - */ -function updateGitHubIssues() { - let promise = Promise.resolve(); - const arrayPromises = []; - // we need to pull issues in batches - for (let batch = 1; batch < NUM_BATCHES; batch++) { - arrayPromises.push(getIssues(batch)); - } - return Promise.all(arrayPromises) - .then((requests) => [].concat.apply([], requests)) - .then((issues) => { - const allIssues = issues; - allIssues.forEach(function (issue) { - const { - labels, - milestone, - assignee, - 'pull_request': pullRequest, - 'updated_at': issueLastUpdate, - } = issue; - let issueType; - let milestoneTitle; - let milestoneState; - let hasPriority = false; - let hasCategory = false; - let issueNewMilestone = MILESTONE_PENDING_TRIAGE; - let assigneeName = ''; - let biweeklyUpdate = true; - let quartelyUpdate = true; - // if an issue is a pull request, we'll skip it - if (pullRequest) { - if (isDryrun) { - log(colors.red(issue.number + ' is a pull request')); - } - return; - } - if (getLastUpdate(issueLastUpdate) > QUARTERLY_DAYS) { - quartelyUpdate = false; - biweeklyUpdate = false; - } else if (getLastUpdate(issueLastUpdate) > BIWEEKLY_DAYS) { - biweeklyUpdate = false; - } - // Get the assignee - if (assignee) { - assigneeName = '@' + assignee.login; - } - // Get the title and state of the milestone - if (milestone) { - milestoneTitle = milestone.title; - milestoneState = milestone.state; - issueNewMilestone = milestone.number; - } - // promise starts - promise = promise.then(function () { - log('Update ' + issue.number); - const updates = []; - // Get the labels we want to check - labels.forEach(function (label) { - if (label) { - // Check if the issues has type - if ( - label.name.startsWith('Type') || - label.name.startsWith('Related') - ) { - issueType = label.name; - } - // Check if the issues has Priority - if ( - label.name.startsWith('P0') || - label.name.startsWith('P1') || - label.name.startsWith('P2') || - label.name.startsWith('P3') - ) { - hasPriority = true; - if ( - label.name.startsWith('P0') || - label.name.startsWith('P1') - ) { - if (biweeklyUpdate == false) { - biweeklyUpdate = true; - updates.push( - applyComment( - issue, - 'This is a high priority' + - " issue but it hasn't been updated in awhile. " + - assigneeName + - ' Do you have any updates?' - ) - ); - } - } else if ( - label.name.startsWith('P2') && - quartelyUpdate == false - ) { - quartelyUpdate = true; - updates.push( - applyComment( - issue, - "This issue hasn't been " + - ' updated in awhile. ' + - assigneeName + - ' Do you have any updates?' - ) - ); - } - } - if ( - label.name.startsWith('Category') || - label.name.startsWith('Related to') || - label.name.startsWith('GFI') || - label.name.startsWith('good first issue') - ) { - hasCategory = true; - } - } - }); - // Milestone task: move issues from closed milestone - if (milestone) { - if (milestoneState === 'closed') { - issueNewMilestone = MILESTONE_BACKLOG_BUGS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } - } - if (issueNewMilestone === MILESTONE_PENDING_TRIAGE) { - if (quartelyUpdate == false) { - quartelyUpdate = true; - updates.push( - applyComment( - issue, - 'This issue seems to be in ' + - ' Pending Triage for awhile. ' + - assigneeName + - ' Please triage this to ' + - 'an appropriate milestone.' - ) - ); - } - } - // if issueType is not null, add correct milestones - if (issueType != null) { - if ( - issueNewMilestone === MILESTONE_PENDING_TRIAGE || - milestone == null - ) { - if (issueType === 'Type: Feature Request') { - issueNewMilestone = MILESTONE_NEW_FRS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if ( - issueType === 'Related to: Documentation' || - issueType === 'Type: Design Review' || - issueType === 'Type: Status Update' - ) { - issueNewMilestone = MILESTONE_DOCS_UPDATES; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if ( - issueType === 'Type: Bug' || - issueType === 'Related to: Flaky Tests' - ) { - issueNewMilestone = MILESTONE_BACKLOG_BUGS; - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if (milestone == null) { - updates.push(applyMilestone(issue, issueNewMilestone)); - } - } - } else if (milestone == null) { - updates.push(applyMilestone(issue, issueNewMilestone)); - } else if ( - issueNewMilestone === MILESTONE_PRIORITIZED_FRS || - issueNewMilestone === MILESTONE_NEW_FRS - ) { - updates.push(applyLabel(issue, 'Type: Feature Request')); - } else if ( - issueNewMilestone === MILESTONE_BACKLOG_BUGS || - milestoneTitle.startsWith('Sprint') - ) { - updates.push(applyLabel(issue, 'Type: Bug')); - } - // Apply default priority if no priority - if ( - hasPriority == false && - issueNewMilestone != MILESTONE_NEW_FRS && - issueNewMilestone !== MILESTONE_3P_IMPLEMENTATION && - issueNewMilestone !== MILESTONE_PENDING_TRIAGE && - milestone != null - ) { - updates.push(applyLabel(issue, 'P2: Soon')); - } - // Add comment with missing Category - if (hasCategory == false) { - if ( - issueNewMilestone === MILESTONE_PENDING_TRIAGE || - issueNewMilestone === MILESTONE_DOCS_UPDATES || - issueNewMilestone == null || - issueNewMilestone === MILESTONE_GREAT_ISSUES - ) { - if (isDryrun) { - log(colors.green('No comment needed for #' + issue.number)); - } - } else { - updates.push( - applyComment( - issue, - "This issue doesn't have a category" + - ' which makes it harder for us to keep track of it. ' + - assigneeName + - ' Please add an appropriate category.' - ) - ); - } - } - return Promise.all(updates); - }); - }); - return promise; - }); -} - -/** - * @param {string} issue - * @param {number} milestoneNumber - * @return {!Promise<*>} - */ -function applyMilestone(issue, milestoneNumber) { - const options = extend({}, milestoneOptions); - options.qs = { - 'state': 'open', - 'per_page': 100, - 'access_token': GITHUB_ACCESS_TOKEN, - }; - - issue.milestone = milestoneNumber; - if (isDryrun) { - log( - colors.green( - 'Milestone applied ' + milestoneNumber + ' for #' + issue.number - ) - ); - return; - } else { - return createGithubRequest( - '/issues/' + issue.number, - 'PATCH', - issue.milestone, - 'milestone' - ); - } -} - -/** - * @param {string} issue - * @param {string} label - * @return {!Promise<*>} - */ -function applyLabel(issue, label) { - const options = extend({}, issuesOptions); - options.qs = { - 'state': 'open', - 'per_page': 100, - 'access_token': GITHUB_ACCESS_TOKEN, - }; - if (isDryrun) { - log(colors.green('Label applied ' + label + ' for #' + issue.number)); - return; - } else { - return createGithubRequest( - '/issues/' + issue.number + '/labels', - 'POST', - [label], - 'label' - ); - } -} - -/** - * @param {string} issue - * @param {string} comment - * @return {!Promise<*>} - */ -function applyComment(issue, comment) { - const options = extend({}, issuesOptions); - options.qs = { - 'state': 'open', - 'per_page': 100, - 'access_token': GITHUB_ACCESS_TOKEN, - }; - // delay the comment request so we don't reach github rate limits requests - const promise = new Promise((resolve) => setTimeout(resolve, 120000)); - return promise.then(function () { - if (isDryrun) { - log(colors.blue('waited 2 minutes to avoid gh rate limits')); - log( - colors.green( - 'Comment applied after ' + - 'waiting 2 minutes to avoid github rate limits: ' + - comment + - ' for #' + - issue.number - ) - ); - return; - } else { - createGithubRequest( - '/issues/' + issue.number + '/comments', - 'POST', - comment, - 'comment' - ); - } - }); -} -// calculate number of days since the latest update -function getLastUpdate(issueLastUpdate) { - const t = new Date(); - const splits = issueLastUpdate.split('-', 3); - const exactDay = splits[2].split('T', 1); - const firstDate = Date.UTC(splits[0], splits[1], exactDay[0]); - const secondDate = Date.UTC(t.getFullYear(), t.getMonth() + 1, t.getDate()); - const diff = Math.abs( - (firstDate.valueOf() - secondDate.valueOf()) / (24 * 60 * 60 * 1000) - ); - return diff; -} - -/** - * Function pushes the updates requested based on the path received - * @param {string} path - * @param {string=} opt_method - * @param {*} opt_data - * @param {string} typeRequest - * @return {!Promise<*>} - */ -function createGithubRequest(path, opt_method, opt_data, typeRequest) { - const options = { - url: 'https://api.github.com/repos/ampproject/amphtml' + path, - body: {}, - headers: { - 'User-Agent': 'amp-changelog-gulp-task', - 'Accept': 'application/vnd.github.v3+json', - }, - qs: { - 'access_token': GITHUB_ACCESS_TOKEN, - }, - }; - if (opt_method) { - options.method = opt_method; - } - if (opt_data) { - options.json = true; - if (typeRequest === 'milestone') { - options.body.milestone = opt_data; - } else if (typeRequest === 'comment') { - options.body.body = opt_data; - } else { - options.body = opt_data; - } - } - return request(options); -} - -module.exports = { - processGithubIssues, -}; - -processGithubIssues.description = - 'Automatically updates the labels ' + - 'and milestones of all open issues at github.com/ampproject/amphtml.'; -processGithubIssues.flags = { - dryrun: " Generate process but don't push it out", -}; diff --git a/build-system/tasks/release/index.js b/build-system/tasks/release/index.js new file mode 100644 index 0000000000000..7ac3494962335 --- /dev/null +++ b/build-system/tasks/release/index.js @@ -0,0 +1,511 @@ +/** + * Copyright 2020 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. + */ +'use strict'; + +/** + * @typedef {{ + * name?: string, + * environment?: string, + * issue?: string, + * expiration_date_utc?: string, + * define_experiment_constant?: string, + * }} + */ +let ExperimentConfigDef; + +/** + * @typedef {{ + * experimentA: ExperimentConfigDef, + * experimentB: ExperimentConfigDef, + * experimentC: ExperimentConfigDef, + * }} + */ +let ExperimentsConfigDef; + +/** + * @typedef {ExperimentConfigDef & { + * flavorType: string; + * rtvPrefixes: string[]; + * command: string; + * }} + */ +let DistFlavorDef; + +const argv = require('minimist')(process.argv.slice(2)); +/** @type {ExperimentsConfigDef} */ +const experimentsConfig = require('../../global-configs/experiments-config.json'); +const fetch = require('node-fetch'); +const fs = require('fs-extra'); +const klaw = require('klaw'); +const path = require('path'); +const tar = require('tar'); +const {cyan, green} = require('../../common/colors'); +const {execOrDie} = require('../../common/exec'); +const {log} = require('../../common/logging'); +const {MINIFIED_TARGETS} = require('../helpers'); +const {VERSION} = require('../../compile/internal-version'); + +// Flavor config for the base flavor type. +const BASE_FLAVOR_CONFIG = { + flavorType: 'base', + name: 'base', + rtvPrefixes: ['00', '01', '02', '03', '04', '05'], + command: 'amp dist --noconfig', + environment: 'AMP', +}; + +// Deep map from [environment][flavorType] to an RTV prefix. +const EXPERIMENTAL_RTV_PREFIXES = { + AMP: { + experimentA: '10', + experimentB: '11', + experimentC: '12', + }, + INABOX: { + 'experimentA-control': '20', + experimentA: '21', + 'experimentB-control': '22', + experimentB: '23', + 'experimentC-control': '24', + experimentC: '25', + }, +}; + +// Directory containing multiple static files to dist/. +const STATIC_FILES_DIR = path.resolve(__dirname, 'static'); + +// List of individual files to copy directly from the Git workspace to dist/. +const STATIC_FILE_PATHS = ['build-system/global-configs/caches.json']; + +// List of individual files to copy from post-build files, key-values as the +// from-to of the copy operations. +const POST_BUILD_MOVES = { + 'dist.tools/experiments/experiments.cdn.html': 'dist/experiments.html', + 'dist.tools/experiments/experiments.js': 'dist/v0/experiments.js', + 'dist.tools/experiments/experiments.js.map': 'dist/v0/experiments.js.map', +}; + +// URL to the JSON info API for the amp-sw package on the npm registry. +const AMP_SW_NPM_PACKAGE_URL = 'https://registry.npmjs.org/@ampproject/amp-sw'; + +// List of directories to keep in the temp directory from each flavor's build. +const DIST_DIRS = ['dist', 'dist.3p', 'dist.tools']; + +// Mapping from a channel's RTV prefix, to an object with channel configuration +// data based on the spec/amp-framework-hosting.md document. The fields are: +// - type: machine name of the channel (Some of these are the same as their name +// in spec/amp-framework-hosting.md, but some are different. Those that have a +// different machine name are marked in a comment.) +// - configBase: name of the config JSON file to prepend as the AMP_CONFIG to +// all the entry files in this channel. Either `canary` or `prod` (these files +// are located in build-system/global-configs/${configBase}-config.json). +const CHANNEL_CONFIGS = { + '00': {type: 'experimental', configBase: 'canary'}, + '01': {type: 'production', configBase: 'prod'}, // Spec name: 'stable' + '02': {type: 'control', configBase: 'prod'}, + '03': {type: 'rc', configBase: 'prod'}, // Spec name: 'beta' + '04': {type: 'nightly', configBase: 'prod'}, + '05': {type: 'nightly-control', configBase: 'prod'}, + '10': {type: 'experimentA', configBase: 'prod'}, + '11': {type: 'experimentB', configBase: 'prod'}, + '12': {type: 'experimentC', configBase: 'prod'}, + '20': {type: 'experimentA-control', configBase: 'prod'}, // Spec name: 'inabox-experimentA-control' + '21': {type: 'experimentA', configBase: 'prod'}, // Spec name: 'inabox-experimentA' + '22': {type: 'experimentB-control', configBase: 'prod'}, // Spec name: 'inabox-experimentB-control' + '23': {type: 'experimentB', configBase: 'prod'}, // Spec name: 'inabox-experimentB' + '24': {type: 'experimentC-control', configBase: 'prod'}, // Spec name: 'inabox-experimentC-control' + '25': {type: 'experimentC', configBase: 'prod'}, // Spec name: 'inabox-experimentC' +}; + +/** + * Prints a separator line so logs are easy to read. + */ +function logSeparator_() { + log('---\n\n'); +} + +/** + * Prepares output and temp directories. + * + * @param {string} outputDir full directory path to emplace artifacts in. + * @param {string} tempDir full directory path to temporary working directory. + */ +async function prepareEnvironment_(outputDir, tempDir) { + execOrDie('amp clean'); + await fs.emptyDir(outputDir); + await fs.emptyDir(tempDir); + logSeparator_(); +} + +/** + * Discovers which AMP flavors are defined in the current working directory. + * + * The returned list of flavors will always contain the base flavor, and any + * defined experiments in ../../global-configs/experiments-config.json. + * + * @return {!Array} list of AMP flavors to build. + */ +function discoverDistFlavors_() { + const experimentConfigDefs = Object.entries(experimentsConfig); + const distFlavors = [ + BASE_FLAVOR_CONFIG, + ...experimentConfigDefs + .filter( + // Only include experiments that have a `define_experiment_constant` field. + ([, experimentConfig]) => experimentConfig.define_experiment_constant + ) + .map(([flavorType, experimentConfig]) => ({ + // TODO(#28168, erwinmombay): relace with single `--module --nomodule` command. + command: `amp dist --noconfig --define_experiment_constant ${experimentConfig.define_experiment_constant}`, + flavorType, + rtvPrefixes: [ + EXPERIMENTAL_RTV_PREFIXES[experimentConfig.environment][flavorType], + ], + ...experimentConfig, + })), + ].filter( + // If --flavor is defined, filter out the rest. + ({flavorType}) => !argv.flavor || flavorType == argv.flavor + ); + + log( + 'The following', + cyan('amp dist'), + 'commands will be executed to compile each', + `${green('flavor')}:` + ); + distFlavors.forEach(({command, flavorType, name}) => { + log('-', `(${green(flavorType)}, ${green(name)})`, cyan(command)); + }); + + logSeparator_(); + + return distFlavors; +} + +/** + * Compiles all AMP flavors sequentially. + * + * @param {string} flavorType AMP flavor to build. + * @param {string} command `amp` command to build the flavor. + * @param {string} tempDir full directory path to temporary working directory. + */ +async function compileDistFlavors_(flavorType, command, tempDir) { + // TODO(danielrozenberg): remove undefined case when the release automation platform explicitly handles it. + if (argv.esm === undefined) { + command = `${command} --esm && ${command}`; + } else if (argv.esm) { + command += ' --esm'; + } + log('Compiling flavor', green(flavorType), 'using', cyan(command)); + + execOrDie('amp clean --exclude release'); + execOrDie(command); + + const flavorTempDistDir = path.join(tempDir, flavorType); + log('Moving build artifacts to', `${cyan(flavorTempDistDir)}...`); + await fs.ensureDir(flavorTempDistDir); + + await Promise.all( + DIST_DIRS.map((distDir) => + fs.move(distDir, path.join(flavorTempDistDir, distDir)) + ) + ); + + log('Copying static files...'); + const staticFilesPromises = [ + // Directory-to-directory copy from the ./static sub-directory. + fs.copy(STATIC_FILES_DIR, path.join(flavorTempDistDir, 'dist')), + // Individual files to copy from the Git repository. + ...STATIC_FILE_PATHS.map((staticFilePath) => + fs.copy( + staticFilePath, + path.join(flavorTempDistDir, 'dist', path.basename(staticFilePath)) + ) + ), + ]; + const postBuildMovesPromises = !argv.esm + ? [ + // Individual files to copy from the resulting build artifacts. + // This is only relevant for nomodule builds. + ...Object.entries(POST_BUILD_MOVES).map(([from, to]) => + fs.copy( + path.join(flavorTempDistDir, from), + path.join(flavorTempDistDir, to) + ) + ), + ] + : [Promise.resolve()]; + await Promise.all([...staticFilesPromises, ...postBuildMovesPromises]); + + logSeparator_(); +} + +/** + * Fetches latest AMP service-worker package from the npm registry. + * + * @param {string} flavorType AMP flavor to build. + * @param {string} tempDir full directory path to temporary working directory. + */ +async function fetchAmpSw_(flavorType, tempDir) { + const ampSwTempDir = path.join(tempDir, 'ampproject/amp-sw'); + await Promise.all([ + fs.ensureDir(ampSwTempDir), + fs.ensureDir(path.join(tempDir, flavorType, 'dist/sw')), + ]); + + const ampSwNpmPackageResponse = await fetch(AMP_SW_NPM_PACKAGE_URL); + const ampSwNpmPackageJson = await ampSwNpmPackageResponse.json(); + const {latest} = ampSwNpmPackageJson['dist-tags']; + const ampSwTarballUrl = ampSwNpmPackageJson.versions[latest].dist.tarball; + + const tarWritableStream = tar.extract({ + cwd: ampSwTempDir, + filter: (path) => path.startsWith('package/dist'), + strip: 2, // to strip "package/dist/". + }); + (await fetch(ampSwTarballUrl)).body.pipe(tarWritableStream); + await new Promise((resolve) => { + tarWritableStream.on('end', resolve); + }); + + await fs.copy(ampSwTempDir, path.join(tempDir, flavorType, 'dist/sw')); + + logSeparator_(); +} + +/** + * Copies compiled build artifacts to RTV-based directories. + * + * Each flavor translates to one or more RTV numbers, for a detailed explanation + * see spec/amp-framework-hosting.md. + * + * @param {string} flavorType AMP flavor to build. + * @param {!Array} rtvPrefixes list of 2-digit RTV prefixes to generate. + * @param {string} tempDir full directory path to temporary working directory. + * @param {string} outputDir full directory path to emplace artifacts in. + */ +async function populateOrgCdn_(flavorType, rtvPrefixes, tempDir, outputDir) { + const rtvCopyingPromise = async (/** @type {string} */ rtvPrefix) => { + const rtvNumber = `${rtvPrefix}${VERSION}`; + const rtvPath = path.join(outputDir, 'org-cdn/rtv', rtvNumber); + await fs.ensureDir(rtvPath); + return fs.copy(path.join(tempDir, flavorType, 'dist'), rtvPath); + }; + + const rtvCopyingPromises = rtvPrefixes.map(rtvCopyingPromise); + + // Special handling for INABOX experiments when compiling the base flavor. + // INABOX experiments need to have their control population be created from + // the base flavor. + if (flavorType == 'base') { + rtvCopyingPromises.push( + ...Object.entries(experimentsConfig) + .filter(([, {environment}]) => environment == 'INABOX') + .map( + ([experimentFlavor]) => + EXPERIMENTAL_RTV_PREFIXES['INABOX'][`${experimentFlavor}-control`] + ) + .map(rtvCopyingPromise) + ); + } + await Promise.all(rtvCopyingPromises); + + logSeparator_(); +} + +/** + * Generates a listing of all files in each org-cdn/rtv/ subdirectory. + + * @param {string} outputDir full directory path to emplace artifacts in. + */ +async function generateFileListing_(outputDir) { + await Promise.all( + Object.entries(CHANNEL_CONFIGS) + .map(([rtvPrefix]) => + path.join(outputDir, 'org-cdn/rtv', `${rtvPrefix}${VERSION}`) + ) + .filter((rtvPath) => fs.pathExistsSync(path.join(rtvPath))) + .map(async (rtvPath) => { + const filesPath = path.join(rtvPath, 'files.txt'); + + const files = []; + for await (const file of klaw(rtvPath)) { + if (file.stats.isFile()) { + files.push(path.relative(rtvPath, file.path)); + } + } + files.sort(); + files.push(''); // Add an empty line at end of file. + + await fs.writeFile(filesPath, files.join('\n')); + }) + ); +} + +/** + * Prepends the AMP_CONFIG configuration object to all the entry files. + * + * Entry files are those that publishers would embed in their page, i.e. + * https://cdn.ampproject.org/v0.js, but could be others for different users, + * e.g., /amp4ads-v0.js for AMP ads. + * + * @param {string} outputDir full directory path to emplace artifacts in. + */ +async function prependConfig_(outputDir) { + const activeChannels = Object.entries(CHANNEL_CONFIGS).filter( + ([rtvPrefix]) => { + const rtvNumber = `${rtvPrefix}${VERSION}`; + const rtvPath = path.join(outputDir, 'org-cdn/rtv', rtvNumber); + return fs.pathExistsSync(path.join(rtvPath)); + } + ); + + const allPrependPromises = []; + for (const [rtvPrefix, channelConfig] of activeChannels) { + const rtvNumber = `${rtvPrefix}${VERSION}`; + const rtvPath = path.join(outputDir, 'org-cdn/rtv', rtvNumber); + const channelPartialConfig = { + v: rtvNumber, + type: channelConfig.type, + ...require(`../../global-configs/${channelConfig.configBase}-config.json`), + }; + // Mapping of entry file names to a dictionary of AMP_CONFIG additions. + const targetsToConfig = MINIFIED_TARGETS.flatMap((minifiedTarget) => { + const targets = []; + // TODO(danielrozenberg): remove undefined case when the release automation platform explicitly handles it. + if (!argv.esm) { + // For explicit --no-esm or when no ESM flag is passed. + targets.push({file: `${minifiedTarget}.js`, config: {}}); + } + if (argv.esm === undefined || argv.esm) { + // For explicit --esm or when no ESM flag is passed. + targets.push({file: `${minifiedTarget}.mjs`, config: {esm: 1}}); + } + return targets; + }); + + allPrependPromises.push( + ...targetsToConfig.map(async (target) => { + const targetPath = path.join(rtvPath, target.file); + const channelConfig = JSON.stringify({ + ...channelPartialConfig, + ...target.config, + }); + + const contents = await fs.readFile(targetPath, 'utf-8'); + return fs.writeFile( + targetPath, + `self.AMP_CONFIG=${channelConfig};/*AMP_CONFIG*/${contents}` + ); + }) + ); + } + await Promise.all(allPrependPromises); + + logSeparator_(); +} + +/** + * Copies compiled build artifacts to the AMP ads frame container directory. + * + * @param {string} tempDir full directory path to temporary working directory. + * @param {string} outputDir full directory path to emplace artifacts in. + */ +async function populateNetWildcard_(tempDir, outputDir) { + const netWildcardDir = path.join(outputDir, 'net-wildcard', VERSION); + await fs.ensureDir(netWildcardDir); + await fs.copy(path.join(tempDir, 'base/dist.3p', VERSION), netWildcardDir); + + logSeparator_(); +} + +/** + * Cleans are deletes the temp directory. + * + * @param {string} tempDir full directory path to temporary working directory. + */ +async function cleanup_(tempDir) { + await fs.rmdir(tempDir, {recursive: true}); + + logSeparator_(); +} + +/** + * @return {Promise} + */ +async function release() { + const outputDir = path.resolve(argv.output_dir || './release'); + const tempDir = path.join(outputDir, 'tmp'); + + log('Preparing environment for release build in', `${cyan(outputDir)}...`); + await prepareEnvironment_(outputDir, tempDir); + + log('Discovering release', `${green('flavors')}...`); + const distFlavors = discoverDistFlavors_(); + + if (argv.flavor && distFlavors.length == 0) { + log('Flavor', cyan(argv.flavor), 'is inactive. Quitting...'); + return; + } + + if (!argv.flavor) { + log('Compiling all', `${green('flavors')}...`); + } + + for (const {command, flavorType, rtvPrefixes} of distFlavors) { + await compileDistFlavors_(flavorType, command, tempDir); + + log('Fetching npm package', `${cyan('@ampproject/amp-sw')}...`); + await fetchAmpSw_(flavorType, tempDir); + + log('Copying from temporary directory to', cyan('org-cdn')); + await populateOrgCdn_(flavorType, rtvPrefixes, tempDir, outputDir); + } + + log('Generating', cyan('files.txt'), 'files in', cyan('org-cdn/rtv/*')); + await generateFileListing_(outputDir); + + log('Prepending config to entry files...'); + await prependConfig_(outputDir); + + if (!argv.flavor || argv.flavor == 'base') { + // Only populate the net-wildcard directory if --flavor=base or if --flavor is not set. + log('Copying from temporary directory to', cyan('net-wildcard')); + await populateNetWildcard_(tempDir, outputDir); + } + + log('Cleaning up temp dir...'); + await cleanup_(tempDir); + + log('Release build is done!'); + log(' See:', cyan(outputDir)); +} + +module.exports = { + release, +}; + +release.description = 'Generates a release build'; +release.flags = { + 'output_dir': + 'Directory path to emplace release files (defaults to "./release")', + 'flavor': + 'Limit this release build to a single flavor. Can be used to split the release work between multiple build machines.', + 'esm': + // TODO(danielrozenberg): remove undefined case when the release automation platform explicitly handles it. + 'True to compile with --esm, false to compile without; Do not set to compile both.', +}; diff --git a/build-system/tasks/release/static/amp_preconnect_polyfill_404_or_other_error_expected._Do_not_worry_about_it b/build-system/tasks/release/static/amp_preconnect_polyfill_404_or_other_error_expected._Do_not_worry_about_it new file mode 100644 index 0000000000000..c740de7b30f27 --- /dev/null +++ b/build-system/tasks/release/static/amp_preconnect_polyfill_404_or_other_error_expected._Do_not_worry_about_it @@ -0,0 +1 @@ +Hello AMP \ No newline at end of file diff --git a/build-system/tasks/release/static/favicon.ico b/build-system/tasks/release/static/favicon.ico new file mode 100644 index 0000000000000..85a4d9fac03ba Binary files /dev/null and b/build-system/tasks/release/static/favicon.ico differ diff --git a/build-system/tasks/release/static/ping.html b/build-system/tasks/release/static/ping.html new file mode 100644 index 0000000000000..3e3d039c5634a --- /dev/null +++ b/build-system/tasks/release/static/ping.html @@ -0,0 +1 @@ +cdn diff --git a/build-system/tasks/release/static/preconnect.gif b/build-system/tasks/release/static/preconnect.gif new file mode 100644 index 0000000000000..b3aa80d843a92 Binary files /dev/null and b/build-system/tasks/release/static/preconnect.gif differ diff --git a/build-system/tasks/report-test-status.js b/build-system/tasks/report-test-status.js index 0c10fd90663bd..33c99ada48f43 100644 --- a/build-system/tasks/report-test-status.js +++ b/build-system/tasks/report-test-status.js @@ -16,128 +16,186 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const log = require('fancy-log'); -const requestPromise = require('request-promise'); -const {cyan, green, yellow} = require('ansi-colors'); +const fetch = require('node-fetch'); +const { + isCircleciBuild, + isGithubActionsBuild, + isPullRequestBuild, +} = require('../common/ci'); +const {ciJobUrl} = require('../common/ci'); +const {cyan, yellow} = require('../common/colors'); +const {getValidExperiments} = require('../common/utils'); const {gitCommitHash} = require('../common/git'); -const {travisJobUrl, isTravisPullRequestBuild} = require('../common/travis'); +const {log} = require('../common/logging'); +const {Targets, determineBuildTargets} = require('../pr-check/build-targets'); const reportBaseUrl = 'https://amp-test-status-bot.appspot.com/v0/tests'; -const IS_GULP_INTEGRATION = argv._[0] === 'integration'; -const IS_GULP_UNIT = argv._[0] === 'unit'; -const IS_GULP_E2E = argv._[0] === 'e2e'; - -const IS_LOCAL_CHANGES = !!argv.local_changes; -const IS_SAUCELABS = !!argv.saucelabs; -const IS_SAUCELABS_STABLE = !!argv.saucelabs && !!argv.stable; -const IS_SAUCELABS_BETA = !!argv.saucelabs && !!argv.beta; -const IS_DIST = !!argv.compiled; - -const TEST_TYPE_SUBTYPES = new Map([ - ['integration', ['local', 'minified', 'saucelabs-beta', 'saucelabs-stable']], - ['unit', ['local', 'local-changes', 'saucelabs']], - ['e2e', ['local']], -]); +const IS_AMP_INTEGRATION = argv._[0] === 'integration'; +const IS_AMP_UNIT = argv._[0] === 'unit'; +const IS_AMP_E2E = argv._[0] === 'e2e'; + +const TEST_TYPE_SUBTYPES = isGithubActionsBuild() + ? new Map([ + ['integration', ['firefox', 'safari', 'edge', 'ie']], + ['unit', ['firefox', 'safari', 'edge']], + ['e2e', ['firefox', 'safari']], + ]) + : isCircleciBuild() + ? new Map([ + [ + 'integration', + [ + 'unminified', + 'nomodule-prod', + 'nomodule-canary', + 'module-prod', + 'module-canary', + ...getValidExperiments(), + ], + ], + ['unit', ['unminified', 'local-changes']], + ['e2e', ['nomodule', ...getValidExperiments()]], + ]) + : new Map([]); const TEST_TYPE_BUILD_TARGETS = new Map([ - ['integration', ['RUNTIME', 'FLAG_CONFIG', 'INTEGRATION_TEST']], - ['unit', ['RUNTIME', 'UNIT_TEST']], - ['e2e', ['RUNTIME', 'FLAG_CONFIG', 'E2E_TEST']], + ['integration', [Targets.RUNTIME, Targets.INTEGRATION_TEST]], + ['unit', [Targets.RUNTIME, Targets.UNIT_TEST]], + ['e2e', [Targets.RUNTIME, Targets.E2E_TEST]], ]); +/** + * @return {string} + */ function inferTestType() { - if (IS_GULP_E2E) { - return 'e2e/local'; - } - - let type; - if (IS_GULP_UNIT) { - type = 'unit'; - } else if (IS_GULP_INTEGRATION) { - type = 'integration'; - } else { - return null; + // Determine type (early exit if there's no match). + const type = IS_AMP_E2E + ? 'e2e' + : IS_AMP_INTEGRATION + ? 'integration' + : IS_AMP_UNIT + ? 'unit' + : null; + if (type == null) { + throw new Error('No valid test type was inferred'); } - if (IS_LOCAL_CHANGES) { - return `${type}/local-changes`; - } + // Determine subtype (more specific cases come first). + const subtype = argv.local_changes + ? 'local-changes' + : argv.esm + ? 'module' + : argv.firefox + ? 'firefox' + : argv.safari + ? 'safari' + : argv.edge + ? 'edge' + : argv.ie + ? 'ie' + : argv.browsers == 'safari' + ? 'safari' + : argv.browsers == 'firefox' + ? 'firefox' + : argv.experiment + ? argv.experiment + : argv.compiled + ? 'nomodule' + : 'unminified'; + + return `${type}/${subtype}${maybeAddConfigSubtype()}`; +} - if (IS_SAUCELABS_BETA) { - return `${type}/saucelabs-beta`; - } else if (IS_SAUCELABS_STABLE) { - return `${type}/saucelabs-stable`; - } else if (IS_SAUCELABS) { - return `${type}/saucelabs`; - } else if (IS_DIST) { - return `${type}/minified`; +/** + * @return {string} + */ +function maybeAddConfigSubtype() { + if (isCircleciBuild() && argv.config) { + return `-${argv.config}`; } - - return `${type}/local`; + return ''; } -function postReport(type, action) { - if (type !== null && isTravisPullRequestBuild()) { +/** + * @param {string} type + * @param {string} action + * @return {Promise} + */ +async function postReport(type, action) { + if (type && isPullRequestBuild()) { const commitHash = gitCommitHash(); - return requestPromise({ - method: 'POST', - uri: `${reportBaseUrl}/${commitHash}/${type}/${action}`, - body: JSON.stringify({ - travisJobUrl: travisJobUrl(), - }), - headers: { - 'Content-Type': 'application/json', - }, - // Do not use `json: true` because the response is a string, not JSON. - }) - .then((body) => { - log( - green('INFO:'), - 'reported', - cyan(`${type}/${action}`), - 'to the test-status GitHub App' - ); - if (body.length > 0) { - log( - green('INFO:'), - 'response from test-status was', - cyan(body.substr(0, 100)) - ); - } - }) - .catch((error) => { - log( - yellow('WARNING:'), - 'failed to report', - cyan(`${type}/${action}`), - 'to the test-status GitHub App:\n', - error.message.substr(0, 100) - ); - return; + + try { + const url = `${reportBaseUrl}/${commitHash}/${type}/${action}`; + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify({ + ciJobUrl: ciJobUrl(), + }), + headers: { + 'Content-Type': 'application/json', + }, }); + const body = /** @type {string} */ (await response.text()); + + log('Reported', cyan(`${type}/${action}`), 'to GitHub'); + if (body.length > 0) { + log('Response was', cyan(body.substr(0, 100))); + } + } catch (error) { + log( + yellow('WARNING:'), + 'failed to report', + cyan(`${type}/${action}`), + 'to GitHub:\n', + error.message.substr(0, 100) + ); + } } - return Promise.resolve(); } -function reportTestErrored() { - return postReport(inferTestType(), 'report/errored'); +/** + * @return {Promise} + */ +async function reportTestErrored() { + await postReport(inferTestType(), 'report/errored'); } -function reportTestFinished(success, failed) { - return postReport(inferTestType(), `report/${success}/${failed}`); +/** + * @param {number|string} success + * @param {number|string} failed + * @return {Promise} + */ +async function reportTestFinished(success, failed) { + await postReport(inferTestType(), `report/${success}/${failed}`); } -function reportTestSkipped() { - return postReport(inferTestType(), 'skipped'); +/** + * @return {Promise} + */ +async function reportTestSkipped() { + await postReport(inferTestType(), 'skipped'); } -function reportTestStarted() { - return postReport(inferTestType(), 'started'); +/** + * @return {Promise} + */ +async function reportTestStarted() { + await postReport(inferTestType(), 'started'); } -async function reportAllExpectedTests(buildTargets) { +/** + * @return {Promise} + */ +async function reportAllExpectedTests() { + const buildTargets = determineBuildTargets(); for (const [type, subTypes] of TEST_TYPE_SUBTYPES) { const testTypeBuildTargets = TEST_TYPE_BUILD_TARGETS.get(type); + if (testTypeBuildTargets === undefined) { + throw new Error( + `Undefined test type ${type} for build targets ${buildTargets}` + ); + } const action = testTypeBuildTargets.some((target) => buildTargets.has(target) ) @@ -151,15 +209,19 @@ async function reportAllExpectedTests(buildTargets) { /** * Callback to the Karma.Server on('run_complete') event for simple test types. + * Optionally takes an object containing test results if they were run. * - * @param {!any} browsers - * @param {!Karma.TestResults} results + * @param {?{ + * error: string|number, + * failed: string|number, + * success: string|number, + * }} results */ -function reportTestRunComplete(browsers, results) { - if (results.error) { - reportTestErrored(); +async function reportTestRunComplete(results) { + if (!results || results.error) { + await reportTestErrored(); } else { - reportTestFinished(results.success, results.failed); + await reportTestFinished(results.success, results.failed); } } diff --git a/build-system/tasks/runtime-test/OWNERS b/build-system/tasks/runtime-test/OWNERS index 32c5ea6d75438..69a88bfe03860 100644 --- a/build-system/tasks/runtime-test/OWNERS +++ b/build-system/tasks/runtime-test/OWNERS @@ -1,5 +1,5 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ diff --git a/build-system/tasks/runtime-test/helpers-unit.js b/build-system/tasks/runtime-test/helpers-unit.js index 69369b4742605..e0e144bdcc128 100644 --- a/build-system/tasks/runtime-test/helpers-unit.js +++ b/build-system/tasks/runtime-test/helpers-unit.js @@ -18,15 +18,15 @@ const fs = require('fs'); const globby = require('globby'); const listImportsExports = require('list-imports-exports'); -const log = require('fancy-log'); const minimatch = require('minimatch'); const path = require('path'); const testConfig = require('../../test-configs/config'); +const {cyan, green} = require('../../common/colors'); const {execOrDie} = require('../../common/exec'); const {extensions, maybeInitializeExtensions} = require('../extension-helpers'); -const {gitDiffNameOnlyMaster} = require('../../common/git'); -const {green, cyan} = require('ansi-colors'); -const {isTravisBuild} = require('../../common/travis'); +const {gitDiffNameOnlyMain} = require('../../common/git'); +const {isCiBuild} = require('../../common/ci'); +const {log, logLocalDev} = require('../../common/logging'); const {reportTestSkipped} = require('../report-test-status'); const LARGE_REFACTOR_THRESHOLD = 50; @@ -40,7 +40,7 @@ let testsToRun = null; * @return {boolean} */ function isLargeRefactor() { - const filesChanged = gitDiffNameOnlyMaster(); + const filesChanged = gitDiffNameOnlyMain(); return filesChanged.length >= LARGE_REFACTOR_THRESHOLD; } @@ -51,11 +51,18 @@ function isLargeRefactor() { * @return {!Object} */ function extractCssJsFileMap() { - execOrDie('gulp css', {'stdio': 'ignore'}); + execOrDie('amp css', {'stdio': 'ignore'}); maybeInitializeExtensions(extensions); + /** @type {Object} */ const cssJsFileMap = {}; - // Adds an entry that maps a CSS file to a JS file + /** + * Adds an entry that maps a CSS file to a JS file + * + * @param {Object} cssData + * @param {string} cssBinaryName + * @param {Object} cssJsFileMap + */ function addCssJsEntry(cssData, cssBinaryName, cssJsFileMap) { const cssFilePath = `extensions/${cssData['name']}/${cssData['version']}/` + @@ -87,7 +94,9 @@ function extractCssJsFileMap() { */ function getImports(jsFile) { const jsFileContents = fs.readFileSync(jsFile, 'utf8'); - const {imports} = listImportsExports.parse(jsFileContents); + const {imports} = listImportsExports.parse(jsFileContents, [ + 'importAssertions', + ]); const files = []; const jsFileDir = path.dirname(jsFile); imports.forEach(function (file) { @@ -124,7 +133,11 @@ function getJsFilesFor(cssFile, cssJsFileMap) { return jsFiles; } -function getUnitTestsToRun() { +/** + * Computes the list of unit tests to run under difference scenarios + * @return {Promise|void>} + */ +async function getUnitTestsToRun() { log(green('INFO:'), 'Determining which unit tests to run...'); if (isLargeRefactor()) { @@ -132,7 +145,7 @@ function getUnitTestsToRun() { green('INFO:'), 'Skipping tests on local changes because this is a large refactor.' ); - reportTestSkipped(); + await reportTestSkipped(); return; } @@ -142,15 +155,15 @@ function getUnitTestsToRun() { green('INFO:'), 'No unit tests were directly affected by local changes.' ); - reportTestSkipped(); + await reportTestSkipped(); return; } - if (isTravisBuild() && tests.length > TEST_FILE_COUNT_THRESHOLD) { + if (isCiBuild() && tests.length > TEST_FILE_COUNT_THRESHOLD) { log( green('INFO:'), 'Several tests were affected by local changes. Running all tests below.' ); - reportTestSkipped(); + await reportTestSkipped(); return; } @@ -173,17 +186,26 @@ function unitTestsToRun() { return testsToRun; } const cssJsFileMap = extractCssJsFileMap(); - const filesChanged = gitDiffNameOnlyMaster(); + const filesChanged = gitDiffNameOnlyMain(); const {unitTestPaths} = testConfig; testsToRun = []; let srcFiles = []; + /** + * @param {string} file + * @return {boolean} + */ function isUnitTest(file) { return unitTestPaths.some((pattern) => { return minimatch(file, pattern); }); } + /** + * @param {string} testFile + * @param {string[]} srcFiles + * @return {boolean} + */ function shouldRunTest(testFile, srcFiles) { const filesImported = getImports(testFile); return ( @@ -193,8 +215,13 @@ function unitTestsToRun() { ); } - // Retrieves the set of unit tests that should be run - // for a set of source files. + /** + * Retrieves the set of unit tests that should be run + * for a set of source files. + * + * @param {string[]} srcFiles + * @return {string[]} + */ function getTestsFor(srcFiles) { const allUnitTests = globby.sync(unitTestPaths); return allUnitTests.filter((testFile) => { @@ -204,9 +231,12 @@ function unitTestsToRun() { filesChanged.forEach((file) => { if (!fs.existsSync(file)) { - if (!isTravisBuild()) { - log(green('INFO:'), 'Skipping', cyan(file), 'because it was deleted'); - } + logLocalDev( + green('INFO:'), + 'Skipping', + cyan(file), + 'because it was deleted' + ); } else if (isUnitTest(file)) { testsToRun.push(file); } else if (path.extname(file) == '.js') { diff --git a/build-system/tasks/runtime-test/helpers.js b/build-system/tasks/runtime-test/helpers.js index e09e4ca540caa..6f56cf49cf4df 100644 --- a/build-system/tasks/runtime-test/helpers.js +++ b/build-system/tasks/runtime-test/helpers.js @@ -17,42 +17,17 @@ const argv = require('minimist')(process.argv.slice(2)); const fs = require('fs'); -const log = require('fancy-log'); -const opn = require('opn'); const path = require('path'); -const { - reportTestErrored, - reportTestFinished, - reportTestRunComplete, -} = require('../report-test-status'); -const {green, yellow, cyan} = require('ansi-colors'); -const {isTravisBuild} = require('../../common/travis'); +const {cyan, green, red, yellow} = require('../../common/colors'); +const {isCiBuild} = require('../../common/ci'); +const {log, logWithoutTimestamp} = require('../../common/logging'); +const {maybePrintCoverageMessage} = require('../helpers'); +const {reportTestRunComplete} = require('../report-test-status'); const {Server} = require('karma'); -// Number of Sauce Lab browsers. Three was determined to be the lowest number we -// could set without causing a significant increase in Travis job time. See -// https://github.com/ampproject/amphtml/pull/27016 for the relevant discussion. -const BATCHSIZE = 3; const CHROMEBASE = argv.chrome_canary ? 'ChromeCanary' : 'Chrome'; const chromeFlags = []; -/** - * Validates arguments before test runs - * @return {boolean} - */ -function shouldNotRun() { - if (argv.saucelabs) { - if (!process.env.SAUCE_USERNAME) { - throw new Error('Missing SAUCE_USERNAME Env variable'); - } - if (!process.env.SAUCE_ACCESS_KEY) { - throw new Error('Missing SAUCE_ACCESS_KEY Env variable'); - } - } - - return false; -} - /** * Returns an array of ad types. * @return {!Array} @@ -97,7 +72,7 @@ function getAdTypes() { * Prints help messages for args if tests are being run for local development. */ function maybePrintArgvMessages() { - if (argv.nohelp || isTravisBuild()) { + if (argv.nohelp || isCiBuild()) { return; } @@ -108,7 +83,6 @@ function maybePrintArgvMessages() { edge: 'Running tests on Edge.', // eslint-disable-next-line chrome_canary: 'Running tests on Chrome Canary.', - saucelabs: 'Running tests on Sauce Labs browsers.', nobuild: 'Skipping build.', watch: 'Enabling watch mode. Editing and saving a file will cause the' + @@ -133,13 +107,13 @@ function maybePrintArgvMessages() { green('Launching'), cyan(CHROMEBASE), green('with flags'), - cyan(chromeFlags) + cyan(`${chromeFlags}`) ); } log( green('Run'), - cyan('gulp help'), + cyan('amp --tasks'), green('to see a list of all test flags.') ); log(green('⤷ Use'), cyan('--nohelp'), green('to silence these messages.')); @@ -168,6 +142,7 @@ function maybePrintArgvMessages() { log(green('Running tests against unminified code.')); } Object.keys(argv).forEach((arg) => { + /** @type {string} */ const message = argvMessages[arg]; if (message) { log(yellow(`--${arg}:`), green(message)); @@ -175,16 +150,6 @@ function maybePrintArgvMessages() { }); } -function maybePrintCoverageMessage() { - if (!argv.coverage || isTravisBuild()) { - return; - } - - const url = 'file://' + path.resolve('test/coverage/index.html'); - log(green('INFO:'), 'Generated code coverage report at', cyan(url)); - opn(url, {wait: false}); -} - /** * @param {Object} browser * @private @@ -192,213 +157,50 @@ function maybePrintCoverageMessage() { async function karmaBrowserComplete_(browser) { const result = browser.lastResult; result.total = result.success + result.failed + result.skipped; - // Initially we were reporting an error with reportTestErrored() when zero tests were detected (see #16851), - // but since Karma sometimes returns a transient, recoverable state, we will - // print a warning without reporting an error to the github test status. (see #24957) + // This used to be a warning with karma-browserify. See #16851 and #24957. + // Now, with karma-esbuild, this is a fatal error. See #34040. if (result.total == 0) { log( - yellow('WARNING:'), - 'Received a status with zero tests:', - cyan(JSON.stringify(result)) + red('ERROR:'), + 'Karma returned a result with zero tests.', + 'This usually indicates a transformation error. See logs above.' ); + log(cyan(JSON.stringify(result))); + process.exit(1); } } /** * @private */ -function karmaBrowsersReady_() { - console./*OK*/ log('\n'); +function karmaBrowserStart_() { + logWithoutTimestamp('\n'); log(green('Done. Running tests...')); } -/** - * @private - */ -function karmaRunStart_() { - if (!argv.saucelabs) { - log(green('Running tests locally...')); - } -} - -/** - * Runs tests in Sauce Labs in batches. - * - * If --stable is provided, runs tests only on stable browsers in Sauce Labs. - * If --beta is provided, runs tests only on beta browsers in Sauce Labs. Does not fail the build. - * If neither --stable nor --beta are provided, runs test on all browsers in Sauce Labs. - * - * @param {Object} config karma config - * @return {!Promise} exitCode - */ -async function runTestInSauceLabs(config) { - const flagSet = argv.stable || argv.beta; - const useStable = argv.stable || !flagSet; - const useBeta = argv.beta || !flagSet; - - const isBeta = (browserId) => browserId.toLowerCase().endsWith('_beta'); - const isStable = (browserId) => !isBeta(browserId); - - return await runTestInBatches_(config, { - beta: useBeta ? config.browsers.filter(isBeta) : [], - stable: useStable ? config.browsers.filter(isStable) : [], - }); -} - -/** - * Runs tests in batches. - * - * Splits stable and beta browsers to separate batches. Test failures in any - * of the stable browsers will return an exit code of 1, whereas test failures - * in any of the beta browsers will only print error messages, but will return - * an exit code of 0. - * - * @param {Object} config karma config - * @param {!Array} browsers browsers - * @return {number} exitCode - * @private - */ -async function runTestInBatches_(config, browsers) { - let errored = false; - let totalSuccess = 0; - let totalFailed = 0; - const partialTestRunCompleteFn = async (browsers, results) => { - if (results.error) { - errored = true; - } else { - totalSuccess += results.success; - totalFailed += results.failed; - } - }; - - const reportResults = async () => { - if (errored) { - await reportTestErrored(); - } else { - await reportTestFinished(totalSuccess, totalFailed); - } - }; - - if (browsers.stable.length) { - const allBatchesExitCodes = await runTestInBatchesWithBrowsers_( - 'stable', - browsers.stable, - config, - partialTestRunCompleteFn - ); - if (allBatchesExitCodes || errored) { - await reportResults(); - log( - yellow('Some tests have failed on'), - cyan('stable'), - yellow('browsers, so skipping running them on'), - cyan('beta'), - yellow('browsers.') - ); - return allBatchesExitCodes || Number(errored); - } - } - - if (browsers.beta.length) { - const allBatchesExitCodes = await runTestInBatchesWithBrowsers_( - 'beta', - browsers.beta, - config, - partialTestRunCompleteFn - ); - if (allBatchesExitCodes) { - log( - yellow('Some tests have failed on'), - cyan('beta'), - yellow('browsers.') - ); - log( - yellow('This is not currently a fatal error, but will become an'), - yellow('error once the beta browsers are released as next stable'), - yellow('version!') - ); - } - } - - await reportResults(); - return 0; -} - -/** - * Runs tests in named batch(es), with the specified browsers. - * - * @param {string} batchName a human readable name for the batch. - * @param {!Array{string}} browsers list of SauceLabs browsers as - * customLaunchers IDs. * - * @param {Object} config karma config - * @param {function()} runCompleteFn a function to execute on the - * `run_complete` event. It should take two arguments, (browser, results), - * and return nothing. - * @return {number} processExitCode - * @private - */ -async function runTestInBatchesWithBrowsers_( - batchName, - browsers, - config, - runCompleteFn -) { - let batch = 1; - let startIndex = 0; - let endIndex = BATCHSIZE; - const batchExitCodes = []; - - log( - green('Running tests on'), - cyan(browsers.length), - green('Sauce Labs'), - cyan(batchName), - green('browser(s)...') - ); - while (startIndex < endIndex) { - const configBatch = {...config}; - configBatch.browsers = browsers.slice(startIndex, endIndex); - log( - green('Batch'), - cyan(`#${batch}`) + green(':'), - cyan(configBatch.browsers.join(', ')) - ); - batchExitCodes.push(await createKarmaServer(configBatch, runCompleteFn)); - startIndex = batch * BATCHSIZE; - batch++; - endIndex = Math.min(batch * BATCHSIZE, browsers.length); - } - - return batchExitCodes.every((exitCode) => exitCode == 0) ? 0 : 1; -} - /** * Creates and starts karma server - * @param {!Object} configBatch - * @param {function()} runCompleteFn a function to execute on the - * `run_complete` event. It should take two arguments, (browser, results), - * and return nothing. + * @param {!Object} config * @return {!Promise} */ -async function createKarmaServer( - configBatch, - runCompleteFn = reportTestRunComplete -) { - let resolver; +async function createKarmaServer(config) { + let resolver, results_; const deferred = new Promise((resolverIn) => { resolver = resolverIn; }); - const karmaServer = new Server(configBatch, (exitCode) => { - maybePrintCoverageMessage(); + const karmaServer = new Server(config, async (exitCode) => { + await reportTestRunComplete(results_); + maybePrintCoverageMessage('test/coverage/index.html'); resolver(exitCode); }); karmaServer - .on('run_start', karmaRunStart_) - .on('browsers_ready', karmaBrowsersReady_) + .on('browser_start', karmaBrowserStart_) .on('browser_complete', karmaBrowserComplete_) - .on('run_complete', runCompleteFn); + .on('run_complete', (_browsers, results) => { + results_ = results; + }); karmaServer.start(); @@ -409,6 +211,4 @@ module.exports = { createKarmaServer, getAdTypes, maybePrintArgvMessages, - runTestInSauceLabs, - shouldNotRun, }; diff --git a/build-system/tasks/runtime-test/runtime-test-base.js b/build-system/tasks/runtime-test/runtime-test-base.js index e4cd9f690f9c7..8b32f8cd4b818 100644 --- a/build-system/tasks/runtime-test/runtime-test-base.js +++ b/build-system/tasks/runtime-test/runtime-test-base.js @@ -16,220 +16,374 @@ 'use strict'; const argv = require('minimist')(process.argv.slice(2)); -const karmaConfig = require('../karma.conf'); -const log = require('fancy-log'); -const testConfig = require('../../test-configs/config'); +const karmaConfig = require('../../test-configs/karma.conf'); +const { + commonIntegrationTestPaths, + commonUnitTestPaths, + integrationTestPaths, + karmaHtmlFixturesPath, + karmaJsPaths, + unitTestCrossBrowserPaths, + unitTestPaths, +} = require('../../test-configs/config'); const { createCtrlcHandler, exitCtrlcHandler, } = require('../../common/ctrlcHandler'); -const { - createKarmaServer, - getAdTypes, - runTestInSauceLabs, -} = require('./helpers'); const {app} = require('../../server/test-server'); +const {createKarmaServer, getAdTypes} = require('./helpers'); +const {cyan, green, red, yellow} = require('../../common/colors'); +const {dotWrappingWidth} = require('../../common/logging'); +const {getEsbuildBabelPlugin} = require('../../common/esbuild-babel'); const {getFilesFromArgv} = require('../../common/utils'); -const {green, yellow, cyan, red} = require('ansi-colors'); -const {isTravisBuild} = require('../../common/travis'); -const {reportTestStarted} = require('.././report-test-status'); +const {isCiBuild, isCircleciBuild} = require('../../common/ci'); +const {log} = require('../../common/logging'); +const {reportTestStarted} = require('../report-test-status'); +const {SERVER_TRANSFORM_PATH} = require('../../server/typescript-compile'); const {startServer, stopServer} = require('../serve'); const {unitTestsToRun} = require('./helpers-unit'); /** - * Updates the browsers based off of the test type - * being run (unit, integration, a4a) and test settings. - * Keeps the default spec as is if no matching settings are found. - * @param {!RuntimeTestConfig} config + * Used to print dots during esbuild + babel transforms + */ +let wrapCounter = 0; + +/** + * Used to lazy-require the HTML transformer function after the server is built. + */ +let transform; + +/** + * Consumes {@link karmaConfig} and dynamically populates fields based on test + * type and command line arguments. */ -function updateBrowsers(config) { - if (argv.saucelabs) { - if (config.testType == 'unit') { - Object.assign(config, {browsers: ['SL_Safari_12', 'SL_Firefox']}); +class RuntimeTestConfig { + /** @type {Array>} */ + plugins = []; + + /**@type {Record} */ + preprocessors = {}; + + /** @type {string[]} */ + reporters = []; + + client = {}; + + /** + * @param {string} testType + */ + constructor(testType) { + this.testType = testType; + /** + * TypeScript is used for typechecking here and is unable to infer the type + * after using Object.assign. This results in errors relating properties of + * which can never be `null` being treated as though they could be. + */ + Object.assign(this, karmaConfig); + this.updateBrowsers(); + this.updateReporters(); + this.updateFiles(); + this.updatePreprocessors(); + this.updateEsbuildConfig(); + this.updateClient(); + this.updateMiddleware(); + this.updateCoverageSettings(); + } + + /** + * Updates the set of preprocessors to run on HTML and JS files before testing. + * Notes: + * - The HTML transform is lazy-required because the server is built at startup. + * - We must use babel on windows until esbuild can natively downconvert to ES5. + */ + updatePreprocessors() { + const createHtmlTransformer = function () { + return function (content, _file, done) { + if (!transform) { + const outputDir = `../../../${SERVER_TRANSFORM_PATH}/dist/transform`; + transform = require(outputDir).transformSync; + } + done(transform(content)); + }; + }; + createHtmlTransformer.$inject = []; + this.plugins.push({ + 'preprocessor:htmlTransformer': ['factory', createHtmlTransformer], + }); + this.preprocessors[karmaHtmlFixturesPath] = ['htmlTransformer', 'html2js']; + for (const karmaJsPath of karmaJsPaths) { + this.preprocessors[karmaJsPath] = ['esbuild']; + } + } + + /** + * Updates the browsers based off of the test type + * being run (unit, integration, a4a) and test settings. + * Defaults to Chrome if no matching settings are found. + */ + updateBrowsers() { + if (argv.edge) { + Object.assign(this, { + browsers: [argv.headless ? 'EdgeHeadless' : 'Edge'], + }); return; } - if (config.testType == 'integration') { - Object.assign(config, { - browsers: [ - 'SL_Chrome', - 'SL_Firefox', - 'SL_Edge', - 'SL_Safari_12', - 'SL_Safari_11', - 'SL_IE', - // TODO(amp-infra): Evaluate and add more platforms here. - //'SL_Chrome_Android_7', - //'SL_iOS_11', - //'SL_iOS_12', - 'SL_Chrome_Beta', - 'SL_Firefox_Beta', - ], + if (argv.firefox) { + Object.assign(this, { + browsers: ['Firefox_flags'], + customLaunchers: { + // eslint-disable-next-line + Firefox_flags: { + base: 'Firefox', + flags: argv.headless ? ['-headless'] : [], + }, + }, }); return; } - throw new Error( - 'The --saucelabs flag is valid only for `gulp unit` and `gulp integration`.' - ); - } + if (argv.ie) { + Object.assign(this, { + browsers: ['IE'], + customLaunchers: { + IeNoAddOns: { + base: 'IE', + flags: ['-extoff'], + }, + }, + }); + return; + } - const chromeFlags = []; - if (argv.chrome_flags) { - argv.chrome_flags.split(',').forEach((flag) => { - chromeFlags.push('--'.concat(flag)); - }); - } + if (argv.safari) { + Object.assign(this, {browsers: ['SafariNative']}); + return; + } + + if (argv.chrome_canary) { + Object.assign(this, {browsers: ['ChromeCanary']}); + return; + } - const options = new Map(); - options - .set('chrome_canary', {browsers: ['ChromeCanary']}) - .set('chrome_flags', { - browsers: ['Chrome_flags'], - customLaunchers: { - // eslint-disable-next-line + if (argv.chrome_flags) { + const chromeFlags = []; + argv.chrome_flags.split(',').forEach((flag) => { + chromeFlags.push('--'.concat(flag)); + }); + Object.assign(this, { + browsers: ['Chrome_flags'], + customLaunchers: { + // eslint-disable-next-line Chrome_flags: { - base: 'Chrome', - flags: chromeFlags, + base: 'Chrome', + flags: chromeFlags, + }, }, - }, - }) - .set('edge', {browsers: ['Edge']}) - .set('firefox', {browsers: ['Firefox']}) - .set('headless', {browsers: ['Chrome_no_extensions_headless']}) - .set('ie', { - browsers: ['IE'], - customLaunchers: { - IeNoAddOns: { - base: 'IE', - flags: ['-extoff'], - }, - }, - }) - .set('safari', {browsers: ['Safari']}); + }); + return; + } - for (const [key, value] of options) { - if (argv.hasOwnProperty(key)) { - Object.assign(config, value); + if (argv.headless) { + Object.assign(this, {browsers: ['Chrome_no_extensions_headless']}); return; } - } -} -/** - * Get the appropriate files based off of the test type - * being run (unit, integration, a4a) and test settings. - * @param {string} testType - * @return {!Array} - */ -function getFiles(testType) { - let files; - - switch (testType) { - case 'unit': - files = testConfig.commonUnitTestPaths; - if (argv.files) { - return files.concat(getFilesFromArgv()); - } - if (argv.saucelabs) { - return files.concat(testConfig.unitTestOnSaucePaths); - } - if (argv.local_changes) { - return files.concat(unitTestsToRun()); - } - return files.concat(testConfig.unitTestPaths); - - case 'integration': - files = testConfig.commonIntegrationTestPaths; - if (argv.files) { - return files.concat(getFilesFromArgv()); - } - return files.concat(testConfig.integrationTestPaths); - - case 'a4a': - return testConfig.a4aTestPaths; - - default: - throw new Error(`Test type ${testType} was not recognized`); + // Default to Chrome. + Object.assign(this, { + browsers: [isCiBuild() ? 'Chrome_ci' : 'Chrome_no_extensions'], + }); } -} -/** - * Adds reporters to the default karma spec per test settings. - * Overrides default reporters for verbose settings. - * @param {!RuntimeTestConfig} config - */ -function updateReporters(config) { - if ( - (argv.testnames || argv.local_changes || argv.files || argv.verbose) && - !isTravisBuild() - ) { - config.reporters = ['mocha']; + /** + * Adds reporters to the default karma spec per test settings. + * Overrides default reporters for verbose settings. + */ + updateReporters() { + if ( + (argv.testnames || argv.local_changes || argv.files || argv.verbose) && + !isCiBuild() + ) { + this.reporters = ['mocha']; + } + + if (isCircleciBuild()) { + this.reporters.push('junit'); + this.junitReporter = { + outputFile: `result-reports/${this.testType}.xml`, + useBrowserName: false, + }; + } + + if (argv.coverage) { + this.reporters.push('coverage-istanbul'); + } + + if (argv.report) { + this.reporters.push('json-result'); + this.jsonResultReporter = { + outputFile: `result-reports/${this.testType}.json`, + }; + } } - if (argv.coverage) { - config.reporters.push('coverage-istanbul'); + /** + * Computes the set of files for Karma to load based on factors like test type, + * target browser, and flags. + */ + updateFiles() { + switch (this.testType) { + case 'unit': + if (argv.files) { + this.files = commonUnitTestPaths.concat(getFilesFromArgv()); + return; + } + if (argv.firefox || argv.safari || argv.edge) { + this.files = commonUnitTestPaths.concat(unitTestCrossBrowserPaths); + return; + } + if (argv.local_changes) { + this.files = commonUnitTestPaths.concat(unitTestsToRun()); + return; + } + this.files = commonUnitTestPaths.concat(unitTestPaths); + return; + + case 'integration': + if (argv.files) { + this.files = commonIntegrationTestPaths.concat(getFilesFromArgv()); + return; + } + this.files = commonIntegrationTestPaths.concat(integrationTestPaths); + return; + + default: + throw new Error(`Test type ${this.testType} was not recognized`); + } } - if (argv.saucelabs) { - config.reporters.push('saucelabs'); + /** + * Logs a message indicating the start of babel transforms. + */ + logBabelStart() { + wrapCounter = 0; + log( + green('Transforming tests with'), + cyan('esbuild'), + green('and'), + cyan('babel') + green('...') + ); } -} -class RuntimeTestConfig { - constructor(testType) { - this.testType = testType; + /** + * Prints a dot for every babel transform, with wrapping if needed. + */ + printBabelDot() { + process.stdout.write('.'); + if (++wrapCounter >= dotWrappingWidth) { + wrapCounter = 0; + process.stdout.write('\n'); + } + } - Object.assign(this, karmaConfig); - updateBrowsers(this); - updateReporters(this); - this.files = getFiles(this.testType); - this.singleRun = !argv.watch && !argv.w; - this.client.mocha.grep = !!argv.grep; - this.client.verboseLogging = !!argv.verbose || !!argv.v; - this.client.captureConsole = !!argv.verbose || !!argv.v || !!argv.files; - this.browserify.configure = function (bundle) { - bundle.on('prebundle', function () { - log( - green('Transforming tests with'), - cyan('browserify') + green('...') - ); - }); + /** + * Updates the esbuild config in the karma spec so esbuild can run with it. + */ + updateEsbuildConfig() { + const importPathPlugin = { + name: 'import-path', + setup(build) { + build.onResolve({filter: /^[\w-]+$/}, (file) => { + if (file.path === 'stream') { + return {path: require.resolve('stream-browserify'), namespace: ''}; + } + }); + }, + }; + const babelPlugin = getEsbuildBabelPlugin( + /* callerName */ 'test', + /* enableCache */ true, + /* preSetup */ this.logBabelStart, + /* postLoad */ this.printBabelDot + ); + this.esbuild = { + target: 'es5', + define: { + 'process.env.NODE_DEBUG': 'false', + 'process.env.NODE_ENV': '"test"', + }, + plugins: [importPathPlugin, babelPlugin], + sourcemap: 'inline', }; + } - // c.client is available in test browser via window.parent.karma.config + /** + * Updates the client so that tests can access karma state. This is available in + * the browser via window.parent.karma.config. + */ + updateClient() { + this.singleRun = !argv.watch; + this.client.mocha.grep = !!argv.grep; + this.client.verboseLogging = !!argv.verbose; + this.client.captureConsole = !!argv.verbose || !!argv.files; this.client.amp = { useCompiledJs: !!argv.compiled, - saucelabs: !!argv.saucelabs, adTypes: getAdTypes(), mochaTimeout: this.client.mocha.timeout, testServerPort: this.client.testServerPort, + isModuleBuild: !!argv.esm, // Used by skip matchers in testing/test-config.js }; + } - if (argv.coverage && this.testType != 'a4a') { + /** + * Inserts the AMP dev server into the middleware used by the Karma server. + */ + updateMiddleware() { + const createDevServerMiddleware = function () { + return require(require.resolve('../../server/app.js')); + }; + this.plugins.push({ + 'middleware:devServer': ['factory', createDevServerMiddleware], + }); + this.beforeMiddleware = ['devServer']; + } + + /** + * Updates the Karma config to gather coverage info if coverage is enabled. + */ + updateCoverageSettings() { + if (argv.coverage) { this.plugins.push('karma-coverage-istanbul-reporter'); this.coverageIstanbulReporter = { dir: 'test/coverage', - reports: isTravisBuild() - ? ['lcovonly'] - : ['html', 'text', 'text-summary'], - 'report-config': {lcovonly: {file: `lcov-${testType}.info`}}, + reports: isCiBuild() ? ['lcovonly'] : ['html', 'text', 'text-summary'], + 'report-config': {lcovonly: {file: `lcov-${this.testType}.info`}}, }; } } } class RuntimeTestRunner { + /** + * + * @param {RuntimeTestConfig} config + */ constructor(config) { this.config = config; this.env = null; this.exitCode = 0; } + /** + * @return {Promise} + */ async maybeBuild() { throw new Error('maybeBuild method must be overridden'); } + /** + * @return {Promise} + */ async setup() { await this.maybeBuild(); await startServer({ @@ -238,31 +392,28 @@ class RuntimeTestRunner { port: this.config.client.testServerPort, middleware: () => [app], }); - const handlerProcess = createCtrlcHandler(`gulp ${this.config.testType}`); - + const handlerProcess = createCtrlcHandler(`amp ${this.config.testType}`); this.env = new Map().set('handlerProcess', handlerProcess); } + /** + * @return {Promise} + */ async run() { - reportTestStarted(); - - if (argv.saucelabs) { - this.exitCode = await runTestInSauceLabs(this.config); - } else { - this.exitCode = await createKarmaServer(this.config); - } + await reportTestStarted(); + this.exitCode = await createKarmaServer(this.config); } + /** + * @return {Promise} + */ async teardown() { await stopServer(); - exitCtrlcHandler(this.env.get('handlerProcess')); - + exitCtrlcHandler(/** @type {Map} */ (this.env).get('handlerProcess')); if (this.exitCode != 0) { - log( - red('ERROR:'), - yellow(`Karma test failed with exit code ${this.exitCode}`) - ); - process.exitCode = this.exitCode; + const message = `Karma test failed with exit code ${this.exitCode}`; + log(red('ERROR:'), yellow(message)); + throw new Error(message); } } } diff --git a/build-system/tasks/serve.js b/build-system/tasks/serve.js index fef44f84997c2..05756a362bf18 100644 --- a/build-system/tasks/serve.js +++ b/build-system/tasks/serve.js @@ -19,29 +19,53 @@ const connect = require('gulp-connect'); const debounce = require('debounce'); const globby = require('globby'); const header = require('connect-header'); -const log = require('fancy-log'); const minimist = require('minimist'); const morgan = require('morgan'); +const open = require('open'); +const os = require('os'); const path = require('path'); -const watch = require('gulp-watch'); const { + lazyBuild3pVendor, lazyBuildExtensions, lazyBuildJs, - preBuildRuntimeFiles, preBuildExtensions, + preBuildRuntimeFiles, } = require('../server/lazy-build'); +const { + SERVER_TRANSFORM_PATH, + buildNewServer, +} = require('../server/typescript-compile'); const {createCtrlcHandler} = require('../common/ctrlcHandler'); -const {cyan, green, red} = require('ansi-colors'); -const {distNailgunPort, stopNailgunServer} = require('./nailgun'); -const {exec} = require('../common/exec'); +const {cyan, green, red} = require('../common/colors'); const {logServeMode, setServeMode} = require('../server/app-utils'); +const {log} = require('../common/logging'); const {watchDebounceDelay} = require('./helpers'); +const {watch} = require('chokidar'); + +/** + * @typedef {{ + * name: string, + * port: string, + * root: string, + * host: string, + * debug?: boolean, + * silent?: boolean, + * https?: boolean, + * preferHttp1?: boolean, + * liveReload?: boolean, + * middleware?: function[], + * startedcallback?: function, + * serverInit?: function, + * fallback?: string, + * index: boolean | string | string[], + * }} + */ +let GulpConnectOptionsDef; const argv = minimist(process.argv.slice(2), {string: ['rtv']}); -// Used by new server implementation -const typescriptBinary = './node_modules/typescript/bin/tsc'; -const transformsPath = 'build-system/server/new-server/transforms'; +const HOST = argv.host || '0.0.0.0'; +const PORT = argv.port || '8000'; // Used for logging. let url = null; @@ -50,7 +74,7 @@ let quiet = !!argv.quiet; // Used for live reload. const serverFiles = globby.sync([ 'build-system/server/**', - `!${transformsPath}/dist/**`, + `!${SERVER_TRANSFORM_PATH}/dist/**`, ]); // Used to enable / disable lazy building. @@ -98,6 +122,7 @@ function getMiddleware() { if (lazyBuild) { middleware.push(lazyBuildExtensions); middleware.push(lazyBuildJs); + middleware.push(lazyBuild3pVendor); } return middleware; } @@ -114,6 +139,7 @@ async function startServer( serverOptions = {}, modeOptions = {} ) { + await buildNewServer(); if (serverOptions.lazyBuild) { lazyBuild = serverOptions.lazyBuild; } @@ -126,11 +152,13 @@ async function startServer( started = resolve; }); setServeMode(modeOptions); + + /** @type {GulpConnectOptionsDef} */ const options = { name: 'AMP Dev Server', root: process.cwd(), - host: argv.host || 'localhost', - port: argv.port || 8000, + host: HOST, + port: PORT, https: argv.https, preferHttp1: true, silent: true, @@ -139,28 +167,31 @@ async function startServer( }; connect.server(options, started); await startedPromise; - url = `http${options.https ? 's' : ''}://${options.host}:${options.port}`; - log(green('Started'), cyan(options.name), green('at'), cyan(url)); - logServeMode(); -} -/** - * Builds the new server by converting typescript transforms to JS - */ -function buildNewServer() { - const buildCmd = `${typescriptBinary} -p ${transformsPath}/tsconfig.json`; - log( - green('Building'), - cyan('AMP Dev Server'), - green('at'), - cyan(`${transformsPath}/dist`) + green('...') - ); - const result = exec(buildCmd, {'stdio': ['inherit', 'inherit', 'pipe']}); - if (result.status != 0) { - const err = new Error('Could not build AMP Dev Server'); - err.showStack = false; - throw err; + /** + * @param {string} host + * @return {string} + */ + function makeUrl(host) { + return `http${options.https ? 's' : ''}://${host}:${options.port}`; + } + + url = makeUrl(options.host); + log(green('Started'), cyan(options.name), green('at:')); + log('\t', cyan(url)); + for (const device of Object.entries(os.networkInterfaces())) { + for (const detail of device[1] ?? []) { + if (detail.family === 'IPv4') { + log('\t', cyan(makeUrl(detail.address))); + } + } + } + if (argv.coverage == 'live') { + const covUrl = `${url}/coverage`; + log(green('Collecting live code coverage at'), cyan(covUrl)); + await Promise.all([open(covUrl), open(url)]); } + logServeMode(); } /** @@ -177,9 +208,6 @@ function resetServerFiles() { * Stops the currently running server */ async function stopServer() { - if (lazyBuild && argv.compiled) { - await stopNailgunServer(distNailgunPort); - } if (url) { connect.serverClose(); log(green('Stopped server at'), cyan(url)); @@ -191,14 +219,12 @@ async function stopServer() { * Closes the existing server and restarts it */ async function restartServer() { - await stopServer(); - if (argv.new_server) { - try { - buildNewServer(); - } catch { - log(red('ERROR:'), 'Could not build', cyan('AMP Dev Server')); - return; - } + stopServer(); + try { + await buildNewServer(); + } catch { + log(red('ERROR:'), 'Could not rebuild', cyan('AMP Server')); + return; } resetServerFiles(); startServer(); @@ -213,7 +239,7 @@ async function performPreBuildSteps() { } /** - * Entry point of the `gulp serve` task. + * Entry point of the `amp serve` task. */ async function serve() { await doServe(); @@ -228,10 +254,7 @@ async function doServe(lazyBuild = false) { const watchFunc = async () => { await restartServer(); }; - watch(serverFiles, debounce(watchFunc, watchDebounceDelay)); - if (argv.new_server) { - buildNewServer(); - } + watch(serverFiles).on('change', debounce(watchFunc, watchDebounceDelay)); await startServer({}, {lazyBuild}, {}); if (lazyBuild) { await performPreBuildSteps(); @@ -239,26 +262,29 @@ async function doServe(lazyBuild = false) { } module.exports = { - buildNewServer, serve, doServe, startServer, stopServer, + HOST, + PORT, }; /* eslint "google-camelcase/google-camelcase": 0 */ serve.description = 'Starts a webserver at the project root directory'; serve.flags = { - host: ' Hostname or IP address to bind to (default: localhost)', - port: ' Specifies alternative port (default: 8000)', - https: ' Use HTTPS server', - quiet: " Run in quiet mode and don't log HTTP requests", - cache: ' Make local resources cacheable by the browser', - no_caching_extensions: ' Disable caching for extensions', - new_server: ' Use new server transforms', - compiled: ' Serve minified JS', - esm: ' Serve ESM JS (requires the use of --new_server)', - cdn: ' Serve current prod JS', - rtv: ' Serve JS from the RTV provided', + host: 'Hostname or IP address to bind to (default: localhost)', + port: 'Specifies alternative port (default: 8000)', + https: 'Use HTTPS server', + quiet: "Run in quiet mode and don't log HTTP requests", + cache: 'Make local resources cacheable by the browser', + no_caching_extensions: 'Disable caching for extensions', + compiled: 'Serve minified JS', + esm: 'Serve ESM JS (uses the new typescript server transforms)', + cdn: 'Serve current prod JS', + rtv: 'Serve JS from the RTV provided', + coverage: + 'Serve instrumented code to collect coverage info; use ' + + '--coverage=live to auto-report coverage on page unload', }; diff --git a/build-system/tasks/server-tests.js b/build-system/tasks/server-tests.js index dcffedfd2cd52..619703fc26512 100644 --- a/build-system/tasks/server-tests.js +++ b/build-system/tasks/server-tests.js @@ -17,17 +17,18 @@ const assert = require('assert'); const fs = require('fs'); const globby = require('globby'); -const gulp = require('gulp'); -const log = require('fancy-log'); const path = require('path'); const posthtml = require('posthtml'); -const through = require('through2'); -const {buildNewServer} = require('../tasks/serve'); -const {cyan, green, red} = require('ansi-colors'); -const {isTravisBuild} = require('../common/travis'); +const { + log, + logWithoutTimestamp, + logWithoutTimestampLocalDev, +} = require('../common/logging'); +const {buildNewServer} = require('../server/typescript-compile'); +const {cyan, green, red} = require('../common/colors'); const transformsDir = path.resolve('build-system/server/new-server/transforms'); -const inputPaths = [`${transformsDir}/**/*input.html`]; +const inputPaths = [`${transformsDir}/**/input.html`]; let passed = 0; let failed = 0; @@ -36,10 +37,10 @@ let failed = 0; * Extracts the input for a test from its input file. * * @param {string} inputFile - * @return {string} + * @return {Promise} */ async function getInput(inputFile) { - return await fs.promises.readFile(inputFile, 'utf8'); + return fs.promises.readFile(inputFile, 'utf8'); } /** @@ -49,36 +50,65 @@ async function getInput(inputFile) { * @return {string} */ function getTestName(inputFile) { - const testPath = path.relative(transformsDir, inputFile); - const transformName = path.dirname(path.dirname(testPath)); - const testSuffix = path.basename(testPath).replace('-input.html', ''); + const transformName = path.basename(getTransformerDir(inputFile)); + const testSuffix = getTestPath(inputFile); return `${transformName} → ${testSuffix}`; } /** - * Extracts the expected output for a test from its output file. + * Computes the directory of the transformer used in the test. + * + * @param {string} inputFile + * @return {string} + */ +function getTransformerDir(inputFile) { + // The prior assumption is that the transformer is in the parent directory. + // However, with Jest (and to mimic Jest), this is not necessarily true. + let transformerDir = inputFile; + while (path.basename(path.dirname(transformerDir)) != 'transforms') { + transformerDir = path.dirname(transformerDir); + } + return transformerDir; +} + +/** + * Computes the relative dirname of the input from the test directory. + * For example, if the input is "test/foo/bar/input.html", we get "foo/bar". * * @param {string} inputFile * @return {string} */ +function getTestPath(inputFile) { + let testDir = inputFile; + while (path.basename(testDir) != 'test') { + testDir = path.dirname(testDir); + } + return path.dirname(path.relative(testDir, inputFile)); +} + +/** + * Extracts the expected output for a test from its output file. + * + * @param {string} inputFile + * @return {Promise} + */ async function getExpectedOutput(inputFile) { const expectedOutputFile = inputFile.replace('input.html', 'output.html'); - return await fs.promises.readFile(expectedOutputFile, 'utf8'); + return fs.promises.readFile(expectedOutputFile, 'utf8'); } /** * Extracts the JS transform for a test from its transform file. - * * @param {string} inputFile - * @return {string} + * @param {!Object} extraOptions + * @return {Promise} */ -async function getTransform(inputFile) { - const transformDir = path.dirname(path.dirname(inputFile)); +async function getTransform(inputFile, extraOptions) { + const transformDir = getTransformerDir(inputFile); const parsed = path.parse(transformDir); const transformPath = path.join(parsed.dir, 'dist', parsed.base); const transformFile = (await globby(path.resolve(transformPath, '*.js')))[0]; - // TODO(rsimha): Change require to import when node v14 is the active LTS. - return require(transformFile).default; + return (await import(transformFile)).default.default(extraOptions); } /** @@ -86,10 +116,25 @@ async function getTransform(inputFile) { * * @param {string} transform * @param {string} input - * @return {string} + * @return {Promise} */ async function getOutput(transform, input) { - return (await posthtml(transform).process(input)).html; + return (await posthtml(/** @type {*} */ (transform)).process(input)).html; +} + +/** + * Loads optional arguments residing in a options.json file, if any. + * + * @param {string} inputFile + * @return {!Object} + */ +function loadOptions(inputFile) { + const transformDir = path.dirname(inputFile); + const optionsPath = path.join(transformDir, 'options.json'); + if (fs.existsSync(optionsPath)) { + return require(optionsPath); + } + return {}; } /** @@ -100,10 +145,12 @@ async function getOutput(transform, input) { */ function logError(testName, err) { const {message} = err; - console.log(red('✖'), 'Failed', cyan(testName)); - console.group(); - console.log(message.split('\n').splice(3).join('\n')); - console.groupEnd(); + logWithoutTimestamp(red('✖'), 'Failed', cyan(testName)); + console /*OK*/ + .group(); + logWithoutTimestamp(message.split('\n').splice(3).join('\n')); + console /*OK*/ + .groupEnd(); } /** @@ -115,9 +162,7 @@ function reportResult() { `(${cyan(passed)} passed, ${cyan(failed)} failed).`; if (failed > 0) { log(red('ERROR:'), result); - const err = new Error('Tests failed'); - err.showStack = false; - throw err; + throw new Error('Tests failed'); } else { log(green('SUCCESS:'), result); } @@ -126,40 +171,37 @@ function reportResult() { /** * Runs the test in a single input file * - * @return {!ReadableStream} + * @param {string} inputFile */ -function runTest() { - return through.obj(async (file, enc, cb) => { - const inputFile = file.path; - const input = await getInput(inputFile); - const testName = getTestName(inputFile); - const expectedOutput = await getExpectedOutput(inputFile); - const transform = await getTransform(inputFile); - const output = await getOutput(transform, input); - try { - assert.strictEqual(output, expectedOutput); - } catch (err) { - ++failed; - logError(testName, err); - cb(); - return; - } - ++passed; - if (!isTravisBuild()) { - console.log(green('✔'), 'Passed', cyan(testName)); - } - cb(); - }); +async function runTest(inputFile) { + const testName = getTestName(inputFile); + const [input, expectedOutput, transform] = await Promise.all([ + getInput(inputFile), + getExpectedOutput(inputFile), + getTransform(inputFile, loadOptions(inputFile)), + ]); + const output = await getOutput(transform, input); + try { + assert.strictEqual(output, expectedOutput); + } catch (err) { + ++failed; + logError(testName, err); + return; + } + ++passed; + logWithoutTimestampLocalDev(green('✔'), 'Passed', cyan(testName)); } /** - * Tests for AMP server custom transforms. Entry point for `gulp server-tests`. - * - * @return {!Vinyl} + * Tests for AMP server custom transforms. Entry point for `amp server-tests`. */ -function serverTests() { - buildNewServer(); - return gulp.src(inputPaths).pipe(runTest()).on('end', reportResult); +async function serverTests() { + await buildNewServer(); + const inputFiles = globby.sync(inputPaths); + for (const inputFile of inputFiles) { + await runTest(inputFile); + } + reportResult(); } module.exports = { diff --git a/build-system/tasks/size.js b/build-system/tasks/size.js deleted file mode 100644 index ad1a01f792ab5..0000000000000 --- a/build-system/tasks/size.js +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright 2015 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. - */ -'use strict'; - -const del = require('del'); -const fs = require('fs'); -const gulp = require('gulp'); -const gzipSize = require('gzip-size'); -const PluginError = require('plugin-error'); -const prettyBytes = require('pretty-bytes'); -const table = require('text-table'); -const through = require('through2'); - -const tempFolderName = '__size-temp'; - -const MIN_FILE_SIZE_POS = 0; -const FILENAME_POS = 2; - -// normalized table headers -const tableHeaders = [ - ['max', 'min', 'gzip', 'file'], - ['---', '---', '---', '---'], -]; - -const tableOptions = { - align: ['r', 'r', 'r', 'l'], - hsep: ' | ', -}; - -/** - * Returns a number greater than -1 if item is found within the array, else - * returns -1. - * - * @param {!Array} rows - * @param {!fuction(string)} predicate - * @return {number} - */ -function findMaxIndexByFilename(rows, predicate) { - for (let i = 0; i < rows.length; i++) { - const curRow = rows[i]; - const curFilename = curRow[FILENAME_POS]; - if (predicate(curFilename)) { - return i; - } - } - return -1; -} - -/** - * Mutates the rows in place and merges the minified file entry as well - * as its unminified file entry counterpart. - * @param {!Array} rows - * @param {string} minFilename - * @param {string} maxFilename - * @param {boolean} mergeNames - */ -function normalizeRow(rows, minFilename, maxFilename, mergeNames) { - const minIndex = findMaxIndexByFilename(rows, function (filename) { - return filename == minFilename; - }); - const maxIndex = findMaxIndexByFilename(rows, function (filename) { - return filename == maxFilename; - }); - - if (maxIndex != -1 && minIndex != -1) { - if (mergeNames) { - rows[minIndex][FILENAME_POS] += ' / ' + rows[maxIndex][FILENAME_POS]; - } - rows[minIndex].unshift(rows[maxIndex][MIN_FILE_SIZE_POS]); - rows.splice(maxIndex, 1); - } -} - -/** - * Call normalizeRow on the core file, integration file and all extensions. - * @param {!Array} rows - * @return {!Array} - */ -function normalizeRows(rows) { - // normalize amp.js - normalizeRow(rows, 'v0.js', 'amp.js', true); - - // normalize integration.js - normalizeRow(rows, 'current-min/f.js', 'current/integration.js', true); - - normalizeRow( - rows, - 'current-min/ampcontext-v0.js', - 'current/ampcontext-lib.js', - true - ); - - normalizeRow( - rows, - 'current-min/iframe-transport-client-v0.js', - 'current/iframe-transport-client-lib.js', - true - ); - - // normalize alp.js - normalizeRow(rows, 'alp.js', 'alp.max.js', true); - - // normalize amp-shadow.js - normalizeRow(rows, 'shadow-v0.js', 'amp-shadow.js', true); - - normalizeRow(rows, 'amp4ads-v0.js', 'amp-inabox.js', true); - - normalizeRow(rows, 'amp4ads-host-v0.js', 'amp-inabox-host.js', true); - - normalizeRow(rows, 'examiner.js', 'examiner.max.js', true); - - normalizeRow(rows, 'ww.js', 'ww.max.js', true); - - // normalize sw.js - normalizeRow(rows, 'sw.js', 'sw.max.js', true); - normalizeRow(rows, 'sw-kill.js', 'sw-kill.max.js', true); - - // normalize extensions - let curName = null; - let i = rows.length; - // we are mutating in place... kind of icky but this will do fow now. - while (i--) { - curName = rows[i][FILENAME_POS]; - if (/^v0/.test(curName)) { - normalizeExtension(rows, curName); - } - } - return rows; -} - -/** - * Finds the counterpart entry of the extension file, wether it be - * the unminified or the minified counterpart. - * @param {!Array} rows - * @param {string} filename - */ -function normalizeExtension(rows, filename) { - const isMax = /\.max\.js$/.test(filename); - const counterpartName = filename.replace( - /(v0\/.*?)(\.max)?(\.js)$/, - function (full, grp1, grp2, grp3) { - if (isMax) { - return grp1 + grp3; - } - return full; - } - ); - - if (isMax) { - normalizeRow(rows, counterpartName, filename, false); - } else { - normalizeRow(rows, filename, counterpartName, false); - } -} - -/** - * Through2 transform function - Tracks the original size and the gzipped size - * of the file content in the rows array. Passes the file and/or err to the - * callback when finished. - * @param {!Array>} rows array to store content size information - * @param {!File} file File to process - * @param {string} enc Encoding (not used) - * @param {function(?Error, !File)} cb Callback function - */ -function onFileThrough(rows, file, enc, cb) { - if (file.isNull()) { - cb(null, file); - return; - } - - if (file.isStream()) { - cb(new PluginError('size', 'Stream not supported')); - return; - } - - rows.push([ - prettyBytes(file.contents.length), - prettyBytes(gzipSize.sync(file.contents)), - file.relative, - ]); - - cb(null, file); -} - -/** - * Through2 flush function - combines headers with the rows and generates - * a text-table of the content size information to log to the console and the - * test/size.txt logfile. - * - * @param {!Array>} rows array of content size information - * @param {function()} cb Callback function - */ -function onFileThroughEnd(rows, cb) { - rows = normalizeRows(rows); - rows.unshift.apply(rows, tableHeaders); - const tbl = table(rows, tableOptions); - console /* OK*/ - .log(tbl); - fs.writeFileSync('test/size.txt', tbl); - cb(); -} - -/** - * Setup through2 to capture size information using the above transform and - * flush functions on a stream - * @return {!Stream} a Writable Stream - */ -function sizer() { - const rows = []; - return through.obj( - onFileThrough.bind(null, rows), - onFileThroughEnd.bind(null, rows) - ); -} - -/** - * Pipe the distributable js files through the sizer to get a record of - * the content size before and after gzip and cleanup any temporary file - * output from the process. - */ -function size() { - gulp - .src([ - 'dist/**/*.js', - '!dist/**/*-latest.js', - '!dist/**/*check-types.js', - '!dist/**/amp-viewer-host.max.js', - 'dist.3p/{current,current-min}/**/*.js', - ]) - .pipe(sizer()) - .pipe(gulp.dest(tempFolderName)) - .on('end', del.bind(null, [tempFolderName])); -} - -module.exports = { - size, -}; - -size.description = 'Runs a report on artifact size'; diff --git a/build-system/tasks/storybook/OWNERS b/build-system/tasks/storybook/OWNERS index 5526b7d53b21e..865f2a0152730 100644 --- a/build-system/tasks/storybook/OWNERS +++ b/build-system/tasks/storybook/OWNERS @@ -1,14 +1,15 @@ // For an explanation of the OWNERS rules and syntax, see: -// https://github.com/ampproject/amp-github-apps/blob/master/owners/OWNERS.example +// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example { rules: [ { owners: [ {name: 'ampproject/wg-infra'}, - {name: 'ampproject/wg-ui-and-a11y'}, + {name: 'ampproject/wg-bento'}, + {name: 'ampproject/wg-components'}, {name: 'ampproject/wg-performance'}, - {name: 'wassgha', notify: true}, + {name: 'alanorozco', notify: true}, ], }, { diff --git a/build-system/tasks/storybook/amp-env/decorator.js b/build-system/tasks/storybook/amp-env/decorator.js deleted file mode 100644 index c149d100b19a1..0000000000000 --- a/build-system/tasks/storybook/amp-env/decorator.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2020 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. - */ - -import * as Preact from '../../../../src/preact'; -import {makeDecorator} from '@storybook/addons'; -import flush from 'styled-jsx/server'; -import render from 'preact-render-to-string/jsx'; - -export default makeDecorator({ - name: 'withAmpDecorator', - parameterName: 'amp', - wrapper: (getStory, context) => { - const contents = render(getStory(context)); - const styleElements = flush(); - const styles = render( - - ${(context?.parameters?.extensions || []).map( - (ext) => - `` - )} - ${styles} - - - ${contents} - - - `; - const blob = new Blob([ampHtml], {type: 'text/html'}); - - return ( - - - - - - - - - - - - - - - - - - - -
-

What is your favorite movie?

- - -
-
-
- - - - - - -
-

What is your favorite movie?

- - -
-
-
- - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/amp-story/rtl.html b/examples/amp-story/rtl.html index 4aaece60bbf5f..e8ac47dda8fc3 100644 --- a/examples/amp-story/rtl.html +++ b/examples/amp-story/rtl.html @@ -298,9 +298,6 @@

Join the worldwide AMP Roadshow

- - - diff --git a/examples/amp-story/share.html b/examples/amp-story/share.html new file mode 100644 index 0000000000000..8879898b52129 --- /dev/null +++ b/examples/amp-story/share.html @@ -0,0 +1,52 @@ + + + + + + + My Story + + + + + + + + + + + +

Check the share providers

+

Should show Facebook and Whatsapp

+
+
+ + + + +
+ + diff --git a/examples/amp-story/show-tooltip.html b/examples/amp-story/show-tooltip.html index c3beab60b6a1f..8ecc4dee97994 100644 --- a/examples/amp-story/show-tooltip.html +++ b/examples/amp-story/show-tooltip.html @@ -3,8 +3,8 @@ - + + My Story @@ -58,6 +58,15 @@

fill

+ + + + + + + diff --git a/examples/amp-story/text-background-color.html b/examples/amp-story/text-background-color.html index 0153e8ffb920a..3d7617fe2c528 100644 --- a/examples/amp-story/text-background-color.html +++ b/examples/amp-story/text-background-color.html @@ -62,7 +62,7 @@

Full block background color

Text only background color because I'm in a <span>

- +

Call to action example

diff --git a/examples/amp-story/video-one-page.html b/examples/amp-story/video-one-page.html new file mode 100644 index 0000000000000..059b31f3a7431 --- /dev/null +++ b/examples/amp-story/video-one-page.html @@ -0,0 +1,53 @@ + + + + + + + + Single page video story [test] + + + + + + + + + + + > + + + + + + + + 1 + + + + + diff --git a/examples/amp-story/video-one-page2.html b/examples/amp-story/video-one-page2.html new file mode 100644 index 0000000000000..feaf20ce7e2bd --- /dev/null +++ b/examples/amp-story/video-one-page2.html @@ -0,0 +1,53 @@ + + + + + + + + Single page video story [test] + + + + + + + + + + + > + + + + + + + + 2 + + + + + diff --git a/examples/amp-story/videos-cdn.html b/examples/amp-story/videos-cdn.html new file mode 100644 index 0000000000000..ba0ac126f187b --- /dev/null +++ b/examples/amp-story/videos-cdn.html @@ -0,0 +1,239 @@ + + + + + + + + 10 videos with flexible bitrate + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + + 4 + + + + + + + + + 5 + + + + + + + + + 6 + + + + + + + + + 7 + + + + + + + + + 8 + + + + + + + + + 9 + + + + + + + + + 10 + + + + diff --git a/examples/amp-story/videos-google-cache.html b/examples/amp-story/videos-google-cache.html new file mode 100644 index 0000000000000..36a6c477e8298 --- /dev/null +++ b/examples/amp-story/videos-google-cache.html @@ -0,0 +1,76 @@ + + + + + + + + Example story with Google cache videos + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + 2 + + + + + + + + + 2 + + + + diff --git a/examples/amp-story/videos.html b/examples/amp-story/videos.html new file mode 100644 index 0000000000000..b103687157e48 --- /dev/null +++ b/examples/amp-story/videos.html @@ -0,0 +1,173 @@ + + + + + + + + Example story with 10 videos + + + + + + + + + + + + + + + + + 1 + + + + + + + + + 2 + + + + + + + + + 3 + + + + + + + + + 4 + + + + + + + + + 5 + + + + + + + + + 6 + + + + + + + + + 7 + + + + + + + + + 8 + + + + + + + + + 9 + + + + + + + + + 10 + + + + diff --git a/examples/amp-subscriptions-google/amp-subscriptions-contribution.amp.html b/examples/amp-subscriptions-google/amp-subscriptions-contribution.amp.html index c3b317d6764ff..233cd77352bdc 100644 --- a/examples/amp-subscriptions-google/amp-subscriptions-contribution.amp.html +++ b/examples/amp-subscriptions-google/amp-subscriptions-contribution.amp.html @@ -281,7 +281,7 @@

Lorem Ipsum

Nullam in libero nisi.

- +

Sed pharetra semper fringilla. Nulla fringilla, neque eget diff --git a/examples/amp-subscriptions-google/amp-subscriptions-iframe.amp.html b/examples/amp-subscriptions-google/amp-subscriptions-iframe.amp.html index 6445a5127c637..357b96513611e 100644 --- a/examples/amp-subscriptions-google/amp-subscriptions-iframe.amp.html +++ b/examples/amp-subscriptions-google/amp-subscriptions-iframe.amp.html @@ -167,7 +167,7 @@ padding: 16px; background: #ffc; } - + amp-subscriptions-dialog { text-align: center; padding: 5px; diff --git a/examples/amp-subscriptions-google/amp-subscriptions-iframe.provider.html b/examples/amp-subscriptions-google/amp-subscriptions-iframe.provider.html index af82bb9ce982a..00fa169b2349a 100644 --- a/examples/amp-subscriptions-google/amp-subscriptions-iframe.provider.html +++ b/examples/amp-subscriptions-google/amp-subscriptions-iframe.provider.html @@ -2,9 +2,9 @@ - + + + + + + + + + + + + +

+
+
+
+

SwG Metering Example

+ +
+ +
+

-- PAYWALL --

+
+ +
+

🎉 Congratulations! You unlocked the paywall!

+
+
+
+
+ + diff --git a/examples/amp-subscriptions-google/amp-subscriptions-metering-gaa.amp.html b/examples/amp-subscriptions-google/amp-subscriptions-metering-gaa.amp.html new file mode 100644 index 0000000000000..ea52fb926a85e --- /dev/null +++ b/examples/amp-subscriptions-google/amp-subscriptions-metering-gaa.amp.html @@ -0,0 +1,195 @@ + + + + + + Showcase with GAA metering entitlements that grant + + + + + + + + + + + + + + + + +
+
+
+
+

SwG Metering Example

+ +
+ +
+

-- PAYWALL --

+
+ +
+

🎉 Congratulations! You unlocked the paywall!

+
+
+
+
+ + diff --git a/examples/amp-subscriptions-google/amp-subscriptions-metering-laa.amp.html b/examples/amp-subscriptions-google/amp-subscriptions-metering-laa.amp.html new file mode 100644 index 0000000000000..f27d831e7d6ff --- /dev/null +++ b/examples/amp-subscriptions-google/amp-subscriptions-metering-laa.amp.html @@ -0,0 +1,194 @@ + + + + + + Showcase with LAA metering entitlements that grant + + + + + + + + + + + + + + + + + +
+
+
+
+

SwG Metering Example

+ +
+ +
+

-- PAYWALL --

+
+ +
+

🎉 Congratulations! You unlocked the paywall!

+
+
+
+
+ + diff --git a/examples/amp-subscriptions-google/amp-subscriptions-metering-registration-widget.html b/examples/amp-subscriptions-google/amp-subscriptions-metering-registration-widget.html new file mode 100644 index 0000000000000..d28b604e62cae --- /dev/null +++ b/examples/amp-subscriptions-google/amp-subscriptions-metering-registration-widget.html @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/examples/amp-subscriptions-google/amp-subscriptions-smartbox.amp.html b/examples/amp-subscriptions-google/amp-subscriptions-smartbox.amp.html index ecf679b9d9f74..92dddf0b1045e 100644 --- a/examples/amp-subscriptions-google/amp-subscriptions-smartbox.amp.html +++ b/examples/amp-subscriptions-google/amp-subscriptions-smartbox.amp.html @@ -245,6 +245,6 @@

"Smart Button (Dark w/ custom message text color)"

subscriptions-message-text-color="rgba(0, 120, 255, 0.95)" subscriptions-message-number="1" subscriptions-decorate>Yes I will Contribute - + diff --git a/examples/amp-subscriptions-rtp.amp.html b/examples/amp-subscriptions-rtp.amp.html index 92c09296aef34..68eb4c220fa30 100644 --- a/examples/amp-subscriptions-rtp.amp.html +++ b/examples/amp-subscriptions-rtp.amp.html @@ -386,7 +386,7 @@

subscriptions-service="local" subscriptions-decorate>Publisher offer
btw u are not subscribed
- + diff --git a/examples/amp-subscriptions.rtc.amp.html b/examples/amp-subscriptions.rtc.amp.html new file mode 100644 index 0000000000000..e9f77b7039c08 --- /dev/null +++ b/examples/amp-subscriptions.rtc.amp.html @@ -0,0 +1,286 @@ + + + + + subscriptions example + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ + + + + + diff --git a/examples/amp-tiktok.amp.html b/examples/amp-tiktok.amp.html new file mode 100644 index 0000000000000..e45353152ddc0 --- /dev/null +++ b/examples/amp-tiktok.amp.html @@ -0,0 +1,84 @@ + + + + + amp-tiktok example + + + + + + + + + +

`amp-tiktok` minimalist implementation with video id

+
+ + +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. +
+
+

`amp-tiktok` minimalist implementation with oEmbed request link

+ + +

`amp-tiktok` TikTok generated `blockquote` implementation

+ +
+
+ @countingprimes +

+ VIM is great.... right up until you start typing the commands into every + single text editor you see. I’d like to apologize for all my unneeded + “:wq”’s +

+ ♬ original sound - countingprimes +
+
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+ + diff --git a/examples/amp-timeago.amp.html b/examples/amp-timeago.amp.html index 2c882f3e49241..3276120bb71c4 100644 --- a/examples/amp-timeago.amp.html +++ b/examples/amp-timeago.amp.html @@ -19,12 +19,7 @@ border: 1px solid black; } - - + diff --git a/examples/amp-video-iframe.html b/examples/amp-video-iframe.html index 873571bf4cf17..cec51d8a84ffc 100644 --- a/examples/amp-video-iframe.html +++ b/examples/amp-video-iframe.html @@ -3,28 +3,16 @@ amp-video-iframe - + @@ -45,5 +33,11 @@ This is a fallback +

+ Different example using amp-consent: + + /examples/amp-video-iframe/consent.html + +

diff --git a/examples/amp-video-iframe/ForBiggerJoyRIdes.jpg b/examples/amp-video-iframe/ForBiggerJoyRIdes.jpg new file mode 100644 index 0000000000000..e5f89819806ab Binary files /dev/null and b/examples/amp-video-iframe/ForBiggerJoyRIdes.jpg differ diff --git a/examples/amp-video-iframe/consent.html b/examples/amp-video-iframe/consent.html new file mode 100644 index 0000000000000..e2ee6c4baee19 --- /dev/null +++ b/examples/amp-video-iframe/consent.html @@ -0,0 +1,61 @@ + + + + + amp-video-iframe with amp-consent + + + + + + + + + + + + + Consent rejected + + + + + + + + + diff --git a/examples/amp-video-iframe/frame-consent-es2015.html b/examples/amp-video-iframe/frame-consent-es2015.html new file mode 100644 index 0000000000000..1fab00e226529 --- /dev/null +++ b/examples/amp-video-iframe/frame-consent-es2015.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + diff --git a/examples/amp-video-iframe/frame-consent.html b/examples/amp-video-iframe/frame-consent.html new file mode 100644 index 0000000000000..973cef60ad6a2 --- /dev/null +++ b/examples/amp-video-iframe/frame-consent.html @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + diff --git a/examples/amp-video/multi-bitrate/1000.mp4 b/examples/amp-video/multi-bitrate/1000.mp4 new file mode 100644 index 0000000000000..2cbc7d5536622 Binary files /dev/null and b/examples/amp-video/multi-bitrate/1000.mp4 differ diff --git a/examples/amp-video/multi-bitrate/2000.mp4 b/examples/amp-video/multi-bitrate/2000.mp4 new file mode 100644 index 0000000000000..a92d845d447aa Binary files /dev/null and b/examples/amp-video/multi-bitrate/2000.mp4 differ diff --git a/examples/amp-video/multi-bitrate/223.mp4 b/examples/amp-video/multi-bitrate/223.mp4 new file mode 100644 index 0000000000000..250443db76355 Binary files /dev/null and b/examples/amp-video/multi-bitrate/223.mp4 differ diff --git a/examples/amp-video/multi-bitrate/320.mp4 b/examples/amp-video/multi-bitrate/320.mp4 new file mode 100644 index 0000000000000..9a8d2dd1eb5f1 Binary files /dev/null and b/examples/amp-video/multi-bitrate/320.mp4 differ diff --git a/examples/amp-video/multi-bitrate/611.mp4 b/examples/amp-video/multi-bitrate/611.mp4 new file mode 100644 index 0000000000000..870f1927b149a Binary files /dev/null and b/examples/amp-video/multi-bitrate/611.mp4 differ diff --git a/examples/amp-video/multi-bitrate/multi-bitrate.html b/examples/amp-video/multi-bitrate/multi-bitrate.html new file mode 100644 index 0000000000000..1fbb4b208bedd --- /dev/null +++ b/examples/amp-video/multi-bitrate/multi-bitrate.html @@ -0,0 +1,54 @@ + + + + + Multi bitrate + + + + + + + + + +

amp-video

+ + + + + + + + + + diff --git a/examples/amp-viewer-gpay-button.amp.html b/examples/amp-viewer-gpay-button.amp.html deleted file mode 100644 index 9335c0ebb4106..0000000000000 --- a/examples/amp-viewer-gpay-button.amp.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - amp-viewer-gpay-button example - - - - - - - - - - - diff --git a/examples/amp-viewer-gpay-inline.amp.html b/examples/amp-viewer-gpay-inline.amp.html deleted file mode 100644 index 83c4cf82c603d..0000000000000 --- a/examples/amp-viewer-gpay-inline.amp.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - amp-viewer-gpay-inline example - - - - - - - - - - - diff --git a/examples/ampcontext-creative-json.html b/examples/ampcontext-creative-json.html index 0da1a59a27017..04bba06626a47 100644 --- a/examples/ampcontext-creative-json.html +++ b/examples/ampcontext-creative-json.html @@ -1,15 +1,25 @@ - + + + + + - - - - - - Test Ad using ampcontext.js to create window.context - + + Test Ad using ampcontext.js to create window.context + + + + + diff --git a/examples/ampcontext-creative.html b/examples/ampcontext-creative.html index 7af805b136650..3a07d5b79353f 100644 --- a/examples/ampcontext-creative.html +++ b/examples/ampcontext-creative.html @@ -1,29 +1,25 @@ - + - + Test Ad using ampcontext.js to create window.context - - - - + + + + diff --git a/examples/amphtml-ads/README.md b/examples/amphtml-ads/README.md index df803593b1f73..772af59d88b91 100644 --- a/examples/amphtml-ads/README.md +++ b/examples/amphtml-ads/README.md @@ -1,6 +1,6 @@ Files under this folder are mainly one of the 2 types, differentiated by their file name patterns: -- `*.a4a.html`: are AMPHTML ads, which need to be viewed through - one of the supported [AMPHTML ads envelopes](../../../../contributing/TESTING.md#a4a-envelope-a4a-a4a-3p). -- `*.host.html`: are regular HTML pages that load AMPHTML ads. +- `*.a4a.html`: are AMPHTML ads, which need to be viewed through + one of the supported [AMPHTML ads envelopes](../../../../docs/testing.md#a4a-envelope-a4a-a4a-3p). +- `*.host.html`: are regular HTML pages that load AMPHTML ads. diff --git a/examples/amphtml-ads/adchoices-1.a4a.html b/examples/amphtml-ads/adchoices-1.a4a.html index d18697971c664..ccbe72360c341 100644 --- a/examples/amphtml-ads/adchoices-1.a4a.html +++ b/examples/amphtml-ads/adchoices-1.a4a.html @@ -1,3 +1,4 @@ + diff --git a/examples/amphtml-ads/adchoices-2.a4a.html b/examples/amphtml-ads/adchoices-2.a4a.html index a904b96466fe4..70e7dbe320668 100644 --- a/examples/amphtml-ads/adchoices-2.a4a.html +++ b/examples/amphtml-ads/adchoices-2.a4a.html @@ -16,7 +16,7 @@ a { color: #0000ff } - + body, table, div, @@ -25,11 +25,11 @@ margin: 0; padding: 0 } - + body { font-family: "Roboto", arial, sans-serif; } - + #adunit { overflow: hidden; width: 970px; @@ -38,26 +38,26 @@ top: 0; left: 0; } - + #adunit .ads .ad { overflow: hidden; } - + .attribution { z-index: 999; } - + amp-img.contain img { object-fit: contain; } - + #adunit #ads .image .image-link { width: 474px; height: 248px; background-color: transparent; position: relative; } - + body { background-color: #fff; border: 1px solid #ddd; @@ -67,7 +67,7 @@ position: relative; width: 968px } - + .attribution { background-color: #fff; border-radius: 2px; @@ -79,7 +79,7 @@ position: absolute; top: 4px } - + .title { display: table-cell; height: 99px; @@ -88,7 +88,7 @@ padding-top: 15px; vertical-align: middle } - + .title a { color: #212121; font-size: 24px; @@ -96,7 +96,7 @@ line-height: 1.4; text-decoration: none } - + .body { border-bottom: 1px solid #ddd; color: #777; @@ -106,45 +106,45 @@ padding-right: 20px; vertical-align: bottom } - + .body a { font-size: 16px; font-weight: 400; line-height: 1.4; text-decoration: none } - + .image { float: left; padding-right: 25px; width: 474px } - + .image amp-img { height: 100%; width: 100% } - + .image .image-link { background-color: #fff; height: 100%; width: 100% } - + .call-to-action { float: right; padding-right: 20px; padding-top: 23px; text-align: right } - + .call-to-action a { color: #4285f4; font-size: 16px; font-weight: 500; text-decoration: none } - + .logo { float: left; height: 39px; @@ -152,61 +152,61 @@ padding-top: 13px; width: 39px } - + .logo amp-img { display: flex; height: 100%; width: 100% } - + .logo .logo-link { height: 100%; width: 100% } - + .advertiser { float: left; padding-top: 22px } - + .advertiser a { color: #80868b; font-size: 16px; font-weight: 400; text-decoration: none } - + .ads .image-link { display: inline-block } - + .ads .image-link img { display: block } - + .reviews { line-height: 0 } - + .body-link, .body-link:hover, .body-link:visited { text-decoration: none; color: inherit } - + .abgc { position: absolute; z-index: 2147483646; right: 0; top: 0; } - + .abgc amp-img, .abgc img { display: block; } - + .abgs { position: absolute; -webkit-transform: translateX(110px); @@ -214,7 +214,7 @@ right: 16px; top: 0; } - + .abgcp { position: absolute; right: 0; @@ -224,18 +224,18 @@ padding-left: 10px; padding-bottom: 10px; } - + .abgb { position: relative; margin-right: 16px; top: 0; } - + .abgc:hover .abgs { -webkit-transform: none; transform: none; } - + .cbb { display: block; position: absolute; @@ -247,7 +247,7 @@ z-index: 9020; padding-left: 16px; } - + .btn { display: inline-block; border-radius: 2px; @@ -259,7 +259,7 @@ font-size: 0.7em; margin: 0 1px 0.4em 1px; } - + @media (max-width: 375px) and (min-height: 100px) { .btn { display: block; @@ -269,49 +269,49 @@ margin-right: auto; } } - + #spv1 amp-fit-text>div { -webkit-justify-content: flex-start; justify-content: flex-start; } - + .jm.sh #spv1 amp-fit-text>div { -webkit-justify-content: center; justify-content: center; } - + .jt .pn amp-fit-text>div { -webkit-justify-content: flex-start; justify-content: flex-start; } - + .btn > span { display: inline-block; padding: 0.5em 0.6em; line-height: 1em; } - + #sbtn { background-color: #FFFFFF; color: #9E9EA6; text-decoration: none; } - + #sbtn:hover, #sbtn:active { background-color: #F5F5F5; } - + #rbtn { background-color: rgb(66, 133, 245); color: white; } - + #rbtn:hover, #rbtn:active { background-color: #3275E5; } - + #mta { position: absolute; top: 0; @@ -321,11 +321,11 @@ font-weight: 400; line-height: 1em; } - + #mta input[type="radio"] { display: none; } - + #mta .pn { left: -970px; top: -250px; @@ -338,7 +338,7 @@ background-color: #FAFAFA; text-align: center; } - + #spv2 { display: -webkit-flex; display: flex; @@ -350,75 +350,75 @@ background-color: #FAFAFA; font-size: 0; } - + .sv #spv2 { -webkit-flex-direction: column; flex-direction: column; } - + .sh #spv2 { -webkit-flex-direction: row; flex-direction: row; -webkit-justify-content: center; justify-content: center; } - + .sh.sr #spv2 { -webkit-justify-content: flex-start; justify-content: flex-start; } - + .jt.sv #spv2 { -webkit-justify-content: flex-start; justify-content: flex-start; -webkit-align-items: center; align-items: center; } - + .jm.sh #spv2 { -webkit-align-items: center; align-items: center; } - + .jm.sv #spv2 { -webkit-justify-content: center; justify-content: center; -webkit-align-items: center; align-items: center; } - + #spv2 * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } - + #mta input[name="a"]:checked ~ #cbb { display: none; } - + #spv3 { opacity: 1; } - + .amp-animate #spv4 { opacity: 0; transition: opacity 0.5s linear 2.5s; } - + .amp-animate #spv3 amp-fit-text { opacity: 1; transition: opacity 0.5s linear 2s; } - + #spr3:checked ~ #spv3 amp-fit-text { opacity: 0 } - + #spr3:checked ~ #spv4 { opacity: 1; } - + #spr1:checked ~ #spv1, #spr2:checked ~ #spv2, #spr3:checked ~ #spv3, @@ -426,7 +426,7 @@ right: 0px; top: 0px; } - + .ct svg { border: 0; margin: 0 0 -0.45em 0; @@ -434,7 +434,7 @@ height: 1.38em; opacity: 0.4; } - + .ct { display: inline-block; line-height: 1.28em; @@ -442,30 +442,30 @@ text-align: center; padding: 0.3em; } - + .fct { padding: 1em; } - + #pct { display: block; font-weight: bold; padding: 1em 0.3em; } - + #ti { width: 970px; } - + #btns { width: 970px; } - + .fl { width: 970px; height: 250px; } - + #si { position: relative; display: inline-block; @@ -474,12 +474,12 @@ width: 1em; opacity: 0.4; } - + .sb { flex-shrink: 0; height: 50px; } - + .so { position: relative; z-index: 9110; @@ -492,12 +492,12 @@ background-color: #FFFFFF; cursor: pointer; } - + .so:hover, .so:active { background-color: #F5F5F5; } - + .so div { display: -webkit-flex; display: flex; @@ -508,7 +508,7 @@ width: 100%; height: 100%; } - + .so span { color: #4285F4; font-family: Arial, sans-serif; @@ -517,7 +517,7 @@ line-height: 14px; white-space: normal; } - + @media (min-height: 54px) { .sh.ss .so, .sv .so { @@ -525,37 +525,37 @@ border: none; } } - + .sh .so { margin-left: -1px; box-shadow: none; } - + .sh .so:first-child { margin-left: 0; } - + .sh.ss .so { margin-left: 8px; } - + .sh.ss .so:first-child { margin-left: 0; } - + .sh.jt .sb { margin-top: 8px; } - + .sv .so, .sh.ss .so { border-radius: 2px; } - + .sv .so { margin: 4px; } - + .sv.jt .so:first-child { margin-top: 8px; } @@ -569,9 +569,9 @@
Lorem ipsum
@@ -579,7 +579,7 @@ @@ -601,7 +601,7 @@
diff --git a/examples/amphtml-ads/ads-tag-integration.js b/examples/amphtml-ads/ads-tag-integration.js index c80176090ab8a..c9b1be7dd3ddc 100644 --- a/examples/amphtml-ads/ads-tag-integration.js +++ b/examples/amphtml-ads/ads-tag-integration.js @@ -24,7 +24,9 @@ initInaboxHost(win); // Enable amp-inabox APIs for all iframes. Normally, the actualy bootstrap // script would be more specific about which iframes to support. Array.prototype.push.apply( - win.ampInaboxIframes, document.querySelectorAll('iframe')); + win.ampInaboxIframes, + document.querySelectorAll('iframe') +); /** * A sample bootstrap script that is to be run in ads tag. @@ -43,7 +45,7 @@ function initInaboxHost(win) { let hostScriptRequested = false; - const listener = function(event) { + const listener = function (event) { if (!isInaboxMessage(event.data)) { return; } @@ -55,7 +57,7 @@ function initInaboxHost(win) { } hostScriptRequested = true; // Load inabox-host.js when the 1st inabox message is received. - loadScript(win, hostScriptUrl, function() { + loadScript(win, hostScriptUrl, function () { win.removeEventListener('message', listener); console.log('a4a-host.js loaded.'); }); diff --git a/examples/amphtml-ads/nws.a4a.html b/examples/amphtml-ads/nws.a4a.html new file mode 100644 index 0000000000000..e0c5b3d51da4c --- /dev/null +++ b/examples/amphtml-ads/nws.a4a.html @@ -0,0 +1,69 @@ + + + +Web Story Auto Ads Example
-
-

Scene 1

-

Scene 2

-

Scene 3

-

Scene 4

-

Scene 5

-

Scene 6

-

Scene 7

-

Scene 8

diff --git a/examples/amphtml-ads/visibility-ad.a4a.html b/examples/amphtml-ads/visibility-ad.a4a.html index 091f626f26a41..4df77fb242442 100644 --- a/examples/amphtml-ads/visibility-ad.a4a.html +++ b/examples/amphtml-ads/visibility-ad.a4a.html @@ -34,7 +34,8 @@ + + + + + + + + + + + + + + + + + + - + @@ -1424,37 +1507,57 @@ - - + + - - - + + - + @@ -1582,6 +1685,45 @@ + + + + + + + + + + + + + + + + +
diff --git a/examples/analytics-visibility.amp.html b/examples/analytics-visibility.amp.html index 33d57e01fb310..1c866f008d79e 100644 --- a/examples/analytics-visibility.amp.html +++ b/examples/analytics-visibility.amp.html @@ -30,22 +30,31 @@ - - +
diff --git a/examples/autocomplete.amp.html b/examples/autocomplete.amp.html index 1ce20b23600b5..7ce7263047425 100644 --- a/examples/autocomplete.amp.html +++ b/examples/autocomplete.amp.html @@ -62,7 +62,7 @@ padding: 0.5rem; /* default-ui-space-large */ border: 1px solid rgba(0, 0, 0, 0.33); /* default-ui-med-gray */ font-size: 1rem; /* default-ui-font-size */ - line-height: 1.5rem; /* default-ui-space-large */ + line-height: 1.5rem; /* default-ui-space-large */ } @@ -75,6 +75,7 @@

Inline

suggest-first filter="none" src="/form/mention/query" + [src]="colorsEndpoint" query="q" on="select:AMP.setState({inlineInputSelect: event.value})" > @@ -91,6 +92,11 @@

Inline

+

+ +

Select Event

Hello World

+

Select event with object value

+ + + + + +

+ Select a place +

+

Disable browser autofill

form has no autocomplete attr

diff --git a/examples/av/ForBiggerJoyrides-tiny.mp4 b/examples/av/ForBiggerJoyrides-tiny.mp4 new file mode 100644 index 0000000000000..29def93a5ea90 Binary files /dev/null and b/examples/av/ForBiggerJoyrides-tiny.mp4 differ diff --git a/examples/bento.amp.html b/examples/bento.amp.html new file mode 100644 index 0000000000000..3b2418a4a1644 --- /dev/null +++ b/examples/bento.amp.html @@ -0,0 +1,78 @@ + + + + + Basic Bento example + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+

Hero

+
+ +
+
+
+

Sea

+
+ +
+
+
+ + + + + diff --git a/examples/beopinion.amp.html b/examples/beopinion.amp.html index 052ad9a9c23a5..6ff18015d64b5 100644 --- a/examples/beopinion.amp.html +++ b/examples/beopinion.amp.html @@ -4,7 +4,6 @@ BeOpinion examples - diff --git a/examples/bind/basic.amp.html b/examples/bind/basic.amp.html index 6e435e206909f..f9871ba0ec465 100644 --- a/examples/bind/basic.amp.html +++ b/examples/bind/basic.amp.html @@ -5,7 +5,6 @@ amp-bind: Basic - diff --git a/examples/bind/carousels.amp.html b/examples/bind/carousels.amp.html index 88217942d1125..1e43a77df8a12 100644 --- a/examples/bind/carousels.amp.html +++ b/examples/bind/carousels.amp.html @@ -5,7 +5,6 @@ amp-bind: Carousels - diff --git a/examples/bind/details.amp.html b/examples/bind/details.amp.html index b4f785be7cb8a..4f3b6af66ece8 100644 --- a/examples/bind/details.amp.html +++ b/examples/bind/details.amp.html @@ -5,7 +5,6 @@ amp-bind: Details - diff --git a/examples/bind/errors.amp.html b/examples/bind/errors.amp.html index 34d4fcff625a3..92b9f42ce3bb1 100644 --- a/examples/bind/errors.amp.html +++ b/examples/bind/errors.amp.html @@ -5,7 +5,6 @@ amp-bind: Errors - diff --git a/examples/bind/iframe.amp.html b/examples/bind/iframe.amp.html index 27bf295f4dccf..f56864877f235 100644 --- a/examples/bind/iframe.amp.html +++ b/examples/bind/iframe.amp.html @@ -5,7 +5,6 @@ amp-bind: iframe - diff --git a/examples/bind/list.amp.html b/examples/bind/list.amp.html index a0bd3671c6302..f47e77b4ede5a 100644 --- a/examples/bind/list.amp.html +++ b/examples/bind/list.amp.html @@ -5,7 +5,6 @@ amp-bind: Lists - diff --git a/examples/bind/macros.amp.html b/examples/bind/macros.amp.html index 4519bf2756877..8af334697e7b6 100644 --- a/examples/bind/macros.amp.html +++ b/examples/bind/macros.amp.html @@ -5,7 +5,6 @@ amp-bind: Basic - diff --git a/examples/bind/performance.amp.html b/examples/bind/performance.amp.html index a96dfd9331424..1f2348ec17d4a 100644 --- a/examples/bind/performance.amp.html +++ b/examples/bind/performance.amp.html @@ -5,7 +5,6 @@ amp-bind: Performance - diff --git a/examples/bind/sandbox.amp.html b/examples/bind/sandbox.amp.html index 661789cd22070..28bfa289e9bcf 100644 --- a/examples/bind/sandbox.amp.html +++ b/examples/bind/sandbox.amp.html @@ -5,7 +5,6 @@ amp-bind: Sandbox - diff --git a/examples/bind/svgimage.amp.html b/examples/bind/svgimage.amp.html index 13dc61b2d2ef5..677b8903d8b3d 100644 --- a/examples/bind/svgimage.amp.html +++ b/examples/bind/svgimage.amp.html @@ -5,7 +5,6 @@ amp-bind: svg - diff --git a/examples/bind/title.amp.html b/examples/bind/title.amp.html index b8f31dfb57519..74b9c8917e56e 100644 --- a/examples/bind/title.amp.html +++ b/examples/bind/title.amp.html @@ -5,7 +5,6 @@ amp-bind: Title - diff --git a/examples/bind/video.amp.html b/examples/bind/video.amp.html index eff95ce2dbb87..a79bbfc577154 100644 --- a/examples/bind/video.amp.html +++ b/examples/bind/video.amp.html @@ -5,7 +5,6 @@ amp-bind: Video - diff --git a/examples/bind/xhr.amp.html b/examples/bind/xhr.amp.html index 33bc6e5f0a867..a08a17b87c714 100644 --- a/examples/bind/xhr.amp.html +++ b/examples/bind/xhr.amp.html @@ -5,7 +5,6 @@ amp-bind: XHR - diff --git a/examples/bodymovin-animation.amp.html b/examples/bodymovin-animation.amp.html index 2a7ae1d5884bc..e8a8ffa9be5d8 100644 --- a/examples/bodymovin-animation.amp.html +++ b/examples/bodymovin-animation.amp.html @@ -5,7 +5,6 @@ Bodymovin Animation examples - @@ -74,6 +73,36 @@

Example of a responsive layout animation

+

Canvas renderer

+ +

Examples of animations that loops infinitely

+ + + + + + +

Example of an animation that does one iteration

+ + + +

Example of an animation that does 2 iteration

+ + + +

Example of an animation that doesn't autoplay

+ + + +

Example of a bodymovin animation controlled by scroll position

+ + + + + +

Example of a responsive layout animation

+ + diff --git a/examples/brid-player.amp.html b/examples/brid-player.amp.html index 4ffe047330c4b..10844736eda6d 100644 --- a/examples/brid-player.amp.html +++ b/examples/brid-player.amp.html @@ -17,8 +17,8 @@ - -

Brid Player

+ +

Brid Player

Autoplay layout="responsive" width="480" height="270"> - +

Outstream

Outstream width="480" height="270"> +

Carousel

+ + +

Dock

Dock layout="responsive" width="480" height="270"> - +
diff --git a/examples/brightcove.amp.html b/examples/brightcove.amp.html index 10bae0458b029..f61d873a699a2 100644 --- a/examples/brightcove.amp.html +++ b/examples/brightcove.amp.html @@ -12,7 +12,7 @@ diff --git a/examples/connatix-player.amp.html b/examples/connatix-player.amp.html index ae5441382d9c6..4aa10dbb37d81 100644 --- a/examples/connatix-player.amp.html +++ b/examples/connatix-player.amp.html @@ -18,21 +18,21 @@

Connatix AMP examples

Connatix Player Responsive

-

Connatix Player Fixed

-

Connatix Player Responsive - With Media Id

-

CSA Example

AdSense for Search ad

- AdSense for Search ad
Page Content
- +

AdSense for Shopping ad

- Dailymotion examples - - - - - -

NOTE: Must disable check that isSecureUrl for testing

-

Doubleclick with RTC

- - - - - - - - - - diff --git a/examples/doubleload.amp.html b/examples/doubleload.amp.html new file mode 100644 index 0000000000000..96b7cf5c2f80d --- /dev/null +++ b/examples/doubleload.amp.html @@ -0,0 +1,42 @@ + + + + + AMP #0 + + + + + + + + + + + + + + + + + diff --git a/examples/example-sw.js b/examples/example-sw.js index 1373cec0351f5..2ced5ffbfcfa0 100644 --- a/examples/example-sw.js +++ b/examples/example-sw.js @@ -1,3 +1,18 @@ -self.addEventListener('fetch', e => { +/** + * Copyright 2018 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. + */ +self.addEventListener('fetch', (e) => { return fetch(e.request); }); diff --git a/examples/experiment-1.0.amp.html b/examples/experiment-1.0.amp.html index 39212ce3638e4..d33e6d7d8b2e7 100644 --- a/examples/experiment-1.0.amp.html +++ b/examples/experiment-1.0.amp.html @@ -108,7 +108,7 @@

Title TBD

layout=nodisplay id="amp-user-notification"> Dismiss this notification to see a background-color on the title on your next visit. - + diff --git a/examples/experiment.amp.html b/examples/experiment.amp.html index 8081636f0bfe4..b022bc582a11a 100644 --- a/examples/experiment.amp.html +++ b/examples/experiment.amp.html @@ -73,7 +73,7 @@

Background color is user sticky; text color may change on a refresh

layout=nodisplay id="amp-user-notification"> Dismiss this notification to see a border around title on your next visit. - + diff --git a/examples/facebook.amp.html b/examples/facebook.amp.html index 767448a97bcd8..0ec6e469cac26 100644 --- a/examples/facebook.amp.html +++ b/examples/facebook.amp.html @@ -5,7 +5,6 @@ Facebook examples - @@ -51,7 +50,8 @@

More Posts

+ data-href="https://www.facebook.com/zuck/videos/10102509264909801/" + data-allowfullscreen="true">

Comments

diff --git a/examples/fake-ad.amp.html b/examples/fake-ad.amp.html index 1fe84a88a607e..941de1efbc270 100644 --- a/examples/fake-ad.amp.html +++ b/examples/fake-ad.amp.html @@ -20,5 +20,34 @@

Fake Ad Network:

Could not display the fake ad :(

+ +

Fake Ad Network [srcdoc]:

+ +
Loading...
+
Could not display the fake ad :(
+
diff --git a/examples/fluid.amp.html b/examples/fluid.amp.html index 5010eb324cf01..a4a84849cc5d5 100644 --- a/examples/fluid.amp.html +++ b/examples/fluid.amp.html @@ -5,7 +5,6 @@ DoubleClick with Fluid Example - diff --git a/examples/gfk-sensic-analytics.amp.html b/examples/gfk-sensic-analytics.amp.html new file mode 100644 index 0000000000000..ccd82906f4c58 --- /dev/null +++ b/examples/gfk-sensic-analytics.amp.html @@ -0,0 +1,77 @@ + + + + + GfK (Sensic) Media Analytics + + + + + + + + + + + + +

Audio player

+

Play/Pause to send event

+ + +

Play/Pause to send event

+ + + + + + Change page + + + diff --git a/examples/gfycat.amp.html b/examples/gfycat.amp.html index 810772eaa5653..212a272953544 100644 --- a/examples/gfycat.amp.html +++ b/examples/gfycat.amp.html @@ -5,7 +5,6 @@ Gfycat examples - + + + + + +
+ + +
+ + + + + diff --git a/examples/img/bigbuckbunny copy.jpg b/examples/img/bigbuckbunny copy.jpg new file mode 100644 index 0000000000000..4d4125eac8a06 Binary files /dev/null and b/examples/img/bigbuckbunny copy.jpg differ diff --git a/examples/img/panorama1.jpg b/examples/img/panorama1.jpg deleted file mode 100644 index 31cfdda4d421f..0000000000000 Binary files a/examples/img/panorama1.jpg and /dev/null differ diff --git a/examples/instagram.amp.html b/examples/instagram.amp.html index 9d19618408da1..4b4c41845d637 100644 --- a/examples/instagram.amp.html +++ b/examples/instagram.amp.html @@ -5,7 +5,6 @@ Instagram examples - diff --git a/examples/jwplayer.amp.html b/examples/jwplayer.amp.html index aa8b64f0b7b78..3de23fcde3f7a 100644 --- a/examples/jwplayer.amp.html +++ b/examples/jwplayer.amp.html @@ -19,8 +19,8 @@

Responsive

@@ -29,7 +29,7 @@

Non-responsive, with a playlist

@@ -39,7 +39,7 @@

Docking

data-media-id="CtaIzmFs" data-player-id="BjcwyK37" dock - width="480" + width="480" height="270" > @@ -51,8 +51,8 @@

Responsive contextual

data-content-search="__CONTEXTUAL__" data-content-backfill=true data-content-recency="9D" - layout="responsive" - width="16" + layout="responsive" + width="16" height="9" > @@ -61,7 +61,7 @@

Non-Responsive, with a VAST Ad

@@ -70,7 +70,7 @@

Non-Responsive, with an IMA Ad

diff --git a/examples/layout-flex-item.amp.html b/examples/layout-flex-item.amp.html index 59d4d64c908d5..3cf2b6c1157ad 100644 --- a/examples/layout-flex-item.amp.html +++ b/examples/layout-flex-item.amp.html @@ -99,7 +99,7 @@

Flex container parent with non flex-item children. Children's width and heig
- +
diff --git a/examples/lightbox-gallery.amp.html b/examples/lightbox-gallery.amp.html index c4ce7bd5a2b1b..5ce4d04409cc6 100644 --- a/examples/lightbox-gallery.amp.html +++ b/examples/lightbox-gallery.amp.html @@ -14,6 +14,10 @@ max-width: 400px; margin-bottom: 100px; } + .amp-lightbox-gallery-caption{ + color: red; + font-size: 30px; + } @@ -43,6 +47,47 @@

AMP lightbox gallery with just regualar old jpgs.

> +

AMP lightbox gallery with caption styling.

+

We set captions to be colored red and have a 30px font size.

+
+
+ +
+ Let's walk the dog. +
+
+
+ +
+ This is the desert. +
+
+
+ +
+ This is a door. +
+
+
+

AMP lightbox gallery with webp and jpeg fallback.

This doesnt work in browsers that doesnt support webp

Heading 1

Heading 2

Heading 3

" } ], "next": "./list.example.json" diff --git a/examples/live-list-update-reverse.amp.html b/examples/live-list-update-reverse.amp.html index ab70df1963434..fcbbb389cc31f 100644 --- a/examples/live-list-update-reverse.amp.html +++ b/examples/live-list-update-reverse.amp.html @@ -168,4 +168,3 @@

the quick brown fox

- diff --git a/examples/live-list-update.amp.html b/examples/live-list-update.amp.html index 6ac78f965465c..6d0ede37aba5a 100644 --- a/examples/live-list-update.amp.html +++ b/examples/live-list-update.amp.html @@ -156,4 +156,3 @@ - diff --git a/examples/lots-of-images.amp.html b/examples/lots-of-images.amp.html index cfc114c99ebbe..fadf611ff3bed 100644 --- a/examples/lots-of-images.amp.html +++ b/examples/lots-of-images.amp.html @@ -21,7 +21,7 @@

Lots of images

Even more

- + @@ -32,7 +32,7 @@

Even more

- + @@ -43,7 +43,7 @@

Even more

- + @@ -54,7 +54,7 @@

Even more

- + @@ -65,7 +65,7 @@

Even more

- + @@ -76,7 +76,7 @@

Even more

- + @@ -87,7 +87,7 @@

Even more

- + @@ -98,7 +98,7 @@

Even more

- + @@ -109,7 +109,7 @@

Even more

- + @@ -120,7 +120,7 @@

Even more

- + @@ -132,7 +132,7 @@

Even more

- + @@ -143,7 +143,7 @@

Even more

- + @@ -154,7 +154,7 @@

Even more

- + @@ -165,7 +165,7 @@

Even more

- + @@ -176,7 +176,7 @@

Even more

- + @@ -187,7 +187,7 @@

Even more

- + @@ -198,7 +198,7 @@

Even more

- + @@ -209,7 +209,7 @@

Even more

- + @@ -220,7 +220,7 @@

Even more

- + @@ -231,7 +231,7 @@

Even more

- + @@ -243,7 +243,7 @@

Even more

- + @@ -254,7 +254,7 @@

Even more

- + @@ -265,7 +265,7 @@

Even more

- + @@ -276,7 +276,7 @@

Even more

- + @@ -287,7 +287,7 @@

Even more

- + @@ -298,7 +298,7 @@

Even more

- + @@ -309,7 +309,7 @@

Even more

- + @@ -320,7 +320,7 @@

Even more

- + @@ -331,7 +331,7 @@

Even more

- + @@ -342,7 +342,7 @@

Even more

- + @@ -354,7 +354,7 @@

Even more

- + @@ -365,7 +365,7 @@

Even more

- + @@ -376,7 +376,7 @@

Even more

- + @@ -387,7 +387,7 @@

Even more

- + @@ -398,7 +398,7 @@

Even more

- + @@ -409,7 +409,7 @@

Even more

- + @@ -420,7 +420,7 @@

Even more

- + @@ -431,7 +431,7 @@

Even more

- + @@ -442,7 +442,7 @@

Even more

- + @@ -453,7 +453,7 @@

Even more

- + @@ -465,7 +465,7 @@

Even more

- + @@ -476,7 +476,7 @@

Even more

- + @@ -487,7 +487,7 @@

Even more

- + @@ -498,7 +498,7 @@

Even more

- + @@ -509,7 +509,7 @@

Even more

- + @@ -520,7 +520,7 @@

Even more

- + @@ -531,7 +531,7 @@

Even more

- + @@ -542,7 +542,7 @@

Even more

- + @@ -553,7 +553,7 @@

Even more

- + @@ -564,6 +564,6 @@

Even more

- + \ No newline at end of file diff --git a/examples/masking.amp.html b/examples/masking.amp.html index 7657777e13161..adffb2cceda98 100644 --- a/examples/masking.amp.html +++ b/examples/masking.amp.html @@ -29,13 +29,13 @@

Mask with amp-state

on="change:AMP.setState({maskedValue: event.value})" name="statemask" mask="000-000-000" - mask-output="alphanumeric" + mask-output="alphanumeric" placeholder="Type some numbers..." />
diff --git a/examples/megaphone.amp.html b/examples/megaphone.amp.html index 85f05d288e6c1..363a1df7ff6c2 100644 --- a/examples/megaphone.amp.html +++ b/examples/megaphone.amp.html @@ -5,7 +5,6 @@ Megaphone examples - @@ -46,7 +45,7 @@

Megaphone Episode (Disabled sharing controls)

layout="fixed-height" data-episode="OSC7749686951" data-sharing="false"> - +

Megaphone Playlist (With limited number of episodes)

Nexxtv Player data-mediaid="PTPFEC4U184674" data-client="583" data-streamtype="video" - data-seek-to="10" data-mode="static" - data-origin="https://embed.nexx.cloud/" + data-exit-mode="replay" layout="responsive" width="480" height="270" > diff --git a/examples/o2player.amp.html b/examples/o2player.amp.html index ee601c2ef065e..e7934870b40cf 100644 --- a/examples/o2player.amp.html +++ b/examples/o2player.amp.html @@ -5,7 +5,6 @@ O2Player examples - @@ -30,4 +29,3 @@

O2Player

- diff --git a/examples/old-boilerplate.amp.html b/examples/old-boilerplate.amp.html index 1ca3c5bfcd6a1..4a1b91120363e 100644 --- a/examples/old-boilerplate.amp.html +++ b/examples/old-boilerplate.amp.html @@ -5,7 +5,6 @@ Twitter examples - diff --git a/examples/onetap.amp.html b/examples/onetap.amp.html new file mode 100644 index 0000000000000..e423b45df5ff1 --- /dev/null +++ b/examples/onetap.amp.html @@ -0,0 +1,54 @@ + + + + + Google One Tap Example + + + + + + + + + + + + +

Google One Tap

+ + + + + + + + diff --git a/examples/pinterest.amp.html b/examples/pinterest.amp.html index 64645828d81ef..fb92ea0350949 100644 --- a/examples/pinterest.amp.html +++ b/examples/pinterest.amp.html @@ -5,7 +5,6 @@ Pinterest examples - diff --git a/examples/refresh.amp.html b/examples/refresh.amp.html index 237ce60126246..b8731f31a0f48 100644 --- a/examples/refresh.amp.html +++ b/examples/refresh.amp.html @@ -6,7 +6,6 @@ - diff --git a/examples/responsive-menu.amp.html b/examples/responsive-menu.amp.html index 4eb94643b7ab1..e450963703abb 100644 --- a/examples/responsive-menu.amp.html +++ b/examples/responsive-menu.amp.html @@ -5,7 +5,6 @@ AMP responsive menu example - diff --git a/examples/runtime/README.md b/examples/runtime/README.md index f0140e404a119..91fe6cab388ce 100644 --- a/examples/runtime/README.md +++ b/examples/runtime/README.md @@ -1,7 +1,7 @@ # Runtime performance examples -This folder contains example pages owned by [@wg-runtime](https://github.com/ampproject/wg-runtime), and made for testing performance with `gulp performance` +This folder contains example pages owned by [@wg-performance](https://github.com/ampproject/wg-performance), and made for testing performance with `amp performance` -- **images.html**: This page has 1000 images. It is a good stress test for the resources system as we want to ensure it prioritizes 1vp elements over other elements within the loadRect. -- **list-always.html**: This page contains the slowest form of an ``, namely one with `binding="always"`. -- **article.html**: Meant to be as similar to a standard AMP article as possible. Contains the most used components. +- **images.html**: This page has 1000 images. It is a good stress test for the resources system as we want to ensure it prioritizes 1vp elements over other elements within the loadRect. +- **list-always.html**: This page contains the slowest form of an ``, namely one with `binding="always"`. +- **article.html**: Meant to be as similar to a standard AMP article as possible. Contains the most used components. diff --git a/examples/runtime/article.html b/examples/runtime/article.html index c0dc0e34a7b62..1e44c611e40b8 100644 --- a/examples/runtime/article.html +++ b/examples/runtime/article.html @@ -7,7 +7,7 @@ - @@ -170,7 +170,7 @@

Headline of the article

itemprop="author">Lorem Ipsum - +
Headline of the article Fusce pretium tempor justo, vitae.
- +

Section 1

@@ -212,7 +212,7 @@

Section 3

src="https://lh3.googleusercontent.com/qNn8GDz8Jfd-s9lt3Nc4lJeLjVyEaqGJTk1vuCUWazCmAeOBVjSWDD0SMTU7x0zhVe5UzOTKR0n-kN4SXx7yElvpKYvCMaRyS_g-jydhJ_cEVYmYPiZ_j1Y9de43mlKxU6s06uK1NAlpbSkL_046amEKOdgIACICkuWfOBwlw2hUDfjPOWskeyMrcTu8XOEerCLuVqXugG31QC345hz3lUyOlkdT9fMYVUynSERGNzHba7bXMOxKRe3izS5DIWUgJs3oeKYqA-V8iqgCvneD1jj0Ff68V_ajm4BDchQubBJU0ytXVkoWh27ngeEHubpnApOS6fcGsjPxeuMjnzAUtoTsiXz2FZi1mMrxrblJ-kZoAq1DJ95cnoqoa2CYq3BTgq2E8BRe2paNxLJ5GXBCTpNdXMpVJc6eD7ceijQyn-2qanilX-iK3ChbOq0uBHMvsdoC_LsFOu5KzbbNH71vM3DPkvDGmHJmF67Vj8sQ6uBrLnzpYlCyN4-Y9frR8zugDcqX5Q=w300-h200-no" width="300" height="200"> - +
@@ -374,7 +374,7 @@

Jump To

data-aax_src="302">
- +
Jump To torquent per conubia nostra, per inceptos himenaeos.

- +