Bug 221072 - Synthesize range responses if needed in WebCoreNSURLSession
Summary: Synthesize range responses if needed in WebCoreNSURLSession
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Alex Christensen
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-01-27 18:27 PST by Alex Christensen
Modified: 2021-02-22 16:30 PST (History)
20 users (show)

See Also:


Attachments
Patch (21.12 KB, patch)
2021-01-27 18:35 PST, Alex Christensen
no flags Details | Formatted Diff | Diff
Patch (60.32 KB, patch)
2021-01-29 19:29 PST, Alex Christensen
no flags Details | Formatted Diff | Diff
Patch (67.89 KB, patch)
2021-02-06 19:41 PST, Alex Christensen
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (69.09 KB, patch)
2021-02-06 19:54 PST, Alex Christensen
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (69.12 KB, patch)
2021-02-06 20:05 PST, Alex Christensen
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (69.70 KB, patch)
2021-02-06 20:24 PST, Alex Christensen
no flags Details | Formatted Diff | Diff
Patch (69.55 KB, patch)
2021-02-09 12:04 PST, Alex Christensen
no flags Details | Formatted Diff | Diff
Patch (72.06 KB, patch)
2021-02-15 17:35 PST, Alex Christensen
no flags Details | Formatted Diff | Diff
Patch (72.21 KB, patch)
2021-02-15 17:43 PST, Alex Christensen
ews-feeder: commit-queue-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Alex Christensen 2021-01-27 18:27:13 PST
Synthesize range responses if needed in WebCoreNSURLSession
Comment 1 Alex Christensen 2021-01-27 18:35:05 PST
Created attachment 418606 [details]
Patch
Comment 2 Geoffrey Garen 2021-01-28 09:42:21 PST
Comment on attachment 418606 [details]
Patch

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

> Source/WebCore/platform/network/cocoa/WebCoreNSURLSession.mm:983
>  - (void)resource:(PlatformMediaResource&)resource receivedData:(const char*)data length:(int)length
>  {
> +    if (_buffer) {
> +        _buffer->append(data, length);
> +        return;
> +    }

Should we consider synthesizing a receivedData here too, in case there's been a range request within the received range, and loadFinished won't happen for a while?
Comment 3 Alex Christensen 2021-01-28 09:43:22 PST
Comment on attachment 418606 [details]
Patch

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

> Source/WebCore/ChangeLog:9
> +        This first iteration is awful.  Don't approve it.  It downloads the entire video anytime any range is requested.

This suggests there's some serious room for improvement.
Comment 4 Geoffrey Garen 2021-01-28 09:47:15 PST
Got it.
Comment 5 Alexey Proskuryakov 2021-01-28 18:54:15 PST
rdar://problem/20696449&71729210 :)
Comment 6 Alex Christensen 2021-01-29 19:29:46 PST
Created attachment 418800 [details]
Patch
Comment 7 Radar WebKit Bug Importer 2021-02-03 18:28:13 PST
<rdar://problem/73959759>
Comment 8 Alex Christensen 2021-02-06 19:41:28 PST
Created attachment 419520 [details]
Patch
Comment 9 Alex Christensen 2021-02-06 19:54:52 PST
Created attachment 419522 [details]
Patch
Comment 10 Alex Christensen 2021-02-06 20:05:52 PST
Created attachment 419523 [details]
Patch
Comment 11 Alex Christensen 2021-02-06 20:24:53 PST
Created attachment 419524 [details]
Patch
Comment 12 Sam Weinig 2021-02-07 10:04:39 PST
Neat! Does this fix the issue on bugs.webkit.org not working with video attachments???
Comment 13 Alex Christensen 2021-02-08 09:35:13 PST
Comment on attachment 419524 [details]
Patch

It does!
Comment 14 Geoffrey Garen 2021-02-08 15:27:35 PST
Comment on attachment 419524 [details]
Patch

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

r=me -- is the + 1 thing a bug?

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:127
> +            size_t bytesFromThisViewToDeliver = std::min(bufferView.size(), range.end() - byteIndex + 1);

Why + 1? (Typically, end - index is the right math for computing the size of a range.)

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:203
> +    class Client : public PlatformMediaResourceClient {

While it's true that we don't need access to this class declaration outside this scope, I think it would be more readable to declare it at file level. Maybe name it RangeResponsePlatformMediaResourceClient.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:210
> +        // These methods should have been called before changing the client to this.

We might as well make these release asserts, since they'll never be called.
Comment 15 Alex Christensen 2021-02-08 19:51:08 PST
(In reply to Geoffrey Garen from comment #14)
> Comment on attachment 419524 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=419524&action=review
> 
> r=me -- is the + 1 thing a bug?
> 
> > Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:127
> > +            size_t bytesFromThisViewToDeliver = std::min(bufferView.size(), range.end() - byteIndex + 1);
> 
> Why + 1? (Typically, end - index is the right math for computing the size of
> a range.)
With HTTP ranges, you need to go to and including the end.  For example, if you have a request with a header field like this:
Range: bytes=0-1
then you need to deliver two bytes, byte zero and byte one.  If you don't, then our media implementation detects improper HTTP range support and fails to play the video.
Comment 16 Alex Christensen 2021-02-09 12:04:27 PST
Comment on attachment 419524 [details]
Patch

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

>> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:203
>> +    class Client : public PlatformMediaResourceClient {
> 
> While it's true that we don't need access to this class declaration outside this scope, I think it would be more readable to declare it at file level. Maybe name it RangeResponsePlatformMediaResourceClient.

It needs to access private members of RangeResponseGenerator.  I'm going to make it a nested class outside the function to get all the benefits of scope, private access, and readability.

>> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:210
>> +        // These methods should have been called before changing the client to this.
> 
> We might as well make these release asserts, since they'll never be called.

Sure.
Comment 17 Alex Christensen 2021-02-09 12:04:37 PST
Created attachment 419750 [details]
Patch
Comment 18 Alex Christensen 2021-02-09 12:58:23 PST
http://trac.webkit.org/r272603
Comment 19 Alex Christensen 2021-02-09 12:59:12 PST
This was also rdar://problem/73575468
Comment 20 Darin Adler 2021-02-11 10:05:53 PST
Comment on attachment 419750 [details]
Patch

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

Apparently landed already, but I have a few coding style comments.

> Source/WebCore/platform/network/ParsedRequestRange.cpp:33
> +Optional<ParsedRequestRange> ParsedRequestRange::parse(const String& input)

This should take a StringView. This will naturally make the implementation more efficient.

> Source/WebCore/platform/network/ParsedRequestRange.cpp:46
> +    // FIXME: Add StringView.toUInt64Strict to avoid these unnecessary String copies.

If we don’t want to put the StringView changes into the same patch, it’s still straightforward to avoid the unnecessary string copies by calling using an if (is8Bit()) call and calling charactersToUInt64Strict directly, without adding to StringView. I would prefer that. Or you can even put the helper function in this file and just have the FIXME be about moving the function from this file to StringView.h/cpp.

> Source/WebCore/platform/network/ParsedRequestRange.h:32
> +class ParsedRequestRange {

Seems to me this it would be nicer to have this be a struct rather than a class. In contexts where we want to emphasize that we don’t want to modify begin and end, we can use const. Rather than having a class where the const-ness is built in.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.h:50
> +    class MediaResourceClient;

Just want to double check that this needs to be a member. Can just be in the .cpp file, but I think being a member gives it access to private stuff. Leaving it out of the header is nicer for future refactoring, so want to confirm it’s worthwhile to have it here.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.h:56
> +    HashMap<URL, std::unique_ptr<Data>> m_map;

Should consider whether we want to use URLs or URL strings to key this map. Is it important to use the extra memory to keep the parsed URL character offsets in the map? If not, I don’t see much downside to key based on the URL strings instead of the actual URL objects. But maybe I am missing some advantage that is worth the considerable extra memory use.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:43
> +    WTF_MAKE_FAST_ALLOCATED;
> +public:

We have a WTF_MAKE_STRUCT_FAST_ALLOCATED macro that avoids the need for this awkward "public".

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:49
> +    struct TaskData : public CanMakeWeakPtr<TaskData> {

I suggest omitting public when specifying base classes for structs; they are public by default. For classes we tend to be explicit about both public and private even though private is the default, but I think that’s because public inheritance is *so* much more common.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:51
> +        WTF_MAKE_FAST_ALLOCATED;
> +    public:

Here too.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:53
> +            : range(WTFMove(range))

I don’t think we need to use move semantics for a struct with two integers in it.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:154
> +    // FIXME: ResourceResponseBase::expectedContentLength() should return Optional<size_t> instead of us doing this check here.

I agree with this because NSURLResponseUnknownLength is a platform-specific sentinel value that should not make its way into platform-independent code like ResourceResponseBase.

> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:198
> +        RELEASE_ASSERT_NOT_REACHED();

I’d love to find an adjustment we could make to design this out. I think it’s the classic Liskov Substitution Principle problem.

> Source/WebCore/platform/network/cocoa/WebCoreNSURLSession.h:154
> +@interface WebCoreNSURLSessionDataTask (WebKitInternal)

Does this belong in a different header? Is this exposing internals of NSURLSession? If so, I think it should go into an "SPI" header, even though SPI is not quite the right term.

Why the category name WebKitInternal? What does that mean exactly; for use in WebKit internally, or defined by WebKit for user internally? Maybe omitting the name is better?

> Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaLoading.mm:126
> +        "video.autoplay = 1;"

I’d leave out the spaces here to match the lines above.

> Tools/TestWebKitAPI/cocoa/HTTPServer.mm:215
> +static void appendToVector(Vector<uint8_t>& vector, const String& string)

I think this function needs UTF8 in its name.

> Tools/TestWebKitAPI/cocoa/HTTPServer.mm:217
> +    auto utf8 = string.utf8();

Very unfortunate to make so many copies.
Comment 21 Alex Christensen 2021-02-11 10:49:39 PST
Comment on attachment 419750 [details]
Patch

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

>> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.h:50
>> +    class MediaResourceClient;
> 
> Just want to double check that this needs to be a member. Can just be in the .cpp file, but I think being a member gives it access to private stuff. Leaving it out of the header is nicer for future refactoring, so want to confirm it’s worthwhile to have it here.

It needs access to RangeResponseGenerator's private members, and I like the scoping of nested classes here.

>> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.h:56
>> +    HashMap<URL, std::unique_ptr<Data>> m_map;
> 
> Should consider whether we want to use URLs or URL strings to key this map. Is it important to use the extra memory to keep the parsed URL character offsets in the map? If not, I don’t see much downside to key based on the URL strings instead of the actual URL objects. But maybe I am missing some advantage that is worth the considerable extra memory use.

URL strings would save memory.  That's true.

>> Source/WebCore/platform/network/cocoa/RangeResponseGenerator.mm:43
>> +public:
> 
> We have a WTF_MAKE_STRUCT_FAST_ALLOCATED macro that avoids the need for this awkward "public".

That's exactly what I need here.  Thanks.

>> Source/WebCore/platform/network/cocoa/WebCoreNSURLSession.h:154
>> +@interface WebCoreNSURLSessionDataTask (WebKitInternal)
> 
> Does this belong in a different header? Is this exposing internals of NSURLSession? If so, I think it should go into an "SPI" header, even though SPI is not quite the right term.
> 
> Why the category name WebKitInternal? What does that mean exactly; for use in WebKit internally, or defined by WebKit for user internally? Maybe omitting the name is better?

We do need to move this to a different header.  See rdar://problem/72317626
Comment 22 Alex Christensen 2021-02-11 10:53:08 PST
Comment on attachment 419750 [details]
Patch

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

>> Source/WebCore/platform/network/ParsedRequestRange.cpp:46
>> +    // FIXME: Add StringView.toUInt64Strict to avoid these unnecessary String copies.
> 
> If we don’t want to put the StringView changes into the same patch, it’s still straightforward to avoid the unnecessary string copies by calling using an if (is8Bit()) call and calling charactersToUInt64Strict directly, without adding to StringView. I would prefer that. Or you can even put the helper function in this file and just have the FIXME be about moving the function from this file to StringView.h/cpp.

StringView.toUInt64Strict already exists.  Will adopt.
Comment 23 Alex Christensen 2021-02-11 18:01:05 PST
This also caused https://bugs.webkit.org/show_bug.cgi?id=221698 and has room for improvement and needs some more investigation as to why some videos still aren't playing.  Reverting.
Comment 24 Alex Christensen 2021-02-15 12:50:17 PST
Reopening because I reverted.
Comment 25 Alex Christensen 2021-02-15 17:35:02 PST
Created attachment 420405 [details]
Patch
Comment 26 Alex Christensen 2021-02-15 17:43:48 PST
Created attachment 420406 [details]
Patch
Comment 27 EWS 2021-02-15 20:53:15 PST
ChangeLog entry in Source/WebKit/ChangeLog contains OOPS!.
Comment 28 Alex Christensen 2021-02-16 09:00:28 PST
http://trac.webkit.org/r272908
Comment 29 Darin Adler 2021-02-16 09:32:59 PST
Comment on attachment 420406 [details]
Patch

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

Sorry to catch you after you landed. A few thoughts post-landing:

> Source/WebCore/ChangeLog:11
> +        Seeking is problematic, but at least we will try our best to play the video.

I would like to understand more about why "seeking is problematic".

> Source/WebCore/platform/network/ParsedRequestRange.cpp:36
> +    if (!input.startsWith(StringView("bytes="_s)))

Should just be able to write "bytes=" here, not StringView("bytes="_s).

> Source/WebCore/platform/network/ParsedRequestRange.cpp:40
> +    size_t begin { 0 };
> +    size_t end { 0 };

I suggest we define these below when we set them. Not important to have local variables above there that aren’t used.

    auto begin = *optionalBegin;

Or just dump the name optionalBegin entirely, name that variable something more like just "begin" and write *begin in a few places.

> Source/WebCore/platform/network/ParsedRequestRange.cpp:41
> +    size_t rangeBeginPosition = 6;

Maybe constexpr for this to make it clear that it’s a constant.

> Source/WebCore/platform/network/ParsedRequestRange.cpp:50
> +    begin = *optionalBegin;

Here we are chopping a uint64_t to a size_t; not quite right on 32-bit.

> Source/WebCore/platform/network/ParsedRequestRange.cpp:56
> +    end = *optionalEnd;

Here we are chopping a uint64_t to a size_t; not quite right on 32-bit.

> Source/WebCore/platform/network/ParsedRequestRange.h:29
> +#include <wtf/HashTraits.h>

Why are we including this header?

> Source/WebCore/platform/network/ParsedRequestRange.h:35
> +public:

No need for public in a struct.

> Source/WebCore/platform/network/ParsedRequestRange.h:37
> +    static Optional<ParsedRequestRange> parse(const String& string) { return parse(StringView(string)); }

Do we really need this? I’d expect everything would compile just fine without this, and we also would not have to include StringView.h if we didn’t have this code.

> Source/WebCore/platform/network/ParsedRequestRange.h:40
> +    const size_t begin { 0 };
> +    const size_t end { 0 };

I think these should be uint64_t; I understand that if the values are really large then the code won’t work, but parsing should not depend on 32 vs. 64-bit. I guess this mostly manipulates buffers in memory, which of course should use size_t. But there may be some point where larger numbers have to be checked for on 32-bit systems?

Also, I suggest we just make the whole object const, rather than having each member be const.

> Source/WebCore/platform/network/ParsedRequestRange.h:45
> +private:
> +    ParsedRequestRange(size_t begin, size_t end)
> +        : begin(begin)
> +        , end(end) { }

Can we do without this constructor? I don’t see a strong reason this needs to be private.
Comment 30 Alex Christensen 2021-02-16 11:51:22 PST
Thanks for the feedback.  I'll do a follow-up patch that incorporates most of it.  Here's some responses:

(In reply to Darin Adler from comment #29)
> Comment on attachment 420406 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=420406&action=review
> 
> Sorry to catch you after you landed. A few thoughts post-landing:
> 
> > Source/WebCore/ChangeLog:11
> > +        Seeking is problematic, but at least we will try our best to play the video.
> 
> I would like to understand more about why "seeking is problematic".
Seeking to near the end of a large video would cause a new range request to be issued for a range near the end, which would have to wait until the whole video is loaded to respond.  There's not much of a way around this, but Jer and I had discussed maybe preventing seeking to parts of the video that haven't loaded yet.

> > Source/WebCore/platform/network/ParsedRequestRange.cpp:36
> > +    if (!input.startsWith(StringView("bytes="_s)))
> 
> Should just be able to write "bytes=" here, not StringView("bytes="_s).
That would require the StringView(ASCIILiteral) constructor no longer being explicit.  I'm not sure why it is, but I felt like I didn't have the justification to change it.

> > Source/WebCore/platform/network/ParsedRequestRange.h:29
> > +#include <wtf/HashTraits.h>
> 
> Why are we including this header?
I needed it in an earlier iteration of this patch.  I can remove it.

> > Source/WebCore/platform/network/ParsedRequestRange.h:37
> > +    static Optional<ParsedRequestRange> parse(const String& string) { return parse(StringView(string)); }
> 
> Do we really need this? I’d expect everything would compile just fine
> without this, and we also would not have to include StringView.h if we
> didn’t have this code.
There are places where we pass in an NSString *, and there isn't a way to get a StringView out of an NSString * directly.  It's either this or an explicit String constructor at each call site to get the implicit StringView constructor.
 
> > Source/WebCore/platform/network/ParsedRequestRange.h:45
> > +private:
> > +    ParsedRequestRange(size_t begin, size_t end)
> > +        : begin(begin)
> > +        , end(end) { }
> 
> Can we do without this constructor? I don’t see a strong reason this needs
> to be private.
I wanted the only way to make a ParsedRequestRange be parsing one, which would return nullopt if it failed.  That's not too important, and I could just remove this for elegance.
Comment 31 Alex Christensen 2021-02-17 14:45:04 PST
Reverted in r273033
Comment 32 Ryan Haddad 2021-02-17 15:19:29 PST
As an additional note before this gets re-landed, WebKitAPI.MediaLoading.RangeRequestSynthesisWithContentLength was a flaky failure on Big Sur release bots https://results.webkit.org/?suite=api-tests&test=TestWebKitAPI.MediaLoading.RangeRequestSynthesisWithContentLength
Comment 33 Alex Christensen 2021-02-18 09:52:53 PST
Off again in http://trac.webkit.org/r273081
Comment 34 Alex Christensen 2021-02-22 10:56:31 PST
And off again in http://trac.webkit.org/r273263
Comment 35 Alex Christensen 2021-02-22 16:30:10 PST
And on again in https://trac.webkit.org/changeset/273287/webkit