Bug 217886 - Optimize Range and StaticRange by having AbstractRange derive from SimpleRange
Summary: Optimize Range and StaticRange by having AbstractRange derive from SimpleRange
Status: ASSIGNED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Darin Adler
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2020-10-17 18:01 PDT by Darin Adler
Modified: 2022-08-02 13:57 PDT (History)
14 users (show)

See Also:


Attachments
Patch (55.41 KB, patch)
2020-10-17 18:24 PDT, Darin Adler
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (55.37 KB, patch)
2020-10-17 18:28 PDT, Darin Adler
no flags Details | Formatted Diff | Diff
Patch (68.06 KB, patch)
2020-10-18 10:18 PDT, Darin Adler
no flags Details | Formatted Diff | Diff
Patch (66.56 KB, patch)
2020-12-10 12:55 PST, Darin Adler
rniwa: review+
Details | Formatted Diff | Diff
Patch (73.28 KB, patch)
2020-12-15 11:21 PST, Darin Adler
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (74.62 KB, patch)
2020-12-15 12:35 PST, Darin Adler
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (78.32 KB, patch)
2020-12-15 18:11 PST, Darin Adler
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (78.31 KB, patch)
2020-12-16 10:06 PST, Darin Adler
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (77.14 KB, patch)
2020-12-17 12:04 PST, Darin Adler
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Darin Adler 2020-10-17 18:01:25 PDT
Optimize Range and StaticRange by having AbstractRange derive from SimpleRange
Comment 1 Darin Adler 2020-10-17 18:24:18 PDT
Created attachment 411690 [details]
Patch
Comment 2 Darin Adler 2020-10-17 18:28:23 PDT
Created attachment 411691 [details]
Patch
Comment 3 Ryosuke Niwa 2020-10-17 23:44:46 PDT
Comment on attachment 411691 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411691&action=review

> Source/WebCore/dom/AbstractRange.h:34
> +class AbstractRange : public RefCounted<AbstractRange>, protected SimpleRange {

This is a random tangential point but it occurs to me that SimpleRange having two BoundaryPoint isn't great for packing.
It might be better to instead store Ref<Node>, Ref<Node>, then two offsets in that order, and create BoundaryPoint object on fly when they're needed.
That would reduce the size of SimpleRange by 8 bytes in 64-bit architecture although I'm not certain if it's all that important.

> Source/WebCore/dom/AbstractRange.h:52
> +WEBCORE_EXPORT const SimpleRange& makeSimpleRange(const AbstractRange&);

I think MSVC doesn't like the fact this has WEBCORE_EXPORT but the above friend declaration doesn't.

> Source/WebCore/dom/Range.cpp:126
> +    boundary.container = WTFMove(container);
> +    boundary.offset = offset;

Why not just boundary = BoundaryPoint { WTFMove(container), offset } ?

> Source/WebCore/dom/Range.cpp:128
> +    if (!is_lteq(documentOrder(start, end)))

I commented in https://bugs.webkit.org/show_bug.cgi?id=215755 but this check is currently wrong.
We need to add step 4.1 in https://dom.spec.whatwg.org/#concept-range-bp-set and also check that the root nodes are equal.

> Source/WebCore/dom/Range.cpp:129
> +        otherBoundary(boundary) = boundary;

I think it's more readable for this to be passed in as an argument.
Also, don't we need to update childBefore(otherBoundary(boundary)) here?

> Source/WebCore/dom/Range.cpp:869
> +    if (boundary.container.ptr() == &container)
> +        boundary.offset = computeNodeIndexAfter(childBefore(boundary).get());

Don't we also need to invalidate m_childBeforeStart/m_childBeforeEnd?
Maybe we should make some kind of wrapper objects for start & end,
and have most of Range code work with that phantom object.
Otherwise, it's easy to forget those side data exists.

> Source/WebCore/dom/Range.cpp:939
>  void Range::textInserted(Node& text, unsigned offset, unsigned length)

It's silly that this takes Node& instead of CharacterData&.
Comment 4 Darin Adler 2020-10-18 08:43:03 PDT
Comment on attachment 411691 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411691&action=review

>> Source/WebCore/dom/Range.cpp:128
>> +    if (!is_lteq(documentOrder(start, end)))
> 
> I commented in https://bugs.webkit.org/show_bug.cgi?id=215755 but this check is currently wrong.
> We need to add step 4.1 in https://dom.spec.whatwg.org/#concept-range-bp-set and also check that the root nodes are equal.

Are you sure? When the root nodes are not equal the order is “unordered” and that will return false from “is_lteq”. Just to double check, is there a test failing because this is wrong or are you just saying it’s wrong because of code inspection?
Comment 5 Darin Adler 2020-10-18 08:50:33 PDT
Comment on attachment 411691 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411691&action=review

Thanks so much for looking this over! I have to find the mistake I made that’s causing all the tests to fail, but I will also take all your suggestions.

>> Source/WebCore/dom/AbstractRange.h:34
>> +class AbstractRange : public RefCounted<AbstractRange>, protected SimpleRange {
> 
> This is a random tangential point but it occurs to me that SimpleRange having two BoundaryPoint isn't great for packing.
> It might be better to instead store Ref<Node>, Ref<Node>, then two offsets in that order, and create BoundaryPoint object on fly when they're needed.
> That would reduce the size of SimpleRange by 8 bytes in 64-bit architecture although I'm not certain if it's all that important.

This would have a colossal effect on use of SimpleRange. “Create BoundaryPoint on the fly when needed” would change this from a struct to a class and change all uses. You could no longer take references to the boundary points or use assignment to change them.

I understand that we could make them smaller this way but it’s a big deal and deeply changes how the thing works.

>> Source/WebCore/dom/Range.cpp:126
>> +    boundary.offset = offset;
> 
> Why not just boundary = BoundaryPoint { WTFMove(container), offset } ?

Good idea, will do.

>> Source/WebCore/dom/Range.cpp:869
>> +        boundary.offset = computeNodeIndexAfter(childBefore(boundary).get());
> 
> Don't we also need to invalidate m_childBeforeStart/m_childBeforeEnd?
> Maybe we should make some kind of wrapper objects for start & end,
> and have most of Range code work with that phantom object.
> Otherwise, it's easy to forget those side data exists.

No, we don’t. This is the case where *other* children changed and we re-derive the offset *from* childBefore, which does not change. That was the same before; invalidateOffsef did the same thing.

>> Source/WebCore/dom/Range.cpp:939
>>  void Range::textInserted(Node& text, unsigned offset, unsigned length)
> 
> It's silly that this takes Node& instead of CharacterData&.

Yes I will change that.
Comment 6 Darin Adler 2020-10-18 09:24:59 PDT
Comment on attachment 411691 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411691&action=review

>> Source/WebCore/dom/Range.cpp:129
>> +        otherBoundary(boundary) = boundary;
> 
> I think it's more readable for this to be passed in as an argument.
> Also, don't we need to update childBefore(otherBoundary(boundary)) here?

Yes, we do need to update childBefore here.

When you say "more readable" to have this passed in, should we pass in all four of boundary, other, childBefore, and other childBefore? I didn’t want to do that because I thought it would be easier to know we had it correct if we didn’t pass things separately, but I could definitely do so. Here and in the other places too.
Comment 7 Darin Adler 2020-10-18 10:18:49 PDT
Created attachment 411708 [details]
Patch
Comment 8 Darin Adler 2020-10-18 11:59:33 PDT
OK, this is passing tests now, so ready to land once I have a review+. I considered all of Ryosuke’s comments, so those should be mostly resolved now.
Comment 9 Darin Adler 2020-10-18 12:00:06 PDT
Comment on attachment 411708 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411708&action=review

> Source/WebCore/ChangeLog:8
> +        * Headers.cmake: Removed BoundaryPoint.h.

Oops, this should say RangeBoundaryPoint.h. Code change fine, change log comment wrong.
Comment 10 Darin Adler 2020-10-18 12:06:03 PDT
Comment on attachment 411708 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411708&action=review

> Source/WebCore/dom/Range.cpp:947
> +        childBeforeBoundary = nullptr;

I can remove this line of code, because it’s guaranteed that the boundary contains was a text node in this case, which means it’s already null. This is the same reason that we can use the boundary.offset, knowing it’s a character offset. So I should remove this line of code.
Comment 11 Darin Adler 2020-10-18 12:07:06 PDT
Comment on attachment 411708 [details]
Patch

Setting commit-queue- to make sure I don’t forget to at least fix the small change log mistake and remove the one unnecessary line of code before landing. The two things I just mentioned in comments.
Comment 12 Darin Adler 2020-10-18 13:33:43 PDT
Comment on attachment 411708 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411708&action=review

>> Source/WebCore/dom/Range.cpp:947
>> +        childBeforeBoundary = nullptr;
> 
> I can remove this line of code, because it’s guaranteed that the boundary contains was a text node in this case, which means it’s already null. This is the same reason that we can use the boundary.offset, knowing it’s a character offset. So I should remove this line of code.

the boundary *container* was a text node
Comment 13 Darin Adler 2020-10-19 18:21:20 PDT
Comment on attachment 411708 [details]
Patch

The mac-debug-wk1 failures look real. Will fix.
Comment 14 Ryosuke Niwa 2020-10-19 21:16:43 PDT
https://ews-build.s3-us-west-2.amazonaws.com/macOS-Mojave-Debug-WK1-Tests-EWS/r411708-20704/results.html

fast/dom/non-numeric-values-numeric-parameters.html:
ASSERTION FAILED: is<Text>(oldNode.nextSibling())
./dom/Range.cpp(966) : void WebCore::Range::textNodeSplit(WebCore::BoundaryPoint &, RefPtr<WebCore::Node> &, WebCore::Text &)
1   0x10564ac89 WTFCrash
2   0x121aac56b WTFCrashWithInfo(int, char const*, char const*, int)
3   0x12515d0aa WebCore::Range::textNodeSplit(WebCore::BoundaryPoint&, WTF::RefPtr<WebCore::Node, WTF::DumbPtrTraits<WebCore::Node>, WTF::DefaultRefDerefTraits<WebCore::Node> >&, WebCore::Text&)
4   0x12515cff6 WebCore::Range::textNodeSplit(WebCore::Text&)
5   0x124f65478 WebCore::Document::textNodeSplit(WebCore::Text&)
6   0x1251cfa82 WebCore::Text::splitText(unsigned int)
7   0x1237fd243 WebCore::jsTextPrototypeFunction_splitTextBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSText*)
8   0x12376df79 long long WebCore::IDLOperation<WebCore::JSText>::call<&(WebCore::jsTextPrototypeFunction_splitTextBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSText*)), (WebCore::CastedThisErrorBehavior)0>(JSC::JSGlobalObject&, JSC::CallFrame&, char const*)
9   0x12376dc54 WebCore::jsTextPrototypeFunction_splitText(JSC::JSGlobalObject*, JSC::CallFrame*)
10  0x22b0f6601178
11  0x105c2e05c llint_entry
Comment 15 Ryosuke Niwa 2020-10-19 21:37:17 PDT
Comment on attachment 411708 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411708&action=review

> Source/WebCore/dom/Range.cpp:108
> -ExceptionOr<void> Range::setStart(Ref<Node>&& container, unsigned offset)
> +ExceptionOr<void> Range::setBoundary(BoundaryPoint& boundary, RefPtr<Node>& childBeforeBoundary, BoundaryPoint& otherBoundary, RefPtr<Node>& childBeforeOtherBoundary, Ref<Node>&& container, unsigned offset)

Hm... it sucks that we have to pass in 4 variables.

> Source/WebCore/dom/Range.cpp:114
> +    boundary = { WTFMove(container), offset };
> +    childBeforeBoundary = childNode.releaseReturnValue();

What if we added a helper struct like this:
struct BoundaryPointWithChildBeforeBoundary {
    BoundaryPoint& boundary;
    RefPtr<Node>& childBeforeBoundary;
};

> Source/WebCore/dom/Range.cpp:117
> +        otherBoundary = boundary;
> +        childBeforeOtherBoundary = childBeforeBoundary;

Then this could be one liner.

But maybe it's not really worth the extra complexity / code.

> Source/WebCore/dom/Range.cpp:214
> +    const BoundaryPoint* thisPoint;
> +    const BoundaryPoint* otherPoint;

Instead of computing this & other point, why don't we just call documentOrder in each case below?

> Source/WebCore/dom/Range.cpp:216
>      switch (how) {
>      case START_TO_START:

In fact, we could wrap this into a helper function so that we can just do:
switch (how) {
case START_TO_START:
    return documentOrder(start, sourceRange.start);
...
}

> Source/WebCore/dom/Range.cpp:258
> -static inline Node* highestAncestorUnderCommonRoot(Node* node, Node* commonRoot)
> +static inline RefPtr<Node> highestAncestorUnderCommonRoot(Node& node, Node& commonRoot)

Per rule we've come up with, we don't need to return a RefPtr here.
It's okay to return a raw pointer. It's the caller which has to store it in a Ref/RefPtr.
But it's okay to make this return Ref/RefPtr if we want to avoid repeating makeRefPtr in every call site.

>>> Source/WebCore/dom/Range.cpp:947
>>> +        childBeforeBoundary = nullptr;
>> 
>> I can remove this line of code, because it’s guaranteed that the boundary contains was a text node in this case, which means it’s already null. This is the same reason that we can use the boundary.offset, knowing it’s a character offset. So I should remove this line of code.
> 
> the boundary *container* was a text node

Maybe we should assert it instead?

> Source/WebCore/dom/Range.cpp:966
> +    ASSERT(is<Text>(oldNode.nextSibling()));

I think it's possible that oldNode doesn't have a parent here as evidenced by the assert in line 986 below.
In fact, all these assertions seem kind of bogus as it's always possible for mutation events to have re-arranged these nodes.

Document::textNodeSplit is called too late. We need to make the adjustment before we start firing mutation events.
Thats probably too big of a patch to do it as a part of this patch though.

> Source/WebCore/dom/Range.cpp:969
>          unsigned splitOffset = oldNode.length();

Can we assert here that childBeforeBoundary is null?
Comment 16 Radar WebKit Bug Importer 2020-10-24 18:02:16 PDT
<rdar://problem/70654513>
Comment 17 Darin Adler 2020-12-10 11:19:07 PST
Comment on attachment 411708 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=411708&action=review

>> Source/WebCore/dom/Range.cpp:108
>> +ExceptionOr<void> Range::setBoundary(BoundaryPoint& boundary, RefPtr<Node>& childBeforeBoundary, BoundaryPoint& otherBoundary, RefPtr<Node>& childBeforeOtherBoundary, Ref<Node>&& container, unsigned offset)
> 
> Hm... it sucks that we have to pass in 4 variables.

I did that to get rid of the the childBefore and otherBoundary helper functions. There are only two callers; I don’t think it’s so bad.

>> Source/WebCore/dom/Range.cpp:114
>> +    childBeforeBoundary = childNode.releaseReturnValue();
> 
> What if we added a helper struct like this:
> struct BoundaryPointWithChildBeforeBoundary {
>     BoundaryPoint& boundary;
>     RefPtr<Node>& childBeforeBoundary;
> };

I wouldn’t mind adding that.

>> Source/WebCore/dom/Range.cpp:117
>> +        childBeforeOtherBoundary = childBeforeBoundary;
> 
> Then this could be one liner.
> 
> But maybe it's not really worth the extra complexity / code.

No, it wouldn’t be a one-liner; can’t assign to a struct like that which contains references.

>> Source/WebCore/dom/Range.cpp:214
>> +    const BoundaryPoint* otherPoint;
> 
> Instead of computing this & other point, why don't we just call documentOrder in each case below?

Not sure it’s better, but happy to do that.

>> Source/WebCore/dom/Range.cpp:258
>> +static inline RefPtr<Node> highestAncestorUnderCommonRoot(Node& node, Node& commonRoot)
> 
> Per rule we've come up with, we don't need to return a RefPtr here.
> It's okay to return a raw pointer. It's the caller which has to store it in a Ref/RefPtr.
> But it's okay to make this return Ref/RefPtr if we want to avoid repeating makeRefPtr in every call site.

Thanks for clarifying. I will leave RefPtr here because the two places that use it would have to add a makeRefPtr otherwise.

> Source/WebCore/dom/Range.cpp:269
> +static inline RefPtr<Node> childOfCommonRootBeforeOffset(const BoundaryPoint& boundary, Node& commonRoot)

Same logic here.

>>>> Source/WebCore/dom/Range.cpp:947
>>>> +        childBeforeBoundary = nullptr;
>>> 
>>> I can remove this line of code, because it’s guaranteed that the boundary contains was a text node in this case, which means it’s already null. This is the same reason that we can use the boundary.offset, knowing it’s a character offset. So I should remove this line of code.
>> 
>> the boundary *container* was a text node
> 
> Maybe we should assert it instead?

OK.

>> Source/WebCore/dom/Range.cpp:966
>> +    ASSERT(is<Text>(oldNode.nextSibling()));
> 
> I think it's possible that oldNode doesn't have a parent here as evidenced by the assert in line 986 below.
> In fact, all these assertions seem kind of bogus as it's always possible for mutation events to have re-arranged these nodes.
> 
> Document::textNodeSplit is called too late. We need to make the adjustment before we start firing mutation events.
> Thats probably too big of a patch to do it as a part of this patch though.

Yes, this assertion is failing in the case where the old node doesn't have a parent.

As far as Document::textNodeSplit is concerned, it is *not* called too late; I researched this. It might look like it when reading the Text::splitText function, but the EventQueueScope object in that function prevents mutation events from being fired until the function is about to return, which is after textNodeSplit is called.

>> Source/WebCore/dom/Range.cpp:969
>>          unsigned splitOffset = oldNode.length();
> 
> Can we assert here that childBeforeBoundary is null?

Done.
Comment 18 Darin Adler 2020-12-10 12:55:41 PST
Created attachment 415915 [details]
Patch
Comment 19 Darin Adler 2020-12-11 13:30:27 PST
I made most of the changes Ryosuke asked for, and EWS bubbles are all green, so I think this is ready to go.
Comment 20 Ryosuke Niwa 2020-12-11 14:58:43 PST
Comment on attachment 415915 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=415915&action=review

> Source/WebCore/dom/AbstractRange.cpp:32
> +AbstractRange::AbstractRange(SimpleRange&& range)
> +    : SimpleRange(WTFMove(range))

It seems like we can just inline this now?

> Source/WebCore/dom/AbstractRange.cpp:38
> +    return range;

Ditto.

> Source/WebCore/dom/AbstractRange.cpp:41
> -SimpleRange makeSimpleRange(const Ref<AbstractRange>& range)
> +const SimpleRange& makeSimpleRange(const Ref<AbstractRange>& range)

Ditto.

> Source/WebCore/dom/Range.cpp:131
> +    return setBoundary(start, m_childBeforeStart, end, m_childBeforeEnd, WTFMove(container), offset);

Hm... the fact SimpleRange doesn't use m_start & m_end makes this code a bit harder to parse/read.
I wonder if we should make SimpleRange a member of AbstractRange instead so that we can access these as m_range.start & m_range.end?

> Source/WebCore/dom/Range.cpp:204
> -    auto startOrdering = treeOrder(nodeRange->start, makeBoundaryPoint(m_start));
> -    auto endOrdering = treeOrder(nodeRange->end, makeBoundaryPoint(m_end));
> +    auto startOrdering = treeOrder(nodeRange->start, start);
> +    auto endOrdering = treeOrder(nodeRange->end, end);

Nice!

> Source/WebCore/dom/Range.cpp:230
> -        thisPoint = &m_start;
> -        otherPoint = &sourceRange.m_start;
> -        break;
> +        return compareBoundaryPoints(start, sourceRange.start);

This is a much cleaner code too.

> Source/WebCore/dom/Range.cpp:930
> +    if (offset + length >= boundary.offset)
> +        boundary.offset = offset;
>      else
> -        boundary.setOffset(boundaryOffset - length);
> +        boundary.offset -= length;

Hm... I wish there was some kind of validation of these boundary offsets.
Maybe we can add makeBoundaryPoint and have that function validate the offset?

> Source/WebCore/dom/Range.cpp:944
> +        boundary = { *oldNode.node().previousSibling(), boundary.offset + offset };

Here again, I wish we can assert the the offset is valid.

> Source/WebCore/dom/Range.cpp:946
> +        boundary = { *oldNode.node().previousSibling(), offset };

Ditto.

> Source/WebCore/dom/Range.cpp:969
> -                boundary.set(*oldNode.nextSibling(), boundaryOffset - splitOffset, 0);
> +                boundary = { *oldNode.nextSibling(), boundary.offset - splitOffset };

Ditto.

> Source/WebCore/dom/Range.cpp:971
> -                boundary.setOffset(splitOffset);
> +                boundary.offset = splitOffset;

Ditto.

> Source/WebCore/dom/Range.cpp:1052
> +const SimpleRange& makeSimpleRange(const Ref<Range>& range)

It seems like we can inline this now?

> Source/WebCore/dom/Range.cpp:1057
>  Optional<SimpleRange> makeSimpleRange(const RefPtr<Range>& range)

Ditto.

> Source/WebCore/dom/StaticRange.cpp:78
> +const SimpleRange& makeSimpleRange(const Ref<StaticRange>& range)

Same comment about making these inline.

> Source/WebCore/dom/StaticRange.h:-49
> -    Node& startContainer() const final { return SimpleRange::startContainer(); }

Very nice!
Comment 21 Darin Adler 2020-12-11 16:28:14 PST
Comment on attachment 415915 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=415915&action=review

>> Source/WebCore/dom/AbstractRange.cpp:32
>> +    : SimpleRange(WTFMove(range))
> 
> It seems like we can just inline this now?

Maybe. I’d prefer to do more inlining in a separate pass, just add the inline keyword and move functions to headers. Need to be sure it’s worth it; don’t want to do it if I’m not sure it’s better.

>> Source/WebCore/dom/Range.cpp:131
>> +    return setBoundary(start, m_childBeforeStart, end, m_childBeforeEnd, WTFMove(container), offset);
> 
> Hm... the fact SimpleRange doesn't use m_start & m_end makes this code a bit harder to parse/read.
> I wonder if we should make SimpleRange a member of AbstractRange instead so that we can access these as m_range.start & m_range.end?

Yes, could do that.

>> Source/WebCore/dom/Range.cpp:930
>> +        boundary.offset -= length;
> 
> Hm... I wish there was some kind of validation of these boundary offsets.
> Maybe we can add makeBoundaryPoint and have that function validate the offset?

Could add a function that checks BoundaryPoint invariants and call it any time we modify one in code that’s supposed to only create "canonical" ones like this.

Many BoundaryPoints aren’t guaranteed to stay valid—they can contain any offset—but range endpoints are special, so maybe it should be a Range member function to check the invariants. We can call it at the ends of functions like these.
Comment 22 Darin Adler 2020-12-15 11:21:20 PST
Created attachment 416267 [details]
Patch
Comment 23 Darin Adler 2020-12-15 12:35:20 PST
Created attachment 416279 [details]
Patch
Comment 24 Darin Adler 2020-12-15 18:11:19 PST
Created attachment 416306 [details]
Patch
Comment 25 Darin Adler 2020-12-16 10:06:53 PST
Created attachment 416345 [details]
Patch
Comment 26 Darin Adler 2020-12-16 17:05:44 PST
Adding an invariant check has proven difficult. When a single composite operation like splitText happens, there are times during the processing when the invariants are not true, and they only become valid at the end. The callers don’t communicate this down through the levels to Range. I am tempted to not add it now and come back to this later.
Comment 27 Ryosuke Niwa 2020-12-16 17:36:03 PST
(In reply to Darin Adler from comment #26)
> Adding an invariant check has proven difficult. When a single composite
> operation like splitText happens, there are times during the processing when
> the invariants are not true, and they only become valid at the end. The
> callers don’t communicate this down through the levels to Range. I am
> tempted to not add it now and come back to this later.

I see. That's unfortunate but sounds too.
Comment 28 Darin Adler 2020-12-17 12:04:25 PST
Created attachment 416446 [details]
Patch
Comment 29 Brent Fulgham 2022-08-02 13:34:01 PDT
It's a shame this seems to have gotten dropped!
Comment 30 Darin Adler 2022-08-02 13:57:59 PDT
Not really sure this optimization will be super-successful. I dropped it because it was getting hard and I’m not sure it will pay off.