| // 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" |
| |
| #import <WebKit/WebKit.h> |
| |
| #include <stdlib.h> |
| |
| #include "base/logging.h" |
| #include "base/mac/bundle_locations.h" |
| #include "base/mac/foundation_util.h" |
| #include "ios/web/public/referrer.h" |
| #import "ios/web/public/web_state/ui/crw_context_menu_delegate.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" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| @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_)); |
| NSString* result = [[NSString alloc] initWithBytes:html.data() |
| length:html.size() |
| encoding:encoding_]; |
| callback(result); |
| } |
| @end |
| |
| @interface StaticHtmlViewController ()<CRWContextMenuDelegate, |
| 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. |
| 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. |
| NSURL* resourcesRootDirectory_; |
| |
| // If the view is displaying a resources, contains the name of the resource. |
| NSString* resource_; |
| |
| // If the view displayes a generated HTML, contains the |HtmlGenerator| to |
| // generate it. |
| 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. |
| WKWebView* webView_; |
| |
| // The delegate of the native content. |
| __weak id<CRWNativeContentDelegate> delegate_; |
| |
| // The loader to navigate from the page. |
| __weak id<UrlLoader> loader_; |
| } |
| |
| // 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| |
| // and if the request is issued by the main frame (|fromMainFrame|). |
| - (BOOL)shouldStartLoadWithRequest:(NSURLRequest*)request |
| fromMainFrame:(BOOL)fromMainFrame; |
| @end |
| |
| @implementation StaticHtmlViewController |
| |
| - (instancetype)initWithResource:(NSString*)resource |
| browserState:(web::BrowserState*)browserState { |
| DCHECK(resource); |
| DCHECK(browserState); |
| if ((self = [super init])) { |
| resource_ = [resource copy]; |
| browserState_ = browserState; |
| } |
| return self; |
| } |
| |
| - (instancetype)initWithGenerator:(id<HtmlGenerator>)generator |
| browserState:(web::BrowserState*)browserState { |
| DCHECK(generator); |
| DCHECK(browserState); |
| if ((self = [super init])) { |
| generator_ = generator; |
| 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_ = net::NSURLWithGURL(URL); |
| resourcesRootDirectory_ = net::NSURLWithGURL(resourcesRoot); |
| browserState_ = browserState; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| [self removeWebViewObservers]; |
| } |
| |
| - (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_ = nil; |
| } |
| |
| - (BOOL)isViewAlive { |
| return webView_ != 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 |
| fromMainFrame:[navigationAction.targetFrame isMainFrame]] |
| ? WKNavigationActionPolicyAllow |
| : WKNavigationActionPolicyCancel); |
| } |
| |
| #pragma mark - |
| #pragma mark CRWContextMenuDelegate implementation |
| |
| - (BOOL)webView:(WKWebView*)webView |
| handleContextMenu:(const web::ContextMenuParams&)params { |
| if ([delegate_ |
| respondsToSelector:@selector(nativeContent:handleContextMenu:)]) { |
| return [delegate_ nativeContent:self handleContextMenu:params]; |
| } |
| return NO; |
| } |
| |
| #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 |
| fromMainFrame:(BOOL)fromMainFrame { |
| // Only allow displaying the URL which correspond to the authorized resource. |
| if ([[request URL] isEqual:[self resourceURL]]) |
| return YES; |
| |
| // All other navigation URLs will be loaded by our UrlLoader if one exists and |
| // if they are issued by the main frame. |
| if (loader_ && fromMainFrame) { |
| 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_; |
| |
| DCHECK(resource_ || generator_); |
| NSString* path = nil; |
| if (resource_) { |
| NSBundle* bundle = base::mac::FrameworkBundle(); |
| NSString* bundlePath = [bundle bundlePath]; |
| path = [bundlePath stringByAppendingPathComponent:resource_]; |
| } 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. |
| NSURLComponents* components = [[NSURLComponents alloc] init]; |
| [components setScheme:@"file"]; |
| [components setHost:@""]; |
| [components setPath:path]; |
| resourceUrl_ = [components URL]; |
| resourcesRootDirectory_ = [resourceUrl_ copy]; |
| return resourceUrl_; |
| } |
| |
| - (void)ensureWebViewCreated { |
| if (!webView_) { |
| WKWebView* webView = web::BuildWKWebViewWithCustomContextMenu( |
| CGRectZero, browserState_, self); |
| [webView addObserver:self forKeyPath:@"title" options:0 context:nullptr]; |
| [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| UIViewAutoresizingFlexibleHeight]; |
| [self loadWebViewContents:webView]; |
| [webView setNavigationDelegate:self]; |
| webView_ = webView; |
| } |
| } |
| |
| - (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 |