ResourceLoadStatistics.cpp   [plain text]


/*
 * Copyright (C) 2016 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "ResourceLoadStatistics.h"

#include "KeyedCoding.h"
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringHash.h>

namespace WebCore {

typedef WTF::HashMap<String, unsigned, StringHash, HashTraits<String>, HashTraits<unsigned>>::KeyValuePairType ResourceLoadStatisticsValue;

static void encodeHashCountedSet(KeyedEncoder& encoder, const String& label, const HashCountedSet<String>& hashCountedSet)
{
    if (hashCountedSet.isEmpty())
        return;

    encoder.encodeObjects(label, hashCountedSet.begin(), hashCountedSet.end(), [](KeyedEncoder& encoderInner, const ResourceLoadStatisticsValue& origin) {
        encoderInner.encodeString("origin", origin.key);
        encoderInner.encodeUInt32("count", origin.value);
    });
}

void ResourceLoadStatistics::encode(KeyedEncoder& encoder) const
{
    encoder.encodeString("PrevalentResourceOrigin", highLevelDomain);
    
    // User interaction
    encoder.encodeBool("hadUserInteraction", hadUserInteraction);
    
    // Top frame stats
    encoder.encodeBool("topFrameHasBeenNavigatedToBefore", topFrameHasBeenNavigatedToBefore);
    encoder.encodeUInt32("topFrameHasBeenRedirectedTo", topFrameHasBeenRedirectedTo);
    encoder.encodeUInt32("topFrameHasBeenRedirectedFrom", topFrameHasBeenRedirectedFrom);
    encoder.encodeUInt32("topFrameInitialLoadCount", topFrameInitialLoadCount);
    encoder.encodeUInt32("topFrameHasBeenNavigatedTo", topFrameHasBeenNavigatedTo);
    encoder.encodeUInt32("topFrameHasBeenNavigatedFrom", topFrameHasBeenNavigatedFrom);
    
    // Subframe stats
    encoder.encodeBool("subframeHasBeenLoadedBefore", subframeHasBeenLoadedBefore);
    encoder.encodeUInt32("subframeHasBeenRedirectedTo", subframeHasBeenRedirectedTo);
    encoder.encodeUInt32("subframeHasBeenRedirectedFrom", subframeHasBeenRedirectedFrom);
    encoder.encodeUInt32("subframeSubResourceCount", subframeSubResourceCount);
    encodeHashCountedSet(encoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
    encodeHashCountedSet(encoder, "subframeUniqueRedirectsTo", subframeUniqueRedirectsTo);
    encoder.encodeUInt32("subframeHasBeenNavigatedTo", subframeHasBeenNavigatedTo);
    encoder.encodeUInt32("subframeHasBeenNavigatedFrom", subframeHasBeenNavigatedFrom);
    
    // Subresource stats
    encoder.encodeUInt32("subresourceHasBeenRedirectedFrom", subresourceHasBeenRedirectedFrom);
    encoder.encodeUInt32("subresourceHasBeenRedirectedTo", subresourceHasBeenRedirectedTo);
    encoder.encodeUInt32("subresourceHasBeenSubresourceCount", subresourceHasBeenSubresourceCount);
    encoder.encodeDouble("subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited", subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited);
    encodeHashCountedSet(encoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
    encodeHashCountedSet(encoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
    
    // Prevalent Resource
    encodeHashCountedSet(encoder, "redirectedToOtherPrevalentResourceOrigins", redirectedToOtherPrevalentResourceOrigins);
    encoder.encodeBool("isPrevalentResource", isPrevalentResource);
    encoder.encodeUInt32("dataRecordsRemoved", dataRecordsRemoved);
}

static void decodeHashCountedSet(KeyedDecoder& decoder, const String& label, HashCountedSet<String>& hashCountedSet)
{
    Vector<String> ignore;
    decoder.decodeObjects(label, ignore, [&hashCountedSet](KeyedDecoder& decoderInner, String& origin) {
        if (!decoderInner.decodeString("origin", origin))
            return false;
        
        unsigned count;
        if (!decoderInner.decodeUInt32("count", count))
            return false;

        hashCountedSet.add(origin, count);
        return true;
    });
}

bool ResourceLoadStatistics::decode(KeyedDecoder& decoder, unsigned version)
{
    if (!decoder.decodeString("PrevalentResourceOrigin", highLevelDomain))
        return false;
    
    // User interaction
    if (!decoder.decodeBool("hadUserInteraction", hadUserInteraction))
        return false;
    
    // Top frame stats
    if (!decoder.decodeBool("topFrameHasBeenNavigatedToBefore", topFrameHasBeenNavigatedToBefore))
        return false;
    
    if (!decoder.decodeUInt32("topFrameHasBeenRedirectedTo", topFrameHasBeenRedirectedTo))
        return false;
    
    if (!decoder.decodeUInt32("topFrameHasBeenRedirectedFrom", topFrameHasBeenRedirectedFrom))
        return false;
    
    if (!decoder.decodeUInt32("topFrameInitialLoadCount", topFrameInitialLoadCount))
        return false;
    
    if (!decoder.decodeUInt32("topFrameHasBeenNavigatedTo", topFrameHasBeenNavigatedTo))
        return false;
    
    if (!decoder.decodeUInt32("topFrameHasBeenNavigatedFrom", topFrameHasBeenNavigatedFrom))
        return false;
    
    // Subframe stats
    if (!decoder.decodeBool("subframeHasBeenLoadedBefore", subframeHasBeenLoadedBefore))
        return false;
    
    if (!decoder.decodeUInt32("subframeHasBeenRedirectedTo", subframeHasBeenRedirectedTo))
        return false;
    
    if (!decoder.decodeUInt32("subframeHasBeenRedirectedFrom", subframeHasBeenRedirectedFrom))
        return false;
    
    if (!decoder.decodeUInt32("subframeSubResourceCount", subframeSubResourceCount))
        return false;

    decodeHashCountedSet(decoder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
    decodeHashCountedSet(decoder, "subframeUniqueRedirectsTo", subframeUniqueRedirectsTo);
    
    if (!decoder.decodeUInt32("subframeHasBeenNavigatedTo", subframeHasBeenNavigatedTo))
        return false;
    
    if (!decoder.decodeUInt32("subframeHasBeenNavigatedFrom", subframeHasBeenNavigatedFrom))
        return false;
    
    // Subresource stats
    if (!decoder.decodeUInt32("subresourceHasBeenRedirectedFrom", subresourceHasBeenRedirectedFrom))
        return false;
    
    if (!decoder.decodeUInt32("subresourceHasBeenRedirectedTo", subresourceHasBeenRedirectedTo))
        return false;
    
    if (!decoder.decodeUInt32("subresourceHasBeenSubresourceCount", subresourceHasBeenSubresourceCount))
        return false;
    
    if (!decoder.decodeDouble("subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited", subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited))
        return false;

    decodeHashCountedSet(decoder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
    decodeHashCountedSet(decoder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
    
    // Prevalent Resource
    decodeHashCountedSet(decoder, "redirectedToOtherPrevalentResourceOrigins", redirectedToOtherPrevalentResourceOrigins);
    
    if (!decoder.decodeBool("isPrevalentResource", isPrevalentResource))
        return false;

    if (version < 2)
        return true;

    if (!decoder.decodeUInt32("dataRecordsRemoved", dataRecordsRemoved))
        return false;
    
    return true;
}

static void appendBoolean(StringBuilder& builder, const String& label, bool flag)
{
    builder.appendLiteral("    ");
    builder.append(label);
    builder.appendLiteral(": ");
    builder.append(flag ? "Yes" : "No");
}

static void appendHashCountedSet(StringBuilder& builder, const String& label, const HashCountedSet<String>& hashCountedSet)
{
    if (hashCountedSet.isEmpty())
        return;

    builder.appendLiteral("    ");
    builder.append(label);
    builder.appendLiteral(":\n");

    for (auto& entry : hashCountedSet) {
        builder.appendLiteral("        ");
        builder.append(entry.key);
        builder.appendLiteral(": ");
        builder.appendNumber(entry.value);
        builder.append('\n');
    }
}

String ResourceLoadStatistics::toString() const
{
    StringBuilder builder;
    
    // User interaction
    appendBoolean(builder, "hadUserInteraction", hadUserInteraction);
    builder.append('\n');
    
    // Top frame stats
    appendBoolean(builder, "topFrameHasBeenNavigatedToBefore", topFrameHasBeenNavigatedToBefore);
    builder.append('\n');
    builder.appendLiteral("    topFrameHasBeenRedirectedTo: ");
    builder.appendNumber(topFrameHasBeenRedirectedTo);
    builder.append('\n');
    builder.appendLiteral("    topFrameHasBeenRedirectedFrom: ");
    builder.appendNumber(topFrameHasBeenRedirectedFrom);
    builder.append('\n');
    builder.appendLiteral("    topFrameInitialLoadCount: ");
    builder.appendNumber(topFrameInitialLoadCount);
    builder.append('\n');
    builder.appendLiteral("    topFrameHasBeenNavigatedTo: ");
    builder.appendNumber(topFrameHasBeenNavigatedTo);
    builder.append('\n');
    builder.appendLiteral("    topFrameHasBeenNavigatedFrom: ");
    builder.appendNumber(topFrameHasBeenNavigatedFrom);
    builder.append('\n');
    
    // Subframe stats
    appendBoolean(builder, "subframeHasBeenLoadedBefore", subframeHasBeenLoadedBefore);
    builder.append('\n');
    builder.appendLiteral("    subframeHasBeenRedirectedTo: ");
    builder.appendNumber(subframeHasBeenRedirectedTo);
    builder.append('\n');
    builder.appendLiteral("    subframeHasBeenRedirectedFrom: ");
    builder.appendNumber(subframeHasBeenRedirectedFrom);
    builder.append('\n');
    builder.appendLiteral("    subframeSubResourceCount: ");
    builder.appendNumber(subframeSubResourceCount);
    builder.append('\n');
    appendHashCountedSet(builder, "subframeUnderTopFrameOrigins", subframeUnderTopFrameOrigins);
    appendHashCountedSet(builder, "subframeUniqueRedirectsTo", subframeUniqueRedirectsTo);
    builder.appendLiteral("    subframeHasBeenNavigatedTo: ");
    builder.appendNumber(subframeHasBeenNavigatedTo);
    builder.append('\n');
    builder.appendLiteral("    subframeHasBeenNavigatedFrom: ");
    builder.appendNumber(subframeHasBeenNavigatedFrom);
    builder.append('\n');
    
    // Subresource stats
    builder.appendLiteral("    subresourceHasBeenRedirectedFrom: ");
    builder.appendNumber(subresourceHasBeenRedirectedFrom);
    builder.append('\n');
    builder.appendLiteral("    subresourceHasBeenRedirectedTo: ");
    builder.appendNumber(subresourceHasBeenRedirectedTo);
    builder.append('\n');
    builder.appendLiteral("    subresourceHasBeenSubresourceCount: ");
    builder.appendNumber(subresourceHasBeenSubresourceCount);
    builder.append('\n');
    builder.appendLiteral("    subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited: ");
    builder.appendNumber(subresourceHasBeenSubresourceCountDividedByTotalNumberOfOriginsVisited);
    builder.append('\n');
    appendHashCountedSet(builder, "subresourceUnderTopFrameOrigins", subresourceUnderTopFrameOrigins);
    appendHashCountedSet(builder, "subresourceUniqueRedirectsTo", subresourceUniqueRedirectsTo);
    
    // Prevalent Resource
    appendHashCountedSet(builder, "redirectedToOtherPrevalentResourceOrigins", redirectedToOtherPrevalentResourceOrigins);
    appendBoolean(builder, "isPrevalentResource", isPrevalentResource);
    builder.appendLiteral("    dataRecordsRemoved: ");
    builder.appendNumber(dataRecordsRemoved);
    builder.append('\n');

    builder.append('\n');

    return builder.toString();
}

template <typename T>
static void mergeHashCountedSet(HashCountedSet<T>& to, const HashCountedSet<T>& from)
{
    for (auto& entry : from)
        to.add(entry.key, entry.value);
}

void ResourceLoadStatistics::merge(const ResourceLoadStatistics& other)
{
    ASSERT(other.highLevelDomain == highLevelDomain);

    hadUserInteraction |= other.hadUserInteraction;
    
    // Top frame stats
    topFrameHasBeenRedirectedTo += other.topFrameHasBeenRedirectedTo;
    topFrameHasBeenRedirectedFrom += other.topFrameHasBeenRedirectedFrom;
    topFrameInitialLoadCount += other.topFrameInitialLoadCount;
    topFrameHasBeenNavigatedTo += other.topFrameHasBeenNavigatedTo;
    topFrameHasBeenNavigatedFrom += other.topFrameHasBeenNavigatedFrom;
    topFrameHasBeenNavigatedToBefore |= other.topFrameHasBeenNavigatedToBefore;
    
    // Subframe stats
    mergeHashCountedSet(subframeUnderTopFrameOrigins, other.subframeUnderTopFrameOrigins);
    subframeHasBeenRedirectedTo += other.subframeHasBeenRedirectedTo;
    subframeHasBeenRedirectedFrom += other.subframeHasBeenRedirectedFrom;
    mergeHashCountedSet(subframeUniqueRedirectsTo, other.subframeUniqueRedirectsTo);
    subframeSubResourceCount += other.subframeSubResourceCount;
    subframeHasBeenNavigatedTo += other.subframeHasBeenNavigatedTo;
    subframeHasBeenNavigatedFrom += other.subframeHasBeenNavigatedFrom;
    subframeHasBeenLoadedBefore |= other.subframeHasBeenLoadedBefore;
    
    // Subresource stats
    mergeHashCountedSet(subresourceUnderTopFrameOrigins, other.subresourceUnderTopFrameOrigins);
    subresourceHasBeenSubresourceCount += other.subresourceHasBeenSubresourceCount;
    subresourceHasBeenRedirectedFrom += other.subresourceHasBeenRedirectedFrom;
    subresourceHasBeenRedirectedTo += other.subresourceHasBeenRedirectedTo;
    mergeHashCountedSet(subresourceUniqueRedirectsTo, other.subresourceUniqueRedirectsTo);
    
    // Prevalent resource stats
    mergeHashCountedSet(redirectedToOtherPrevalentResourceOrigins, other.redirectedToOtherPrevalentResourceOrigins);
    isPrevalentResource |= other.isPrevalentResource;
    dataRecordsRemoved += other.dataRecordsRemoved;
}

}