Bug 240120 - Image controls menu button is not appearing for multi-page PDFs
Summary: Image controls menu button is not appearing for multi-page PDFs
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: Kate Cheney
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2022-05-05 07:45 PDT by Kate Cheney
Modified: 2022-05-09 09:24 PDT (History)
14 users (show)

See Also:


Attachments
Patch (36.59 KB, patch)
2022-05-05 08:00 PDT, Kate Cheney
no flags Details | Formatted Diff | Diff
Patch (37.36 KB, patch)
2022-05-05 14:14 PDT, Kate Cheney
no flags Details | Formatted Diff | Diff
Patch (37.47 KB, patch)
2022-05-05 15:20 PDT, Kate Cheney
no flags Details | Formatted Diff | Diff
Patch (36.28 KB, patch)
2022-05-06 09:04 PDT, Kate Cheney
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch (36.29 KB, patch)
2022-05-06 09:51 PDT, Kate Cheney
no flags Details | Formatted Diff | Diff
Patch for landing (36.30 KB, patch)
2022-05-09 08:38 PDT, Kate Cheney
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Kate Cheney 2022-05-05 07:45:28 PDT
Image controls menu button is not appearing for multi-page PDFs
Comment 1 Kate Cheney 2022-05-05 08:00:23 PDT
Created attachment 458873 [details]
Patch
Comment 2 Kate Cheney 2022-05-05 08:01:18 PDT
rdar://86425721
Comment 3 Wenson Hsieh 2022-05-05 08:20:22 PDT
Comment on attachment 458873 [details]
Patch

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

> Source/WebCore/html/HTMLElement.cpp:1123
> +void HTMLElement::updateImageControls()

Nit - I think it might be more ideal to move these helpers into ImageControlsMac.(h/cpp) as helper functions that take in an `HTMLElement&`, instead of adding these as methods directly to HTMLElement.

> Source/WebCore/html/HTMLElement.cpp:1132
> +    document().eventLoop().queueTask(TaskSource::InternalAsyncTask, [this, protectedThis = Ref { *this }] {

Capturing `this` by raw pointer here seems a bit unsafe — I think it's possible for this to result in a UAF if the element is destroyed after the event loop task is scheduled, but before it is dispatched.

> Source/WebCore/html/HTMLElement.h:194
> +    bool m_imageMenuEnabled { false };

Does this make HTMLElement bigger? If so, we _may_ want to consider an approach where we maintain a WeakSet<HTMLElement> on something like Page instead, which would keep track of image-menu-enabled elements. In practice, this would always be empty, except for in Mail on macOS.
Comment 4 Kate Cheney 2022-05-05 08:59:31 PDT
Comment on attachment 458873 [details]
Patch

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

>> Source/WebCore/html/HTMLElement.cpp:1123
>> +void HTMLElement::updateImageControls()
> 
> Nit - I think it might be more ideal to move these helpers into ImageControlsMac.(h/cpp) as helper functions that take in an `HTMLElement&`, instead of adding these as methods directly to HTMLElement.

Good idea.

>> Source/WebCore/html/HTMLElement.cpp:1132
>> +    document().eventLoop().queueTask(TaskSource::InternalAsyncTask, [this, protectedThis = Ref { *this }] {
> 
> Capturing `this` by raw pointer here seems a bit unsafe — I think it's possible for this to result in a UAF if the element is destroyed after the event loop task is scheduled, but before it is dispatched.

Good point. I am curious why this is async in the first place, I copied it over from existing code. Maybe Megan remembers why?

>> Source/WebCore/html/HTMLElement.h:194
>> +    bool m_imageMenuEnabled { false };
> 
> Does this make HTMLElement bigger? If so, we _may_ want to consider an approach where we maintain a WeakSet<HTMLElement> on something like Page instead, which would keep track of image-menu-enabled elements. In practice, this would always be empty, except for in Mail on macOS.

Yeah ok let me see if I can figure out something different.
Comment 5 Wenson Hsieh 2022-05-05 10:54:53 PDT
> >> Source/WebCore/html/HTMLElement.h:194
> >> +    bool m_imageMenuEnabled { false };
> > 
> > Does this make HTMLElement bigger? If so, we _may_ want to consider an approach where we maintain a WeakSet<HTMLElement> on something like Page instead, which would keep track of image-menu-enabled elements. In practice, this would always be empty, except for in Mail on macOS.
> 
> Yeah ok let me see if I can figure out something different.

Ah, so I just did a bit of testing — this new `bool` flag won't make HTMLElement any bigger, it seems, since it fits in the padding space after `StyledElement`:

(Before)
```
  +0 <120> WebCore::HTMLElement
  +0 <120>     WebCore::StyledElement WebCore::StyledElement

    …

+112 <  1>           bool m_hasDuplicateAttribute
+113 <  7>   <PADDING: 7 bytes>
Total byte size: 120
Total pad bytes: 7
```

(After)
```
  +0 <120> WebCore::HTMLElement
  +0 <120>     WebCore::StyledElement WebCore::StyledElement

    …

+112 <  1>           bool m_hasDuplicateAttribute
+113 <  1>   bool m_testing
+114 <  6>   <PADDING: 6 bytes>
Total byte size: 120
Total pad bytes: 6
Padding percentage: 5.00 %
```

...so I think adding the new bool will be okay for now, at least w.r.t. perf/memory impact.
Comment 6 Kate Cheney 2022-05-05 13:52:51 PDT
(In reply to Wenson Hsieh from comment #5)
> > >> Source/WebCore/html/HTMLElement.h:194
> > >> +    bool m_imageMenuEnabled { false };
> > > 
> > > Does this make HTMLElement bigger? If so, we _may_ want to consider an approach where we maintain a WeakSet<HTMLElement> on something like Page instead, which would keep track of image-menu-enabled elements. In practice, this would always be empty, except for in Mail on macOS.
> > 
> > Yeah ok let me see if I can figure out something different.
> 
> Ah, so I just did a bit of testing — this new `bool` flag won't make
> HTMLElement any bigger, it seems, since it fits in the padding space after
> `StyledElement`:
> 
> (Before)
> ```
>   +0 <120> WebCore::HTMLElement
>   +0 <120>     WebCore::StyledElement WebCore::StyledElement
> 
>     …
> 
> +112 <  1>           bool m_hasDuplicateAttribute
> +113 <  7>   <PADDING: 7 bytes>
> Total byte size: 120
> Total pad bytes: 7
> ```
> 
> (After)
> ```
>   +0 <120> WebCore::HTMLElement
>   +0 <120>     WebCore::StyledElement WebCore::StyledElement
> 
>     …
> 
> +112 <  1>           bool m_hasDuplicateAttribute
> +113 <  1>   bool m_testing
> +114 <  6>   <PADDING: 6 bytes>
> Total byte size: 120
> Total pad bytes: 6
> Padding percentage: 5.00 %
> ```
> 
> ...so I think adding the new bool will be okay for now, at least w.r.t.
> perf/memory impact.

Thanks for testing that! It also isn't too messy to just store the bool in the necessary elements and downcast the HTMLElement to access it. Since the majority of HTMLElements won't need this at all, it may be the best long term solution to avoid any future perf impact.
Comment 7 Wenson Hsieh 2022-05-05 13:54:03 PDT
> > ...so I think adding the new bool will be okay for now, at least w.r.t.
> > perf/memory impact.
> 
> Thanks for testing that! It also isn't too messy to just store the bool in
> the necessary elements and downcast the HTMLElement to access it. Since the
> majority of HTMLElements won't need this at all, it may be the best long
> term solution to avoid any future perf impact.

That sounds good to me! (i.e. just keeping the flag that's already on HTMLImageElement, and adding a similar bool to HTMLAttachmentElement).
Comment 8 Kate Cheney 2022-05-05 14:14:05 PDT
Created attachment 458914 [details]
Patch
Comment 9 Kate Cheney 2022-05-05 14:48:27 PDT
(In reply to Kate Cheney from comment #8)
> Created attachment 458914 [details]
> Patch

Ugh, need to rebase
Comment 10 Kate Cheney 2022-05-05 15:20:39 PDT
Created attachment 458918 [details]
Patch
Comment 11 Aditya Keerthi 2022-05-05 15:24:22 PDT
Comment on attachment 458918 [details]
Patch

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

> Source/WebCore/dom/mac/ImageControlsMac.cpp:157
> +        if (is<HTMLImageElement>(node.shadowHost())) {

This could be written as:

```
if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(node.shadowHost())) {

} else (RefPtr attachmentElement = dynamicDowncast<HTMLAttachmentElement>(node.shadowHost())) {

}
```

Right now, it performs redundant `is` checks, due to the use of `dynamicDowncast`.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:198
> +        if (is<HTMLAttachmentElement>(protectedElement))

Ditto (ImageControlsMac.cpp:157).

> Source/WebCore/dom/mac/ImageControlsMac.cpp:231
> +    if (is<RenderImage>(*renderObject))

This doesn't have the same redundant `is` check due to the use of `downcast`, but maybe this should also be written using `dynamicDowncast` and a variable to match the proposed style above?

> Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm:221
> +        isPDFAttachment = attachment->utiType() == [UTTypePDF.identifier UTF8String];

Is it safe to use `UTType` yet? UniformTypeIdentifiers.framework is only available on macOS 11.0+.
Comment 12 Kate Cheney 2022-05-05 15:29:40 PDT
(In reply to Aditya Keerthi from comment #11)
> Comment on attachment 458918 [details]
> Patch
> 

Thanks for looking!

> View in context:
> https://bugs.webkit.org/attachment.cgi?id=458918&action=review
> 
> > Source/WebCore/dom/mac/ImageControlsMac.cpp:157
> > +        if (is<HTMLImageElement>(node.shadowHost())) {
> 
> This could be written as:
> 
> ```
> if (RefPtr imageElement =
> dynamicDowncast<HTMLImageElement>(node.shadowHost())) {
> 
> } else (RefPtr attachmentElement =
> dynamicDowncast<HTMLAttachmentElement>(node.shadowHost())) {
> 
> }
> ```
> 
> Right now, it performs redundant `is` checks, due to the use of
> `dynamicDowncast`.
> 
> > Source/WebCore/dom/mac/ImageControlsMac.cpp:198
> > +        if (is<HTMLAttachmentElement>(protectedElement))
> 
> Ditto (ImageControlsMac.cpp:157).
> 
> > Source/WebCore/dom/mac/ImageControlsMac.cpp:231
> > +    if (is<RenderImage>(*renderObject))
> 
> This doesn't have the same redundant `is` check due to the use of
> `downcast`, but maybe this should also be written using `dynamicDowncast`
> and a variable to match the proposed style above?
> 

Yes, will fix all of these.

> > Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm:221
> > +        isPDFAttachment = attachment->utiType() == [UTTypePDF.identifier UTF8String];
> 
> Is it safe to use `UTType` yet? UniformTypeIdentifiers.framework is only
> available on macOS 11.0+.

Hmm good call, I'll add some compile time flags here and use the deprecated version for older OS's.
Comment 13 Chris Dumez 2022-05-05 15:30:25 PDT
Comment on attachment 458918 [details]
Patch

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

> Source/WebCore/dom/mac/ImageControlsMac.cpp:158
> +            RefPtr shadowHost = dynamicDowncast<HTMLImageElement>(node.shadowHost());

You already did a is<HTMLImageElement>() so you shouldn't use a dynamicDowncast<> here, just a downcast<>(). No point in checking the type twice. But really, I would just change the if condition to:
```
if (RefPtr shadowHost = dynamicDowncast<HTMLImageElement>(node.shadowHost())) {
```

> Source/WebCore/dom/mac/ImageControlsMac.cpp:159
> +            if (!shadowHost)

Again, this check is redundant because of your if check above. We know shadowHost() is a non-null HTMLImageElement.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:168
> +            RefPtr shadowHost = dynamicDowncast<HTMLAttachmentElement>(node.shadowHost());

Same comments as above.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:195
> +        ASSERT(is<HTMLAttachmentElement>(protectedElement) || is<HTMLImageElement>(protectedElement));

ASSERT(is<HTMLAttachmentElement>(*protectedElement) || is<HTMLImageElement>(*protectedElement));

to avoid unnecessary null checks.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:199
> +            hasImageMenu = dynamicDowncast<HTMLAttachmentElement>(*protectedElement)->imageMenuEnabled();

downcast<>, not dynamicDowncast<>. You're making me wonder if introducing dynamicDowncast<> was a good decision :)

> Source/WebCore/dom/mac/ImageControlsMac.cpp:201
> +            hasImageMenu = dynamicDowncast<HTMLImageElement>(*protectedElement)->imageMenuEnabled();

ditto.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:223
> +    if (RefPtr<Node> node = shadowRoot->firstChild()) {

if (RefPtr node = shadowRoot->firstChild()) {

should work

> Source/WebCore/dom/mac/ImageControlsMac.cpp:224
> +        RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ImageControlsMac::hasControls(downcast<HTMLElement>(*node)));

A little unclear why it is guaranteed that node is an HTMLElement.

> Source/WebCore/html/HTMLAttachmentElement.h:81
> +    bool imageMenuEnabled() const { return m_imageMenuEnabled; }

missing prefix (probably "is" here) for boolean variable / getter.

> Source/WebCore/html/HTMLAttachmentElement.h:101
> +    bool childShouldCreateRenderer(const Node&) const override;

Can this be final?

> Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm:219
> +    auto attachment = page()->attachmentForIdentifier(m_context.controlledImageAttachmentID());

If not used outside the if scope, I suggest:
if (auto attachment = page()->attachmentForIdentifier(m_context.controlledImageAttachmentID()))
Comment 14 Kate Cheney 2022-05-05 15:59:01 PDT
Comment on attachment 458918 [details]
Patch

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

>> Source/WebCore/dom/mac/ImageControlsMac.cpp:199
>> +            hasImageMenu = dynamicDowncast<HTMLAttachmentElement>(*protectedElement)->imageMenuEnabled();
> 
> downcast<>, not dynamicDowncast<>. You're making me wonder if introducing dynamicDowncast<> was a good decision :)

honest mistake!

>> Source/WebCore/dom/mac/ImageControlsMac.cpp:224
>> +        RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ImageControlsMac::hasControls(downcast<HTMLElement>(*node)));
> 
> A little unclear why it is guaranteed that node is an HTMLElement.

Good point, I am not sure how that assumption was made in the original patch. Will ask Megan.

>> Source/WebCore/html/HTMLAttachmentElement.h:81
>> +    bool imageMenuEnabled() const { return m_imageMenuEnabled; }
> 
> missing prefix (probably "is" here) for boolean variable / getter.

Will fix.

>> Source/WebCore/html/HTMLAttachmentElement.h:101
>> +    bool childShouldCreateRenderer(const Node&) const override;
> 
> Can this be final?

seems so, compiler will tell me for sure!

>> Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm:219
>> +    auto attachment = page()->attachmentForIdentifier(m_context.controlledImageAttachmentID());
> 
> If not used outside the if scope, I suggest:
> if (auto attachment = page()->attachmentForIdentifier(m_context.controlledImageAttachmentID()))

I use it below when creating the itemProvider, did this to avoid duplicating.

Thanks for comments!
Comment 15 Kate Cheney 2022-05-06 09:04:08 PDT
Created attachment 458955 [details]
Patch
Comment 16 Kate Cheney 2022-05-06 09:51:43 PDT
Created attachment 458958 [details]
Patch
Comment 17 Megan Gardner 2022-05-06 20:48:28 PDT
Comment on attachment 458958 [details]
Patch

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

> Source/WebCore/dom/mac/ImageControlsMac.cpp:195
> +

nit. I don't think you need this space.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:197
> +

nit. I don't think you need this space.

> Source/WebCore/dom/mac/ImageControlsMac.cpp:198
> +        bool hasImageMenu = isImageMenuEnabled(*protectedElement);

I feel like hasImgeMenu would be better named isImageMenuEnabled, both the hasImageMenu and hasControls sound like they should be the same thing, but the difference really is if they are enable or not.

> Source/WebCore/html/HTMLAttachmentElement.h:101
> +    bool childShouldCreateRenderer(const Node&) const final;

should this be const override?
Comment 18 Kate Cheney 2022-05-09 08:12:17 PDT
Comment on attachment 458958 [details]
Patch

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

Thanks for the review!

>> Source/WebCore/dom/mac/ImageControlsMac.cpp:195
>> +
> 
> nit. I don't think you need this space.

will remove.

>> Source/WebCore/dom/mac/ImageControlsMac.cpp:197
>> +
> 
> nit. I don't think you need this space.

ditto!

>> Source/WebCore/dom/mac/ImageControlsMac.cpp:198
>> +        bool hasImageMenu = isImageMenuEnabled(*protectedElement);
> 
> I feel like hasImgeMenu would be better named isImageMenuEnabled, both the hasImageMenu and hasControls sound like they should be the same thing, but the difference really is if they are enable or not.

Good point. I can't use the exact same name as the function because the C++ compiler gets confused, but I can do something similar like "imageMenuEnabled". 

>> Source/WebCore/html/HTMLAttachmentElement.h:101
>> +    bool childShouldCreateRenderer(const Node&) const final;
> 
> should this be const override?

I think final is OK because no derived class will override it, see Chris's comment above.
Comment 19 Kate Cheney 2022-05-09 08:38:50 PDT
Created attachment 459049 [details]
Patch for landing
Comment 20 EWS 2022-05-09 09:23:56 PDT
Committed r293977 (250415@main): <https://commits.webkit.org/250415@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 459049 [details].