Files
OpenSceneGraph/examples/osgbrowser/llembeddedbrowserwindow.cpp
2008-11-18 17:20:45 +00:00

1419 lines
47 KiB
C++

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Linden Lab Inc. (http://lindenlab.com) code.
*
* The Initial Developer of the Original Code is:
* Callum Prentice (callum@ubrowser.com)
*
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Callum Prentice (callum@ubrowser.com)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// seems to be required by LibXUL/Mozilla code to avoid crashes in their debug code, but only on Windows.
// undef'd at end of this
#ifdef _DEBUG
#ifdef WIN32
#define DEBUG 1
#endif
#endif
// needed for the code in LLEmbeddedBrowserWindow::NotifyInvalidated() which will
// one day be moved to platform agnostic code when I find out how...
#ifdef WIN32
#include "windows.h"
#endif
#include "llembeddedbrowser.h"
#include "llembeddedbrowserwindow.h"
// Mozilla code has non-virtual destructors
#ifdef WIN32
#pragma warning( disable : 4291 ) // (no matching operator delete found; memory will not be freed if initialization throws an exception)
#pragma warning( disable : 4265 ) // "class has virtual functions, but destructor is not virtual"
#endif
#include "nsCWebBrowser.h"
#include "nsGUIEvent.h"
#include "nsICaret.h"
#include "nsIContent.h"
#include "nsIDOMDocument.h"
#include "nsIDOMElement.h"
#include "nsIDOMWindow.h"
#include "nsIDOMEvent.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocument.h"
#include "nsIFrame.h"
#include "nsIHttpChannel.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIScrollableView.h"
#include "nsISelection.h"
#include "nsISelectionController.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebBrowserChromeFocus.h"
#include "nsIWebBrowserFocus.h"
#include "nsIWebProgress.h"
#include "nsIWebProgressListener.h"
#include "nsPresContext.h"
#include "nsProfileDirServiceProvider.h"
#include "nsXPCOMGlue.h"
#include "nsXULAppAPI.h"
#include "llembeddedbrowserwindow.h"
#include <iostream>
#ifdef WIN32
#pragma warning( 3 : 4265 ) // "class has virtual functions, but destructor is not virtual"
#endif
////////////////////////////////////////////////////////////////////////////////
//
LLEmbeddedBrowserWindow::LLEmbeddedBrowserWindow() :
mParent( 0 ),
mWebBrowser( nsnull ),
mBaseWindow( nsnull ),
mWindowId( 0 ),
mPercentComplete( 0 ),
mBrowserWidth( 0 ),
mBrowserHeight( 0 ),
mBrowserDepth( 4 ),
mPageBuffer( 0 ),
mEnabled( true ),
mCurrentUri( "" ),
mStatusText( "" ),
mClickHref( "" ),
mClickTarget( "" ),
mNoFollowScheme( "secondlife://" ),
mBkgRed( 0xff ),
mBkgGreen( 0xff ),
mBkgBlue( 0xff ),
mCaretRed( 0x00 ),
mCaretGreen( 0x00 ),
mCaretBlue( 0x00 ),
m404RedirectUrl( "" ),
mFlipBitmap( false )
{
}
////////////////////////////////////////////////////////////////////////////////
//
LLEmbeddedBrowserWindow::~LLEmbeddedBrowserWindow()
{
if ( mWebNav )
{
mWebNav->Stop ( nsIWebNavigation::STOP_ALL );
mWebNav = nsnull;
};
if ( mBaseWindow )
{
mBaseWindow->Destroy();
mBaseWindow = nsnull;
};
if ( mPageBuffer )
{
delete[] mPageBuffer;
mPageBuffer = 0;
};
}
////////////////////////////////////////////////////////////////////////////////
//
nsresult LLEmbeddedBrowserWindow::createBrowser( void* nativeWindowHandleIn, PRInt32 widthIn, PRInt32 heightIn, nsIWebBrowser **aBrowser )
{
NS_ENSURE_ARG_POINTER(aBrowser);
*aBrowser = nsnull;
nsresult rv;
mWebBrowser = do_CreateInstance( NS_WEBBROWSER_CONTRACTID, &rv );
if ( ! mWebBrowser )
{
return NS_ERROR_FAILURE;
}
(void)mWebBrowser->SetContainerWindow( NS_STATIC_CAST( nsIWebBrowserChrome*, this ) );
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface( mWebBrowser );
dsti->SetItemType( nsIDocShellTreeItem::typeContentWrapper );
mBaseWindow = do_QueryInterface( mWebBrowser );
mBaseWindow->InitWindow( nativeWindowHandleIn, nsnull, 0, 0, mBrowserWidth, mBrowserHeight );
mBaseWindow->Create();
nsCOMPtr< nsIWebProgressListener > listener( NS_STATIC_CAST( nsIWebProgressListener*, this ) );
nsCOMPtr< nsIWeakReference > thisListener( do_GetWeakReference( listener ) );
mWebBrowser->AddWebBrowserListener( thisListener, NS_GET_IID( nsIWebProgressListener ) );
#if LL_DARWIN
// Without this, the mac doesn't get upates for animating gifs, mouseovers, etc.
mBaseWindow->SetVisibility( PR_TRUE );
#else
mBaseWindow->SetVisibility( PR_FALSE );
#endif
nsresult result;
mWebNav = do_QueryInterface( mWebBrowser, &result );
if ( NS_FAILED( result ) || ! mWebNav )
{
return NS_ERROR_FAILURE;
};
setSize( widthIn, heightIn );
if ( mWebBrowser )
{
*aBrowser = mWebBrowser;
NS_ADDREF( *aBrowser );
return NS_OK;
};
return NS_ERROR_FAILURE;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMPL_ADDREF( LLEmbeddedBrowserWindow )
NS_IMPL_RELEASE( LLEmbeddedBrowserWindow )
////////////////////////////////////////////////////////////////////////////////
//
NS_INTERFACE_MAP_BEGIN( LLEmbeddedBrowserWindow )
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS( nsISupports, nsIWebBrowserChrome )
NS_INTERFACE_MAP_ENTRY( nsIInterfaceRequestor )
NS_INTERFACE_MAP_ENTRY( nsIWebBrowserChrome )
NS_INTERFACE_MAP_ENTRY( nsIWebProgressListener )
NS_INTERFACE_MAP_ENTRY( nsIURIContentListener )
#ifdef SUPPORTS_WEAK_REFENCE
NS_INTERFACE_MAP_ENTRY( nsISupportsWeakReference )
#endif
#ifdef NS_DECL_NSITOOLKITOBSERVER
NS_INTERFACE_MAP_ENTRY( nsIToolkitObserver )
#endif
NS_INTERFACE_MAP_END
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::GetInterface( const nsIID &aIID, void** aInstancePtr )
{
if ( aIID.Equals( NS_GET_IID( nsIDOMWindow ) ) )
{
if ( mWebBrowser )
{
return mWebBrowser->GetContentDOMWindow( ( nsIDOMWindow** )aInstancePtr );
};
return NS_ERROR_NOT_INITIALIZED;
};
return QueryInterface( aIID, aInstancePtr );
}
////////////////////////////////////////////////////////////////////////////////
// called when something changes the status text - emits event to consumer
NS_IMETHODIMP LLEmbeddedBrowserWindow::SetStatus( PRUint32 aType, const PRUnichar* aStatus )
{
mStatusText = std::string( NS_ConvertUTF16toUTF8( aStatus ).get() );
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mStatusText );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onStatusTextChange, event );
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::GetWebBrowser( nsIWebBrowser** aWebBrowser )
{
NS_ENSURE_ARG_POINTER( aWebBrowser );
*aWebBrowser = mWebBrowser;
NS_IF_ADDREF( *aWebBrowser );
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::SetWebBrowser( nsIWebBrowser* aWebBrowser )
{
NS_ENSURE_ARG_POINTER( aWebBrowser );
mWebBrowser = aWebBrowser;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::GetChromeFlags( PRUint32* aChromeMask )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::SetChromeFlags( PRUint32 aChromeMask )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::DestroyBrowserWindow()
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::SizeBrowserTo( PRInt32 aCX, PRInt32 aCY )
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::ShowAsModal()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::IsWindowModal( PRBool* retval )
{
*retval = PR_FALSE;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::ExitModalEventLoop( nsresult aStatus )
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// called when the page loading progress changes - emits event to consumer
NS_IMETHODIMP LLEmbeddedBrowserWindow::OnProgressChange( nsIWebProgress* progress, nsIRequest* request,
PRInt32 curSelfProgress, PRInt32 maxSelfProgress,
PRInt32 curTotalProgress, PRInt32 maxTotalProgress )
{
mPercentComplete = static_cast< PRInt16 >
( static_cast< float >( curTotalProgress * 100.0f ) / static_cast< float >( maxTotalProgress ) );
if ( mPercentComplete < 0 )
mPercentComplete = 0;
if ( mPercentComplete > 100 )
mPercentComplete = 100;
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mPercentComplete );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onUpdateProgress, event );
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// called when the browser state changes - as described below - emits event to consumer
NS_IMETHODIMP LLEmbeddedBrowserWindow::OnStateChange( nsIWebProgress* progress, nsIRequest* request,
PRUint32 progressStateFlags, nsresult status )
{
if ( ( progressStateFlags & STATE_START ) && ( progressStateFlags & STATE_IS_DOCUMENT ) && ( status == NS_OK ) )
{
// TODO: move this to a better place.
enableToolkitObserver( false );
enableToolkitObserver( true );
// page load is starting so remove listener that catches "click" events
nsCOMPtr< nsIDOMWindow > window;
nsresult result = progress->GetDOMWindow( getter_AddRefs( window ) );
if ( result == NS_OK )
{
nsCOMPtr< nsIDOMEventTarget > target = do_QueryInterface( window );
if ( target )
target->RemoveEventListener(NS_ConvertUTF8toUTF16( "click" ), this, PR_TRUE );
};
// set the listener to we can catch nsURIContentListener events
if ( mWebBrowser )
{
mWebBrowser->SetParentURIContentListener( NS_STATIC_CAST( nsIURIContentListener*, this ) );
};
// emit event that navigation is beginning
mStatusText = std::string( "Browser loaded" );
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mStatusText );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onNavigateBegin, event );
// about to move to a different page so have to stop grabbing a page
// but done one final grab in case the app doesn't ever call grabWindow again
grabWindow( 0, 0, mBrowserWidth, mBrowserHeight );
};
if ( ( progressStateFlags & STATE_STOP ) && ( progressStateFlags & STATE_IS_WINDOW ) && ( status == NS_OK ) )
{
// page load is complete so add listener that catches "click" events
nsCOMPtr< nsIDOMWindow > window;
nsresult result = progress->GetDOMWindow( getter_AddRefs( window ) );
if ( result == NS_OK )
{
nsCOMPtr< nsIDOMEventTarget > target = do_QueryInterface( window );
if ( target )
target->AddEventListener(NS_ConvertUTF8toUTF16( "click" ), this, PR_TRUE );
};
// pick up raw HTML response status code
PRUint32 responseStatus = 0;
if ( request )
{
nsCOMPtr< nsIHttpChannel > httpChannel = do_QueryInterface( request );
if ( httpChannel )
{
httpChannel->GetResponseStatus( &responseStatus );
};
};
// emit event that navigation is finished
mStatusText = std::string( "Done" );
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mStatusText, (int)responseStatus );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onNavigateComplete, event );
// also set the flag here since back/forward navigation doesn't call progress change
grabWindow( 0, 0, mBrowserWidth, mBrowserHeight );
};
if ( progressStateFlags & STATE_REDIRECTING )
{
mStatusText = std::string( "Redirecting..." );
};
if ( progressStateFlags & STATE_TRANSFERRING )
{
mStatusText = std::string( "Transferring..." );
};
if ( progressStateFlags & STATE_NEGOTIATING )
{
mStatusText = std::string( "Negotiating..." );
};
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mStatusText );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onStatusTextChange, event );
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// call when the location changes - e.g. when a site redirects - emits event to consumer
// TODO: ought to check that this change is on the top frame and
// indicate this to the consumer of this class
NS_IMETHODIMP LLEmbeddedBrowserWindow::OnLocationChange( nsIWebProgress* webProgress,
nsIRequest* request,
nsIURI* location )
{
if ( request )
{
nsCOMPtr< nsIHttpChannel > http_channel = do_QueryInterface( request );
if ( http_channel )
{
PRUint32 response_status = 0;
http_channel->GetResponseStatus( &response_status );
if ( response_status == 404 )
{
if ( ! m404RedirectUrl.empty() )
{
if ( mWebNav )
{
mWebNav->LoadURI( reinterpret_cast< const PRUnichar* >
( NS_ConvertUTF8toUTF16( m404RedirectUrl.c_str() ).get() ),
nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY,
nsnull, nsnull, nsnull );
};
};
};
};
};
nsCAutoString newURI;
location->GetSpec( newURI );
mCurrentUri = newURI.get();
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri() );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onLocationChange, event );
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// change the background color that gets used between pages (usually white)
void LLEmbeddedBrowserWindow::setBackgroundColor( const PRUint8 redIn, const PRUint8 greenIn, const PRUint8 blueIn )
{
mBkgRed = redIn;
mBkgGreen = greenIn;
mBkgBlue = blueIn;
}
////////////////////////////////////////////////////////////////////////////////
// change the caret color (we have different backgrounds to edit fields - black caret on black background == bad)
void LLEmbeddedBrowserWindow::setCaretColor( const PRUint8 redIn, const PRUint8 greenIn, const PRUint8 blueIn )
{
mCaretRed = redIn;
mCaretGreen = greenIn;
mCaretBlue = blueIn;
}
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::setEnabled( PRBool enabledIn )
{
mEnabled = enabledIn;
}
////////////////////////////////////////////////////////////////////////////////
// allow consumers of this class to observe events - add themselves as an observer
bool LLEmbeddedBrowserWindow::addObserver( LLEmbeddedBrowserWindowObserver* observerIn )
{
return mEventEmitter.addObserver( observerIn );
}
////////////////////////////////////////////////////////////////////////////////
// allow consumers of this class to observe events - remove themselves as an observer
bool LLEmbeddedBrowserWindow::remObserver( LLEmbeddedBrowserWindowObserver* observerIn )
{
return mEventEmitter.remObserver( observerIn );
}
////////////////////////////////////////////////////////////////////////////////
// used by observers of this class to get the current URI
const std::string& LLEmbeddedBrowserWindow::getCurrentUri()
{
return mCurrentUri;
}
////////////////////////////////////////////////////////////////////////////////
// utility method that is used by observers to retrieve data after an event
const PRInt16 LLEmbeddedBrowserWindow::getPercentComplete()
{
return mPercentComplete;
}
////////////////////////////////////////////////////////////////////////////////
// utility method that is used by observers to retrieve data after an event
const std::string& LLEmbeddedBrowserWindow::getStatusMsg()
{
return mStatusText;
}
////////////////////////////////////////////////////////////////////////////////
// utility method that is used by observers to retrieve data after an event
const std::string& LLEmbeddedBrowserWindow::getClickLinkHref()
{
return mClickHref;
}
////////////////////////////////////////////////////////////////////////////////
// utility method that is used by observers to retrieve data after an event
const std::string& LLEmbeddedBrowserWindow::getClickLinkTarget()
{
return mClickTarget;
}
////////////////////////////////////////////////////////////////////////////////
// called when the status text is changed - emits event to consumer
NS_IMETHODIMP LLEmbeddedBrowserWindow::OnStatusChange( nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
nsresult aStatus,
const PRUnichar* aMessage )
{
mStatusText = std::string( NS_ConvertUTF16toUTF8( aMessage ).get() );
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mStatusText );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onStatusTextChange, event );
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// implement this if you want to do something when the security state changtes
NS_IMETHODIMP LLEmbeddedBrowserWindow::OnSecurityChange( nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
PRUint32 state )
{
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// render a page into memory and grab the window
// TODO: 0,0, browser width, browser height is always passed in right now
// need to make this work with arbitrary rects (i.e. the dirty rect)
unsigned char* LLEmbeddedBrowserWindow::grabWindow( int xIn, int yIn, int widthIn, int heightIn )
{
// sanity check
if ( ! mWebBrowser )
return 0;
// only grab the window if it's enabled
if ( ! mEnabled )
return false;
// get the docshell
nsCOMPtr< nsIDocShell > docShell = do_GetInterface( mWebBrowser );
if ( ! docShell )
return PR_FALSE;
// get pres context
nsCOMPtr< nsPresContext > presContext;
nsresult result = docShell->GetPresContext( getter_AddRefs( presContext ) );
if ( NS_FAILED( result ) || ( ! presContext ) )
return PR_FALSE;
// get view manager
nsIViewManager* viewManager = presContext->GetViewManager();
if ( ! viewManager )
return PR_FALSE;
// get the view
nsIScrollableView* scrollableView = NULL;
viewManager->GetRootScrollableView( &scrollableView );
nsIView* view = NULL;
if ( scrollableView )
scrollableView->GetScrolledView( view );
else
viewManager->GetRootView( view );
// get the rectangle we want to render in twips (this looks odd but takees care of scrolling too)
nsRect rect = view->GetBounds() - view->GetPosition() - view->GetPosition();
if ( rect.IsEmpty() )
return 0;
float p2t = presContext->PixelsToTwips();
rect.width = NSIntPixelsToTwips( widthIn, p2t );
rect.height = NSIntPixelsToTwips( heightIn, p2t );
// render the page
nsCOMPtr< nsIRenderingContext > context;
result = viewManager->RenderOffscreen( view, rect, PR_FALSE, PR_FALSE, NS_RGB( mBkgRed, mBkgGreen, mBkgBlue ), getter_AddRefs( context ) );
if ( NS_FAILED( result ) )
return 0;
// retrieve the surface we rendered to
nsIDrawingSurface* surface = nsnull;
context->GetDrawingSurface( &surface );
if ( ! surface )
return 0;
// lock the surface and retrieve a pointer to the rendered data and current row span
PRUint8* data;
PRInt32 rowLen;
// sometime rowspan ! width in pixels * bytes per pixel so save row span value and use in application
result = surface->Lock( xIn, yIn, widthIn, heightIn, reinterpret_cast< void** >( &data ), &mBrowserRowSpan, &rowLen, NS_LOCK_SURFACE_READ_ONLY );
if ( NS_FAILED ( result ) )
return 0;
// save row span - it *can* change during the life of the app
mBrowserDepth = rowLen / mBrowserWidth;
// create memory buffer here so it can be deleted and recreated elsewhere
if ( ! mPageBuffer )
mPageBuffer = new unsigned char[ mBrowserRowSpan * mBrowserHeight ];
// save the pixels and optionally invert them
// (it's useful the SL client to get bitmaps that are inverted compared
// to the way that Mozilla renders them - allow to optionally flip
if ( mFlipBitmap )
{
for( int y = mBrowserHeight - 1; y > -1; --y )
{
memcpy( mPageBuffer + y * mBrowserRowSpan,
data + ( mBrowserHeight - y - 1 ) * mBrowserRowSpan,
mBrowserRowSpan );
};
}
else
{
memcpy( mPageBuffer, data, mBrowserRowSpan * mBrowserHeight );
};
// release and destroy the surface we rendered to
surface->Unlock();
context->DestroyDrawingSurface( surface );
renderCaret();
return mPageBuffer;
}
////////////////////////////////////////////////////////////////////////////////
// all this just to render a caret!
PRBool LLEmbeddedBrowserWindow::renderCaret()
{
nsCOMPtr< nsIWebBrowserFocus > focus = do_QueryInterface( mWebBrowser );
if ( ! focus )
return NS_ERROR_FAILURE;
nsCOMPtr< nsIDOMElement > focusedElement;
focus->GetFocusedElement( getter_AddRefs( focusedElement ) );
if ( ! focusedElement )
return NS_ERROR_FAILURE;
nsCOMPtr<nsIContent> focusedContent = do_QueryInterface( focusedElement );
if ( ! focusedContent )
return NS_ERROR_FAILURE;
nsIPresShell* presShell = focusedContent->GetCurrentDoc()->GetShellAt( 0 );
if ( ! presShell )
return NS_ERROR_FAILURE;
nsCOMPtr< nsICaret > caret;
presShell->GetCaret( getter_AddRefs( caret ) );
if ( ! caret )
return NS_ERROR_FAILURE;
nsIFrame* frame = nsnull;
presShell->GetPrimaryFrameFor( focusedContent, &frame );
if ( ! frame )
return NS_ERROR_FAILURE;
nsCOMPtr<nsISelectionController> selCtrl;
frame->GetSelectionController( presShell->GetPresContext(), getter_AddRefs( selCtrl ) );
if ( ! selCtrl )
return NS_ERROR_FAILURE;
nsCOMPtr<nsISelection> selection;
selCtrl->GetSelection( nsISelectionController::SELECTION_NORMAL, getter_AddRefs( selection ) );
if ( ! selection )
return NS_ERROR_FAILURE;
PRBool collapsed;
nsRect coords;
nsIView* caretView;
caret->GetCaretCoordinates( nsICaret::eTopLevelWindowCoordinates, selection, &coords, &collapsed, &caretView );
if ( ! caretView )
return NS_ERROR_FAILURE;
float twips2Pixls = presShell->GetPresContext()->TwipsToPixels();
PRInt32 caretX = NSTwipsToIntPixels( coords.x, twips2Pixls );
PRInt32 caretY = NSTwipsToIntPixels( coords.y, twips2Pixls );
PRInt32 caretHeight = NSTwipsToIntPixels( coords.height, twips2Pixls );
if ( caretX > -1 && caretX < mBrowserWidth && caretY > -1 && caretY < mBrowserHeight )
{
if ( mPageBuffer )
{
for( int y = 1; y < caretHeight - 1; ++y )
{
PRInt32 base_pos = caretY + y;
if ( mFlipBitmap )
base_pos = mBrowserHeight - ( caretY + y );
// sometimes the caret seems valid when it really isn't - cap it to size of screen
if ( caretY + y + caretHeight < mBrowserHeight )
{
mPageBuffer[ base_pos * getBrowserRowSpan() + ( caretX + 1 ) * mBrowserDepth + 0 ] = mCaretBlue;
mPageBuffer[ base_pos * getBrowserRowSpan() + ( caretX + 1 ) * mBrowserDepth + 1 ] = mCaretGreen;
mPageBuffer[ base_pos * getBrowserRowSpan() + ( caretX + 1 ) * mBrowserDepth + 2 ] = mCaretRed;
};
};
};
};
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// return the buffer that contains the rendered page
unsigned char* LLEmbeddedBrowserWindow::getPageBuffer()
{
return mPageBuffer;
}
////////////////////////////////////////////////////////////////////////////////
//
PRInt16 LLEmbeddedBrowserWindow::getBrowserWidth()
{
return mBrowserWidth;
}
////////////////////////////////////////////////////////////////////////////////
//
PRInt16 LLEmbeddedBrowserWindow::getBrowserHeight()
{
return mBrowserHeight;
}
////////////////////////////////////////////////////////////////////////////////
//
PRInt16 LLEmbeddedBrowserWindow::getBrowserDepth()
{
return mBrowserDepth;
}
////////////////////////////////////////////////////////////////////////////////
//
PRInt32 LLEmbeddedBrowserWindow::getBrowserRowSpan()
{
return mBrowserRowSpan;
}
////////////////////////////////////////////////////////////////////////////////
//
PRBool LLEmbeddedBrowserWindow::navigateTo( const std::string uriIn )
{
if ( mWebNav )
{
mWebNav->LoadURI( reinterpret_cast< const PRUnichar* >( NS_ConvertUTF8toUTF16( uriIn.c_str() ).get() ),
nsIWebNavigation::LOAD_FLAGS_NONE,
nsnull, nsnull, nsnull );
return PR_TRUE;
};
return PR_FALSE;
};
////////////////////////////////////////////////////////////////////////////////
//
PRBool LLEmbeddedBrowserWindow::canNavigateBack()
{
if ( ! mWebNav )
{
return PR_FALSE;
};
PRBool canGoBack = PR_FALSE;
nsresult result = mWebNav->GetCanGoBack( &canGoBack );
if ( NS_FAILED( result ) )
{
return PR_FALSE;
};
return canGoBack;
};
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::navigateStop()
{
if ( mWebNav )
mWebNav->Stop( nsIWebNavigation::STOP_ALL );
};
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::navigateBack()
{
if ( mWebNav )
mWebNav->GoBack();
};
////////////////////////////////////////////////////////////////////////////////
//
PRBool LLEmbeddedBrowserWindow::canNavigateForward()
{
if ( ! mWebNav )
return PR_FALSE;
PRBool canGoForward = PR_FALSE;
nsresult result = mWebNav->GetCanGoForward( &canGoForward );
if ( NS_FAILED( result ) )
{
return PR_FALSE;
};
return canGoForward;
};
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::navigateForward()
{
if ( mWebNav )
mWebNav->GoForward();
};
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::navigateReload()
{
// maybe need a cache version of this too?
if ( mWebNav )
mWebNav->Reload( nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE );
};
////////////////////////////////////////////////////////////////////////////////
// set the size of the browser window
PRBool LLEmbeddedBrowserWindow::setSize( PRInt16 widthIn, PRInt16 heightIn )
{
if ( mBaseWindow )
{
// if there is a buffer already, get rid of it (it will get created as required in grabWindow())
if ( mPageBuffer )
{
delete[] mPageBuffer;
mPageBuffer = 0;
};
// record new size (important: may change after grabWindow() is called);
mBrowserWidth = widthIn;
mBrowserHeight = heightIn;
mBrowserRowSpan = mBrowserWidth * mBrowserDepth;
// On the Mac, these calls do strange things to the main viewer window, and they don't seem necessary in any case.
#ifdef WIN32
// this is the actual OS (on Win32) Window so it needs to be hidden
mBaseWindow->SetVisibility( PR_FALSE );
// move WAY off screen (and in a place that makes the combobox hack work)
mBaseWindow->SetPosition( 8000, -6000 );
#endif
// tell Mozilla about the new size
mBaseWindow->SetSize( widthIn, heightIn, PR_FALSE );
return PR_TRUE;
};
return PR_FALSE;
}
////////////////////////////////////////////////////////////////////////////////
//
PRBool LLEmbeddedBrowserWindow::flipWindow( PRBool flip )
{
mFlipBitmap = flip;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// higher level mouse event
void LLEmbeddedBrowserWindow::mouseLeftDoubleClick( PRInt16 xPosIn, PRInt16 yPosIn )
{
// Internally Mozilla represents double-click as a 2-count mouse down event.
// TODO: support triple-click
const PRUint32 clickCount = 2;
sendMozillaMouseEvent( NS_MOUSE_LEFT_BUTTON_DOWN, xPosIn, yPosIn, clickCount );
}
////////////////////////////////////////////////////////////////////////////////
// higher level mouse event
void LLEmbeddedBrowserWindow::mouseDown( PRInt16 xPosIn, PRInt16 yPosIn )
{
const PRUint32 clickCount = 1;
sendMozillaMouseEvent( NS_MOUSE_LEFT_BUTTON_DOWN, xPosIn, yPosIn, clickCount );
}
////////////////////////////////////////////////////////////////////////////////
// higher level mouse event
void LLEmbeddedBrowserWindow::mouseUp( PRInt16 xPosIn, PRInt16 yPosIn )
{
const PRUint32 clickCount = 1;
sendMozillaMouseEvent( NS_MOUSE_LEFT_BUTTON_UP, xPosIn, yPosIn, clickCount );
}
////////////////////////////////////////////////////////////////////////////////
// higher level mouse event
void LLEmbeddedBrowserWindow::mouseMove( PRInt16 xPosIn, PRInt16 yPosIn )
{
const PRUint32 clickCount = 1; // ignored?
sendMozillaMouseEvent( NS_MOUSE_MOVE, xPosIn, yPosIn, clickCount );
}
////////////////////////////////////////////////////////////////////////////////
// utility methods to set an error message so something else can look at it
void LLEmbeddedBrowserWindow::scrollByLines( PRInt16 linesIn )
{
if ( mWebBrowser )
{
nsCOMPtr< nsIDOMWindow > window;
nsresult result = mWebBrowser->GetContentDOMWindow( getter_AddRefs( window ) );
if ( ! NS_FAILED( result ) && window )
{
result = window->ScrollByLines( linesIn );
};
};
}
////////////////////////////////////////////////////////////////////////////////
// synthesizes a mouse event and sends into the embedded instance
// eventIn - NS_MOUSE_LEFT_BUTTON_DOWN, NS_MOUSE_LEFT_BUTTON_UP, etc.
// xPosIn, yPosIn - coordinates (in browser window space)
// clickCountIn - use 1 for single click, 2 for double-click, etc.
PRBool LLEmbeddedBrowserWindow::sendMozillaMouseEvent( PRInt16 eventIn, PRInt16 xPosIn, PRInt16 yPosIn, PRUint32 clickCountIn )
{
if ( ! mEnabled )
return PR_FALSE;
if ( ! mWebBrowser )
return PR_FALSE;
nsCOMPtr< nsIDocShell > docShell = do_GetInterface( mWebBrowser );
if ( ! docShell )
return PR_FALSE;
nsCOMPtr< nsPresContext > presContext;
nsresult result = docShell->GetPresContext( getter_AddRefs( presContext ) );
if ( NS_FAILED( result ) || ( ! presContext ) )
return PR_FALSE;
nsIViewManager* viewManager = presContext->GetViewManager();
if ( ! viewManager )
return PR_FALSE;
nsIView* rootView;
result = viewManager->GetRootView( rootView );
if ( NS_FAILED( result ) || ( ! rootView ) )
return PR_FALSE;
nsCOMPtr< nsIWidget > widget = rootView->GetWidget();
if ( ! widget )
return PR_FALSE;
nsMouseEvent mouseEvent( PR_TRUE, eventIn, widget, nsMouseEvent::eReal );
mouseEvent.clickCount = clickCountIn;
mouseEvent.isShift = 0;
mouseEvent.isControl = 0;
mouseEvent.isAlt = 0;
mouseEvent.isMeta = 0;
mouseEvent.widget = widget;
mouseEvent.nativeMsg = nsnull;
mouseEvent.point.x = xPosIn;
mouseEvent.point.y = yPosIn;
mouseEvent.refPoint.x = xPosIn;
mouseEvent.refPoint.y = yPosIn;
mouseEvent.flags = 0;
nsEventStatus status;
result = viewManager->DispatchEvent( &mouseEvent, &status );
if ( NS_FAILED( result ) )
return PR_FALSE;
return PR_TRUE;
};
////////////////////////////////////////////////////////////////////////////////
// higher level keyboard functions
// accept a (mozilla-style) keycode
void LLEmbeddedBrowserWindow::keyPress( PRInt16 keyCode )
{
sendMozillaKeyboardEvent( 0, keyCode );
}
// accept keyboard input that's already been translated into a unicode char.
void LLEmbeddedBrowserWindow::unicodeInput( PRUint32 uni_char )
{
sendMozillaKeyboardEvent( uni_char, 0 );
}
////////////////////////////////////////////////////////////////////////////////
// synthesizes a keyboard event and sends into the embedded instance
PRBool LLEmbeddedBrowserWindow::sendMozillaKeyboardEvent( PRUint32 uni_char, PRUint32 ns_vk_code )
{
if ( ! mEnabled )
return PR_FALSE;
if ( ! mWebBrowser )
return PR_FALSE;
nsCOMPtr< nsIDocShell > docShell = do_GetInterface( mWebBrowser );
if ( ! docShell )
return PR_FALSE;
nsCOMPtr< nsPresContext > presContext;
docShell->GetPresContext( getter_AddRefs( presContext ) );
if ( ! presContext )
return PR_FALSE;
nsIViewManager* viewManager = presContext->GetViewManager();
if ( ! viewManager )
return PR_FALSE;
nsIView* rootView;
viewManager->GetRootView( rootView );
if ( ! rootView )
return PR_FALSE;
nsCOMPtr< nsIWidget > widget = rootView->GetWidget();
if ( ! widget )
return PR_FALSE;
nsKeyEvent keyEvent( PR_TRUE, NS_KEY_PRESS, widget );
keyEvent.keyCode = ns_vk_code;
keyEvent.charCode = uni_char;
keyEvent.isChar = uni_char ? PR_TRUE : PR_FALSE;
keyEvent.isShift = 0;
keyEvent.isControl = 0;
keyEvent.isAlt = 0;
keyEvent.isMeta = 0;
keyEvent.widget = widget;
keyEvent.nativeMsg = nsnull;
keyEvent.point.x = 0;
keyEvent.point.y = 0;
keyEvent.refPoint.x = 0;
keyEvent.refPoint.y = 0;
keyEvent.flags = 0;
nsEventStatus status;
nsresult result = viewManager->DispatchEvent( &keyEvent, &status );
if ( NS_FAILED( result ) )
return PR_FALSE;
return PR_TRUE;
}
////////////////////////////////////////////////////////////////////////////////
// override nsIWebBrowserChrome::HandleEvent ()
NS_IMETHODIMP LLEmbeddedBrowserWindow::HandleEvent( nsIDOMEvent* anEvent )
{
nsCOMPtr< nsIDOMEventTarget > eventTarget;
anEvent->GetTarget( getter_AddRefs( eventTarget ) );
nsCOMPtr<nsIDOMElement> linkElement ( do_QueryInterface ( eventTarget ) );
if ( linkElement )
{
// look for an href link
nsString name;
linkElement->GetAttribute( NS_ConvertUTF8toUTF16( "href" ), name );
mClickHref = std::string( NS_ConvertUTF16toUTF8( name ).get() );
// look for a target element
linkElement->GetAttribute( NS_ConvertUTF8toUTF16( "target" ), name );
mClickTarget = std::string( NS_ConvertUTF16toUTF8( name ).get() );
// if the href link contains something
if ( mClickHref.length() )
{
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), mClickHref, mClickTarget );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onClickLinkHref, event );
};
};
return NS_OK;
};
////////////////////////////////////////////////////////////////////////////////
// override nsIURIContentListener methods
NS_IMETHODIMP LLEmbeddedBrowserWindow::OnStartURIOpen( nsIURI *aURI, PRBool *_retval )
{
nsCAutoString newURI;
aURI->GetSpec( newURI );
std::string rawUri = newURI.get();
// are we navigating to a 'nofollow' link
if ( mNoFollowScheme.length() && rawUri.substr( 0, mNoFollowScheme.length() ) == mNoFollowScheme )
{
LLEmbeddedBrowserWindowEvent event( getWindowId(), rawUri, rawUri );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onClickLinkNoFollow, event );
// tell browser we're handling things and don't follow link
*_retval = PR_TRUE;
}
else
{
// tell browser to proceed as normal
*_retval = PR_FALSE;
};
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::setNoFollowScheme( std::string schemeIn )
{
mNoFollowScheme = schemeIn;
}
////////////////////////////////////////////////////////////////////////////////
//
std::string LLEmbeddedBrowserWindow::getNoFollowScheme()
{
return mNoFollowScheme;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::DoContent( const char *aContentType,
PRBool aIsContentPreferred,
nsIRequest *aRequest,
nsIStreamListener **aContentHandler,
PRBool *_retval )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::IsPreferred( const char *aContentType,
char **aDesiredContentType,
PRBool *_retval )
{
// important (otherwise, links try to open in a new window and trigger the window watcher code)
*_retval = PR_TRUE;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::CanHandleContent( const char *aContentType,
PRBool aIsContentPreferred,
char **aDesiredContentType,
PRBool *_retval )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::GetLoadCookie( nsISupports * *aLoadCookie )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::SetLoadCookie( nsISupports * aLoadCookie )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::GetParentContentListener( nsIURIContentListener** aParentContentListener )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
//
NS_IMETHODIMP LLEmbeddedBrowserWindow::SetParentContentListener( nsIURIContentListener* aParentContentListener )
{
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
// give focus to the browser so that input keyboard events work
void LLEmbeddedBrowserWindow::focusBrowser( PRBool focusBrowserIn )
{
if ( mWebBrowser )
{
if ( focusBrowserIn )
{
nsCOMPtr< nsIWebBrowserFocus > focus( do_GetInterface( mWebBrowser ) );
focus->Activate();
}
else
{
nsCOMPtr< nsIWebBrowserFocus > focus( do_GetInterface( mWebBrowser ) );
focus->Deactivate();
};
};
}
////////////////////////////////////////////////////////////////////////////////
//
void LLEmbeddedBrowserWindow::setWindowId( int windowIdIn )
{
mWindowId = windowIdIn;
}
////////////////////////////////////////////////////////////////////////////////
//
int LLEmbeddedBrowserWindow::getWindowId()
{
//printf("## Getting id for %p and it is %d\n", this, mWindowId );
return mWindowId;
}
////////////////////////////////////////////////////////////////////////////////
// add / remove the toolkit (and therefore 'page changed') observer
PRBool LLEmbeddedBrowserWindow::enableToolkitObserver( PRBool enableIn )
{
//TODO: AddObserver fails if I grab the toolkit this way.
//static NS_DEFINE_CID(kNS_TOOLKIT_CID, NS_TOOLKIT_CID);
//nsresult result1;
//nsCOMPtr< nsIToolkit > toolkit = do_GetService( kNS_TOOLKIT_CID, &result1 );
//if ( ( result1 == NS_OK ) && toolkit )
//{
// if ( toolkit->AddObserver( this ) )
// {
// return true;
// };
//};
//return false;
// TODO: this is horrible but seems to work - need a better way to get the toolkit
nsCOMPtr< nsIDocShell > docShell = do_GetInterface( mWebBrowser );
if ( ! docShell )
return false;
nsCOMPtr< nsPresContext > presContext;
nsresult result = docShell->GetPresContext( getter_AddRefs( presContext ) );
if ( NS_FAILED( result ) || ( ! presContext ) )
return false;
nsIViewManager* viewManager = presContext->GetViewManager();
if ( ! viewManager )
return false;
nsIView* rootView;
result = viewManager->GetRootView( rootView );
if ( NS_FAILED( result ) || ( ! rootView ) )
return false;
nsCOMPtr< nsIWidget > widget = rootView->GetWidget();
if ( ! widget )
return false;
nsCOMPtr< nsIToolkit > mToolkit = widget->GetToolkit();
if ( ! mToolkit )
return false;
#ifdef NS_DECL_NSITOOLKITOBSERVER
if ( enableIn )
mToolkit->AddObserver( this );
else
mToolkit->RemoveObserver( this );
#endif
return true;
}
////////////////////////////////////////////////////////////////////////////////
// something on the page changed - e.g. a new page loaded, scrolling, user
// input or as the result of some kind of animation.
// NOTE: we don't call grabWindow here as this can stall graphics apps -
// we merely pass back an event to the app and let it decide when/if
// to call grabWindow()
NS_METHOD LLEmbeddedBrowserWindow::NotifyInvalidated( nsIWidget *aWidget, PRInt32 x, PRInt32 y, PRInt32 width, PRInt32 height )
{
// printf("LLEmbeddedBrowserWindow::NotifyInvalidated(%p, %d, %d, %d, %d)\n", (void*)aWidget, (int)x, (int)y, (int)width, (int)height);
// try to match widget-window against ourselves to see if we need to update the texture
// only works using native widgets (on Windows) at the moment - needs to be moved to platform agnostic code ASAP
#ifdef WIN32
// this is horrible beyond words but it seems to work...
// nsToolkit tells us that a widget changed and we need to see if it's this instance
// so we can emit an event that causes the parent app to update the browser texture
nsIWidget* mainWidget;
mBaseWindow->GetMainWidget( &mainWidget );
HWND nativeWidget = (HWND)aWidget->GetNativeData( NS_NATIVE_WIDGET );
HWND nativeWidgetChild = 0;
while ( ::GetParent( nativeWidget ) )
{
nativeWidgetChild = nativeWidget;
nativeWidget = ::GetParent( nativeWidget );
};
if ( ( (HWND)mainWidget->GetNativeData( NS_NATIVE_WIDGET ) ) == nativeWidgetChild )
{
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), x, y, width, height );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onPageChanged, event );
};
// other platforms will always update - desperately inefficient but you'll see something.
#else
LLEmbeddedBrowserWindowEvent event( getWindowId(), getCurrentUri(), x, y, width, height );
mEventEmitter.update( &LLEmbeddedBrowserWindowObserver::onPageChanged, event );
#endif
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//
std::string LLEmbeddedBrowserWindow::evaluateJavascript( std::string scriptIn )
{
nsCOMPtr< nsIScriptGlobalObjectOwner > theGlobalObjectOwner( do_GetInterface( mWebBrowser ) );
if ( theGlobalObjectOwner )
{
nsIScriptGlobalObject* theGlobalObject;
theGlobalObject = theGlobalObjectOwner->GetScriptGlobalObject();
nsIScriptContext* theScriptContext = theGlobalObject->GetContext();
PRBool IsUndefined;
nsString output;
nsresult result = theScriptContext->EvaluateString(NS_ConvertUTF8toUTF16(scriptIn.c_str()),
nsnull, nsnull, "", 1, nsnull, &output, &IsUndefined);
if( NS_FAILED( result ) )
return "";
return std::string( NS_ConvertUTF16toUTF8( output ).get() );
}
return "";
}
////////////////////////////////////////////////////////////////////////////////
//
bool LLEmbeddedBrowserWindow::set404RedirectUrl( std::string redirect_url )
{
m404RedirectUrl = redirect_url;
return true;
}
////////////////////////////////////////////////////////////////////////////////
//
bool LLEmbeddedBrowserWindow::clr404RedirectUrl()
{
m404RedirectUrl = std::string( "" );
return true;
}
// #define required by this file for LibXUL/Mozilla code to avoid crashes in their debug code
#ifdef _DEBUG
#ifdef WIN32
#undef DEBUG
#endif
#endif