[mac] Dynamically update serviceability when the set of services changes
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Aug 2014 23:27:25 +0000 (23:27 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Aug 2014 23:27:25 +0000 (23:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=135738
<rdar://problem/17533459>

Reviewed by Brady Eidson.

* UIProcess/WebContext.cpp:
(WebKit::WebContext::createNewWebProcess):
Adopt the new universal refreshExistingServices.

(WebKit::WebContext::refreshExistingServices): Deleted.
* UIProcess/WebContext.h:
Remove WebContext::refreshExistingServices; there's no need for it.

* UIProcess/mac/ServicesController.h:
* UIProcess/mac/ServicesController.mm:
Fix the build with only public headers by including NSSharingService.h itself.
Place the NSSharingServicePicker (Details) category outside the #ifdef.
Forward-declare and possibly import NSExtension SPI.

(WebKit::ServicesController::ServicesController):
Register a callback to be notified whenever the set of services changes.
When this occurs, call refreshExistingServices. We let refreshExistingServices
coalesce updates because these notifications can come in small batches.

(WebKit::ServicesController::refreshExistingServices):
Dispatch changes in service availability to all processes in all contexts.

* UIProcess/mac/WebContextMenuProxyMac.mm:
(WebKit::WebContextMenuProxyMac::setupServicesMenu):
Adjust the referenced rdar:// and call ServicesController::refreshExistingServices
instead of the now-removed WebContext:: variant. We can't remove this
yet because our services state can still be stale because NSServiceSharingPicker
can still sometimes lie about the current service state immediately after a change occurs;
once that is fixed, we should get rid of this as well as the refresh in Web Process creation.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@172366 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/WebContext.cpp
Source/WebKit2/UIProcess/WebContext.h
Source/WebKit2/UIProcess/mac/ServicesController.h
Source/WebKit2/UIProcess/mac/ServicesController.mm
Source/WebKit2/UIProcess/mac/WebContextMenuProxyMac.mm

index ba15e39153f5507357084b0065292a11322a2649..d00e1b1614f83c5d10c2af3f20464a74e5b48fa6 100644 (file)
@@ -1,3 +1,41 @@
+2014-08-08  Tim Horton  <timothy_horton@apple.com>
+
+        [mac] Dynamically update serviceability when the set of services changes
+        https://bugs.webkit.org/show_bug.cgi?id=135738
+        <rdar://problem/17533459>
+
+        Reviewed by Brady Eidson.
+
+        * UIProcess/WebContext.cpp:
+        (WebKit::WebContext::createNewWebProcess):
+        Adopt the new universal refreshExistingServices.
+
+        (WebKit::WebContext::refreshExistingServices): Deleted.
+        * UIProcess/WebContext.h:
+        Remove WebContext::refreshExistingServices; there's no need for it.
+
+        * UIProcess/mac/ServicesController.h:
+        * UIProcess/mac/ServicesController.mm:
+        Fix the build with only public headers by including NSSharingService.h itself.
+        Place the NSSharingServicePicker (Details) category outside the #ifdef.
+        Forward-declare and possibly import NSExtension SPI.
+
+        (WebKit::ServicesController::ServicesController):
+        Register a callback to be notified whenever the set of services changes.
+        When this occurs, call refreshExistingServices. We let refreshExistingServices
+        coalesce updates because these notifications can come in small batches.
+
+        (WebKit::ServicesController::refreshExistingServices):
+        Dispatch changes in service availability to all processes in all contexts.
+
+        * UIProcess/mac/WebContextMenuProxyMac.mm:
+        (WebKit::WebContextMenuProxyMac::setupServicesMenu):
+        Adjust the referenced rdar:// and call ServicesController::refreshExistingServices
+        instead of the now-removed WebContext:: variant. We can't remove this
+        yet because our services state can still be stale because NSServiceSharingPicker
+        can still sometimes lie about the current service state immediately after a change occurs;
+        once that is fixed, we should get rid of this as well as the refresh in Web Process creation.
+
 2014-08-08  Timothy Horton  <timothy_horton@apple.com>
 
         Clients that request the selection services menu after WebKit2 will get one with different metrics than otherwise
index 447a684ce15f5e1867e9fb0b874d9edd0b1751c8..c319f1917e919003a5e8e40f75f10d4052aba2cd 100644 (file)
@@ -682,7 +682,7 @@ WebProcessProxy& WebContext::createNewWebProcess()
     parameters.hasImageServices = ServicesController::shared().hasImageServices();
     parameters.hasSelectionServices = ServicesController::shared().hasSelectionServices();
     parameters.hasRichContentServices = ServicesController::shared().hasRichContentServices();
-    ServicesController::shared().refreshExistingServices(this);
+    ServicesController::shared().refreshExistingServices();
 #endif
 
     // Add any platform specific parameters
@@ -1361,14 +1361,6 @@ void WebContext::didGetStatistics(const StatisticsData& statisticsData, uint64_t
     request->completedRequest(requestID, statisticsData);
 }
 
-#if ENABLE(SERVICE_CONTROLS)
-void WebContext::refreshExistingServices()
-{
-    ServicesController::shared().refreshExistingServices(this);
-}
-#endif
-
-    
 void WebContext::garbageCollectJavaScriptObjects()
 {
     sendToAllProcesses(Messages::WebProcess::GarbageCollectJavaScriptObjects());
index 32775d81659a5a1c8dc7ce66327a2af1a359ae4f..d36f1964f17873153cf121e5b7df1e6f512a2752 100644 (file)
@@ -352,10 +352,6 @@ public:
 
     void setMemoryCacheDisabled(bool);
 
-#if ENABLE(SERVICE_CONTROLS)
-    void refreshExistingServices();
-#endif
-
 private:
     void platformInitialize();
 
index aa3386327e9cc9dfbd561a13b05b5ad538194b0b..988ed674c0eb5516baad34748b55d055a9f63921 100644 (file)
 
 #if ENABLE(SERVICE_CONTROLS)
 
-#include <wtf/HashSet.h>
+#include <wtf/Forward.h>
 #include <wtf/Noncopyable.h>
-#include <wtf/RefPtr.h>
-#include <wtf/RunLoop.h>
+#include <wtf/RetainPtr.h>
 
 namespace WebKit {
 
@@ -47,21 +46,20 @@ public:
     bool hasSelectionServices() const { return m_hasSelectionServices; }
     bool hasRichContentServices() const { return m_hasRichContentServices; }
 
-    void refreshExistingServices(WebContext*);
+    void refreshExistingServices(bool refreshImmediately = true);
 
 private:
     ServicesController();
 
-    void refreshExistingServices();
-
     dispatch_queue_t m_refreshQueue;
-    bool m_isRefreshing;
+    std::atomic_bool m_hasPendingRefresh;
 
     bool m_hasImageServices;
     bool m_hasSelectionServices;
     bool m_hasRichContentServices;
 
-    HashSet<RefPtr<WebContext>> m_contextsToNotify;
+    RetainPtr<id> m_extensionWatcher;
+    RetainPtr<id> m_uiExtensionWatcher;
 };
 
 } // namespace WebKit
index 07f90243fbd40bf2f81df68692a7ca8995136a96..bb158e904713419876e5ca0777a5662a6c801410 100644 (file)
@@ -30,6 +30,7 @@
 
 #import "WebContext.h"
 #import "WebProcessMessages.h"
+#import <AppKit/NSSharingService.h>
 #import <wtf/NeverDestroyed.h>
 
 #if __has_include(<AppKit/NSSharingService_Private.h>)
@@ -41,13 +42,24 @@ typedef enum {
     NSSharingServicePickerStyleTextSelection = 2,
     NSSharingServicePickerStyleDataDetector = 3
 } NSSharingServicePickerStyle;
+#endif
 
 @interface NSSharingServicePicker (Details)
 @property NSSharingServicePickerStyle style;
 - (NSMenu *)menu;
 @end
+
+#if __has_include(<Foundation/NSExtension.h>)
+#import <Foundation/NSExtension.h>
+#else
+@interface NSExtension
+@end
 #endif
 
+@interface NSExtension (Details)
++ (id)beginMatchingExtensionsWithAttributes:(NSDictionary *)attributes completion:(void (^)(NSArray *matchingExtensions, NSError *error))handler;
+@end
+
 namespace WebKit {
 
 ServicesController& ServicesController::shared()
@@ -58,30 +70,33 @@ ServicesController& ServicesController::shared()
 
 ServicesController::ServicesController()
     : m_refreshQueue(dispatch_queue_create("com.apple.WebKit.ServicesController", DISPATCH_QUEUE_SERIAL))
-    , m_isRefreshing(false)
+    , m_hasPendingRefresh(false)
     , m_hasImageServices(false)
     , m_hasSelectionServices(false)
     , m_hasRichContentServices(false)
 {
     refreshExistingServices();
-}
 
-void ServicesController::refreshExistingServices(WebContext* context)
-{
-    ASSERT_ARG(context, context);
-    ASSERT([NSThread isMainThread]);
+    auto refreshCallback = [](NSArray *, NSError *) {
+        // We coalese refreshes from the notification callbacks because they can come in small batches.
+        ServicesController::shared().refreshExistingServices(false);
+    };
 
-    m_contextsToNotify.add(context);
-    refreshExistingServices();
+    auto extensionAttributes = @{ @"NSExtensionPointName" : @"com.apple.services" };
+    m_extensionWatcher = [NSExtension beginMatchingExtensionsWithAttributes:extensionAttributes completion:refreshCallback];
+    auto uiExtensionAttributes = @{ @"NSExtensionPointName" : @"com.apple.ui-services" };
+    m_uiExtensionWatcher = [NSExtension beginMatchingExtensionsWithAttributes:uiExtensionAttributes completion:refreshCallback];
 }
 
-void ServicesController::refreshExistingServices()
+void ServicesController::refreshExistingServices(bool refreshImmediately)
 {
-    if (m_isRefreshing)
+    if (m_hasPendingRefresh)
         return;
 
-    m_isRefreshing = true;
-    dispatch_async(m_refreshQueue, ^{
+    m_hasPendingRefresh = true;
+
+    auto refreshTime = dispatch_time(DISPATCH_TIME_NOW, refreshImmediately ? 0 : (int64_t)(1 * NSEC_PER_SEC));
+    dispatch_after(refreshTime, m_refreshQueue, ^{
         static NeverDestroyed<NSImage *> image([[NSImage alloc] init]);
         RetainPtr<NSSharingServicePicker>  picker = adoptNS([[NSSharingServicePicker alloc] initWithItems:@[ image ]]);
         [picker setStyle:NSSharingServicePickerStyleRollover];
@@ -110,19 +125,18 @@ void ServicesController::refreshExistingServices()
         bool hasRichContentServices = picker.get().menu;
         
         dispatch_async(dispatch_get_main_queue(), ^{
-            bool notifyContexts = (hasImageServices != m_hasImageServices) || (hasSelectionServices != m_hasSelectionServices) || (hasRichContentServices != m_hasRichContentServices);
+            bool availableServicesChanged = (hasImageServices != m_hasImageServices) || (hasSelectionServices != m_hasSelectionServices) || (hasRichContentServices != m_hasRichContentServices);
+
             m_hasSelectionServices = hasSelectionServices;
             m_hasImageServices = hasImageServices;
             m_hasRichContentServices = hasRichContentServices;
 
-            if (notifyContexts) {
-                for (const RefPtr<WebContext>& context : m_contextsToNotify)
+            if (availableServicesChanged) {
+                for (auto& context : WebContext::allContexts())
                     context->sendToAllProcesses(Messages::WebProcess::SetEnabledServices(m_hasImageServices, m_hasSelectionServices, m_hasRichContentServices));
             }
 
-            m_contextsToNotify.clear();
-
-            m_isRefreshing = false;
+            m_hasPendingRefresh = false;
         });
     });
 }
index cc5e8146d3b9e66d1cd196d36b452203be890fc6..78cfe3d92dbf636dd0da13384b4db61de0f70f19 100644 (file)
@@ -31,6 +31,7 @@
 #import "DataReference.h"
 #import "MenuUtilities.h"
 #import "PageClientImpl.h"
+#import "ServicesController.h"
 #import "ShareableBitmap.h"
 #import "StringUtilities.h"
 #import "WebContext.h"
@@ -412,11 +413,10 @@ void WebContextMenuProxyMac::setupServicesMenu(const ContextMenuContextData& con
         }
     }
 
-    // If there is no services menu, then the existing services on the system have changed.
-    // Ask the UIProcess to refresh that list of services.
-    // If <rdar://problem/16776831> is resolved then we can more accurately keep the list up to date without this call.
+    // If there is no services menu, then the existing services on the system have changed, so refresh that list of services.
+    // If <rdar://problem/17954709> is resolved then we can more accurately keep the list up to date without this call.
     if (!m_servicesMenu)
-        m_page->process().context().refreshExistingServices();
+        ServicesController::shared().refreshExistingServices();
 }
 
 void WebContextMenuProxyMac::clearServicesMenu()