bb158e904713419876e5ca0777a5662a6c801410
[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 enum {
40     NSSharingServicePickerStyleMenu = 0,
41     NSSharingServicePickerStyleRollover = 1,
42     NSSharingServicePickerStyleTextSelection = 2,
43     NSSharingServicePickerStyleDataDetector = 3
44 } NSSharingServicePickerStyle;
45 #endif
46
47 @interface NSSharingServicePicker (Details)
48 @property NSSharingServicePickerStyle style;
49 - (NSMenu *)menu;
50 @end
51
52 #if __has_include(<Foundation/NSExtension.h>)
53 #import <Foundation/NSExtension.h>
54 #else
55 @interface NSExtension
56 @end
57 #endif
58
59 @interface NSExtension (Details)
60 + (id)beginMatchingExtensionsWithAttributes:(NSDictionary *)attributes completion:(void (^)(NSArray *matchingExtensions, NSError *error))handler;
61 @end
62
63 namespace WebKit {
64
65 ServicesController& ServicesController::shared()
66 {
67     static NeverDestroyed<ServicesController> sharedController;
68     return sharedController;
69 }
70
71 ServicesController::ServicesController()
72     : m_refreshQueue(dispatch_queue_create("com.apple.WebKit.ServicesController", DISPATCH_QUEUE_SERIAL))
73     , m_hasPendingRefresh(false)
74     , m_hasImageServices(false)
75     , m_hasSelectionServices(false)
76     , m_hasRichContentServices(false)
77 {
78     refreshExistingServices();
79
80     auto refreshCallback = [](NSArray *, NSError *) {
81         // We coalese refreshes from the notification callbacks because they can come in small batches.
82         ServicesController::shared().refreshExistingServices(false);
83     };
84
85     auto extensionAttributes = @{ @"NSExtensionPointName" : @"com.apple.services" };
86     m_extensionWatcher = [NSExtension beginMatchingExtensionsWithAttributes:extensionAttributes completion:refreshCallback];
87     auto uiExtensionAttributes = @{ @"NSExtensionPointName" : @"com.apple.ui-services" };
88     m_uiExtensionWatcher = [NSExtension beginMatchingExtensionsWithAttributes:uiExtensionAttributes completion:refreshCallback];
89 }
90
91 void ServicesController::refreshExistingServices(bool refreshImmediately)
92 {
93     if (m_hasPendingRefresh)
94         return;
95
96     m_hasPendingRefresh = true;
97
98     auto refreshTime = dispatch_time(DISPATCH_TIME_NOW, refreshImmediately ? 0 : (int64_t)(1 * NSEC_PER_SEC));
99     dispatch_after(refreshTime, m_refreshQueue, ^{
100         static NeverDestroyed<NSImage *> image([[NSImage alloc] init]);
101         RetainPtr<NSSharingServicePicker>  picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ image ]]);
102         [picker setStyle:NSSharingServicePickerStyleRollover];
103
104         bool hasImageServices = picker.get().menu;
105
106         static NeverDestroyed<NSAttributedString *> attributedString([[NSAttributedString alloc] initWithString:@"a"]);
107         picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ attributedString ]]);
108         [picker setStyle:NSSharingServicePickerStyleTextSelection];
109
110         bool hasSelectionServices = picker.get().menu;
111
112         static NSAttributedString *attributedStringWithRichContent;
113         if (!attributedStringWithRichContent) {
114             NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
115             NSTextAttachmentCell *cell = [[NSTextAttachmentCell alloc] initImageCell:image.get()];
116             [attachment setAttachmentCell:cell];
117             NSMutableAttributedString *richString = (NSMutableAttributedString *)[NSMutableAttributedString attributedStringWithAttachment:attachment];
118             [richString appendAttributedString: attributedString];
119             attributedStringWithRichContent = [richString retain];
120         }
121
122         picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ attributedStringWithRichContent ]]);
123         [picker setStyle:NSSharingServicePickerStyleTextSelection];
124
125         bool hasRichContentServices = picker.get().menu;
126         
127         dispatch_async(dispatch_get_main_queue(), ^{
128             bool availableServicesChanged = (hasImageServices != m_hasImageServices) || (hasSelectionServices != m_hasSelectionServices) || (hasRichContentServices != m_hasRichContentServices);
129
130             m_hasSelectionServices = hasSelectionServices;
131             m_hasImageServices = hasImageServices;
132             m_hasRichContentServices = hasRichContentServices;
133
134             if (availableServicesChanged) {
135                 for (auto& context : WebContext::allContexts())
136                     context->sendToAllProcesses(Messages::WebProcess::SetEnabledServices(m_hasImageServices, m_hasSelectionServices, m_hasRichContentServices));
137             }
138
139             m_hasPendingRefresh = false;
140         });
141     });
142 }
143
144 } // namespace WebKit
145
146 #endif // ENABLE(SERVICE_CONTROLS)