/** * @file LLMediaCtrl.cpp * @brief Web browser UI control * * $LicenseInfo:firstyear=2006&license=viewergpl$ * * Copyright (c) 2006-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llmediactrl.h" // viewer includes #include "llfloaterhtml.h" #include "llfloaterworldmap.h" #include "lluictrlfactory.h" #include "llurldispatcher.h" #include "llurlsimstring.h" #include "llviewborder.h" #include "llviewercontrol.h" #include "llviewermedia.h" #include "llviewerwindow.h" #include "llnotifications.h" #include "llweb.h" #include "llrender.h" #include "llpluginclassmedia.h" // linden library includes #include "llfocusmgr.h" extern BOOL gRestoreGL; // Setting the mozilla buffer width to 2048 exactly doesn't work, since it pads its rowbytes a bit, pushing the texture width over 2048. // 2000 should give enough headroom for any amount of padding it cares to add. const S32 MAX_DIMENSION = 2000; const S32 MAX_TEXTURE_DIMENSION = 2048; static LLRegisterWidget r("web_browser"); LLMediaCtrl::LLMediaCtrl( const std::string& name, const LLRect& rect ) : LLUICtrl( name, rect, FALSE, NULL, NULL ), mTextureDepthBytes( 4 ), mWebBrowserImage( 0 ), mBorder(NULL), mFrequentUpdates( true ), mForceUpdate( false ), mOpenLinksInExternalBrowser( false ), mOpenLinksInInternalBrowser( false ), mTrusted( false ), mHomePageUrl( "" ), mIgnoreUIScale( true ), mAlwaysRefresh( false ), mExternalUrl( "" ), mMediaSource( 0 ), mTakeFocusOnClick( true ), mCurrentNavUrl( "about:blank" ), mLastSetCursor( UI_CURSOR_ARROW ), mStretchToFill( true ), mMaintainAspectRatio ( true ), mHideLoading (false) { S32 screen_width = mIgnoreUIScale ? llround((F32)getRect().getWidth() * LLUI::sGLScaleFactor.mV[VX]) : getRect().getWidth(); S32 screen_height = mIgnoreUIScale ? llround((F32)getRect().getHeight() * LLUI::sGLScaleFactor.mV[VY]) : getRect().getHeight(); mMediaSource = LLViewerMedia::newMediaImpl(mHomePageUrl, LLUUID::null, screen_width, screen_height, false, false, "text/html"); if ( !mMediaSource ) { llwarns << "media source create failed " << llendl; // return; } else { // create a new texture (based on LLDynamic texture) that will be used to display the output mWebBrowserImage = new LLWebBrowserTexture( screen_width, screen_height, this, mMediaSource ); } mMediaSource->setVisible( getVisible() ); mMediaSource->addObserver( this ); LLRect border_rect( 0, getRect().getHeight() + 2, getRect().getWidth() + 2, 0 ); mBorder = new LLViewBorder( std::string("web control border"), border_rect, LLViewBorder::BEVEL_IN ); addChild( mBorder ); } //////////////////////////////////////////////////////////////////////////////// // note: this is now a singleton and destruction happens via initClass() now LLMediaCtrl::~LLMediaCtrl() { if (mMediaSource) { mMediaSource->remObserver( this ); mMediaSource = NULL; } if ( mWebBrowserImage ) { delete mWebBrowserImage; mWebBrowserImage = NULL; } } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::setBorderVisible( BOOL border_visible ) { if ( mBorder ) { mBorder->setVisible( border_visible ); }; }; //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::setTakeFocusOnClick( bool take_focus ) { mTakeFocusOnClick = take_focus; } //////////////////////////////////////////////////////////////////////////////// // set flag that forces the embedded browser to open links in the external system browser void LLMediaCtrl::setOpenInExternalBrowser( bool valIn ) { mOpenLinksInExternalBrowser = valIn; }; //////////////////////////////////////////////////////////////////////////////// // set flag that forces the embedded browser to open links in the internal browser floater void LLMediaCtrl::setOpenInInternalBrowser( bool valIn ) { mOpenLinksInInternalBrowser = valIn; }; //////////////////////////////////////////////////////////////////////////////// void LLMediaCtrl::setTrusted( bool valIn ) { mTrusted = valIn; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleHover( S32 x, S32 y, MASK mask ) { convertInputCoords(x, y); if (mMediaSource) mMediaSource->mouseMove(x, y); gViewerWindow->setCursor(mLastSetCursor); return TRUE; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleScrollWheel( S32 x, S32 y, S32 clicks ) { if (mMediaSource && mMediaSource->hasMedia()) mMediaSource->getMediaPlugin()->scrollEvent(0, clicks, MASK_NONE); return TRUE; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleMouseUp( S32 x, S32 y, MASK mask ) { convertInputCoords(x, y); if (mMediaSource) { mMediaSource->mouseUp(x, y); // *HACK: media_plugin_webkit automatically takes focus on mouseup, // in addition to the onFocusReceived() call below. Undo this. JC // IMP-595: Is this really still the case for webkit? if (!mTakeFocusOnClick) { mMediaSource->focus(false); gViewerWindow->focusClient(); } } gFocusMgr.setMouseCapture( NULL ); return TRUE; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleMouseDown( S32 x, S32 y, MASK mask ) { convertInputCoords(x, y); if (mMediaSource) mMediaSource->mouseDown(x, y); gFocusMgr.setMouseCapture( this ); if (mTakeFocusOnClick) { setFocus( TRUE ); } return TRUE; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleDoubleClick( S32 x, S32 y, MASK mask ) { convertInputCoords(x, y); if (mMediaSource) mMediaSource->mouseLeftDoubleClick( x, y ); gFocusMgr.setMouseCapture( this ); if (mTakeFocusOnClick) { setFocus( TRUE ); } return TRUE; } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::onFocusReceived() { if (mMediaSource) { mMediaSource->focus(true); // Set focus for edit menu items LLEditMenuHandler::gEditMenuHandler = mMediaSource; } LLUICtrl::onFocusReceived(); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::onFocusLost() { if (mMediaSource) { mMediaSource->focus(false); if( LLEditMenuHandler::gEditMenuHandler == mMediaSource ) { // Clear focus for edit menu items LLEditMenuHandler::gEditMenuHandler = NULL; } } gViewerWindow->focusClient(); LLUICtrl::onFocusLost(); } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleKeyHere( KEY key, MASK mask ) { BOOL result = FALSE; // FIXME: THIS IS SO WRONG. // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... if (mMediaSource) { if( MASK_CONTROL & mask ) { if( 'C' == key ) { mMediaSource->copy(); result = TRUE; } else if( 'V' == key ) { mMediaSource->paste(); result = TRUE; } else if( 'X' == key ) { mMediaSource->cut(); result = TRUE; } } if(!result) { result = mMediaSource->handleKeyHere(key, mask); } } return result; } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::handleVisibilityChange ( BOOL new_visibility ) { llinfos << "visibility changed to " << (new_visibility?"true":"false") << llendl; if(mMediaSource) { mMediaSource->setVisible( new_visibility ); } } //////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleUnicodeCharHere(llwchar uni_char) { BOOL result = FALSE; // only accept 'printable' characters, sigh... if (uni_char >= 32 // discard 'control' characters && uni_char != 127) // SDL thinks this is 'delete' - yuck. { if (mMediaSource) result = mMediaSource->handleUnicodeCharHere(uni_char); } return result; } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::onVisibilityChange ( BOOL new_visibility ) { // set state of frequent updates automatically if visibility changes if ( new_visibility ) { mFrequentUpdates = true; } else { mFrequentUpdates = false; } LLUICtrl::onVisibilityChange(new_visibility); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) { S32 screen_width = mIgnoreUIScale ? llround((F32)width * LLUI::sGLScaleFactor.mV[VX]) : width; S32 screen_height = mIgnoreUIScale ? llround((F32)height * LLUI::sGLScaleFactor.mV[VY]) : height; // llinfos << "reshape called with width = " << width << ", height = " << height << llendl; // when floater is minimized, these sizes are negative if ( mWebBrowserImage && screen_height > 0 && screen_width > 0 ) { mWebBrowserImage->resize( screen_width, screen_height ); mForceUpdate = true; } LLUICtrl::reshape( width, height, called_from_parent ); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::navigateBack() { if (mMediaSource && mMediaSource->hasMedia()) { mMediaSource->getMediaPlugin()->browse_back(); } } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::navigateForward() { if (mMediaSource && mMediaSource->hasMedia()) { mMediaSource->getMediaPlugin()->browse_forward(); } } //////////////////////////////////////////////////////////////////////////////// // bool LLMediaCtrl::canNavigateBack() { if (mMediaSource) return mMediaSource->canNavigateBack(); else return false; } //////////////////////////////////////////////////////////////////////////////// // bool LLMediaCtrl::canNavigateForward() { if (mMediaSource) return mMediaSource->canNavigateForward(); else return false; } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::set404RedirectUrl( std::string redirect_url ) { if(mMediaSource && mMediaSource->hasMedia()) mMediaSource->getMediaPlugin()->set_status_redirect( 404, redirect_url ); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::clr404RedirectUrl() { if(mMediaSource && mMediaSource->hasMedia()) mMediaSource->getMediaPlugin()->set_status_redirect(404, ""); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type) { // don't browse to anything that starts with secondlife:// or sl:// const std::string protocol1 = "secondlife://"; const std::string protocol2 = "sl://"; if ((LLStringUtil::compareInsensitive(url_in.substr(0, protocol1.length()), protocol1) == 0) || (LLStringUtil::compareInsensitive(url_in.substr(0, protocol2.length()), protocol2) == 0)) { // TODO: Print out/log this attempt? // llinfos << "Rejecting attempt to load restricted website :" << urlIn << llendl; return; } if (mMediaSource) { mCurrentNavUrl = url_in; mMediaSource->navigateTo(url_in, mime_type, mime_type.empty()); } } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::navigateToLocalPage( const std::string& subdir, const std::string& filename_in ) { std::string language = LLUI::getLanguage(); std::string delim = gDirUtilp->getDirDelimiter(); std::string filename; filename += subdir; filename += delim; filename += filename_in; std::string expanded_filename = gDirUtilp->findSkinnedFilename("html", language, filename); if (! gDirUtilp->fileExists(expanded_filename)) { if (language != "en-us") { expanded_filename = gDirUtilp->findSkinnedFilename("html", "en-us", filename); if (! gDirUtilp->fileExists(expanded_filename)) { llwarns << "File " << subdir << delim << filename_in << "not found" << llendl; return; } } else { llwarns << "File " << subdir << delim << filename_in << "not found" << llendl; return; } } if (mMediaSource) { mCurrentNavUrl = expanded_filename; mMediaSource->navigateTo(expanded_filename, "text/html", false); } } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::navigateHome() { if( mHomePageUrl.length() ) { if (mMediaSource) mMediaSource->navigateTo(mHomePageUrl); }; } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::setHomePageUrl( const std::string urlIn ) { mHomePageUrl = urlIn; } //////////////////////////////////////////////////////////////////////////////// // bool LLMediaCtrl::setCaretColor(unsigned int red, unsigned int green, unsigned int blue) { //NOOP return false; } //////////////////////////////////////////////////////////////////////////////// // std::string LLMediaCtrl::getHomePageUrl() { return mHomePageUrl; } //////////////////////////////////////////////////////////////////////////////// // LLPluginClassMedia* LLMediaCtrl::getMediaPlugin() { return mMediaSource.isNull() ? NULL : mMediaSource->getMediaPlugin(); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::draw() { if ( ! mWebBrowserImage ) return; if ( gRestoreGL == 1 ) { LLRect r = getRect(); reshape( r.getWidth(), r.getHeight(), FALSE ); return; }; // NOTE: optimization needed here - probably only need to do this once // unless tearoffs change the parent which they probably do. const LLUICtrl* ptr = findRootMostFocusRoot(); if ( ptr && ptr->hasFocus() ) { setFrequentUpdates( true ); } else { setFrequentUpdates( false ); }; // alpha off for this LLGLSUIDefault gls_ui; LLGLDisable gls_alphaTest( GL_ALPHA_TEST ); gGL.pushMatrix(); { if (mIgnoreUIScale) { glLoadIdentity(); // font system stores true screen origin, need to scale this by UI scale factor // to get render origin for this view (with unit scale) gGL.translatef(floorf(LLFontGL::sCurOrigin.mX * LLUI::sGLScaleFactor.mV[VX]), floorf(LLFontGL::sCurOrigin.mY * LLUI::sGLScaleFactor.mV[VY]), LLFontGL::sCurOrigin.mZ); } // scale texture to fit the space using texture coords gGL.getTexUnit(0)->bind(mWebBrowserImage->getTexture()); gGL.color4fv( LLColor4::white.mV ); F32 max_u = ( F32 )mWebBrowserImage->getMediaWidth() / ( F32 )mWebBrowserImage->getWidth(); F32 max_v = ( F32 )mWebBrowserImage->getMediaHeight() / ( F32 )mWebBrowserImage->getHeight(); LLRect r = getRect(); S32 width, height; S32 x_offset = 0; S32 y_offset = 0; if(mStretchToFill) { if(mMaintainAspectRatio) { F32 media_aspect = (F32)(mWebBrowserImage->getMediaWidth()) / (F32)(mWebBrowserImage->getMediaHeight()); F32 view_aspect = (F32)(r.getWidth()) / (F32)(r.getHeight()); if(media_aspect > view_aspect) { // max width, adjusted height width = r.getWidth(); height = llmin(llmax(S32(width / media_aspect), 0), r.getHeight()); } else { // max height, adjusted width height = r.getHeight(); width = llmin(llmax(S32(height * media_aspect), 0), r.getWidth()); } } else { width = r.getWidth(); height = r.getHeight(); } } else { width = llmin(mWebBrowserImage->getMediaWidth(), r.getWidth()); height = llmin(mWebBrowserImage->getMediaHeight(), r.getHeight()); } x_offset = (r.getWidth() - width) / 2; y_offset = (r.getHeight() - height) / 2; if (mIgnoreUIScale) { width = llround((F32)width * LLUI::sGLScaleFactor.mV[VX]); height = llround((F32)height * LLUI::sGLScaleFactor.mV[VY]); x_offset = llround((F32)x_offset * LLUI::sGLScaleFactor.mV[VX]); y_offset = llround((F32)y_offset * LLUI::sGLScaleFactor.mV[VY]); } // draw the browser gGL.setSceneBlendType(LLRender::BT_REPLACE); gGL.begin( LLRender::QUADS ); if (! mWebBrowserImage->getTextureCoordsOpenGL()) { // render using web browser reported width and height, instead of trying to invert GL scale gGL.texCoord2f( max_u, 0.f ); gGL.vertex2i( x_offset + width, y_offset + height ); gGL.texCoord2f( 0.f, 0.f ); gGL.vertex2i( x_offset, y_offset + height ); gGL.texCoord2f( 0.f, max_v ); gGL.vertex2i( x_offset, y_offset ); gGL.texCoord2f( max_u, max_v ); gGL.vertex2i( x_offset + width, y_offset ); } else { // render using web browser reported width and height, instead of trying to invert GL scale gGL.texCoord2f( max_u, max_v ); gGL.vertex2i( x_offset + width, y_offset + height ); gGL.texCoord2f( 0.f, max_v ); gGL.vertex2i( x_offset, y_offset + height ); gGL.texCoord2f( 0.f, 0.f ); gGL.vertex2i( x_offset, y_offset ); gGL.texCoord2f( max_u, 0.f ); gGL.vertex2i( x_offset + width, y_offset ); } gGL.end(); gGL.setSceneBlendType(LLRender::BT_ALPHA); } gGL.popMatrix(); // highlight if keyboard focus here. (TODO: this needs some work) if ( mBorder->getVisible() ) mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus( this ) ); LLUICtrl::draw(); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::convertInputCoords(S32& x, S32& y) { x = mIgnoreUIScale ? llround((F32)x * LLUI::sGLScaleFactor.mV[VX]) : x; if ( ! mWebBrowserImage->getTextureCoordsOpenGL() ) { y = mIgnoreUIScale ? llround((F32)(y) * LLUI::sGLScaleFactor.mV[VY]) : y; } else { y = mIgnoreUIScale ? llround((F32)(getRect().getHeight() - y) * LLUI::sGLScaleFactor.mV[VY]) : getRect().getHeight() - y; }; } //////////////////////////////////////////////////////////////////////////////// // static bool LLMediaCtrl::onClickLinkExternalTarget(const LLSD& notification, const LLSD& response ) { S32 option = LLNotification::getSelectedOption(notification, response); if ( 0 == option ) { // open in external browser because we don't support // creation of our own secondary browser windows LLWeb::loadURLExternal( notification["payload"]["external_url"].asString() ); } return false; } //////////////////////////////////////////////////////////////////////////////// // inherited from LLViewerMediaObserver //virtual void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) { switch(event) { case MEDIA_EVENT_CONTENT_UPDATED: { // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; }; break; case MEDIA_EVENT_TIME_DURATION_UPDATED: { // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; }; break; case MEDIA_EVENT_SIZE_CHANGED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; LLRect r = getRect(); reshape( r.getWidth(), r.getHeight(), FALSE ); }; break; case MEDIA_EVENT_CURSOR_CHANGED: { LL_INFOS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; std::string cursor = self->getCursorName(); if(cursor == "arrow") mLastSetCursor = UI_CURSOR_ARROW; else if(cursor == "ibeam") mLastSetCursor = UI_CURSOR_IBEAM; else if(cursor == "splith") mLastSetCursor = UI_CURSOR_SIZEWE; else if(cursor == "splitv") mLastSetCursor = UI_CURSOR_SIZENS; else if(cursor == "hand") mLastSetCursor = UI_CURSOR_HAND; else // for anything else, default to the arrow mLastSetCursor = UI_CURSOR_ARROW; }; break; case MEDIA_EVENT_NAVIGATE_BEGIN: { LL_INFOS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN, url is " << self->getNavigateURI() << LL_ENDL; if(mMediaSource && mHideLoading) { mMediaSource->suspendUpdates(true); } }; break; case MEDIA_EVENT_NAVIGATE_COMPLETE: { LL_INFOS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; if(mMediaSource && mHideLoading) { mMediaSource->suspendUpdates(false); } }; break; case MEDIA_EVENT_PROGRESS_UPDATED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; }; break; case MEDIA_EVENT_STATUS_TEXT_CHANGED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; }; break; case MEDIA_EVENT_LOCATION_CHANGED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; }; break; case MEDIA_EVENT_CLICK_LINK_HREF: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << self->getClickTarget() << "\", uri is " << self->getClickURL() << LL_ENDL; onClickLinkHref(self); }; break; case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; onClickLinkNoFollow(self); }; break; case MEDIA_EVENT_PLUGIN_FAILED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; }; break; case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL; }; break; case MEDIA_EVENT_NAME_CHANGED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAME_CHANGED" << LL_ENDL; }; break; }; // chain all events to any potential observers of this object. emitEvent(self, event); } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::onClickLinkHref( LLPluginClassMedia* self ) { // retrieve the event parameters std::string target = self->getClickTarget(); std::string url = self->getClickURL(); // if there is a value for the target if ( !target.empty() ) { if ( target == "_external" ) { mExternalUrl = url; LLSD payload; payload["external_url"] = mExternalUrl; LLNotifications::instance().add( "WebLaunchExternalTarget", LLSD(), payload, onClickLinkExternalTarget); return; } } const std::string protocol1( "http://" ); const std::string protocol2( "https://" ); if( mOpenLinksInExternalBrowser ) { if ( !url.empty() ) { if ( LLStringUtil::compareInsensitive( url.substr( 0, protocol1.length() ), protocol1 ) == 0 || LLStringUtil::compareInsensitive( url.substr( 0, protocol2.length() ), protocol2 ) == 0 ) { LLWeb::loadURLExternal( url ); } } } else if( mOpenLinksInInternalBrowser ) { if ( !url.empty() ) { if ( LLStringUtil::compareInsensitive( url.substr( 0, protocol1.length() ), protocol1 ) == 0 || LLStringUtil::compareInsensitive( url.substr( 0, protocol2.length() ), protocol2 ) == 0 ) { // If we spawn a new LLFloaterHTML, assume we want it to // follow this LLMediaCtrl's trust for whether or // not to open secondlife:///app/ links. JC. // const bool open_links_externally = false; // LLFloaterHtml::getInstance()->show( // event_in.mStringPayload, // "Second Life Browser", // open_links_externally, // mTrusted); } } } } //////////////////////////////////////////////////////////////////////////////// // void LLMediaCtrl::onClickLinkNoFollow( LLPluginClassMedia* self ) { std::string url = self->getClickURL(); if (LLURLDispatcher::isSLURLCommand(url) && !mTrusted) { // block handling of this secondlife:///app/ URL LLNotifications::instance().add("UnableToOpenCommandURL"); return; } LLURLDispatcher::dispatch(url, this, mTrusted); } //////////////////////////////////////////////////////////////////////////////// // LLWebBrowserTexture::LLWebBrowserTexture( S32 width, S32 height, LLMediaCtrl* browserCtrl, viewer_media_t media_source ) : LLDynamicTexture( 512, 512, 4, ORDER_FIRST, TRUE ), mNeedsUpdate( true ), mNeedsResize( false ), mTextureCoordsOpenGL( true ), mWebBrowserCtrl( browserCtrl ), mMediaSource(media_source) { mElapsedTime.start(); resize( width, height ); } //////////////////////////////////////////////////////////////////////////////// // LLWebBrowserTexture::~LLWebBrowserTexture() { mElapsedTime.stop(); mMediaSource = NULL; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLWebBrowserTexture::needsRender() { bool texture_dirty = false; if ( mWebBrowserCtrl->getFrequentUpdates() || mWebBrowserCtrl->getAlwaysRefresh() || mWebBrowserCtrl->getForceUpdate() ) { // All of these force an update return TRUE; } // If the texture needs updating, render needs to be called. if (mMediaSource && mMediaSource->hasMedia()) { LLPluginClassMedia* media = mMediaSource->getMediaPlugin(); if(media->textureValid() && media->getDirty()) { texture_dirty = true; } } return texture_dirty; } //////////////////////////////////////////////////////////////////////////////// // BOOL LLWebBrowserTexture::render() { if(updateBrowserTexture()) { // updateBrowserTexture already verified that the media plugin is there and the texture is valid. LLPluginClassMedia* media_plugin = mMediaSource->getMediaPlugin(); LLRect dirty_rect; if(mNeedsUpdate) { // If we need an update, use the whole rect instead of the dirty rect. dirty_rect.mLeft = 0; dirty_rect.mBottom = 0; dirty_rect.mRight = media_plugin->getWidth(); dirty_rect.mTop = media_plugin->getHeight(); } else { mNeedsUpdate = media_plugin->getDirty(&dirty_rect); } if ( mNeedsUpdate ) { mNeedsUpdate = false; mWebBrowserCtrl->setForceUpdate(false); // Constrain the dirty rect to be inside the texture S32 x_pos = llmax(dirty_rect.mLeft, 0); S32 y_pos = llmax(dirty_rect.mBottom, 0); S32 width = llmin(dirty_rect.mRight, getWidth()) - x_pos; S32 height = llmin(dirty_rect.mTop, getHeight()) - y_pos; if(width > 0 && height > 0) { U8* data = media_plugin->getBitsData(); // Offset the pixels pointer to match x_pos and y_pos data += ( x_pos * media_plugin->getTextureDepth() * media_plugin->getBitsWidth() ); data += ( y_pos * media_plugin->getTextureDepth() ); mTexture->setSubImage( data, media_plugin->getBitsWidth(), media_plugin->getBitsHeight(), x_pos, y_pos, width, height, TRUE); // force a fast update (i.e. don't call analyzeAlpha, etc.) } media_plugin->resetDirty(); return TRUE; }; }; return FALSE; } //////////////////////////////////////////////////////////////////////////////// // S32 LLWebBrowserTexture::getMediaWidth() { return mMediaWidth; } //////////////////////////////////////////////////////////////////////////////// // S32 LLWebBrowserTexture::getMediaHeight() { return mMediaHeight; } //////////////////////////////////////////////////////////////////////////////// // void LLWebBrowserTexture::setNeedsUpdate() { mNeedsUpdate = true; } //////////////////////////////////////////////////////////////////////////////// // bool LLWebBrowserTexture::getNeedsUpdate() { return mNeedsUpdate; } //////////////////////////////////////////////////////////////////////////////// // bool LLWebBrowserTexture::getTextureCoordsOpenGL() { return mTextureCoordsOpenGL; } //////////////////////////////////////////////////////////////////////////////// // void LLWebBrowserTexture::resize( S32 new_width, S32 new_height ) { F32 scale_ratio = 1.f; if (new_width > MAX_DIMENSION) { scale_ratio = (F32)MAX_DIMENSION / (F32)new_width; } if (new_height > MAX_DIMENSION) { scale_ratio = llmin(scale_ratio, (F32)MAX_DIMENSION / (F32)new_height); } mMediaWidth = llround(scale_ratio * (F32)new_width); mMediaHeight = llround(scale_ratio * (F32)new_height); adjustSize(); } bool LLWebBrowserTexture::adjustSize() { if (mMediaSource && mMediaSource->hasMedia()) { int natural_width = mMediaSource->getMediaPlugin()->getNaturalWidth(); int natural_height = mMediaSource->getMediaPlugin()->getNaturalHeight(); if(natural_width != 0) { // If the media has a "natural size", use it. mMediaWidth = natural_width; mMediaHeight = natural_height; } mMediaSource->setSize(mMediaWidth, mMediaHeight); mNeedsResize = false; return true; } else { // The media isn't fully initialized yet, delay the resize until later. mNeedsResize = true; } return false; } bool LLWebBrowserTexture::updateBrowserTexture() { if (!adjustSize()) return false; LLPluginClassMedia* media = mMediaSource->getMediaPlugin(); if(!media->textureValid()) return false; if(mMediaSource->mNeedsNewTexture || media->getTextureWidth() != mWidth || media->getTextureHeight() != mHeight ) { releaseGLTexture(); mWidth = media->getTextureWidth(); mHeight = media->getTextureHeight(); mTextureCoordsOpenGL = media->getTextureCoordsOpenGL(); // will create mWidth * mHeight sized texture, using the texture params specified by the media. LLDynamicTexture::generateGLTexture( media->getTextureFormatInternal(), media->getTextureFormatPrimary(), media->getTextureFormatType(), media->getTextureFormatSwapBytes()); mMediaSource->mNeedsNewTexture = false; } return true; } // virtual LLXMLNodePtr LLMediaCtrl::getXML(bool save_children) const { LLXMLNodePtr node = LLUICtrl::getXML(); node->setName(LL_WEB_BROWSER_CTRL_TAG); return node; } LLView* LLMediaCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) { std::string name("web_browser"); node->getAttributeString("name", name); std::string start_url(""); node->getAttributeString("start_url", start_url ); BOOL border_visible = true; node->getAttributeBOOL("border_visible", border_visible); LLRect rect; createRect(node, rect, parent, LLRect()); LLMediaCtrl* web_browser = new LLMediaCtrl( name, rect ); if(node->hasAttribute("caret_color")) { LLColor4 color; LLUICtrlFactory::getAttributeColor(node, "caret_color", color); LLColor4U colorU = LLColor4U(color); web_browser->setCaretColor( colorU.mV[0], colorU.mV[1], colorU.mV[2] ); } BOOL ignore_ui_scale = web_browser->getIgnoreUIScale(); node->getAttributeBOOL("ignore_ui_scale", ignore_ui_scale); web_browser->setIgnoreUIScale((bool)ignore_ui_scale); web_browser->initFromXML(node, parent); web_browser->setHomePageUrl( start_url ); web_browser->setBorderVisible( border_visible ); if(! start_url.empty()) { web_browser->navigateHome(); } return web_browser; } std::string LLMediaCtrl::getCurrentNavUrl() { return mCurrentNavUrl; }