1105 lines
36 KiB
C++
1105 lines
36 KiB
C++
/**
|
|
* @file media_plugin_cef.cpp
|
|
* @brief CEF (Chromium Embedding Framework) plugin for LLMedia API plugin system
|
|
*
|
|
* @cond
|
|
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, 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;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* 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
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
* @endcond
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
#include "indra_constants.h" // for indra keyboard codes
|
|
|
|
#include "llgl.h"
|
|
#include "llsdutil.h"
|
|
#include "llplugininstance.h"
|
|
#include "llpluginmessage.h"
|
|
#include "llpluginmessageclasses.h"
|
|
#include "volume_catcher.h"
|
|
#include "media_plugin_base.h"
|
|
|
|
#include <functional>
|
|
#include <chrono>
|
|
|
|
#include "dullahan.h"
|
|
#include "dullahan_version.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
class MediaPluginCEF :
|
|
public MediaPluginBase
|
|
{
|
|
public:
|
|
MediaPluginCEF(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
|
|
~MediaPluginCEF();
|
|
|
|
/*virtual*/
|
|
void receiveMessage(const char* message_string);
|
|
|
|
private:
|
|
bool init();
|
|
|
|
void onPageChangedCallback(const unsigned char* pixels, int x, int y, const int width, const int height);
|
|
void onCustomSchemeURLCallback(std::string url);
|
|
void onConsoleMessageCallback(std::string message, std::string source, int line);
|
|
void onStatusMessageCallback(std::string value);
|
|
void onTitleChangeCallback(std::string title);
|
|
void onTooltipCallback(std::string text);
|
|
void onLoadStartCallback();
|
|
void onRequestExitCallback();
|
|
void onLoadEndCallback(int httpStatusCode);
|
|
void onLoadError(int status, const std::string error_text);
|
|
void onAddressChangeCallback(std::string url);
|
|
void onOpenPopupCallback(std::string url, std::string target);
|
|
bool onHTTPAuthCallback(const std::string host, const std::string realm, std::string& username, std::string& password);
|
|
void onCursorChangedCallback(dullahan::ECursorType type);
|
|
const std::vector<std::string> onFileDialog(dullahan::EFileDialogType dialog_type, const std::string dialog_title, const std::string default_file, const std::string dialog_accept_filter, bool& use_default);
|
|
bool onJSDialogCallback(const std::string origin_url, const std::string message_text, const std::string default_prompt_text);
|
|
bool onJSBeforeUnloadCallback();
|
|
|
|
void postDebugMessage(const std::string& msg);
|
|
void authResponse(LLPluginMessage &message);
|
|
|
|
void keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_data);
|
|
void unicodeInput(std::string event, LLSD native_key_data);
|
|
|
|
void checkEditState();
|
|
void setVolume();
|
|
|
|
bool mEnableMediaPluginDebugging;
|
|
std::string mHostLanguage;
|
|
bool mCookiesEnabled;
|
|
bool mPluginsEnabled;
|
|
bool mJavascriptEnabled;
|
|
bool mDisableGPU;
|
|
bool mDisableNetworkService;
|
|
bool mUseMockKeyChain;
|
|
std::string mUserAgentSubtring;
|
|
std::string mAuthUsername;
|
|
std::string mAuthPassword;
|
|
bool mAuthOK;
|
|
bool mCanCut;
|
|
bool mCanCopy;
|
|
bool mCanPaste;
|
|
std::string mRootCachePath;
|
|
std::string mCachePath;
|
|
std::string mContextCachePath;
|
|
std::string mCefLogFile;
|
|
bool mCefLogVerbose;
|
|
std::vector<std::string> mPickedFiles;
|
|
VolumeCatcher mVolumeCatcher;
|
|
F32 mCurVolume;
|
|
dullahan* mCEFLib;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
MediaPluginCEF::MediaPluginCEF(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data) :
|
|
MediaPluginBase(host_send_func, host_user_data)
|
|
{
|
|
mWidth = 0;
|
|
mHeight = 0;
|
|
mDepth = 4;
|
|
mPixels = 0;
|
|
mEnableMediaPluginDebugging = true;
|
|
mHostLanguage = "en";
|
|
mCookiesEnabled = true;
|
|
mPluginsEnabled = false;
|
|
mJavascriptEnabled = true;
|
|
mDisableGPU = false;
|
|
mDisableNetworkService = true;
|
|
mUseMockKeyChain = true;
|
|
mUserAgentSubtring = "";
|
|
mAuthUsername = "";
|
|
mAuthPassword = "";
|
|
mAuthOK = false;
|
|
mCanCut = false;
|
|
mCanCopy = false;
|
|
mCanPaste = false;
|
|
mCachePath = "";
|
|
mCefLogFile = "";
|
|
mCefLogVerbose = false;
|
|
mPickedFiles.clear();
|
|
mCurVolume = 0.0;
|
|
|
|
mCEFLib = new dullahan();
|
|
|
|
setVolume();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
MediaPluginCEF::~MediaPluginCEF()
|
|
{
|
|
mCEFLib->shutdown();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::postDebugMessage(const std::string& msg)
|
|
{
|
|
if (mEnableMediaPluginDebugging)
|
|
{
|
|
std::stringstream str;
|
|
str << "@Media Msg> " << msg;
|
|
|
|
LLPluginMessage debug_message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "debug_message");
|
|
debug_message.setValue("message_text", str.str());
|
|
debug_message.setValue("message_level", "info");
|
|
sendMessage(debug_message);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onPageChangedCallback(const unsigned char* pixels, int x, int y, const int width, const int height)
|
|
{
|
|
if( mPixels && pixels )
|
|
{
|
|
if (mWidth == width && mHeight == height)
|
|
{
|
|
memcpy(mPixels, pixels, mWidth * mHeight * mDepth);
|
|
}
|
|
else
|
|
{
|
|
mCEFLib->setSize(mWidth, mHeight);
|
|
}
|
|
setDirty(0, 0, mWidth, mHeight);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onConsoleMessageCallback(std::string message, std::string source, int line)
|
|
{
|
|
std::stringstream str;
|
|
str << "Console message: " << message << " in file(" << source << ") at line " << line;
|
|
postDebugMessage(str.str());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onStatusMessageCallback(std::string value)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "status_text");
|
|
message.setValue("status", value);
|
|
sendMessage(message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onTitleChangeCallback(std::string title)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
|
|
message.setValue("name", title);
|
|
message.setValueBoolean("history_back_available", mCEFLib->canGoBack());
|
|
message.setValueBoolean("history_forward_available", mCEFLib->canGoForward());
|
|
sendMessage(message);
|
|
}
|
|
|
|
void MediaPluginCEF::onTooltipCallback(std::string text)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "tooltip_text");
|
|
message.setValue("tooltip", text);
|
|
sendMessage(message);
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onLoadStartCallback()
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_begin");
|
|
//message.setValue("uri", event.getEventUri()); // not easily available here in CEF - needed?
|
|
message.setValueBoolean("history_back_available", mCEFLib->canGoBack());
|
|
message.setValueBoolean("history_forward_available", mCEFLib->canGoForward());
|
|
sendMessage(message);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onLoadError(int status, const std::string error_text)
|
|
{
|
|
std::stringstream msg;
|
|
|
|
msg << "<b>Loading error!</b>";
|
|
msg << "<p>";
|
|
msg << "Message: " << error_text;
|
|
msg << "<br>";
|
|
msg << "Code: " << status;
|
|
|
|
mCEFLib->showBrowserMessage(msg.str());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onRequestExitCallback()
|
|
{
|
|
LLPluginMessage message("base", "goodbye");
|
|
sendMessage(message);
|
|
|
|
// Will trigger delete on next staticReceiveMessage()
|
|
mDeleteMe = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onLoadEndCallback(int httpStatusCode)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete");
|
|
//message.setValue("uri", event.getEventUri()); // not easily available here in CEF - needed?
|
|
message.setValueS32("result_code", httpStatusCode);
|
|
message.setValueBoolean("history_back_available", mCEFLib->canGoBack());
|
|
message.setValueBoolean("history_forward_available", mCEFLib->canGoForward());
|
|
sendMessage(message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onAddressChangeCallback(std::string url)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "location_changed");
|
|
message.setValue("uri", url);
|
|
sendMessage(message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onOpenPopupCallback(std::string url, std::string target)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_href");
|
|
message.setValue("uri", url);
|
|
message.setValue("target", target);
|
|
sendMessage(message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onCustomSchemeURLCallback(std::string url)
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_nofollow");
|
|
message.setValue("uri", url);
|
|
message.setValue("nav_type", "clicked"); // TODO: differentiate between click and navigate to
|
|
sendMessage(message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
bool MediaPluginCEF::onHTTPAuthCallback(const std::string host, const std::string realm, std::string& username, std::string& password)
|
|
{
|
|
mAuthOK = false;
|
|
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "auth_request");
|
|
message.setValue("url", host);
|
|
message.setValue("realm", realm);
|
|
message.setValueBoolean("blocking_request", true);
|
|
|
|
// The "blocking_request" key in the message means this sendMessage call will block until a response is received.
|
|
sendMessage(message);
|
|
|
|
if (mAuthOK)
|
|
{
|
|
username = mAuthUsername;
|
|
password = mAuthPassword;
|
|
}
|
|
|
|
return mAuthOK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
const std::vector<std::string> MediaPluginCEF::onFileDialog(dullahan::EFileDialogType dialog_type, const std::string dialog_title, const std::string default_file, std::string dialog_accept_filter, bool& use_default)
|
|
{
|
|
// do not use the default CEF file picker
|
|
use_default = false;
|
|
|
|
if (dialog_type == dullahan::FD_OPEN_FILE)
|
|
{
|
|
mPickedFiles.clear();
|
|
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "pick_file");
|
|
message.setValueBoolean("blocking_request", true);
|
|
message.setValueBoolean("multiple_files", false);
|
|
|
|
sendMessage(message);
|
|
|
|
return mPickedFiles;
|
|
}
|
|
else if (dialog_type == dullahan::FD_OPEN_MULTIPLE_FILES)
|
|
{
|
|
mPickedFiles.clear();
|
|
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "pick_file");
|
|
message.setValueBoolean("blocking_request", true);
|
|
message.setValueBoolean("multiple_files", true);
|
|
|
|
sendMessage(message);
|
|
|
|
return mPickedFiles;
|
|
}
|
|
else if (dialog_type == dullahan::FD_SAVE_FILE)
|
|
{
|
|
mAuthOK = false;
|
|
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "file_download");
|
|
message.setValue("filename", default_file);
|
|
|
|
sendMessage(message);
|
|
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
bool MediaPluginCEF::onJSDialogCallback(const std::string origin_url, const std::string message_text, const std::string default_prompt_text)
|
|
{
|
|
// return true indicates we suppress the JavaScript alert UI entirely
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
bool MediaPluginCEF::onJSBeforeUnloadCallback()
|
|
{
|
|
// return true indicates we suppress the JavaScript UI entirely
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::onCursorChangedCallback(dullahan::ECursorType type)
|
|
{
|
|
std::string name = "";
|
|
|
|
switch (type)
|
|
{
|
|
case dullahan::CT_POINTER:
|
|
name = "UI_CURSOR_ARROW";
|
|
break;
|
|
case dullahan::CT_CROSS:
|
|
name = "UI_CURSOR_CROSS";
|
|
break;
|
|
case dullahan::CT_HAND:
|
|
name = "UI_CURSOR_HAND";
|
|
break;
|
|
case dullahan::CT_IBEAM:
|
|
name = "UI_CURSOR_IBEAM";
|
|
break;
|
|
case dullahan::CT_WAIT:
|
|
name = "UI_CURSOR_WAIT";
|
|
break;
|
|
//case dullahan::CT_HELP:
|
|
case dullahan::CT_ROWRESIZE:
|
|
case dullahan::CT_NORTHRESIZE:
|
|
case dullahan::CT_SOUTHRESIZE:
|
|
case dullahan::CT_NORTHSOUTHRESIZE:
|
|
name = "UI_CURSOR_SIZENS";
|
|
break;
|
|
case dullahan::CT_COLUMNRESIZE:
|
|
case dullahan::CT_EASTRESIZE:
|
|
case dullahan::CT_WESTRESIZE:
|
|
case dullahan::CT_EASTWESTRESIZE:
|
|
name = "UI_CURSOR_SIZEWE";
|
|
break;
|
|
case dullahan::CT_NORTHEASTRESIZE:
|
|
case dullahan::CT_SOUTHWESTRESIZE:
|
|
case dullahan::CT_NORTHEASTSOUTHWESTRESIZE:
|
|
name = "UI_CURSOR_SIZENESW";
|
|
break;
|
|
case dullahan::CT_SOUTHEASTRESIZE:
|
|
case dullahan::CT_NORTHWESTRESIZE:
|
|
case dullahan::CT_NORTHWESTSOUTHEASTRESIZE:
|
|
name = "UI_CURSOR_SIZENWSE";
|
|
break;
|
|
case dullahan::CT_MOVE:
|
|
name = "UI_CURSOR_SIZEALL";
|
|
break;
|
|
//case dullahan::CT_MIDDLEPANNING:
|
|
//case dullahan::CT_EASTPANNING:
|
|
//case dullahan::CT_NORTHPANNING:
|
|
//case dullahan::CT_NORTHEASTPANNING:
|
|
//case dullahan::CT_NORTHWESTPANNING:
|
|
//case dullahan::CT_SOUTHPANNING:
|
|
//case dullahan::CT_SOUTHEASTPANNING:
|
|
//case dullahan::CT_SOUTHWESTPANNING:
|
|
//case dullahan::CT_WESTPANNING:
|
|
//case dullahan::CT_VERTICALTEXT:
|
|
//case dullahan::CT_CELL:
|
|
//case dullahan::CT_CONTEXTMENU:
|
|
case dullahan::CT_ALIAS:
|
|
name = "UI_CURSOR_TOOLMEDIAOPEN";
|
|
break;
|
|
case dullahan::CT_PROGRESS:
|
|
name = "UI_CURSOR_WORKING";
|
|
break;
|
|
case dullahan::CT_COPY:
|
|
name = "UI_CURSOR_ARROWCOPY";
|
|
break;
|
|
case dullahan::CT_NONE:
|
|
name = "UI_CURSOR_NO";
|
|
break;
|
|
case dullahan::CT_NODROP:
|
|
case dullahan::CT_NOTALLOWED:
|
|
name = "UI_CURSOR_NOLOCKED";
|
|
break;
|
|
case dullahan::CT_ZOOMIN:
|
|
name = "UI_CURSOR_TOOLZOOMIN";
|
|
break;
|
|
case dullahan::CT_ZOOMOUT:
|
|
name = "UI_CURSOR_TOOLZOOMOUT";
|
|
break;
|
|
case dullahan::CT_GRAB:
|
|
name = "UI_CURSOR_TOOLGRAB";
|
|
break;
|
|
//case dullahan::CT_GRABING:
|
|
//case dullahan::CT_CUSTOM:
|
|
|
|
default:
|
|
LL_WARNS() << "Unknown cursor ID: " << (int)type << LL_ENDL;
|
|
break;
|
|
}
|
|
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "cursor_changed");
|
|
message.setValue("name", name);
|
|
sendMessage(message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::authResponse(LLPluginMessage &message)
|
|
{
|
|
mAuthOK = message.getValueBoolean("ok");
|
|
if (mAuthOK)
|
|
{
|
|
mAuthUsername = message.getValue("username");
|
|
mAuthPassword = message.getValue("password");
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::receiveMessage(const char* message_string)
|
|
{
|
|
// std::cerr << "MediaPluginCEF::receiveMessage: received message: \"" << message_string << "\"" << std::endl;
|
|
LLPluginMessage message_in;
|
|
|
|
if (message_in.parse(message_string) >= 0)
|
|
{
|
|
std::string message_class = message_in.getClass();
|
|
std::string message_name = message_in.getName();
|
|
if (message_class == LLPLUGIN_MESSAGE_CLASS_BASE)
|
|
{
|
|
if (message_name == "init")
|
|
{
|
|
LLPluginMessage message("base", "init_response");
|
|
LLSD versions = LLSD::emptyMap();
|
|
versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION;
|
|
versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION;
|
|
versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER] = LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION;
|
|
message.setValueLLSD("versions", versions);
|
|
|
|
std::string plugin_version = "CEF plugin 1.1.412";
|
|
message.setValue("plugin_version", plugin_version);
|
|
sendMessage(message);
|
|
}
|
|
else if (message_name == "idle")
|
|
{
|
|
mCEFLib->update();
|
|
|
|
mVolumeCatcher.pump();
|
|
|
|
// this seems bad but unless the state changes (it won't until we figure out
|
|
// how to get CEF to tell us if copy/cut/paste is available) then this function
|
|
// will return immediately
|
|
checkEditState();
|
|
}
|
|
else if (message_name == "cleanup")
|
|
{
|
|
mCEFLib->requestExit();
|
|
}
|
|
else if (message_name == "force_exit")
|
|
{
|
|
mDeleteMe = true;
|
|
}
|
|
else if (message_name == "shm_added")
|
|
{
|
|
SharedSegmentInfo info;
|
|
info.mAddress = message_in.getValuePointer("address");
|
|
info.mSize = (size_t)message_in.getValueS32("size");
|
|
std::string name = message_in.getValue("name");
|
|
|
|
mSharedSegments.insert(SharedSegmentMap::value_type(name, info));
|
|
|
|
}
|
|
else if (message_name == "shm_remove")
|
|
{
|
|
std::string name = message_in.getValue("name");
|
|
|
|
SharedSegmentMap::iterator iter = mSharedSegments.find(name);
|
|
if (iter != mSharedSegments.end())
|
|
{
|
|
if (mPixels == iter->second.mAddress)
|
|
{
|
|
mPixels = NULL;
|
|
mTextureSegmentName.clear();
|
|
}
|
|
mSharedSegments.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
LLPluginMessage message("base", "shm_remove_response");
|
|
message.setValue("name", name);
|
|
sendMessage(message);
|
|
}
|
|
else
|
|
{
|
|
}
|
|
}
|
|
else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
|
|
{
|
|
if (message_name == "init")
|
|
{
|
|
// event callbacks from Dullahan
|
|
mCEFLib->setOnPageChangedCallback(std::bind(&MediaPluginCEF::onPageChangedCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
|
|
mCEFLib->setOnCustomSchemeURLCallback(std::bind(&MediaPluginCEF::onCustomSchemeURLCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnConsoleMessageCallback(std::bind(&MediaPluginCEF::onConsoleMessageCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
|
mCEFLib->setOnStatusMessageCallback(std::bind(&MediaPluginCEF::onStatusMessageCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnTitleChangeCallback(std::bind(&MediaPluginCEF::onTitleChangeCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnTooltipCallback(std::bind(&MediaPluginCEF::onTooltipCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnLoadStartCallback(std::bind(&MediaPluginCEF::onLoadStartCallback, this));
|
|
mCEFLib->setOnLoadEndCallback(std::bind(&MediaPluginCEF::onLoadEndCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnLoadErrorCallback(std::bind(&MediaPluginCEF::onLoadError, this, std::placeholders::_1, std::placeholders::_2));
|
|
mCEFLib->setOnAddressChangeCallback(std::bind(&MediaPluginCEF::onAddressChangeCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnOpenPopupCallback(std::bind(&MediaPluginCEF::onOpenPopupCallback, this, std::placeholders::_1, std::placeholders::_2));
|
|
mCEFLib->setOnHTTPAuthCallback(std::bind(&MediaPluginCEF::onHTTPAuthCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
|
mCEFLib->setOnFileDialogCallback(std::bind(&MediaPluginCEF::onFileDialog, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
|
|
mCEFLib->setOnCursorChangedCallback(std::bind(&MediaPluginCEF::onCursorChangedCallback, this, std::placeholders::_1));
|
|
mCEFLib->setOnRequestExitCallback(std::bind(&MediaPluginCEF::onRequestExitCallback, this));
|
|
mCEFLib->setOnJSDialogCallback(std::bind(&MediaPluginCEF::onJSDialogCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
|
mCEFLib->setOnJSBeforeUnloadCallback(std::bind(&MediaPluginCEF::onJSBeforeUnloadCallback, this));
|
|
|
|
dullahan::dullahan_settings settings;
|
|
#if LL_WINDOWS
|
|
// As of CEF version 83+, for Windows versions, we need to tell CEF
|
|
// where the host helper process is since this DLL is not in the same
|
|
// dir as the executable that loaded it (SLPlugin.exe). The code in
|
|
// Dullahan that tried to figure out the location automatically uses
|
|
// the location of the exe which isn't helpful so we tell it explicitly.
|
|
char cur_dir_str[MAX_PATH];
|
|
GetCurrentDirectoryA(MAX_PATH, cur_dir_str);
|
|
settings.host_process_path = std::string(cur_dir_str);
|
|
#endif
|
|
settings.accept_language_list = mHostLanguage;
|
|
|
|
// SL-15560: Product team overruled my change to set the default
|
|
// embedded background color to match the floater background
|
|
// and set it to white
|
|
settings.background_color = 0xffffffff; // white
|
|
|
|
settings.cache_enabled = true;
|
|
settings.root_cache_path = mRootCachePath;
|
|
settings.cache_path = mCachePath;
|
|
settings.context_cache_path = mContextCachePath;
|
|
settings.cookies_enabled = mCookiesEnabled;
|
|
settings.disable_gpu = mDisableGPU;
|
|
#if LL_DARWIN
|
|
settings.disable_network_service = mDisableNetworkService;
|
|
settings.use_mock_keychain = mUseMockKeyChain;
|
|
#endif
|
|
// This setting applies to all plugins, not just Flash
|
|
// Regarding, SL-15559 PDF files do not load in CEF v91,
|
|
// it turns out that on Windows, PDF support is treated
|
|
// as a plugin on Windows only so turning all plugins
|
|
// off, disabled built in PDF support. (Works okay in
|
|
// macOS surprisingly). To mitigrate this, we set the global
|
|
// media enabled flag to whatever the consumer wants and
|
|
// explicitly disable Flash with a different setting (below)
|
|
settings.plugins_enabled = mPluginsEnabled;
|
|
|
|
// SL-14897 Disable Flash support in the embedded browser
|
|
settings.flash_enabled = false;
|
|
|
|
settings.flip_mouse_y = false;
|
|
settings.flip_pixels_y = true;
|
|
settings.frame_rate = 60;
|
|
|
|
settings.force_wave_audio = true;
|
|
|
|
settings.initial_height = 1024;
|
|
settings.initial_width = 1024;
|
|
settings.java_enabled = false;
|
|
settings.javascript_enabled = mJavascriptEnabled;
|
|
settings.media_stream_enabled = false; // MAINT-6060 - WebRTC media removed until we can add granularity/query UI
|
|
|
|
settings.user_agent_substring = mCEFLib->makeCompatibleUserAgentString(mUserAgentSubtring);
|
|
settings.webgl_enabled = true;
|
|
settings.log_file = mCefLogFile;
|
|
settings.log_verbose = mCefLogVerbose;
|
|
settings.autoplay_without_gesture = true;
|
|
|
|
std::vector<std::string> custom_schemes(1, "secondlife");
|
|
mCEFLib->setCustomSchemes(custom_schemes);
|
|
|
|
bool result = mCEFLib->init(settings);
|
|
if (!result)
|
|
{
|
|
// if this fails, the media system in viewer will put up a message
|
|
}
|
|
|
|
// now we can set page zoom factor
|
|
F32 factor = (F32)message_in.getValueReal("factor");
|
|
#if LL_DARWIN
|
|
//temporary fix for SL-10473: issue with displaying checkboxes on Mojave
|
|
factor*=1.001;
|
|
#endif
|
|
mCEFLib->setPageZoom(factor);
|
|
|
|
// Plugin gets to decide the texture parameters to use.
|
|
mDepth = 4;
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params");
|
|
message.setValueS32("default_width", 1024);
|
|
message.setValueS32("default_height", 1024);
|
|
message.setValueS32("depth", mDepth);
|
|
message.setValueU32("internalformat", GL_RGB);
|
|
message.setValueU32("format", GL_BGRA);
|
|
message.setValueU32("type", GL_UNSIGNED_BYTE);
|
|
message.setValueBoolean("coords_opengl", true);
|
|
sendMessage(message);
|
|
}
|
|
else if (message_name == "set_user_data_path")
|
|
{
|
|
std::string user_data_path_cache = message_in.getValue("cache_path");
|
|
std::string subfolder = message_in.getValue("username");
|
|
|
|
mRootCachePath = user_data_path_cache + "cef_cache";
|
|
if (!subfolder.empty())
|
|
{
|
|
std::string delim;
|
|
#if LL_WINDOWS
|
|
// media plugin doesn't have access to gDirUtilp
|
|
delim = "\\";
|
|
#else
|
|
delim = "/";
|
|
#endif
|
|
mCachePath = mRootCachePath + delim + subfolder;
|
|
}
|
|
else
|
|
{
|
|
mCachePath = mRootCachePath;
|
|
}
|
|
mContextCachePath = ""; // disabled by ""
|
|
mCefLogFile = message_in.getValue("cef_log_file");
|
|
mCefLogVerbose = message_in.getValueBoolean("cef_verbose_log");
|
|
}
|
|
else if (message_name == "size_change")
|
|
{
|
|
std::string name = message_in.getValue("name");
|
|
S32 width = message_in.getValueS32("width");
|
|
S32 height = message_in.getValueS32("height");
|
|
S32 texture_width = message_in.getValueS32("texture_width");
|
|
S32 texture_height = message_in.getValueS32("texture_height");
|
|
|
|
if (!name.empty())
|
|
{
|
|
// Find the shared memory region with this name
|
|
SharedSegmentMap::iterator iter = mSharedSegments.find(name);
|
|
if (iter != mSharedSegments.end())
|
|
{
|
|
mPixels = (unsigned char*)iter->second.mAddress;
|
|
mWidth = width;
|
|
mHeight = height;
|
|
|
|
mTextureWidth = texture_width;
|
|
mTextureHeight = texture_height;
|
|
|
|
mCEFLib->setSize(mWidth, mHeight);
|
|
};
|
|
};
|
|
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response");
|
|
message.setValue("name", name);
|
|
message.setValueS32("width", width);
|
|
message.setValueS32("height", height);
|
|
message.setValueS32("texture_width", texture_width);
|
|
message.setValueS32("texture_height", texture_height);
|
|
sendMessage(message);
|
|
|
|
}
|
|
else if (message_name == "set_language_code")
|
|
{
|
|
mHostLanguage = message_in.getValue("language");
|
|
}
|
|
else if (message_name == "load_uri")
|
|
{
|
|
std::string uri = message_in.getValue("uri");
|
|
mCEFLib->navigate(uri);
|
|
}
|
|
else if (message_name == "set_cookie")
|
|
{
|
|
std::string uri = message_in.getValue("uri");
|
|
std::string name = message_in.getValue("name");
|
|
std::string value = message_in.getValue("value");
|
|
std::string domain = message_in.getValue("domain");
|
|
std::string path = message_in.getValue("path");
|
|
bool httponly = message_in.getValueBoolean("httponly");
|
|
bool secure = message_in.getValueBoolean("secure");
|
|
mCEFLib->setCookie(uri, name, value, domain, path, httponly, secure);
|
|
}
|
|
else if (message_name == "mouse_event")
|
|
{
|
|
std::string event = message_in.getValue("event");
|
|
|
|
S32 x = message_in.getValueS32("x");
|
|
S32 y = message_in.getValueS32("y");
|
|
|
|
// only even send left mouse button events to the CEF library
|
|
// (partially prompted by crash in OS X CEF when sending right button events)
|
|
// we catch the right click in viewer and display our own context menu anyway
|
|
S32 button = message_in.getValueS32("button");
|
|
dullahan::EMouseButton btn = dullahan::MB_MOUSE_BUTTON_LEFT;
|
|
|
|
if (event == "down" && button == 0)
|
|
{
|
|
mCEFLib->mouseButton(btn, dullahan::ME_MOUSE_DOWN, x, y);
|
|
mCEFLib->setFocus();
|
|
|
|
std::stringstream str;
|
|
str << "Mouse down at = " << x << ", " << y;
|
|
postDebugMessage(str.str());
|
|
}
|
|
else if (event == "up" && button == 0)
|
|
{
|
|
mCEFLib->mouseButton(btn, dullahan::ME_MOUSE_UP, x, y);
|
|
|
|
std::stringstream str;
|
|
str << "Mouse up at = " << x << ", " << y;
|
|
postDebugMessage(str.str());
|
|
}
|
|
else if (event == "double_click")
|
|
{
|
|
mCEFLib->mouseButton(btn, dullahan::ME_MOUSE_DOUBLE_CLICK, x, y);
|
|
}
|
|
else
|
|
{
|
|
mCEFLib->mouseMove(x, y);
|
|
}
|
|
}
|
|
else if (message_name == "scroll_event")
|
|
{
|
|
// Mouse coordinates for cef to be able to scroll 'containers'
|
|
S32 x = message_in.getValueS32("x");
|
|
S32 y = message_in.getValueS32("y");
|
|
|
|
// Wheel's clicks
|
|
S32 delta_x = message_in.getValueS32("clicks_x");
|
|
S32 delta_y = message_in.getValueS32("clicks_y");
|
|
const int scaling_factor = 40;
|
|
delta_x *= -scaling_factor;
|
|
delta_y *= -scaling_factor;
|
|
|
|
mCEFLib->mouseWheel(x, y, delta_x, delta_y);
|
|
}
|
|
else if (message_name == "text_event")
|
|
{
|
|
std::string event = message_in.getValue("event");
|
|
LLSD native_key_data = message_in.getValueLLSD("native_key_data");
|
|
unicodeInput(event, native_key_data);
|
|
}
|
|
else if (message_name == "key_event")
|
|
{
|
|
#if LL_DARWIN
|
|
std::string event = message_in.getValue("event");
|
|
LLSD native_key_data = message_in.getValueLLSD("native_key_data");
|
|
|
|
dullahan::EKeyEvent key_event = dullahan::KE_KEY_UP;
|
|
if (event == "down")
|
|
{
|
|
key_event = dullahan::KE_KEY_DOWN;
|
|
}
|
|
else if (event == "repeat")
|
|
{
|
|
key_event = dullahan::KE_KEY_REPEAT;
|
|
}
|
|
|
|
keyEvent(key_event, native_key_data);
|
|
|
|
//#elif LL_WINDOWS // <FS:ND/> Windows & Linux
|
|
#else
|
|
std::string event = message_in.getValue("event");
|
|
LLSD native_key_data = message_in.getValueLLSD("native_key_data");
|
|
|
|
// Treat unknown events as key-up for safety.
|
|
dullahan::EKeyEvent key_event = dullahan::KE_KEY_UP;
|
|
if (event == "down")
|
|
{
|
|
key_event = dullahan::KE_KEY_DOWN;
|
|
}
|
|
else if (event == "repeat")
|
|
{
|
|
key_event = dullahan::KE_KEY_REPEAT;
|
|
}
|
|
|
|
keyEvent(key_event, native_key_data);
|
|
#endif
|
|
}
|
|
else if (message_name == "enable_media_plugin_debugging")
|
|
{
|
|
mEnableMediaPluginDebugging = message_in.getValueBoolean("enable");
|
|
}
|
|
if (message_name == "pick_file_response")
|
|
{
|
|
LLSD file_list_llsd = message_in.getValueLLSD("file_list");
|
|
|
|
LLSD::array_const_iterator iter = file_list_llsd.beginArray();
|
|
LLSD::array_const_iterator end = file_list_llsd.endArray();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
mPickedFiles.push_back(((*iter).asString()));
|
|
}
|
|
}
|
|
if (message_name == "auth_response")
|
|
{
|
|
authResponse(message_in);
|
|
}
|
|
if (message_name == "edit_cut")
|
|
{
|
|
mCEFLib->editCut();
|
|
}
|
|
if (message_name == "edit_copy")
|
|
{
|
|
mCEFLib->editCopy();
|
|
}
|
|
if (message_name == "edit_paste")
|
|
{
|
|
mCEFLib->editPaste();
|
|
}
|
|
}
|
|
else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER)
|
|
{
|
|
if (message_name == "set_page_zoom_factor")
|
|
{
|
|
F32 factor = (F32)message_in.getValueReal("factor");
|
|
#if LL_DARWIN
|
|
//temporary fix for SL-10473: issue with displaying checkboxes on Mojave
|
|
factor*=1.001;
|
|
#endif
|
|
mCEFLib->setPageZoom(factor);
|
|
}
|
|
if (message_name == "browse_stop")
|
|
{
|
|
mCEFLib->stop();
|
|
}
|
|
else if (message_name == "browse_reload")
|
|
{
|
|
bool ignore_cache = true;
|
|
mCEFLib->reload(ignore_cache);
|
|
}
|
|
else if (message_name == "browse_forward")
|
|
{
|
|
mCEFLib->goForward();
|
|
}
|
|
else if (message_name == "browse_back")
|
|
{
|
|
mCEFLib->goBack();
|
|
}
|
|
else if (message_name == "cookies_enabled")
|
|
{
|
|
mCookiesEnabled = message_in.getValueBoolean("enable");
|
|
}
|
|
else if (message_name == "clear_cookies")
|
|
{
|
|
mCEFLib->deleteAllCookies();
|
|
}
|
|
else if (message_name == "set_user_agent")
|
|
{
|
|
mUserAgentSubtring = message_in.getValue("user_agent");
|
|
}
|
|
else if (message_name == "show_web_inspector")
|
|
{
|
|
mCEFLib->showDevTools();
|
|
}
|
|
else if (message_name == "plugins_enabled")
|
|
{
|
|
mPluginsEnabled = message_in.getValueBoolean("enable");
|
|
}
|
|
else if (message_name == "javascript_enabled")
|
|
{
|
|
mJavascriptEnabled = message_in.getValueBoolean("enable");
|
|
}
|
|
else if (message_name == "gpu_disabled")
|
|
{
|
|
mDisableGPU = message_in.getValueBoolean("disable");
|
|
}
|
|
}
|
|
else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
|
|
{
|
|
if (message_name == "set_volume")
|
|
{
|
|
F32 volume = (F32)message_in.getValueReal("volume");
|
|
mCurVolume = volume;
|
|
setVolume();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
};
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_data = LLSD::emptyMap())
|
|
{
|
|
#if LL_DARWIN
|
|
U32 event_modifiers = native_key_data["event_modifiers"].asInteger();
|
|
U32 event_keycode = native_key_data["event_keycode"].asInteger();
|
|
U32 event_chars = native_key_data["event_chars"].asInteger();
|
|
U32 event_umodchars = native_key_data["event_umodchars"].asInteger();
|
|
bool event_isrepeat = native_key_data["event_isrepeat"].asBoolean();
|
|
|
|
// adding new code below in unicodeInput means we don't send ascii chars
|
|
// here too or we get double key presses on a mac.
|
|
bool esc_key = (event_umodchars == 27);
|
|
bool tab_key_up = (event_umodchars == 9) && (key_event == dullahan::EKeyEvent::KE_KEY_UP);
|
|
if ((esc_key || ((unsigned char)event_chars < 0x10 || (unsigned char)event_chars >= 0x7f )) && !tab_key_up)
|
|
{
|
|
mCEFLib->nativeKeyboardEventOSX(key_event, event_modifiers,
|
|
event_keycode, event_chars,
|
|
event_umodchars, event_isrepeat);
|
|
}
|
|
#elif LL_WINDOWS
|
|
U32 msg = ll_U32_from_sd(native_key_data["msg"]);
|
|
U32 wparam = ll_U32_from_sd(native_key_data["w_param"]);
|
|
U64 lparam = ll_U32_from_sd(native_key_data["l_param"]);
|
|
|
|
mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam);
|
|
#endif
|
|
|
|
// <FS:ND> Keyboard handling for Linux.
|
|
#if LL_LINUX
|
|
uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger());
|
|
uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger());
|
|
uint32_t native_modifiers = (uint32_t)(native_key_data["cef_modifiers"].asInteger());
|
|
if( native_scan_code == '\n' )
|
|
native_scan_code = '\r';
|
|
mCEFLib->nativeKeyboardEvent(key_event, native_scan_code, native_virtual_key, native_modifiers);
|
|
#endif
|
|
// </FS:ND>
|
|
};
|
|
|
|
void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD::emptyMap())
|
|
{
|
|
#if LL_DARWIN
|
|
// i didn't think this code was needed for macOS but without it, the IME
|
|
// input in japanese (and likely others too) doesn't work correctly.
|
|
// see maint-7654
|
|
U32 event_modifiers = native_key_data["event_modifiers"].asInteger();
|
|
U32 event_keycode = native_key_data["event_keycode"].asInteger();
|
|
U32 event_chars = native_key_data["event_chars"].asInteger();
|
|
U32 event_umodchars = native_key_data["event_umodchars"].asInteger();
|
|
bool event_isrepeat = native_key_data["event_isrepeat"].asBoolean();
|
|
|
|
dullahan::EKeyEvent key_event = dullahan::KE_KEY_UP;
|
|
if (event == "down")
|
|
{
|
|
key_event = dullahan::KE_KEY_DOWN;
|
|
}
|
|
|
|
mCEFLib->nativeKeyboardEventOSX(key_event, event_modifiers,
|
|
event_keycode, event_chars,
|
|
event_umodchars, event_isrepeat);
|
|
#elif LL_WINDOWS
|
|
event = ""; // not needed here but prevents unused var warning as error
|
|
U32 msg = ll_U32_from_sd(native_key_data["msg"]);
|
|
U32 wparam = ll_U32_from_sd(native_key_data["w_param"]);
|
|
U64 lparam = ll_U32_from_sd(native_key_data["l_param"]);
|
|
mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam);
|
|
#endif
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void MediaPluginCEF::checkEditState()
|
|
{
|
|
bool can_cut = mCEFLib->editCanCut();
|
|
bool can_copy = mCEFLib->editCanCopy();
|
|
bool can_paste = mCEFLib->editCanPaste();
|
|
|
|
if ((can_cut != mCanCut) || (can_copy != mCanCopy) || (can_paste != mCanPaste))
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_state");
|
|
|
|
if (can_cut != mCanCut)
|
|
{
|
|
mCanCut = can_cut;
|
|
message.setValueBoolean("cut", can_cut);
|
|
}
|
|
|
|
if (can_copy != mCanCopy)
|
|
{
|
|
mCanCopy = can_copy;
|
|
message.setValueBoolean("copy", can_copy);
|
|
}
|
|
|
|
if (can_paste != mCanPaste)
|
|
{
|
|
mCanPaste = can_paste;
|
|
message.setValueBoolean("paste", can_paste);
|
|
}
|
|
|
|
sendMessage(message);
|
|
}
|
|
}
|
|
|
|
void MediaPluginCEF::setVolume()
|
|
{
|
|
mVolumeCatcher.setVolume(mCurVolume);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
bool MediaPluginCEF::init()
|
|
{
|
|
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
|
|
message.setValue("name", "CEF Plugin");
|
|
sendMessage(message);
|
|
|
|
return true;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func,
|
|
void* host_user_data,
|
|
LLPluginInstance::sendMessageFunction *plugin_send_func,
|
|
void **plugin_user_data)
|
|
{
|
|
MediaPluginCEF* self = new MediaPluginCEF(host_send_func, host_user_data);
|
|
*plugin_send_func = MediaPluginCEF::staticReceiveMessage;
|
|
*plugin_user_data = (void*)self;
|
|
|
|
return 0;
|
|
}
|