Index: ios/chrome/browser/ui/static_content/static_html_view_controller.mm |
diff --git a/ios/chrome/browser/ui/static_content/static_html_view_controller.mm b/ios/chrome/browser/ui/static_content/static_html_view_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e22d51be30dfc15ce6ccfdcb8eba00aa3f3cc1c9 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/static_content/static_html_view_controller.mm |
@@ -0,0 +1,296 @@ |
+// Copyright 2012 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/chrome/browser/ui/static_content/static_html_view_controller.h" |
+ |
+#include <WebKit/WebKit.h> |
+ |
+#include <stdlib.h> |
+ |
+#include "base/logging.h" |
+#include "base/mac/bundle_locations.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "ios/web/public/referrer.h" |
+#import "ios/web/public/web_state/ui/crw_native_content.h" |
+#import "ios/web/public/web_view_creation_util.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "ui/base/page_transition_types.h" |
+#include "ui/base/resource/resource_bundle.h" |
+ |
+@implementation IdrHtmlGenerator |
+- (id)initWithResourceId:(int)resourceId encoding:(NSStringEncoding)encoding { |
+ if ((self = [super init])) { |
+ resourceId_ = resourceId; |
+ encoding_ = encoding; |
+ } |
+ return self; |
+} |
+ |
+- (id)initWithResourceId:(int)resourceId { |
+ return [self initWithResourceId:resourceId encoding:NSUTF8StringEncoding]; |
+} |
+ |
+- (void)generateHtml:(HtmlCallback)callback { |
+ base::StringPiece html( |
+ ResourceBundle::GetSharedInstance().GetRawDataResource(resourceId_)); |
+ base::scoped_nsobject<NSString> result([[NSString alloc] |
+ initWithBytes:html.data() |
+ length:html.size() |
+ encoding:encoding_]); |
+ callback(result); |
+} |
+@end |
+ |
+@interface StaticHtmlViewController ()<WKNavigationDelegate> { |
+ @private |
+ // The referrer that will be passed when navigating from this page. |
+ web::Referrer referrer_; |
+ |
+ // The URL that will be passed to the web page when loading. |
+ // If the page is displaying a local HTML file, it contains the file URL to |
+ // the file. |
+ // If the page is a generated HTML, it contains a random resource URL. |
+ base::scoped_nsobject<NSURL> resourceUrl_; |
+ |
+ // If the view is displaying a local file, contains the URL of the root |
+ // directory containing all resources needed by the page. |
+ // The web view will get access to this page. |
+ base::scoped_nsobject<NSURL> resourcesRootDirectory_; |
+ |
+ // If the view is displaying a resources, contains the name of the resource. |
+ base::scoped_nsobject<NSString> resource_; |
+ |
+ // If the view displayes a generated HTML, contains the |HtmlGenerator| to |
+ // generate it. |
+ base::scoped_nsprotocol<id<HtmlGenerator>> generator_; |
+ |
+ // Browser state associated with the view controller, used to create the |
+ // WKWebView. |
+ web::BrowserState* browserState_; // Weak. |
+ |
+ // The web view that is used to display the content. |
+ base::scoped_nsobject<WKWebView> webView_; |
+ |
+ // The delegate of the native content. |
+ id<CRWNativeContentDelegate> delegate_; // weak |
+ |
+ // The loader to navigate from the page. |
+ id<UrlLoader> loader_; // weak |
+} |
+ |
+// Returns the URL of the static page to display. |
+- (NSURL*)resourceURL; |
+// Ensures that webView_ has been created, creating it if necessary. |
+- (void)ensureWebViewCreated; |
+// Determines if the page load should begin based on the current |resourceURL|. |
+- (BOOL)shouldStartLoadWithRequest:(NSURLRequest*)request; |
+@end |
+ |
+@implementation StaticHtmlViewController |
+ |
+- (instancetype)initWithResource:(NSString*)resource |
+ browserState:(web::BrowserState*)browserState { |
+ DCHECK(resource); |
+ DCHECK(browserState); |
+ if ((self = [super init])) { |
+ resource_.reset([resource copy]); |
+ browserState_ = browserState; |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)initWithGenerator:(id<HtmlGenerator>)generator |
+ browserState:(web::BrowserState*)browserState { |
+ DCHECK(generator); |
+ DCHECK(browserState); |
+ if ((self = [super init])) { |
+ generator_.reset([generator retain]); |
+ browserState_ = browserState; |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)initWithFileURL:(const GURL&)URL |
+ allowingReadAccessToURL:(const GURL&)resourcesRoot |
+ browserState:(web::BrowserState*)browserState { |
+ DCHECK(URL.is_valid()); |
+ DCHECK(URL.SchemeIsFile()); |
+ DCHECK(browserState); |
+ if ((self = [super init])) { |
+ resourceUrl_.reset([net::NSURLWithGURL(URL) retain]); |
+ resourcesRootDirectory_.reset([net::NSURLWithGURL(resourcesRoot) retain]); |
+ browserState_ = browserState; |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [self removeWebViewObservers]; |
+ [super dealloc]; |
+} |
+ |
+- (void)removeWebViewObservers { |
+ [webView_ removeObserver:self forKeyPath:@"title"]; |
+} |
+ |
+- (void)setLoader:(id<UrlLoader>)loader |
+ referrer:(const web::Referrer&)referrer { |
+ loader_ = loader; |
+ referrer_ = referrer; |
+} |
+ |
+- (void)executeJavaScript:(NSString*)script |
+ completionHandler:(web::JavaScriptResultBlock)handler { |
+ [webView_ evaluateJavaScript:script completionHandler:handler]; |
+} |
+ |
+- (UIScrollView*)scrollView { |
+ return [[self webView] scrollView]; |
+} |
+ |
+- (WKWebView*)webView { |
+ [self ensureWebViewCreated]; |
+ return webView_; |
+} |
+ |
+- (void)handleLowMemory { |
+ [self removeWebViewObservers]; |
+ webView_.reset(); |
+} |
+ |
+- (BOOL)isViewAlive { |
+ return webView_.get() != nil; |
+} |
+ |
+- (NSString*)title { |
+ return [[self webView] title]; |
+} |
+ |
+- (void)reload { |
+ if (!generator_) { |
+ [webView_ reload]; |
+ } else { |
+ NSURL* resourceURL = [self resourceURL]; |
+ [generator_ generateHtml:^(NSString* HTML) { |
+ [webView_ loadHTMLString:HTML baseURL:resourceURL]; |
+ }]; |
+ } |
+} |
+ |
+- (void)triggerPendingLoad { |
+ // Ensure that the web view is created, which triggers loading. |
+ [self ensureWebViewCreated]; |
+} |
+ |
+- (void)setDelegate:(id<CRWNativeContentDelegate>)delegate { |
+ delegate_ = delegate; |
+} |
+ |
+- (void)setScrollEnabled:(BOOL)enabled { |
+ [[self scrollView] setScrollEnabled:enabled]; |
+} |
+ |
+#pragma mark - |
+#pragma mark WKNavigationDelegate implementation |
+ |
+- (void)webView:(WKWebView*)webView |
+ decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction |
+ decisionHandler: |
+ (void (^)(WKNavigationActionPolicy))decisionHandler { |
+ decisionHandler([self shouldStartLoadWithRequest:navigationAction.request] |
+ ? WKNavigationActionPolicyAllow |
+ : WKNavigationActionPolicyCancel); |
+} |
+ |
+#pragma mark - |
+#pragma mark KVO callback |
+ |
+- (void)observeValueForKeyPath:(NSString*)keyPath |
+ ofObject:(id)object |
+ change:(NSDictionary*)change |
+ context:(void*)context { |
+ DCHECK([keyPath isEqualToString:@"title"]); |
+ if ([delegate_ respondsToSelector:@selector(nativeContent:titleDidChange:)]) { |
+ // WKWebView's |title| changes to nil when its web process crashes. |
+ if ([webView_ title]) |
+ [delegate_ nativeContent:self titleDidChange:[webView_ title]]; |
+ } |
+} |
+ |
+#pragma mark - |
+#pragma mark Private |
+ |
+- (BOOL)shouldStartLoadWithRequest:(NSURLRequest*)request { |
+ // Only allow displaying the URL which correspond to the authorized resource. |
+ if ([[request URL] isEqual:[self resourceURL]]) |
+ return YES; |
+ |
+ // All other URLs will be loaded by our UrlLoader if we have one. |
+ if (loader_) { |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [loader_ loadURL:net::GURLWithNSURL([request URL]) |
+ referrer:referrer_ |
+ transition:ui::PAGE_TRANSITION_LINK |
+ rendererInitiated:YES]; |
+ }); |
+ } |
+ return NO; |
+} |
+ |
+- (NSURL*)resourceURL { |
+ if (resourceUrl_) |
+ return resourceUrl_.get(); |
+ |
+ DCHECK(resource_ || generator_); |
+ NSString* path = nil; |
+ if (resource_) { |
+ NSBundle* bundle = base::mac::FrameworkBundle(); |
+ NSString* bundlePath = [bundle bundlePath]; |
+ path = [bundlePath stringByAppendingPathComponent:resource_.get()]; |
+ } else { |
+ // Generate a random resource URL to whitelist the load in |
+ // |webView:shouldStartLoadWithRequest:navigationType:| method. |
+ path = [NSString stringWithFormat:@"/whitelist%u%u%u%u", arc4random(), |
+ arc4random(), arc4random(), arc4random()]; |
+ } |
+ DCHECK(path); |
+ // Necessary because the |fileURLWithPath:| method adds a localhost in the |
+ // URL, and this prevents the URL from being comparable with the ones that |
+ // UIWebView uses when calling the delegate. |
+ base::scoped_nsobject<NSURLComponents> components( |
+ [[NSURLComponents alloc] init]); |
+ [components setScheme:@"file"]; |
+ [components setHost:@""]; |
+ [components setPath:path]; |
+ resourceUrl_.reset([[components URL] retain]); |
+ resourcesRootDirectory_.reset([resourceUrl_ copy]); |
+ return resourceUrl_; |
+} |
+ |
+- (void)ensureWebViewCreated { |
+ if (!webView_) { |
+ WKWebView* webView = web::BuildWKWebView(CGRectZero, browserState_); |
+ [webView addObserver:self forKeyPath:@"title" options:0 context:nullptr]; |
+ [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleHeight]; |
+ [self loadWebViewContents:webView]; |
+ [webView setNavigationDelegate:self]; |
+ webView_.reset([webView retain]); |
+ } |
+} |
+ |
+- (void)loadWebViewContents:(WKWebView*)webView { |
+ if (!generator_) { |
+ [webView loadFileURL:[self resourceURL] |
+ allowingReadAccessToURL:resourcesRootDirectory_]; |
+ } else { |
+ NSURL* resourceURL = [self resourceURL]; |
+ [generator_ generateHtml:^(NSString* HTML) { |
+ [webView loadHTMLString:HTML baseURL:resourceURL]; |
+ }]; |
+ } |
+} |
+ |
+@end |