blob: 29d15563a9a41cbc737998b575766bb57f5a8e4f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2011 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
qx.Class.define( "org.eclipse.swt.browser.Browser", {
extend : qx.ui.embed.Iframe,
construct : function() {
this.base( arguments );
this._hasProgressListener = false;
this._browserFunctions = {};
// TODO [rh] preliminary workaround to make Browser accessible by tab
this.setTabIndex( 1 );
this.setAppearance( "browser" );
this.addEventListener( "create", this._onCreate, this );
},
properties : {
asynchronousResult : {
check : "Boolean",
init : false
},
executedFunctionPending : {
check : "Boolean",
init : false
},
executedFunctionResult : {
nullable : true,
init : null
},
executedFunctionError : {
check : "String",
nullable : true,
init : null
}
},
statics : {
getDomain : function( url ) {
var domain = null;
if( url !== null ) {
var lowerCaseUrl = url.toLowerCase();
// Accepted limitation: In case of other protocls this detection fails.
if( lowerCaseUrl.indexOf( "http://" ) === 0
|| lowerCaseUrl.indexOf( "https://" ) === 0
|| lowerCaseUrl.indexOf( "ftp://" ) === 0
|| lowerCaseUrl.indexOf( "ftps://" ) === 0
) {
var domain = lowerCaseUrl.slice( lowerCaseUrl.indexOf( "://" ) + 3 );
var pathStart = domain.indexOf( "/" );
if( pathStart !== -1 ) {
domain = domain.slice( 0, pathStart );
}
}
}
return domain;
}
},
members : {
syncSource : function() {
if( this.isCreated() ) {
this._syncSource();
}
},
// overwritten
_applySource : function( value, oldValue ) {
// server syncs manually
},
// overwritten
_applyEnabled : function( value, oldValue ) {
this.base( arguments, value, oldValue );
if( value ) {
this.release();
} else {
this.block();
}
},
// overwritten
release : function() {
if( this.getEnabled() ) {
this.base( arguments );
}
},
_onload : function( evt ) {
// syncSource in destroy may cause unwanted load event when widget is about to be disposed
if( !this._isInGlobalDisposeQueue ) {
this.base( arguments, evt );
if( this._isContentAccessible() ) {
this._attachBrowserFunctions();
}
this._sendProgressEvent();
}
},
_onCreate : function( evt ) {
if( !this.getEnabled() ) {
this.block();
}
},
_sendProgressEvent : function() {
if( this._hasProgressListener ) {
var widgetManager = org.eclipse.swt.WidgetManager.getInstance();
var req = org.eclipse.swt.Request.getInstance();
var id = widgetManager.findIdByWidget( this );
req.addEvent( "org.eclipse.swt.events.progressCompleted", id );
req.send();
}
},
setHasProgressListener : function( value ) {
this._hasProgressListener = value;
},
execute : function( script ) {
// NOTE [tb] : For some very strange reason the access check must not be done directly
// before the try-catch for the ipad to recognize the error is may throw.
this._checkIframeAccess();
var success = true;
var result = null;
try {
result = this._parseEvalResult( this._eval( script ) );
} catch( ex ) {
success = false;
}
var req = org.eclipse.swt.Request.getInstance();
var wm = org.eclipse.swt.WidgetManager.getInstance();
var id = wm.findIdByWidget( this );
req.addParameter( id + ".executeResult", success );
req.addParameter( id + ".evaluateResult", result );
if( this.getExecutedFunctionPending() ) {
req.sendSyncronous();
} else {
req.send();
}
},
_srcInLocalDomain : function() {
var src = this.getSource();
var statics = org.eclipse.swt.browser.Browser;
var localDomain = statics.getDomain( document.URL );
var srcDomain = statics.getDomain( src );
var isSameDomain = localDomain === srcDomain;
var isRelative = srcDomain === null;
return isRelative || isSameDomain;
},
_isContentAccessible : function() {
var accessible;
try{
this.getContentDocument().body.URL;
accessible = true;
} catch( ex ) {
accessible = false;
}
return accessible && this._isLoaded;
},
_checkIframeAccess : function( functionName ) {
if( !this._isContentAccessible() ) {
var isSameDomain = this._srcInLocalDomain();
if( !isSameDomain ) {
this._throwSecurityException( false );
}
if( this._isLoaded && isSameDomain ) {
// not accessible when it appears it should be
// => user navigated to external site.
this._throwSecurityException( true );
}
}
},
_throwSecurityException : function( domainUnkown ) {
var statics = org.eclipse.swt.browser.Browser;
var localDomain = statics.getDomain( document.URL );
var srcDomain = domainUnkown ? null : statics.getDomain( this.getSource() );
var msg = "SecurityRestriction:\nBrowser-Widget can not access "
msg += srcDomain !== null
? "\"" + srcDomain + "\""
: "unkown domain";
msg += " from \"" + localDomain + "\".";
throw new Error( msg );
},
_eval : function( script ) {
var win = this.getContentWindow();
if( !win.eval && win.execScript ) {
// Workaround for IE bug, see: http://www.thismuchiknow.co.uk/?p=25
win.execScript( "null;", "JScript" );
}
return win.eval( script );
},
_parseEvalResult : function( value ) {
var result = null;
var win;
if( qx.core.Variant.isSet( "qx.client", "gecko" )
&& org.eclipse.rwt.Client.getVersion() < 2 )
{
// in older gecko the prototypes from the parent-frame are used
win = window;
} else {
win = this.getContentWindow();
}
// NOTE: This mimics the behavior of the evaluate method in SWT:
if( value instanceof win.Function ) {
result = this.objectToString( [ [] ] );
} else if( value instanceof win.Array ) {
result = this.objectToString( [ value ] );
} else if( typeof value !== "object" && typeof value !== "function" ) {
// above: some browser say regular expressions of the type "function"
result = this.objectToString( [ value ] );
}
return result;
},
createFunction : function( name ) {
this._browserFunctions[ name ] = true;
this._checkIframeAccess();
if( this.isLoaded() ) {
try {
this._createFunctionImpl( name );
this._createFunctionWrapper( name );
} catch( e ) {
var msg = "Unable to create function: \"" + name + "\".\n" + e;
if( org.eclipse.swt.EventUtil.getSuspended() ) {
throw msg;
} else {
org.eclipse.rwt.ErrorHandler.processJavaScriptError( msg );
}
}
}
},
_attachBrowserFunctions : function() {
// NOTE: In case the user navigates to a page outside the domain,
// this function will not be triggered due to the lack of a loading event.
// That also means that this is the only case were creating a browser
// function in a cross-domain scenario silently fails.
for( var name in this._browserFunctions ) {
this.createFunction( name );
}
},
_createFunctionImpl : function( name ) {
var win = this.getContentWindow();
var req = org.eclipse.swt.Request.getInstance();
var widgetManager = org.eclipse.swt.WidgetManager.getInstance();
var id = widgetManager.findIdByWidget( this );
var that = this;
win[ name + "_impl" ] = function() {
var result = {};
if( that.getExecutedFunctionPending() ) {
result.error = "Unable to execute browser function \""
+ name
+ "\". Another browser function is still pending.";
} else {
var args = that.objectToString( arguments );
req.addParameter( id + ".executeFunction", name );
req.addParameter( id + ".executeArguments", args );
that.setExecutedFunctionResult( null );
that.setExecutedFunctionError( null );
that.setExecutedFunctionPending( true );
that.setAsynchronousResult( false );
req.sendSyncronous();
if( that.getExecutedFunctionPending() ) {
that.setAsynchronousResult( true );
} else {
var error = that.getExecutedFunctionError();
if( error != null ) {
result.error = error;
} else {
result.result = that.getExecutedFunctionResult();
}
}
}
return result;
}
},
// [if] This wrapper function is a workaround for bug 332313
_createFunctionWrapper : function( name ) {
var script = [];
script.push( "function " + name + "(){" );
script.push( " var result = " + name + "_impl.apply( window, arguments );" );
script.push( " if( result.error ) {" );
script.push( " throw new Error( result.error );" );
script.push( " }" );
script.push( " return result.result;" );
script.push( "}");
this._eval( script.join( "" ) );
},
destroyFunction : function( name ) {
delete this._browserFunctions[ name ];
var win = this.getContentWindow();
if( win != null ) {
try {
var script = [];
if( qx.core.Variant.isSet( "qx.client", "mshtml" ) ) {
script.push( "window." + name + " = undefined;" );
script.push( "window." + name + "_impl = undefined;" );
} else {
script.push( "delete window." + name + ";" );
script.push( "delete window." + name + "_impl;" );
}
this._eval( script.join( "" ) );
} catch( e ) {
throw new Error( "Unable to destroy function: " + name + " error: " + e );
}
}
},
setFunctionResult : function( name, result, error ) {
this.setExecutedFunctionResult( result );
this.setExecutedFunctionError( error );
this.setExecutedFunctionPending( false );
},
objectToString : function( object ) {
var result;
var type = typeof( object );
if( object === null ) {
result = String( object );
} else if( type == "object" ) {
result = [];
for( var i = 0; i < object.length; i++ ) {
var value = object[ i ];
type = typeof( value );
if( type == "string" ) {
value = '"' + value.replace( /"/g, "\\\"" ) + '"';
} else if( type == "object" && value !== null ) {
value = this.objectToString( value );
}
result.push( String( value ) );
}
result = "[" + String( result ) + "]";
} else if( type == "string" ) {
result = '"' + object.replace( /"/g, "\\\"" ) + '"';
} else {
result = String( object );
}
return result;
},
destroy : function() {
this.base( arguments );
// Needed for IE dipose fix in Iframe.js because _applySource is overwritten in Browser.js
this.syncSource();
}
}
} );