Add new NSAttributedString API for converting HTML.
authortimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Mar 2019 23:32:20 +0000 (23:32 +0000)
committertimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Mar 2019 23:32:20 +0000 (23:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195636
rdar://problem/45055697

Reviewed by Tim Horton.

Source/WebCore:

* en.lproj/Localizable.strings: Updated.

Source/WebKit:

* Platform/spi/ios/UIKitSPI.h:
* SourcesCocoa.txt:
* UIProcess/API/Cocoa/NSAttributedString.h: Added.
* UIProcess/API/Cocoa/NSAttributedString.mm: Added.
(-[_WKAttributedStringNavigationDelegate webView:decidePolicyForNavigationAction:decisionHandler:]):
(-[_WKAttributedStringNavigationDelegate webView:didFailProvisionalNavigation:withError:]):
(-[_WKAttributedStringNavigationDelegate webView:didFailNavigation:withError:]):
(-[_WKAttributedStringNavigationDelegate webView:didFinishNavigation:]):
(+[_WKAttributedStringWebViewCache cache]):
(+[_WKAttributedStringWebViewCache configuration]):
(+[_WKAttributedStringWebViewCache clearConfiguration]):
(+[_WKAttributedStringWebViewCache retrieveOrCreateWebView]):
(+[_WKAttributedStringWebViewCache cacheWebView:]):
(+[_WKAttributedStringWebViewCache resetPurgeDelay]):
(+[_WKAttributedStringWebViewCache purgeSingleWebView]):
(+[_WKAttributedStringWebViewCache purgeAllWebViews]):
(+[NSAttributedString _loadFromHTMLWithOptions:contentLoader:completionHandler:]):
(+[NSAttributedString loadFromHTMLWithRequest:options:completionHandler:]):
(+[NSAttributedString loadFromHTMLWithFileURL:options:completionHandler:]):
(+[NSAttributedString loadFromHTMLWithString:options:completionHandler:]):
(+[NSAttributedString loadFromHTMLWithData:options:completionHandler:]):
* UIProcess/API/Cocoa/NSAttributedStringPrivate.h: Copied from Source/WebKit/UIProcess/API/Cocoa/WKErrorInternal.h.
* UIProcess/API/Cocoa/WKError.h:
* UIProcess/API/Cocoa/WKError.mm:
(localizedDescriptionForErrorCode):
(createNSError):
* UIProcess/API/Cocoa/WKErrorInternal.h:
* WebKit.xcodeproj/project.pbxproj:
* WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
(WebKit::WebPage::getContentsAsAttributedString):

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

13 files changed:
Source/WebCore/ChangeLog
Source/WebCore/en.lproj/Localizable.strings
Source/WebKit/ChangeLog
Source/WebKit/Platform/spi/ios/UIKitSPI.h
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/Cocoa/NSAttributedString.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/NSAttributedString.mm [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/NSAttributedStringPrivate.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/WKError.h
Source/WebKit/UIProcess/API/Cocoa/WKError.mm
Source/WebKit/UIProcess/API/Cocoa/WKErrorInternal.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm

index 381a707..2ccde76 100644 (file)
@@ -1,3 +1,13 @@
+2019-03-18  Timothy Hatcher  <timothy@apple.com>
+
+        Add new NSAttributedString API for converting HTML.
+        https://bugs.webkit.org/show_bug.cgi?id=195636
+        rdar://problem/45055697
+
+        Reviewed by Tim Horton.
+
+        * en.lproj/Localizable.strings: Updated.
+
 2019-03-18  Zalan Bujtas  <zalan@apple.com>
 
         Call transition and animation callbacks on non-composited renderers too.
index 513e328..96df5a4 100644 (file)
 /* Malware confirmation dialog title */
 "Are you sure you wish to go to this site?" = "Are you sure you wish to go to this site?";
 
+/* WKErrorAttributedStringContentFailedToLoad description */
+"Attributed string content failed to load" = "Attributed string content failed to load";
+
 /* Menu item label for automatic track selection behavior */
 "Auto (Recommended)" = "Auto (Recommended)";
 
 /* Undo action name */
 "Tighten Kerning (Undo action name)" = "Tighten Kerning";
 
+/* WKErrorAttributedStringContentLoadTimedOut description */
+"Timed out while loading attributed string content" = "Timed out while loading attributed string content";
+
 /* prompt string in authentication panel */
 "To view this page, you must log in to area “%@” on %@." = "To view this page, you must log in to area “%@” on %@.";
 
index f87cd5f..e3fec73 100644 (file)
@@ -1,3 +1,42 @@
+2019-03-18  Timothy Hatcher  <timothy@apple.com>
+
+        Add new NSAttributedString API for converting HTML.
+        https://bugs.webkit.org/show_bug.cgi?id=195636
+        rdar://problem/45055697
+
+        Reviewed by Tim Horton.
+
+        * Platform/spi/ios/UIKitSPI.h:
+        * SourcesCocoa.txt:
+        * UIProcess/API/Cocoa/NSAttributedString.h: Added.
+        * UIProcess/API/Cocoa/NSAttributedString.mm: Added.
+        (-[_WKAttributedStringNavigationDelegate webView:decidePolicyForNavigationAction:decisionHandler:]):
+        (-[_WKAttributedStringNavigationDelegate webView:didFailProvisionalNavigation:withError:]):
+        (-[_WKAttributedStringNavigationDelegate webView:didFailNavigation:withError:]):
+        (-[_WKAttributedStringNavigationDelegate webView:didFinishNavigation:]):
+        (+[_WKAttributedStringWebViewCache cache]):
+        (+[_WKAttributedStringWebViewCache configuration]):
+        (+[_WKAttributedStringWebViewCache clearConfiguration]):
+        (+[_WKAttributedStringWebViewCache retrieveOrCreateWebView]):
+        (+[_WKAttributedStringWebViewCache cacheWebView:]):
+        (+[_WKAttributedStringWebViewCache resetPurgeDelay]):
+        (+[_WKAttributedStringWebViewCache purgeSingleWebView]):
+        (+[_WKAttributedStringWebViewCache purgeAllWebViews]):
+        (+[NSAttributedString _loadFromHTMLWithOptions:contentLoader:completionHandler:]):
+        (+[NSAttributedString loadFromHTMLWithRequest:options:completionHandler:]):
+        (+[NSAttributedString loadFromHTMLWithFileURL:options:completionHandler:]):
+        (+[NSAttributedString loadFromHTMLWithString:options:completionHandler:]):
+        (+[NSAttributedString loadFromHTMLWithData:options:completionHandler:]):
+        * UIProcess/API/Cocoa/NSAttributedStringPrivate.h: Copied from Source/WebKit/UIProcess/API/Cocoa/WKErrorInternal.h.
+        * UIProcess/API/Cocoa/WKError.h:
+        * UIProcess/API/Cocoa/WKError.mm:
+        (localizedDescriptionForErrorCode):
+        (createNSError):
+        * UIProcess/API/Cocoa/WKErrorInternal.h:
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
+        (WebKit::WebPage::getContentsAsAttributedString):
+
 2019-03-18  Alex Christensen  <achristensen@webkit.org>
 
         Implement DownloadMonitor to prevent long-running slow downloads from background apps
index 4a7baa3..ca36f5c 100644 (file)
@@ -1193,4 +1193,11 @@ UIEdgeInsets UIEdgeInsetsAdd(UIEdgeInsets lhs, UIEdgeInsets rhs, UIRectEdge);
 
 extern NSString *const UIBacklightLevelChangedNotification;
 
+extern NSString * const NSTextEncodingNameDocumentOption;
+extern NSString * const NSBaseURLDocumentOption;
+extern NSString * const NSTimeoutDocumentOption;
+extern NSString * const NSWebPreferencesDocumentOption;
+extern NSString * const NSWebResourceLoadDelegateDocumentOption;
+extern NSString * const NSTextSizeMultiplierDocumentOption;
+
 WTF_EXTERN_C_END
index f96a4f5..7eb510a 100644 (file)
@@ -272,6 +272,7 @@ UIProcess/API/Cocoa/APIHTTPCookieStoreCocoa.mm
 UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm
 UIProcess/API/Cocoa/APIWebsiteDataStoreCocoa.mm
 UIProcess/API/Cocoa/LegacyBundleForClass.mm
+UIProcess/API/Cocoa/NSAttributedString.mm
 UIProcess/API/Cocoa/WKBackForwardList.mm
 UIProcess/API/Cocoa/WKBackForwardListItem.mm
 UIProcess/API/Cocoa/WKBrowsingContextController.mm
diff --git a/Source/WebKit/UIProcess/API/Cocoa/NSAttributedString.h b/Source/WebKit/UIProcess/API/Cocoa/NSAttributedString.h
new file mode 100644 (file)
index 0000000..8adff9e
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#if TARGET_OS_IPHONE
+#import <UIKit/NSAttributedString.h>
+#else
+#import <AppKit/NSAttributedString.h>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+/*!
+ @abstract Indicates which local files WebKit can access when loading content.
+ @discussion If NSReadAccessURLDocumentOption references a single file, only that file may be
+ loaded by WebKit. If NSReadAccessURLDocumentOption references a directory, files inside that
+ directory may be loaded by WebKit.
+*/
+WK_EXTERN NSAttributedStringDocumentReadingOptionKey const NSReadAccessURLDocumentOption
+    NS_SWIFT_NAME(NSAttributedStringDocumentReadingOptionKey.readAccessURL) WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+/*!
+ @abstract Type definition for the completion handler block used to get asynchronous attributed strings.
+ @discussion The completion handler block is passed the attributed string result along with any
+ document-level attributes, like NSBackgroundColorDocumentAttribute, or an error. An implementation
+ of this block type must expect to be called asynchronously when passed to HTML loading methods.
+*/
+typedef void (^NSAttributedStringCompletionHandler)(NSAttributedString * _Nullable, NSDictionary<NSAttributedStringDocumentAttributeKey, id> * _Nullable, NSError * _Nullable)
+    NS_SWIFT_NAME(NSAttributedString.CompletionHandler) WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+/*!
+ @discussion Extension of @link //apple_ref/occ/NSAttributedString NSAttributedString @/link to
+ create attributed strings from HTML content using WebKit.
+*/
+@interface NSAttributedString (NSAttributedStringWebKitAdditions)
+
+/*!
+ @abstract Loads an HTML URL request and converts the contents into an attributed string.
+ @param request The request specifying the URL to load.
+ @param options Document attributes for interpreting the document contents.
+ NSTextSizeMultiplierDocumentOption and NSTimeoutDocumentOption are supported option keys.
+ @param completionHandler A block to invoke when the operation completes or fails.
+ @discussion The completionHandler is passed the attributed string result along with any
+ document-level attributes, or an error.
+*/
++ (void)loadFromHTMLWithRequest:(NSURLRequest *)request options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+    NS_SWIFT_NAME(loadFromHTML(request:options:completionHandler:)) WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+/*!
+ @abstract Converts a local HTML file into an attributed string.
+ @param fileURL The file URL to load.
+ @param options Document attributes for interpreting the document contents.
+ NSTextSizeMultiplierDocumentOption, NSTimeoutDocumentOption and NSReadAccessURLDocumentOption
+ are supported option keys.
+ @param completionHandler A block to invoke when the operation completes or fails.
+ @discussion The completionHandler is passed the attributed string result along with any
+ document-level attributes, or an error. If NSReadAccessURLDocumentOption references a single file,
+ only that file may be loaded by WebKit. If NSReadAccessURLDocumentOption references a directory,
+ files inside that directory may be loaded by WebKit.
+*/
++ (void)loadFromHTMLWithFileURL:(NSURL *)fileURL options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+    NS_SWIFT_NAME(loadFromHTML(fileURL:options:completionHandler:)) WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+/*!
+ @abstract Converts an HTML string into an attributed string.
+ @param string The HTML string to use as the contents.
+ @param options Document attributes for interpreting the document contents.
+ NSTextSizeMultiplierDocumentOption, NSTimeoutDocumentOption and NSBaseURLDocumentOption
+ are supported option keys.
+ @param completionHandler A block to invoke when the operation completes or fails.
+ @discussion The completionHandler is passed the attributed string result along with any
+ document-level attributes, or an error. NSBaseURLDocumentOption is used to resolve relative URLs
+ within the document.
+*/
++ (void)loadFromHTMLWithString:(NSString *)string options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+    NS_SWIFT_NAME(loadFromHTML(string:options:completionHandler:)) WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+/*!
+ @abstract Converts HTML data into an attributed string.
+ @param data The HTML data to use as the contents.
+ @param options Document attributes for interpreting the document contents.
+ NSTextSizeMultiplierDocumentOption, NSTimeoutDocumentOption, NSTextEncodingNameDocumentOption,
+ and NSCharacterEncodingDocumentOption are supported option keys.
+ @param completionHandler A block to invoke when the operation completes or fails.
+ @discussion The completionHandler is passed the attributed string result along with any
+ document-level attributes, or an error. If neither NSTextEncodingNameDocumentOption nor
+ NSCharacterEncodingDocumentOption is supplied, a best-guess encoding is used.
+*/
++ (void)loadFromHTMLWithData:(NSData *)data options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+    NS_SWIFT_NAME(loadFromHTML(data:options:completionHandler:)) WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Source/WebKit/UIProcess/API/Cocoa/NSAttributedString.mm b/Source/WebKit/UIProcess/API/Cocoa/NSAttributedString.mm
new file mode 100644 (file)
index 0000000..10a7695
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#import "NSAttributedStringPrivate.h"
+
+#import "WKErrorInternal.h"
+#import "WKNavigationActionPrivate.h"
+#import "WKNavigationDelegate.h"
+#import "WKPreferences.h"
+#import "WKProcessPoolPrivate.h"
+#import "WKWebViewConfigurationPrivate.h"
+#import "WKWebViewPrivate.h"
+#import "WKWebsiteDataStore.h"
+#import "_WKProcessPoolConfiguration.h"
+#import <wtf/Deque.h>
+#import <wtf/MemoryPressureHandler.h>
+#import <wtf/RetainPtr.h>
+
+#if PLATFORM(IOS_FAMILY)
+#import <UIKitSPI.h>
+#endif
+
+NSString * const NSReadAccessURLDocumentOption = @"ReadAccessURL";
+
+constexpr NSRect webViewRect = {{0, 0}, {800, 600}};
+constexpr NSTimeInterval defaultTimeoutInterval = 60;
+constexpr NSTimeInterval purgeWebViewCacheDelay = 15;
+constexpr NSUInteger maximumWebViewCacheSize = 3;
+
+@interface _WKAttributedStringNavigationDelegate : NSObject <WKNavigationDelegate>
+
+@property (nonatomic, copy) void (^webContentProcessDidTerminate)(WKWebView *);
+@property (nonatomic, copy) void (^decidePolicyForNavigationAction)(WKNavigationAction *, void (^)(WKNavigationActionPolicy));
+@property (nonatomic, copy) void (^didFailProvisionalNavigation)(WKWebView *, WKNavigation *, NSError *);
+@property (nonatomic, copy) void (^didFailNavigation)(WKWebView *, WKNavigation *, NSError *);
+@property (nonatomic, copy) void (^didFinishNavigation)(WKWebView *, WKNavigation *);
+
+@end
+
+@implementation _WKAttributedStringNavigationDelegate
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+    if (_webContentProcessDidTerminate)
+        _webContentProcessDidTerminate(webView);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+    if (_decidePolicyForNavigationAction)
+        return _decidePolicyForNavigationAction(navigationAction, decisionHandler);
+    decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+    if (_didFailProvisionalNavigation)
+        _didFailProvisionalNavigation(webView, navigation, error);
+}
+
+- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
+{
+    if (_didFailNavigation)
+        _didFailNavigation(webView, navigation, error);
+}
+
+- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
+{
+    if (_didFinishNavigation)
+        _didFinishNavigation(webView, navigation);
+}
+
+@end
+
+@interface _WKAttributedStringWebViewCache : NSObject
+
++ (RetainPtr<WKWebView>)retrieveOrCreateWebView;
++ (void)cacheWebView:(WKWebView *)webView;
+
+@end
+
+@implementation _WKAttributedStringWebViewCache
+
++ (NSMutableArray<WKWebView *> *)cache
+{
+    static auto* cache = [[NSMutableArray alloc] initWithCapacity:maximumWebViewCacheSize];
+    return cache;
+}
+
+static WKWebViewConfiguration *configuration;
+
++ (WKWebViewConfiguration *)configuration
+{
+    if (!configuration) {
+        configuration = [[WKWebViewConfiguration alloc] init];
+        configuration.processPool = [[[WKProcessPool alloc] init] autorelease];
+        configuration.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
+        configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeAll;
+        configuration._allowsJavaScriptMarkup = NO;
+        configuration._allowsMetaRefresh = NO;
+        configuration._attachmentElementEnabled = YES;
+        configuration._invisibleAutoplayNotPermitted = YES;
+        configuration._mediaDataLoadsAutomatically = NO;
+        configuration._needsStorageAccessFromFileURLsQuirk = NO;
+#if PLATFORM(IOS_FAMILY)
+        configuration.allowsInlineMediaPlayback = NO;
+        configuration._alwaysRunsAtForegroundPriority = YES;
+#endif
+    }
+
+    return configuration;
+}
+
++ (void)clearConfiguration
+{
+    [configuration release];
+    configuration = nil;
+}
+
++ (RetainPtr<WKWebView>)retrieveOrCreateWebView
+{
+    [self resetPurgeDelay];
+
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        auto& memoryPressureHandler = MemoryPressureHandler::singleton();
+        memoryPressureHandler.setLowMemoryHandler([self] (Critical, Synchronous) {
+            [self purgeAllWebViews];
+        });
+    });
+
+    auto* cache = self.cache;
+    if (cache.count) {
+        RetainPtr<WKWebView> webView = cache.lastObject;
+        [cache removeLastObject];
+        return webView;
+    }
+
+    return adoptNS([[WKWebView alloc] initWithFrame:webViewRect configuration:self.configuration]);
+}
+
++ (void)cacheWebView:(WKWebView *)webView
+{
+    auto* cache = self.cache;
+    if (cache.count >= maximumWebViewCacheSize)
+        return;
+
+    [cache addObject:webView];
+
+    // Load a blank page to clear anything loaded while in the cache.
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];
+}
+
++ (void)resetPurgeDelay
+{
+    static const auto purgeSelector = @selector(purgeSingleWebView);
+    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:purgeSelector object:nil];
+    [self performSelector:purgeSelector withObject:nil afterDelay:purgeWebViewCacheDelay];
+}
+
++ (void)purgeSingleWebView
+{
+    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:_cmd object:nil];
+
+    auto* cache = self.cache;
+    if (!cache.count)
+        return;
+
+    [cache.lastObject _close];
+    [cache removeLastObject];
+
+    if (!cache.count) {
+        [self clearConfiguration];
+        return;
+    }
+
+    // Keep going until every view is removed, or the delay is reset.
+    [self performSelector:_cmd withObject:nil afterDelay:purgeWebViewCacheDelay];
+}
+
++ (void)purgeAllWebViews
+{
+    auto* cache = self.cache;
+    if (!cache.count)
+        return;
+
+    [cache makeObjectsPerformSelector:@selector(_close)];
+    [cache removeAllObjects];
+
+    [self clearConfiguration];
+}
+
+@end
+
+@implementation NSAttributedString (WKPrivate)
+
++ (void)_loadFromHTMLWithOptions:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options contentLoader:(WKNavigation *(^)(WKWebView *))loadWebContent completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+{
+    if (options[NSWebPreferencesDocumentOption])
+        [NSException raise:NSInvalidArgumentException format:@"NSWebPreferencesDocumentOption option is not supported"];
+    if (options[NSWebResourceLoadDelegateDocumentOption])
+        [NSException raise:NSInvalidArgumentException format:@"NSWebResourceLoadDelegateDocumentOption option is not supported"];
+    if (options[@"WebPolicyDelegate"])
+        [NSException raise:NSInvalidArgumentException format:@"WebPolicyDelegate option is not supported"];
+
+    auto runConversion = ^{
+        __block auto finished = NO;
+        __block auto webView = [_WKAttributedStringWebViewCache retrieveOrCreateWebView];
+        __block auto navigationDelegate = adoptNS([[_WKAttributedStringNavigationDelegate alloc] init]);
+
+        __block RetainPtr<WKNavigation> contentNavigation;
+
+        webView.get().navigationDelegate = navigationDelegate.get();
+
+        if (auto* textZoomFactor = dynamic_objc_cast<NSNumber>(options[NSTextSizeMultiplierDocumentOption]))
+            webView.get()._textZoomFactor = textZoomFactor.doubleValue;
+        else
+            webView.get()._textZoomFactor = 1;
+
+        auto finish = ^(NSAttributedString *attributedString, NSDictionary<NSAttributedStringDocumentAttributeKey, id> *attributes, NSError *error) {
+            if (finished)
+                return;
+
+            finished = YES;
+
+            webView.get().navigationDelegate = nil;
+            navigationDelegate = nil;
+            contentNavigation = nil;
+
+            if (!error)
+                [_WKAttributedStringWebViewCache cacheWebView:webView.get()];
+            webView = nil;
+
+            // Make the string be an instance of the receiver class.
+            if (attributedString && self != attributedString.class)
+                attributedString = [[[self alloc] initWithAttributedString:attributedString] autorelease];
+
+            // Make the document attributes immutable.
+            if ([attributes isKindOfClass:NSMutableDictionary.class])
+                attributes = [[[NSDictionary alloc] initWithDictionary:attributes] autorelease];
+
+            completionHandler(attributedString, attributes, error);
+        };
+
+        auto cancel = ^(WKErrorCode errorCode, NSError* underlyingError) {
+            finish(nil, nil, createNSError(errorCode, underlyingError).get());
+        };
+
+        navigationDelegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) {
+            if ([action._mainFrameNavigation isEqual:contentNavigation.get()])
+                return decisionHandler(WKNavigationActionPolicyAllow);
+            decisionHandler(WKNavigationActionPolicyCancel);
+        };
+
+        navigationDelegate.get().webContentProcessDidTerminate = ^(WKWebView *) {
+            cancel(WKErrorWebContentProcessTerminated, nil);
+        };
+
+        navigationDelegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
+            cancel(WKErrorAttributedStringContentFailedToLoad, error);
+        };
+
+        navigationDelegate.get().didFailNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
+            cancel(WKErrorAttributedStringContentFailedToLoad, error);
+        };
+
+        navigationDelegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
+            if (finished)
+                return;
+
+            navigationDelegate = nil;
+
+            [webView _getContentsAsAttributedStringWithCompletionHandler:^(NSAttributedString *attributedString, NSDictionary<NSAttributedStringDocumentAttributeKey, id> *documentAttributes, NSError *error) {
+                if (error)
+                    return cancel(WKErrorUnknown, error);
+                finish([[attributedString retain] autorelease], [[documentAttributes retain] autorelease], nil);
+            }];
+        };
+
+        auto timeoutInterval = defaultTimeoutInterval;
+        if (auto* timeoutOption = dynamic_objc_cast<NSNumber>(options[NSTimeoutDocumentOption])) {
+            if (timeoutOption.doubleValue >= 0)
+                timeoutInterval = timeoutOption.doubleValue;
+        }
+
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeoutInterval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+            if (finished)
+                return;
+            cancel(WKErrorAttributedStringContentLoadTimedOut, nil);
+        });
+
+        contentNavigation = loadWebContent(webView.get());
+
+        ASSERT(contentNavigation);
+        ASSERT(webView.get().loading);
+    };
+
+    if ([NSThread isMainThread])
+        runConversion();
+    else
+        dispatch_async(dispatch_get_main_queue(), runConversion);
+}
+
+@end
+
+@implementation NSAttributedString (NSAttributedStringWebKitAdditions)
+
++ (void)loadFromHTMLWithRequest:(NSURLRequest *)request options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+{
+    [self _loadFromHTMLWithOptions:options contentLoader:^WKNavigation *(WKWebView *webView) {
+        return [webView loadRequest:request];
+    } completionHandler:completionHandler];
+}
+
++ (void)loadFromHTMLWithFileURL:(NSURL *)fileURL options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+{
+    [self _loadFromHTMLWithOptions:options contentLoader:^WKNavigation *(WKWebView *webView) {
+        auto* readAccessURL = dynamic_objc_cast<NSURL>(options[NSReadAccessURLDocumentOption]);
+        return [webView loadFileURL:fileURL allowingReadAccessToURL:readAccessURL];
+    } completionHandler:completionHandler];
+}
+
++ (void)loadFromHTMLWithString:(NSString *)string options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+{
+    [self _loadFromHTMLWithOptions:options contentLoader:^WKNavigation *(WKWebView *webView) {
+        auto* baseURL = dynamic_objc_cast<NSURL>(options[NSBaseURLDocumentOption]);
+        return [webView loadHTMLString:string baseURL:baseURL];
+    } completionHandler:completionHandler];
+}
+
++ (void)loadFromHTMLWithData:(NSData *)data options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options completionHandler:(NSAttributedStringCompletionHandler)completionHandler
+{
+    [self _loadFromHTMLWithOptions:options contentLoader:^WKNavigation *(WKWebView *webView) {
+        auto* textEncodingName = dynamic_objc_cast<NSString>(options[NSTextEncodingNameDocumentOption]);
+        auto characterEncoding = static_cast<NSStringEncoding>(dynamic_objc_cast<NSNumber>(options[NSCharacterEncodingDocumentOption]).unsignedIntegerValue);
+        auto* baseURL = dynamic_objc_cast<NSURL>(options[NSBaseURLDocumentOption]);
+
+        if (characterEncoding && !textEncodingName) {
+            auto stringEncoding = CFStringConvertNSStringEncodingToEncoding(characterEncoding);
+            if (stringEncoding != kCFStringEncodingInvalidId)
+                textEncodingName = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(stringEncoding);
+        }
+
+        return [webView loadData:data MIMEType:@"text/html" characterEncodingName:textEncodingName baseURL:baseURL];
+    } completionHandler:completionHandler];
+}
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/NSAttributedStringPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/NSAttributedStringPrivate.h
new file mode 100644 (file)
index 0000000..b8582f0
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/NSAttributedString.h>
+
+@class WKNavigation;
+@class WKWebView;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/*!
+ @discussion Private extension of @link //apple_ref/occ/NSAttributedString NSAttributedString @/link to
+ translate HTML content into attributed strings using WebKit.
+ */
+@interface NSAttributedString (WKPrivate)
+
+/*!
+ @abstract Converts the contents loaded by a content loader block into an attributed string.
+ @param options Document attributes for interpreting the document contents.
+ NSTextSizeMultiplierDocumentOption, and NSTimeoutDocumentOption are supported option keys.
+ @param contentLoader A block to invoke when content needs to be loaded in the supplied
+ @link WKWebView @/link. A @link WKNavigation @/link for the main frame must be returned.
+ @param completionHandler A block to invoke when the translation completes or fails.
+ @discussion The completionHandler is passed the attributed string result along with any
+ document-level attributes, or an error.
+ */
++ (void)_loadFromHTMLWithOptions:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options contentLoader:(WKNavigation *(^)(WKWebView *))loadWebContent completionHandler:(NSAttributedStringCompletionHandler)completionHandler;
+
+@end
+
+NS_ASSUME_NONNULL_END
index f277e8d..b0808ec 100644 (file)
@@ -43,6 +43,8 @@ WK_EXTERN NSString * const WKErrorDomain WK_API_AVAILABLE(macosx(10.10), ios(8.0
  @constant WKErrorContentRuleListStoreLookUpFailed     Indicates that looking up a WKUserContentRuleList failed.
  @constant WKErrorContentRuleListStoreRemoveFailed     Indicates that removing a WKUserContentRuleList failed.
  @constant WKErrorContentRuleListStoreVersionMismatch  Indicates that the WKUserContentRuleList version did not match the latest.
+ @constant WKErrorAttributedStringContentFailedToLoad  Indicates that the attributed string content failed to load.
+ @constant WKErrorAttributedStringContentLoadTimedOut  Indicates that loading attributed string content timed out.
  */
 typedef NS_ENUM(NSInteger, WKErrorCode) {
     WKErrorUnknown = 1,
@@ -54,6 +56,8 @@ typedef NS_ENUM(NSInteger, WKErrorCode) {
     WKErrorContentRuleListStoreLookUpFailed WK_API_AVAILABLE(macosx(10.13), ios(11.0)),
     WKErrorContentRuleListStoreRemoveFailed WK_API_AVAILABLE(macosx(10.13), ios(11.0)),
     WKErrorContentRuleListStoreVersionMismatch WK_API_AVAILABLE(macosx(10.13), ios(11.0)),
+    WKErrorAttributedStringContentFailedToLoad WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA)),
+    WKErrorAttributedStringContentLoadTimedOut WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA)),
 } WK_API_AVAILABLE(macosx(10.10), ios(8.0));
 
 NS_ASSUME_NONNULL_END
index fcbfc4d..36e1f4b 100644 (file)
@@ -67,12 +67,22 @@ NSString *localizedDescriptionForErrorCode(WKErrorCode errorCode)
 
     case WKErrorContentRuleListStoreRemoveFailed:
         return WEB_UI_STRING("Removing a WKContentRuleList failed", "WKErrorContentRuleListStoreRemoveFailed description");
+
+    case WKErrorAttributedStringContentFailedToLoad:
+        return WEB_UI_STRING("Attributed string content failed to load", "WKErrorAttributedStringContentFailedToLoad description");
+
+    case WKErrorAttributedStringContentLoadTimedOut:
+        return WEB_UI_STRING("Timed out while loading attributed string content", "WKErrorAttributedStringContentLoadTimedOut description");
     }
 }
 
-RetainPtr<NSError> createNSError(WKErrorCode errorCode)
+RetainPtr<NSError> createNSError(WKErrorCode errorCode, NSError* underlyingError)
 {
-    auto userInfo = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:localizedDescriptionForErrorCode(errorCode), NSLocalizedDescriptionKey, nil]);
+    NSDictionary *userInfo = nil;
+    if (underlyingError)
+        userInfo = @{ NSLocalizedDescriptionKey: localizedDescriptionForErrorCode(errorCode), NSUnderlyingErrorKey: underlyingError };
+    else
+        userInfo = @{ NSLocalizedDescriptionKey: localizedDescriptionForErrorCode(errorCode) };
 
-    return adoptNS([[NSError alloc] initWithDomain:WKErrorDomain code:errorCode userInfo:userInfo.get()]);
+    return adoptNS([[NSError alloc] initWithDomain:WKErrorDomain code:errorCode userInfo:userInfo]);
 }
index 261e2e9..11bd1f5 100644 (file)
@@ -28,5 +28,5 @@
 #import <wtf/RetainPtr.h>
 #import "GenericCallback.h"
 
-RetainPtr<NSError> createNSError(WKErrorCode);
+RetainPtr<NSError> createNSError(WKErrorCode, NSError* underlyingError = nil);
 NSString *localizedDescriptionForErrorCode(WKErrorCode);
index 9eb5a64..89c1076 100644 (file)
                1C0A19571C90068F00FE0EBB /* WebAutomationSessionMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0A19551C90068F00FE0EBB /* WebAutomationSessionMessageReceiver.cpp */; };
                1C0A19581C90068F00FE0EBB /* WebAutomationSessionMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C0A19561C90068F00FE0EBB /* WebAutomationSessionMessages.h */; };
                1C0A195C1C916E1B00FE0EBB /* WebAutomationSessionProxyScriptSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C0A195B1C916E1B00FE0EBB /* WebAutomationSessionProxyScriptSource.h */; };
+               1C20936022318CB000026A39 /* NSAttributedString.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C20935E22318CB000026A39 /* NSAttributedString.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               1C2184022233872800BAC700 /* NSAttributedStringPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C2184012233872800BAC700 /* NSAttributedStringPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                1C891D6619B124FF00BA79DD /* WebInspectorUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C891D6319B124FF00BA79DD /* WebInspectorUI.h */; };
                1C8E28201275D15400BC7BD0 /* WebInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C8E281E1275D15400BC7BD0 /* WebInspector.h */; };
                1C8E28341275D73800BC7BD0 /* WebInspectorProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C8E28321275D73800BC7BD0 /* WebInspectorProxy.h */; };
                1C0A19591C9006EA00FE0EBB /* WebAutomationSession.messages.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = WebAutomationSession.messages.in; sourceTree = "<group>"; };
                1C0A195A1C91669500FE0EBB /* WebAutomationSessionProxy.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = WebAutomationSessionProxy.js; sourceTree = "<group>"; };
                1C0A195B1C916E1B00FE0EBB /* WebAutomationSessionProxyScriptSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebAutomationSessionProxyScriptSource.h; path = DerivedSources/WebKit2/WebAutomationSessionProxyScriptSource.h; sourceTree = BUILT_PRODUCTS_DIR; };
+               1C20935E22318CB000026A39 /* NSAttributedString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSAttributedString.h; sourceTree = "<group>"; };
+               1C20935F22318CB000026A39 /* NSAttributedString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NSAttributedString.mm; sourceTree = "<group>"; };
+               1C2184012233872800BAC700 /* NSAttributedStringPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSAttributedStringPrivate.h; sourceTree = "<group>"; };
                1C77C1951288A872006A742F /* WebInspectorProxy.messages.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = WebInspectorProxy.messages.in; sourceTree = "<group>"; };
                1C891D6219B124FF00BA79DD /* WebInspectorUI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebInspectorUI.cpp; sourceTree = "<group>"; };
                1C891D6319B124FF00BA79DD /* WebInspectorUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebInspectorUI.h; sourceTree = "<group>"; };
                                FED3C1DA1B447AE800E0EB7F /* APISerializedScriptValueCocoa.mm */,
                                1A3635AB1A3145E500ED6197 /* APIWebsiteDataStoreCocoa.mm */,
                                1AFDE64319510B5500C48FFA /* LegacyBundleForClass.mm */,
+                               1C20935E22318CB000026A39 /* NSAttributedString.h */,
+                               1C20935F22318CB000026A39 /* NSAttributedString.mm */,
+                               1C2184012233872800BAC700 /* NSAttributedStringPrivate.h */,
                                37C4C08B1814AC5C003688B9 /* WKBackForwardList.h */,
                                37C4C08A1814AC5C003688B9 /* WKBackForwardList.mm */,
                                37C4C08E1814AF3A003688B9 /* WKBackForwardListInternal.h */,
                                1A2161B011F37664008AD0F5 /* NPRuntimeObjectMap.h in Headers */,
                                1A2162B111F38971008AD0F5 /* NPRuntimeUtilities.h in Headers */,
                                1A2D84A3127F6AD1001EB962 /* NPVariantData.h in Headers */,
+                               1C20936022318CB000026A39 /* NSAttributedString.h in Headers */,
+                               1C2184022233872800BAC700 /* NSAttributedStringPrivate.h in Headers */,
                                3754D5451B3A29FD003A4C7F /* NSInvocationSPI.h in Headers */,
                                BC8ACA1316670D89004C1941 /* ObjCObjectGraph.h in Headers */,
                                37B47E2D1D64DB76005F4EFF /* objcSPI.h in Headers */,
index bd87bfe..16f3fa8 100644 (file)
@@ -208,6 +208,10 @@ void WebPage::getContentsAsAttributedString(CompletionHandler<void(const Attribu
     Frame& frame = m_page->mainFrame();
 
     RefPtr<Range> range = TextIterator::rangeFromLocationAndLength(frame.document()->documentElement(), 0, INT_MAX);
+    if (!range) {
+        completionHandler({ });
+        return;
+    }
 
     NSDictionary* documentAttributes = nil;