2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "ServicesController.h"
29 #if ENABLE(SERVICE_CONTROLS)
31 #import "WebContext.h"
32 #import "WebProcessMessages.h"
33 #import <AppKit/NSSharingService.h>
34 #import <wtf/NeverDestroyed.h>
36 #if __has_include(<AppKit/NSSharingService_Private.h>)
37 #import <AppKit/NSSharingService_Private.h>
40 NSSharingServicePickerStyleMenu = 0,
41 NSSharingServicePickerStyleRollover = 1,
42 NSSharingServicePickerStyleTextSelection = 2,
43 NSSharingServicePickerStyleDataDetector = 3
44 } NSSharingServicePickerStyle;
47 @interface NSSharingServicePicker (Details)
48 @property NSSharingServicePickerStyle style;
52 #if __has_include(<Foundation/NSExtension.h>)
53 #import <Foundation/NSExtension.h>
55 @interface NSExtension
59 @interface NSExtension (Details)
60 + (id)beginMatchingExtensionsWithAttributes:(NSDictionary *)attributes completion:(void (^)(NSArray *matchingExtensions, NSError *error))handler;
65 ServicesController& ServicesController::shared()
67 static NeverDestroyed<ServicesController> sharedController;
68 return sharedController;
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)
78 refreshExistingServices();
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);
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];
91 void ServicesController::refreshExistingServices(bool refreshImmediately)
93 if (m_hasPendingRefresh)
96 m_hasPendingRefresh = true;
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];
104 bool hasImageServices = picker.get().menu;
106 static NeverDestroyed<NSAttributedString *> attributedString([[NSAttributedString alloc] initWithString:@"a"]);
107 picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ attributedString ]]);
108 [picker setStyle:NSSharingServicePickerStyleTextSelection];
110 bool hasSelectionServices = picker.get().menu;
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];
122 picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ attributedStringWithRichContent ]]);
123 [picker setStyle:NSSharingServicePickerStyleTextSelection];
125 bool hasRichContentServices = picker.get().menu;
127 dispatch_async(dispatch_get_main_queue(), ^{
128 bool availableServicesChanged = (hasImageServices != m_hasImageServices) || (hasSelectionServices != m_hasSelectionServices) || (hasRichContentServices != m_hasRichContentServices);
130 m_hasSelectionServices = hasSelectionServices;
131 m_hasImageServices = hasImageServices;
132 m_hasRichContentServices = hasRichContentServices;
134 if (availableServicesChanged) {
135 for (auto& context : WebContext::allContexts())
136 context->sendToAllProcesses(Messages::WebProcess::SetEnabledServices(m_hasImageServices, m_hasSelectionServices, m_hasRichContentServices));
139 m_hasPendingRefresh = false;
144 } // namespace WebKit
146 #endif // ENABLE(SERVICE_CONTROLS)