Index: ios/web/webui/crw_web_ui_page_builder.mm |
diff --git a/ios/web/webui/crw_web_ui_page_builder.mm b/ios/web/webui/crw_web_ui_page_builder.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..33a4e91b4796bff2c387cbca71a11e48b57d0520 |
--- /dev/null |
+++ b/ios/web/webui/crw_web_ui_page_builder.mm |
@@ -0,0 +1,317 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ios/web/webui/crw_web_ui_page_builder.h" |
+ |
+#include <map> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/bundle_locations.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/strings/sys_string_conversions.h" |
+ |
+namespace { |
+// Prefix for script tags. Used to locate JavaScript subresources. |
+NSString* const kJSTagPrefix = @"<script src=\""; |
+// Suffix for script tags. Used to locate JavaScript subresources. |
+NSString* const kJSTagSuffix = @"\"></script>"; |
+// Prefix for stylesheet tags. Used to locate CSS subresources. |
+NSString* const kCSSTagPrefix = @"<link rel=\"stylesheet\" href=\""; |
+// Suffix for stylesheet tags. Used to locate CSS subresources. |
+NSString* const kCSSTagSuffix = @"\">"; |
+// Template for creating inlined JavaScript tags. |
+NSString* const kWebUIScriptTextTemplate = @"<script>%@</script>"; |
+// Template for creating inlined CSS tags. |
+NSString* const kWebUIStyleTextTemplate = @"<style>%@</style>"; |
+// URL placeholder for WebUI messaging JavaScript. |
+NSString* const kWebUICoreJSURL = @"chrome://resources/js/ios/core.js"; |
+} // namespace |
+ |
+@interface CRWWebUIPageBuilder () |
+ |
+// Builds WebUI page for URL with HTML as default resource. |
+- (void)buildWebUIPageForHTML:(NSString*)HTML |
+ webUIURL:(const GURL&)URL |
+ completionHandler:(web::WebUIPageCompletion)completionHandler; |
+ |
+// Loads |resourceURL| by invoking _delegate. |
+- (void)fetchResourceWithURL:(const GURL&)resourceURL |
+ completionHandler:(web::WebUIDelegateCompletion)handler; |
+ |
+// Loads |resourceURLs| by invoking _delegate. Members of resourceURLs must be |
+// valid subresource URLs. |completionHandler| is called upon load of each |
+// resource. |
+- (void)fetchSubresourcesWithURLs:(const std::vector<GURL>&)resourceURLs |
+ completionHandler:(web::WebUIDelegateCompletion)handler; |
+ |
+// Looks for substrings surrounded by |prefix| and |suffix| in resource and |
+// returns them as an NSSet. |
+// For example, if prefix is "<a href='", suffix is "'>", and |resource| |
+// includes substrings "<a href='http://www.apple.com'>" and |
+// "<a href='chrome.html'>", return value will contain strings |
+// "http://www.apple.com" and "chrome.html". |
+- (NSSet*)URLStringsFromResource:(NSString*)resource |
+ prefix:(NSString*)prefix |
+ suffix:(NSString*)suffix; |
+ |
+// Returns URL strings for subresources found in |HTML|. URLs are found by |
+// searching for tags of the format <link rel="stylesheet" href="URLString"> |
+// and <script src="URLString">. |
+- (NSSet*)URLStringsFromHTML:(NSString*)HTML; |
+ |
+// Returns URL strings for subresources found in |CSS|. URLs are found by |
+// searching for statements of the format @import(URLString). |
+- (NSSet*)URLStringsFromCSS:(NSString*)CSS; |
+ |
+// YES if subresourceURL is a valid subresource URL. Valid subresource URLs |
+// include files with extension ".js" and ".css". |
+- (BOOL)isValidSubresourceURL:(const GURL&)subresourceURL; |
+ |
+// YES if subresourceURL is for a CSS resource. |
+- (BOOL)isCSSSubresourceURL:(const GURL&)subresourceURL; |
+ |
+// Prepends "<link rel="stylesheet" href="|URL|">" to the HTML link tag with |
+// href=|sourceURL| in |HTML|. |
+- (void)addCSSTagToHTML:(NSMutableString*)HTML |
+ forURL:(const GURL&)URL |
+ sourceURL:(const GURL&)sourceURL; |
+ |
+// Flattens HTML with provided map of URLs to resource strings. |
+- (void)flattenHTML:(NSMutableString*)HTML |
+ withSubresources:(std::map<GURL, std::string>)subresources; |
+ |
+// Returns JavaScript needed for bridging WebUI chrome.send() messages to |
+// the core.js defined message delivery system. |
+- (NSString*)webUIJavaScript; |
+ |
+@end |
+ |
+@implementation CRWWebUIPageBuilder { |
+ // Delegate for requesting resources. |
+ base::WeakNSProtocol<id<CRWWebUIPageBuilderDelegate>> _delegate; |
+} |
+ |
+#pragma mark - Public Methods |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return self; |
+} |
+ |
+- (instancetype)initWithDelegate:(id<CRWWebUIPageBuilderDelegate>)delegate { |
+ if (self = [super init]) { |
+ _delegate.reset(delegate); |
+ } |
+ return self; |
+} |
+ |
+- (void)buildWebUIPageForURL:(const GURL&)webUIURL |
+ completionHandler:(web::WebUIPageCompletion)completionHandler { |
+ [self fetchResourceWithURL:webUIURL |
+ completionHandler:^(NSString* webUIHTML, const GURL& URL) { |
+ [self buildWebUIPageForHTML:webUIHTML |
+ webUIURL:URL |
+ completionHandler:completionHandler]; |
+ }]; |
+} |
+ |
+#pragma mark - Private Methods |
+ |
+- (void)buildWebUIPageForHTML:(NSString*)HTML |
+ webUIURL:(const GURL&)pageURL |
+ completionHandler:(web::WebUIPageCompletion)completionHandler { |
+ __block base::scoped_nsobject<NSMutableString> webUIHTML([HTML mutableCopy]); |
+ NSSet* subresourceURLStrings = [self URLStringsFromHTML:webUIHTML]; |
+ __block NSUInteger pendingSubresourceCount = [subresourceURLStrings count]; |
+ if (!pendingSubresourceCount) { |
+ completionHandler(webUIHTML); |
+ return; |
+ } |
+ __block std::map<GURL, std::string> subresources; |
+ base::WeakNSObject<CRWWebUIPageBuilder> weakSelf(self); |
+ // Completion handler for subresource loads. |
+ __block web::WebUIDelegateCompletion subresourceHandler = nil; |
+ subresourceHandler = [[^(NSString* subresource, const GURL& subresourceURL) { |
+ // Import statements in CSS resources are also loaded. |
+ if ([self isCSSSubresourceURL:subresourceURL]) { |
+ NSSet* URLStrings = [weakSelf URLStringsFromCSS:subresource]; |
+ for (NSString* URLString in URLStrings) { |
+ GURL URL(subresourceURL.Resolve(base::SysNSStringToUTF8(URLString))); |
+ [weakSelf addCSSTagToHTML:webUIHTML |
+ forURL:URL |
+ sourceURL:subresourceURL]; |
+ pendingSubresourceCount++; |
+ [weakSelf fetchResourceWithURL:URL |
+ completionHandler:subresourceHandler]; |
+ } |
+ } |
+ subresources[subresourceURL] = base::SysNSStringToUTF8(subresource); |
+ pendingSubresourceCount--; |
+ // When subresource loading is complete, flatten the default resource |
+ // and invoke the completion handler. |
+ if (!pendingSubresourceCount) { |
+ [weakSelf flattenHTML:webUIHTML withSubresources:subresources]; |
+ completionHandler(webUIHTML); |
+ } |
+ } copy] autorelease]; |
+ |
+ for (NSString* URLString in subresourceURLStrings) { |
+ // chrome://resources/js/ios/core.js is skipped because it is |
+ // retrieved via webUIJavaScript rather than the net stack. |
+ if ([URLString isEqualToString:kWebUICoreJSURL]) { |
+ pendingSubresourceCount--; |
+ if (!pendingSubresourceCount) { |
+ [weakSelf flattenHTML:webUIHTML withSubresources:subresources]; |
+ completionHandler(webUIHTML); |
+ } |
+ continue; |
+ } |
+ GURL URL(pageURL.Resolve(base::SysNSStringToUTF8(URLString))); |
+ // If the resolved URL is different from URLString, replace URLString in |
+ // webUIHTML so that it will be located appropriately when flattening |
+ // occurs. |
+ if (URL.spec() != base::SysNSStringToUTF8(URLString)) { |
+ [webUIHTML replaceOccurrencesOfString:URLString |
+ withString:base::SysUTF8ToNSString(URL.spec()) |
+ options:0 |
+ range:NSMakeRange(0, [HTML length])]; |
+ } |
+ [self fetchResourceWithURL:URL completionHandler:subresourceHandler]; |
+ } |
+} |
+ |
+- (void)fetchResourceWithURL:(const GURL&)resourceURL |
+ completionHandler:(web::WebUIDelegateCompletion)handler { |
+ [_delegate webUIPageBuilder:self |
+ fetchResourceWithURL:resourceURL |
+ completionHandler:handler]; |
+} |
+ |
+- (void)fetchSubresourcesWithURLs:(const std::vector<GURL>&)resourceURLs |
+ completionHandler:(web::WebUIDelegateCompletion)handler { |
+ for (const GURL& URL : resourceURLs) { |
+ DCHECK([self isValidSubresourceURL:URL]); |
+ [self fetchResourceWithURL:URL completionHandler:handler]; |
+ } |
+} |
+ |
+- (NSSet*)URLStringsFromResource:(NSString*)resource |
+ prefix:(NSString*)prefix |
+ suffix:(NSString*)suffix { |
+ DCHECK(resource); |
+ DCHECK(prefix); |
+ DCHECK(suffix); |
+ NSMutableSet* URLStrings = [NSMutableSet set]; |
+ NSError* error = nil; |
+ NSString* tagPattern = [NSString |
+ stringWithFormat:@"%@(.*?)%@", |
+ [NSRegularExpression escapedPatternForString:prefix], |
+ [NSRegularExpression escapedPatternForString:suffix]]; |
+ NSRegularExpression* tagExpression = [NSRegularExpression |
+ regularExpressionWithPattern:tagPattern |
+ options:NSRegularExpressionCaseInsensitive |
+ error:&error]; |
+ if (error) { |
+ DLOG(WARNING) << "Error: " << error.description.UTF8String; |
+ } |
+ NSArray* matches = |
+ [tagExpression matchesInString:resource |
+ options:0 |
+ range:NSMakeRange(0, [resource length])]; |
+ for (NSTextCheckingResult* match in matches) { |
+ NSRange matchRange = [match rangeAtIndex:1]; |
+ DCHECK(matchRange.length); |
+ NSString* URLString = [resource substringWithRange:matchRange]; |
+ [URLStrings addObject:URLString]; |
+ } |
+ return URLStrings; |
+} |
+ |
+- (NSSet*)URLStringsFromHTML:(NSString*)HTML { |
+ NSSet* JS = [self URLStringsFromResource:HTML |
+ prefix:kJSTagPrefix |
+ suffix:kJSTagSuffix]; |
+ NSSet* CSS = [self URLStringsFromResource:HTML |
+ prefix:kCSSTagPrefix |
+ suffix:kCSSTagSuffix]; |
+ return [JS setByAddingObjectsFromSet:CSS]; |
+} |
+ |
+- (NSSet*)URLStringsFromCSS:(NSString*)CSS { |
+ NSString* prefix = @"@import url("; |
+ NSString* suffix = @");"; |
+ return [self URLStringsFromResource:CSS prefix:prefix suffix:suffix]; |
+} |
+ |
+- (BOOL)isValidSubresourceURL:(const GURL&)subresourceURL { |
+ base::FilePath resourcePath(subresourceURL.ExtractFileName()); |
+ std::string extension = resourcePath.Extension(); |
+ return extension == ".css" || extension == ".js"; |
+} |
+ |
+- (BOOL)isCSSSubresourceURL:(const GURL&)subresourceURL { |
+ base::FilePath resourcePath(subresourceURL.ExtractFileName()); |
+ std::string extension = resourcePath.Extension(); |
+ return extension == ".css"; |
+} |
+ |
+- (void)addCSSTagToHTML:(NSMutableString*)HTML |
+ forURL:(const GURL&)URL |
+ sourceURL:(const GURL&)sourceURL { |
+ NSString* URLString = base::SysUTF8ToNSString(URL.spec()); |
+ NSString* sourceURLString = base::SysUTF8ToNSString(sourceURL.spec()); |
+ NSString* sourceTag = |
+ [NSString stringWithFormat:@"%@%@%@", kCSSTagPrefix, sourceURLString, |
+ kCSSTagSuffix]; |
+ NSString* extendedTag = [[NSString |
+ stringWithFormat:@"%@%@%@", kCSSTagPrefix, URLString, kCSSTagSuffix] |
+ stringByAppendingString:sourceTag]; |
+ [HTML replaceOccurrencesOfString:sourceTag |
+ withString:extendedTag |
+ options:0 |
+ range:NSMakeRange(0, [HTML length])]; |
+} |
+ |
+- (void)flattenHTML:(NSMutableString*)HTML |
+ withSubresources:(std::map<GURL, std::string>)subresources { |
+ // Add core.js script to resources. |
+ GURL webUIJSURL("chrome://resources/js/ios/core.js"); |
+ subresources[webUIJSURL] = base::SysNSStringToUTF8([self webUIJavaScript]); |
+ for (auto it = subresources.begin(); it != subresources.end(); it++) { |
+ NSString* linkTemplate = @""; |
+ NSString* textTemplate = @""; |
+ if ([self isCSSSubresourceURL:it->first]) { |
+ linkTemplate = |
+ [NSString stringWithFormat:@"%@%%@%@", kCSSTagPrefix, kCSSTagSuffix]; |
+ textTemplate = kWebUIStyleTextTemplate; |
+ } else { // JavaScript. |
+ linkTemplate = |
+ [NSString stringWithFormat:@"%@%%@%@", kJSTagPrefix, kJSTagSuffix]; |
+ textTemplate = kWebUIScriptTextTemplate; |
+ } |
+ NSString* resourceURLString = base::SysUTF8ToNSString(it->first.spec()); |
+ NSString* linkTag = |
+ [NSString stringWithFormat:linkTemplate, resourceURLString]; |
+ NSString* resource = base::SysUTF8ToNSString(it->second); |
+ NSString* textTag = [NSString stringWithFormat:textTemplate, resource]; |
+ [HTML replaceOccurrencesOfString:linkTag |
+ withString:textTag |
+ options:0 |
+ range:NSMakeRange(0, [HTML length])]; |
+ } |
+} |
+ |
+- (NSString*)webUIJavaScript { |
+ NSBundle* bundle = base::mac::FrameworkBundle(); |
+ NSString* path = [bundle pathForResource:@"web_ui" ofType:@"js"]; |
+ DCHECK(path) << "web_ui.js file not found"; |
+ return [NSString stringWithContentsOfFile:path |
+ encoding:NSUTF8StringEncoding |
+ error:nil]; |
+} |
+ |
+@end |