Title: [181663] trunk
Revision
181663
Author
[email protected]
Date
2015-03-17 13:47:42 -0700 (Tue, 17 Mar 2015)

Log Message

Compile character ranges targeting the same state as range check in the bytecode
https://bugs.webkit.org/show_bug.cgi?id=142759

Patch by Benjamin Poulain <[email protected]> on 2015-03-17
Reviewed by Alex Christensen.

Source/WebCore:

Previously, character ranges would be compiled as many individual character checks.
For example, a transition on "[a-z]" would do 26 character checks + jump, which leads
to enormous matchines.

With this patch, we find the ranges at lowering time and generate a single instruction
for them: "CheckValueRange". This helps making the machine denser when the input
use character sets.

The second part of this patch goes further in the case where the transitions out of
a state cover the entire alphabet. In that case, we create a fallback transition
on the fly and remove all the ranges made useless.
That case is common when ranges are used with inverse character set (e.g. [^a]+a).

* contentextensions/DFABytecode.h:
(WebCore::ContentExtensions::instructionSizeWithArguments):
* contentextensions/DFABytecodeCompiler.cpp:
(WebCore::ContentExtensions::DFABytecodeCompiler::emitCheckValueRange):
(WebCore::ContentExtensions::DFABytecodeCompiler::compileNode):
(WebCore::ContentExtensions::DFABytecodeCompiler::compileNodeTransitions):
(WebCore::ContentExtensions::DFABytecodeCompiler::compileCheckForRange):
* contentextensions/DFABytecodeCompiler.h:
Extend the compiler to detect ranges and lower them as CheckValueRange.

* contentextensions/DFABytecodeInterpreter.cpp:
(WebCore::ContentExtensions::DFABytecodeInterpreter::interpret):
Range checks in the interpreter.

* contentextensions/NFA.cpp:
(WebCore::ContentExtensions::NFA::setFinal):
This assertion does not make sense with the current codebase. Actions are "compressed",
it is possible to have two patterns with the same action.

* contentextensions/NFAToDFA.cpp:
(WebCore::ContentExtensions::simplifyTransitions):
A very simple DFA optimization function: it only reduce the strength of ranges.

(WebCore::ContentExtensions::NFAToDFA::convert):

Tools:

* TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
(TestWebKitAPI::TEST_F):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (181662 => 181663)


--- trunk/Source/WebCore/ChangeLog	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/ChangeLog	2015-03-17 20:47:42 UTC (rev 181663)
@@ -1,3 +1,48 @@
+2015-03-17  Benjamin Poulain  <[email protected]>
+
+        Compile character ranges targeting the same state as range check in the bytecode
+        https://bugs.webkit.org/show_bug.cgi?id=142759
+
+        Reviewed by Alex Christensen.
+
+        Previously, character ranges would be compiled as many individual character checks.
+        For example, a transition on "[a-z]" would do 26 character checks + jump, which leads
+        to enormous matchines.
+
+        With this patch, we find the ranges at lowering time and generate a single instruction
+        for them: "CheckValueRange". This helps making the machine denser when the input
+        use character sets.
+
+        The second part of this patch goes further in the case where the transitions out of
+        a state cover the entire alphabet. In that case, we create a fallback transition
+        on the fly and remove all the ranges made useless.
+        That case is common when ranges are used with inverse character set (e.g. [^a]+a).
+
+        * contentextensions/DFABytecode.h:
+        (WebCore::ContentExtensions::instructionSizeWithArguments):
+        * contentextensions/DFABytecodeCompiler.cpp:
+        (WebCore::ContentExtensions::DFABytecodeCompiler::emitCheckValueRange):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::compileNode):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::compileNodeTransitions):
+        (WebCore::ContentExtensions::DFABytecodeCompiler::compileCheckForRange):
+        * contentextensions/DFABytecodeCompiler.h:
+        Extend the compiler to detect ranges and lower them as CheckValueRange.
+
+        * contentextensions/DFABytecodeInterpreter.cpp:
+        (WebCore::ContentExtensions::DFABytecodeInterpreter::interpret):
+        Range checks in the interpreter.
+
+        * contentextensions/NFA.cpp:
+        (WebCore::ContentExtensions::NFA::setFinal):
+        This assertion does not make sense with the current codebase. Actions are "compressed",
+        it is possible to have two patterns with the same action.
+
+        * contentextensions/NFAToDFA.cpp:
+        (WebCore::ContentExtensions::simplifyTransitions):
+        A very simple DFA optimization function: it only reduce the strength of ranges.
+
+        (WebCore::ContentExtensions::NFAToDFA::convert):
+
 2015-03-17  Jer Noble  <[email protected]>
 
         REGRESSION (r181423): Crash @ generatedcontent.org at com.apple.WebCore: WebCore::MediaPlayer::maximumDurationToCacheMediaTime const + 4

Modified: trunk/Source/WebCore/contentextensions/DFABytecode.h (181662 => 181663)


--- trunk/Source/WebCore/contentextensions/DFABytecode.h	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/contentextensions/DFABytecode.h	2015-03-17 20:47:42 UTC (rev 181663)
@@ -41,6 +41,12 @@
     // The index to jump to if the values are equal (4 bytes).
     CheckValue,
 
+    // Jump to an offset if the input value is within a certain range.
+    // The lower value (1 byte).
+    // The higher value (1 byte).
+    // The index to jump to if the value is in the range (4 bytes).
+    CheckValueRange,
+
     // AppendAction has one argument:
     // The action to append (4 bytes).
     AppendAction,
@@ -63,6 +69,8 @@
     switch (instruction) {
     case DFABytecodeInstruction::CheckValue:
         return sizeof(DFABytecodeInstruction) + sizeof(uint8_t) + sizeof(unsigned);
+    case DFABytecodeInstruction::CheckValueRange:
+        return sizeof(DFABytecodeInstruction) + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(unsigned);
     case DFABytecodeInstruction::AppendAction:
         return sizeof(DFABytecodeInstruction) + sizeof(unsigned);
     case DFABytecodeInstruction::TestFlagsAndAppendAction:

Modified: trunk/Source/WebCore/contentextensions/DFABytecodeCompiler.cpp (181662 => 181663)


--- trunk/Source/WebCore/contentextensions/DFABytecodeCompiler.cpp	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/contentextensions/DFABytecodeCompiler.cpp	2015-03-17 20:47:42 UTC (rev 181663)
@@ -30,6 +30,7 @@
 
 #include "ContentExtensionRule.h"
 #include "DFA.h"
+#include "DFANode.h"
 
 namespace WebCore {
     
@@ -76,6 +77,18 @@
     append<unsigned>(m_bytecode, 0); // This value will be set when linking.
 }
 
+void DFABytecodeCompiler::emitCheckValueRange(uint8_t lowValue, uint8_t highValue, unsigned destinationNodeIndex)
+{
+    ASSERT_WITH_MESSAGE(lowValue != highValue, "A single value check should be emitted for single values.");
+    ASSERT_WITH_MESSAGE(lowValue < highValue, "The instruction semantic impose lowValue is smaller than highValue.");
+
+    append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::CheckValueRange);
+    append<uint8_t>(m_bytecode, lowValue);
+    append<uint8_t>(m_bytecode, highValue);
+    m_linkRecords.append(std::make_pair(m_bytecode.size(), destinationNodeIndex));
+    append<unsigned>(m_bytecode, 0);
+}
+
 void DFABytecodeCompiler::emitTerminate()
 {
     append<DFABytecodeInstruction>(m_bytecode, DFABytecodeInstruction::Terminate);
@@ -95,16 +108,63 @@
         else
             emitAppendAction(static_cast<unsigned>(action));
     }
-    
-    for (const auto& transition : node.transitions)
-        emitCheckValue(transition.key, transition.value);
-    
+    compileNodeTransitions(node);
+}
+
+void DFABytecodeCompiler::compileNodeTransitions(const DFANode& node)
+{
+    bool hasRangeMin = false;
+    uint16_t rangeMin;
+    unsigned rangeDestination = 0;
+
+    for (unsigned char i = 0; i < 128; ++i) {
+        auto transitionIterator = node.transitions.find(i);
+        if (transitionIterator == node.transitions.end()) {
+            if (hasRangeMin) {
+                ASSERT_WITH_MESSAGE(!(node.hasFallbackTransition && node.fallbackTransition == rangeDestination), "Individual transitions to the fallback transitions should have been eliminated by the optimizer.");
+
+                unsigned char lastHighValue = i - 1;
+                compileCheckForRange(rangeMin, lastHighValue, rangeDestination);
+                hasRangeMin = false;
+            }
+            continue;
+        }
+
+        if (!hasRangeMin) {
+            hasRangeMin = true;
+            rangeMin = transitionIterator->key;
+            rangeDestination = transitionIterator->value;
+        } else {
+            if (transitionIterator->value == rangeDestination)
+                continue;
+
+            unsigned char lastHighValue = i - 1;
+            compileCheckForRange(rangeMin, lastHighValue, rangeDestination);
+            rangeMin = i;
+            rangeDestination = transitionIterator->value;
+        }
+    }
+    if (hasRangeMin)
+        compileCheckForRange(rangeMin, 127, rangeDestination);
+
     if (node.hasFallbackTransition)
         emitJump(node.fallbackTransition);
     else
         emitTerminate();
 }
-    
+
+void DFABytecodeCompiler::compileCheckForRange(uint16_t lowValue, uint16_t highValue, unsigned destinationNodeIndex)
+{
+    ASSERT_WITH_MESSAGE(lowValue < 128, "The DFA engine only supports the ASCII alphabet.");
+    ASSERT_WITH_MESSAGE(highValue < 128, "The DFA engine only supports the ASCII alphabet.");
+    ASSERT(lowValue <= highValue);
+
+    if (lowValue == highValue)
+        emitCheckValue(lowValue, destinationNodeIndex);
+    else
+        emitCheckValueRange(lowValue, highValue, destinationNodeIndex);
+}
+
 void DFABytecodeCompiler::compile()
 {
     ASSERT(!m_bytecode.size());

Modified: trunk/Source/WebCore/contentextensions/DFABytecodeCompiler.h (181662 => 181663)


--- trunk/Source/WebCore/contentextensions/DFABytecodeCompiler.h	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/contentextensions/DFABytecodeCompiler.h	2015-03-17 20:47:42 UTC (rev 181663)
@@ -29,7 +29,6 @@
 #if ENABLE(CONTENT_EXTENSIONS)
 
 #include "DFABytecode.h"
-#include "DFANode.h"
 #include <wtf/Vector.h>
 
 namespace WebCore {
@@ -37,6 +36,7 @@
 namespace ContentExtensions {
 
 class DFA;
+class DFANode;
 
 class DFABytecodeCompiler {
 public:
@@ -50,11 +50,14 @@
 
 private:
     void compileNode(unsigned);
+    void compileNodeTransitions(const DFANode&);
+    void compileCheckForRange(uint16_t lowValue, uint16_t highValue, unsigned destinationNodeIndex);
 
     void emitAppendAction(unsigned);
     void emitTestFlagsAndAppendAction(uint16_t flags, unsigned);
     void emitJump(unsigned destinationNodeIndex);
     void emitCheckValue(uint8_t value, unsigned destinationNodeIndex);
+    void emitCheckValueRange(uint8_t lowValue, uint8_t highValue, unsigned destinationNodeIndex);
     void emitTerminate();
 
     Vector<DFABytecode>& m_bytecode;

Modified: trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp (181662 => 181663)


--- trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/contentextensions/DFABytecodeInterpreter.cpp	2015-03-17 20:47:42 UTC (rev 181663)
@@ -73,6 +73,22 @@
                 programCounter += instructionSizeWithArguments(DFABytecodeInstruction::CheckValue);
             break;
 
+        case DFABytecodeInstruction::CheckValueRange: {
+            if (urlIndexIsAfterEndOfString)
+                return actions;
+
+            char character = url[urlIndex];
+            if (character >= getBits<uint8_t>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode))
+                && character <= getBits<uint8_t>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode) + sizeof(uint8_t))) {
+                programCounter = getBits<unsigned>(m_bytecode, m_bytecodeLength, programCounter + sizeof(DFABytecode) + sizeof(uint8_t) + sizeof(uint8_t));
+                if (!character)
+                    urlIndexIsAfterEndOfString = true;
+                urlIndex++; // This represents an edge in the DFA.
+            } else
+                programCounter += instructionSizeWithArguments(DFABytecodeInstruction::CheckValueRange);
+            break;
+        }
+
         case DFABytecodeInstruction::Jump:
             if (!url[urlIndex] || urlIndexIsAfterEndOfString)
                 return actions;

Modified: trunk/Source/WebCore/contentextensions/NFA.cpp (181662 => 181663)


--- trunk/Source/WebCore/contentextensions/NFA.cpp	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/contentextensions/NFA.cpp	2015-03-17 20:47:42 UTC (rev 181663)
@@ -83,8 +83,8 @@
 
 void NFA::setFinal(unsigned node, uint64_t ruleId)
 {
-    ASSERT(!m_nodes[node].finalRuleIds.contains(ruleId));
-    m_nodes[node].finalRuleIds.append(ruleId);
+    if (!m_nodes[node].finalRuleIds.contains(ruleId))
+        m_nodes[node].finalRuleIds.append(ruleId);
 }
 
 unsigned NFA::graphSize() const

Modified: trunk/Source/WebCore/contentextensions/NFAToDFA.cpp (181662 => 181663)


--- trunk/Source/WebCore/contentextensions/NFAToDFA.cpp	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Source/WebCore/contentextensions/NFAToDFA.cpp	2015-03-17 20:47:42 UTC (rev 181663)
@@ -338,6 +338,49 @@
     return uniqueNodeIdAddResult.iterator->impl()->m_dfaNodeId;
 }
 
+static void simplifyTransitions(Vector<DFANode>& dfaGraph)
+{
+    for (DFANode& dfaNode : dfaGraph) {
+        if (!dfaNode.hasFallbackTransition
+            && ((dfaNode.transitions.size() == 126 && !dfaNode.transitions.contains(0))
+                || (dfaNode.transitions.size() == 127 && dfaNode.transitions.contains(0)))) {
+            unsigned bestTarget = std::numeric_limits<unsigned>::max();
+            unsigned bestTargetScore = 0;
+            HashMap<unsigned, unsigned, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> targetHistogram;
+            for (const auto transition : dfaNode.transitions) {
+                if (!transition.key)
+                    continue;
+
+                unsigned transitionTarget = transition.value;
+                auto addResult = targetHistogram.add(transitionTarget, 1);
+                if (!addResult.isNewEntry)
+                    addResult.iterator->value++;
+
+                if (addResult.iterator->value > bestTargetScore) {
+                    bestTargetScore = addResult.iterator->value;
+                    bestTarget = transitionTarget;
+                }
+            }
+            ASSERT_WITH_MESSAGE(bestTargetScore, "There should be at least one valid target since having transitions is a precondition to enter this path.");
+
+            dfaNode.hasFallbackTransition = true;
+            dfaNode.fallbackTransition = bestTarget;
+        }
+
+        if (dfaNode.hasFallbackTransition) {
+            Vector<uint16_t, 128> keys;
+            DFANodeTransitions& transitions = dfaNode.transitions;
+            copyKeysToVector(transitions, keys);
+
+            for (uint16_t key : keys) {
+                auto transitionIterator = transitions.find(key);
+                if (transitionIterator->value == dfaNode.fallbackTransition)
+                    transitions.remove(transitionIterator);
+            }
+        }
+    }
+}
+
 DFA NFAToDFA::convert(NFA& nfa)
 {
     Vector<NFANode>& nfaGraph = nfa.m_nodes;
@@ -387,6 +430,7 @@
         }
     } while (!unprocessedNodes.isEmpty());
 
+    simplifyTransitions(dfaGraph);
     return DFA(WTF::move(dfaGraph), 0);
 }
 

Modified: trunk/Tools/ChangeLog (181662 => 181663)


--- trunk/Tools/ChangeLog	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Tools/ChangeLog	2015-03-17 20:47:42 UTC (rev 181663)
@@ -1,3 +1,13 @@
+2015-03-17  Benjamin Poulain  <[email protected]>
+
+        Compile character ranges targeting the same state as range check in the bytecode
+        https://bugs.webkit.org/show_bug.cgi?id=142759
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
+        (TestWebKitAPI::TEST_F):
+
 2015-03-17  Youenn Fablet  <[email protected]>
 
         W3C test parser and converter should use test importer host

Modified: trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp (181662 => 181663)


--- trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp	2015-03-17 20:28:33 UTC (rev 181662)
+++ trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp	2015-03-17 20:47:42 UTC (rev 181663)
@@ -122,6 +122,56 @@
     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { ContentExtensions::ActionType::BlockLoad });
 }
 
+TEST_F(ContentExtensionTest, RangeBasic)
+{
+    const char* rangeBasicFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*w[0-9]c\", \"url-filter-is-case-sensitive\":true}},{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"url-filter\":\".*[A-H][a-z]cko\", \"url-filter-is-case-sensitive\":true}}]";
+    auto extensionData = ContentExtensions::compileRuleList(rangeBasicFilter);
+    auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
+
+    ContentExtensions::ContentExtensionsBackend backend;
+    backend.addContentExtension("PatternNestedGroupsFilter", extension);
+
+    testRequest(backend, mainDocumentRequest("http://w3c.org"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("w2c://whatwg.org/"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/w0c"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/wac"), { });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/wAc"), { });
+
+    // Note: URL parsing and canonicalization lowercase the scheme and hostname.
+    testRequest(backend, mainDocumentRequest("Aacko://webkit.org"), { });
+    testRequest(backend, mainDocumentRequest("aacko://webkit.org"), { });
+    testRequest(backend, mainDocumentRequest("http://gCcko.org/"), { });
+    testRequest(backend, mainDocumentRequest("http://gccko.org/"), { });
+
+    testRequest(backend, mainDocumentRequest("http://webkit.org/Gecko"), { ContentExtensions::ActionType::BlockCookies });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/gecko"), { });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/GEcko"), { });
+}
+
+TEST_F(ContentExtensionTest, RangeExclusionGeneratingUniversalTransition)
+{
+    // Transition of the type ([^X]X) effictively transition on every input.
+    const char* rangeExclusionGeneratingUniversalTransitionFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*[^a]+afoobar\"}}]";
+    auto extensionData = ContentExtensions::compileRuleList(rangeExclusionGeneratingUniversalTransitionFilter);
+    auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
+
+    ContentExtensions::ContentExtensionsBackend backend;
+    backend.addContentExtension("PatternNestedGroupsFilter", extension);
+
+    testRequest(backend, mainDocumentRequest("http://w3c.org"), { });
+
+    testRequest(backend, mainDocumentRequest("http://w3c.org/foobafoobar"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://w3c.org/foobarfoobar"), { });
+    testRequest(backend, mainDocumentRequest("http://w3c.org/FOOBAFOOBAR"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://w3c.org/FOOBARFOOBAR"), { });
+
+    // The character before the "a" prefix cannot be another "a".
+    testRequest(backend, mainDocumentRequest("http://w3c.org/aafoobar"), { });
+    testRequest(backend, mainDocumentRequest("http://w3c.org/Aafoobar"), { });
+    testRequest(backend, mainDocumentRequest("http://w3c.org/aAfoobar"), { });
+    testRequest(backend, mainDocumentRequest("http://w3c.org/AAfoobar"), { });
+}
+
 const char* patternsStartingWithGroupFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"(http://whatwg\\\\.org/)?webkit\134\134.org\"}}]";
 
 TEST_F(ContentExtensionTest, PatternStartingWithGroup)
@@ -219,6 +269,26 @@
     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarfoo"), { });
     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarf"), { });
 }
+
+TEST_F(ContentExtensionTest, EndOfLineAssertionWithInvertedCharacterSet)
+{
+    const char* endOfLineAssertionWithInvertedCharacterSetFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*[^y]$\"}}]";
+    auto extensionData = ContentExtensions::compileRuleList(endOfLineAssertionWithInvertedCharacterSetFilter);
+    auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
+
+    ContentExtensions::ContentExtensionsBackend backend;
+    backend.addContentExtension("EndOfLineAssertion", extension);
+
+    testRequest(backend, mainDocumentRequest("http://webkit.org/"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/a"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/foobar"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/Ya"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/yFoobar"), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/y"), { });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/Y"), { });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/foobary"), { });
+    testRequest(backend, mainDocumentRequest("http://webkit.org/foobarY"), { });
+}
     
 const char* loadTypeFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*webkit.org\",\"load-type\":[\"third-party\"]}},"
     "{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*whatwg.org\",\"load-type\":[\"first-party\"]}},"
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to