More occasional crashes in ServicesController::resfreshExistingServices
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / ServicesController.mm
1 /*
2  * Copyright (C) 2014 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 "ServicesController.h"
28
29 #if ENABLE(SERVICE_CONTROLS)
30
31 #import "WebContext.h"
32 #import "WebProcessMessages.h"
33 #import <AppKit/NSSharingService.h>
34 #import <wtf/NeverDestroyed.h>
35
36 #if __has_include(<AppKit/NSSharingService_Private.h>)
37 #import <AppKit/NSSharingService_Private.h>
38 #else
39 typedef NS_ENUM(NSInteger, NSSharingServicePickerStyle) {
40     NSSharingServicePickerStyleMenu = 0,
41     NSSharingServicePickerStyleRollover = 1,
42     NSSharingServicePickerStyleTextSelection = 2,
43     NSSharingServicePickerStyleDataDetector = 3
44 } NS_ENUM_AVAILABLE_MAC(10_10);
45
46 typedef NS_ENUM(NSInteger, NSSharingServiceType) {
47     NSSharingServiceTypeShare = 0,
48     NSSharingServiceTypeViewer = 1,
49     NSSharingServiceTypeEditor = 2
50 } NS_ENUM_AVAILABLE_MAC(10_10);
51
52 typedef NS_OPTIONS(NSUInteger, NSSharingServiceMask) {
53     NSSharingServiceMaskShare = (1 << NSSharingServiceTypeShare),
54     NSSharingServiceMaskViewer = (1 << NSSharingServiceTypeViewer),
55     NSSharingServiceMaskEditor = (1 << NSSharingServiceTypeEditor),
56
57     NSSharingServiceMaskAllTypes = 0xFFFF
58 } NS_ENUM_AVAILABLE_MAC(10_10);
59 #endif
60
61 @interface NSSharingServicePicker (WKDetails)
62 @property NSSharingServicePickerStyle style;
63 - (NSMenu *)menu;
64 @end
65
66 @interface NSSharingService (WKDetails)
67 + (NSArray *)sharingServicesForItems:(NSArray *)items mask:(NSSharingServiceMask)maskForFiltering;
68 @end
69
70 #ifdef __LP64__
71 #if __has_include(<Foundation/NSExtension.h>)
72 #import <Foundation/NSExtension.h>
73 #else
74 @interface NSExtension
75 @end
76 #endif
77
78 @interface NSExtension (WKDetails)
79 + (id)beginMatchingExtensionsWithAttributes:(NSDictionary *)attributes completion:(void (^)(NSArray *matchingExtensions, NSError *error))handler;
80 @end
81 #endif // __LP64__
82
83 namespace WebKit {
84
85 ServicesController& ServicesController::shared()
86 {
87     static NeverDestroyed<ServicesController> sharedController;
88     return sharedController;
89 }
90
91 ServicesController::ServicesController()
92     : m_refreshQueue(dispatch_queue_create("com.apple.WebKit.ServicesController", DISPATCH_QUEUE_SERIAL))
93     , m_hasPendingRefresh(false)
94     , m_hasImageServices(false)
95     , m_hasSelectionServices(false)
96     , m_hasRichContentServices(false)
97 {
98     refreshExistingServices();
99
100 #ifdef __LP64__
101     auto refreshCallback = [this](NSArray *, NSError *) {
102         // We coalese refreshes from the notification callbacks because they can come in small batches.
103         refreshExistingServices(false);
104     };
105
106     auto extensionAttributes = @{ @"NSExtensionPointName" : @"com.apple.services" };
107     m_extensionWatcher = [NSExtension beginMatchingExtensionsWithAttributes:extensionAttributes completion:refreshCallback];
108     auto uiExtensionAttributes = @{ @"NSExtensionPointName" : @"com.apple.ui-services" };
109     m_uiExtensionWatcher = [NSExtension beginMatchingExtensionsWithAttributes:uiExtensionAttributes completion:refreshCallback];
110 #endif // __LP64__
111 }
112
113 static bool hasCompatibleServicesForItems(NSArray *items)
114 {
115     return [NSSharingService sharingServicesForItems:items mask:NSSharingServiceMaskViewer | NSSharingServiceMaskEditor].count;
116 }
117
118 void ServicesController::refreshExistingServices(bool refreshImmediately)
119 {
120     if (m_hasPendingRefresh)
121         return;
122
123     m_hasPendingRefresh = true;
124
125     auto refreshTime = dispatch_time(DISPATCH_TIME_NOW, refreshImmediately ? 0 : (int64_t)(1 * NSEC_PER_SEC));
126     dispatch_after(refreshTime, m_refreshQueue, ^{
127         static NeverDestroyed<NSImage *> image([[NSImage alloc] init]);
128         bool hasImageServices = hasCompatibleServicesForItems(@[ image ]);
129
130         static NeverDestroyed<NSAttributedString *> attributedString([[NSAttributedString alloc] initWithString:@"a"]);
131         bool hasSelectionServices = hasCompatibleServicesForItems(@[ attributedString ]);
132
133         static NSAttributedString *attributedStringWithRichContent;
134         if (!attributedStringWithRichContent) {
135             NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
136             NSTextAttachmentCell *cell = [[NSTextAttachmentCell alloc] initImageCell:image.get()];
137             [attachment setAttachmentCell:cell];
138             NSMutableAttributedString *richString = (NSMutableAttributedString *)[NSMutableAttributedString attributedStringWithAttachment:attachment];
139             [richString appendAttributedString: attributedString];
140             attributedStringWithRichContent = [richString retain];
141         }
142
143         bool hasRichContentServices = hasCompatibleServicesForItems(@[ attributedStringWithRichContent ]);
144         
145         dispatch_async(dispatch_get_main_queue(), ^{
146             bool availableServicesChanged = (hasImageServices != m_hasImageServices) || (hasSelectionServices != m_hasSelectionServices) || (hasRichContentServices != m_hasRichContentServices);
147
148             m_hasSelectionServices = hasSelectionServices;
149             m_hasImageServices = hasImageServices;
150             m_hasRichContentServices = hasRichContentServices;
151
152             if (availableServicesChanged) {
153                 for (auto& context : WebContext::allContexts())
154                     context->sendToAllProcesses(Messages::WebProcess::SetEnabledServices(m_hasImageServices, m_hasSelectionServices, m_hasRichContentServices));
155             }
156
157             m_hasPendingRefresh = false;
158         });
159     });
160 }
161
162 } // namespace WebKit
163
164 #endif // ENABLE(SERVICE_CONTROLS)