Files
cappuccino/AppKit/CPWebView.j
T

626 lines
14 KiB
Plaintext

/*
* CPWebView.j
* AppKit
*
* Created by Thomas Robinson.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <AppKit/CPView.j>
#include "Platform/Platform.h"
// FIXME: implement these where possible:
/*
CPWebViewDidBeginEditingNotification = "CPWebViewDidBeginEditingNotification";
CPWebViewDidChangeNotification = "CPWebViewDidChangeNotification";
CPWebViewDidChangeSelectionNotification = "CPWebViewDidChangeSelectionNotification";
CPWebViewDidChangeTypingStyleNotification = "CPWebViewDidChangeTypingStyleNotification";
CPWebViewDidEndEditingNotification = "CPWebViewDidEndEditingNotification";
CPWebViewProgressEstimateChangedNotification = "CPWebViewProgressEstimateChangedNotification";
*/
CPWebViewProgressStartedNotification = "CPWebViewProgressStartedNotification";
CPWebViewProgressFinishedNotification = "CPWebViewProgressFinishedNotification";
CPWebViewScrollAppKit = 1;
CPWebViewScrollNative = 2;
// FIXME: somehow make CPWebView work with CPScrollView instead of native scrollbars (is this even possible?)
/*!
@ingroup appkit
*/
@implementation CPWebView : CPView
{
CPScrollView _scrollView;
CPView _frameView;
IFrame _iframe;
CPString _mainFrameURL;
CPArray _backwardStack;
CPArray _forwardStack;
BOOL _ignoreLoadStart;
BOOL _ignoreLoadEnd;
id _downloadDelegate;
id _frameLoadDelegate;
id _policyDelegate;
id _resourceLoadDelegate;
id _UIDelegate;
CPWebScriptObject _wso;
CPString _url;
CPString _html;
Function _loadCallback;
int _scrollMode;
CGSize _scrollSize;
}
- (id)initWithFrame:(CPRect)frameRect frameName:(CPString)frameName groupName:(CPString)groupName
{
if (self = [self initWithFrame:frameRect])
{
_iframe.name = frameName;
}
return self
}
- (id)initWithFrame:(CPRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
_mainFrameURL = nil;
_backwardStack = [];
_forwardStack = [];
_scrollMode = CPWebViewScrollNative;
[self _initDOMWithFrame:aFrame];
}
return self;
}
- (id)_initDOMWithFrame:(CPRect)aFrame
{
_ignoreLoadStart = YES;
_ignoreLoadEnd = YES;
_iframe = document.createElement("iframe");
_iframe.name = "iframe_" + Math.floor(Math.random()*10000);
_iframe.style.width = "100%";
_iframe.style.height = "100%";
_iframe.style.borderWidth = "0px";
[self setDrawsBackground:YES];
_loadCallback = function() {
// HACK: this block handles the case where we don't know about loads initiated by the user clicking a link
if (!_ignoreLoadStart)
{
// post the start load notification
[self _startedLoading];
if (_mainFrameURL)
[_backwardStack addObject:_mainFrameURL];
// FIXME: this doesn't actually get the right URL for different domains. Not possible due to browser security restrictions.
_mainFrameURL = _iframe.src;
_mainFrameURL = _iframe.src;
// clear the forward
[_forwardStack removeAllObjects];
}
else
_ignoreLoadStart = NO;
if (!_ignoreLoadEnd)
{
[self _finishedLoading];
}
else
_ignoreLoadEnd = NO;
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}
if (_iframe.addEventListener)
_iframe.addEventListener("load", _loadCallback, false);
else if (_iframe.attachEvent)
_iframe.attachEvent("onload", _loadCallback);
_frameView = [[CPView alloc] initWithFrame:[self bounds]];
[_frameView setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable];
_scrollView = [[CPScrollView alloc] initWithFrame:[self bounds]];
[_scrollView setAutohidesScrollers:YES];
[_scrollView setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable];
[_scrollView setDocumentView:_frameView];
_frameView._DOMElement.appendChild(_iframe);
[self _setScrollMode:_scrollMode];
[self addSubview:_scrollView];
}
- (void)setFrameSize:(CPSize)aSize
{
[super setFrameSize:aSize];
[self _resizeWebFrame];
}
- (BOOL)_resizeWebFrame
{
if (_scrollMode === CPWebViewScrollAppKit)
{
if (_scrollSize)
{
[_frameView setFrameSize:_scrollSize];
}
else
{
var visibleRect = [_frameView visibleRect];
[_frameView setFrameSize:CGSizeMake(CGRectGetMaxX(visibleRect), CGRectGetMaxY(visibleRect))];
// try to get the document size so we can correctly set the frame
var win = null;
try { win = [self DOMWindow]; } catch (e) {}
if (win && win.document && win.document.body)
{
var width = win.document.body.scrollWidth,
height = win.document.body.scrollHeight;
_iframe.setAttribute("width", width);
_iframe.setAttribute("height", height);
[_frameView setFrameSize:CGSizeMake(width, height)];
}
else
{
CPLog.warn("using default size 800*1600");
[_frameView setFrameSize:CGSizeMake(800, 1600)];
}
[_frameView scrollRectToVisible:visibleRect];
}
}
}
- (void)setScrollMode:(int)aScrollMode
{
if (_scrollMode == aScrollMode)
return;
[self _setScrollMode:aScrollMode];
}
- (void)_setScrollMode:(int)aScrollMode
{
_scrollMode = aScrollMode;
_ignoreLoadStart = YES;
_ignoreLoadEnd = YES;
var parent = _iframe.parentNode;
parent.removeChild(_iframe);
if (_scrollMode === CPWebViewScrollAppKit)
{
[_scrollView setHasHorizontalScroller:YES];
[_scrollView setHasVerticalScroller:YES];
_iframe.setAttribute("scrolling", "no");
}
else
{
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setHasVerticalScroller:NO];
_iframe.setAttribute("scrolling", "auto");
[_frameView setFrameSize:[_scrollView bounds].size];
}
parent.appendChild(_iframe);
}
- (void)loadHTMLString:(CPString)aString
{
[self loadHTMLString:aString baseURL:nil];
}
- (void)loadHTMLString:(CPString)aString baseURL:(CPURL)URL
{
// FIXME: do something with baseURL?
[self _setScrollMode:CPWebViewScrollAppKit];
[_frameView setFrameSize:[_scrollView contentSize]];
[self _startedLoading];
_ignoreLoadStart = YES;
_ignoreLoadEnd = NO;
_url = null;
_html = aString;
[self _load];
}
- (void)_loadMainFrameURL
{
[self _setScrollMode:CPWebViewScrollNative];
[self _startedLoading];
_ignoreLoadStart = YES;
_ignoreLoadEnd = NO;
_url = _mainFrameURL;
_html = null;
[self _load];
}
- (void)_load
{
if (_url)
{
_iframe.src = _url;
}
else if (_html)
{
// clear the iframe
_iframe.src = "";
// need to give the browser a chance to reset iframe, otherwise we'll be document.write()-ing the previous document
window.setTimeout(function() {
var win = [self DOMWindow];
win.document.write(_html);
window.setTimeout(_loadCallback, 1);
}, 0);
}
}
- (void)_startedLoading
{
[[CPNotificationCenter defaultCenter] postNotificationName:CPWebViewProgressStartedNotification object:self];
if ([_frameLoadDelegate respondsToSelector:@selector(webView:didStartProvisionalLoadForFrame:)])
[_frameLoadDelegate webView:self didStartProvisionalLoadForFrame:nil]; // FIXME: give this a frame somehow?
}
- (void)_finishedLoading
{
[self _resizeWebFrame];
[[CPNotificationCenter defaultCenter] postNotificationName:CPWebViewProgressFinishedNotification object:self];
if ([_frameLoadDelegate respondsToSelector:@selector(webView:didFinishLoadForFrame:)])
[_frameLoadDelegate webView:self didFinishLoadForFrame:nil]; // FIXME: give this a frame somehow?
}
- (CPString)mainFrameURL
{
return _mainFrameURL;
}
- (void)setMainFrameURL:(CPString)URLString
{
if (_mainFrameURL)
[_backwardStack addObject:_mainFrameURL];
_mainFrameURL = URLString;
[_forwardStack removeAllObjects];
[self _loadMainFrameURL];
}
- (BOOL)goBack
{
if (_backwardStack.length > 0)
{
if (_mainFrameURL)
[_forwardStack addObject:_mainFrameURL];
_mainFrameURL = [_backwardStack lastObject];
[_backwardStack removeLastObject];
[self _loadMainFrameURL];
return YES;
}
return NO;
}
- (BOOL)goForward
{
if (_forwardStack.length > 0)
{
if (_mainFrameURL)
[_backwardStack addObject:_mainFrameURL];
_mainFrameURL = [_forwardStack lastObject];
[_forwardStack removeLastObject];
[self _loadMainFrameURL];
return YES;
}
return NO;
}
- (BOOL)canGoBack
{
return (_backwardStack.length > 0);
}
- (BOOL)canGoForward
{
return (_forwardStack.length > 0);
}
- (WebBackForwardList)backForwardList
{
// FIXME: return a real WebBackForwardList?
return { back: _backwardStack, forward: _forwardStack };
}
- (void)close
{
_iframe.parentNode.removeChild(_iframe);
}
- (DOMWindow)DOMWindow
{
return (_iframe.contentDocument && _iframe.contentDocument.defaultView) || _iframe.contentWindow;
}
- (CPWebScriptObject)windowScriptObject
{
var win = [self DOMWindow];
if (!_wso || win != [_wso window])
{
if (win)
_wso = [[CPWebScriptObject alloc] initWithWindow:win];
else
_wso = nil;
}
return _wso;
}
- (CPString)stringByEvaluatingJavaScriptFromString:(CPString)script
{
var result = [self objectByEvaluatingJavaScriptFromString:script];
return result ? String(result) : nil;
}
- (JSObject)objectByEvaluatingJavaScriptFromString:(CPString)script
{
return [[self windowScriptObject] evaluateWebScript:script];
}
- (DOMCSSStyleDeclaration)computedStyleForElement:(DOMElement)element pseudoElement:(CPString)pseudoElement
{
var win = [[self windowScriptObject] window];
if (win)
{
// FIXME: IE version?
return win.document.defaultView.getComputedStyle(element, pseudoElement);
}
return nil;
}
- (BOOL)drawsBackground
{
return _iframe.style.backgroundColor != "";
}
- (void)setDrawsBackground:(BOOL)drawsBackround
{
_iframe.style.backgroundColor = drawsBackround ? "white" : "";
}
// IBActions
- (IBAction)takeStringURLFrom:(id)sender
{
[self setMainFrameURL:[sender stringValue]];
}
- (IBAction)goBack:(id)sender
{
[self goBack];
}
- (IBAction)goForward:(id)sender
{
[self goForward];
}
- (IBAction)stopLoading:(id)sender
{
// FIXME: what to do?
}
- (IBAction)reload:(id)sender
{
[self _loadMainFrameURL];
}
- (IBAction)print:(id)sender
{
try
{
[self DOMWindow].print();
}
catch (e)
{
alert('Please click the webpage and select "Print" from the "File" menu');
}
}
// Delegates:
// FIXME: implement more delegates, though most of these will likely never work with the iframe implementation
- (id)downloadDelegate
{
return _downloadDelegate;
}
- (void)setDownloadDelegate:(id)anObject
{
_downloadDelegate = anObject;
}
- (id)frameLoadDelegate
{
return _frameLoadDelegate;
}
- (void)setFrameLoadDelegate:(id)anObject
{
_frameLoadDelegate = anObject;
}
- (id)policyDelegate
{
return _policyDelegate;
}
- (void)setPolicyDelegate:(id)anObject
{
_policyDelegate = anObject;
}
- (id)resourceLoadDelegate
{
return _resourceLoadDelegate;
}
- (void)setResourceLoadDelegate:(id)anObject
{
_resourceLoadDelegate = anObject;
}
- (id)UIDelegate
{
return _UIDelegate;
}
- (void)setUIDelegate:(id)anObject
{
_UIDelegate = anObject;
}
@end
@implementation CPWebScriptObject : CPObject
{
Window _window;
}
- (id)initWithWindow:(Window)aWindow
{
if (self = [super init])
{
_window = aWindow;
}
return self;
}
- (id)callWebScriptMethod:(CPString)methodName withArguments:(CPArray)args
{
// Would using "with" be better here?
if (typeof _window[methodName] == "function")
{
try {
return _window[methodName].apply(args);
} catch (e) {
}
}
return undefined;
}
- (id)evaluateWebScript:(CPString)script
{
try {
return _window.eval(script);
} catch (e) {
}
return undefined;
}
- (Window)window
{
return _window;
}
@end
@implementation CPWebView (CPCoding)
/*!
Initializes the web view from the data in a coder.
@param aCoder the coder from which to read the data
@return the initialized web view
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
// FIXME: encode/decode these?
_mainFrameURL = nil;
_backwardStack = [];
_forwardStack = [];
_scrollMode = CPWebViewScrollNative;
#if PLATFORM(DOM)
[self _initDOMWithFrame:[self frame]];
#endif
[self setBackgroundColor:[CPColor whiteColor]];
}
return self;
}
/*!
Writes out the web view's instance information to a coder.
@param aCoder the coder to which to write the data
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
var actualSubviews = _subviews;
_subviews = [];
[super encodeWithCoder:aCoder];
_subviews = actualSubviews;
}
@end