OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ios/web/webui/crw_web_ui_page_builder.h" |
| 6 |
| 7 #include <map> |
| 8 #include <string> |
| 9 #include <vector> |
| 10 |
| 11 #include "base/ios/weak_nsobject.h" |
| 12 #include "base/logging.h" |
| 13 #include "base/mac/bundle_locations.h" |
| 14 #include "base/mac/scoped_nsobject.h" |
| 15 #include "base/strings/sys_string_conversions.h" |
| 16 |
| 17 namespace { |
| 18 // Prefix for script tags. Used to locate JavaScript subresources. |
| 19 NSString* const kJSTagPrefix = @"<script src=\""; |
| 20 // Suffix for script tags. Used to locate JavaScript subresources. |
| 21 NSString* const kJSTagSuffix = @"\"></script>"; |
| 22 // Prefix for stylesheet tags. Used to locate CSS subresources. |
| 23 NSString* const kCSSTagPrefix = @"<link rel=\"stylesheet\" href=\""; |
| 24 // Suffix for stylesheet tags. Used to locate CSS subresources. |
| 25 NSString* const kCSSTagSuffix = @"\">"; |
| 26 // Template for creating inlined JavaScript tags. |
| 27 NSString* const kWebUIScriptTextTemplate = @"<script>%@</script>"; |
| 28 // Template for creating inlined CSS tags. |
| 29 NSString* const kWebUIStyleTextTemplate = @"<style>%@</style>"; |
| 30 // URL placeholder for WebUI messaging JavaScript. |
| 31 NSString* const kWebUICoreJSURL = @"chrome://resources/js/ios/core.js"; |
| 32 } // namespace |
| 33 |
| 34 @interface CRWWebUIPageBuilder () |
| 35 |
| 36 // Builds WebUI page for URL with HTML as default resource. |
| 37 - (void)buildWebUIPageForHTML:(NSString*)HTML |
| 38 webUIURL:(const GURL&)URL |
| 39 completionHandler:(web::WebUIPageCompletion)completionHandler; |
| 40 |
| 41 // Loads |resourceURL| by invoking _delegate. |
| 42 - (void)fetchResourceWithURL:(const GURL&)resourceURL |
| 43 completionHandler:(web::WebUIDelegateCompletion)handler; |
| 44 |
| 45 // Loads |resourceURLs| by invoking _delegate. Members of resourceURLs must be |
| 46 // valid subresource URLs. |completionHandler| is called upon load of each |
| 47 // resource. |
| 48 - (void)fetchSubresourcesWithURLs:(const std::vector<GURL>&)resourceURLs |
| 49 completionHandler:(web::WebUIDelegateCompletion)handler; |
| 50 |
| 51 // Looks for substrings surrounded by |prefix| and |suffix| in resource and |
| 52 // returns them as an NSSet. |
| 53 // For example, if prefix is "<a href='", suffix is "'>", and |resource| |
| 54 // includes substrings "<a href='http://www.apple.com'>" and |
| 55 // "<a href='chrome.html'>", return value will contain strings |
| 56 // "http://www.apple.com" and "chrome.html". |
| 57 - (NSSet*)URLStringsFromResource:(NSString*)resource |
| 58 prefix:(NSString*)prefix |
| 59 suffix:(NSString*)suffix; |
| 60 |
| 61 // Returns URL strings for subresources found in |HTML|. URLs are found by |
| 62 // searching for tags of the format <link rel="stylesheet" href="URLString"> |
| 63 // and <script src="URLString">. |
| 64 - (NSSet*)URLStringsFromHTML:(NSString*)HTML; |
| 65 |
| 66 // Returns URL strings for subresources found in |CSS|. URLs are found by |
| 67 // searching for statements of the format @import(URLString). |
| 68 - (NSSet*)URLStringsFromCSS:(NSString*)CSS; |
| 69 |
| 70 // YES if subresourceURL is a valid subresource URL. Valid subresource URLs |
| 71 // include files with extension ".js" and ".css". |
| 72 - (BOOL)isValidSubresourceURL:(const GURL&)subresourceURL; |
| 73 |
| 74 // YES if subresourceURL is for a CSS resource. |
| 75 - (BOOL)isCSSSubresourceURL:(const GURL&)subresourceURL; |
| 76 |
| 77 // Prepends "<link rel="stylesheet" href="|URL|">" to the HTML link tag with |
| 78 // href=|sourceURL| in |HTML|. |
| 79 - (void)addCSSTagToHTML:(NSMutableString*)HTML |
| 80 forURL:(const GURL&)URL |
| 81 sourceURL:(const GURL&)sourceURL; |
| 82 |
| 83 // Flattens HTML with provided map of URLs to resource strings. |
| 84 - (void)flattenHTML:(NSMutableString*)HTML |
| 85 withSubresources:(std::map<GURL, std::string>)subresources; |
| 86 |
| 87 // Returns JavaScript needed for bridging WebUI chrome.send() messages to |
| 88 // the core.js defined message delivery system. |
| 89 - (NSString*)webUIJavaScript; |
| 90 |
| 91 @end |
| 92 |
| 93 @implementation CRWWebUIPageBuilder { |
| 94 // Delegate for requesting resources. |
| 95 base::WeakNSProtocol<id<CRWWebUIPageBuilderDelegate>> _delegate; |
| 96 } |
| 97 |
| 98 #pragma mark - Public Methods |
| 99 |
| 100 - (instancetype)init { |
| 101 NOTREACHED(); |
| 102 return self; |
| 103 } |
| 104 |
| 105 - (instancetype)initWithDelegate:(id<CRWWebUIPageBuilderDelegate>)delegate { |
| 106 if (self = [super init]) { |
| 107 _delegate.reset(delegate); |
| 108 } |
| 109 return self; |
| 110 } |
| 111 |
| 112 - (void)buildWebUIPageForURL:(const GURL&)webUIURL |
| 113 completionHandler:(web::WebUIPageCompletion)completionHandler { |
| 114 [self fetchResourceWithURL:webUIURL |
| 115 completionHandler:^(NSString* webUIHTML, const GURL& URL) { |
| 116 [self buildWebUIPageForHTML:webUIHTML |
| 117 webUIURL:URL |
| 118 completionHandler:completionHandler]; |
| 119 }]; |
| 120 } |
| 121 |
| 122 #pragma mark - Private Methods |
| 123 |
| 124 - (void)buildWebUIPageForHTML:(NSString*)HTML |
| 125 webUIURL:(const GURL&)pageURL |
| 126 completionHandler:(web::WebUIPageCompletion)completionHandler { |
| 127 __block base::scoped_nsobject<NSMutableString> webUIHTML([HTML mutableCopy]); |
| 128 NSSet* subresourceURLStrings = [self URLStringsFromHTML:webUIHTML]; |
| 129 __block NSUInteger pendingSubresourceCount = [subresourceURLStrings count]; |
| 130 if (!pendingSubresourceCount) { |
| 131 completionHandler(webUIHTML); |
| 132 return; |
| 133 } |
| 134 __block std::map<GURL, std::string> subresources; |
| 135 base::WeakNSObject<CRWWebUIPageBuilder> weakSelf(self); |
| 136 // Completion handler for subresource loads. |
| 137 __block web::WebUIDelegateCompletion subresourceHandler = nil; |
| 138 subresourceHandler = [[^(NSString* subresource, const GURL& subresourceURL) { |
| 139 // Import statements in CSS resources are also loaded. |
| 140 if ([self isCSSSubresourceURL:subresourceURL]) { |
| 141 NSSet* URLStrings = [weakSelf URLStringsFromCSS:subresource]; |
| 142 for (NSString* URLString in URLStrings) { |
| 143 GURL URL(subresourceURL.Resolve(base::SysNSStringToUTF8(URLString))); |
| 144 [weakSelf addCSSTagToHTML:webUIHTML |
| 145 forURL:URL |
| 146 sourceURL:subresourceURL]; |
| 147 pendingSubresourceCount++; |
| 148 [weakSelf fetchResourceWithURL:URL |
| 149 completionHandler:subresourceHandler]; |
| 150 } |
| 151 } |
| 152 subresources[subresourceURL] = base::SysNSStringToUTF8(subresource); |
| 153 pendingSubresourceCount--; |
| 154 // When subresource loading is complete, flatten the default resource |
| 155 // and invoke the completion handler. |
| 156 if (!pendingSubresourceCount) { |
| 157 [weakSelf flattenHTML:webUIHTML withSubresources:subresources]; |
| 158 completionHandler(webUIHTML); |
| 159 } |
| 160 } copy] autorelease]; |
| 161 |
| 162 for (NSString* URLString in subresourceURLStrings) { |
| 163 // chrome://resources/js/ios/core.js is skipped because it is |
| 164 // retrieved via webUIJavaScript rather than the net stack. |
| 165 if ([URLString isEqualToString:kWebUICoreJSURL]) { |
| 166 pendingSubresourceCount--; |
| 167 if (!pendingSubresourceCount) { |
| 168 [weakSelf flattenHTML:webUIHTML withSubresources:subresources]; |
| 169 completionHandler(webUIHTML); |
| 170 } |
| 171 continue; |
| 172 } |
| 173 GURL URL(pageURL.Resolve(base::SysNSStringToUTF8(URLString))); |
| 174 // If the resolved URL is different from URLString, replace URLString in |
| 175 // webUIHTML so that it will be located appropriately when flattening |
| 176 // occurs. |
| 177 if (URL.spec() != base::SysNSStringToUTF8(URLString)) { |
| 178 [webUIHTML replaceOccurrencesOfString:URLString |
| 179 withString:base::SysUTF8ToNSString(URL.spec()) |
| 180 options:0 |
| 181 range:NSMakeRange(0, [HTML length])]; |
| 182 } |
| 183 [self fetchResourceWithURL:URL completionHandler:subresourceHandler]; |
| 184 } |
| 185 } |
| 186 |
| 187 - (void)fetchResourceWithURL:(const GURL&)resourceURL |
| 188 completionHandler:(web::WebUIDelegateCompletion)handler { |
| 189 [_delegate webUIPageBuilder:self |
| 190 fetchResourceWithURL:resourceURL |
| 191 completionHandler:handler]; |
| 192 } |
| 193 |
| 194 - (void)fetchSubresourcesWithURLs:(const std::vector<GURL>&)resourceURLs |
| 195 completionHandler:(web::WebUIDelegateCompletion)handler { |
| 196 for (const GURL& URL : resourceURLs) { |
| 197 DCHECK([self isValidSubresourceURL:URL]); |
| 198 [self fetchResourceWithURL:URL completionHandler:handler]; |
| 199 } |
| 200 } |
| 201 |
| 202 - (NSSet*)URLStringsFromResource:(NSString*)resource |
| 203 prefix:(NSString*)prefix |
| 204 suffix:(NSString*)suffix { |
| 205 DCHECK(resource); |
| 206 DCHECK(prefix); |
| 207 DCHECK(suffix); |
| 208 NSMutableSet* URLStrings = [NSMutableSet set]; |
| 209 NSError* error = nil; |
| 210 NSString* tagPattern = [NSString |
| 211 stringWithFormat:@"%@(.*?)%@", |
| 212 [NSRegularExpression escapedPatternForString:prefix], |
| 213 [NSRegularExpression escapedPatternForString:suffix]]; |
| 214 NSRegularExpression* tagExpression = [NSRegularExpression |
| 215 regularExpressionWithPattern:tagPattern |
| 216 options:NSRegularExpressionCaseInsensitive |
| 217 error:&error]; |
| 218 if (error) { |
| 219 DLOG(WARNING) << "Error: " << error.description.UTF8String; |
| 220 } |
| 221 NSArray* matches = |
| 222 [tagExpression matchesInString:resource |
| 223 options:0 |
| 224 range:NSMakeRange(0, [resource length])]; |
| 225 for (NSTextCheckingResult* match in matches) { |
| 226 NSRange matchRange = [match rangeAtIndex:1]; |
| 227 DCHECK(matchRange.length); |
| 228 NSString* URLString = [resource substringWithRange:matchRange]; |
| 229 [URLStrings addObject:URLString]; |
| 230 } |
| 231 return URLStrings; |
| 232 } |
| 233 |
| 234 - (NSSet*)URLStringsFromHTML:(NSString*)HTML { |
| 235 NSSet* JS = [self URLStringsFromResource:HTML |
| 236 prefix:kJSTagPrefix |
| 237 suffix:kJSTagSuffix]; |
| 238 NSSet* CSS = [self URLStringsFromResource:HTML |
| 239 prefix:kCSSTagPrefix |
| 240 suffix:kCSSTagSuffix]; |
| 241 return [JS setByAddingObjectsFromSet:CSS]; |
| 242 } |
| 243 |
| 244 - (NSSet*)URLStringsFromCSS:(NSString*)CSS { |
| 245 NSString* prefix = @"@import url("; |
| 246 NSString* suffix = @");"; |
| 247 return [self URLStringsFromResource:CSS prefix:prefix suffix:suffix]; |
| 248 } |
| 249 |
| 250 - (BOOL)isValidSubresourceURL:(const GURL&)subresourceURL { |
| 251 base::FilePath resourcePath(subresourceURL.ExtractFileName()); |
| 252 std::string extension = resourcePath.Extension(); |
| 253 return extension == ".css" || extension == ".js"; |
| 254 } |
| 255 |
| 256 - (BOOL)isCSSSubresourceURL:(const GURL&)subresourceURL { |
| 257 base::FilePath resourcePath(subresourceURL.ExtractFileName()); |
| 258 std::string extension = resourcePath.Extension(); |
| 259 return extension == ".css"; |
| 260 } |
| 261 |
| 262 - (void)addCSSTagToHTML:(NSMutableString*)HTML |
| 263 forURL:(const GURL&)URL |
| 264 sourceURL:(const GURL&)sourceURL { |
| 265 NSString* URLString = base::SysUTF8ToNSString(URL.spec()); |
| 266 NSString* sourceURLString = base::SysUTF8ToNSString(sourceURL.spec()); |
| 267 NSString* sourceTag = |
| 268 [NSString stringWithFormat:@"%@%@%@", kCSSTagPrefix, sourceURLString, |
| 269 kCSSTagSuffix]; |
| 270 NSString* extendedTag = [[NSString |
| 271 stringWithFormat:@"%@%@%@", kCSSTagPrefix, URLString, kCSSTagSuffix] |
| 272 stringByAppendingString:sourceTag]; |
| 273 [HTML replaceOccurrencesOfString:sourceTag |
| 274 withString:extendedTag |
| 275 options:0 |
| 276 range:NSMakeRange(0, [HTML length])]; |
| 277 } |
| 278 |
| 279 - (void)flattenHTML:(NSMutableString*)HTML |
| 280 withSubresources:(std::map<GURL, std::string>)subresources { |
| 281 // Add core.js script to resources. |
| 282 GURL webUIJSURL("chrome://resources/js/ios/core.js"); |
| 283 subresources[webUIJSURL] = base::SysNSStringToUTF8([self webUIJavaScript]); |
| 284 for (auto it = subresources.begin(); it != subresources.end(); it++) { |
| 285 NSString* linkTemplate = @""; |
| 286 NSString* textTemplate = @""; |
| 287 if ([self isCSSSubresourceURL:it->first]) { |
| 288 linkTemplate = |
| 289 [NSString stringWithFormat:@"%@%%@%@", kCSSTagPrefix, kCSSTagSuffix]; |
| 290 textTemplate = kWebUIStyleTextTemplate; |
| 291 } else { // JavaScript. |
| 292 linkTemplate = |
| 293 [NSString stringWithFormat:@"%@%%@%@", kJSTagPrefix, kJSTagSuffix]; |
| 294 textTemplate = kWebUIScriptTextTemplate; |
| 295 } |
| 296 NSString* resourceURLString = base::SysUTF8ToNSString(it->first.spec()); |
| 297 NSString* linkTag = |
| 298 [NSString stringWithFormat:linkTemplate, resourceURLString]; |
| 299 NSString* resource = base::SysUTF8ToNSString(it->second); |
| 300 NSString* textTag = [NSString stringWithFormat:textTemplate, resource]; |
| 301 [HTML replaceOccurrencesOfString:linkTag |
| 302 withString:textTag |
| 303 options:0 |
| 304 range:NSMakeRange(0, [HTML length])]; |
| 305 } |
| 306 } |
| 307 |
| 308 - (NSString*)webUIJavaScript { |
| 309 NSBundle* bundle = base::mac::FrameworkBundle(); |
| 310 NSString* path = [bundle pathForResource:@"web_ui" ofType:@"js"]; |
| 311 DCHECK(path) << "web_ui.js file not found"; |
| 312 return [NSString stringWithContentsOfFile:path |
| 313 encoding:NSUTF8StringEncoding |
| 314 error:nil]; |
| 315 } |
| 316 |
| 317 @end |
OLD | NEW |