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