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
39typedef NS_ENUM(NSInteger, NSSharingServicePickerStyle) {
40    NSSharingServicePickerStyleMenu = 0,
41    NSSharingServicePickerStyleRollover = 1,
42    NSSharingServicePickerStyleTextSelection = 2,
43    NSSharingServicePickerStyleDataDetector = 3
44} NS_ENUM_AVAILABLE_MAC(10_10);
45
46typedef NS_ENUM(NSInteger, NSSharingServiceType) {
47    NSSharingServiceTypeShare = 0,
48    NSSharingServiceTypeViewer = 1,
49    NSSharingServiceTypeEditor = 2
50} NS_ENUM_AVAILABLE_MAC(10_10);
51
52typedef 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
83namespace WebKit {
84
85ServicesController& ServicesController::shared()
86{
87    static NeverDestroyed<ServicesController> sharedController;
88    return sharedController;
89}
90
91ServicesController::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 = [](NSArray *, NSError *) {
102        // We coalese refreshes from the notification callbacks because they can come in small batches.
103        ServicesController::shared().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
113static bool hasCompatibleServicesForItems(NSArray *items)
114{
115    return [NSSharingService sharingServicesForItems:items mask:NSSharingServiceMaskViewer | NSSharingServiceMaskEditor].count;
116}
117
118void 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)
165