mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
729 lines
22 KiB
C++
729 lines
22 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
JUCE is an open source framework subject to commercial or open source
|
|
licensing.
|
|
|
|
By downloading, installing, or using the JUCE framework, or combining the
|
|
JUCE framework with any other source code, object code, content or any other
|
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
|
do not agree to the terms of these agreements, we will not license the JUCE
|
|
framework to you, and you must discontinue the installation or download
|
|
process and cease use of the JUCE framework.
|
|
|
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
|
|
|
Or:
|
|
|
|
You may also use this code under the terms of the AGPLv3:
|
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
#if JUCE_WEB_BROWSER || DOXYGEN
|
|
|
|
bool WebBrowserComponent::pageAboutToLoad ([[maybe_unused]] const String& newURL) { return true; }
|
|
void WebBrowserComponent::pageFinishedLoading ([[maybe_unused]] const String& url) {}
|
|
bool WebBrowserComponent::pageLoadHadNetworkError ([[maybe_unused]] const String& errorInfo) { return true; }
|
|
void WebBrowserComponent::windowCloseRequest() {}
|
|
void WebBrowserComponent::newWindowAttemptingToLoad ([[maybe_unused]] const String& newURL) {}
|
|
|
|
// At least this much code has to be injected as user script, since the native backend functions
|
|
// rely on the presence of the `window.__JUCE__.backend.emitByBackend()` function. The rest can be
|
|
// optionally imported as JS.
|
|
static constexpr const char* lowLevelIntegrationsScript = R"(
|
|
if (
|
|
typeof window.__JUCE__ !== "undefined" &&
|
|
typeof window.__JUCE__.getAndroidUserScripts !== "undefined" &&
|
|
typeof window.inAndroidUserScriptEval === "undefined"
|
|
) {
|
|
window.inAndroidUserScriptEval = true;
|
|
eval(window.__JUCE__.getAndroidUserScripts());
|
|
delete window.inAndroidUserScriptEval;
|
|
}
|
|
|
|
{
|
|
if (typeof window.__JUCE__ === "undefined") {
|
|
console.warn(
|
|
"The 'window.__JUCE__' object is undefined." +
|
|
" Native integration features will not work." +
|
|
" Defining a placeholder 'window.__JUCE__' object."
|
|
);
|
|
|
|
window.__JUCE__ = {
|
|
postMessage: function () {},
|
|
};
|
|
}
|
|
|
|
if (typeof window.__JUCE__.initialisationData === "undefined") {
|
|
window.__JUCE__.initialisationData = {
|
|
__juce__platform: [],
|
|
__juce__functions: [],
|
|
__juce__registeredGlobalEventIds: [],
|
|
__juce__sliders: [],
|
|
__juce__toggles: [],
|
|
__juce__comboBoxes: [],
|
|
};
|
|
}
|
|
|
|
class ListenerList {
|
|
constructor() {
|
|
this.listeners = new Map();
|
|
this.listenerId = 0;
|
|
}
|
|
|
|
addListener(fn) {
|
|
const newListenerId = this.listenerId++;
|
|
this.listeners.set(newListenerId, fn);
|
|
return newListenerId;
|
|
}
|
|
|
|
removeListener(id) {
|
|
if (this.listeners.has(id)) {
|
|
this.listeners.delete(id);
|
|
}
|
|
}
|
|
|
|
callListeners(payload) {
|
|
for (const [, value] of this.listeners) {
|
|
value(payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
class EventListenerList {
|
|
constructor() {
|
|
this.eventListeners = new Map();
|
|
}
|
|
|
|
addEventListener(eventId, fn) {
|
|
if (!this.eventListeners.has(eventId))
|
|
this.eventListeners.set(eventId, new ListenerList());
|
|
|
|
const id = this.eventListeners.get(eventId).addListener(fn);
|
|
|
|
return [eventId, id];
|
|
}
|
|
|
|
removeEventListener([eventId, id]) {
|
|
if (this.eventListeners.has(eventId)) {
|
|
this.eventListeners.get(eventId).removeListener(id);
|
|
}
|
|
}
|
|
|
|
emitEvent(eventId, object) {
|
|
if (this.eventListeners.has(eventId))
|
|
this.eventListeners.get(eventId).callListeners(object);
|
|
}
|
|
}
|
|
|
|
class Backend {
|
|
constructor() {
|
|
this.listeners = new EventListenerList();
|
|
}
|
|
|
|
addEventListener(eventId, fn) {
|
|
return this.listeners.addEventListener(eventId, fn);
|
|
}
|
|
|
|
removeEventListener([eventId, id]) {
|
|
this.listeners.removeEventListener([eventId, id]);
|
|
}
|
|
|
|
emitEvent(eventId, object) {
|
|
window.__JUCE__.postMessage(
|
|
JSON.stringify({ eventId: eventId, payload: object })
|
|
);
|
|
}
|
|
|
|
emitByBackend(eventId, object) {
|
|
this.listeners.emitEvent(eventId, JSON.parse(object));
|
|
}
|
|
}
|
|
|
|
if (typeof window.__JUCE__.backend === "undefined")
|
|
window.__JUCE__.backend = new Backend();
|
|
}
|
|
)";
|
|
|
|
static void evaluationHandler (WebBrowserComponent::EvaluationResult r)
|
|
{
|
|
if (r.getResult() == nullptr)
|
|
{
|
|
// The unsupported return type means a successful Javascript evaluation that yielded a
|
|
// result that cannot be translated and returned to native code such as a Promise.
|
|
jassert (r.getError()->type == WebBrowserComponent::EvaluationResult::Error::Type::unsupportedReturnType);
|
|
DBG (r.getError()->message);
|
|
}
|
|
}
|
|
|
|
static StringArray getUserScriptsForInitialisationData (const std::vector<std::pair<String, var>>& data)
|
|
{
|
|
std::map<String, StringArray> sortedData;
|
|
|
|
for (const auto& [key, value] : data)
|
|
sortedData[key].add (JSON::toString (value));
|
|
|
|
StringArray result;
|
|
|
|
for (const auto& [key, values] : sortedData)
|
|
result.add ("window.__JUCE__.initialisationData." + key + " = ["
|
|
+ values.joinIntoString (",") + "];");
|
|
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
static auto getCommaSeparatedList (const T& mapOfStringable)
|
|
{
|
|
StringArray keys;
|
|
|
|
for (auto it : mapOfStringable)
|
|
keys.add (it.first.toString().quoted());
|
|
|
|
return keys.joinIntoString (",");
|
|
}
|
|
|
|
struct NativeEvents
|
|
{
|
|
NativeEvents() = delete;
|
|
|
|
struct NativeEvent
|
|
{
|
|
String eventId;
|
|
var payload;
|
|
|
|
static constexpr std::optional<int> marshallingVersion = std::nullopt;
|
|
|
|
template <typename Archive, typename Item>
|
|
static void serialise (Archive& archive, Item& item)
|
|
{
|
|
archive (named ("eventId", item.eventId),
|
|
named ("payload", item.payload));
|
|
}
|
|
};
|
|
|
|
struct Invoke
|
|
{
|
|
String name;
|
|
var params;
|
|
int64 resultId;
|
|
|
|
static constexpr std::optional<int> marshallingVersion = std::nullopt;
|
|
|
|
template <typename Archive, typename Item>
|
|
static void serialise (Archive& archive, Item& item)
|
|
{
|
|
archive (named ("name", item.name),
|
|
named ("params", item.params),
|
|
named ("resultId", item.resultId));
|
|
}
|
|
|
|
static inline const Identifier eventId { "__juce__invoke" };
|
|
static inline const Identifier completeId { "__juce__complete" };
|
|
};
|
|
};
|
|
|
|
class NativeFunctionsProvider : public OptionsBuilder<WebBrowserComponent::Options>,
|
|
private AsyncUpdater
|
|
{
|
|
public:
|
|
explicit NativeFunctionsProvider (WebBrowserComponent& ownerIn)
|
|
: owner (ownerIn)
|
|
{
|
|
}
|
|
|
|
WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override
|
|
{
|
|
nativeFunctions = initialOptions.getNativeFunctions();
|
|
|
|
if (nativeFunctions.empty())
|
|
return initialOptions;
|
|
|
|
auto options = initialOptions.withNativeIntegrationEnabled()
|
|
.withEventListener (NativeEvents::Invoke::eventId,
|
|
[this] (const auto& object)
|
|
{
|
|
handleNativeFunctionCall (object);
|
|
});
|
|
|
|
for (auto it : nativeFunctions)
|
|
options = options.withInitialisationData ("__juce__functions", it.first.toString());
|
|
|
|
return options;
|
|
}
|
|
|
|
private:
|
|
void handleAsyncUpdate() override
|
|
{
|
|
const auto popFront = [&] (auto&& callback)
|
|
{
|
|
functionCompletionsMutex.lock();
|
|
ErasedScopeGuard unlocker { [&] { functionCompletionsMutex.unlock(); } };
|
|
|
|
if (! functionCompletions.empty())
|
|
{
|
|
auto completion = functionCompletions.front();
|
|
functionCompletions.pop_front();
|
|
unlocker.reset();
|
|
callback (completion);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
while (popFront ([&] (const auto& c) { emitCompletionEvent (c.first, c.second); }))
|
|
;
|
|
}
|
|
|
|
void handleNativeFunctionCall (const var& object)
|
|
{
|
|
const auto invocation = FromVar::convert<NativeEvents::Invoke> (object);
|
|
|
|
if (! invocation.has_value())
|
|
{
|
|
jassertfalse;
|
|
return;
|
|
}
|
|
|
|
auto it = nativeFunctions.find (invocation->name);
|
|
|
|
if (it == nativeFunctions.end())
|
|
{
|
|
jassertfalse;
|
|
return;
|
|
}
|
|
|
|
jassert (invocation->params.isArray());
|
|
|
|
it->second (*invocation->params.getArray(),
|
|
[this, resultId = invocation->resultId] (auto result)
|
|
{
|
|
completeNativeFunctionCall (resultId, result);
|
|
});
|
|
}
|
|
|
|
void completeNativeFunctionCall (int64 resultId, const var& object)
|
|
{
|
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
|
{
|
|
emitCompletionEvent (resultId, object);
|
|
}
|
|
else
|
|
{
|
|
const std::lock_guard lock { functionCompletionsMutex };
|
|
functionCompletions.push_back (std::make_pair (resultId, object));
|
|
triggerAsyncUpdate();
|
|
}
|
|
}
|
|
|
|
void emitCompletionEvent (int64 resultId, const var& object)
|
|
{
|
|
DynamicObject::Ptr eventObject { new DynamicObject() };
|
|
eventObject->setProperty ("promiseId", resultId);
|
|
eventObject->setProperty ("result", object);
|
|
|
|
jassert (owner.isVisible());
|
|
owner.emitEventIfBrowserIsVisible (NativeEvents::Invoke::completeId, eventObject.get());
|
|
}
|
|
|
|
WebBrowserComponent& owner;
|
|
std::map<Identifier, WebBrowserComponent::NativeFunction> nativeFunctions;
|
|
std::deque<std::pair<int64, var>> functionCompletions;
|
|
std::mutex functionCompletionsMutex;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (NativeFunctionsProvider)
|
|
JUCE_DECLARE_NON_MOVEABLE (NativeFunctionsProvider)
|
|
};
|
|
|
|
class NativeEventListeners
|
|
{
|
|
public:
|
|
void addListener (const Identifier& eventId, std::function<void (const var&)> handler)
|
|
{
|
|
listeners.push_back (std::move (handler));
|
|
listenerMap[eventId].add (&listeners.back());
|
|
}
|
|
|
|
void emit (const Identifier& eventId, const var& object)
|
|
{
|
|
if (const auto& it = listenerMap.find (eventId); it != listenerMap.end())
|
|
it->second.call ([&object] (auto& l) { l (object); });
|
|
}
|
|
|
|
private:
|
|
std::list<WebBrowserComponent::NativeEventListener> listeners;
|
|
|
|
std::map<Identifier, ListenerList<WebBrowserComponent::NativeEventListener>> listenerMap;
|
|
};
|
|
|
|
class WebBrowserComponent::Impl
|
|
{
|
|
public:
|
|
Impl (WebBrowserComponent& ownerIn, const Options& optionsIn)
|
|
: owner (ownerIn),
|
|
options ([&]
|
|
{
|
|
makeFunctionsProviderIfNecessary (nativeFunctionsProvider, *this, optionsIn);
|
|
|
|
if (nativeFunctionsProvider.has_value())
|
|
return getOptions (optionsIn).withOptionsFrom (*nativeFunctionsProvider);
|
|
|
|
return getOptions (optionsIn);
|
|
}())
|
|
{
|
|
auto userScripts = options.getUserScripts();
|
|
|
|
// Adding user scripts in reverse order of depending on each other. The most depended on
|
|
// comes last.
|
|
for (const auto& eventListener : options.getEventListeners())
|
|
{
|
|
userScripts.insert (0,
|
|
"window.__JUCE__.initialisationData.__juce__registeredGlobalEventIds = ["
|
|
+ getCommaSeparatedList (options.getEventListeners()) + "];");
|
|
|
|
addPermanentEventListener (eventListener.first, eventListener.second);
|
|
}
|
|
|
|
for (const auto& script : getUserScriptsForInitialisationData (options.getInitialisationData()))
|
|
userScripts.insert (0, script);
|
|
|
|
userScripts.insert (0, lowLevelIntegrationsScript);
|
|
|
|
platform = createAndInitPlatformDependentPart (*this, options, userScripts);
|
|
}
|
|
|
|
void emitEvent (const Identifier& eventId, const var& object)
|
|
{
|
|
// The object parameter is serialised into a string and used as a parameter to a Javascript
|
|
// function call. During this JS parameter substitution, control character escape sequences
|
|
// would be interpreted as the control characters themselves. So we need to escape anything
|
|
// that was escaped.
|
|
//
|
|
// We also need to escape the ' character since we use this to delimit the parameter string
|
|
// to emitByBackend.
|
|
const auto objectAsString = JSON::toString (object, true);
|
|
const auto escaped = objectAsString.replace ("\\", "\\\\").replace ("'", "\\'");
|
|
|
|
evaluateJavascript ("window.__JUCE__.backend.emitByBackend(" + eventId.toString().quoted() + ", "
|
|
+ escaped.quoted ('\'')
|
|
+ ");", evaluationHandler);
|
|
}
|
|
|
|
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
|
|
{
|
|
platform->goToURL (url, headers, postData);
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
platform->stop();
|
|
}
|
|
|
|
void goBack()
|
|
{
|
|
platform->goBack();
|
|
|
|
owner.lastURL.clear();
|
|
owner.blankPageShown = false;
|
|
}
|
|
|
|
void goForward()
|
|
{
|
|
owner.lastURL.clear();
|
|
|
|
platform->goForward();
|
|
}
|
|
|
|
void refresh()
|
|
{
|
|
platform->refresh();
|
|
}
|
|
|
|
void evaluateJavascript (const String& script, EvaluationCallback callback)
|
|
{
|
|
platform->evaluateJavascript (script, std::move (callback));
|
|
}
|
|
|
|
void setSize (int width, int height)
|
|
{
|
|
platform->setWebViewSize (width, height);
|
|
}
|
|
|
|
void checkWindowAssociation()
|
|
{
|
|
platform->checkWindowAssociation();
|
|
}
|
|
|
|
void fallbackPaint (Graphics& webBrowserComponentContext)
|
|
{
|
|
platform->fallbackPaint (webBrowserComponentContext);
|
|
}
|
|
|
|
void focusGainedWithDirection (FocusChangeType type, FocusChangeDirection dir)
|
|
{
|
|
platform->focusGainedWithDirection (type, dir);
|
|
}
|
|
|
|
struct Platform;
|
|
|
|
private:
|
|
class PlatformInterface
|
|
{
|
|
public:
|
|
PlatformInterface() = default;
|
|
virtual ~PlatformInterface() = default;
|
|
|
|
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
|
|
virtual void goBack() = 0;
|
|
virtual void goForward() = 0;
|
|
virtual void stop() = 0;
|
|
virtual void refresh() = 0;
|
|
virtual void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback) = 0;
|
|
virtual void setWebViewSize (int, int) = 0;
|
|
virtual void checkWindowAssociation() = 0;
|
|
|
|
virtual void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) {}
|
|
virtual void fallbackPaint (Graphics&) {}
|
|
};
|
|
|
|
static void makeFunctionsProviderIfNecessary (std::optional<NativeFunctionsProvider>& provider,
|
|
WebBrowserComponent::Impl& impl,
|
|
const WebBrowserComponent::Options& options)
|
|
{
|
|
if (! options.getNativeFunctions().empty())
|
|
provider.emplace (impl.owner);
|
|
}
|
|
|
|
static Options getOptions (const Options& optionsIn)
|
|
{
|
|
const auto os = SystemStats::getOperatingSystemType();
|
|
|
|
const auto platformString = [&]
|
|
{
|
|
using OsType = SystemStats::OperatingSystemType;
|
|
|
|
if ((os & OsType::MacOSX) != 0)
|
|
return "macos";
|
|
|
|
if ((os & OsType::iOS) != 0)
|
|
return "ios";
|
|
|
|
if ((os & OsType::Windows) != 0)
|
|
return "windows";
|
|
|
|
if ((os & OsType::Android) != 0)
|
|
return "android";
|
|
|
|
if ((os & OsType::Linux) != 0)
|
|
return "linux";
|
|
|
|
return "";
|
|
}();
|
|
|
|
return optionsIn.withInitialisationData ("__juce__platform", platformString);
|
|
}
|
|
|
|
void addPermanentEventListener (const Identifier& eventId, NativeEventListener listener)
|
|
{
|
|
nativeEventListeners.addListener (eventId, std::move (listener));
|
|
}
|
|
|
|
std::optional<Resource> handleResourceRequest (const String& url)
|
|
{
|
|
if (resourceProvider != nullptr)
|
|
return resourceProvider (url);
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
void handleNativeEvent (const var& message)
|
|
{
|
|
const auto event = FromVar::convert<NativeEvents::NativeEvent> (message);
|
|
|
|
if (! event.has_value())
|
|
{
|
|
jassertfalse;
|
|
return;
|
|
}
|
|
|
|
nativeEventListeners.emit (event->eventId, event->payload);
|
|
}
|
|
|
|
static std::unique_ptr<PlatformInterface> createAndInitPlatformDependentPart (WebBrowserComponent::Impl&,
|
|
const Options&,
|
|
const StringArray&);
|
|
|
|
WebBrowserComponent& owner;
|
|
std::optional<NativeFunctionsProvider> nativeFunctionsProvider;
|
|
Options options;
|
|
ResourceProvider resourceProvider { options.getResourceProvider() };
|
|
NativeEventListeners nativeEventListeners;
|
|
|
|
std::unique_ptr<PlatformInterface> platform;
|
|
};
|
|
|
|
//==============================================================================
|
|
WebBrowserComponent::WebBrowserComponent (const Options& options)
|
|
: impl (std::make_unique<Impl> (*this, options)),
|
|
unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
|
|
{
|
|
setOpaque (true);
|
|
|
|
#if JUCE_LINUX
|
|
ignoreUnused (blankPageShown);
|
|
ignoreUnused (unloadPageWhenHidden);
|
|
#endif
|
|
|
|
for (const auto& l : options.getLifetimeListeners())
|
|
lifetimeListeners.add (l);
|
|
|
|
lifetimeListeners.call (&WebViewLifetimeListener::webViewConstructed, this);
|
|
}
|
|
|
|
WebBrowserComponent::~WebBrowserComponent()
|
|
{
|
|
lifetimeListeners.call (&WebViewLifetimeListener::webViewDestructed, this);
|
|
}
|
|
|
|
void WebBrowserComponent::emitEventIfBrowserIsVisible (const Identifier& eventId, const var& object)
|
|
{
|
|
if (isVisible())
|
|
impl->emitEvent (eventId, object);
|
|
}
|
|
|
|
const String& WebBrowserComponent::getResourceProviderRoot()
|
|
{
|
|
const auto os = SystemStats::getOperatingSystemType();
|
|
|
|
using OsType = SystemStats::OperatingSystemType;
|
|
|
|
if ( (os & OsType::MacOSX) != 0
|
|
|| (os & OsType::iOS) != 0
|
|
|| (os & OsType::Linux) != 0)
|
|
{
|
|
static String backendUrl { "juce://juce.backend/" };
|
|
return backendUrl;
|
|
}
|
|
|
|
if ( (os & OsType::Windows) != 0
|
|
|| (os & OsType::Android) != 0)
|
|
{
|
|
static String backendUrl { "https://juce.backend/" };
|
|
return backendUrl;
|
|
}
|
|
|
|
static String emptyUrl { "" };
|
|
return emptyUrl;
|
|
}
|
|
|
|
void WebBrowserComponent::goToURL (const String& url,
|
|
const StringArray* headers,
|
|
const MemoryBlock* postData)
|
|
{
|
|
lastURL = url;
|
|
|
|
if (headers != nullptr)
|
|
lastHeaders = *headers;
|
|
else
|
|
lastHeaders.clear();
|
|
|
|
if (postData != nullptr)
|
|
lastPostData = *postData;
|
|
else
|
|
lastPostData.reset();
|
|
|
|
impl->goToURL (url, headers, postData);
|
|
|
|
blankPageShown = false;
|
|
}
|
|
|
|
void WebBrowserComponent::stop()
|
|
{
|
|
impl->stop();
|
|
}
|
|
|
|
void WebBrowserComponent::goBack()
|
|
{
|
|
impl->goBack();
|
|
lastURL.clear();
|
|
blankPageShown = false;
|
|
}
|
|
|
|
void WebBrowserComponent::goForward()
|
|
{
|
|
lastURL.clear();
|
|
impl->goForward();
|
|
}
|
|
|
|
void WebBrowserComponent::refresh()
|
|
{
|
|
impl->refresh();
|
|
}
|
|
|
|
void WebBrowserComponent::paint (Graphics& g)
|
|
{
|
|
impl->fallbackPaint (g);
|
|
}
|
|
|
|
void WebBrowserComponent::parentHierarchyChanged()
|
|
{
|
|
impl->checkWindowAssociation();
|
|
}
|
|
|
|
void WebBrowserComponent::visibilityChanged()
|
|
{
|
|
impl->checkWindowAssociation();
|
|
}
|
|
|
|
void WebBrowserComponent::resized()
|
|
{
|
|
impl->setSize (getWidth(), getHeight());
|
|
}
|
|
|
|
void WebBrowserComponent::reloadLastURL()
|
|
{
|
|
const auto ptrOrNullIfEmpty = [] (auto& value)
|
|
{
|
|
return ! value.isEmpty() ? &value : nullptr;
|
|
};
|
|
|
|
if (lastURL.isNotEmpty())
|
|
{
|
|
goToURL (lastURL, ptrOrNullIfEmpty (lastHeaders), ptrOrNullIfEmpty (lastPostData));
|
|
lastURL.clear();
|
|
}
|
|
}
|
|
|
|
void WebBrowserComponent::evaluateJavascript (const String& script, EvaluationCallback callback)
|
|
{
|
|
impl->evaluateJavascript (script, std::move (callback));
|
|
}
|
|
|
|
void WebBrowserComponent::focusGainedWithDirection (FocusChangeType type,
|
|
FocusChangeDirection direction)
|
|
{
|
|
impl->focusGainedWithDirection (type, direction);
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace juce
|