<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[186079] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/186079">186079</a></dd>
<dt>Author</dt> <dd>benjamin@webkit.org</dd>
<dt>Date</dt> <dd>2015-06-29 12:40:12 -0700 (Mon, 29 Jun 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Make the NFA transitions range-based
https://bugs.webkit.org/show_bug.cgi?id=146338

Reviewed by Alex Christensen.

Source/WebCore:

Change the NFA to use range based transition for any kind of transition.
The fallback transition is also absorbed as ranges.

Previously, the NFA would only have single transitions and a fallback
transition for all cases not covered by single transitions.

The problem with that design was that character ranges (e.g. [a-z]) were
extended as individual transitions. Something like [^a] would cover
most of the alphabet with transitions.
When converting the NFA to DFA, the time is proportional to the number of states
multiplied by the number of transitions. With many individual transitions,
the run time was an order of magnitude higher than what we want.

This patch changes the NFA to only handle ranges of characters. A single transition
becomes a range with the character as first and last character in the range
('a' becomes 'a' to 'a').
Ranges of characters are handled direclty (e.g. [a-z] becomes a single 'a' to 'z' transition).

In the context of the state machines, ranges have identifies (the target of the transitions).
When two ranges collide, they have to be split such that each part retain its target
except the intersection that gets the union of the targets.

Handling the union of ranges efficiently is critical because we have to do
it for every NFA node in any subset when building the DFA. The helper
class that does that is MutableRange.

The union of ranges is done efficiently because of preconditions on our list of ranges:
-The ranges must be sorted.
-No range in a list can intersect any other range in the same list.

To merge two ranges, we can go over them in order and split them part by part.
It is easy to find what goes where because they are both sorted and we can
compare the characters of each to know how to move forward.
The time to merge 2 range list is proportional to O(n+m) where 'n' and 'm' are
the number of ranges in each list.

Since taking the union of two lists usually create new ranges, we have to allocate
those somewhere efficiently. To do that, MutableRange support an inline capacity,
which is used for the NFAToDFA Convertion.

---

With ranges, the NFA-to-DFA conversion changes very little:
-Each NFA nodes contains a list of ranges and each range has a list of targets.
-The subset construction select any number of NFA nodes corresponding to
 a single deterministic state.
-For the subset, we can use MutableRange to merge the ranges of every
 NFA node in the set. The resulting list has rangeis with targets corresponding
 to the union of all the transitions.
-We go over all the ranges the same way we used to go over the transitions.
 Since the DFA does not support ranges, the ranges are spread as individual
 transitions + fallback transition.

---

With the efficient merging solved, we still need the actual NFA to use ranges
instead of individual transitions.

I could have used MutableRange for that but it is not the most compact
way to represent ranges that do not need merging.

Instead, the NFA uses a custom structure: ImmutableNFA. It is basically
the same thing, but in that one you cannot change a list of range
after creating it.

Consequently, the sorted ranges in ImmutableNFA are also subsequent
in memory, which is really nice to go over them efficiently
when merging ranges in the NFA-to-DFA conversion. :)

When building the NFA, we don't know all the transitions when creating
each node, BUT we know that we have very few node &quot;unfinished&quot; at any
time. Since we build by going depth-first in the prefix-tree, we only
have the max-depth of live nodes in the worst case.

To help building the NFA out of the prefix tree, we have
ImmutableNFANodeBuilder. It keeps all the informations about a NFA node,
but in a non-compact, mutable form. When a ImmutableNFANodeBuilder
is destroyed, it serialize the information into the immutable NFA.

* WebCore.xcodeproj/project.pbxproj:
* contentextensions/CombinedURLFilters.cpp:
(WebCore::ContentExtensions::generateNFAForSubtree):
(WebCore::ContentExtensions::CombinedURLFilters::processNFAs):
* contentextensions/ContentExtensionCompiler.cpp:
(WebCore::ContentExtensions::compileRuleList):
* contentextensions/DFA.cpp:
(WebCore::ContentExtensions::DFA::empty):
* contentextensions/DFA.h:
* contentextensions/ImmutableNFA.h: Added.
(WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator*):
(WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator-&gt;):
(WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator==):
(WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator!=):
(WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator++):
(WebCore::ContentExtensions::ImmutableNFA::IterableConstTargets::begin):
(WebCore::ContentExtensions::ImmutableNFA::IterableConstTargets::end):
(WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator*):
(WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator-&gt;):
(WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator==):
(WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator!=):
(WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator++):
(WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::data):
(WebCore::ContentExtensions::ImmutableNFA::IterableConstRange::begin):
(WebCore::ContentExtensions::ImmutableNFA::IterableConstRange::end):
(WebCore::ContentExtensions::ImmutableNFA::IterableConstRange::debugPrint):
(WebCore::ContentExtensions::ImmutableNFA::transitionsForNode):
(WebCore::ContentExtensions::ImmutableNFA::root):
(WebCore::ContentExtensions::ImmutableNFA::finalize):
(WebCore::ContentExtensions::ImmutableNFA::memoryUsed):
* contentextensions/ImmutableNFANodeBuilder.h: Added.
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::ImmutableNFANodeBuilder):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::~ImmutableNFANodeBuilder):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::addTransition):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::addEpsilonTransition):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::setActions):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::operator=):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::finalize):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::sinkActions):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::sinkTransitions):
(WebCore::ContentExtensions::ImmutableNFANodeBuilder::sinkEpsilonTransitions):
* contentextensions/MutableRange.h: Added.
(WebCore::ContentExtensions::MutableRange::MutableRange):
(WebCore::ContentExtensions::MutableRange::operator=):
(WebCore::ContentExtensions::MutableRange::size):
* contentextensions/MutableRangeList.h: Added.
(WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator*):
(WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator-&gt;):
(WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator==):
(WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator!=):
(WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator++):
(WebCore::ContentExtensions::MutableRangeList::begin):
(WebCore::ContentExtensions::MutableRangeList::end):
(WebCore::ContentExtensions::MutableRangeList::appendRange):
(WebCore::ContentExtensions::MutableRangeList::extend):
(WebCore::ContentExtensions::MutableRangeList::isEmpty):
(WebCore::ContentExtensions::MutableRangeList::clear):
(WebCore::ContentExtensions::MutableRangeList::debugPrint):
(WebCore::ContentExtensions::MutableRangeList::insertBetween):
(WebCore::ContentExtensions::MutableRangeList::initializeFrom):
* contentextensions/NFA.cpp:
(WebCore::ContentExtensions::NFA::debugPrintDot):
(WebCore::ContentExtensions::NFA::NFA): Deleted.
(WebCore::ContentExtensions::NFA::createNode): Deleted.
(WebCore::ContentExtensions::NFA::memoryUsed): Deleted.
(WebCore::ContentExtensions::NFA::addTransition): Deleted.
(WebCore::ContentExtensions::NFA::addEpsilonTransition): Deleted.
(WebCore::ContentExtensions::NFA::addTransitionsOnAnyCharacter): Deleted.
(WebCore::ContentExtensions::NFA::setActions): Deleted.
(WebCore::ContentExtensions::NFA::graphSize): Deleted.
(WebCore::ContentExtensions::NFA::restoreToGraphSize): Deleted.
(WebCore::ContentExtensions::printRange): Deleted.
(WebCore::ContentExtensions::printTransitions): Deleted.
* contentextensions/NFA.h:
(WebCore::ContentExtensions::NFA::root): Deleted.
(WebCore::ContentExtensions::NFA::addRuleId): Deleted.
* contentextensions/NFANode.h:
* contentextensions/NFAToDFA.cpp:
(WebCore::ContentExtensions::epsilonClosureExcludingSelf):
(WebCore::ContentExtensions::resolveEpsilonClosures):
(WebCore::ContentExtensions::NodeIdSetToUniqueNodeIdSetSource::NodeIdSetToUniqueNodeIdSetSource):
(WebCore::ContentExtensions::NodeIdSetToUniqueNodeIdSetTranslator::translate):
(WebCore::ContentExtensions::DataConverterWithEpsilonClosure::convert):
(WebCore::ContentExtensions::DataConverterWithEpsilonClosure::extend):
(WebCore::ContentExtensions::createCombinedTransition):
(WebCore::ContentExtensions::canUseFallbackTransition):
(WebCore::ContentExtensions::findBestFallbackTarget):
(WebCore::ContentExtensions::getOrCreateDFANode):
(WebCore::ContentExtensions::NFAToDFA::convert):
(WebCore::ContentExtensions::populateTransitions): Deleted.
* contentextensions/NFAToDFA.h:
* contentextensions/Term.h:
(WebCore::ContentExtensions::Term::Term):
(WebCore::ContentExtensions::Term::generateGraph):
(WebCore::ContentExtensions::Term::generateSubgraphForAtom):
(WebCore::ContentExtensions::Term::Term::generateGraph): Deleted.

Tools:

* TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
Test some new interesting cases.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreWebCorexcodeprojprojectpbxproj">trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsCombinedURLFilterscpp">trunk/Source/WebCore/contentextensions/CombinedURLFilters.cpp</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsContentExtensionCompilercpp">trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsDFAcpp">trunk/Source/WebCore/contentextensions/DFA.cpp</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsDFAh">trunk/Source/WebCore/contentextensions/DFA.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsNFAcpp">trunk/Source/WebCore/contentextensions/NFA.cpp</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsNFAh">trunk/Source/WebCore/contentextensions/NFA.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsNFANodeh">trunk/Source/WebCore/contentextensions/NFANode.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsNFAToDFAcpp">trunk/Source/WebCore/contentextensions/NFAToDFA.cpp</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsNFAToDFAh">trunk/Source/WebCore/contentextensions/NFAToDFA.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsTermh">trunk/Source/WebCore/contentextensions/Term.h</a></li>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebCoreContentExtensionscpp">trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkSourceWebCorecontentextensionsImmutableNFAh">trunk/Source/WebCore/contentextensions/ImmutableNFA.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsImmutableNFANodeBuilderh">trunk/Source/WebCore/contentextensions/ImmutableNFANodeBuilder.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsMutableRangeh">trunk/Source/WebCore/contentextensions/MutableRange.h</a></li>
<li><a href="#trunkSourceWebCorecontentextensionsMutableRangeListh">trunk/Source/WebCore/contentextensions/MutableRangeList.h</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/ChangeLog        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -1,3 +1,186 @@
</span><ins>+2015-06-29  Benjamin Poulain  &lt;benjamin@webkit.org&gt;
+
+        Make the NFA transitions range-based
+        https://bugs.webkit.org/show_bug.cgi?id=146338
+
+        Reviewed by Alex Christensen.
+
+        Change the NFA to use range based transition for any kind of transition.
+        The fallback transition is also absorbed as ranges.
+
+        Previously, the NFA would only have single transitions and a fallback
+        transition for all cases not covered by single transitions.
+
+        The problem with that design was that character ranges (e.g. [a-z]) were
+        extended as individual transitions. Something like [^a] would cover
+        most of the alphabet with transitions.
+        When converting the NFA to DFA, the time is proportional to the number of states
+        multiplied by the number of transitions. With many individual transitions,
+        the run time was an order of magnitude higher than what we want.
+
+        This patch changes the NFA to only handle ranges of characters. A single transition
+        becomes a range with the character as first and last character in the range
+        ('a' becomes 'a' to 'a').
+        Ranges of characters are handled direclty (e.g. [a-z] becomes a single 'a' to 'z' transition).
+
+        In the context of the state machines, ranges have identifies (the target of the transitions).
+        When two ranges collide, they have to be split such that each part retain its target
+        except the intersection that gets the union of the targets.
+
+        Handling the union of ranges efficiently is critical because we have to do
+        it for every NFA node in any subset when building the DFA. The helper
+        class that does that is MutableRange.
+
+        The union of ranges is done efficiently because of preconditions on our list of ranges:
+        -The ranges must be sorted.
+        -No range in a list can intersect any other range in the same list.
+
+        To merge two ranges, we can go over them in order and split them part by part.
+        It is easy to find what goes where because they are both sorted and we can
+        compare the characters of each to know how to move forward.
+        The time to merge 2 range list is proportional to O(n+m) where 'n' and 'm' are
+        the number of ranges in each list.
+
+        Since taking the union of two lists usually create new ranges, we have to allocate
+        those somewhere efficiently. To do that, MutableRange support an inline capacity,
+        which is used for the NFAToDFA Convertion.
+
+        ---
+
+        With ranges, the NFA-to-DFA conversion changes very little:
+        -Each NFA nodes contains a list of ranges and each range has a list of targets.
+        -The subset construction select any number of NFA nodes corresponding to
+         a single deterministic state.
+        -For the subset, we can use MutableRange to merge the ranges of every
+         NFA node in the set. The resulting list has rangeis with targets corresponding
+         to the union of all the transitions.
+        -We go over all the ranges the same way we used to go over the transitions.
+         Since the DFA does not support ranges, the ranges are spread as individual
+         transitions + fallback transition.
+
+        ---
+
+        With the efficient merging solved, we still need the actual NFA to use ranges
+        instead of individual transitions.
+
+        I could have used MutableRange for that but it is not the most compact
+        way to represent ranges that do not need merging.
+
+        Instead, the NFA uses a custom structure: ImmutableNFA. It is basically
+        the same thing, but in that one you cannot change a list of range
+        after creating it.
+
+        Consequently, the sorted ranges in ImmutableNFA are also subsequent
+        in memory, which is really nice to go over them efficiently
+        when merging ranges in the NFA-to-DFA conversion. :)
+
+        When building the NFA, we don't know all the transitions when creating
+        each node, BUT we know that we have very few node &quot;unfinished&quot; at any
+        time. Since we build by going depth-first in the prefix-tree, we only
+        have the max-depth of live nodes in the worst case.
+
+        To help building the NFA out of the prefix tree, we have
+        ImmutableNFANodeBuilder. It keeps all the informations about a NFA node,
+        but in a non-compact, mutable form. When a ImmutableNFANodeBuilder
+        is destroyed, it serialize the information into the immutable NFA.
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * contentextensions/CombinedURLFilters.cpp:
+        (WebCore::ContentExtensions::generateNFAForSubtree):
+        (WebCore::ContentExtensions::CombinedURLFilters::processNFAs):
+        * contentextensions/ContentExtensionCompiler.cpp:
+        (WebCore::ContentExtensions::compileRuleList):
+        * contentextensions/DFA.cpp:
+        (WebCore::ContentExtensions::DFA::empty):
+        * contentextensions/DFA.h:
+        * contentextensions/ImmutableNFA.h: Added.
+        (WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator*):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator-&gt;):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator==):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator!=):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstTargetIterator::operator++):
+        (WebCore::ContentExtensions::ImmutableNFA::IterableConstTargets::begin):
+        (WebCore::ContentExtensions::ImmutableNFA::IterableConstTargets::end):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator*):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator-&gt;):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator==):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator!=):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::operator++):
+        (WebCore::ContentExtensions::ImmutableNFA::ConstRangeIterator::data):
+        (WebCore::ContentExtensions::ImmutableNFA::IterableConstRange::begin):
+        (WebCore::ContentExtensions::ImmutableNFA::IterableConstRange::end):
+        (WebCore::ContentExtensions::ImmutableNFA::IterableConstRange::debugPrint):
+        (WebCore::ContentExtensions::ImmutableNFA::transitionsForNode):
+        (WebCore::ContentExtensions::ImmutableNFA::root):
+        (WebCore::ContentExtensions::ImmutableNFA::finalize):
+        (WebCore::ContentExtensions::ImmutableNFA::memoryUsed):
+        * contentextensions/ImmutableNFANodeBuilder.h: Added.
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::ImmutableNFANodeBuilder):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::~ImmutableNFANodeBuilder):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::addTransition):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::addEpsilonTransition):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::setActions):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::operator=):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::finalize):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::sinkActions):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::sinkTransitions):
+        (WebCore::ContentExtensions::ImmutableNFANodeBuilder::sinkEpsilonTransitions):
+        * contentextensions/MutableRange.h: Added.
+        (WebCore::ContentExtensions::MutableRange::MutableRange):
+        (WebCore::ContentExtensions::MutableRange::operator=):
+        (WebCore::ContentExtensions::MutableRange::size):
+        * contentextensions/MutableRangeList.h: Added.
+        (WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator*):
+        (WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator-&gt;):
+        (WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator==):
+        (WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator!=):
+        (WebCore::ContentExtensions::MutableRangeList::ConstIterator::operator++):
+        (WebCore::ContentExtensions::MutableRangeList::begin):
+        (WebCore::ContentExtensions::MutableRangeList::end):
+        (WebCore::ContentExtensions::MutableRangeList::appendRange):
+        (WebCore::ContentExtensions::MutableRangeList::extend):
+        (WebCore::ContentExtensions::MutableRangeList::isEmpty):
+        (WebCore::ContentExtensions::MutableRangeList::clear):
+        (WebCore::ContentExtensions::MutableRangeList::debugPrint):
+        (WebCore::ContentExtensions::MutableRangeList::insertBetween):
+        (WebCore::ContentExtensions::MutableRangeList::initializeFrom):
+        * contentextensions/NFA.cpp:
+        (WebCore::ContentExtensions::NFA::debugPrintDot):
+        (WebCore::ContentExtensions::NFA::NFA): Deleted.
+        (WebCore::ContentExtensions::NFA::createNode): Deleted.
+        (WebCore::ContentExtensions::NFA::memoryUsed): Deleted.
+        (WebCore::ContentExtensions::NFA::addTransition): Deleted.
+        (WebCore::ContentExtensions::NFA::addEpsilonTransition): Deleted.
+        (WebCore::ContentExtensions::NFA::addTransitionsOnAnyCharacter): Deleted.
+        (WebCore::ContentExtensions::NFA::setActions): Deleted.
+        (WebCore::ContentExtensions::NFA::graphSize): Deleted.
+        (WebCore::ContentExtensions::NFA::restoreToGraphSize): Deleted.
+        (WebCore::ContentExtensions::printRange): Deleted.
+        (WebCore::ContentExtensions::printTransitions): Deleted.
+        * contentextensions/NFA.h:
+        (WebCore::ContentExtensions::NFA::root): Deleted.
+        (WebCore::ContentExtensions::NFA::addRuleId): Deleted.
+        * contentextensions/NFANode.h:
+        * contentextensions/NFAToDFA.cpp:
+        (WebCore::ContentExtensions::epsilonClosureExcludingSelf):
+        (WebCore::ContentExtensions::resolveEpsilonClosures):
+        (WebCore::ContentExtensions::NodeIdSetToUniqueNodeIdSetSource::NodeIdSetToUniqueNodeIdSetSource):
+        (WebCore::ContentExtensions::NodeIdSetToUniqueNodeIdSetTranslator::translate):
+        (WebCore::ContentExtensions::DataConverterWithEpsilonClosure::convert):
+        (WebCore::ContentExtensions::DataConverterWithEpsilonClosure::extend):
+        (WebCore::ContentExtensions::createCombinedTransition):
+        (WebCore::ContentExtensions::canUseFallbackTransition):
+        (WebCore::ContentExtensions::findBestFallbackTarget):
+        (WebCore::ContentExtensions::getOrCreateDFANode):
+        (WebCore::ContentExtensions::NFAToDFA::convert):
+        (WebCore::ContentExtensions::populateTransitions): Deleted.
+        * contentextensions/NFAToDFA.h:
+        * contentextensions/Term.h:
+        (WebCore::ContentExtensions::Term::Term):
+        (WebCore::ContentExtensions::Term::generateGraph):
+        (WebCore::ContentExtensions::Term::generateSubgraphForAtom):
+        (WebCore::ContentExtensions::Term::Term::generateGraph): Deleted.
+
</ins><span class="cx"> 2015-06-29  Youenn Fablet  &lt;youenn.fablet@crf.canon.fr&gt;
</span><span class="cx"> 
</span><span class="cx">         Binding generator should allow using JSC::Value for &quot;any&quot; parameter in lieu of ScriptValue
</span></span></pre></div>
<a id="trunkSourceWebCoreWebCorexcodeprojprojectpbxproj"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -1040,6 +1040,10 @@
</span><span class="cx">                 26F0C89F1A2EC3BE002794F8 /* ContentExtensionsBackend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26F0C89D1A2EC3BE002794F8 /* ContentExtensionsBackend.cpp */; };
</span><span class="cx">                 26F0C8A01A2EC3BE002794F8 /* ContentExtensionsBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F0C89E1A2EC3BE002794F8 /* ContentExtensionsBackend.h */; settings = {ATTRIBUTES = (Private, ); }; };
</span><span class="cx">                 26F40D4A14904A6300CA67C4 /* EventLoopIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = 26F40D4914904A6300CA67C4 /* EventLoopIOS.mm */; };
</span><ins>+                26F756B01B3B65AC0005DD79 /* MutableRange.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F756AE1B3B65AC0005DD79 /* MutableRange.h */; settings = {ATTRIBUTES = (Private, ); }; };
+                26F756B11B3B65AC0005DD79 /* MutableRangeList.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F756AF1B3B65AC0005DD79 /* MutableRangeList.h */; settings = {ATTRIBUTES = (Private, ); }; };
+                26F756B31B3B66F70005DD79 /* ImmutableNFA.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F756B21B3B66F70005DD79 /* ImmutableNFA.h */; settings = {ATTRIBUTES = (Private, ); }; };
+                26F756B51B3B68F20005DD79 /* ImmutableNFANodeBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F756B41B3B68F20005DD79 /* ImmutableNFANodeBuilder.h */; settings = {ATTRIBUTES = (Private, ); }; };
</ins><span class="cx">                 26F9A83818A046AC00AEB88A /* ViewportConfiguration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26F9A83618A046AC00AEB88A /* ViewportConfiguration.cpp */; };
</span><span class="cx">                 26F9A83918A046AC00AEB88A /* ViewportConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 26F9A83718A046AC00AEB88A /* ViewportConfiguration.h */; settings = {ATTRIBUTES = (Private, ); }; };
</span><span class="cx">                 26FAE4CC1852E3A5004C8C46 /* ResourceHandleCFURLConnectionDelegate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26FAE4C81852E3A5004C8C46 /* ResourceHandleCFURLConnectionDelegate.cpp */; };
</span><span class="lines">@@ -8181,6 +8185,10 @@
</span><span class="cx">                 26F0C89D1A2EC3BE002794F8 /* ContentExtensionsBackend.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ContentExtensionsBackend.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 26F0C89E1A2EC3BE002794F8 /* ContentExtensionsBackend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentExtensionsBackend.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 26F40D4914904A6300CA67C4 /* EventLoopIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EventLoopIOS.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><ins>+                26F756AE1B3B65AC0005DD79 /* MutableRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MutableRange.h; sourceTree = &quot;&lt;group&gt;&quot;; };
+                26F756AF1B3B65AC0005DD79 /* MutableRangeList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MutableRangeList.h; sourceTree = &quot;&lt;group&gt;&quot;; };
+                26F756B21B3B66F70005DD79 /* ImmutableNFA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImmutableNFA.h; sourceTree = &quot;&lt;group&gt;&quot;; };
+                26F756B41B3B68F20005DD79 /* ImmutableNFANodeBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImmutableNFANodeBuilder.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</ins><span class="cx">                 26F9A83618A046AC00AEB88A /* ViewportConfiguration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ViewportConfiguration.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 26F9A83718A046AC00AEB88A /* ViewportConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewportConfiguration.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 26FAE4C81852E3A5004C8C46 /* ResourceHandleCFURLConnectionDelegate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ResourceHandleCFURLConnectionDelegate.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="lines">@@ -15688,6 +15696,10 @@
</span><span class="cx">                                 26A517FB1AB92238006335DF /* DFAMinimizer.cpp */,
</span><span class="cx">                                 26A517FC1AB92238006335DF /* DFAMinimizer.h */,
</span><span class="cx">                                 267725F91A5B3AD9003C24DD /* DFANode.h */,
</span><ins>+                                26F756B21B3B66F70005DD79 /* ImmutableNFA.h */,
+                                26F756B41B3B68F20005DD79 /* ImmutableNFANodeBuilder.h */,
+                                26F756AE1B3B65AC0005DD79 /* MutableRange.h */,
+                                26F756AF1B3B65AC0005DD79 /* MutableRangeList.h */,
</ins><span class="cx">                                 269397251A4A5FBD00E8349D /* NFA.cpp */,
</span><span class="cx">                                 269397231A4A5B6400E8349D /* NFA.h */,
</span><span class="cx">                                 269397201A4A412F00E8349D /* NFANode.h */,
</span><span class="lines">@@ -24240,6 +24252,7 @@
</span><span class="cx">                                 62CD325A1157E57C0063B0A7 /* CustomEvent.h in Headers */,
</span><span class="cx">                                 A8CB413E0E8633FD0032C4F0 /* DashArray.h in Headers */,
</span><span class="cx">                                 A80E6D0B0A1989CA007FB8C5 /* DashboardRegion.h in Headers */,
</span><ins>+                                26F756B01B3B65AC0005DD79 /* MutableRange.h in Headers */,
</ins><span class="cx">                                 97BC6A211505F081001B74AC /* Database.h in Headers */,
</span><span class="cx">                                 97BC6A241505F081001B74AC /* DatabaseAuthorizer.h in Headers */,
</span><span class="cx">                                 FE16CFD4169D1DED00D3A0C7 /* DatabaseBackend.h in Headers */,
</span><span class="lines">@@ -24400,6 +24413,7 @@
</span><span class="cx">                                 85909CE40ACC7A7E00DF01F1 /* DOMCSSUnknownRuleInternal.h in Headers */,
</span><span class="cx">                                 858C381C0AA8E29600B187A4 /* DOMCSSValue.h in Headers */,
</span><span class="cx">                                 85B498F30ADB336A00925CBB /* DOMCSSValueInternal.h in Headers */,
</span><ins>+                                26F756B31B3B66F70005DD79 /* ImmutableNFA.h in Headers */,
</ins><span class="cx">                                 858C383C0AA8ED8200B187A4 /* DOMCSSValueList.h in Headers */,
</span><span class="cx">                                 85909D2B0ACC7D5500DF01F1 /* DOMCSSValueListInternal.h in Headers */,
</span><span class="cx">                                 E10B9CCC0B747A44003ED890 /* DOMCustomXPathNSResolver.h in Headers */,
</span><span class="lines">@@ -26234,6 +26248,7 @@
</span><span class="cx">                                 BCEA486E097D93020094C9E4 /* RenderDeprecatedFlexibleBox.h in Headers */,
</span><span class="cx">                                 D302754A12A5FE84004BD828 /* RenderDetailsMarker.h in Headers */,
</span><span class="cx">                                 A76E5F7F135E0DCF00A69837 /* RenderedDocumentMarker.h in Headers */,
</span><ins>+                                26F756B51B3B68F20005DD79 /* ImmutableNFANodeBuilder.h in Headers */,
</ins><span class="cx">                                 9B32CDA913DF7FA900F34D13 /* RenderedPosition.h in Headers */,
</span><span class="cx">                                 E43A023B17EB370A004CDD25 /* RenderElement.h in Headers */,
</span><span class="cx">                                 0F5B7A5510F65D7A00376302 /* RenderEmbeddedObject.h in Headers */,
</span><span class="lines">@@ -26858,6 +26873,7 @@
</span><span class="cx">                                 B2227A810D00BF220071B782 /* SVGPathSegList.h in Headers */,
</span><span class="cx">                                 8476C9E611DF6A0B00555B02 /* SVGPathSegListBuilder.h in Headers */,
</span><span class="cx">                                 084A0829128D7867002DB1F1 /* SVGPathSegListPropertyTearOff.h in Headers */,
</span><ins>+                                26F756B11B3B65AC0005DD79 /* MutableRangeList.h in Headers */,
</ins><span class="cx">                                 84B6B978120F13E500B8EFAF /* SVGPathSegListSource.h in Headers */,
</span><span class="cx">                                 83C1D435178D5AB500141E68 /* SVGPathSegMovetoAbs.h in Headers */,
</span><span class="cx">                                 83C1D436178D5AB500141E68 /* SVGPathSegMovetoRel.h in Headers */,
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsCombinedURLFilterscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/CombinedURLFilters.cpp (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/CombinedURLFilters.cpp        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/CombinedURLFilters.cpp        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -193,25 +193,25 @@
</span><span class="cx">         actions.append(actionId);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-static void generateNFAForSubtree(NFA&amp; nfa, unsigned nfaRootId, PrefixTreeVertex&amp; root, size_t maxNFASize)
</del><ins>+static void generateNFAForSubtree(NFA&amp; nfa, ImmutableCharNFANodeBuilder&amp;&amp; subtreeRoot, PrefixTreeVertex&amp; root, size_t maxNFASize)
</ins><span class="cx"> {
</span><span class="cx">     // This recurses the subtree of the prefix tree.
</span><span class="cx">     // For each edge that has fixed length (no quantifiers like ?, *, or +) it generates the nfa graph,
</span><span class="cx">     // recurses into children, and deletes any processed leaf nodes.
</span><span class="cx">     struct ActiveSubtree {
</span><del>-        ActiveSubtree(PrefixTreeVertex&amp; vertex, unsigned nfaNodeId, unsigned edgeIndex)
</del><ins>+        ActiveSubtree(PrefixTreeVertex&amp; vertex, ImmutableCharNFANodeBuilder&amp;&amp; nfaNode, unsigned edgeIndex)
</ins><span class="cx">             : vertex(vertex)
</span><del>-            , nfaNodeId(nfaNodeId)
</del><ins>+            , nfaNode(WTF::move(nfaNode))
</ins><span class="cx">             , edgeIndex(edgeIndex)
</span><span class="cx">         {
</span><span class="cx">         }
</span><span class="cx">         PrefixTreeVertex&amp; vertex;
</span><del>-        unsigned nfaNodeId;
</del><ins>+        ImmutableCharNFANodeBuilder nfaNode;
</ins><span class="cx">         unsigned edgeIndex;
</span><span class="cx">     };
</span><span class="cx">     Vector&lt;ActiveSubtree&gt; stack;
</span><span class="cx">     if (!root.edges.isEmpty())
</span><del>-        stack.append(ActiveSubtree(root, nfaRootId, 0));
</del><ins>+        stack.append(ActiveSubtree(root, WTF::move(subtreeRoot), 0));
</ins><span class="cx">     bool nfaTooBig = false;
</span><span class="cx">     
</span><span class="cx">     // Generate graphs for each subtree that does not contain any quantifiers.
</span><span class="lines">@@ -220,7 +220,7 @@
</span><span class="cx">         const unsigned edgeIndex = stack.last().edgeIndex;
</span><span class="cx">         
</span><span class="cx">         // Only stop generating an NFA at a leaf to ensure we have a correct NFA. We could go slightly over the maxNFASize.
</span><del>-        if (vertex.edges.isEmpty() &amp;&amp; nfa.graphSize() &gt; maxNFASize)
</del><ins>+        if (vertex.edges.isEmpty() &amp;&amp; nfa.nodes.size() &gt; maxNFASize)
</ins><span class="cx">             nfaTooBig = true;
</span><span class="cx">         
</span><span class="cx">         if (edgeIndex &lt; vertex.edges.size()) {
</span><span class="lines">@@ -237,10 +237,10 @@
</span><span class="cx">                 stack.last().edgeIndex++;
</span><span class="cx">                 continue;
</span><span class="cx">             }
</span><del>-            
-            unsigned subtreeRootId = edge.term.generateGraph(nfa, stack.last().nfaNodeId, edge.child-&gt;finalActions);
</del><ins>+
+            ImmutableCharNFANodeBuilder newNode = edge.term.generateGraph(nfa, stack.last().nfaNode, edge.child-&gt;finalActions);
</ins><span class="cx">             ASSERT(edge.child.get());
</span><del>-            stack.append(ActiveSubtree(*edge.child.get(), subtreeRootId, 0));
</del><ins>+            stack.append(ActiveSubtree(*edge.child.get(), WTF::move(newNode), 0));
</ins><span class="cx">         } else {
</span><span class="cx">             ASSERT(edgeIndex == vertex.edges.size());
</span><span class="cx">             vertex.edges.removeAllMatching([](PrefixTreeEdge&amp; edge)
</span><span class="lines">@@ -286,22 +286,26 @@
</span><span class="cx">             stack.removeLast();
</span><span class="cx">         }
</span><span class="cx">         ASSERT_WITH_MESSAGE(!stack.isEmpty(), &quot;At least the root should be in the stack&quot;);
</span><del>-        
</del><ins>+
</ins><span class="cx">         // Make an NFA with the subtrees for whom this is also the last quantifier (or who also have no quantifier).
</span><span class="cx">         NFA nfa;
</span><del>-        // Put the prefix into the NFA.
-        unsigned prefixEnd = nfa.root();
-        for (unsigned i = 0; i &lt; stack.size() - 1; ++i) {
-            ASSERT(!stack[i]-&gt;edges.isEmpty());
-            const PrefixTreeEdge&amp; edge = stack[i]-&gt;edges.last();
-            prefixEnd = edge.term.generateGraph(nfa, prefixEnd, edge.child-&gt;finalActions);
</del><ins>+        {
+            // Put the prefix into the NFA.
+            ImmutableCharNFANodeBuilder lastNode(nfa);
+            for (unsigned i = 0; i &lt; stack.size() - 1; ++i) {
+                const PrefixTreeEdge&amp; edge = stack[i]-&gt;edges.last();
+                ImmutableCharNFANodeBuilder newNode = edge.term.generateGraph(nfa, lastNode, edge.child-&gt;finalActions);
+                lastNode = WTF::move(newNode);
+            }
+
+            // Put the non-quantified vertices in the subtree into the NFA and delete them.
+            ASSERT(stack.last());
+            generateNFAForSubtree(nfa, WTF::move(lastNode), *stack.last(), maxNFASize);
</ins><span class="cx">         }
</span><del>-        // Put the non-quantified vertices in the subtree into the NFA and delete them.
-        ASSERT(stack.last());
-        generateNFAForSubtree(nfa, prefixEnd, *stack.last(), maxNFASize);
-        
</del><ins>+        nfa.finalize();
+
</ins><span class="cx">         handler(WTF::move(nfa));
</span><del>-        
</del><ins>+
</ins><span class="cx">         // Clean up any processed leaf nodes.
</span><span class="cx">         while (true) {
</span><span class="cx">             if (stack.size() &gt; 1) {
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsContentExtensionCompilercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/ContentExtensionCompiler.cpp        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -309,9 +309,7 @@
</span><span class="cx">         // Our bytecode interpreter expects to have at least one DFA, so if we haven't seen any
</span><span class="cx">         // create a dummy one and add any universal actions.
</span><span class="cx"> 
</span><del>-        NFA dummyNFA;
-        DFA dummyDFA = NFAToDFA::convert(dummyNFA);
-
</del><ins>+        DFA dummyDFA = DFA::empty();
</ins><span class="cx">         addUniversalActionsToDFA(dummyDFA, universalActionsWithoutDomains);
</span><span class="cx"> 
</span><span class="cx">         Vector&lt;DFABytecode&gt; bytecode;
</span><span class="lines">@@ -376,10 +374,8 @@
</span><span class="cx">     if (!firstNFAWithDomainsSeen) {
</span><span class="cx">         // Our bytecode interpreter expects to have at least one DFA, so if we haven't seen any
</span><span class="cx">         // create a dummy one and add any universal actions.
</span><del>-        
-        NFA dummyNFA;
-        DFA dummyDFA = NFAToDFA::convert(dummyNFA);
-        
</del><ins>+
+        DFA dummyDFA = DFA::empty();
</ins><span class="cx">         addUniversalActionsToDFA(dummyDFA, universalActionsWithDomains);
</span><span class="cx">         
</span><span class="cx">         Vector&lt;DFABytecode&gt; bytecode;
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsDFAcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/DFA.cpp (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/DFA.cpp        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/DFA.cpp        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -35,6 +35,13 @@
</span><span class="cx"> 
</span><span class="cx"> namespace ContentExtensions {
</span><span class="cx"> 
</span><ins>+DFA DFA::empty()
+{
+    DFA newDFA;
+    newDFA.nodes.append(DFANode());
+    return newDFA;
+}
+
</ins><span class="cx"> size_t DFA::memoryUsed() const
</span><span class="cx"> {
</span><span class="cx">     return sizeof(DFA)
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsDFAh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/DFA.h (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/DFA.h        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/DFA.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -38,6 +38,8 @@
</span><span class="cx"> 
</span><span class="cx"> // The DFA abstract a partial DFA graph in a compact form.
</span><span class="cx"> struct WEBCORE_EXPORT DFA {
</span><ins>+    static DFA empty();
+
</ins><span class="cx">     void minimize();
</span><span class="cx">     unsigned graphSize() const;
</span><span class="cx">     size_t memoryUsed() const;
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsImmutableNFAh"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/contentextensions/ImmutableNFA.h (0 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/ImmutableNFA.h                                (rev 0)
+++ trunk/Source/WebCore/contentextensions/ImmutableNFA.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -0,0 +1,173 @@
</span><ins>+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+#ifndef ImmutableNFA_h
+#define ImmutableNFA_h
+
+#include &lt;wtf/Vector.h&gt;
+
+#if ENABLE(CONTENT_EXTENSIONS)
+
+namespace WebCore {
+
+namespace ContentExtensions {
+
+template &lt;typename CharacterType&gt;
+struct ImmutableRange {
+    uint32_t targetStart;
+    uint32_t targetEnd;
+    CharacterType first;
+    CharacterType last;
+};
+
+struct ImmutableNFANode {
+    uint32_t rangesStart { 0 };
+    uint32_t rangesEnd { 0 };
+    uint32_t epsilonTransitionTargetsStart { 0 };
+    uint32_t epsilonTransitionTargetsEnd { 0 };
+    uint32_t actionStart { 0 };
+    uint32_t actionEnd { 0 };
+};
+
+template &lt;typename CharacterType, typename ActionType&gt;
+struct ImmutableNFA {
+    Vector&lt;ImmutableNFANode&gt; nodes;
+    Vector&lt;ImmutableRange&lt;CharacterType&gt;&gt; transitions;
+    Vector&lt;uint32_t&gt; targets;
+    Vector&lt;uint32_t&gt; epsilonTransitionsTargets;
+    Vector&lt;ActionType&gt; actions;
+
+    struct ConstTargetIterator {
+        const ImmutableNFA&amp; immutableNFA;
+        uint32_t position;
+
+        const uint32_t&amp; operator*() const { return immutableNFA.targets[position]; }
+        const uint32_t* operator-&gt;() const { return &amp;immutableNFA.targets[position]; }
+
+        bool operator==(const ConstTargetIterator&amp; other) const
+        {
+            ASSERT(&amp;immutableNFA == &amp;other.immutableNFA);
+            return position == other.position;
+        }
+        bool operator!=(const ConstTargetIterator&amp; other) const { return !(*this == other); }
+
+        ConstTargetIterator&amp; operator++()
+        {
+            ++position;
+            return *this;
+        }
+    };
+
+    struct IterableConstTargets {
+        const ImmutableNFA&amp; immutableNFA;
+        uint32_t targetStart;
+        uint32_t targetEnd;
+
+        ConstTargetIterator begin() const { return { immutableNFA, targetStart }; }
+        ConstTargetIterator end() const { return { immutableNFA, targetEnd }; }
+    };
+
+    struct ConstRangeIterator {
+        const ImmutableNFA&amp; immutableNFA;
+        uint32_t position;
+
+        const ImmutableRange&lt;CharacterType&gt;&amp; operator*() const { return immutableNFA.transitions[position]; }
+        const ImmutableRange&lt;CharacterType&gt;* operator-&gt;() const { return &amp;immutableNFA.transitions[position]; }
+
+        bool operator==(const ConstRangeIterator&amp; other) const
+        {
+            ASSERT(&amp;immutableNFA == &amp;other.immutableNFA);
+            return position == other.position;
+        }
+        bool operator!=(const ConstRangeIterator&amp; other) const { return !(*this == other); }
+
+        ConstRangeIterator&amp; operator++()
+        {
+            ++position;
+            return *this;
+        }
+
+        IterableConstTargets data() const
+        {
+            const ImmutableRange&lt;CharacterType&gt;&amp; range = immutableNFA.transitions[position];
+            return { immutableNFA, range.targetStart, range.targetEnd };
+        };
+    };
+
+    struct IterableConstRange {
+        const ImmutableNFA&amp; immutableNFA;
+        uint32_t rangesStart;
+        uint32_t rangesEnd;
+
+        ConstRangeIterator begin() const { return { immutableNFA, rangesStart }; }
+        ConstRangeIterator end() const { return { immutableNFA, rangesEnd }; }
+
+#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
+        void debugPrint() const
+        {
+            for (const auto&amp; range : *this)
+                WTFLogAlways(&quot;    %d-%d&quot;, range.first, range.last);
+        }
+#endif
+    };
+
+    IterableConstRange transitionsForNode(uint32_t nodeId) const
+    {
+        const ImmutableNFANode&amp; node = nodes[nodeId];
+        return { *this, node.rangesStart, node.rangesEnd };
+    };
+
+    uint32_t root() const
+    {
+        RELEASE_ASSERT(!nodes.isEmpty());
+        return 0;
+    }
+
+    void finalize()
+    {
+        nodes.shrinkToFit();
+        transitions.shrinkToFit();
+        targets.shrinkToFit();
+        epsilonTransitionsTargets.shrinkToFit();
+        actions.shrinkToFit();
+    }
+
+    size_t memoryUsed() const
+    {
+        return nodes.capacity() * sizeof(ImmutableNFANode)
+            + transitions.capacity() * sizeof(ImmutableRange&lt;CharacterType&gt;)
+            + targets.capacity() * sizeof(uint32_t)
+            + epsilonTransitionsTargets.capacity() * sizeof(uint32_t)
+            + actions.capacity() * sizeof(ActionType);
+    }
+};
+
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(CONTENT_EXTENSIONS)
+
+#endif // ImmutableNFA_h
</ins></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsImmutableNFANodeBuilderh"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/contentextensions/ImmutableNFANodeBuilder.h (0 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/ImmutableNFANodeBuilder.h                                (rev 0)
+++ trunk/Source/WebCore/contentextensions/ImmutableNFANodeBuilder.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -0,0 +1,244 @@
</span><ins>+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+#ifndef ImmutableNFANodeBuilder_h
+#define ImmutableNFANodeBuilder_h
+
+#include &quot;ImmutableNFA.h&quot;
+#include &quot;MutableRangeList.h&quot;
+#include &lt;wtf/HashSet.h&gt;
+
+#if ENABLE(CONTENT_EXTENSIONS)
+
+namespace WebCore {
+
+namespace ContentExtensions {
+
+// A ImmutableNFANodeBuilder let you build a NFA node by adding states and linking with other nodes.
+// Whe a builder is destructed, all its properties are finalized into the NFA. Using the NA with a live
+// builder results in undefined behaviors.
+template &lt;typename CharacterType, typename ActionType&gt;
+class ImmutableNFANodeBuilder {
+    typedef ImmutableNFA&lt;CharacterType, ActionType&gt; TypedImmutableNFA;
+    typedef HashSet&lt;uint32_t, DefaultHash&lt;uint32_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint32_t&gt;&gt; TargetSet;
+public:
+    ImmutableNFANodeBuilder() { }
+
+    ImmutableNFANodeBuilder(TypedImmutableNFA&amp; immutableNFA)
+        : m_immutableNFA(&amp;immutableNFA)
+        , m_finalized(false)
+    {
+        m_nodeId = m_immutableNFA-&gt;nodes.size();
+        m_immutableNFA-&gt;nodes.append(ImmutableNFANode());
+#if !ASSERT_DISABLED
+        m_isDisconnected = true;
+#endif
+    }
+
+    ImmutableNFANodeBuilder(ImmutableNFANodeBuilder&amp;&amp; other)
+        : m_immutableNFA(other.m_immutableNFA)
+        , m_ranges(WTF::move(other.m_ranges))
+        , m_epsilonTransitionTargets(WTF::move(m_epsilonTransitionTargets))
+        , m_actions(WTF::move(other.m_actions))
+        , m_nodeId(other.m_nodeId)
+        , m_finalized(other.m_finalized)
+#if !ASSERT_DISABLED
+        , m_isDisconnected(other.m_isDisconnected)
+#endif
+    {
+        other.m_immutableNFA = nullptr;
+        other.m_finalized = true;
+#if !ASSERT_DISABLED
+        other.m_isDisconnected = false;
+#endif
+    }
+
+    ~ImmutableNFANodeBuilder()
+    {
+        ASSERT_WITH_MESSAGE(!m_isDisconnected, &quot;This nodes is not connected to anything and is not reached by any other node.&quot;);
+
+        if (!m_finalized)
+            finalize();
+    }
+
+    struct TrivialRange {
+        CharacterType first;
+        CharacterType last;
+    };
+    
+    struct FakeRangeIterator {
+        const TrivialRange&amp; operator*() const { return range; }
+        const TrivialRange* operator-&gt;() const { return &amp;range; }
+        uint32_t data() const { return targetId; }
+        bool operator==(const FakeRangeIterator&amp; other)
+        {
+            return this-&gt;isEnd == other.isEnd;
+        }
+        bool operator!=(const FakeRangeIterator&amp; other) { return !(*this == other); }
+        FakeRangeIterator operator++()
+        {
+            isEnd = true;
+            return *this;
+        }
+        
+        TrivialRange range;
+        uint32_t targetId;
+        bool isEnd;
+    };
+
+    void addTransition(CharacterType first, CharacterType last, const ImmutableNFANodeBuilder&amp; target)
+    {
+        ASSERT(!m_finalized);
+        ASSERT(m_immutableNFA);
+        ASSERT(m_immutableNFA == target.m_immutableNFA);
+#if !ASSERT_DISABLED
+        m_isDisconnected = false;
+        target.m_isDisconnected = false;
+#endif
+
+        struct Converter {
+            TargetSet convert(uint32_t target)
+            {
+                return TargetSet({ target });
+            }
+            void extend(TargetSet&amp; existingTargets, uint32_t target)
+            {
+                existingTargets.add(target);
+            }
+        };
+        
+        Converter converter;
+        m_ranges.extend(FakeRangeIterator { { first, last }, target.m_nodeId, false }, FakeRangeIterator { { 0, 0 }, target.m_nodeId, true }, converter);
+    }
+
+    void addEpsilonTransition(const ImmutableNFANodeBuilder&amp; target)
+    {
+        ASSERT(!m_finalized);
+        ASSERT(m_immutableNFA);
+        ASSERT(m_immutableNFA == target.m_immutableNFA);
+#if !ASSERT_DISABLED
+        m_isDisconnected = false;
+        target.m_isDisconnected = false;
+#endif
+        m_epsilonTransitionTargets.add(target.m_nodeId);
+    }
+
+    template&lt;typename ActionIterator&gt;
+    void setActions(ActionIterator begin, ActionIterator end)
+    {
+        ASSERT(!m_finalized);
+        ASSERT(m_immutableNFA);
+
+        m_actions.add(begin, end);
+    }
+
+    ImmutableNFANodeBuilder&amp; operator=(ImmutableNFANodeBuilder&amp;&amp; other)
+    {
+        if (!m_finalized)
+            finalize();
+
+        m_immutableNFA = other.m_immutableNFA;
+        m_ranges = WTF::move(other.m_ranges);
+        m_epsilonTransitionTargets = WTF::move(other.m_epsilonTransitionTargets);
+        m_actions = WTF::move(other.m_actions);
+        m_nodeId = other.m_nodeId;
+        m_finalized = other.m_finalized;
+
+        other.m_immutableNFA = nullptr;
+        other.m_finalized = true;
+#if !ASSERT_DISABLED
+        m_isDisconnected = other.m_isDisconnected;
+        other.m_isDisconnected = false;
+#endif
+        return *this;
+    }
+
+private:
+    void finalize()
+    {
+        ASSERT_WITH_MESSAGE(!m_finalized, &quot;The API contract is that the builder can only be finalized once.&quot;);
+        m_finalized = true;
+        ImmutableNFANode&amp; immutableNFANode = m_immutableNFA-&gt;nodes[m_nodeId];
+        sinkActions(immutableNFANode);
+        sinkEpsilonTransitions(immutableNFANode);
+        sinkTransitions(immutableNFANode);
+    }
+
+    void sinkActions(ImmutableNFANode&amp; immutableNFANode)
+    {
+        unsigned actionStart = m_immutableNFA-&gt;actions.size();
+        for (const ActionType&amp; action : m_actions)
+            m_immutableNFA-&gt;actions.append(action);
+        unsigned actionEnd = m_immutableNFA-&gt;actions.size();
+        immutableNFANode.actionStart = actionStart;
+        immutableNFANode.actionEnd = actionEnd;
+    }
+
+    void sinkTransitions(ImmutableNFANode&amp; immutableNFANode)
+    {
+        unsigned transitionsStart = m_immutableNFA-&gt;transitions.size();
+        for (const auto&amp; range : m_ranges) {
+            unsigned targetsStart = m_immutableNFA-&gt;targets.size();
+            for (uint32_t target : range.data)
+                m_immutableNFA-&gt;targets.append(target);
+            unsigned targetsEnd = m_immutableNFA-&gt;targets.size();
+
+            m_immutableNFA-&gt;transitions.append(ImmutableRange&lt;CharacterType&gt; { targetsStart, targetsEnd, range.first, range.last });
+        }
+        unsigned transitionsEnd = m_immutableNFA-&gt;transitions.size();
+
+        immutableNFANode.rangesStart = transitionsStart;
+        immutableNFANode.rangesEnd = transitionsEnd;
+    }
+
+    void sinkEpsilonTransitions(ImmutableNFANode&amp; immutableNFANode)
+    {
+        unsigned start = m_immutableNFA-&gt;epsilonTransitionsTargets.size();
+        for (uint32_t target : m_epsilonTransitionTargets)
+            m_immutableNFA-&gt;epsilonTransitionsTargets.append(target);
+        unsigned end = m_immutableNFA-&gt;epsilonTransitionsTargets.size();
+
+        immutableNFANode.epsilonTransitionTargetsStart = start;
+        immutableNFANode.epsilonTransitionTargetsEnd = end;
+    }
+
+    TypedImmutableNFA* m_immutableNFA { nullptr };
+    MutableRangeList&lt;CharacterType, TargetSet&gt; m_ranges;
+    TargetSet m_epsilonTransitionTargets;
+    HashSet&lt;ActionType, WTF::IntHash&lt;ActionType&gt;, WTF::UnsignedWithZeroKeyHashTraits&lt;ActionType&gt;&gt; m_actions;
+    uint32_t m_nodeId;
+    bool m_finalized { true };
+#if !ASSERT_DISABLED
+    mutable bool m_isDisconnected { false };
+#endif
+};
+
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(CONTENT_EXTENSIONS)
+
+#endif // ImmutableNFANodeBuilder_h
</ins></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsMutableRangeh"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/contentextensions/MutableRange.h (0 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/MutableRange.h                                (rev 0)
+++ trunk/Source/WebCore/contentextensions/MutableRange.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -0,0 +1,102 @@
</span><ins>+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+#ifndef MutableRange_h
+#define MutableRange_h
+
+#if ENABLE(CONTENT_EXTENSIONS)
+
+namespace WebCore {
+
+namespace ContentExtensions {
+
+template &lt;typename CharacterType, typename DataType&gt;
+class MutableRange {
+    typedef MutableRange&lt;CharacterType, DataType&gt; TypedMutableRange;
+public:
+    MutableRange(uint32_t nextRangeIndex, CharacterType first, CharacterType last)
+        : nextRangeIndex(nextRangeIndex)
+        , first(first)
+        , last(last)
+    {
+        ASSERT(first &lt;= last);
+    }
+
+    MutableRange(const DataType&amp; data, uint32_t nextRangeIndex, CharacterType first, CharacterType last)
+        : data(data)
+        , nextRangeIndex(nextRangeIndex)
+        , first(first)
+        , last(last)
+    {
+        ASSERT(first &lt;= last);
+    }
+
+    MutableRange(DataType&amp;&amp; data, uint32_t nextRangeIndex, CharacterType first, CharacterType last)
+        : data(WTF::move(data))
+        , nextRangeIndex(nextRangeIndex)
+        , first(first)
+        , last(last)
+    {
+        ASSERT(first &lt;= last);
+    }
+
+    MutableRange(MutableRange&amp;&amp; other)
+        : data(WTF::move(other.data))
+        , nextRangeIndex(other.nextRangeIndex)
+        , first(other.first)
+        , last(other.last)
+    {
+        ASSERT(first &lt;= last);
+    }
+
+    TypedMutableRange&amp; operator=(TypedMutableRange&amp;&amp; other)
+    {
+        data = WTF::move(other.data);
+        nextRangeIndex = WTF::move(other.nextRangeIndex);
+        first = WTF::move(other.first);
+        last = WTF::move(other.last);
+        return *this;
+    }
+
+    DataType data;
+
+    // We use a funny convention: if there are no nextRange, the nextRangeIndex is zero.
+    // This is faster to check than a special value in many cases.
+    // We can use zero because ranges can only form a chain, and the first range is always zero by convention.
+    // When we insert something in from of the first range, we swap the values.
+    uint32_t nextRangeIndex;
+    CharacterType first;
+    CharacterType last;
+
+    CharacterType size() const { return last - first; }
+};
+
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(CONTENT_EXTENSIONS)
+
+#endif // MutableRange_h
</ins></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsMutableRangeListh"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/contentextensions/MutableRangeList.h (0 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/MutableRangeList.h                                (rev 0)
+++ trunk/Source/WebCore/contentextensions/MutableRangeList.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -0,0 +1,261 @@
</span><ins>+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+#ifndef MutableRangeList_h
+#define MutableRangeList_h
+
+#include &quot;MutableRange.h&quot;
+
+#if ENABLE(CONTENT_EXTENSIONS)
+
+namespace WebCore {
+
+namespace ContentExtensions {
+
+// A range list keeps ranges sorted. Ranges are not sorted in the vector, but
+template &lt;typename CharacterType, typename DataType, unsigned inlineCapacity = 0&gt;
+class MutableRangeList {
+    typedef MutableRange&lt;CharacterType, DataType&gt; TypedMutableRange;
+public:
+    class ConstIterator {
+    public:
+        const MutableRangeList&amp; rangeList;
+        uint32_t index;
+        bool atEnd;
+
+        const TypedMutableRange&amp; operator*() const { return rangeList.m_ranges[index]; }
+        const TypedMutableRange* operator-&gt;() const { return &amp;rangeList.m_ranges[index]; }
+
+        bool operator==(const ConstIterator&amp; other) const
+        {
+            ASSERT(&amp;rangeList == &amp;other.rangeList);
+            if (atEnd || other.atEnd)
+                return atEnd == other.atEnd;
+            return index == other.index;
+        }
+        bool operator!=(const ConstIterator&amp; other) const { return !(*this == other); }
+
+        ConstIterator&amp; operator++()
+        {
+            ASSERT(!atEnd);
+            index = rangeList.m_ranges[index].nextRangeIndex;
+            if (!index)
+                atEnd = true;
+            return *this;
+        }
+    };
+
+    ConstIterator begin() const { return ConstIterator { *this, 0, m_ranges.isEmpty() }; }
+    ConstIterator end() const { return ConstIterator { *this, 0, true }; }
+
+    uint32_t appendRange(uint32_t lastRangeIndex, CharacterType first, CharacterType last, const DataType&amp; data)
+    {
+        uint32_t newRangeIndex = m_ranges.size();
+        m_ranges.append(TypedMutableRange(data, 0, first, last));
+        if (!newRangeIndex)
+            return 0;
+
+        ASSERT(!m_ranges[lastRangeIndex].nextRangeIndex);
+        ASSERT(m_ranges[lastRangeIndex].last &lt; first);
+
+        m_ranges[lastRangeIndex].nextRangeIndex = newRangeIndex;
+        return newRangeIndex;
+    }
+
+    template &lt;typename RangeIterator, typename DataConverter&gt;
+    void extend(RangeIterator otherIterator, RangeIterator otherEnd, DataConverter dataConverter)
+    {
+        if (otherIterator == otherEnd)
+            return;
+
+        if (m_ranges.isEmpty()) {
+            initializeFrom(otherIterator, otherEnd, dataConverter);
+            return;
+        }
+
+        bool reachedSelfEnd = false;
+        uint32_t lastSelfRangeIndex = 0;
+        uint32_t selfRangeIndex = 0;
+
+        auto otherRangeOffset = otherIterator-&gt;first; // To get the right type :)
+        otherRangeOffset = 0;
+
+        do {
+            TypedMutableRange* activeSelfRange = &amp;m_ranges[selfRangeIndex];
+
+            // First, we move forward until we find something interesting.
+            if (activeSelfRange-&gt;last &lt; otherIterator-&gt;first + otherRangeOffset) {
+                lastSelfRangeIndex = selfRangeIndex;
+                selfRangeIndex = activeSelfRange-&gt;nextRangeIndex;
+                reachedSelfEnd = !selfRangeIndex;
+                continue;
+            }
+            if (otherIterator-&gt;last &lt; activeSelfRange-&gt;first) {
+                insertBetween(lastSelfRangeIndex, selfRangeIndex, otherIterator-&gt;first + otherRangeOffset, otherIterator-&gt;last, dataConverter.convert(otherIterator.data()));
+
+                ++otherIterator;
+                otherRangeOffset = 0;
+                continue;
+            }
+
+            // If we reached here, we have:
+            // 1) activeRangeA-&gt;last &gt;= activeRangeB-&gt;first.
+            // 2) activeRangeA-&gt;first &lt;= activeRangeB-&gt;last.
+            // But we don't know how they collide.
+
+            // Do we have a part on the left? Create a new range for it.
+            if (activeSelfRange-&gt;first &lt; otherIterator-&gt;first + otherRangeOffset) {
+                DataType copiedData = activeSelfRange-&gt;data;
+                CharacterType newRangeFirstCharacter = activeSelfRange-&gt;first;
+                activeSelfRange-&gt;first = otherIterator-&gt;first + otherRangeOffset;
+                insertBetween(lastSelfRangeIndex, selfRangeIndex, newRangeFirstCharacter, otherIterator-&gt;first + otherRangeOffset - 1, WTF::move(copiedData));
+                activeSelfRange = &amp;m_ranges[selfRangeIndex];
+            } else if (otherIterator-&gt;first + otherRangeOffset &lt; activeSelfRange-&gt;first) {
+                insertBetween(lastSelfRangeIndex, selfRangeIndex, otherIterator-&gt;first + otherRangeOffset, activeSelfRange-&gt;first - 1, dataConverter.convert(otherIterator.data()));
+
+                activeSelfRange = &amp;m_ranges[selfRangeIndex];
+                ASSERT_WITH_MESSAGE(otherRangeOffset &lt; activeSelfRange-&gt;first - otherIterator-&gt;first, &quot;The offset must move forward or we could get stuck on this operation.&quot;);
+                otherRangeOffset = activeSelfRange-&gt;first - otherIterator-&gt;first;
+            }
+
+            // Here, we know both ranges start at the same point, we need to create the part that intersect
+            // and merge the data.
+
+            if (activeSelfRange-&gt;last == otherIterator-&gt;last) {
+                // If they finish together, things are really easy: we just add B to A.
+                dataConverter.extend(activeSelfRange-&gt;data, otherIterator.data());
+
+                lastSelfRangeIndex = selfRangeIndex;
+                selfRangeIndex = activeSelfRange-&gt;nextRangeIndex;
+                reachedSelfEnd = !selfRangeIndex;
+
+                ++otherIterator;
+                otherRangeOffset = 0;
+                continue;
+            }
+
+            if (activeSelfRange-&gt;last &gt; otherIterator-&gt;last) {
+                // If A is bigger than B, we add a merged version and move A to the right.
+
+                CharacterType combinedPartStart = activeSelfRange-&gt;first;
+                activeSelfRange-&gt;first = otherIterator-&gt;last + 1;
+
+                DataType combinedData = activeSelfRange-&gt;data;
+                dataConverter.extend(combinedData, otherIterator.data());
+                insertBetween(lastSelfRangeIndex, selfRangeIndex, combinedPartStart, otherIterator-&gt;last, WTF::move(combinedData));
+
+                ++otherIterator;
+                otherRangeOffset = 0;
+                continue;
+            }
+
+            // If we reached here, B ends after A. We merge the intersection and move on.
+            ASSERT(otherIterator-&gt;last &gt; activeSelfRange-&gt;last);
+            dataConverter.extend(activeSelfRange-&gt;data, otherIterator.data());
+
+            otherRangeOffset = activeSelfRange-&gt;last - otherIterator-&gt;first + 1;
+            lastSelfRangeIndex = selfRangeIndex;
+            selfRangeIndex = activeSelfRange-&gt;nextRangeIndex;
+            reachedSelfEnd = !selfRangeIndex;
+        } while (!reachedSelfEnd &amp;&amp; otherIterator != otherEnd);
+
+        while (otherIterator != otherEnd) {
+            lastSelfRangeIndex = appendRange(lastSelfRangeIndex, otherIterator-&gt;first + otherRangeOffset, otherIterator-&gt;last, dataConverter.convert(otherIterator.data()));
+            otherRangeOffset = 0;
+            ++otherIterator;
+        }
+    }
+
+    bool isEmpty() const
+    {
+        return m_ranges.isEmpty();
+    }
+
+    void clear()
+    {
+        m_ranges.clear();
+    }
+
+#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
+    void debugPrint() const
+    {
+        for (const TypedMutableRange&amp; range : *this)
+            WTFLogAlways(&quot;    %d-%d&quot;, range.first, range.last);
+    }
+#endif
+
+    Vector&lt;MutableRange&lt;CharacterType, DataType&gt;, inlineCapacity&gt; m_ranges;
+private:
+    void insertBetween(uint32_t&amp; leftRangeIndex, uint32_t&amp; rightRangeIndex, CharacterType first, CharacterType last, DataType&amp;&amp; data)
+    {
+        ASSERT(m_ranges[rightRangeIndex].first &gt; last);
+
+        if (!rightRangeIndex) {
+            // This is a special case. We always keep the first range as the first element in the vector.
+            uint32_t movedRangeIndex = m_ranges.size();
+            m_ranges.append(WTF::move(m_ranges.first()));
+            m_ranges[0] = TypedMutableRange(WTF::move(data), movedRangeIndex, first, last);
+            leftRangeIndex = 0;
+            rightRangeIndex = movedRangeIndex;
+            return;
+        }
+
+        ASSERT(m_ranges[leftRangeIndex].nextRangeIndex == rightRangeIndex);
+        ASSERT(m_ranges[leftRangeIndex].last &lt; first);
+
+        uint32_t newRangeIndex = m_ranges.size();
+        m_ranges.append(TypedMutableRange(WTF::move(data), rightRangeIndex, first, last));
+        m_ranges[leftRangeIndex].nextRangeIndex = newRangeIndex;
+        leftRangeIndex = newRangeIndex;
+    }
+
+    template &lt;typename RangeIterator, typename DataConverter&gt;
+    void initializeFrom(RangeIterator otherIterator, RangeIterator otherEnd, DataConverter dataConverter)
+    {
+        ASSERT_WITH_MESSAGE(otherIterator != otherEnd, &quot;We should never do anything when extending with a null range.&quot;);
+        ASSERT_WITH_MESSAGE(m_ranges.isEmpty(), &quot;This code does not handle splitting, it can only be used on empty RangeList.&quot;);
+
+        uint32_t loopCounter = 0;
+        do {
+            m_ranges.append(TypedMutableRange(dataConverter.convert(otherIterator.data()),
+                loopCounter + 1,
+                otherIterator-&gt;first,
+                otherIterator-&gt;last));
+            ++loopCounter;
+            ++otherIterator;
+        } while (otherIterator != otherEnd);
+
+        if (!m_ranges.isEmpty())
+            m_ranges.last().nextRangeIndex = 0;
+    }
+};
+
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(CONTENT_EXTENSIONS)
+
+#endif // MutableRangeList_h
</ins></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsNFAcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/NFA.cpp (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/NFA.cpp        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/NFA.cpp        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -28,6 +28,7 @@
</span><span class="cx"> 
</span><span class="cx"> #if ENABLE(CONTENT_EXTENSIONS)
</span><span class="cx"> 
</span><ins>+#include &lt;wtf/ASCIICType.h&gt;
</ins><span class="cx"> #include &lt;wtf/DataLog.h&gt;
</span><span class="cx"> #include &lt;wtf/HashSet.h&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -35,184 +36,85 @@
</span><span class="cx"> 
</span><span class="cx"> namespace ContentExtensions {
</span><span class="cx"> 
</span><del>-NFA::NFA()
-    : m_root(createNode())
-{
-}
-
-unsigned NFA::createNode()
-{
-    unsigned nextId = m_nodes.size();
-    m_nodes.append(NFANode());
-    return nextId;
-}
-
-#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
-size_t NFA::memoryUsed() const
-{
-    size_t size = sizeof(NFA);
-    for (const NFANode&amp; node : m_nodes) {
-        for (const auto&amp; transition : node.transitions)
-            size += transition.value.capacity() * sizeof(unsigned);
-        size += sizeof(node)
-            + node.transitions.capacity() * sizeof(std::pair&lt;uint16_t, NFANodeIndexSet&gt;)
-            + node.transitionsOnAnyCharacter.capacity() * sizeof(unsigned)
-            + node.finalRuleIds.capacity() * sizeof(uint64_t);
-    }
-    return size;
-}
-#endif
-
-void NFA::addTransition(unsigned from, unsigned to, char character)
-{
-    ASSERT(from &lt; m_nodes.size());
-    ASSERT(to &lt; m_nodes.size());
-
-    NFANode&amp; fromNode = m_nodes[from];
-    if (fromNode.transitionsOnAnyCharacter.contains(to))
-        return;
-
-    auto addResult = m_nodes[from].transitions.add(character, NFANodeIndexSet());
-    addResult.iterator-&gt;value.add(to);
-}
-
-void NFA::addEpsilonTransition(unsigned from, unsigned to)
-{
-    ASSERT(from &lt; m_nodes.size());
-    ASSERT(to &lt; m_nodes.size());
-
-    auto addResult = m_nodes[from].transitions.add(epsilonTransitionCharacter, NFANodeIndexSet());
-    addResult.iterator-&gt;value.add(to);
-}
-
-void NFA::addTransitionsOnAnyCharacter(unsigned from, unsigned to)
-{
-    ASSERT(from &lt; m_nodes.size());
-    ASSERT(to &lt; m_nodes.size());
-
-    NFANode&amp; fromNode = m_nodes[from];
-    fromNode.transitionsOnAnyCharacter.add(to);
-
-    for (auto transitionSlot : fromNode.transitions)
-        transitionSlot.value.remove(to);
-}
-
-void NFA::setActions(unsigned node, const ActionList&amp; actions)
-{
-    ASSERT_WITH_MESSAGE(m_nodes[node].finalRuleIds.isEmpty(), &quot;The final state should only be defined once.&quot;);
-    copyToVector(actions, m_nodes[node].finalRuleIds);
-}
-
-unsigned NFA::graphSize() const
-{
-    return m_nodes.size();
-}
-
-void NFA::restoreToGraphSize(unsigned size)
-{
-    ASSERT(size &gt;= 1);
-    ASSERT(size &lt;= graphSize());
-
-    m_nodes.shrink(size);
-}
-
</del><span class="cx"> #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
</span><del>-static void printRange(bool firstRange, uint16_t rangeStart, uint16_t rangeEnd, uint16_t epsilonTransitionCharacter)
-{
-    if (!firstRange)
-        dataLogF(&quot;, &quot;);
-    if (rangeStart == rangeEnd) {
-        if (rangeStart == epsilonTransitionCharacter)
-            dataLogF(&quot;É›&quot;);
-        else if (rangeStart == '&quot;' || rangeStart == '\\')
-            dataLogF(&quot;\\%c&quot;, rangeStart);
-        else if (rangeStart &gt;= '!' &amp;&amp; rangeStart &lt;= '~')
-            dataLogF(&quot;%c&quot;, rangeStart);
-        else
-            dataLogF(&quot;\\\\%d&quot;, rangeStart);
-    } else {
-        if (rangeStart == 1 &amp;&amp; rangeEnd == 127)
-            dataLogF(&quot;[any input]&quot;);
-        else
-            dataLogF(&quot;\\\\%d-\\\\%d&quot;, rangeStart, rangeEnd);
-    }
-}
-
-static void printTransitions(const Vector&lt;NFANode&gt;&amp; graph, unsigned sourceNode, uint16_t epsilonTransitionCharacter)
-{
-    const NFANode&amp; node = graph[sourceNode];
-    const HashMap&lt;uint16_t, NFANodeIndexSet, DefaultHash&lt;uint16_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint16_t&gt;&gt;&amp; transitions = node.transitions;
-
-    HashMap&lt;unsigned, HashSet&lt;uint16_t, DefaultHash&lt;uint16_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint16_t&gt;&gt;, DefaultHash&lt;unsigned&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;unsigned&gt;&gt; transitionsPerTarget;
-
-    for (const auto&amp; transition : transitions) {
-        for (unsigned targetNode : transition.value) {
-            transitionsPerTarget.add(targetNode, HashSet&lt;uint16_t, DefaultHash&lt;uint16_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint16_t&gt;&gt;());
-            transitionsPerTarget.find(targetNode)-&gt;value.add(transition.key);
-        }
-    }
-
-    for (const auto&amp; transitionPerTarget : transitionsPerTarget) {
-        dataLogF(&quot;        %d -&gt; %d [label=\&quot;&quot;, sourceNode, transitionPerTarget.key);
-
-        Vector&lt;uint16_t&gt; incommingCharacters;
-        copyToVector(transitionPerTarget.value, incommingCharacters);
-        std::sort(incommingCharacters.begin(), incommingCharacters.end());
-
-        uint16_t rangeStart = incommingCharacters.first();
-        uint16_t rangeEnd = rangeStart;
-        bool first = true;
-        for (unsigned sortedTransitionIndex = 1; sortedTransitionIndex &lt; incommingCharacters.size(); ++sortedTransitionIndex) {
-            uint16_t nextChar = incommingCharacters[sortedTransitionIndex];
-            if (nextChar == rangeEnd+1) {
-                rangeEnd = nextChar;
-                continue;
-            }
-            printRange(first, rangeStart, rangeEnd, epsilonTransitionCharacter);
-            rangeStart = rangeEnd = nextChar;
-            first = false;
-        }
-        printRange(first, rangeStart, rangeEnd, epsilonTransitionCharacter);
-
-        dataLogF(&quot;\&quot;];\n&quot;);
-    }
-
-    for (unsigned targetOnAnyCharacter : node.transitionsOnAnyCharacter)
-        dataLogF(&quot;        %d -&gt; %d [label=\&quot;[any input]\&quot;];\n&quot;, sourceNode, targetOnAnyCharacter);
-}
-
</del><span class="cx"> void NFA::debugPrintDot() const
</span><span class="cx"> {
</span><span class="cx">     dataLogF(&quot;digraph NFA_Transitions {\n&quot;);
</span><span class="cx">     dataLogF(&quot;    rankdir=LR;\n&quot;);
</span><span class="cx">     dataLogF(&quot;    node [shape=circle];\n&quot;);
</span><span class="cx">     dataLogF(&quot;    {\n&quot;);
</span><del>-    for (unsigned i = 0; i &lt; m_nodes.size(); ++i) {
</del><ins>+
+    for (unsigned i = 0; i &lt; nodes.size(); ++i) {
</ins><span class="cx">         dataLogF(&quot;         %d [label=&lt;Node %d&quot;, i, i);
</span><span class="cx"> 
</span><del>-        const ActionList&amp; finalRules = m_nodes[i].finalRuleIds;
-        if (!finalRules.isEmpty()) {
</del><ins>+        const auto&amp; node = nodes[i];
+
+        if (node.actionStart != node.actionEnd) {
</ins><span class="cx">             dataLogF(&quot;&lt;BR/&gt;(Final: &quot;);
</span><del>-            for (unsigned ruleIndex = 0; ruleIndex &lt; finalRules.size(); ++ruleIndex) {
-                if (ruleIndex)
</del><ins>+            bool isFirst = true;
+            for (unsigned actionIndex = node.actionStart; actionIndex &lt; node.actionEnd; ++actionIndex) {
+                if (!isFirst)
</ins><span class="cx">                     dataLogF(&quot;, &quot;);
</span><del>-                dataLogF(&quot;%llu&quot;, finalRules[ruleIndex]);
</del><ins>+                dataLogF(&quot;%llu&quot;, actions[actionIndex]);
+                isFirst = false;
</ins><span class="cx">             }
</span><span class="cx">             dataLogF(&quot;)&quot;);
</span><span class="cx">         }
</span><del>-
</del><span class="cx">         dataLogF(&quot;&gt;]&quot;);
</span><span class="cx"> 
</span><del>-        if (!finalRules.isEmpty())
</del><ins>+        if (node.actionStart != node.actionEnd)
</ins><span class="cx">             dataLogF(&quot; [shape=doublecircle]&quot;);
</span><del>-
</del><span class="cx">         dataLogF(&quot;;\n&quot;);
</span><span class="cx">     }
</span><span class="cx">     dataLogF(&quot;    }\n&quot;);
</span><span class="cx"> 
</span><span class="cx">     dataLogF(&quot;    {\n&quot;);
</span><del>-    for (unsigned i = 0; i &lt; m_nodes.size(); ++i)
-        printTransitions(m_nodes, i, epsilonTransitionCharacter);
</del><ins>+    for (unsigned i = 0; i &lt; nodes.size(); ++i) {
+        const auto&amp; node = nodes[i];
+
+        HashMap&lt;uint32_t, Vector&lt;uint32_t&gt;, DefaultHash&lt;unsigned&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;unsigned&gt;&gt; transitionsPerTarget;
+
+        for (uint32_t transitionIndex = node.rangesStart; transitionIndex &lt; node.rangesEnd; ++transitionIndex) {
+            const ImmutableCharRange&amp; range = transitions[transitionIndex];
+            for (uint32_t targetIndex = range.targetStart; targetIndex &lt; range.targetEnd; ++targetIndex) {
+                uint32_t target = targets[targetIndex];
+                auto addResult = transitionsPerTarget.add(target, Vector&lt;uint32_t&gt;());
+                addResult.iterator-&gt;value.append(transitionIndex);
+            }
+        }
+
+        for (const auto&amp; slot : transitionsPerTarget) {
+            dataLogF(&quot;        %d -&gt; %d [label=\&quot;&quot;, i, slot.key);
+
+            bool isFirst = true;
+            for (uint32_t rangeIndex : slot.value) {
+                if (!isFirst)
+                    dataLogF(&quot;, &quot;);
+                else
+                    isFirst = false;
+
+                const ImmutableCharRange&amp; range = transitions[rangeIndex];
+                if (range.first == range.last) {
+                    if (isASCIIPrintable(range.first))
+                    dataLogF(&quot;%c&quot;, range.first);
+                    else
+                    dataLogF(&quot;%d&quot;, range.first);
+                } else {
+                    if (isASCIIPrintable(range.first) &amp;&amp; isASCIIPrintable(range.last))
+                    dataLogF(&quot;%c-%c&quot;, range.first, range.last);
+                    else
+                    dataLogF(&quot;%d-%d&quot;, range.first, range.last);
+                }
+            }
+            dataLogF(&quot;\&quot;]\n&quot;);
+        }
+
+        for (uint32_t epsilonTargetIndex = node.epsilonTransitionTargetsStart; epsilonTargetIndex &lt; node.epsilonTransitionTargetsEnd; ++epsilonTargetIndex) {
+            uint32_t target = epsilonTransitionsTargets[epsilonTargetIndex];
+            dataLogF(&quot;        %d -&gt; %d [label=\&quot;É›\&quot;]\n&quot;, i, target);
+        }
+    }
+
</ins><span class="cx">     dataLogF(&quot;    }\n&quot;);
</span><span class="cx">     dataLogF(&quot;}\n&quot;);
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsNFAh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/NFA.h (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/NFA.h        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/NFA.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -29,6 +29,7 @@
</span><span class="cx"> #if ENABLE(CONTENT_EXTENSIONS)
</span><span class="cx"> 
</span><span class="cx"> #include &quot;ContentExtensionsDebugging.h&quot;
</span><ins>+#include &quot;ImmutableNFANodeBuilder.h&quot;
</ins><span class="cx"> #include &quot;NFANode.h&quot;
</span><span class="cx"> #include &lt;wtf/Vector.h&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -36,42 +37,13 @@
</span><span class="cx"> 
</span><span class="cx"> namespace ContentExtensions {
</span><span class="cx"> 
</span><del>-class NFAToDFA;
</del><ins>+typedef ImmutableRange&lt;char&gt; ImmutableCharRange;
+typedef ImmutableNFANodeBuilder&lt;char, uint64_t&gt; ImmutableCharNFANodeBuilder;
</ins><span class="cx"> 
</span><del>-// The NFA provides a way to build a NFA graph with characters or epsilon as transitions.
-// The nodes are accessed through an identifier.
-class NFA {
-public:
-    WEBCORE_EXPORT NFA();
-    unsigned root() const { return m_root; }
-    unsigned createNode();
-
-    void addTransition(unsigned from, unsigned to, char character);
-    void addEpsilonTransition(unsigned from, unsigned to);
-    void addTransitionsOnAnyCharacter(unsigned from, unsigned to);
-    void setActions(unsigned node, const ActionList&amp; finalActions);
-
-    unsigned graphSize() const;
-    void restoreToGraphSize(unsigned);
-
</del><ins>+struct NFA : public ImmutableNFA&lt;char, uint64_t&gt; {
</ins><span class="cx"> #if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
</span><del>-    void addRuleId(unsigned node, const ActionList&amp; ruleIds);
-
</del><span class="cx">     void debugPrintDot() const;
</span><del>-#else
-    void addRuleId(unsigned, uint64_t) { }
</del><span class="cx"> #endif
</span><del>-#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
-    size_t memoryUsed() const;
-#endif
-
-private:
-    friend class NFAToDFA;
-
-    static const unsigned epsilonTransitionCharacter = 256;
-
-    Vector&lt;NFANode&gt; m_nodes;
-    unsigned m_root;
</del><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsNFANodeh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/NFANode.h (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/NFANode.h        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/NFANode.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -45,7 +45,7 @@
</span><span class="cx"> 
</span><span class="cx"> class NFANode {
</span><span class="cx"> public:
</span><del>-    HashMap&lt;uint16_t, NFANodeIndexSet, DefaultHash&lt;uint16_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint16_t&gt;&gt; transitions;
</del><ins>+    NFANodeTransitions transitions;
</ins><span class="cx">     NFANodeIndexSet transitionsOnAnyCharacter;
</span><span class="cx"> 
</span><span class="cx">     ActionList finalRuleIds;
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsNFAToDFAcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/NFAToDFA.cpp (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/NFAToDFA.cpp        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/NFAToDFA.cpp        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -30,6 +30,8 @@
</span><span class="cx"> 
</span><span class="cx"> #include &quot;ContentExtensionsDebugging.h&quot;
</span><span class="cx"> #include &quot;DFANode.h&quot;
</span><ins>+#include &quot;ImmutableNFA.h&quot;
+#include &quot;MutableRangeList.h&quot;
</ins><span class="cx"> #include &quot;NFA.h&quot;
</span><span class="cx"> #include &lt;wtf/DataLog.h&gt;
</span><span class="cx"> #include &lt;wtf/HashMap.h&gt;
</span><span class="lines">@@ -39,53 +41,48 @@
</span><span class="cx"> 
</span><span class="cx"> namespace ContentExtensions {
</span><span class="cx"> 
</span><ins>+typedef MutableRange&lt;char, NFANodeIndexSet&gt; NFANodeRange;
+typedef MutableRangeList&lt;char, NFANodeIndexSet&gt; NFANodeRangeList;
+typedef MutableRangeList&lt;char, NFANodeIndexSet, 128&gt; PreallocatedNFANodeRangeList;
+
</ins><span class="cx"> // FIXME: set a better initial size.
</span><span class="cx"> // FIXME: include the hash inside NodeIdSet.
</span><span class="cx"> typedef NFANodeIndexSet NodeIdSet;
</span><span class="cx"> 
</span><del>-static inline void epsilonClosureExcludingSelf(const Vector&lt;NFANode&gt;&amp; nfaGraph, unsigned nodeId, unsigned epsilonTransitionCharacter, Vector&lt;unsigned&gt;&amp; output)
</del><ins>+static inline void epsilonClosureExcludingSelf(NFA&amp; nfa, unsigned nodeId, Vector&lt;unsigned&gt;&amp; output)
</ins><span class="cx"> {
</span><del>-    const auto&amp; transitions = nfaGraph[nodeId].transitions;
-    auto epsilonTransitionSlot = transitions.find(epsilonTransitionCharacter);
-
-    if (epsilonTransitionSlot == transitions.end())
-        return;
-
</del><span class="cx">     NodeIdSet closure({ nodeId });
</span><del>-    Vector&lt;unsigned, 64&gt; unprocessedNodes;
-    copyToVector(epsilonTransitionSlot-&gt;value, unprocessedNodes);
-    closure.add(unprocessedNodes.begin(), unprocessedNodes.end());
-    output = unprocessedNodes;
</del><ins>+    Vector&lt;unsigned, 64&gt; unprocessedNodes({ nodeId });
</ins><span class="cx"> 
</span><span class="cx">     do {
</span><span class="cx">         unsigned unprocessedNodeId = unprocessedNodes.takeLast();
</span><del>-        const NFANode&amp; node = nfaGraph[unprocessedNodeId];
-        auto epsilonTransitionSlot = node.transitions.find(epsilonTransitionCharacter);
-        if (epsilonTransitionSlot != node.transitions.end()) {
-            for (unsigned targetNodeId : epsilonTransitionSlot-&gt;value) {
-                auto addResult = closure.add(targetNodeId);
-                if (addResult.isNewEntry) {
-                    unprocessedNodes.append(targetNodeId);
-                    output.append(targetNodeId);
-                }
</del><ins>+        const auto&amp; node = nfa.nodes[unprocessedNodeId];
+
+        for (uint32_t epsilonTargetIndex = node.epsilonTransitionTargetsStart; epsilonTargetIndex &lt; node.epsilonTransitionTargetsEnd; ++epsilonTargetIndex) {
+            uint32_t targetNodeId = nfa.epsilonTransitionsTargets[epsilonTargetIndex];
+            auto addResult = closure.add(targetNodeId);
+            if (addResult.isNewEntry) {
+                unprocessedNodes.append(targetNodeId);
+                output.append(targetNodeId);
</ins><span class="cx">             }
</span><span class="cx">         }
</span><span class="cx">     } while (!unprocessedNodes.isEmpty());
</span><ins>+
+    output.shrinkToFit();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-static void resolveEpsilonClosures(Vector&lt;NFANode&gt;&amp; nfaGraph, unsigned epsilonTransitionCharacter, Vector&lt;Vector&lt;unsigned&gt;&gt;&amp; nfaNodeClosures)
</del><ins>+static void resolveEpsilonClosures(NFA&amp; nfa, Vector&lt;Vector&lt;unsigned&gt;&gt;&amp; nfaNodeClosures)
</ins><span class="cx"> {
</span><del>-    unsigned nfaGraphSize = nfaGraph.size();
</del><ins>+    unsigned nfaGraphSize = nfa.nodes.size();
</ins><span class="cx">     nfaNodeClosures.resize(nfaGraphSize);
</span><ins>+
</ins><span class="cx">     for (unsigned nodeId = 0; nodeId &lt; nfaGraphSize; ++nodeId)
</span><del>-        epsilonClosureExcludingSelf(nfaGraph, nodeId, epsilonTransitionCharacter, nfaNodeClosures[nodeId]);
</del><ins>+        epsilonClosureExcludingSelf(nfa, nodeId, nfaNodeClosures[nodeId]);
</ins><span class="cx"> 
</span><del>-    for (unsigned nodeId = 0; nodeId &lt; nfaGraphSize; ++nodeId) {
-        if (!nfaNodeClosures[nodeId].isEmpty()) {
-            bool epsilonTransitionIsRemoved = nfaGraph[nodeId].transitions.remove(epsilonTransitionCharacter);
-            ASSERT_UNUSED(epsilonTransitionIsRemoved, epsilonTransitionIsRemoved);
-        }
-    }
</del><ins>+    // Every nodes still point to that table, but we won't use it ever again.
+    // Clear it to get back the memory. That's not pretty but memory is important here, we have both
+    // graphs existing at the same time.
+    nfa.epsilonTransitionsTargets.clear();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> static ALWAYS_INLINE void extendSetWithClosure(const Vector&lt;Vector&lt;unsigned&gt;&gt;&amp; nfaNodeClosures, unsigned nodeId, NodeIdSet&amp; set)
</span><span class="lines">@@ -214,9 +211,9 @@
</span><span class="cx"> typedef HashSet&lt;std::unique_ptr&lt;UniqueNodeIdSet&gt;, UniqueNodeIdSetHash, UniqueNodeIdSetHashHashTraits&gt; UniqueNodeIdSetTable;
</span><span class="cx"> 
</span><span class="cx"> struct NodeIdSetToUniqueNodeIdSetSource {
</span><del>-    NodeIdSetToUniqueNodeIdSetSource(DFA&amp; dfa, const Vector&lt;NFANode&gt;&amp; nfaGraph, const NodeIdSet&amp; nodeIdSet)
</del><ins>+    NodeIdSetToUniqueNodeIdSetSource(DFA&amp; dfa, const NFA&amp; nfa, const NodeIdSet&amp; nodeIdSet)
</ins><span class="cx">         : dfa(dfa)
</span><del>-        , nfaGraph(nfaGraph)
</del><ins>+        , nfa(nfa)
</ins><span class="cx">         , nodeIdSet(nodeIdSet)
</span><span class="cx">     {
</span><span class="cx">         // The hashing operation must be independant of the nodeId.
</span><span class="lines">@@ -226,7 +223,7 @@
</span><span class="cx">         this-&gt;hash = DefaultHash&lt;unsigned&gt;::Hash::hash(hash);
</span><span class="cx">     }
</span><span class="cx">     DFA&amp; dfa;
</span><del>-    const Vector&lt;NFANode&gt;&amp; nfaGraph;
</del><ins>+    const NFA&amp; nfa;
</ins><span class="cx">     const NodeIdSet&amp; nodeIdSet;
</span><span class="cx">     unsigned hash;
</span><span class="cx"> };
</span><span class="lines">@@ -247,14 +244,13 @@
</span><span class="cx">         DFANode newDFANode;
</span><span class="cx"> 
</span><span class="cx">         HashSet&lt;uint64_t, DefaultHash&lt;uint64_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint64_t&gt;&gt; actions;
</span><ins>+
</ins><span class="cx">         for (unsigned nfaNodeId : source.nodeIdSet) {
</span><del>-            const NFANode&amp; nfaNode = source.nfaGraph[nfaNodeId];
-            actions.add(nfaNode.finalRuleIds.begin(), nfaNode.finalRuleIds.end());
-#if CONTENT_EXTENSIONS_STATE_MACHINE_DEBUGGING
-            newDFANode.correspondingNFANodes.append(nfaNodeId);
-#endif
</del><ins>+            const auto&amp; nfaNode = source.nfa.nodes[nfaNodeId];
+            for (unsigned actionIndex = nfaNode.actionStart; actionIndex &lt; nfaNode.actionEnd; ++actionIndex)
+                actions.add(source.nfa.actions[actionIndex]);
</ins><span class="cx">         }
</span><del>-        
</del><ins>+
</ins><span class="cx">         unsigned actionsStart = source.dfa.actions.size();
</span><span class="cx">         for (uint64_t action : actions)
</span><span class="cx">             source.dfa.actions.append(action);
</span><span class="lines">@@ -298,46 +294,118 @@
</span><span class="cx">     NodeIdSet m_targets[128];
</span><span class="cx"> };
</span><span class="cx"> 
</span><del>-static inline void populateTransitions(SetTransitions&amp; setTransitions, NodeIdSet&amp; setFallbackTransition, const UniqueNodeIdSetImpl&amp; sourceNodeSet, const Vector&lt;NFANode&gt;&amp; graph, const Vector&lt;Vector&lt;unsigned&gt;&gt;&amp; nfaNodeclosures)
</del><ins>+struct DataConverterWithEpsilonClosure {
+    const Vector&lt;Vector&lt;unsigned&gt;&gt;&amp; nfaNodeclosures;
+
+    template&lt;typename Iterable&gt;
+    NFANodeIndexSet convert(const Iterable&amp; iterable)
+    {
+        NFANodeIndexSet result;
+        for (unsigned nodeId : iterable) {
+            result.add(nodeId);
+            const Vector&lt;unsigned&gt;&amp; nodeClosure = nfaNodeclosures[nodeId];
+            result.add(nodeClosure.begin(), nodeClosure.end());
+        }
+        return result;
+    }
+
+    template&lt;typename Iterable&gt;
+    void extend(NFANodeIndexSet&amp; destination, const Iterable&amp; iterable)
+    {
+        for (unsigned nodeId : iterable) {
+            auto addResult = destination.add(nodeId);
+            if (addResult.isNewEntry) {
+                const Vector&lt;unsigned&gt;&amp; nodeClosure = nfaNodeclosures[nodeId];
+                destination.add(nodeClosure.begin(), nodeClosure.end());
+            }
+        }
+    }
+};
+
+static inline void createCombinedTransition(PreallocatedNFANodeRangeList&amp; combinedRangeList, const UniqueNodeIdSetImpl&amp; sourceNodeSet, const NFA&amp; immutableNFA, const Vector&lt;Vector&lt;unsigned&gt;&gt;&amp; nfaNodeclosures)
</ins><span class="cx"> {
</span><del>-    ASSERT(!graph.isEmpty());
-    ASSERT(setFallbackTransition.isEmpty());
-#if !ASSERT_DISABLED
-    for (const NodeIdSet&amp; set : setTransitions)
-        ASSERT(set.isEmpty());
-#endif
</del><ins>+    combinedRangeList.clear();
</ins><span class="cx"> 
</span><del>-    Vector&lt;unsigned, 8&gt; allFallbackTransitions;
</del><span class="cx">     const unsigned* buffer = sourceNodeSet.buffer();
</span><ins>+
+    DataConverterWithEpsilonClosure converter { nfaNodeclosures };
</ins><span class="cx">     for (unsigned i = 0; i &lt; sourceNodeSet.m_size; ++i) {
</span><span class="cx">         unsigned nodeId = buffer[i];
</span><del>-        const NFANode&amp; nfaSourceNode = graph[nodeId];
-        for (unsigned targetTransition : nfaSourceNode.transitionsOnAnyCharacter)
-            allFallbackTransitions.append(targetTransition);
</del><ins>+        auto transitions = immutableNFA.transitionsForNode(nodeId);
+        combinedRangeList.extend(transitions.begin(), transitions.end(), converter);
</ins><span class="cx">     }
</span><del>-    for (unsigned targetNodeId : allFallbackTransitions) {
-        auto addResult = setFallbackTransition.add(targetNodeId);
-        if (addResult.isNewEntry)
-            extendSetWithClosure(nfaNodeclosures, targetNodeId, setFallbackTransition);
</del><ins>+}
+
+static inline bool canUseFallbackTransition(const PreallocatedNFANodeRangeList&amp; rangeList)
+{
+    // Transitions can contains &quot;0&quot; if the expression has a end-of-line marker.
+    // Fallback transitions cover 1-127. We have to be careful with the first.
+
+    auto iterator = rangeList.begin();
+    auto end = rangeList.end();
+    if (iterator == end)
+        return false;
+
+    char lastSeenCharacter = 0;
+    if (!iterator-&gt;first) {
+        lastSeenCharacter = iterator-&gt;last;
+        if (lastSeenCharacter == 127)
+            return true;
+        ++iterator;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    for (unsigned i = 0; i &lt; sourceNodeSet.m_size; ++i) {
-        unsigned nodeId = buffer[i];
-        for (const auto&amp; transitionSlot : graph[nodeId].transitions) {
-            NodeIdSet&amp; targetSet = setTransitions[transitionSlot.key];
-            for (unsigned targetNodId : transitionSlot.value) {
-                targetSet.add(targetNodId);
-                extendSetWithClosure(nfaNodeclosures, targetNodId, targetSet);
-            }
-            if (transitionSlot.key)
-                targetSet.add(setFallbackTransition.begin(), setFallbackTransition.end());
</del><ins>+    for (;iterator != end; ++iterator) {
+        ASSERT(iterator-&gt;first &gt; lastSeenCharacter);
+        if (iterator-&gt;first != lastSeenCharacter + 1)
+            return false;
+
+        if (iterator-&gt;last == 127)
+            return true;
+        lastSeenCharacter = iterator-&gt;last;
+    }
+    return false;
+}
+
+static inline uint32_t findBestFallbackTarget(const PreallocatedNFANodeRangeList&amp; rangeList, const Vector&lt;unsigned, 128&gt;&amp; rangeToTarget)
+{
+    ASSERT(canUseFallbackTransition(rangeList));
+
+    HashMap&lt;uint32_t, unsigned, DefaultHash&lt;uint32_t&gt;::Hash, WTF::UnsignedWithZeroKeyHashTraits&lt;uint32_t&gt;&gt; histogram;
+
+    unsigned rangeToTargetIndex = 0;
+    auto iterator = rangeList.begin();
+    auto end = rangeList.end();
+    ASSERT_WITH_MESSAGE(iterator != end, &quot;An empty range list cannot use a fallback transition.&quot;);
+
+    if (!iterator-&gt;first &amp;&amp; !iterator-&gt;last) {
+        ++iterator;
+        ++rangeToTargetIndex;
+    }
+    ASSERT_WITH_MESSAGE(iterator != end, &quot;An empty range list matching only zero cannot use a fallback transition.&quot;);
+
+    uint32_t bestTarget = rangeToTarget[rangeToTargetIndex];
+    unsigned bestTargetScore = !iterator-&gt;first ? iterator-&gt;size() - 1 : iterator-&gt;size();
+    histogram.add(bestTarget, bestTargetScore);
+    ++iterator;
+    ++rangeToTargetIndex;
+
+    for (;iterator != end; ++iterator, ++rangeToTargetIndex) {
+        unsigned rangeSize = iterator-&gt;size();
+        uint32_t target = rangeToTarget[rangeToTargetIndex];
+        auto addResult = histogram.add(target, rangeSize);
+        if (!addResult.isNewEntry)
+            addResult.iterator-&gt;value += rangeSize;
+        if (addResult.iterator-&gt;value &gt; bestTargetScore) {
+            bestTargetScore = addResult.iterator-&gt;value;
+            bestTarget = target;
</ins><span class="cx">         }
</span><span class="cx">     }
</span><ins>+    return bestTarget;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-static ALWAYS_INLINE unsigned getOrCreateDFANode(const NodeIdSet&amp; nfaNodeSet, const Vector&lt;NFANode&gt;&amp; nfaGraph, DFA&amp; dfa, UniqueNodeIdSetTable&amp; uniqueNodeIdSetTable, Vector&lt;UniqueNodeIdSetImpl*&gt;&amp; unprocessedNodes)
</del><ins>+static ALWAYS_INLINE unsigned getOrCreateDFANode(const NodeIdSet&amp; nfaNodeSet, const NFA&amp; nfa, DFA&amp; dfa, UniqueNodeIdSetTable&amp; uniqueNodeIdSetTable, Vector&lt;UniqueNodeIdSetImpl*&gt;&amp; unprocessedNodes)
</ins><span class="cx"> {
</span><del>-    NodeIdSetToUniqueNodeIdSetSource nodeIdSetToUniqueNodeIdSetSource(dfa, nfaGraph, nfaNodeSet);
</del><ins>+    NodeIdSetToUniqueNodeIdSetSource nodeIdSetToUniqueNodeIdSetSource(dfa, nfa, nfaNodeSet);
</ins><span class="cx">     auto uniqueNodeIdAddResult = uniqueNodeIdSetTable.add&lt;NodeIdSetToUniqueNodeIdSetTranslator&gt;(nodeIdSetToUniqueNodeIdSetSource);
</span><span class="cx">     if (uniqueNodeIdAddResult.isNewEntry)
</span><span class="cx">         unprocessedNodes.append(uniqueNodeIdAddResult.iterator-&gt;impl());
</span><span class="lines">@@ -347,60 +415,63 @@
</span><span class="cx"> 
</span><span class="cx"> DFA NFAToDFA::convert(NFA&amp; nfa)
</span><span class="cx"> {
</span><ins>+    Vector&lt;Vector&lt;unsigned&gt;&gt; nfaNodeClosures;
+    resolveEpsilonClosures(nfa, nfaNodeClosures);
+
</ins><span class="cx">     DFA dfa;
</span><del>-    Vector&lt;NFANode&gt;&amp; nfaGraph = nfa.m_nodes;
</del><span class="cx"> 
</span><del>-    Vector&lt;Vector&lt;unsigned&gt;&gt; nfaNodeClosures;
-    resolveEpsilonClosures(nfaGraph, NFA::epsilonTransitionCharacter, nfaNodeClosures);
-
</del><span class="cx">     NodeIdSet initialSet({ nfa.root() });
</span><span class="cx">     extendSetWithClosure(nfaNodeClosures, nfa.root(), initialSet);
</span><span class="cx"> 
</span><span class="cx">     UniqueNodeIdSetTable uniqueNodeIdSetTable;
</span><span class="cx"> 
</span><del>-    NodeIdSetToUniqueNodeIdSetSource initialNodeIdSetToUniqueNodeIdSetSource(dfa, nfaGraph, initialSet);
</del><ins>+    NodeIdSetToUniqueNodeIdSetSource initialNodeIdSetToUniqueNodeIdSetSource(dfa, nfa, initialSet);
</ins><span class="cx">     auto addResult = uniqueNodeIdSetTable.add&lt;NodeIdSetToUniqueNodeIdSetTranslator&gt;(initialNodeIdSetToUniqueNodeIdSetSource);
</span><span class="cx"> 
</span><span class="cx">     Vector&lt;UniqueNodeIdSetImpl*&gt; unprocessedNodes;
</span><span class="cx">     unprocessedNodes.append(addResult.iterator-&gt;impl());
</span><span class="cx"> 
</span><del>-    SetTransitions transitionsFromClosedSet;
-
</del><ins>+    PreallocatedNFANodeRangeList combinedRangeList;
+    Vector&lt;unsigned, 128&gt; rangeToTarget;
</ins><span class="cx">     do {
</span><span class="cx">         UniqueNodeIdSetImpl* uniqueNodeIdSetImpl = unprocessedNodes.takeLast();
</span><ins>+        createCombinedTransition(combinedRangeList, *uniqueNodeIdSetImpl, nfa, nfaNodeClosures);
</ins><span class="cx"> 
</span><del>-        unsigned dfaNodeId = uniqueNodeIdSetImpl-&gt;m_dfaNodeId;
-        NodeIdSet setFallbackTransition;
-        populateTransitions(transitionsFromClosedSet, setFallbackTransition, *uniqueNodeIdSetImpl, nfaGraph, nfaNodeClosures);
</del><ins>+        rangeToTarget.clear();
+        for (const NFANodeRange&amp; range : combinedRangeList) {
+            unsigned targetNodeId = getOrCreateDFANode(range.data, nfa, dfa, uniqueNodeIdSetTable, unprocessedNodes);
+            rangeToTarget.append(targetNodeId);
+        }
</ins><span class="cx"> 
</span><ins>+        bool useFallbackTransition = canUseFallbackTransition(combinedRangeList);
+        uint32_t preferedFallbackTarget = 0;
+        if (useFallbackTransition)
+            preferedFallbackTarget = findBestFallbackTarget(combinedRangeList, rangeToTarget);
+
</ins><span class="cx">         unsigned transitionsStart = dfa.transitionCharacters.size();
</span><del>-        ASSERT(dfa.transitionCharacters.size() == dfa.transitionDestinations.size());
-        for (unsigned key = 0; key &lt; transitionsFromClosedSet.size(); ++key) {
-            NodeIdSet&amp; targetNodeSet = transitionsFromClosedSet[key];
</del><ins>+        unsigned rangeToTargetIndex = 0;
</ins><span class="cx"> 
</span><del>-            if (targetNodeSet.isEmpty())
-                continue;
</del><ins>+        for (const NFANodeRange&amp; range : combinedRangeList) {
+            unsigned targetNodeId = rangeToTarget[rangeToTargetIndex];
</ins><span class="cx"> 
</span><del>-            unsigned targetNodeId = getOrCreateDFANode(targetNodeSet, nfaGraph, dfa, uniqueNodeIdSetTable, unprocessedNodes);
-            RELEASE_ASSERT(key &lt;= 127);
-            dfa.transitionCharacters.append(static_cast&lt;uint8_t&gt;(key));
-            dfa.transitionDestinations.append(targetNodeId);
-
-            targetNodeSet.clear();
</del><ins>+            if (!(useFallbackTransition &amp;&amp; targetNodeId == preferedFallbackTarget)) {
+                for (unsigned i = range.first; i &lt;= static_cast&lt;unsigned&gt;(range.last); ++i) {
+                    dfa.transitionCharacters.append(static_cast&lt;uint8_t&gt;(i));
+                    dfa.transitionDestinations.append(targetNodeId);
+                }
+            }
+            ++rangeToTargetIndex;
</ins><span class="cx">         }
</span><span class="cx">         unsigned transitionsEnd = dfa.transitionCharacters.size();
</span><del>-        ASSERT(dfa.transitionCharacters.size() == dfa.transitionDestinations.size());
</del><span class="cx">         unsigned transitionsLength = transitionsEnd - transitionsStart;
</span><del>-        RELEASE_ASSERT(transitionsLength &lt;= 127);
-        dfa.nodes[dfaNodeId].setTransitions(transitionsStart, static_cast&lt;uint8_t&gt;(transitionsLength));
-        
-        if (!setFallbackTransition.isEmpty()) {
-            unsigned targetNodeId = getOrCreateDFANode(setFallbackTransition, nfaGraph, dfa, uniqueNodeIdSetTable, unprocessedNodes);
-            DFANode&amp; dfaSourceNode = dfa.nodes[dfaNodeId];
-            dfaSourceNode.addFallbackTransition(dfa, targetNodeId);
-        }
</del><ins>+        unsigned dfaNodeId = uniqueNodeIdSetImpl-&gt;m_dfaNodeId;
+        DFANode&amp; dfaSourceNode = dfa.nodes[dfaNodeId];
+        ASSERT_WITH_MESSAGE(transitionsLength &lt; 127, &quot;Transitions covering the entire alphabet should use a fallback transition&quot;);
+        dfaSourceNode.setTransitions(transitionsStart, static_cast&lt;uint8_t&gt;(transitionsLength));
+
+        if (useFallbackTransition)
+            dfaSourceNode.addFallbackTransition(dfa, preferedFallbackTarget);
</ins><span class="cx">     } while (!unprocessedNodes.isEmpty());
</span><del>-
</del><span class="cx">     return dfa;
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsNFAToDFAh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/NFAToDFA.h (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/NFAToDFA.h        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/NFAToDFA.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -34,7 +34,7 @@
</span><span class="cx"> 
</span><span class="cx"> namespace ContentExtensions {
</span><span class="cx"> 
</span><del>-class NFA;
</del><ins>+struct NFA;
</ins><span class="cx"> 
</span><span class="cx"> // NFAToDFA provides a way to build a DFA corresponding to a NFA.
</span><span class="cx"> class NFAToDFA {
</span></span></pre></div>
<a id="trunkSourceWebCorecontentextensionsTermh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/contentextensions/Term.h (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/contentextensions/Term.h        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Source/WebCore/contentextensions/Term.h        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -86,7 +86,7 @@
</span><span class="cx"> 
</span><span class="cx">     void quantify(const AtomQuantifier&amp;);
</span><span class="cx"> 
</span><del>-    unsigned generateGraph(NFA&amp;, unsigned start, const ActionList&amp; finalActions) const;
</del><ins>+    ImmutableCharNFANodeBuilder generateGraph(NFA&amp;, ImmutableCharNFANodeBuilder&amp; start, const ActionList&amp; finalActions) const;
</ins><span class="cx"> 
</span><span class="cx">     bool isEndOfLineAssertion() const;
</span><span class="cx"> 
</span><span class="lines">@@ -118,7 +118,7 @@
</span><span class="cx">     // The return value can be false for a group equivalent to a universal transition.
</span><span class="cx">     bool isUniversalTransition() const;
</span><span class="cx"> 
</span><del>-    unsigned generateSubgraphForAtom(NFA&amp;, unsigned source) const;
</del><ins>+    ImmutableCharNFANodeBuilder generateSubgraphForAtom(NFA&amp;, ImmutableCharNFANodeBuilder&amp; source) const;
</ins><span class="cx"> 
</span><span class="cx">     void destroy();
</span><span class="cx"> 
</span><span class="lines">@@ -286,7 +286,7 @@
</span><span class="cx">     : m_termType(TermType::CharacterSet)
</span><span class="cx"> {
</span><span class="cx">     new (NotNull, &amp;m_atomData.characterSet) CharacterSet();
</span><del>-    for (UChar i = 0; i &lt; 128; ++i)
</del><ins>+    for (UChar i = 1; i &lt; 128; ++i)
</ins><span class="cx">         m_atomData.characterSet.set(i);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -395,11 +395,11 @@
</span><span class="cx">     m_quantifier = quantifier;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-inline unsigned Term::Term::generateGraph(NFA&amp; nfa, unsigned start, const ActionList&amp; finalActions) const
</del><ins>+inline ImmutableCharNFANodeBuilder Term::generateGraph(NFA&amp; nfa, ImmutableCharNFANodeBuilder&amp; start, const ActionList&amp; finalActions) const
</ins><span class="cx"> {
</span><span class="cx">     ASSERT(isValid());
</span><span class="cx"> 
</span><del>-    unsigned newEnd;
</del><ins>+    ImmutableCharNFANodeBuilder newEnd;
</ins><span class="cx"> 
</span><span class="cx">     switch (m_quantifier) {
</span><span class="cx">     case AtomQuantifier::One: {
</span><span class="lines">@@ -408,36 +408,36 @@
</span><span class="cx">     }
</span><span class="cx">     case AtomQuantifier::ZeroOrOne: {
</span><span class="cx">         newEnd = generateSubgraphForAtom(nfa, start);
</span><del>-        nfa.addEpsilonTransition(start, newEnd);
</del><ins>+        start.addEpsilonTransition(newEnd);
</ins><span class="cx">         break;
</span><span class="cx">     }
</span><span class="cx">     case AtomQuantifier::ZeroOrMore: {
</span><del>-        unsigned repeatStart = nfa.createNode();
-        nfa.addEpsilonTransition(start, repeatStart);
</del><ins>+        ImmutableCharNFANodeBuilder repeatStart(nfa);
+        start.addEpsilonTransition(repeatStart);
</ins><span class="cx"> 
</span><del>-        unsigned repeatEnd = generateSubgraphForAtom(nfa, repeatStart);
-        nfa.addEpsilonTransition(repeatEnd, repeatStart);
</del><ins>+        ImmutableCharNFANodeBuilder repeatEnd = generateSubgraphForAtom(nfa, repeatStart);
+        repeatEnd.addEpsilonTransition(repeatStart);
</ins><span class="cx"> 
</span><del>-        unsigned kleenEnd = nfa.createNode();
-        nfa.addEpsilonTransition(repeatEnd, kleenEnd);
-        nfa.addEpsilonTransition(start, kleenEnd);
-        newEnd = kleenEnd;
</del><ins>+        ImmutableCharNFANodeBuilder kleenEnd(nfa);
+        repeatEnd.addEpsilonTransition(kleenEnd);
+        start.addEpsilonTransition(kleenEnd);
+        newEnd = WTF::move(kleenEnd);
</ins><span class="cx">         break;
</span><span class="cx">     }
</span><span class="cx">     case AtomQuantifier::OneOrMore: {
</span><del>-        unsigned repeatStart = nfa.createNode();
-        nfa.addEpsilonTransition(start, repeatStart);
</del><ins>+        ImmutableCharNFANodeBuilder repeatStart(nfa);
+        start.addEpsilonTransition(repeatStart);
</ins><span class="cx"> 
</span><del>-        unsigned repeatEnd = generateSubgraphForAtom(nfa, repeatStart);
-        nfa.addEpsilonTransition(repeatEnd, repeatStart);
</del><ins>+        ImmutableCharNFANodeBuilder repeatEnd = generateSubgraphForAtom(nfa, repeatStart);
+        repeatEnd.addEpsilonTransition(repeatStart);
</ins><span class="cx"> 
</span><del>-        unsigned afterRepeat = nfa.createNode();
-        nfa.addEpsilonTransition(repeatEnd, afterRepeat);
-        newEnd = afterRepeat;
</del><ins>+        ImmutableCharNFANodeBuilder afterRepeat(nfa);
+        repeatEnd.addEpsilonTransition(afterRepeat);
+        newEnd = WTF::move(afterRepeat);
</ins><span class="cx">         break;
</span><span class="cx">     }
</span><span class="cx">     }
</span><del>-    nfa.setActions(newEnd, finalActions);
</del><ins>+    newEnd.setActions(finalActions.begin(), finalActions.end());
</ins><span class="cx">     return newEnd;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -612,36 +612,61 @@
</span><span class="cx">     return false;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-inline unsigned Term::generateSubgraphForAtom(NFA&amp; nfa, unsigned source) const
</del><ins>+inline ImmutableCharNFANodeBuilder Term::generateSubgraphForAtom(NFA&amp; nfa, ImmutableCharNFANodeBuilder&amp; source) const
</ins><span class="cx"> {
</span><span class="cx">     switch (m_termType) {
</span><span class="cx">     case TermType::Empty:
</span><span class="cx">     case TermType::Deleted:
</span><span class="cx">         ASSERT_NOT_REACHED();
</span><del>-        return -1;
</del><ins>+        return ImmutableCharNFANodeBuilder();
</ins><span class="cx">     case TermType::CharacterSet: {
</span><del>-        unsigned target = nfa.createNode();
-        if (isUniversalTransition())
-            nfa.addTransitionsOnAnyCharacter(source, target);
-        else {
-            if (!m_atomData.characterSet.inverted()) {
-                for (UChar i = 0; i &lt; 128; ++i) {
-                    if (m_atomData.characterSet.get(i))
-                        nfa.addTransition(source, target, static_cast&lt;char&gt;(i));
-                }
-            } else {
-                for (UChar i = 1; i &lt; 128; ++i) {
-                    if (!m_atomData.characterSet.get(i))
-                        nfa.addTransition(source, target, static_cast&lt;char&gt;(i));
-                }
</del><ins>+        ImmutableCharNFANodeBuilder newNode(nfa);
+        if (!m_atomData.characterSet.inverted()) {
+            UChar i = 0;
+            while (true) {
+                while (i &lt; 128 &amp;&amp; !m_atomData.characterSet.get(i))
+                    ++i;
+                if (i == 128)
+                    break;
+
+                UChar start = i;
+                ++i;
+                while (i &lt; 128 &amp;&amp; m_atomData.characterSet.get(i))
+                    ++i;
+                source.addTransition(start, i - 1, newNode);
</ins><span class="cx">             }
</span><ins>+        } else {
+            UChar i = 1;
+            while (true) {
+                while (i &lt; 128 &amp;&amp; m_atomData.characterSet.get(i))
+                    ++i;
+                if (i == 128)
+                    break;
+
+                UChar start = i;
+                ++i;
+                while (i &lt; 128 &amp;&amp; !m_atomData.characterSet.get(i))
+                    ++i;
+                source.addTransition(start, i - 1, newNode);
+            }
</ins><span class="cx">         }
</span><del>-        return target;
</del><ins>+        return newNode;
</ins><span class="cx">     }
</span><span class="cx">     case TermType::Group: {
</span><del>-        unsigned lastTarget = source;
-        for (const Term&amp; term : m_atomData.group.terms)
-            lastTarget = term.generateGraph(nfa, lastTarget, ActionList());
</del><ins>+        if (m_atomData.group.terms.isEmpty()) {
+            // FIXME: any kind of empty term could be avoided in the parser. This case should turned into an assertion.
+            ImmutableCharNFANodeBuilder newNode(nfa);
+            source.addEpsilonTransition(newNode);
+            return newNode;
+        }
+
+        ImmutableCharNFANodeBuilder lastTarget = m_atomData.group.terms.first().generateGraph(nfa, source, ActionList());
+        for (unsigned i = 1; i &lt; m_atomData.group.terms.size(); ++i) {
+            const Term&amp; currentTerm = m_atomData.group.terms[i];
+            ImmutableCharNFANodeBuilder newNode = currentTerm.generateGraph(nfa, lastTarget, ActionList());
+            lastTarget = WTF::move(newNode);
+        }
+
</ins><span class="cx">         return lastTarget;
</span><span class="cx">     }
</span><span class="cx">     }
</span></span></pre></div>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Tools/ChangeLog        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -1,3 +1,13 @@
</span><ins>+2015-06-29  Benjamin Poulain  &lt;benjamin@webkit.org&gt;
+
+        Make the NFA transitions range-based
+        https://bugs.webkit.org/show_bug.cgi?id=146338
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp:
+        Test some new interesting cases.
+
</ins><span class="cx"> 2015-06-28  Dan Bernstein  &lt;mitz@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [Xcode] Use the same environment for command-line and IDE builds
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebCoreContentExtensionscpp"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp (186078 => 186079)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp        2015-06-29 19:12:29 UTC (rev 186078)
+++ trunk/Tools/TestWebKitAPI/Tests/WebCore/ContentExtensions.cpp        2015-06-29 19:40:12 UTC (rev 186079)
</span><span class="lines">@@ -304,6 +304,38 @@
</span><span class="cx">     testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/fobar&quot;), { });
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+TEST_F(ContentExtensionTest, EmptyGroups)
+{
+    auto backend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^http://webkit\\\\.org/foo()bar\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^http://webkit\\\\.org/((me)()(too))\&quot;}}]&quot;);
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foo&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/bar&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foobar&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/me&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/too&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/metoo&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foome&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foomebar&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/mefoo&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/mefootoo&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, QuantifiedEmptyGroups)
+{
+    auto backend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^http://webkit\\\\.org/foo()+bar\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^http://webkit\\\\.org/(()*()?(target)()+)\&quot;}}]&quot;);
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foo&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/bar&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foobar&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/me&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/too&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/target&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foome&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foomebar&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/mefoo&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/mefootoo&quot;), { });
+}
+
</ins><span class="cx"> TEST_F(ContentExtensionTest, MatchPastEndOfString)
</span><span class="cx"> {
</span><span class="cx">     auto backend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;.+\&quot;}}]&quot;);
</span><span class="lines">@@ -359,6 +391,16 @@
</span><span class="cx">     testRequest(backend, mainDocumentRequest(&quot;http://webkit.org/foobarY&quot;), { });
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+TEST_F(ContentExtensionTest, DotDoesNotIncludeEndOfLine)
+{
+    auto backend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;https://webkit\\\\.org/.\&quot;}}]&quot;);
+
+    testRequest(backend, mainDocumentRequest(&quot;https://webkit.org/foobar&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest(&quot;https://webkit.org/&quot;), { });
+    testRequest(backend, mainDocumentRequest(&quot;https://webkit.org/A&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(backend, mainDocumentRequest(&quot;https://webkit.org/z&quot;), { ContentExtensions::ActionType::BlockLoad });
+}
+
</ins><span class="cx"> TEST_F(ContentExtensionTest, PrefixInfixSuffixExactMatch)
</span><span class="cx"> {
</span><span class="cx">     auto backend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;infix\&quot;}},&quot;
</span><span class="lines">@@ -1368,4 +1410,524 @@
</span><span class="cx">     testRequest(backend, mainDocumentRequest(&quot;bbbb://www.webkit.org/&quot;), { });
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+// *** We have the following ranges intersections: ***
+// Full overlap.
+// 1)
+// A: |-----|
+// B:  |---|
+// 2)
+// A: |-----|
+// B:    |
+// 3)
+// A:  |---|
+// B: |-----|
+// 4)
+// A:    |
+// B: |-----|
+// One edge in common
+// 5)
+// A: |-|
+// B:   |-|
+// 6)
+// A: |
+// B: |-|
+// 7)
+// A: |-|
+// B:   |
+// 8)
+// A:   |-|
+// B: |-|
+// 9)
+// A:   |
+// B: |-|
+// 10)
+// A: |-|
+// B: |
+// B overlap on the left side of A.
+// 11)
+// A:   |---|
+// B: |---|
+// 12)
+// A:   |---|
+// B: |-----|
+// A overlap on the left side of B
+// 13)
+// A: |---|
+// B:   |---|
+// 14)
+// A: |-----|
+// B:   |---|
+// Equal ranges
+// 15)
+// A: |---|
+// B: |---|
+// 16)
+// A: |
+// B: |
+
+TEST_F(ContentExtensionTest, RangeOverlapCase1)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-m]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;ignore-previous-rules\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[c-e]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { }, true);
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { }, true);
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { }, true);
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;m://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;n://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-m]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;ignore-previous-rules\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[c-e]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.b.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { }, true);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { }, true);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { }, true);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.m.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.n.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase2)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-m]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;ignore-previous-rules\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^b\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { }, true);
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;m://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;n://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-m]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;ignore-previous-rules\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;l\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.k.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.l.xxx/&quot;), { }, true);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.m.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.n.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase3)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[b-d]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-m]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;m://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;n://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[b-d]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-m]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.b.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.m.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.n.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase4)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^l\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-m]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;k://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;l://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;m://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;n://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;l\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-m]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.k.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.l.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.m.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.n.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase5)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-e]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[e-h]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;i://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-e]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[e-h]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.i.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase6)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^e\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[e-h]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;i://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;e\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[e-h]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.i.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase7)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-e]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^e\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-e]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;e\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase8)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[e-h]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-e]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;i://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[e-h]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-e]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.i.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase9)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^e\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[a-e]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;e\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[a-e]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase10)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[e-h]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^e\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;i://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[e-h]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;e\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.i.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase11)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[e-g]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[d-f]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;g://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[e-g]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[d-f]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.g.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { });
+}
+
+
+TEST_F(ContentExtensionTest, RangeOverlapCase12)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[e-g]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[d-g]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;g://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[e-g]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[d-g]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.g.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase13)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[b-d]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[c-e]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[b-d]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[c-e]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.b.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase14)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[b-e]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[c-e]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;h://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[b-e]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[c-e]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.b.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.h.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase15)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[c-f]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^[c-f]\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;f://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;g://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[c-f]\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[c-f]\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.b.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.f.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.g.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, RangeOverlapCase16)
+{
+    auto matchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^c\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;^c\&quot;}}]&quot;);
+
+    testRequest(matchBackend, mainDocumentRequest(&quot;a://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;b://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;c://www.webkit.org/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(matchBackend, mainDocumentRequest(&quot;d://www.webkit.org/&quot;), { });
+    testRequest(matchBackend, mainDocumentRequest(&quot;e://www.webkit.org/&quot;), { });
+
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;c\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;c\&quot;}}]&quot;);
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.a.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.b.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.c.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.d.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.e.xxx/&quot;), { });
+}
+
+TEST_F(ContentExtensionTest, QuantifiedOneOrMoreRangesCase11And13)
+{
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[c-e]+[g-i]+YYY\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[b-d]+[h-j]+YYY\&quot;}}]&quot;);
+
+    // The interesting ranges only match between 'b' and 'k', plus a gap in 'f'.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.aayyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.abyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.acyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.adyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.aeyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.afyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.agyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ahyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.aiyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ajyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.akyyy.xxx/&quot;), { });
+
+    // 'b' is the first character of the second rule.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.byyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bayyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bbyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bcyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bdyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.beyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bfyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bgyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bhyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.biyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bkyyy.xxx/&quot;), { });
+
+    // 'c' is the first character of the first rule, and it overlaps the first character of the second rule.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cayyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cbyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ccyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cdyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ceyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cfyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cgyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.chyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ciyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ckyyy.xxx/&quot;), { });
+
+    // 'd' is in the first range of both rule. This series cover overlaps between the two rules.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dgyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddgyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddhyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddhhyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.degyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dehyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dfgyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dfhyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.djyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddjjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+}
+
+TEST_F(ContentExtensionTest, QuantifiedOneOrMoreRangesCase11And13InGroups)
+{
+    auto searchBackend = makeBackend(&quot;[{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;block\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;([c-e])+([g-i]+YYY)\&quot;}},&quot;
+        &quot;{\&quot;action\&quot;:{\&quot;type\&quot;:\&quot;css-display-none\&quot;,\&quot;selector\&quot;:\&quot;.hidden\&quot;},\&quot;trigger\&quot;:{\&quot;url-filter\&quot;:\&quot;[b-d]+[h-j]+YYY\&quot;}}]&quot;);
+
+    // The interesting ranges only match between 'b' and 'k', plus a gap in 'f'.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.aayyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.abyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.acyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.adyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.aeyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.afyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.agyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ahyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.aiyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ajyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.akyyy.xxx/&quot;), { });
+
+    // 'b' is the first character of the second rule.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.byyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bayyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bbyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bcyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bdyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.beyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bfyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bgyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bhyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.biyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.bkyyy.xxx/&quot;), { });
+
+    // 'c' is the first character of the first rule, and it overlaps the first character of the second rule.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cayyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cbyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ccyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cdyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ceyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cfyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cgyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.chyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ciyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.cjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ckyyy.xxx/&quot;), { });
+
+    // 'd' is in the first range of both rule. This series cover overlaps between the two rules.
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dgyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddgyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddhyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddhhyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector, ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.degyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dehyyy.xxx/&quot;), { ContentExtensions::ActionType::BlockLoad });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dfgyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.dfhyyy.xxx/&quot;), { });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.djyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+    testRequest(searchBackend, mainDocumentRequest(&quot;zzz://www.ddjjyyy.xxx/&quot;), { ContentExtensions::ActionType::CSSDisplayNoneSelector });
+}
+
</ins><span class="cx"> } // namespace TestWebKitAPI
</span></span></pre>
</div>
</div>

</body>
</html>