text:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jpgilchrist.android.popularmovies">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity
            android:name=".activities.MainActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".activities.DetailActivity"
            android:label="@string/detail_activity_label"
            android:parentActivityName=".activities.MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".activities.MainActivity"/>
        </activity>
        <activity
            android:name=".activities.SettingsActivity"
            android:label="@string/settings_activity_label"
            android:parentActivityName=".activities.MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".activities.MainActivity"/>
        </activity>
    </application>

</manifest>


text:
require 'test/unit'

begin
  require File.dirname(__FILE__) + '/../../../../config/environment'
rescue LoadError
  require 'rubygems'
  gem 'activerecord'
  require 'active_record'

  RAILS_ROOT = File.dirname(__FILE__)
  RAILS_ENV = "proxy_attributes_test"

  require File.join(File.dirname(__FILE__), '../init')
end

# Let's do it more like Rails does!
ActiveRecord::Base.configurations[RAILS_ENV] = {:adapter => "sqlite3", :dbfile => "proxy_attributes.sqlite3"}
ActiveRecord::Base.establish_connection RAILS_ENV

ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define(:version => 1) do
  create_table :projects, :force => true do |t|
    t.string :title
  end

  create_table :documents, :force => true do |t|
    t.integer :project_id
    t.string :title, :type
  end

  create_table :categories, :force => true do |t|
    t.string :title
  end

  create_table :categorizations, :force => true do |t|
    t.integer :document_id, :category_id
  end

  create_table :tags, :force => true do |t|
    t.string :title
  end

  create_table :taggings, :force => true do |t|
    t.integer :document_id, :tag_id
  end

  create_table :attachments, :force => true do |t|
    t.integer :document_id
    t.string :title
  end

  create_table :badges, :force => true do |t|
    t.integer :document_id
    t.string :title
  end

  create_table :mystery_meats, :force => true do |t|
    t.integer :document_id, :project_id
    t.string :meat, :garnish
  end
end
ActiveRecord::Migration.verbose = true

class Project < ActiveRecord::Base
  has_many :documents
  has_many :pages
  has_many :mystery_meats
end

class Document < ActiveRecord::Base
  belongs_to :project

  has_many :categorizations
  has_many :categories, :through => :categorizations

  has_many :taggings
  has_many :tags, :through => :taggings

  has_many :attachments
  has_many :badges
  has_many :mystery_meats

  proxy_attributes do
    by_ids :categories, :badges
    by_string :tags => :title, :mystery_meats => :meat
    by_force :attachments

    before_creating(:mystery_meats) do |meat|
      meat.project_id = self.project_id
    end
  end

  validates_presence_of :title
end

class Page < Document
  proxy_attributes do
    before_creating(:mystery_meats) do |meat|
      meat.garnish = self.title
    end
  end
end

class Category < ActiveRecord::Base
  has_many :categorizations
  has_many :documents, :through => :categorizations

  validates_presence_of :title
end

class Categorization < ActiveRecord::Base
  belongs_to :document
  belongs_to :category
end

class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :documents, :through => :taggings

  validates_presence_of :title
end

class Tagging < ActiveRecord::Base
  belongs_to :document
  belongs_to :tag
end

class Attachment < ActiveRecord::Base
  belongs_to :document
end

class Badge < ActiveRecord::Base
  belongs_to :document
end

class MysteryMeat < ActiveRecord::Base
  belongs_to :document
  belongs_to :project
end

class PostponeAssociationsTest < Test::Unit::TestCase
  def saveable_doc(optional = {})
    unsaveable_doc(optional.merge(:title => "Saveable"))
  end

  def unsaveable_doc(optional = {})
    Document.new(optional)
  end

  def saveable_page(optional = {})
    unsaveable_page(optional.merge(:title => "Saveable Page"))
  end

  def unsaveable_page(optional = {})
    Page.new(optional)
  end

  def setup
    # Just to be safe
    [Document, Category, Tag, Attachment, MysteryMeat].each do |foo|
      foo.delete_all
    end
  end

  def test_assigns_children_by_ids_if_parent_saves
    @cat = Category.create(:title => "Saved")
    @doc = saveable_doc(:category_ids => [@cat.id])
    @doc.save
    assert @doc.categories.include?(@cat)
    assert @cat.documents.include?(@doc)
  end

  def test_caches_children_ids_for_reassignment_if_parent_save_fails
    @cat = Category.create(:title => "Unsaved")
    @doc = unsaveable_doc(:category_ids => [@cat.id])
    @doc.save
    assert @doc.categories.empty?
    assert @cat.documents.empty?
    assert_equal [@cat.id], @doc.category_ids
  end

  def test_assigns_children_by_ids_plain_has_many_support
    @badge = Badge.create(:title => "Unattached")
    @doc = saveable_doc(:badge_ids => [@badge.id])
    @doc.save
    @badge.reload
    assert @doc.badges.include?(@badge)
    assert_equal @doc, @badge.document
    assert_equal [@badge.id], @doc.badge_ids
  end

  def test_assigns_children_by_ids_sti_support
    @cat = Category.create(:title => "Saved Page")
    @page = saveable_page(:category_ids => [@cat.id])
    @page.save
    assert @page.categories.include?(@cat)
    assert @cat.documents.include?(@page)
  end

  def test_add_child_if_parent_saves
    @doc = saveable_doc(:add_category => {:title => "Created"})
    @doc.save
    @cat = Category.find_by_title("Created")
    assert @doc.categories.include?(@cat)
    assert @cat.documents.include?(@doc)
  end

  def test_does_not_add_child_if_parent_save_fails
    @doc = unsaveable_doc(:add_category => {:title => "Not Created"})
    @doc.save
    assert @doc.categories.empty?
    assert_nil Category.find_by_title("Not Created")
  end

  def test_add_child_caches_attributes_for_reassignment_if_parent_save_fails
    @doc = unsaveable_doc(:add_category => {:title => "Not Created"})
    @doc.save
    # Man I wish Foo.new == Foo.new, but it doesn't
    assert_equal({"title" => "Not Created"}, @doc.add_category.attributes)
  end

  def test_add_child_returns_new_child
    @doc = unsaveable_doc
    # Man I wish Foo.new == Foo.new, but it doesn't
    assert @doc.add_category.is_a?(Category)
  end

  def test_add_child_doesnt_add_invalid_records
    @doc = saveable_doc(:add_category => {:title => " "})
    @doc.save
    assert @doc.categories.empty?
  end

  def test_add_child_creates_multiple_records
    @doc = saveable_doc(:add_category => {1 => {:title => "First"}, 2 => {:title => "Second"}})
    @doc.save
    assert Category.find_by_title("First")
    assert Category.find_by_title("Second")
    assert_equal 2, @doc.categories.count
  end

  def test_caches_multiple_attributes_for_reassignment_if_parent_save_fails
    @doc = unsaveable_doc(:add_category => {1 => {:title => "First"}, 2 => {:title => "Second"}})
    @doc.save
    # Man I wish Foo.new == Foo.new, but it doesn't
    assert_equal({"title" => "First"}, @doc.add_category[1].attributes)
    assert_equal({"title" => "Second"}, @doc.add_category[2].attributes)
  end

  def test_add_child_support_for_plain_has_many
    @doc = saveable_doc(:add_badge => {:title => "Clingy"})
    @doc.save
    @badge = Badge.find_by_title("Clingy")
    assert @doc.badges.include?(@badge)
    assert_equal @doc, @badge.document
  end

  def test_add_child_sti_support
    @page = saveable_page(:add_category => {:title => "Created"})
    @page.save
    @cat = Category.find_by_title("Created")
    assert @page.categories.include?(@cat)
    assert @cat.documents.include?(@page)
  end

  def test_assigns_children_by_string_if_parent_saves
    @tag = Tag.create(:title => "prexisting")
    @doc = saveable_doc(:tags_as_string => "prexisting")
    @doc.save
    assert @doc.tags.include?(@tag)
    assert @tag.documents.include?(@doc)
  end

  def test_creates_children_by_string_if_parent_saves
    @doc = saveable_doc(:tags_as_string => "freshly created")
    @doc.save
    @tag = Tag.find_by_title("freshly created")
    assert @doc.tags.include?(@tag)
    assert @tag.documents.include?(@doc)
  end

  def test_assigns_and_creates_children_by_string_if_parent_saves
    @tag = Tag.create(:title => "stale")
    @doc = saveable_doc(:tags_as_string => "stale, minty fresh")
    @doc.save
    @new_tag = Tag.find_by_title("minty fresh")
    assert @doc.tags.include?(@tag)
    assert @doc.tags.include?(@new_tag)
    assert @tag.documents.include?(@doc)
    assert @new_tag.documents.include?(@doc)
  end

  def test_does_not_assign_children_by_string_if_parent_save_fails
    @tag = Tag.create(:title => "prexisting")
    @doc = unsaveable_doc(:tags_as_string => "prexisting")
    @doc.save
    assert @doc.tags.empty?
    assert @tag.documents.empty?
  end

  def test_does_not_create_children_by_string_if_parent_save_fails
    @doc = unsaveable_doc(:tags_as_string => "freshly unborn")
    @doc.save
    assert @doc.tags.empty?
    assert_nil Tag.find_by_title("freshly unborn")
  end

  def test_does_not_assign_or_create_children_by_string_if_parent_save_fails
    @tag = Tag.create(:title => "stale")
    @doc = unsaveable_doc(:tags_as_string => "stale, seedy mint")
    @doc.save
    assert @doc.tags.empty?
    assert @tag.documents.empty?
    assert_nil Tag.find_by_title("seedy mint")
  end

  def test_caches_string_for_reassignment_if_parent_save_fails
    @doc = unsaveable_doc(:tags_as_string => "hateful, bitter, soulless shit")
    @doc.save
    assert_equal "hateful, bitter, soulless shit", @doc.tags_as_string
  end

  def test_assigning_children_as_string_doesnt_add_duplicates
    @doc = saveable_doc(:tags_as_string => "check, , check")
    @doc.save
    assert_equal 1, @doc.tags.count
    assert_equal [Tag.find_by_title("check")], @doc.tags
  end

  def test_assigning_children_as_string_doesnt_add_blank
    @doc = saveable_doc(:tags_as_string => " ")
    @doc.save
    assert @doc.tags.empty?
  end

  def test_children_as_string_reader_should_return_empty_string_not_nil
    assert_equal "", Document.new.tags_as_string
  end

  def test_children_as_string_plain_has_many_support
    @doc = saveable_doc(:mystery_meats_as_string => "scoobish snack")
    @doc.save
    @mystery_meat = MysteryMeat.find_by_meat("scoobish snack")
    assert @doc.mystery_meats.include?(@mystery_meat)
    assert_equal @doc, @mystery_meat.document
  end

  def test_manage_child_updates_object
    @doc = saveable_doc(:add_tag => {:title => "updateable"})
    @doc.save
    @tag = Tag.find_by_title("updateable")
    @doc.update_attributes(:manage_tag => {@tag.id => {:title => "updated!"}})
    @tag.reload
    assert_equal "updated!", @tag.title
  end

  def test_manage_child_hash_returns_object_for_keys_with_objects
    @doc = saveable_doc(:tags_as_string => "manageable")
    @doc.save
    @tag = Tag.find_by_title("manageable")
    assert_equal @tag, @doc.manage_tag[@tag.id]
  end

  def test_manage_child_hash_raises_exception_for_keys_with_no_objects
    @doc = saveable_doc
    @doc.save
    assert_raises LuckySneaks::ProxyAttributes::ImproperAccess do
      @doc.manage_tag[nil]
    end
  end

  def test_adds_child_by_force_if_parent_saves
    @doc = saveable_doc(:add_attachment => {:title => "uploadable"})
    @doc.save
    @attachment = Attachment.find_by_title("uploadable")
    assert_equal [@attachment], @doc.attachments
    assert_equal @doc, @attachment.document
  end

  def test_add_child_by_force_creates_unassociated_child_when_parent_save_fails
    @doc = unsaveable_doc(:add_attachment => {:title => "unsaveable parent"})
    @doc.save
    @attachment = Attachment.find_by_title("unsaveable parent")
    assert @attachment.document.nil?
    # However...
    assert_equal [@attachment], @doc.attachments
    # Because...
    assert_equal [@attachment], @doc.attachments_with_postponed
    assert_equal @attachment.id.to_s, @doc.postponed_attachment_ids
    assert_equal [], @doc.attachments_without_postponed
  end

  def test_add_child_by_force_creates_multiple_records
    @doc = saveable_doc(:add_attachment => {"0" => {:title => "first attachment"},
      "1" => {:title => "second attachment"}})
    @doc.save
    @attachment_1 = Attachment.find_by_title("first attachment")
    @attachment_2 = Attachment.find_by_title("second attachment")
    assert @doc.attachments.include?(@attachment_1)
    assert @doc.attachments.include?(@attachment_2)
    assert_equal @doc, @attachment_1.document
    assert_equal @doc, @attachment_2.document
  end

  def test_postponed_child_ids_associates_records
    @attachment = Attachment.create!(:title => "pre-existing")
    @doc = saveable_doc(:postponed_attachment_ids => @attachment.id.to_s)
    @doc.save
    assert @doc.attachments.include?(@attachment)
    @attachment.reload
    assert_equal @doc, @attachment.document
  end

  def test_child_creation_allows_procs
    @project = Project.create!(:title => "some project")
    @doc = @project.documents.create(:title => "document in a project")
    assert @project.documents.include?(@doc)
    assert_equal @project, @doc.project
    @doc.add_mystery_meat = {:meat => "mystery meat for document in a project"}
    @doc.save
    @meat = MysteryMeat.find_by_meat("mystery meat for document in a project")
    assert @project.mystery_meats.include?(@meat)
    assert_equal @project, @meat.project
  end

  def test_children_as_string_allows_procs
    @project = Project.create!(:title => "some project")
    @doc = @project.documents.create(:title => "document in a project")
    assert @project.documents.include?(@doc)
    assert_equal @project, @doc.project
    @doc.mystery_meats_as_string = "mystery meat for document in a project from string"
    @doc.save
    @meat = MysteryMeat.find_by_meat("mystery meat for document in a project from string")
    assert @project.mystery_meats.include?(@meat)
    assert_equal @project, @meat.project
  end

  def test_child_creation_procs_with_sti
    @project = Project.create!(:title => "some project")
    @page = @project.pages.create(:title => "page in a project")
    assert @project.pages.include?(@page)
    assert_equal @project, @page.project
    @page.add_mystery_meat = {:meat => "mystery meat for page in a project"}
    @page.save
    @meat = MysteryMeat.find_by_meat("mystery meat for page in a project")
    assert @project.mystery_meats.include?(@meat)
    assert_equal @project, @meat.project
    assert_equal @page.title, @meat.garnish
  end
end

redpajama_set_name: RedPajamaGithub


text:
#ifndef SkSVGClipPath_DEFINED
#define SkSVGClipPath_DEFINED

#include "modules/svg/include/SkSVGHiddenContainer.h"
#include "modules/svg/include/SkSVGTypes.h"

class SkSVGClipPath final : public SkSVGHiddenContainer {
public:
    static sk_sp<SkSVGClipPath> Make() {
        return sk_sp<SkSVGClipPath>(new SkSVGClipPath());
    }

    SVG_ATTR(ClipPathUnits, SkSVGObjectBoundingBoxUnits,
             SkSVGObjectBoundingBoxUnits(SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse))

private:
    friend class SkSVGRenderContext;

    SkSVGClipPath();

    bool parseAndSetAttribute(const char*, const char*) override;

    SkPath resolveClip(const SkSVGRenderContext&) const;

    using INHERITED = SkSVGHiddenContainer;
};

#endif // SkSVGClipPath_DEFINED


text:
// Because AdSense and DoubleClick are both operated by Google and their A4A
// implementations share some behavior in common, part of the logic for this
// implementation is located in the ads/google/a4a directory rather than here.
// Most other ad networks will want to put their A4A code entirely in the
// extensions/amp-ad-network-${NETWORK_NAME}-impl directory.

import '../../amp-a4a/0.1/real-time-config-manager';
import {
  AmpA4A,
  DEFAULT_SAFEFRAME_VERSION,
  RENDERING_TYPE_HEADER,
  XORIGIN_MODE,
  assignAdUrlToError,
} from '../../amp-a4a/0.1/amp-a4a';
import {
  AmpAnalyticsConfigDef,
  QQID_HEADER,
  ValidAdContainerTypes,
  addCsiSignalsToAmpAnalyticsConfig,
  extractAmpAnalyticsConfig,
  getCsiAmpAnalyticsConfig,
  getCsiAmpAnalyticsVariables,
  getEnclosingContainerTypes,
  getIdentityToken,
  googleAdUrl,
  googleBlockParameters,
  googlePageParameters,
  groupAmpAdsByType,
  isCdnProxy,
  isReportingEnabled,
  maybeAppendErrorParameter,
  truncAndTimeUrl,
} from '../../../ads/google/a4a/utils';
import {
  DOUBLECLICK_EXPERIMENT_FEATURE,
  DOUBLECLICK_UNCONDITIONED_EXPERIMENTS,
  UNCONDITIONED_CANONICAL_FF_HOLDBACK_EXP_NAME,
  experimentFeatureEnabled,
} from './doubleclick-a4a-config';
import {Layout, isLayoutSizeDefined} from '../../../src/layout';
import {RTC_VENDORS} from '../../amp-a4a/0.1/callout-vendors';
import {
  RefreshManager, // eslint-disable-line no-unused-vars
  getRefreshManager,
} from '../../amp-a4a/0.1/refresh-manager';
import {SafeframeHostApi} from './safeframe-host';
import {Services} from '../../../src/services';
import {VisibilityState} from '../../../src/visibility-state';
import {
  addExperimentIdToElement,
} from '../../../ads/google/a4a/traffic-experiments';
import {createElementWithAttributes, removeElement} from '../../../src/dom';
import {deepMerge, dict} from '../../../src/utils/object';
import {dev, user} from '../../../src/log';
import {domFingerprintPlain} from '../../../src/utils/dom-fingerprint';
import {
  getExperimentBranch,
  randomlySelectUnsetExperiments,
} from '../../../src/experiments';
import {getMode} from '../../../src/mode';
import {getMultiSizeDimensions} from '../../../ads/google/utils';
import {getOrCreateAdCid} from '../../../src/ad-cid';
import {
  googleLifecycleReporterFactory,
  setGoogleLifecycleVarsFromHeaders,
} from '../../../ads/google/a4a/google-data-reporter';
import {
  incrementLoadingAds,
  is3pThrottled,
  waitFor3pThrottle,
} from '../../amp-ad/0.1/concurrent-load';
import {insertAnalyticsElement} from '../../../src/extension-analytics';
import {
  installAnchorClickInterceptor,
} from '../../../src/anchor-click-interceptor';
import {isCancellation} from '../../../src/error';
import {
  isInManualExperiment,
} from '../../../ads/google/a4a/traffic-experiments';
import {isSecureUrl, parseQueryString} from '../../../src/url';
import {
  lineDelimitedStreamer,
  metaJsonCreativeGrouper,
} from '../../../ads/google/a4a/line-delimited-response-handler';
import {setStyles} from '../../../src/style';
import {stringHash32} from '../../../src/string';
import {tryParseJson} from '../../../src/json';
import {utf8Encode} from '../../../src/utils/bytes';

/** @type {string} */
const TAG = 'amp-ad-network-doubleclick-impl';

/** @const {string} */
const DOUBLECLICK_BASE_URL =
    'https://securepubads.g.doubleclick.net/gampad/ads';

/** @const {string} */
const RTC_SUCCESS = '2';

/** @visibleForTesting @const {string} */
export const CORRELATOR_CLEAR_EXP_NAME = 'dbclk-correlator-clear';

/** @visibleForTesting @enum {string} */
export const CORRELATOR_CLEAR_EXP_BRANCHES = {
  CONTROL: '22302764',
  EXPERIMENT: '22302765',
};

/**
 * @const {string}
 * @visibileForTesting
 */
export const TFCD = 'tagForChildDirectedTreatment';

/** @const {string} */
export const SAFEFRAME_ORIGIN = 'https://tpc.googlesyndication.com';

/** @private {?Promise} */
let sraRequests = null;

/** @typedef {{
      adUrl: !Promise<string>,
      lineItemId: string,
      creativeId: string,
      slotId: string,
      slotIndex: string,
    }} */
let TroubleshootDataDef;

/** @private {?JsonObject} */
let windowLocationQueryParameters;

/**
 * @typedef
 * {({width: number, height: number}|../../../src/layout-rect.LayoutRectDef)}
 */
let LayoutRectOrDimsDef;

/**
 * Array of functions used to combine block level request parameters for SRA
 * request.
 * @private @const
 * {!Array<!function(!Array<AmpAdNetworkDoubleclickImpl>):?Object<string,string>}
 */
const BLOCK_SRA_COMBINERS_ = [
  instances => {
    const uniqueIuNames = {};
    let uniqueIuNamesCount = 0;
    const prevIusEncoded = [];
    instances.forEach(instance => {
      const iu = dev().assert(instance.element.getAttribute('data-slot'));
      const componentNames = (iu || '').split('/');
      const encodedNames = [];
      for (let i = 0; i < componentNames.length; i++) {
        if (componentNames[i] == '') {
          continue;
        }
        let index = uniqueIuNames[componentNames[i]];
        if (index == undefined) {
          uniqueIuNames[componentNames[i]] = (index = uniqueIuNamesCount++);
        }
        encodedNames.push(index);
      }
      prevIusEncoded.push(encodedNames.join('/'));
    });
    return {
      'iu_parts': Object.keys(uniqueIuNames).join(),
      'enc_prev_ius': prevIusEncoded.join(),
    };
  },
  // Although declared at a block-level, this is actually page level so
  // return true if ANY indicate cookie opt out.
  instances => getFirstInstanceValue_(instances, instance => {
    return instance.jsonTargeting_ &&
        instance.jsonTargeting_['cookieOptOut'] ? {'co': '1'} : null;
  }),
  instances => {
    return {'adks': instances.map(instance => instance.adKey_).join()};
  },
  instances => {
    return {'prev_iu_szs': instances.map(instance =>
      `${instance.initialSize_.width}x${instance.initialSize_.height}`).join()};
  },
  // Although declared at a block-level, this is actually page level so
  // return true if ANY indicate TFCD.
  instances => getFirstInstanceValue_(instances, instance => {
    return instance.jsonTargeting_ && instance.jsonTargeting_[TFCD] ?
      {'tfcd': instance.jsonTargeting_[TFCD]} : null;
  }),
  // Although declared at a block-level, this is actually page level so
  // return true if ANY indicate manual experiment.
  instances => getFirstInstanceValue_(instances, instance => {
    return isInManualExperiment(instance.element) ? {'adtest': 'on'} : null;
  }),
  instances => {
    const scps = [];
    instances.forEach(instance => {
      if (!instance.jsonTargeting_) {
        return;
      }
      scps.push(serializeTargeting_(
          instance.jsonTargeting_['targeting'] || null,
          instance.jsonTargeting_['categoryExclusions'] || null));
    });
    return scps.length ? {'prev_scp': scps.join('|')} : null;
  },
  instances => {
    const eids = {};
    instances.forEach(instance => {
      const currEids = instance.element.getAttribute('data-experiment-id');
      if (currEids) {
        currEids.split(',').forEach(eid => eids[eid] = 1);
      }
    });
    return Object.keys(eids).length ? {'eid': Object.keys(eids).join()} : null;
  },
  instances => getFirstInstanceValue_(instances,
      instance => instance.buildIdentityParams_()),
  instances => {
    let safeframeForced = false;
    const forceSafeframes = [];
    instances.forEach(instance => {
      safeframeForced = safeframeForced || instance.forceSafeframe_;
      forceSafeframes.push(Number(instance.forceSafeframe_));
    });
    return safeframeForced ? {'fsfs': forceSafeframes.join(',')} : null;
  },
];

/** @final */
export class AmpAdNetworkDoubleclickImpl extends AmpA4A {

  /**
   * @param {!Element} element
   */
  constructor(element) {
    super(element);

    /**
     * @type
     * {!../../../ads/google/a4a/performance.GoogleAdLifecycleReporter}
     */
    this.lifecycleReporter_ = this.lifecycleReporter_ ||
        this.initLifecycleReporter();

    /**
     * Config to generate amp-analytics element for active view reporting.
     * @type {?JsonObject}
     * @private
     */
    this.ampAnalyticsConfig_ = null;

    /** @private {!../../../src/service/extensions-impl.Extensions} */
    this.extensions_ = Services.extensionsFor(this.win);

    /** @private {?string} */
    this.qqid_ = null;

    /** @private {?LayoutRectOrDimsDef} */
    this.initialSize_ = null;

    /** @private {?{width: number, height: number}} */
    this.returnedSize_ = null;

    /** @private {?Element} */
    this.ampAnalyticsElement_ = null;

    /** @private {?Object<string,*>}*/
    this.jsonTargeting_ = null;

    /** @private {number} */
    this.adKey_ = 0;

    /** @protected @const {boolean} */
    this.useSra = getMode().localDev && /(\?|&)force_sra=true(&|$)/.test(
        this.win.location.search) ||
        !!this.win.document.querySelector(
            'meta[name=amp-ad-doubleclick-sra]') ||
        experimentFeatureEnabled(this.win, DOUBLECLICK_EXPERIMENT_FEATURE.SRA);

    const sraInitializer = this.initializeSraPromise_();
    /** @protected {?function(?../../../src/service/xhr-impl.FetchResponse)} */
    this.sraResponseResolver = sraInitializer.resolver;

    /** @protected {?function(*)} */
    this.sraResponseRejector = sraInitializer.rejector;

    /** @private {!Promise<?../../../src/service/xhr-impl.FetchResponse>} */
    this.sraResponsePromise_ = sraInitializer.promise;

    /** @private {?RefreshManager} */
    this.refreshManager_ = null;

    /** @private {number} */
    this.refreshCount_ = 0;

    /** @private {number} */
    this.ifi_ = 0;

    /** @private {boolean} */
    this.isFluid_ = false;

    /** @private {?string} */
    this.fluidImpressionUrl_ = null;

    /** @private {?Promise<!../../../ads/google/a4a/utils.IdentityToken>} */
    this.identityTokenPromise_ = null;

    /** @type {?../../../ads/google/a4a/utils.IdentityToken} */
    this.identityToken = null;

    /** @private {boolean} */
    this.preloadSafeframe_ = true;

    /** @private {!TroubleshootDataDef} */
    this.troubleshootData_ = /** @type {!TroubleshootDataDef} */ ({});

    /**
     * @private {?boolean} whether preferential rendered AMP creative, null
     * indicates no creative render.
     */
    this.isAmpCreative_ = null;

    /** @private {boolean} */
    this.isIdleRender_ = false;

    /** @private {?./safeframe-host.SafeframeHostApi} */
    this.safeframeApi_ = null;

    /** @private {boolean} whether safeframe forced via tag */
    this.forceSafeframe_ = false;
    if ('forceSafeframe' in this.element.dataset) {
      if (!/^(1|(true))$/i.test(this.element.dataset['forceSafeframe'])) {
        user().warn(TAG, 'Ignoring invalid data-force-safeframe attribute: ' +
            this.element.dataset['forceSafeframe']);
      } else {
        this.forceSafeframe_ = true;
      }
    }
  }

  /** @override */
  idleRenderOutsideViewport() {
    // Disable if publisher has indicated a non-default loading strategy.
    if (this.element.getAttribute('data-loading-strategy')) {
      return false;
    }
    const expVal = this.postAdResponseExperimentFeatures['render-idle-vp'];
    let vpRange = parseInt(expVal, 10);
    if (expVal && isNaN(vpRange)) {
      // holdback branch sends non-numeric value.
      return false;
    }
    vpRange = vpRange || 12;
    this.isIdleRender_ = true;
    // NOTE(keithwrightbos): handle race condition where previous
    // idleRenderOutsideViewport marked slot as idle render despite never
    // being schedule due to being beyond viewport max offset.  If slot
    // comes within standard outside viewport range, then ensure throttling
    // will not be applied.
    this.getResource().whenWithinRenderOutsideViewport().then(
        () => this.isIdleRender_ = false);
    return vpRange;
  }

  /** @override */
  isLayoutSupported(layout) {
    this.isFluid_ = layout == Layout.FLUID;
    return this.isFluid_ || isLayoutSizeDefined(layout);
  }

  /** @override */
  isValidElement() {
    return this.isAmpAdElement();
  }

  /** @override */
  delayAdRequestEnabled() {
    return experimentFeatureEnabled(
        this.win, DOUBLECLICK_EXPERIMENT_FEATURE.DELAYED_REQUEST);
  }

  /** @override */
  buildCallback() {
    super.buildCallback();
    this.identityTokenPromise_ = Services.viewerForDoc(this.getAmpDoc())
        .whenFirstVisible()
        .then(() => getIdentityToken(this.win, this.getAmpDoc()));
    this.troubleshootData_.slotId = this.element.getAttribute('data-slot');
    this.troubleshootData_.slotIndex =
        this.element.getAttribute('data-amp-slot-index');
    if (this.win['dbclk_a4a_viz_change']) {
      // Only create one per page but ensure all slots get experiment
      // selection.
      const expId = getExperimentBranch(this.win, CORRELATOR_CLEAR_EXP_NAME);
      if (expId) {
        // If experiment was already selected, add to element to ensure it is
        // included in request.
        addExperimentIdToElement(expId, this.element);
      }
      return;
    }
    this.win['dbclk_a4a_viz_change'] = true;

    const sfPreloadExpName = 'a4a-safeframe-preloading-off';
    const experimentInfoMap =
        /** @type {!Object<string,
        !../../../src/experiments.ExperimentInfo>} */ ({});
    experimentInfoMap[sfPreloadExpName] = {
      isTrafficEligible: () => true,
      branches: ['21061135', '21061136'],
    };
    randomlySelectUnsetExperiments(this.win, experimentInfoMap);
    const sfPreloadExpId = getExperimentBranch(this.win, sfPreloadExpName);
    if (sfPreloadExpId) {
      addExperimentIdToElement(sfPreloadExpId, this.element);
      this.preloadSafeframe_ = sfPreloadExpId == '21061135';
    }

    const viewer = Services.viewerForDoc(this.getAmpDoc());
    viewer.onVisibilityChanged(() => {
      if (viewer.getVisibilityState() != VisibilityState.PAUSED ||
          this.useSra || !this.win.ampAdPageCorrelator) {
        // Do not allow experiment selection if SRA or correlator was not set.
        return;
      }
      // Select into correlator clear experiment of pause visibility change.
      // Should execute prior to resumeCallback.
      // TODO(keithwrightbos,glevitzy) - determine behavior for correlator
      // interaction with refresh.
      const experimentInfoMap =
          /** @type {!Object<string,
          !../../../src/experiments.ExperimentInfo>} */ ({});
      experimentInfoMap[CORRELATOR_CLEAR_EXP_NAME] = {
        isTrafficEligible: () => true,
        branches: ['22302764','22302765'],
      };
      randomlySelectUnsetExperiments(this.win, experimentInfoMap);
      const expId = getExperimentBranch(this.win, CORRELATOR_CLEAR_EXP_NAME);
      if (expId) {
        // Adding to experiment id will cause it to be included in subsequent ad
        // requests.
        addExperimentIdToElement(expId, this.element);
        if (expId === CORRELATOR_CLEAR_EXP_BRANCHES.EXPERIMENT) {
          // TODO(keithwrightbos) - do not clear if at least one slot on the page
          // is an AMP creative that survived unlayoutCallback in order to
          // ensure competitive exclusion/roadblocking.  Note this only applies
          // to non-backfill inventory.
          dev().info(TAG, 'resetting page correlator');
          this.win.ampAdPageCorrelator = null;
        }
      }
    });
  }

  /** @override */
  shouldPreferentialRenderWithoutCrypto() {
    dev().assert(!isCdnProxy(this.win));
    return !experimentFeatureEnabled(
        this.win, DOUBLECLICK_UNCONDITIONED_EXPERIMENTS.CANONICAL_HLDBK_EXP,
        UNCONDITIONED_CANONICAL_FF_HOLDBACK_EXP_NAME);
  }

  /**
   * @return {!{
   *  resolver: ?function(?../../../src/service/xhr-impl.FetchResponse),
   *  rejector: ?function(*),
   *  promise: !Promise<?../../../src/service/xhr-impl.FetchResponse>,
   * }}
   * @private
   */
  initializeSraPromise_() {
    let resolver = null;
    let rejector = null;
    const promise = new Promise((inResolver, inRejector) => {
      resolver = inResolver;
      rejector = inRejector;
    });
    return {resolver, rejector, promise};
  }

  /** @return {!Object<string,string|boolean|number>} */
  getPageParameters_() {
    return {
      'gdfp_req': '1',
      'sfv': DEFAULT_SAFEFRAME_VERSION,
      'u_sd': this.win.devicePixelRatio,
      'gct': this.getLocationQueryParameterValue('google_preview') || null,
    };
  }

  /**
   * Constructs block-level url parameters with side effect of setting
   * size_, jsonTargeting_, and adKey_ fields.
   * @return {!Object<string,string|boolean|number>}
   */
  getBlockParameters_() {
    dev().assert(this.initialSize_);
    dev().assert(this.jsonTargeting_);
    let sizeStr = this.isFluid_ ?
      '320x50' : `${this.initialSize_.width}x${this.initialSize_.height}`;
    const tfcd = this.jsonTargeting_ && this.jsonTargeting_[TFCD];
    const multiSizeDataStr = this.element.getAttribute('data-multi-size');
    if (multiSizeDataStr) {
      if (this.element.getAttribute('layout') == 'responsive') {
        // TODO(levitzky) Define the behavior and remove this warning.
        user().warn(TAG, 'Behavior of multi-size and responsive layout is ' +
            'currently not well defined. Proceed with caution.');
      }
      const multiSizeValidation = this.element
          .getAttribute('data-multi-size-validation') || 'true';
      // The following call will check all specified multi-size dimensions,
      // verify that they meet all requirements, and then return all the valid
      // dimensions in an array.
      const dimensions = getMultiSizeDimensions(
          multiSizeDataStr,
          this.initialSize_.width,
          this.initialSize_.height,
          multiSizeValidation == 'true',
          this.isFluid_);
      sizeStr += '|' + dimensions
          .map(dimension => dimension.join('x'))
          .join('|');
    }
    this.win['ampAdGoogleIfiCounter'] = this.win['ampAdGoogleIfiCounter'] || 1;
    this.ifi_ = (this.isRefreshing && this.ifi_) ||
        this.win['ampAdGoogleIfiCounter']++;
    return Object.assign({
      'iu': this.element.getAttribute('data-slot'),
      'co': this.jsonTargeting_ &&
          this.jsonTargeting_['cookieOptOut'] ? '1' : null,
      'adk': this.adKey_,
      'sz': sizeStr,
      'output': 'html',
      'impl': 'ifr',
      'tfcd': tfcd == undefined ? null : tfcd,
      'adtest': isInManualExperiment(this.element) ? 'on' : null,
      'ifi': this.ifi_,
      'rc': this.refreshCount_ || null,
      'frc': Number(this.fromResumeCallback) || null,
      'fluid': this.isFluid_ ? 'height' : null,
      'fsf': this.forceSafeframe_ ? '1' : null,
      'scp': serializeTargeting_(
          (this.jsonTargeting_ && this.jsonTargeting_['targeting']) || null,
          (this.jsonTargeting_ &&
            this.jsonTargeting_['categoryExclusions']) || null),
    }, googleBlockParameters(this));
  }

  /**
   * Populate's block-level state for ad URL construction.
   * @visibileForTesting
   */
  populateAdUrlState() {
    // Allow for pub to override height/width via override attribute.
    const width = Number(this.element.getAttribute('data-override-width')) ||
      Number(this.element.getAttribute('width'));
    const height = Number(this.element.getAttribute('data-override-height')) ||
      Number(this.element.getAttribute('height'));
    this.initialSize_ = this.isFluid_ ? {width: 0, height: 0} :
      (width && height ?
        // width/height could be 'auto' in which case we fallback to measured.
        {width, height} : this.getIntersectionElementLayoutBox());
    this.jsonTargeting_ =
      tryParseJson(this.element.getAttribute('json')) || {};
    this.adKey_ = this.generateAdKey_(
        `${this.initialSize_.width}x${this.initialSize_.height}`);
  }

  /** @override */
  getAdUrl(opt_rtcResponsesPromise) {
    if (this.iframe && !this.isRefreshing) {
      dev().warn(TAG, `Frame already exists, sra: ${this.useSra}`);
      return '';
    }
    opt_rtcResponsesPromise = opt_rtcResponsesPromise || Promise.resolve();
    // TODO(keithwrightbos): SRA blocks currently unnecessarily generate full
    // ad url.  This could be optimized however non-SRA ad url is required to
    // fallback to non-SRA if single block.
    this.populateAdUrlState();
    // TODO: Check for required and allowed parameters. Probably use
    // validateData, from 3p/3p/js, after noving it someplace common.
    const startTime = Date.now();
    const identityPromise = Services.timerFor(this.win)
        .timeoutPromise(1000, this.identityTokenPromise_)
        .catch(() => {
          // On error/timeout, proceed.
          return /**@type {!../../../ads/google/a4a/utils.IdentityToken}*/({});
        });
    const urlPromise = Promise.all([opt_rtcResponsesPromise, identityPromise])
        .then(results => {
          const rtcParams = this.mergeRtcResponses_(results[0]);
          this.identityToken = results[1];
          return googleAdUrl(
              this, DOUBLECLICK_BASE_URL, startTime, Object.assign(
                  this.getBlockParameters_(), this.buildIdentityParams_(),
                  this.getPageParameters_(), rtcParams));
        });
    this.troubleshootData_.adUrl = urlPromise;
    return urlPromise;
  }

  /**
   * @return {!Object<string,string>}
   * @private
   */
  buildIdentityParams_() {
    return this.identityToken ? {
      adsid: this.identityToken.token || null,
      jar: this.identityToken.jar || null,
      pucrd: this.identityToken.pucrd || null,
    } : {};
  }

  /**
   * Merges all of the rtcResponses into the JSON targeting and
   * category exclusions.
   * @param {?Array<!rtcResponseDef>} rtcResponseArray
   * @private
   */
  mergeRtcResponses_(rtcResponseArray) {
    if (!rtcResponseArray) {
      return null;
    }
    const artc = [];
    const ati = [];
    const ard = [];
    let exclusions;
    rtcResponseArray.forEach(rtcResponse => {
      artc.push(rtcResponse.rtcTime);
      ati.push(rtcResponse.error || RTC_SUCCESS);
      ard.push(rtcResponse.callout);
      if (rtcResponse.response) {
        if (rtcResponse.response['targeting']) {
          const rewrittenResponse = this.rewriteRtcKeys_(
              rtcResponse.response['targeting'],
              rtcResponse.callout);
          this.jsonTargeting_['targeting'] =
              !!this.jsonTargeting_['targeting'] ?
                deepMerge(this.jsonTargeting_['targeting'],
                    rewrittenResponse) :
                rewrittenResponse;
        }
        if (rtcResponse.response['categoryExclusions']) {
          if (!exclusions) {
            exclusions = {};
            if (this.jsonTargeting_['categoryExclusions']) {
              this.jsonTargeting_['categoryExclusions'].forEach(exclusion => {
                exclusions[exclusion] = true;
              });
            }
          }
          rtcResponse.response['categoryExclusions'].forEach(exclusion => {
            exclusions[exclusion] = true;
          });
        }
      }
    });
    if (exclusions) {
      this.jsonTargeting_['categoryExclusions'] = Object.keys(exclusions);
    }
    return {'artc': artc.join() || null, 'ati': ati.join(), 'ard': ard.join()};
  }

  /** @override */
  getCustomRealTimeConfigMacros_() {
    /**
     * This whitelist allow attributes on the amp-ad element to be used as
     * macros for constructing the RTC URL. Add attributes here, in lowercase,
     * to make them available.
     */
    const whitelist = {
      'height': true,
      'width': true,
      'data-slot': true,
      'data-multi-size': true,
      'data-multi-size-validation': true,
      'data-override-width': true,
      'data-override-height': true,
    };
    return {
      PAGEVIEWID: () => Services.documentInfoForDoc(this.element).pageViewId,
      HREF: () => this.win.location.href,
      TGT: () =>
        JSON.stringify(
            (tryParseJson(
                this.element.getAttribute('json')) || {})['targeting']),
      ADCID: opt_timeout => getOrCreateAdCid(
          this.getAmpDoc(), 'AMP_ECID_GOOGLE', '_ga',
          parseInt(opt_timeout, 10)),
      ATTR: name => {
        if (!whitelist[name.toLowerCase()]) {
          dev().warn('TAG', `Invalid attribute ${name}`);
        } else {
          return this.element.getAttribute(name);
        }
      },
      CANONICAL_URL: () =>
        Services.documentInfoForDoc(this.element).canonicalUrl,
    };
  }

  /**
   * Appends the callout value to the keys of response to prevent a collision
   * case caused by multiple vendors returning the same keys.
   * @param {!Object<string, string>} response
   * @param {string} callout
   * @return {!Object<string, string>}
   * @private
   */
  rewriteRtcKeys_(response, callout) {
    // Only perform this substitution for vendor-defined URLs.
    if (!RTC_VENDORS[callout] || RTC_VENDORS[callout].disableKeyAppend) {
      return response;
    }
    const newResponse = {};
    Object.keys(response).forEach(key => {
      newResponse[`${key}_${callout}`] = response[key];
    });
    return newResponse;
  }

  /** @override */
  onNetworkFailure(error, adUrl) {
    dev().info(TAG, 'network error, attempt adding of error parameter', error);
    return {adUrl: maybeAppendErrorParameter(adUrl, 'n')};
  }

  /** @override */
  extractSize(responseHeaders) {
    setGoogleLifecycleVarsFromHeaders(responseHeaders, this.lifecycleReporter_);
    this.ampAnalyticsConfig_ = extractAmpAnalyticsConfig(this, responseHeaders);
    this.qqid_ = responseHeaders.get(QQID_HEADER);
    this.troubleshootData_.creativeId =
        responseHeaders.get('google-creative-id');
    this.troubleshootData_.lineItemId =
        responseHeaders.get('google-lineitem-id');
    if (this.ampAnalyticsConfig_) {
      // Load amp-analytics extensions
      this.extensions_./*OK*/installExtensionForDoc(
          this.getAmpDoc(), 'amp-analytics');
    }
    if (this.isFluid_) {
      this.fluidImpressionUrl_ = responseHeaders.get('X-AmpImps');
    } else {
      this.fireDelayedImpressions(responseHeaders.get('X-AmpImps'));
      this.fireDelayedImpressions(responseHeaders.get('X-AmpRSImps'), true);
    }
    // If the server returned a size, use that, otherwise use the size that we
    // sent in the ad request.
    let size = super.extractSize(responseHeaders);
    if (size) {
      this.returnedSize_ = size;
      this.handleResize_(size.width, size.height);
    } else {
      size = this.getSlotSize();
    }
    return size;
  }

  /**
   * Returns the width and height of the slot as defined by the width and height
   * attributes, or the dimensions as computed by
   * getIntersectionElementLayoutBox.
   * @return {{width: number, height: number}|../../../src/layout-rect.LayoutRectDef}
   */
  getSlotSize() {
    const width = Number(this.element.getAttribute('width'));
    const height = Number(this.element.getAttribute('height'));
    return width && height
      ? {width, height}
      // width/height could be 'auto' in which case we fallback to measured.
      : this.getIntersectionElementLayoutBox();
  }

  /** @override */
  emitLifecycleEvent(eventName, opt_extraVariables) {
    if (opt_extraVariables) {
      this.lifecycleReporter_.setPingParameters(opt_extraVariables);
    }
    this.lifecycleReporter_.sendPing(eventName);
  }

  /** @override */
  tearDownSlot() {
    super.tearDownSlot();
    this.element.setAttribute('data-amp-slot-index',
        this.win.ampAdSlotIdCounter++);
    this.lifecycleReporter_ = this.initLifecycleReporter();
    if (this.ampAnalyticsElement_) {
      removeElement(this.ampAnalyticsElement_);
      this.ampAnalyticsElement_ = null;
    }
    this.ampAnalyticsConfig_ = null;
    this.jsonTargeting_ = null;
    this.isAmpCreative_ = null;
    this.isIdleRender_ = false;
    // Reset SRA requests to allow for resumeCallback to re-fetch
    // ad requests.  Assumes that unlayoutCallback will be called for all slots
    // in rapid succession (meaning onLayoutMeasure initiated promise chain
    // will not be started until resumeCallback).
    sraRequests = null;
    const sraInitializer = this.initializeSraPromise_();
    this.sraResponseResolver = sraInitializer.resolver;
    this.sraResponseRejector = sraInitializer.rejector;
    this.sraResponsePromise_ = sraInitializer.promise;
    this.qqid_ = null;
  }

  /** @override */
  renderNonAmpCreative() {
    // If render idle with throttling, impose one second render delay for
    // non-AMP creatives.  This is not done in the scheduler to ensure as many
    // slots as possible are marked for layout given scheduler imposes 5 seconds
    // past previous execution.
    if (this.postAdResponseExperimentFeatures['render-idle-throttle'] &&
          this.isIdleRender_) {
      if (is3pThrottled(this.win)) {
        return waitFor3pThrottle().then(() => super.renderNonAmpCreative());
      } else {
        incrementLoadingAds(this.win);
        return super.renderNonAmpCreative(true);
      }
    }
    return super.renderNonAmpCreative();
  }

  /** @override  */
  unlayoutCallback() {
    switch (this.postAdResponseExperimentFeatures['unlayout_exp']) {
      case 'all':
        // Ensure all creatives are removed.
        this.isAmpCreative_ = false;
        break;
      case 'remain':
        if (this.qqid_ && this.isAmpCreative_ === null) {
          // Ad response received but not yet rendered.  Note that no fills
          // would fall into this case even if layoutCallback has executed.
          // Assume high probability of continued no fill therefore do not
          // tear down.
          dev().info(TAG, 'unlayoutCallback - unrendered creative can remain');
          return false;
        }
    }
    if (!this.useSra && this.isAmpCreative_) {
      // Allow non-AMP creatives to remain unless SRA.
      return false;
    }
    this.destroySafeFrameApi_();
    return super.unlayoutCallback();
  }

  /** @visibleForTesting */
  cleanupAfterTest() {
    this.destroySafeFrameApi_();
  }

  /** @private */
  destroySafeFrameApi_() {
    if (!this.safeframeApi_) {
      return;
    }
    this.safeframeApi_.destroy();
    this.safeframeApi_ = null;
  }

  /** @override */
  refresh(refreshEndCallback) {
    this.refreshCount_++;
    return super.refresh(refreshEndCallback);
  }

  /**
   * @return {!../../../ads/google/a4a/performance.BaseLifecycleReporter}
   */
  initLifecycleReporter() {
    return googleLifecycleReporterFactory(this);
  }

  /** @override */
  onCreativeRender(creativeMetaData) {
    super.onCreativeRender(creativeMetaData);
    this.isAmpCreative_ = !!creativeMetaData;
    if (creativeMetaData &&
        !creativeMetaData.customElementExtensions.includes('amp-ad-exit')) {
      // Capture phase click handlers on the ad if amp-ad-exit not present
      // (assume it will handle capture).
      dev().assert(this.iframe);
      installAnchorClickInterceptor(
          this.getAmpDoc(), this.iframe.contentWindow);
    }
    if (this.ampAnalyticsConfig_) {
      dev().assert(!this.ampAnalyticsElement_);
      if (isReportingEnabled(this)) {
        addCsiSignalsToAmpAnalyticsConfig(
            this.win,
            this.element,
            this.ampAnalyticsConfig_,
            this.qqid_,
            !!creativeMetaData,
            this.lifecycleReporter_.getDeltaTime(),
            this.lifecycleReporter_.getInitTime());
      }
      this.ampAnalyticsElement_ = insertAnalyticsElement(
          this.element, this.ampAnalyticsConfig_, /*loadAnalytics*/ true,
          !!this.postAdResponseExperimentFeatures['avr_disable_immediate']);
    }
    if (this.isRefreshing) {
      dev().assert(this.refreshManager_);
      this.refreshManager_.initiateRefreshCycle();
      this.isRefreshing = false;
      this.isRelayoutNeededFlag = false;
    }

    this.lifecycleReporter_.addPingsForVisibility(this.element);

    // Force size of frame to match creative or, if creative size is unknown,
    // the slot. This ensures that the creative is centered in the former case,
    // and not truncated in the latter.
    const size = this.returnedSize_ || this.getSlotSize();
    const isMultiSizeFluid = this.isFluid_ && this.returnedSize_ &&
        // TODO(@glevitzky, 11583) Remove this clause once we stop sending back
        // the size header for fluid ads. Fluid size headers always come back as
        // 0x0.
        !(size.width == 0 && size.height == 0);
    setStyles(dev().assertElement(this.iframe), {
      width: `${size.width}px`,
      height: `${size.height}px`,
      position: isMultiSizeFluid ? 'relative' : null,
    });
    if (isMultiSizeFluid) {
      // This is a fluid + multi-size request, where the returned creative is
      // multi-size. The slot needs to not be styled with width: 100%, or the
      // creative will be centered instead of left-aligned.
      this.element.removeAttribute('height');
      setStyles(this.element, {width: `${size.width}px`});
    }

    this.refreshManager_ = this.refreshManager_ ||
        getRefreshManager(this, () => {
          if (this.useSra) {
            user().warn(TAG, 'Refresh not compatible with SRA.');
            return false;
          }
          if (getEnclosingContainerTypes(this.element).filter(container =>
            container != ValidAdContainerTypes['AMP-CAROUSEL'] &&
                container != ValidAdContainerTypes['AMP-STICKY-AD']).length) {
            user().warn(TAG,
                'Refresh not compatible with ad-containers, except for ' +
                'AMP-CAROUSEL and AMP-STICKY-AD');
            return false;
          }
          return true;
        });

    this.postTroubleshootMessage();
  }

  /**
   * @param {string} size
   * @return {string} The ad unit hash key string.
   * @private
   */
  generateAdKey_(size) {
    const element = this.element;
    const domFingerprint = domFingerprintPlain(element);
    const slot = element.getAttribute('data-slot') || '';
    const multiSize = element.getAttribute('data-multi-size') || '';
    const string = `${slot}:${size}:${multiSize}:${domFingerprint}`;
    return stringHash32(string);
  }

  /**
   * Attempts to resize the ad, if the returned size is smaller than the primary
   * dimensions.
   * @param {number} width
   * @param {number} height
   * @private
   */
  handleResize_(width, height) {
    const pWidth = this.element.getAttribute('width');
    const pHeight = this.element.getAttribute('height');
    // We want to resize only if neither returned dimension is larger than its
    // primary counterpart, and if at least one of the returned dimensions
    // differ from its primary counterpart.
    if ((this.isFluid_ && width && height) ||
        (width != pWidth || height != pHeight) &&
        (width <= pWidth && height <= pHeight)) {
      this.attemptChangeSize(height, width).catch(() => {});
    }
  }

  /** @override */
  sendXhrRequest(adUrl) {
    if (!this.useSra) {
      return super.sendXhrRequest(adUrl);
    }
    // Wait for SRA request which will call response promise when this block's
    // response has been returned.
    this.initiateSraRequests();
    // Null response indicates single slot should execute using non-SRA method.
    return this.sraResponsePromise_.then(
        response => response || super.sendXhrRequest(adUrl));
  }

  /**
   * @param {string} impressions
   * @param {boolean=} scrubReferer
   * @visibleForTesting
   */
  fireDelayedImpressions(impressions, scrubReferer) {
    if (!impressions) {
      return;
    }
    impressions.split(',').forEach(url => {
      try {
        if (!isSecureUrl(url)) {
          dev().warn(TAG, `insecure impression url: ${url}`);
          return;
        }
        // Create amp-pixel and append to document to send impression.
        this.win.document.body.appendChild(
            createElementWithAttributes(
                this.win.document,
                'amp-pixel',
                dict({
                  'src': url,
                  'referrerpolicy': scrubReferer ? 'no-referrer' : '',
                })));
      } catch (unusedError) {}
    });
  }

  /**
   * Groups slots by type and networkId from data-slot parameter.  Exposed for
   * ease of testing.
   * @return {!Promise<!Object<string,!Array<!Promise<!../../../src/base-element.BaseElement>>>>}
   * @visibileForTesting
   */
  groupSlotsForSra() {
    return groupAmpAdsByType(
        this.win, this.element.getAttribute('type'), getNetworkId);
  }

  /**
   * Executes SRA request via the following steps:
   * - create only one executor per page
   * - get all doubleclick amp-ad instances on the page
   * - group by networkID allowing for separate SRA requests
   * - for each grouping, construct SRA request
   * - handle chunks for streaming response for each block
   * @visibileForTesting
   */
  initiateSraRequests() {
    if (sraRequests) {
      return;
    }
    // Use cancellation of the first slot's promiseId as indication of
    // unlayoutCallback execution.  Assume that if called for one slot, it will
    // be called for all and we should cancel SRA execution.
    const checkStillCurrent = this.verifyStillCurrent();
    sraRequests = this.groupSlotsForSra()
        .then(groupIdToBlocksAry => {
          checkStillCurrent();
          Object.keys(groupIdToBlocksAry).forEach(networkId => {
            const blocks = dev().assert(groupIdToBlocksAry[networkId]);
            // TODO: filter blocks with SRA disabled?
            Promise.all(blocks).then(instances => {
              dev().assert(instances.length);
              checkStillCurrent();
              // Exclude any instances that do not have an adPromise_ as this
              // indicates they were invalid.
              const typeInstances =
              /** @type {!Array<!AmpAdNetworkDoubleclickImpl>}*/(instances)
                    .filter(instance => {
                      const isValid = instance.hasAdPromise();
                      if (!isValid) {
                        dev().info(TAG,
                            'Ignoring instance without ad promise as ' +
                            'likely invalid',
                            instance.element);
                      }
                      return isValid;
                    });
              if (!typeInstances.length) {
              // Only contained invalid elements.
                return;
              }
              // Determine if more than one block for this element, if not do not
              // set sra request promise which results in sending as
              // non-SRA request (benefit is it allows direct cache method).
              if (typeInstances.length == 1) {
                dev().info(TAG, `single block in network ${networkId}`);
                typeInstances[0].sraResponseResolver(null);
                return;
              }
              // Construct and send SRA request.
              // Chunk hanlder called with metadata and creative for each slot
              // in order of URLs given.  Construct promise for each slot
              // such that its resolver will be called.
              const sraRequestAdUrlResolvers =
              typeInstances.map(instance => instance.sraResponseResolver);
              const slotCallback = metaJsonCreativeGrouper(
                  (creative, headersObj, done) => {
                    checkStillCurrent();
                    // Force safeframe rendering method.
                    headersObj[RENDERING_TYPE_HEADER.toLowerCase()] =
                        XORIGIN_MODE.SAFEFRAME;
                    // Construct pseudo fetch response to be passed down the A4A
                    // promise chain for this block.
                    const headers =
                  /** @type {?../../../src/service/xhr-impl.FetchResponseHeaders} */
                  ({
                    get: name => headersObj[name.toLowerCase()],
                    has: name => !!headersObj[name.toLowerCase()],
                  });
                    const fetchResponse =
                  /** @type {?../../../src/service/xhr-impl.FetchResponse} */
                  ({
                    headers,
                    arrayBuffer: () => Promise.resolve(utf8Encode(creative)),
                  });
                    // Pop head off of the array of resolvers as the response
                    // should match the order of blocks declared in the ad url.
                    // This allows the block to start rendering while the SRA
                    // response is streaming back to the client.
                    dev().assert(sraRequestAdUrlResolvers.shift())(
                        fetchResponse);
                    // If done, expect array to be empty (ensures ad response
                    // included data for all slots).
                    if (done && sraRequestAdUrlResolvers.length) {
                      dev().warn(TAG, 'Premature end of SRA response',
                          sraRequestAdUrlResolvers.length, sraUrl);
                    }
                  });
              // TODO(keithwrightbos) - how do we handle per slot 204 response?
              let sraUrl;
              return constructSRARequest_(
                  this.win, this.getAmpDoc(), typeInstances)
                  .then(sraUrlIn => {
                    checkStillCurrent();
                    sraUrl = sraUrlIn;
                    return Services.xhrFor(this.win).fetch(sraUrl, {
                      mode: 'cors',
                      method: 'GET',
                      credentials: 'include',
                    });
                  })
                  .then(response => {
                    checkStillCurrent();
                    return lineDelimitedStreamer(
                        this.win, response, slotCallback);
                  })
                  .catch(error => {
                    assignAdUrlToError(/** @type {!Error} */(error), sraUrl);
                    const canceled = isCancellation(error);
                    if (!canceled) {
                      this.user().error(TAG, 'SRA request failure', error);
                    }
                    // Collapse all slots on failure so long as they are not
                    // cancellation.
                    typeInstances.forEach(instance => {
                      // Reset ad url to ensure layoutCallback does not fallback to
                      // frame get which would lose SRA guarantees.
                      // TODO(keithwrightbos): publisher should indicate if
                      // explicit is required!
                      instance.resetAdUrl();
                      if (!canceled) {
                        instance.attemptCollapse();
                      }
                      instance.sraResponseRejector(error);
                    });
                  });
            });
          });
        });
  }

  getPreconnectUrls() {
    const urls = ['https://partner.googleadservices.com'];
    if (this.preloadSafeframe_) {
      urls.push(SAFEFRAME_ORIGIN);
    }
    return urls;
  }

  /** @override */
  getNonAmpCreativeRenderingMethod(headerValue) {
    return this.forceSafeframe_ || this.isFluid_ ? XORIGIN_MODE.SAFEFRAME :
      super.getNonAmpCreativeRenderingMethod(headerValue);
  }

  /**
   * Note that location is parsed once on first access and cached.
   * @param {string} parameterName
   * @return {string|undefined} parameter value from window.location.search
   * @visibleForTesting
   */
  getLocationQueryParameterValue(parameterName) {
    windowLocationQueryParameters = windowLocationQueryParameters ||
        parseQueryString((this.win.location && this.win.location.search) || '');
    return windowLocationQueryParameters[parameterName];
  }

  /** @override */
  getAdditionalContextMetadata(isSafeFrame = false) {
    if (!this.isFluid_ && !isSafeFrame) {
      return;
    }
    const creativeSize = this.getCreativeSize();
    dev().assert(creativeSize, 'this.getCreativeSize returned null');
    this.safeframeApi_ = this.safeframeApi_ ||
        new SafeframeHostApi(
            this, this.isFluid_, this.initialSize_,
            /** @type {{height, width}} */(creativeSize),
            this.fluidImpressionUrl_);

    return this.safeframeApi_.getSafeframeNameAttr();
  }

  /**
   * Emits a postMessage containing information about this slot to the DFP
   * Troubleshoot UI. A promise is returned if a message is posted, otherwise
   * null is returned. The promise is returned only for test convenience.
   *
   * @return {?Promise}
   * @visibleForTesting
   */
  postTroubleshootMessage() {
    if (!this.win.opener || !/[?|&]dfpdeb/.test(this.win.location.search)) {
      return null;
    }
    dev().assert(this.troubleshootData_.adUrl, 'ad URL does not exist yet');
    return this.troubleshootData_.adUrl.then(adUrl => {
      const slotId = this.troubleshootData_.slotId + '_' +
          this.troubleshootData_.slotIndex;
      const payload = dict({
        'gutData': JSON.stringify(dict({
          'events': [{
            'timestamp': Date.now(),
            'slotid': slotId,
            'messageId': 4,
          }],
          'slots': [{
            'contentUrl': adUrl || '',
            'id': slotId,
            'leafAdUnitName': this.troubleshootData_.slotId,
            'domId': slotId,
            'lineItemId': this.troubleshootData_.lineItemId,
            'creativeId': this.troubleshootData_.creativeId,
          }],
        })),
        'userAgent': navigator.userAgent,
        'referrer': this.win.location.href,
        'messageType': 'LOAD',
      });
      this.win.opener./*OK*/postMessage(payload, '*');
    });
  }

  /** @override */
  getA4aAnalyticsVars(analyticsTrigger) {
    return getCsiAmpAnalyticsVariables(analyticsTrigger, this, this.qqid_);
  }

  /** @override */
  getA4aAnalyticsConfig() {
    return getCsiAmpAnalyticsConfig();
  }
}

AMP.extension(TAG, '0.1', AMP => {
  AMP.registerElement(TAG, AmpAdNetworkDoubleclickImpl);
});

/** @visibleForTesting */
export function resetSraStateForTesting() {
  sraRequests = null;
}

/** @visibleForTesting */
export function resetLocationQueryParametersForTesting() {
  windowLocationQueryParameters = null;
}

/**
 * @param {!Element} element
 * @return {string} networkId from data-ad-slot attribute.
 * @visibileForTesting
 */
export function getNetworkId(element) {
  const networkId = /^(?:\/)?(\d+)/.exec(
      dev().assertString(element.getAttribute('data-slot')));
  // TODO: guarantee data-ad-slot format as part of isValidElement?
  return networkId ? networkId[1] : '';
}

/**
 * @param {!Window} win
 * @param {!Node|!../../../src/service/ampdoc-impl.AmpDoc} doc
 * @param {!Array<!AmpAdNetworkDoubleclickImpl>} instances
 * @return {!Promise<string>} SRA request URL
 */
export function constructSRARequest_(win, doc, instances) {
  // TODO(bradfrizzell): Need to add support for RTC.
  dev().assert(instances && instances.length);
  const startTime = Date.now();
  return googlePageParameters(win, doc, startTime)
      .then(googPageLevelParameters => {
        const blockParameters = constructSRABlockParameters(instances);
        return truncAndTimeUrl(DOUBLECLICK_BASE_URL,
            Object.assign(blockParameters, googPageLevelParameters,
                instances[0].getPageParameters_()),
            startTime);
      });
}

/**
 * @param {!Array<!AmpAdNetworkDoubleclickImpl>} instances
 * @return {!Object<string, *>}
 * @visibileForTesting
 */
export function constructSRABlockParameters(instances) {
  const parameters = {'output': 'ldjh', 'impl': 'fifs'};
  BLOCK_SRA_COMBINERS_.forEach(
      combiner => Object.assign(parameters, combiner(instances)));
  return parameters;
}

/**
 * @param {?Object<string, (!Array<string>|string)>} targeting
 * @param {?(!Array<string>|string)} categoryExclusions
 * @return {?string}
 * @private
 */
function serializeTargeting_(targeting, categoryExclusions) {
  const serialized = targeting ?
    Object.keys(targeting).map(key => serializeItem_(key, targeting[key])) :
    [];
  if (categoryExclusions) {
    serialized.push(serializeItem_('excl_cat', categoryExclusions));
  }
  return serialized.length ? serialized.join('&') : null;
}

/**
 * @param {string} key
 * @param {(!Array<string>|string)} value
 * @return {string}
 * @private
 */
function serializeItem_(key, value) {
  const serializedValue =
    (Array.isArray(value) ? value : [value]).map(encodeURIComponent).join();
  return `${encodeURIComponent(key)}=${serializedValue}`;
}

/**
 * @param {!Array<!AmpAdNetworkDoubleclickImpl>} instances
 * @param {function(AmpAdNetworkDoubleclickImpl):?T} extractFn
 * @return {?T} value of first instance with non-null/undefined value or null
 *    if none can be found
 * @template T
 * @private
 */
function getFirstInstanceValue_(instances, extractFn) {
  for (let i = 0; i < instances.length; i++) {
    const val = extractFn(instances[i]);
    if (val) {
      return val;
    }
  }
  return null;
}


text:
require 'turn/reporter'

module Turn

  # = Pretty Reporter (by Paydro)
  #
  class PrettyReporter < Reporter
    #
    PADDING_SIZE = 4

    #
    def start_suite(suite)
      #old_sync, @@out.sync = @@out.sync, true if io.respond_to? :sync=
      @suite  = suite
      @time   = Time.now
      #@stdout = StringIO.new
      #@stderr = StringIO.new
      #files = suite.collect{ |s| s.file }.join(' ')
      io.puts "Loaded suite #{suite.name}"
      #io.puts "Loaded suite #{$0.sub(/\.rb$/, '')}\nStarted"
      io.puts "Started"
    end

    #
    def start_case(kase)
      #if kase.size > 0  # TODO: Don't have size yet?
        io.print "\n#{kase.name}:\n"
      #end
    end

    #
    def start_test(test)
      @test_time = Time.now
      @test = test
      #if @file != test.file
      #  @file = test.file
      #  io.puts(test.file)
      #end
      #io.print "    %-69s" % test.name
      #$stdout = @stdout
      #$stderr = @stderr
      #$stdout.rewind
      #$stderr.rewind
    end

    #
    def pass(message=nil)
      io.print pad_with_size("#{PASS}")
      io.print " #{@test}"
      io.print " (%.2fs) " % (Time.now - @test_time)
      if message
        message = Colorize.magenta(message)
        message = message.to_s.tabto(10)
        io.puts(message)
      end
    end

    #
    def fail(assertion)
      io.print pad_with_size("#{FAIL}")
      io.print " #{@test}"
      io.print " (%.2fs) " % (Time.now - @test_time)

      #message = assertion.location[0] + "\n" + assertion.message #.gsub("\n","\n")
      #trace   = MiniTest::filter_backtrace(report[:exception].backtrace).first

      message = assertion.message

      if assertion.respond_to?(:backtrace)
        trace = filter_backtrace(assertion.backtrace).first
      else
        trace = filter_backtrace(assertion.location).first
      end

      io.puts
      #io.puts pad(message, 10)
      io.puts message.tabto(10)
      io.puts trace.tabto(10)
      #show_captured_output
    end

    #
    def error(exception)
      io.print pad_with_size("#{ERROR}")
      io.print " #{@test}"
      io.print " (%.2fs) " % (Time.now - @test_time)

      #message = exception.to_s.split("\n")[2..-1].join("\n")

      message = exception.message

      if exception.respond_to?(:backtrace)
        trace = filter_backtrace(exception.backtrace).first
      else
        trace = filter_backtrace(exception.location).first
      end

      io.puts
      io.puts message.tabto(10)
      io.puts trace.tabto(10)
    end

    # TODO: skip support
    #def skip
    #  io.puts(pad_with_size("#{SKIP}"))
    #end

    #
    def finish_test(test)
      io.puts
      #@test_count += 1
      #@assertion_count += inst._assertions
      #$stdout = STDOUT
      #$stderr = STDERR
    end

=begin
    def show_captured_output
      show_captured_stdout
      show_captured_stderr
    end

    def show_captured_stdout
      @stdout.rewind
      return if @stdout.eof?
      STDOUT.puts(<<-output.tabto(8))
\nSTDOUT:
#{@stdout.read}
      output
    end

    def show_captured_stderr
      @stderr.rewind
      return if @stderr.eof?
      STDOUT.puts(<<-output.tabto(8))
\nSTDERR:
#{@stderr.read}
      output
    end
=end

    def finish_case(kase)
      if kase.size == 0
        io.puts pad("(No Tests)")
      end
    end

    #
    def finish_suite(suite)
      #@@out.sync = old_sync if @@out.respond_to? :sync=

      total   = suite.count_tests
      failure = suite.count_failures
      error   = suite.count_errors
      #pass    = total - failure - error

      io.puts
      io.puts "Finished in #{'%.6f' % (Time.now - @time)} seconds."
      io.puts

      io.print "%d tests, " % total
      io.print "%d assertions, " % suite.count_assertions
      io.print Colorize.fail( "%d failures" % failure) + ', '
      io.print Colorize.error("%d errors" % error) #+ ', '
      #io.puts  Colorize.cyan( "%d skips" % skips ) #TODO
      io.puts
    end

  private

    #
    def pad(str, size=PADDING_SIZE)
      " " * size + str
    end

    #
    def pad_with_size(str)
      " " * (18 - str.size) + str
    end

  end

end


text:
<?php
declare(strict_types=1);

namespace BootstrapUI\View\Widget;

use Cake\View\Form\ContextInterface;
use Cake\View\StringTemplate;
use Cake\View\Widget\LabelWidget;

/**
 * Input widget class for generating a file upload control.
 */
class FileWidget extends \Cake\View\Widget\FileWidget
{
    use InputGroupTrait;

    /**
     * Label widget.
     *
     * @var \Cake\View\Widget\LabelWidget
     */
    protected $_label;

    /**
     * Constructor
     *
     * @param \Cake\View\StringTemplate $templates Templates list.
     * @param \Cake\View\Widget\LabelWidget $label Label widget instance.
     */
    public function __construct(StringTemplate $templates, LabelWidget $label)
    {
        $this->_label = $label;

        parent::__construct($templates);
    }

    /**
     * Render a file upload form widget.
     *
     * Data supports the following keys:
     *
     * - `name` - Set the input name.
     * - `escape` - Set to false to disable HTML escaping.
     *
     * All other keys will be converted into HTML attributes.
     * Unlike other input objects the `val` property will be specifically
     * ignored.
     *
     * @param array $data The data to build a file input with.
     * @param \Cake\View\Form\ContextInterface $context The current form context.
     * @return string HTML elements.
     */
    public function render(array $data, ContextInterface $context): string
    {
        return $this->_withInputGroup($data, $context);
    }
}


text:
import { formUrl } from '../config'
import lang from 'lang'

const langContext = lang.contact.form.validation

function validateName (name) {
  return !!name
}

function validateEmail (email) {
  if (email && email.match(/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i)) {
    return true
  }

  return false
}

function validateMessage (message) {
  return !!message
}

function validateGRecaptchaResponse (gRecaptchaResponse) {
  return !!gRecaptchaResponse
}

async function sendData (name, email, message, gRecaptchaResponse) {
  let postData = JSON.stringify({
    'name': name,
    'mail': email,
    'msg': message,
    'g-recaptcha-response': gRecaptchaResponse
  })

  return fetch(formUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json; charset=UTF-8'
    },
    body: postData
  })
}

function sendToForm (name, email, message, gRecaptchaResponse) {
  return new Promise((resolve, reject) => {
    if (!validateName(name)) {
      return reject(String(langContext.invalid_name))
    }

    if (!validateEmail(email)) {
      return reject(String(langContext.invalid_email))
    }

    if (!validateMessage(message)) {
      return reject(String(langContext.invalid_message))
    }

    if (!validateGRecaptchaResponse(gRecaptchaResponse)) {
      return reject(String(langContext.invalid_grecaptcha))
    }

    sendData(name, email, message, gRecaptchaResponse)
      .then(response => {
        if (response.status >= 200 && response.status < 300) {
          resolve(String(langContext.success))
        } else {
          reject(String(langContext.server_error))
        }
      })
      .catch(err => {
        reject(String(langContext.conection_error))
      })
  })
}

export default sendToForm