Make `JSGlobalObject*` threading change more stabilized by adding tests and assertions
[WebKit-https.git] / Source / WebKit / UIProcess / ios / WKDrawingView.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKDrawingView.h"
28
29 #if HAVE(PENCILKIT)
30
31 #import "EditableImageController.h"
32 #import "WKContentViewInteraction.h"
33 #import "WKDrawingCoordinator.h"
34 #import <wtf/OSObjectPtr.h>
35 #import <wtf/RetainPtr.h>
36
37 #import "PencilKitSoftLink.h"
38
39 @interface WKDrawingView () <PKCanvasViewDelegate>
40 @end
41
42 @implementation WKDrawingView {
43     RetainPtr<PKCanvasView> _pencilView;
44
45 #if !PLATFORM(IOS_FAMILY_SIMULATOR)
46     OSObjectPtr<dispatch_queue_t> _renderQueue;
47     RetainPtr<PKImageRenderer> _renderer;
48 #endif
49
50     __weak WKContentView *_contentView;
51 }
52
53 - (instancetype)initWithEmbeddedViewID:(WebCore::GraphicsLayer::EmbeddedViewID)embeddedViewID contentView:(WKContentView *)contentView
54 {
55     self = [super initWithEmbeddedViewID:embeddedViewID];
56     if (!self)
57         return nil;
58
59     _contentView = contentView;
60
61     _pencilView = adoptNS([WebKit::allocPKCanvasViewInstance() initWithFrame:CGRectZero]);
62
63     [_pencilView setFingerDrawingEnabled:NO];
64     [_pencilView setUserInteractionEnabled:YES];
65     [_pencilView setOpaque:NO];
66     [_pencilView setDelegate:self];
67     [_pencilView setRulerHostingDelegate:_contentView._drawingCoordinator];
68
69     [self addSubview:_pencilView.get()];
70
71     return self;
72 }
73
74 - (void)layoutSubviews
75 {
76     if (!CGRectEqualToRect([_pencilView frame], self.bounds)) {
77         [_pencilView setFrame:self.bounds];
78
79 #if !PLATFORM(IOS_FAMILY_SIMULATOR)
80         // The renderer is instantiated for a particular size output; if
81         // the size changes, we need to re-create the renderer.
82         _renderer = nil;
83 #endif
84
85         [self invalidateAttachment];
86     }
87 }
88
89 static UIImage *emptyImage()
90 {
91     UIGraphicsBeginImageContext(CGSizeMake(1, 1));
92     CGContextClearRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 1, 1));
93     UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
94     UIGraphicsEndImageContext();
95
96     return resultImage;
97 }
98
99 - (UIImage *)renderedDrawing
100 {
101 #if PLATFORM(IOS_FAMILY_SIMULATOR)
102     // PKImageRenderer currently doesn't work in the simulator. In order to
103     // allow strokes to persist regardless (mostly for testing), we'll
104     // synthesize an empty 1x1 image.
105     return emptyImage();
106 #else
107     if (!self.bounds.size.width || !self.bounds.size.height || !self.window.screen.scale)
108         return emptyImage();
109
110     if (!_renderQueue)
111         _renderQueue = adoptOSObject(dispatch_queue_create("com.apple.WebKit.WKDrawingView.Rendering", DISPATCH_QUEUE_SERIAL));
112
113     if (!_renderer)
114         _renderer = adoptNS([WebKit::allocPKImageRendererInstance() initWithSize:self.bounds.size scale:self.window.screen.scale renderQueue:_renderQueue.get()]);
115
116     __block RetainPtr<UIImage> resultImage;
117
118     [_renderer renderDrawing:[_pencilView nonNullDrawing] completion:^(UIImage *image) {
119         resultImage = image;
120     }];
121
122     // FIXME: Ideally we would not synchronously wait for this rendering,
123     // but NSFileWrapper requires data synchronously, and our clients expect
124     // an NSFileWrapper to be available synchronously.
125     dispatch_sync(_renderQueue.get(), ^{ });
126
127     return resultImage.autorelease();
128 #endif
129 }
130
131 - (NSData *)PNGRepresentation
132 {
133     RetainPtr<UIImage> image = [self renderedDrawing];
134     RetainPtr<NSMutableData> PNGData = adoptNS([[NSMutableData alloc] init]);
135     RetainPtr<CGImageDestinationRef> imageDestination = adoptCF(CGImageDestinationCreateWithData((__bridge CFMutableDataRef)PNGData.get(), kUTTypePNG, 1, nil));
136     NSString *base64Drawing = [[[_pencilView nonNullDrawing] serialize] base64EncodedStringWithOptions:0];
137     NSDictionary *properties = nil;
138     if (base64Drawing) {
139         // FIXME: We should put this somewhere less user-facing than the EXIF User Comment field.
140         properties = @{
141             (__bridge NSString *)kCGImagePropertyExifDictionary : @{
142                 (__bridge NSString *)kCGImagePropertyExifUserComment : base64Drawing
143             }
144         };
145     }
146     CGImageDestinationSetProperties(imageDestination.get(), (__bridge CFDictionaryRef)properties);
147     CGImageDestinationAddImage(imageDestination.get(), [image CGImage], (__bridge CFDictionaryRef)properties);
148     CGImageDestinationFinalize(imageDestination.get());
149
150     return PNGData.autorelease();
151 }
152
153 - (void)loadDrawingFromPNGRepresentation:(NSData *)PNGData
154 {
155     RetainPtr<CGImageSourceRef> imageSource = adoptCF(CGImageSourceCreateWithData((__bridge CFDataRef)PNGData, nullptr));
156     if (!imageSource)
157         return;
158     RetainPtr<NSDictionary> properties = adoptNS((__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource.get(), 0, nil));
159     NSString *base64Drawing = [[properties objectForKey:(NSString *)kCGImagePropertyExifDictionary] objectForKey:(NSString *)kCGImagePropertyExifUserComment];
160     if (!base64Drawing)
161         return;
162     RetainPtr<NSData> drawingData = adoptNS([[NSData alloc] initWithBase64EncodedString:base64Drawing options:0]);
163     RetainPtr<PKDrawing> drawing = adoptNS([WebKit::allocPKDrawingInstance() initWithData:drawingData.get() error:nil]);
164     [_pencilView setNonNullDrawing:drawing.get()];
165 }
166
167 - (void)canvasViewDrawingDidChange:(PKCanvasView *)canvasView
168 {
169     [self invalidateAttachment];
170 }
171
172 - (void)_canvasViewWillBeginDrawing:(PKCanvasView *)canvasView
173 {
174 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
175     [_pencilView setInk:_contentView._drawingCoordinator.currentInk];
176 ALLOW_DEPRECATED_DECLARATIONS_END
177 }
178
179 - (void)invalidateAttachment
180 {
181     if (!_contentView.page)
182         return;
183     auto& page = *_contentView.page;
184
185     page.editableImageController().invalidateAttachmentForEditableImage(self.embeddedViewID);
186 }
187
188 - (void)didChangeRulerState:(BOOL)rulerEnabled
189 {
190     [_pencilView setRulerEnabled:rulerEnabled];
191 }
192
193 - (void)didChangeInk:(PKInk *)ink
194 {
195 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
196     [_pencilView setInk:ink];
197 ALLOW_DEPRECATED_DECLARATIONS_END
198 }
199
200 @end
201
202 #endif // HAVE(PENCILKIT)