mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cdd41c833 |
@@ -105,6 +105,9 @@ endif()
|
|||||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||||
|
|
||||||
|
# Add webview module
|
||||||
|
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/core/webview)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||||
@@ -194,7 +197,7 @@ elseif(APPLE)
|
|||||||
include(cmake/macos.cmake)
|
include(cmake/macos.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
target_link_libraries(${PROJECT} PRIVATE ${LIBS} webview)
|
||||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||||
|
|
||||||
# deploy artifacts required to run the application to the debug build folder
|
# deploy artifacts required to run the application to the debug build folder
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
#include <QtPlugin>
|
||||||
|
#include "core/webview/plugin.h"
|
||||||
|
|
||||||
|
Q_IMPORT_PLUGIN(WebViewPlugin)
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/controllers/pageController.h"
|
#include "ui/controllers/pageController.h"
|
||||||
@@ -99,6 +104,14 @@ void AmneziaApplication::init()
|
|||||||
{
|
{
|
||||||
m_engine = new QQmlApplicationEngine;
|
m_engine = new QQmlApplicationEngine;
|
||||||
|
|
||||||
|
// Register AmneziaWebView plugin explicitly
|
||||||
|
QObject *pluginInstance = qt_static_plugin_WebViewPlugin().instance();
|
||||||
|
QQmlExtensionPlugin *p = qobject_cast<QQmlExtensionPlugin*>(pluginInstance);
|
||||||
|
if (p) {
|
||||||
|
p->registerTypes("AmneziaWebView");
|
||||||
|
p->initializeEngine(m_engine, "AmneziaWebView");
|
||||||
|
}
|
||||||
|
|
||||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||||
@@ -130,6 +143,7 @@ void AmneziaApplication::init()
|
|||||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||||
|
|
||||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||||
|
m_engine->addImportPath("qrc:/");
|
||||||
|
|
||||||
if (m_parser.isSet(m_optImport)) {
|
if (m_parser.isSet(m_optImport)) {
|
||||||
const QString data = m_parser.value(m_optImport);
|
const QString data = m_parser.value(m_optImport);
|
||||||
|
|||||||
@@ -0,0 +1,609 @@
|
|||||||
|
package org.amnezia.vpn;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.webkit.*;
|
||||||
|
import android.net.http.SslError;
|
||||||
|
import android.os.Message;
|
||||||
|
import org.qtproject.qt.android.WebViewControllerEx;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
public class WebViewController
|
||||||
|
{
|
||||||
|
private interface RequestFinished {
|
||||||
|
void onRequestCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String baseUrl = "";
|
||||||
|
private static final String INTERNAL_BASE_URL = "file:///";
|
||||||
|
private static final long GEOMETRY_STABLE_INTERVAL = 150; //ms wait geometry settle
|
||||||
|
|
||||||
|
private final Activity m_activity;
|
||||||
|
private final long m_id;
|
||||||
|
private WebView m_webView = null;
|
||||||
|
private ViewGroup m_layout = null;
|
||||||
|
private boolean m_loading = true;
|
||||||
|
private long mLastGeometryChange = 0L;
|
||||||
|
|
||||||
|
private float m_displayDensity = (float) 1.0;
|
||||||
|
|
||||||
|
public native void urlChanged(long viewId, String url);
|
||||||
|
public native byte[] dataForUrl(long viewId, String url, StringBuilder mimeType, StringBuilder encoding);
|
||||||
|
public native boolean canHandleUrl(long viewId, String url);
|
||||||
|
private final Handler m_handler;
|
||||||
|
|
||||||
|
private native void pageFinished(long id, String url);
|
||||||
|
private native void pageStarted(long id, String url);
|
||||||
|
private static final String TAG = WebViewController.class.getSimpleName();
|
||||||
|
|
||||||
|
private class AndroidWebChromeClient extends WebChromeClient {
|
||||||
|
@Override
|
||||||
|
public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg)
|
||||||
|
{
|
||||||
|
// Prevent opening new windows/tabs - load URLs in the same WebView instead
|
||||||
|
// This handles links with target="_blank" or window.open()
|
||||||
|
// Return false to prevent creating new windows - URLs will be handled by shouldOverrideUrlLoading
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebViewController(final Activity activity, final long id) {
|
||||||
|
m_activity = activity;
|
||||||
|
m_id = id;
|
||||||
|
|
||||||
|
ViewGroup root = (ViewGroup)(((ViewGroup)(m_activity.findViewById(android.R.id.content))).getChildAt(0));
|
||||||
|
if (root != null) {
|
||||||
|
m_layout = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_displayDensity = m_activity.getResources().getDisplayMetrics().density;
|
||||||
|
|
||||||
|
m_handler = new Handler(Looper.getMainLooper());
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
m_webView = new WebView(m_activity);
|
||||||
|
m_webView.setFocusable(true);
|
||||||
|
|
||||||
|
m_webView.setFocusableInTouchMode(true);
|
||||||
|
m_webView.getSettings().setJavaScriptEnabled(true);
|
||||||
|
m_webView.getSettings().setAllowFileAccess(true);
|
||||||
|
m_webView.getSettings().setAllowFileAccessFromFileURLs(true);
|
||||||
|
m_webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
|
||||||
|
m_webView.getSettings().setAllowContentAccess(true);
|
||||||
|
m_webView.getSettings().setBuiltInZoomControls(true);
|
||||||
|
m_webView.getSettings().setDisplayZoomControls(false);
|
||||||
|
m_webView.getSettings().setLoadWithOverviewMode(true);
|
||||||
|
|
||||||
|
m_webView.getSettings().setSupportMultipleWindows(false); // Prevent opening new windows
|
||||||
|
m_webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||||
|
|
||||||
|
m_webView.getSettings().setUseWideViewPort(true);
|
||||||
|
m_webView.getSettings().setLoadWithOverviewMode(true);
|
||||||
|
m_webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
||||||
|
|
||||||
|
m_webView.getSettings().setSupportZoom(true);
|
||||||
|
m_webView.getSettings().setBuiltInZoomControls(true);
|
||||||
|
m_webView.getSettings().setDisplayZoomControls(false);
|
||||||
|
|
||||||
|
m_webView.setInitialScale(0);
|
||||||
|
m_webView.setVisibility(android.view.View.INVISIBLE);
|
||||||
|
|
||||||
|
// Ensure WebView can receive and handle touch events for link clicks
|
||||||
|
m_webView.setClickable(true);
|
||||||
|
m_webView.setLongClickable(true);
|
||||||
|
m_webView.setHapticFeedbackEnabled(false);
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
m_webView.setElevation(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_webView.setWebViewClient(buildWebViewClient());
|
||||||
|
m_webView.setWebChromeClient(buildWebChromeClient());
|
||||||
|
|
||||||
|
// Ensure IME appears on tap when focusing editable content
|
||||||
|
m_webView.setOnTouchListener(new android.view.View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(android.view.View v, MotionEvent event) {
|
||||||
|
// Let WebView handle touch events normally for link clicks
|
||||||
|
// Only request focus on ACTION_UP to allow IME to appear for input fields
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
v.requestFocus();
|
||||||
|
// Do not show IME if app temporarily suppresses it
|
||||||
|
try {
|
||||||
|
android.view.inputmethod.InputMethodManager imm = (android.view.inputmethod.InputMethodManager) v.getContext().getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (imm != null) {
|
||||||
|
imm.showSoftInput(v, 0);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignore) {}
|
||||||
|
}
|
||||||
|
// Return false to let WebView handle the touch event (for link clicks, etc.)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebViewClient buildWebViewClient() {
|
||||||
|
return new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
handler.proceed();
|
||||||
|
Log.e(TAG, "SSL certificate error");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted (WebView view, String url, Bitmap favicon) {
|
||||||
|
m_loading = true;
|
||||||
|
|
||||||
|
String dataUrl = updateUrl(url);
|
||||||
|
urlChanged(m_id, dataUrl);
|
||||||
|
pageStarted(m_id, dataUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
m_loading = false;
|
||||||
|
|
||||||
|
String dataUrl = updateUrl(url);
|
||||||
|
pageFinished(m_id, dataUrl);
|
||||||
|
urlChanged(m_id, dataUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
Log.d(TAG, "shouldOverrideUrlLoading (deprecated): " + url);
|
||||||
|
if (url == null || url.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
urlChanged(m_id, url);
|
||||||
|
|
||||||
|
// Always load URLs within WebView, don't open in external browser
|
||||||
|
// Explicitly load the URL in the WebView and return true to indicate we handled it
|
||||||
|
view.loadUrl(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, android.webkit.WebResourceRequest request) {
|
||||||
|
String url = request.getUrl().toString();
|
||||||
|
Log.d(TAG, "shouldOverrideUrlLoading (new): " + url + ", isMainFrame: " + request.isForMainFrame() + ", method: " + request.getMethod());
|
||||||
|
|
||||||
|
if (url == null || url.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
urlChanged(m_id, url);
|
||||||
|
|
||||||
|
// Always load URLs within WebView, don't open in external browser
|
||||||
|
// Handle main frame navigation (link clicks, form submissions, etc.)
|
||||||
|
// For sub-resources (images, CSS, JS), let WebView handle normally by returning false
|
||||||
|
if (request.isForMainFrame()) {
|
||||||
|
view.loadUrl(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// For sub-resources, let WebView handle normally
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||||
|
|
||||||
|
if (url.startsWith("data:") || !canHandleUrl(m_id, url)) {
|
||||||
|
return super.shouldInterceptRequest(view, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder mimeType = new StringBuilder();
|
||||||
|
StringBuilder encoding = new StringBuilder();
|
||||||
|
byte[] data = dataForUrl(m_id, url, mimeType, encoding);
|
||||||
|
|
||||||
|
boolean isDataInvalid = (data == null) || (data.length == 0);
|
||||||
|
if (isDataInvalid) {
|
||||||
|
Log.w(TAG, String.format("Invalid data received for url: %s", url));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ((mimeType.length() == 0) || mimeType.toString().isEmpty()) {
|
||||||
|
Log.w(TAG, String.format("Invalid mimeType received for url: %s", url));
|
||||||
|
}
|
||||||
|
if ((encoding.length() == 0) || encoding.toString().isEmpty()) {
|
||||||
|
Log.w(TAG, String.format("Invalid encoding received for url: %s", url));
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
|
||||||
|
return new WebResourceResponse(mimeType.toString(), encoding.toString(), dataStream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebChromeClient buildWebChromeClient() {
|
||||||
|
return new AndroidWebChromeClient() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(WebView view, int newProgress) {
|
||||||
|
super.onProgressChanged(view, newProgress);
|
||||||
|
if (newProgress == 100) {
|
||||||
|
m_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
||||||
|
callback.invoke(origin, true, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String updateUrl(String url) {
|
||||||
|
if (!url.startsWith(INTERNAL_BASE_URL)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dataUrl = url;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dataUrl = URLDecoder.decode(url.substring(INTERNAL_BASE_URL.length()), "UTF-8");
|
||||||
|
if ((dataUrl.length() == 1) && dataUrl.endsWith("#")) {
|
||||||
|
dataUrl = baseUrl;
|
||||||
|
} else {
|
||||||
|
dataUrl = baseUrl + dataUrl;
|
||||||
|
}
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
Log.e(TAG, e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(() -> {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.setVisibility(android.view.View.INVISIBLE);
|
||||||
|
m_layout.removeView(m_webView);
|
||||||
|
m_webView.stopLoading();
|
||||||
|
m_webView.setWebViewClient(new WebViewClient());
|
||||||
|
m_webView.setWebChromeClient(null);
|
||||||
|
m_webView = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGeometry(final int x, final int y, final int width, final int height) {
|
||||||
|
|
||||||
|
|
||||||
|
Log.d(TAG, String.format(
|
||||||
|
"setGeometry called: x=%d, y=%d, width=%d, height=%d",
|
||||||
|
x, y, width, height));
|
||||||
|
|
||||||
|
|
||||||
|
if (m_handler == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_handler.post(() -> {
|
||||||
|
if (m_webView == null || m_layout == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_layout.indexOfChild(m_webView) < 0) {
|
||||||
|
m_layout.addView(m_webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
float scale = m_activity.getResources().getDisplayMetrics().density;
|
||||||
|
|
||||||
|
int pxX = Math.round(x * scale);
|
||||||
|
int pxY = Math.round(y * scale);
|
||||||
|
int pxW = Math.round(width * scale);
|
||||||
|
int pxH = Math.round(height * scale);
|
||||||
|
|
||||||
|
Log.d(TAG, String.format(
|
||||||
|
"density=%.2f qml: x=%d y=%d w=%d h=%d -> px: x=%d y=%d w=%d h=%d",
|
||||||
|
scale, x, y, width, height, pxX, pxY, pxW, pxH));
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams params =
|
||||||
|
WebViewControllerEx.createQtLayoutParams(
|
||||||
|
pxW,
|
||||||
|
pxH,
|
||||||
|
pxX,
|
||||||
|
pxY
|
||||||
|
);
|
||||||
|
|
||||||
|
m_webView.setLayoutParams(params);
|
||||||
|
m_webView.setInitialScale(0);
|
||||||
|
|
||||||
|
Log.d(TAG, String.format(
|
||||||
|
"WebView positioned (QtLayout) at px: x=%d, y=%d, w=%d, h=%d",
|
||||||
|
pxX, pxY, pxW, pxH));
|
||||||
|
|
||||||
|
m_layout.requestLayout();
|
||||||
|
m_webView.requestLayout();
|
||||||
|
|
||||||
|
mLastGeometryChange = SystemClock.uptimeMillis();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_webView.getVisibility() != android.view.View.VISIBLE) {
|
||||||
|
m_webView.setVisibility(android.view.View.VISIBLE);
|
||||||
|
}
|
||||||
|
// Don't bring WebView to front - let QML elements render on top
|
||||||
|
// Set low elevation so QML elements can appear above WebView
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
m_webView.setElevation(0f);
|
||||||
|
}
|
||||||
|
m_webView.requestLayout();
|
||||||
|
m_layout.requestLayout();
|
||||||
|
m_layout.postInvalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hide() {
|
||||||
|
hideWebView();
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideWebView() {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_handler.post(() -> {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_webView.getVisibility() == android.view.View.VISIBLE) {
|
||||||
|
m_webView.setVisibility(android.view.View.INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadUrl(String url) {
|
||||||
|
final String newUrl;
|
||||||
|
if ((baseUrl.length() > 0) && url.startsWith(baseUrl)) {
|
||||||
|
newUrl = INTERNAL_BASE_URL + url.substring(baseUrl.length());
|
||||||
|
} else {
|
||||||
|
newUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.loadUrl(newUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadDataWithBaseURL(final String url, final String html, final String mime, final String encoding) {
|
||||||
|
baseUrl = url.trim();
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_webView.loadUrl("about:blank");
|
||||||
|
m_webView.loadDataWithBaseURL(INTERNAL_BASE_URL, html, mime, encoding, null);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void evaluateJavaScript(final String script) {
|
||||||
|
if (m_handler == null || script == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.evaluateJavascript(script, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoBack() {
|
||||||
|
final WebView view = m_webView;
|
||||||
|
if (view == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean[] ret = new boolean[1];
|
||||||
|
ret[0] = false;
|
||||||
|
boolean can = false;
|
||||||
|
|
||||||
|
final Semaphore semaphore = new Semaphore(0);
|
||||||
|
if (m_activity != null) {
|
||||||
|
m_activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ret[0] = view.canGoBack();
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
semaphore.acquire(1);
|
||||||
|
can = ret[0];
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, e.toString());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void goBack() {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.goBack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canGoForward() {
|
||||||
|
final WebView view = m_webView;
|
||||||
|
if (view == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean[] ret = new boolean[1];
|
||||||
|
ret[0] = false;
|
||||||
|
boolean can = false;
|
||||||
|
|
||||||
|
final Semaphore semaphore = new Semaphore(0);
|
||||||
|
if (m_activity != null) {
|
||||||
|
m_activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ret[0] = view.canGoForward();
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
semaphore.acquire(1);
|
||||||
|
can = ret[0];
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, e.toString());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void goForward() {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.goForward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundColor(final int color) {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.setBackgroundColor(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextZoom(final int percent) {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int clamped = Math.max(25, Math.min(500, percent));
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (m_webView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_webView.getSettings().setTextZoom(clamped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private int convertToDp(int input) {
|
||||||
|
return (int)(input / m_displayDensity + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultFontSize(int size) {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int fontSize = size;
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
m_webView.getSettings().setDefaultFontSize(convertToDp(fontSize));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStandardFontFamily(String family) {
|
||||||
|
if (m_handler == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String fontFamily = family;
|
||||||
|
m_handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
m_webView.getSettings().setStandardFontFamily(fontFamily);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.qtproject.qt.android;
|
||||||
|
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public class WebViewControllerEx {
|
||||||
|
|
||||||
|
public static ViewGroup.LayoutParams createQtLayoutParams(int width, int height, int x, int y) {
|
||||||
|
return new QtLayout.LayoutParams(width, height, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
get_filename_component(DIR_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
||||||
|
message("Configuring " ${DIR_NAME})
|
||||||
|
|
||||||
|
set(webview_URI AmneziaWebView)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Quick)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick)
|
||||||
|
|
||||||
|
# Widgets and WebEngineWidgets are only available on desktop platforms
|
||||||
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS WebEngineWidgets)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
set(PLUGIN_CLASS_NAME WebViewPlugin)
|
||||||
|
add_definitions(-DURI=${webview_URI})
|
||||||
|
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
set(webview_HEADERS
|
||||||
|
amneziawebview.h
|
||||||
|
amneziawebview_p.h
|
||||||
|
websettings.h
|
||||||
|
mimecache.h
|
||||||
|
filehandler.h
|
||||||
|
qrchandler.h
|
||||||
|
jshandler.h
|
||||||
|
plugin.h
|
||||||
|
amneziawebhistory.h
|
||||||
|
amneziawebhistory_p.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(webview_SOURCES
|
||||||
|
amneziawebview.cpp
|
||||||
|
amneziawebview_p.cpp
|
||||||
|
websettings.cpp
|
||||||
|
mimecache.cpp
|
||||||
|
qrchandler.cpp
|
||||||
|
jshandler.cpp
|
||||||
|
filehandler.cpp
|
||||||
|
plugin.cpp
|
||||||
|
amneziawebhistory.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (CMAKE_CROSSCOMPILING AND ANDROID)
|
||||||
|
|
||||||
|
list(APPEND webview_SOURCES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/jshandler_android.cpp"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/qrchandler_android.cpp"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/filehandler_android.cpp"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/amneziawebview_android.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (CMAKE_CROSSCOMPILING AND APPLE)
|
||||||
|
|
||||||
|
add_definitions(-DENABLE_WKWEBVIEW)
|
||||||
|
list(APPEND webview_SOURCES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/amneziawebview_ios.mm"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/qrchandler_ios.mm"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/jshandler_ios.mm"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/filehandler_ios.mm"
|
||||||
|
)
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT CMAKE_CROSSCOMPILING)
|
||||||
|
# Require WebEngineWidgets for desktop platforms (QtWebKit is not available in Qt 6)
|
||||||
|
if (Qt6WebEngineWidgets_FOUND)
|
||||||
|
message(STATUS "Using Qt WebEngineWidgets for desktop webview")
|
||||||
|
list(APPEND webview_HEADERS
|
||||||
|
amneziawebview_webengine_p.h
|
||||||
|
)
|
||||||
|
list(APPEND webview_SOURCES
|
||||||
|
amneziawebview_webengine.cpp
|
||||||
|
)
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR "Qt WebEngineWidgets is required for desktop builds. QtWebKit is not available in Qt 6. Please install Qt WebEngineWidgets module.")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_library(webview STATIC ${webview_SOURCES} ${webview_HEADERS})
|
||||||
|
|
||||||
|
target_compile_definitions(webview PRIVATE
|
||||||
|
QT_PLUGIN
|
||||||
|
QT_STATICPLUGIN
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(webview PUBLIC
|
||||||
|
Qt${QT_VERSION_MAJOR}::Core
|
||||||
|
Qt${QT_VERSION_MAJOR}::Quick
|
||||||
|
)
|
||||||
|
|
||||||
|
# Widgets and WebEngineWidgets are only available on desktop platforms
|
||||||
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
|
if(TARGET Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
|
target_link_libraries(webview PUBLIC Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (Qt6WebEngineWidgets_FOUND)
|
||||||
|
target_link_libraries(webview PRIVATE
|
||||||
|
Qt${QT_VERSION_MAJOR}::WebEngineWidgets
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link WebKit framework for iOS
|
||||||
|
if (CMAKE_CROSSCOMPILING AND APPLE)
|
||||||
|
find_library(FW_WEBKIT WebKit)
|
||||||
|
if(FW_WEBKIT)
|
||||||
|
target_link_libraries(webview PRIVATE ${FW_WEBKIT})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_target_properties(webview PROPERTIES AUTOMOC_MOC_OPTIONS "-Muri=${webview_URI}")
|
||||||
|
|
||||||
|
#include(precompiled.headers)
|
||||||
|
#add_precompiled_header(webview pch.h FORCEINCLUDE)
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
#include "amneziawebhistory.h"
|
||||||
|
#include "amneziawebhistory_p.h"
|
||||||
|
#include "amneziawebview.h"
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
|
||||||
|
#include <QSharedData>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Constructs a history item from \a other. The new item and \a other
|
||||||
|
will share their data, and modifying either this item or \a other will
|
||||||
|
modify both instances.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem::AmneziaWebHistoryItem(const AmneziaWebHistoryItem &other)
|
||||||
|
: d_ptr(other.d_ptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Assigns the \a other history item to this. This item and \a other
|
||||||
|
will share their data, and modifying either this item or \a other will
|
||||||
|
modify both instances.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem &AmneziaWebHistoryItem::operator=(const AmneziaWebHistoryItem &other)
|
||||||
|
{
|
||||||
|
d_ptr = other.d_ptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Destroys the history item.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem::~AmneziaWebHistoryItem()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the URL associated with the history item.
|
||||||
|
|
||||||
|
\sa originalUrl(), title(), lastVisited(), data(), mimeType()
|
||||||
|
*/
|
||||||
|
QUrl AmneziaWebHistoryItem::url() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistoryItem);
|
||||||
|
if (d)
|
||||||
|
return d->url();
|
||||||
|
return QUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the title of the page associated with the history item.
|
||||||
|
|
||||||
|
\sa icon(), url(), lastVisited(), data(), mimeType()
|
||||||
|
*/
|
||||||
|
QString AmneziaWebHistoryItem::title() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistoryItem);
|
||||||
|
if (d)
|
||||||
|
return d->title();
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the icon associated with the history item.
|
||||||
|
|
||||||
|
\sa title(), url(), lastVisited(), data(), mimeType()
|
||||||
|
*/
|
||||||
|
QIcon AmneziaWebHistoryItem::icon() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistoryItem);
|
||||||
|
if (d)
|
||||||
|
return d->icon();
|
||||||
|
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the data associated with the history item.
|
||||||
|
|
||||||
|
\sa icon(), title(), url(), lastVisited(), mimeType()
|
||||||
|
*/
|
||||||
|
QByteArray AmneziaWebHistoryItem::data() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistoryItem);
|
||||||
|
if(d) return d->data();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the mimeType associated with the history item.
|
||||||
|
|
||||||
|
\sa icon(), title(), url(), lastVisited(), data()
|
||||||
|
*/
|
||||||
|
QString AmneziaWebHistoryItem::mimeType() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistoryItem);
|
||||||
|
if(d) return d->mimeType();
|
||||||
|
return QString("text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!*
|
||||||
|
\internal
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem::AmneziaWebHistoryItem(AmneziaWebHistoryItemPrivate *priv) : d_ptr(priv)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 4.5
|
||||||
|
Returns whether this is a valid history item.
|
||||||
|
*/
|
||||||
|
bool AmneziaWebHistoryItem::isValid() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistoryItem);
|
||||||
|
bool valid = (d);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebHistory::AmneziaWebHistory(AmneziaWebView *parent) : QObject(parent)
|
||||||
|
, d_ptr(new AmneziaWebHistoryPrivate())
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
d->q_ptr = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebHistory::~AmneziaWebHistory()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Clears the history.
|
||||||
|
|
||||||
|
\sa count(), items()
|
||||||
|
*/
|
||||||
|
void AmneziaWebHistory::clear()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
while (d->items.count()) {
|
||||||
|
d->items.removeFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns a list of all items currently in the history.
|
||||||
|
|
||||||
|
\sa count(), clear()
|
||||||
|
*/
|
||||||
|
QList<AmneziaWebHistoryItem> AmneziaWebHistory::items() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
QList<AmneziaWebHistoryItem> ret;
|
||||||
|
|
||||||
|
for (int i = 0; i < d->items.size(); ++i) {
|
||||||
|
AmneziaWebHistoryItem item(d->items[i]);
|
||||||
|
ret.append(item);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the list of items in the backwards history list.
|
||||||
|
At most \a maxItems entries are returned.
|
||||||
|
|
||||||
|
\sa forwardItems()
|
||||||
|
*/
|
||||||
|
QList<AmneziaWebHistoryItem> AmneziaWebHistory::backItems(int maxItems) const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
|
||||||
|
int count = d->currentIndex;
|
||||||
|
if (maxItems >= 0) {
|
||||||
|
count = qMin(count, maxItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<AmneziaWebHistoryItem> ret;
|
||||||
|
for (int i = (d->currentIndex - count); i < d->currentIndex; i++) {
|
||||||
|
ret.append(d->items[i]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the list of items in the forward history list.
|
||||||
|
At most \a maxItems entries are returned.
|
||||||
|
|
||||||
|
\sa backItems()
|
||||||
|
*/
|
||||||
|
QList<AmneziaWebHistoryItem> AmneziaWebHistory::forwardItems(int maxItems) const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
|
||||||
|
int count = d->items.count() - d->currentIndex - 1;
|
||||||
|
if (maxItems >= 0) {
|
||||||
|
count = qMin(count, maxItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<AmneziaWebHistoryItem> ret;
|
||||||
|
for (int i = (d->currentIndex + 1); i <= d->currentIndex + count; i++) {
|
||||||
|
ret.append(d->items[i]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns true if there is an item preceding the current item in the history;
|
||||||
|
otherwise returns false.
|
||||||
|
|
||||||
|
\sa canGoForward()
|
||||||
|
*/
|
||||||
|
bool AmneziaWebHistory::canGoBack() const
|
||||||
|
{
|
||||||
|
const AmneziaWebHistoryItem current = currentItem();
|
||||||
|
bool can = (current.isValid() && current.d_ptr->backItem() != nullptr);
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns true if we have an item to go forward to; otherwise returns false.
|
||||||
|
|
||||||
|
\sa canGoBack()
|
||||||
|
*/
|
||||||
|
bool AmneziaWebHistory::canGoForward() const
|
||||||
|
{
|
||||||
|
const AmneziaWebHistoryItem current = currentItem();
|
||||||
|
bool can = (current.isValid() && current.d_ptr->forwardItem() != nullptr);
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Set the current item to be the previous item in the history and goes to the
|
||||||
|
corresponding page; i.e., goes back one history item.
|
||||||
|
|
||||||
|
\sa forward(), goToItem()
|
||||||
|
*/
|
||||||
|
void AmneziaWebHistory::back()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
if(!canGoBack()) return;
|
||||||
|
AmneziaWebView *view = qobject_cast<AmneziaWebView*>(parent());
|
||||||
|
AmneziaWebHistoryItem item = backItem();
|
||||||
|
|
||||||
|
d->currentIndex--;
|
||||||
|
if (view) {
|
||||||
|
if (item.data().length() > 0) {
|
||||||
|
view->setContent(item.data(), item.mimeType(), item.url());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
view->setUrl(item.url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets the current item to be the next item in the history and goes to the
|
||||||
|
corresponding page; i.e., goes forward one history item.
|
||||||
|
|
||||||
|
\sa back(), goToItem()
|
||||||
|
*/
|
||||||
|
void AmneziaWebHistory::forward()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
if(!canGoForward()) return;
|
||||||
|
AmneziaWebView *view = qobject_cast<AmneziaWebView*>(parent());
|
||||||
|
AmneziaWebHistoryItem item = backItem();
|
||||||
|
|
||||||
|
d->currentIndex++;
|
||||||
|
if (view) {
|
||||||
|
if (item.data().length() > 0) {
|
||||||
|
view->setContent(item.data(), item.mimeType(), item.url());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
view->setUrl(item.url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets the current item to be the specified \a item in the history and goes to the page.
|
||||||
|
|
||||||
|
\sa back(), forward()
|
||||||
|
*/
|
||||||
|
void AmneziaWebHistory::goToItem(const AmneziaWebHistoryItem &item)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
if(!item.isValid()) return;
|
||||||
|
AmneziaWebView *view = qobject_cast<AmneziaWebView*>(parent());
|
||||||
|
if (!view) return; //There is no view to go.
|
||||||
|
if (item.url().isEmpty()) return; //
|
||||||
|
|
||||||
|
int index = -1;
|
||||||
|
for(int i= 0; i < d->items.count(); ++i) {
|
||||||
|
if(d->items[i].d_ptr.data() == item.d_ptr.data()) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
|
||||||
|
d->currentIndex = index;
|
||||||
|
if (item.data().length() > 0) {
|
||||||
|
view->setContent(item.data(), item.mimeType(), item.url());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
view->setUrl(item.url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the current item in the history.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem AmneziaWebHistory::currentItem() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
|
||||||
|
if ((d->currentIndex >= 0) && (d->currentIndex < d->items.count())) {
|
||||||
|
return AmneziaWebHistoryItem(d->items.at(d->currentIndex));
|
||||||
|
}
|
||||||
|
return AmneziaWebHistoryItem(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebHistory::append(const QUrl& url, const QByteArray& data, const QString& mimeType)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
|
||||||
|
const AmneziaWebHistoryItem current = currentItem();
|
||||||
|
// Check if url is same as current, and do not add it second time.
|
||||||
|
if (current.url() == url) return;
|
||||||
|
|
||||||
|
AmneziaWebHistoryItemPrivate *priv = new AmneziaWebHistoryItemPrivate();
|
||||||
|
if(current.isValid()) {
|
||||||
|
current.d_ptr->_forwardItem = priv;
|
||||||
|
priv->_backItem = current.d_ptr.data();
|
||||||
|
}
|
||||||
|
priv->_data = data;
|
||||||
|
priv->_url = url;
|
||||||
|
priv->_mimeType = mimeType;
|
||||||
|
|
||||||
|
//Remove last items till current
|
||||||
|
while (d->items.count() > (d->currentIndex + 1)) {
|
||||||
|
d->items.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
//No more then maximum
|
||||||
|
while (d->items.count() >= d->maximumCount) {
|
||||||
|
d->items.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
d->items.append(AmneziaWebHistoryItem(priv));
|
||||||
|
d->currentIndex = (d->items.count() - 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the item before the current item in the history.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem AmneziaWebHistory::backItem() const
|
||||||
|
{
|
||||||
|
AmneziaWebHistoryItem current = currentItem();
|
||||||
|
return AmneziaWebHistoryItem(current.d_ptr->backItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the item after the current item in the history.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem AmneziaWebHistory::forwardItem() const
|
||||||
|
{
|
||||||
|
AmneziaWebHistoryItem current = currentItem();
|
||||||
|
return AmneziaWebHistoryItem(current.d_ptr->forwardItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 4.5
|
||||||
|
Returns the index of the current item in history.
|
||||||
|
*/
|
||||||
|
int AmneziaWebHistory::currentItemIndex() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
return d->currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the item at index \a i in the history.
|
||||||
|
*/
|
||||||
|
AmneziaWebHistoryItem AmneziaWebHistory::itemAt(int i) const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
int index = (i < 0) ? 0 : i;
|
||||||
|
index = (index >= count()) ? (count() -1) : index;
|
||||||
|
if (index >= 0) {
|
||||||
|
return AmneziaWebHistoryItem(d->items.at(index));
|
||||||
|
}
|
||||||
|
return AmneziaWebHistoryItem(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the total number of items in the history.
|
||||||
|
*/
|
||||||
|
int AmneziaWebHistory::count() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
return d->items.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 4.5
|
||||||
|
Returns the maximum number of items in the history.
|
||||||
|
|
||||||
|
\sa setMaximumItemCount()
|
||||||
|
*/
|
||||||
|
int AmneziaWebHistory::maximumItemCount() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebHistory);
|
||||||
|
return d->maximumCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 4.5
|
||||||
|
Sets the maximum number of items in the history to \a count.
|
||||||
|
|
||||||
|
\sa maximumItemCount()
|
||||||
|
*/
|
||||||
|
void AmneziaWebHistory::setMaximumItemCount(int count)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebHistory);
|
||||||
|
d->maximumCount = count;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef WEBHISTORY_H
|
||||||
|
#define WEBHISTORY_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
class AmneziaWebViewPrivate;
|
||||||
|
class AmneziaWebView;
|
||||||
|
|
||||||
|
class AmneziaWebHistory;
|
||||||
|
class AmneziaWebHistoryItemPrivate;
|
||||||
|
class AmneziaWebHistoryItem
|
||||||
|
{
|
||||||
|
Q_DECLARE_PRIVATE(AmneziaWebHistoryItem)
|
||||||
|
public:
|
||||||
|
|
||||||
|
AmneziaWebHistoryItem(const AmneziaWebHistoryItem &other);
|
||||||
|
AmneziaWebHistoryItem &operator=(const AmneziaWebHistoryItem &other);
|
||||||
|
~AmneziaWebHistoryItem();
|
||||||
|
|
||||||
|
QUrl url() const;
|
||||||
|
QString title() const;
|
||||||
|
QIcon icon() const;
|
||||||
|
|
||||||
|
QByteArray data() const;
|
||||||
|
QString mimeType() const;
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit AmneziaWebHistoryItem(AmneziaWebHistoryItemPrivate *priv);
|
||||||
|
friend class AmneziaWebHistory;
|
||||||
|
friend class AmneziaWebViewPrivate;
|
||||||
|
QExplicitlySharedDataPointer<AmneziaWebHistoryItemPrivate> d_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AmneziaWebHistoryPrivate;
|
||||||
|
class AmneziaWebHistory : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DECLARE_PRIVATE(AmneziaWebHistory)
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~AmneziaWebHistory();
|
||||||
|
|
||||||
|
void append(const QUrl& url, const QByteArray& data = QByteArray(), const QString& mimeType = QString("text/html"));
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
QList<AmneziaWebHistoryItem> items() const;
|
||||||
|
QList<AmneziaWebHistoryItem> backItems(int maxItems) const;
|
||||||
|
QList<AmneziaWebHistoryItem> forwardItems(int maxItems) const;
|
||||||
|
|
||||||
|
bool canGoBack() const;
|
||||||
|
bool canGoForward() const;
|
||||||
|
|
||||||
|
void back();
|
||||||
|
void forward();
|
||||||
|
void goToItem(const AmneziaWebHistoryItem &item);
|
||||||
|
|
||||||
|
AmneziaWebHistoryItem backItem() const;
|
||||||
|
AmneziaWebHistoryItem currentItem() const;
|
||||||
|
AmneziaWebHistoryItem forwardItem() const;
|
||||||
|
AmneziaWebHistoryItem itemAt(int i) const;
|
||||||
|
int currentItemIndex() const;
|
||||||
|
|
||||||
|
int count() const;
|
||||||
|
int maximumItemCount() const;
|
||||||
|
void setMaximumItemCount(int count);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class AmneziaWebViewPrivate;
|
||||||
|
explicit AmneziaWebHistory(AmneziaWebView *parent);
|
||||||
|
Q_DISABLE_COPY(AmneziaWebHistory)
|
||||||
|
QScopedPointer<AmneziaWebHistoryPrivate> d_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#ifndef WEBHISTORY_P_H
|
||||||
|
#define WEBHISTORY_P_H
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "amneziawebhistory.h"
|
||||||
|
|
||||||
|
class AmneziaWebHistoryItemPrivate;
|
||||||
|
class AmneziaWebHistoryItem;
|
||||||
|
|
||||||
|
class AmneziaWebHistoryPrivate
|
||||||
|
{
|
||||||
|
Q_DECLARE_PUBLIC(AmneziaWebHistory)
|
||||||
|
public:
|
||||||
|
|
||||||
|
static AmneziaWebHistoryPrivate *get(AmneziaWebHistory *q)
|
||||||
|
{
|
||||||
|
if (!q) { return nullptr; }
|
||||||
|
return q->d_func();
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebHistoryPrivate(): currentIndex(-1), maximumCount(10), q_ptr(nullptr) { }
|
||||||
|
~AmneziaWebHistoryPrivate() = default;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AmneziaWebHistoryItemPrivate;
|
||||||
|
int currentIndex;
|
||||||
|
int maximumCount;
|
||||||
|
QList<AmneziaWebHistoryItem> items;
|
||||||
|
AmneziaWebHistory *q_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AmneziaWebHistoryItemPrivate : public QSharedData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
static QExplicitlySharedDataPointer<AmneziaWebHistoryItemPrivate> get(AmneziaWebHistoryItem *q)
|
||||||
|
{
|
||||||
|
return q->d_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~AmneziaWebHistoryItemPrivate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url() const { return _url; }
|
||||||
|
QString title() const { return _title; }
|
||||||
|
QIcon icon() const {return _icon;}
|
||||||
|
QByteArray data() const {return _data;}
|
||||||
|
QString mimeType() const {return _mimeType;}
|
||||||
|
|
||||||
|
// Every item knows its back and forward items
|
||||||
|
AmneziaWebHistoryItemPrivate *backItem() {return _backItem; }
|
||||||
|
AmneziaWebHistoryItemPrivate *forwardItem() {return _forwardItem; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AmneziaWebHistory;
|
||||||
|
AmneziaWebHistoryItemPrivate() = default;
|
||||||
|
|
||||||
|
AmneziaWebHistoryItemPrivate *_backItem = nullptr;
|
||||||
|
AmneziaWebHistoryItemPrivate *_forwardItem = nullptr;
|
||||||
|
QIcon _icon;
|
||||||
|
QString _title;
|
||||||
|
QUrl _url;
|
||||||
|
QString _html;
|
||||||
|
QByteArray _data;
|
||||||
|
QString _mimeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,751 @@
|
|||||||
|
#include <QDebug>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QMetaObject>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QList>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#define qApp qGuiApp
|
||||||
|
#else
|
||||||
|
#include <QApplication>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "amneziawebview.h"
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
|
||||||
|
QUrl defaultBaseUrl()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS)
|
||||||
|
return QUrl(QLatin1String("local:///"));
|
||||||
|
#else
|
||||||
|
return QUrl(QLatin1String("file:///"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
AmneziaWebView::AmneziaWebView(QQuickItem *parent) : QQuickPaintedItem(parent),
|
||||||
|
d_ptr(AmneziaWebViewPrivate::create(this))
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->q_ptr = this;
|
||||||
|
d->init();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebView::~AmneziaWebView()
|
||||||
|
{
|
||||||
|
disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::init()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
setAcceptedMouseButtons(Qt::LeftButton);
|
||||||
|
setFlag(QQuickItem::ItemHasContents, true);
|
||||||
|
setOpaquePainting(true);
|
||||||
|
|
||||||
|
setClip(true);
|
||||||
|
|
||||||
|
connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(windowWasChanged(QQuickWindow*)));
|
||||||
|
connect(this, SIGNAL(parentChanged(QQuickItem*)), this, SLOT(parentWasChanged()));
|
||||||
|
connect(this, SIGNAL(fillColorChanged()), this,SLOT(fillColorWasChanged()));
|
||||||
|
|
||||||
|
connect(d, SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged(QString)));
|
||||||
|
connect(d, SIGNAL(loadStarted()), this, SLOT(doLoadStarted()));
|
||||||
|
connect(d, SIGNAL(loadFinished(bool)), this, SLOT(doLoadFinished(bool)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::componentComplete()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
QQuickItem::componentComplete();
|
||||||
|
|
||||||
|
// Update geometry after component is complete
|
||||||
|
if (window()) {
|
||||||
|
QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (d->pending) {
|
||||||
|
case AmneziaWebViewPrivate::PendingUrl:
|
||||||
|
// Make WebView visible before loading
|
||||||
|
if (isVisible() && !d->visible) {
|
||||||
|
d->show();
|
||||||
|
}
|
||||||
|
setUrl(d->pendingUrl);
|
||||||
|
break;
|
||||||
|
case AmneziaWebViewPrivate::PendingHtml:
|
||||||
|
if (isVisible() && !d->visible) {
|
||||||
|
d->show();
|
||||||
|
}
|
||||||
|
setHtml(d->pendingString, d->pendingUrl);
|
||||||
|
break;
|
||||||
|
case AmneziaWebViewPrivate::PendingContent:
|
||||||
|
if (isVisible() && !d->visible) {
|
||||||
|
d->show();
|
||||||
|
}
|
||||||
|
setContent(d->pendingData, d->pendingString, d->pendingUrl);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebView::Status AmneziaWebView::status() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty real WebView::progress
|
||||||
|
This property holds the progress of loading the current URL, from 0 to 1.
|
||||||
|
|
||||||
|
If you just want to know when progress gets to 1, use
|
||||||
|
WebView::onLoadFinished() or WebView::onLoadFailed() instead.
|
||||||
|
*/
|
||||||
|
qreal AmneziaWebView::progress() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::doLoadStarted()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
if (!d->url.isEmpty()) {
|
||||||
|
d->status = Loading;
|
||||||
|
emit statusChanged(d->status);
|
||||||
|
}
|
||||||
|
emit loadStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::doLoadProgress(int p)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
if (d->progress == p / 100.0)
|
||||||
|
return;
|
||||||
|
d->progress = p / 100.0;
|
||||||
|
emit progressChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AmneziaWebView::doLoadFinished(bool ok)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
d->status = d->url.isEmpty() ? Null : Ready;
|
||||||
|
emit loadFinished();
|
||||||
|
} else {
|
||||||
|
d->status = Error;
|
||||||
|
emit loadFailed();
|
||||||
|
}
|
||||||
|
emit statusChanged(d->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty url AmneziaWebView::url
|
||||||
|
This property holds the URL to the page displayed in this item. It can be set,
|
||||||
|
but also can change spontaneously (eg. because of network redirection).
|
||||||
|
|
||||||
|
If the url is empty, the page is blank.
|
||||||
|
|
||||||
|
The url is always absolute (QML will resolve relative URL strings in the context
|
||||||
|
of the containing QML document).
|
||||||
|
*/
|
||||||
|
QUrl AmneziaWebView::url() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setUrl(const QUrl& url)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
QString urlString = url.toString();
|
||||||
|
while ( urlString.endsWith('#')) urlString.chop(1);
|
||||||
|
QUrl newUrl(urlString);
|
||||||
|
|
||||||
|
if (newUrl == QUrl(QLatin1String("about:blank")) ) {
|
||||||
|
newUrl = QUrl("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((url == d->url) || (newUrl == d->url))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (isComponentComplete()) {
|
||||||
|
// Make WebView visible before loading
|
||||||
|
if (isVisible() && !d->visible) {
|
||||||
|
d->show();
|
||||||
|
}
|
||||||
|
d->load(url);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
d->pending = d->PendingUrl;
|
||||||
|
d->pendingUrl = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal AmneziaWebView::preferredWidth() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->preferredwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setPreferredWidth(qreal width)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
if (d->preferredwidth == width)
|
||||||
|
return;
|
||||||
|
|
||||||
|
d->preferredwidth = width;
|
||||||
|
updateContentsSize();
|
||||||
|
setImplicitWidth(width);
|
||||||
|
emit preferredWidthChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal AmneziaWebView::preferredHeight() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->preferredheight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setPreferredHeight(qreal height)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
if (d->preferredheight == height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
d->preferredheight = height;
|
||||||
|
updateContentsSize();
|
||||||
|
setImplicitHeight(height);
|
||||||
|
emit preferredHeightChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlmethod bool AmneziaWebView::evaluateJavaScript(string scriptSource)
|
||||||
|
|
||||||
|
Evaluates the \a scriptSource JavaScript inside the context of the
|
||||||
|
main web frame, and returns the result of the last executed statement.
|
||||||
|
|
||||||
|
Note that this JavaScript does \e not have any access to QML objects
|
||||||
|
except as made available as windowObjects.
|
||||||
|
*/
|
||||||
|
void AmneziaWebView::evaluateJavaScript(const QString& scriptSource)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
if (qApp->thread() == QThread::currentThread()) {
|
||||||
|
d->evaluateJavaScript(scriptSource);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QMetaObject::invokeMethod(d, "evaluateJavaScript", Qt::BlockingQueuedConnection,
|
||||||
|
Q_ARG(const QString, scriptSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::windowWasChanged(QQuickWindow* window)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->setWindowParent(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::updateGeometry()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
QRectF geometry = QRectF(QPointF(x(), y()), QSizeF(width(), height()));
|
||||||
|
if (!geometry.isEmpty() && window()) {
|
||||||
|
QRectF sceneGeometry = mapRectToScene(geometry);
|
||||||
|
QRect rect = sceneGeometry.toRect();
|
||||||
|
qDebug() << "AmneziaWebView::updateGeometry() - local:" << geometry
|
||||||
|
<< "scene:" << sceneGeometry << "rect:" << rect
|
||||||
|
<< "width:" << width() << "height:" << height();
|
||||||
|
d->setGeometry(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AmneziaWebView::parentWasChanged()
|
||||||
|
{
|
||||||
|
if (parentItem()) {
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QQuickItem *> recurseChildren(QQuickItem * parentItem)
|
||||||
|
{
|
||||||
|
QList<QQuickItem *>childs = parentItem->childItems();
|
||||||
|
QList<QQuickItem *> items;
|
||||||
|
int count = childs.count();
|
||||||
|
for(int i = count - 1; i >= 0; --i) {
|
||||||
|
QQuickItem *next = childs.at(i);
|
||||||
|
items.append(recurseChildren(next));
|
||||||
|
}
|
||||||
|
items.append(childs);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AmneziaWebView::paint(QPainter *painter)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
if (!painter || !window() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRectF contentRect = contentsBoundingRect();
|
||||||
|
if ((contentRect.height() <= 0) || (contentRect.width() <= 0)) return;
|
||||||
|
|
||||||
|
painter->setOpacity(1.0);
|
||||||
|
QMutexLocker lock(&d->renderMutex);
|
||||||
|
|
||||||
|
QColor color = d->backgroundColor;
|
||||||
|
painter->fillRect(contentRect, color);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::afterRendering()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
Qt::ApplicationState state = qApp->applicationState();
|
||||||
|
if ( state != Qt::ApplicationActive) {
|
||||||
|
if (d->visible && isVisible()) {
|
||||||
|
QMetaObject::invokeMethod(d, "requestHide", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window()) return;
|
||||||
|
|
||||||
|
if (isVisible()) {
|
||||||
|
QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(d, "requestShow", Qt::QueuedConnection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
|
||||||
|
|
||||||
|
// Update WebView geometry when QML item size changes
|
||||||
|
if (window() && !newGeometry.isEmpty() && newGeometry != oldGeometry) {
|
||||||
|
QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::itemChange(ItemChange change, const ItemChangeData & value)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
switch (change) {
|
||||||
|
case ItemSceneChange: {
|
||||||
|
QQuickWindow *sc = value.window;
|
||||||
|
if (sc) {
|
||||||
|
connect(sc, SIGNAL(afterRendering()), this, SLOT(afterRendering()), Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
disconnect(this, SLOT(afterRendering()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ItemVisibleHasChanged: {
|
||||||
|
|
||||||
|
if (!window()) break;
|
||||||
|
|
||||||
|
if (value.boolValue) {
|
||||||
|
// Component became visible - show WebView
|
||||||
|
if (!d->visible) {
|
||||||
|
d->show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QMetaObject::invokeMethod(d, "requestHide", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
if (value.boolValue && !d->overlapped) {
|
||||||
|
QMetaObject::invokeMethod(d, "requestShow", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
QQuickPaintedItem::itemChange(change, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty list<object> WebView::javaScriptWindowObjects
|
||||||
|
|
||||||
|
A list of QML objects to expose to the web page.
|
||||||
|
|
||||||
|
Each object will be added as a property of the web frame's window object. The
|
||||||
|
property name is controlled by the value of \c WebView.windowObjectName
|
||||||
|
attached property.
|
||||||
|
|
||||||
|
Exposing QML objects to a web page allows JavaScript executing in the web
|
||||||
|
page itself to communicate with QML, by reading and writing properties and
|
||||||
|
by calling methods of the exposed QML objects.
|
||||||
|
|
||||||
|
This example shows how to call into a QML method using a window object.
|
||||||
|
|
||||||
|
\qml
|
||||||
|
WebView {
|
||||||
|
javaScriptWindowObjects: QtObject {
|
||||||
|
WebView.windowObjectName: "qml"
|
||||||
|
|
||||||
|
function qmlCall() {
|
||||||
|
console.log("This call is in QML!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html: "<script>window.qml.qmlCall();</script>"
|
||||||
|
}
|
||||||
|
\endqml
|
||||||
|
|
||||||
|
The output of the example will be:
|
||||||
|
\code
|
||||||
|
This call is in QML!
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
If Javascript is not enabled for the page, then this property does nothing.
|
||||||
|
*/
|
||||||
|
QQmlListProperty<QObject> AmneziaWebView::javaScriptWindowObjects()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
return QQmlListProperty<QObject>(this, d, &AmneziaWebViewPrivate::windowObjectsAppend,
|
||||||
|
&AmneziaWebViewPrivate::windowObjectsCount,
|
||||||
|
&AmneziaWebViewPrivate::windowObjectsAt,
|
||||||
|
&AmneziaWebViewPrivate::windowObjectsClear );
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewSettings* AmneziaWebView::settingsObject() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->m_settings.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AmneziaWebViewAttached* AmneziaWebView::qmlAttachedProperties(QObject* o)
|
||||||
|
{
|
||||||
|
return new AmneziaWebViewAttached(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::updateWindowObjects()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!q_ptr->isComponentCompletePublic())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < windowObjects.count(); ++i) {
|
||||||
|
QObject* object = windowObjects.at(i);
|
||||||
|
AmneziaWebViewAttached* attached = static_cast<AmneziaWebViewAttached *>(qmlAttachedPropertiesObject<AmneziaWebView>(object));
|
||||||
|
if (attached && !attached->windowObjectName().isEmpty())
|
||||||
|
addToJavaScriptWindowObject(attached->windowObjectName(), object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int AmneziaWebView::pressGrabTime() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setPressGrabTime(int millis)
|
||||||
|
{
|
||||||
|
Q_UNUSED(millis)
|
||||||
|
|
||||||
|
emit pressGrabTimeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_ACTION
|
||||||
|
/*!
|
||||||
|
\qmlproperty action WebView::back
|
||||||
|
This property holds the action for causing the previous URL in the history to be displayed.
|
||||||
|
*/
|
||||||
|
QAction* AmneziaWebView::backAction() const
|
||||||
|
{
|
||||||
|
return action(AmneziaWebView::Back);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty action WebView::forward
|
||||||
|
This property holds the action for causing the next URL in the history to be displayed.
|
||||||
|
*/
|
||||||
|
QAction* AmneziaWebView::forwardAction() const
|
||||||
|
{
|
||||||
|
return action(AmneziaWebView::Forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty action WebView::reload
|
||||||
|
This property holds the action for reloading with the current URL
|
||||||
|
*/
|
||||||
|
QAction* AmneziaWebView::reloadAction() const
|
||||||
|
{
|
||||||
|
return action(AmneziaWebView::Reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty action WebView::stop
|
||||||
|
This property holds the action for stopping loading with the current URL
|
||||||
|
*/
|
||||||
|
QAction* AmneziaWebView::stopAction() const
|
||||||
|
{
|
||||||
|
return action(AmneziaWebView::Stop);
|
||||||
|
}
|
||||||
|
#endif // QT_NO_ACTION
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty string WebView::title
|
||||||
|
This property holds the title of the web page currently viewed
|
||||||
|
|
||||||
|
By default, this property contains an empty string.
|
||||||
|
*/
|
||||||
|
QString AmneziaWebView::title() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty pixmap WebView::icon
|
||||||
|
This property holds the icon associated with the web page currently viewed
|
||||||
|
*/
|
||||||
|
QPixmap AmneziaWebView::icon() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->icon().pixmap(QSize(256, 256));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty string WebView::statusText
|
||||||
|
|
||||||
|
This property is the current status suggested by the current web page. In a web browser,
|
||||||
|
such status is often shown in some kind of status bar.
|
||||||
|
*/
|
||||||
|
void AmneziaWebView::setStatusText(const QString& text)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->statusText = text;
|
||||||
|
emit statusTextChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::windowObjectCleared()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->updateWindowObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AmneziaWebView::statusText() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AmneziaWebView::load(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& body)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->load(request, operation, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AmneziaWebView::html() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->toHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setHtml(const QString& html, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
auto originUrl = baseUrl.isValid() ? baseUrl : defaultBaseUrl();
|
||||||
|
|
||||||
|
updateContentsSize();
|
||||||
|
if (isComponentComplete()) {
|
||||||
|
d->setHtml(html, originUrl);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d->pending = d->PendingHtml;
|
||||||
|
d->pendingUrl = originUrl;
|
||||||
|
d->pendingString = html;
|
||||||
|
}
|
||||||
|
emit htmlChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
|
||||||
|
updateContentsSize();
|
||||||
|
auto originUrl = baseUrl.isValid() ? baseUrl : defaultBaseUrl();
|
||||||
|
|
||||||
|
if (isComponentComplete())
|
||||||
|
d->setContent(data, mimeType, qmlContext(this)->resolvedUrl(baseUrl));
|
||||||
|
else {
|
||||||
|
d->pending = d->PendingContent;
|
||||||
|
d->pendingUrl = originUrl;
|
||||||
|
d->pendingString = mimeType;
|
||||||
|
d->pendingData = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebHistory* AmneziaWebView::history() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->history();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_ACTION
|
||||||
|
QAction* AmneziaWebView::action(AmneziaWebView::WebAction action) const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->action(action);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty component WebView::newWindowComponent
|
||||||
|
|
||||||
|
This property holds the component to use for new windows.
|
||||||
|
The component must have a WebView somewhere in its structure.
|
||||||
|
|
||||||
|
When the web engine requests a new window, it will be an instance of
|
||||||
|
this component.
|
||||||
|
|
||||||
|
The parent of the new window is set by newWindowParent. It must be set.
|
||||||
|
*/
|
||||||
|
QQmlComponent* AmneziaWebView::newWindowComponent() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->newWindowComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setNewWindowComponent(QQmlComponent* newWindow)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
if (newWindow == d->newWindowComponent)
|
||||||
|
return;
|
||||||
|
d->newWindowComponent = newWindow;
|
||||||
|
emit newWindowComponentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty item WebView::newWindowParent
|
||||||
|
|
||||||
|
The parent item for new windows.
|
||||||
|
|
||||||
|
\sa newWindowComponent
|
||||||
|
*/
|
||||||
|
QQuickItem* AmneziaWebView::newWindowParent() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->newWindowParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setNewWindowParent(QQuickItem *parent)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
if (parent == d->newWindowParent)
|
||||||
|
return;
|
||||||
|
if (d->newWindowParent && parent) {
|
||||||
|
QList<QQuickItem *> children = d->newWindowParent->childItems();
|
||||||
|
for (int i = 0; i < children.count(); ++i)
|
||||||
|
children.at(i)->setParentItem(parent);
|
||||||
|
}
|
||||||
|
d->newWindowParent = parent;
|
||||||
|
emit newWindowParentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize AmneziaWebView::contentsSize() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->contentsSize() * contentsScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal AmneziaWebView::contentsScale() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->scale();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setContentsScale(qreal scale)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
if (scale == d->scale())
|
||||||
|
return;
|
||||||
|
d->setScale(scale);
|
||||||
|
|
||||||
|
//updateGeometry();
|
||||||
|
emit contentsScaleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setDefaultFontSize(int size)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->setDefaultFontSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setStandardFontFamily(const QString &family)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->setStandardFontFamily(family);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setTextZoom(int percent)
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
d->setTextZoom(percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef Q_REVISION
|
||||||
|
/*!
|
||||||
|
\qmlproperty color WebView::backgroundColor
|
||||||
|
\since QtWebKit 1.1
|
||||||
|
This property holds the background color of the view.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QColor AmneziaWebView::backgroundColor() const
|
||||||
|
{
|
||||||
|
Q_D(const AmneziaWebView);
|
||||||
|
return d->backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::setBackgroundColor(const QColor& color)
|
||||||
|
{
|
||||||
|
setFillColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebView::fillColorWasChanged()
|
||||||
|
{
|
||||||
|
Q_D(AmneziaWebView);
|
||||||
|
QColor color = fillColor();
|
||||||
|
d->setBackgroundColor(color);
|
||||||
|
emit backgroundColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
#ifndef DECLARATIVEWEBVIEW_H
|
||||||
|
#define DECLARATIVEWEBVIEW_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QBasicTimer>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
|
|
||||||
|
#include <QtQml>
|
||||||
|
#include <QQuickPaintedItem>
|
||||||
|
|
||||||
|
#include "websettings.h"
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class AmneziaWebViewSettings;
|
||||||
|
class AmneziaWebViewPrivate;
|
||||||
|
class AmneziaWebViewAttached;
|
||||||
|
class AmneziaWebHistory;
|
||||||
|
|
||||||
|
class AmneziaWebView : public QQuickPaintedItem
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_ENUMS(Status SelectionMode)
|
||||||
|
|
||||||
|
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||||
|
Q_PROPERTY(QPixmap icon READ icon NOTIFY iconChanged)
|
||||||
|
Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
|
||||||
|
Q_PROPERTY(QString html READ html WRITE setHtml NOTIFY htmlChanged)
|
||||||
|
Q_PROPERTY(int pressGrabTime READ pressGrabTime WRITE setPressGrabTime NOTIFY pressGrabTimeChanged)
|
||||||
|
Q_PROPERTY(qreal preferredWidth READ preferredWidth WRITE setPreferredWidth NOTIFY preferredWidthChanged)
|
||||||
|
Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged)
|
||||||
|
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
|
||||||
|
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
|
||||||
|
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef QT_NO_ACTION
|
||||||
|
Q_PROPERTY(QAction* reload READ reloadAction CONSTANT)
|
||||||
|
Q_PROPERTY(QAction* back READ backAction CONSTANT)
|
||||||
|
Q_PROPERTY(QAction* forward READ forwardAction CONSTANT)
|
||||||
|
Q_PROPERTY(QAction* stop READ stopAction CONSTANT)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Q_PROPERTY(AmneziaWebViewSettings* settings READ settingsObject CONSTANT)
|
||||||
|
Q_PROPERTY(QQmlListProperty<QObject> javaScriptWindowObjects READ javaScriptWindowObjects CONSTANT)
|
||||||
|
Q_PROPERTY(QQmlComponent* newWindowComponent READ newWindowComponent WRITE setNewWindowComponent NOTIFY newWindowComponentChanged)
|
||||||
|
Q_PROPERTY(QQuickItem* newWindowParent READ newWindowParent WRITE setNewWindowParent NOTIFY newWindowParentChanged)
|
||||||
|
Q_PROPERTY(QSize contentsSize READ contentsSize NOTIFY contentsSizeChanged)
|
||||||
|
Q_PROPERTY(qreal contentsScale READ contentsScale WRITE setContentsScale NOTIFY contentsScaleChanged)
|
||||||
|
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum WebAction {
|
||||||
|
NoWebAction = - 1,
|
||||||
|
|
||||||
|
OpenLink,
|
||||||
|
|
||||||
|
OpenLinkInNewWindow,
|
||||||
|
OpenFrameInNewWindow,
|
||||||
|
|
||||||
|
DownloadLinkToDisk,
|
||||||
|
CopyLinkToClipboard,
|
||||||
|
|
||||||
|
OpenImageInNewWindow,
|
||||||
|
DownloadImageToDisk,
|
||||||
|
CopyImageToClipboard,
|
||||||
|
|
||||||
|
Back,
|
||||||
|
Forward,
|
||||||
|
Stop,
|
||||||
|
Reload,
|
||||||
|
|
||||||
|
Cut,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
MoveToNextChar,
|
||||||
|
MoveToPreviousChar,
|
||||||
|
MoveToNextWord,
|
||||||
|
MoveToPreviousWord,
|
||||||
|
MoveToNextLine,
|
||||||
|
MoveToPreviousLine,
|
||||||
|
MoveToStartOfLine,
|
||||||
|
MoveToEndOfLine,
|
||||||
|
MoveToStartOfBlock,
|
||||||
|
MoveToEndOfBlock,
|
||||||
|
MoveToStartOfDocument,
|
||||||
|
MoveToEndOfDocument,
|
||||||
|
SelectNextChar,
|
||||||
|
SelectPreviousChar,
|
||||||
|
SelectNextWord,
|
||||||
|
SelectPreviousWord,
|
||||||
|
SelectNextLine,
|
||||||
|
SelectPreviousLine,
|
||||||
|
SelectStartOfLine,
|
||||||
|
SelectEndOfLine,
|
||||||
|
SelectStartOfBlock,
|
||||||
|
SelectEndOfBlock,
|
||||||
|
SelectStartOfDocument,
|
||||||
|
SelectEndOfDocument,
|
||||||
|
DeleteStartOfWord,
|
||||||
|
DeleteEndOfWord,
|
||||||
|
|
||||||
|
SetTextDirectionDefault,
|
||||||
|
SetTextDirectionLeftToRight,
|
||||||
|
SetTextDirectionRightToLeft,
|
||||||
|
|
||||||
|
ToggleBold,
|
||||||
|
ToggleItalic,
|
||||||
|
ToggleUnderline,
|
||||||
|
|
||||||
|
InspectElement,
|
||||||
|
|
||||||
|
InsertParagraphSeparator,
|
||||||
|
InsertLineSeparator,
|
||||||
|
|
||||||
|
SelectAll,
|
||||||
|
ReloadAndBypassCache,
|
||||||
|
|
||||||
|
PasteAndMatchStyle,
|
||||||
|
RemoveFormat,
|
||||||
|
|
||||||
|
ToggleStrikethrough,
|
||||||
|
ToggleSubscript,
|
||||||
|
ToggleSuperscript,
|
||||||
|
InsertUnorderedList,
|
||||||
|
InsertOrderedList,
|
||||||
|
Indent,
|
||||||
|
Outdent,
|
||||||
|
|
||||||
|
AlignCenter,
|
||||||
|
AlignJustified,
|
||||||
|
AlignLeft,
|
||||||
|
AlignRight,
|
||||||
|
|
||||||
|
StopScheduledPageRefresh,
|
||||||
|
|
||||||
|
CopyImageUrlToClipboard,
|
||||||
|
|
||||||
|
WebActionCount
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
explicit AmneziaWebView(QQuickItem *parent = nullptr);
|
||||||
|
virtual ~AmneziaWebView();
|
||||||
|
|
||||||
|
QUrl url() const;
|
||||||
|
void setUrl(const QUrl &);
|
||||||
|
|
||||||
|
QString title() const;
|
||||||
|
|
||||||
|
QPixmap icon() const;
|
||||||
|
|
||||||
|
int pressGrabTime() const;
|
||||||
|
void setPressGrabTime(int);
|
||||||
|
|
||||||
|
qreal preferredWidth() const;
|
||||||
|
void setPreferredWidth(qreal);
|
||||||
|
qreal preferredHeight() const;
|
||||||
|
void setPreferredHeight(qreal);
|
||||||
|
|
||||||
|
enum Status { Null, Ready, Loading, Error };
|
||||||
|
Status status() const;
|
||||||
|
qreal progress() const;
|
||||||
|
QString statusText() const;
|
||||||
|
|
||||||
|
#ifndef QT_NO_ACTION
|
||||||
|
QAction *reloadAction() const;
|
||||||
|
QAction *backAction() const;
|
||||||
|
QAction *forwardAction() const;
|
||||||
|
QAction *stopAction() const;
|
||||||
|
QAction* action(AmneziaWebView::WebAction) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void load(const QNetworkRequest &request, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
|
||||||
|
const QByteArray &body = QByteArray());
|
||||||
|
|
||||||
|
QString html() const;
|
||||||
|
|
||||||
|
void setHtml(const QString &html, const QUrl &baseUrl = QUrl());
|
||||||
|
void setContent(const QByteArray &data, const QString &mimeType = QString(), const QUrl &baseUrl = QUrl());
|
||||||
|
|
||||||
|
AmneziaWebHistory* history() const;
|
||||||
|
|
||||||
|
QQmlListProperty<QObject> javaScriptWindowObjects();
|
||||||
|
AmneziaWebViewSettings* settingsObject() const;
|
||||||
|
static AmneziaWebViewAttached* qmlAttachedProperties(QObject*);
|
||||||
|
|
||||||
|
QQmlComponent *newWindowComponent() const;
|
||||||
|
void setNewWindowComponent(QQmlComponent *newWindow);
|
||||||
|
QQuickItem* newWindowParent() const;
|
||||||
|
void setNewWindowParent(QQuickItem* newWindow);
|
||||||
|
|
||||||
|
bool isComponentCompletePublic() const { return isComponentComplete(); }
|
||||||
|
|
||||||
|
QSize contentsSize() const;
|
||||||
|
|
||||||
|
void setContentsScale(qreal scale);
|
||||||
|
qreal contentsScale() const;
|
||||||
|
|
||||||
|
QColor backgroundColor() const;
|
||||||
|
void setBackgroundColor(const QColor&);
|
||||||
|
|
||||||
|
void paint(QPainter *painter) override;
|
||||||
|
|
||||||
|
void setDefaultFontSize(int size);
|
||||||
|
void setStandardFontFamily(const QString &family);
|
||||||
|
Q_INVOKABLE void setTextZoom(int percent);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
|
||||||
|
void preferredWidthChanged();
|
||||||
|
void preferredHeightChanged();
|
||||||
|
|
||||||
|
void urlChanged();
|
||||||
|
void progressChanged();
|
||||||
|
void statusChanged(Status);
|
||||||
|
void titleChanged(const QString&);
|
||||||
|
void iconChanged();
|
||||||
|
void statusTextChanged();
|
||||||
|
void htmlChanged();
|
||||||
|
void pressGrabTimeChanged();
|
||||||
|
void newWindowComponentChanged();
|
||||||
|
void newWindowParentChanged();
|
||||||
|
void renderingEnabledChanged();
|
||||||
|
void contentsSizeChanged(const QSize&);
|
||||||
|
void contentsScaleChanged();
|
||||||
|
void backgroundColorChanged();
|
||||||
|
|
||||||
|
void loadStarted();
|
||||||
|
void loadFinished();
|
||||||
|
void loadFinished(bool ok);
|
||||||
|
void loadFailed();
|
||||||
|
|
||||||
|
void doubleClick(int clickX, int clickY);
|
||||||
|
void zoomTo(qreal zoom, int centerX, int centerY);
|
||||||
|
void alert(const QString& message);
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void evaluateJavaScript(const QString&);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void afterRendering();
|
||||||
|
void updateGeometry();
|
||||||
|
void windowWasChanged(QQuickWindow* window);
|
||||||
|
void parentWasChanged();
|
||||||
|
void fillColorWasChanged();
|
||||||
|
|
||||||
|
void doLoadStarted();
|
||||||
|
void doLoadProgress(int p);
|
||||||
|
void doLoadFinished(bool ok);
|
||||||
|
void setStatusText(const QString&);
|
||||||
|
void windowObjectCleared();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void itemChange(ItemChange, const ItemChangeData &) override;
|
||||||
|
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||||
|
QScopedPointer<AmneziaWebViewPrivate> d_ptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateContentsSize() {}
|
||||||
|
void init();
|
||||||
|
void componentComplete() override;
|
||||||
|
QTimer upadeTimer;
|
||||||
|
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(AmneziaWebView)
|
||||||
|
Q_DECLARE_PRIVATE(AmneziaWebView)
|
||||||
|
|
||||||
|
friend class QDeclarativeWebPage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AmneziaWebViewAttached : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString windowObjectName READ windowObjectName WRITE setWindowObjectName)
|
||||||
|
public:
|
||||||
|
explicit AmneziaWebViewAttached(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString windowObjectName() const
|
||||||
|
{
|
||||||
|
return m_windowObjectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWindowObjectName(const QString &n)
|
||||||
|
{
|
||||||
|
m_windowObjectName = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_windowObjectName;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
QML_DECLARE_TYPE(AmneziaWebView)
|
||||||
|
QML_DECLARE_TYPEINFO(AmneziaWebView, QML_HAS_ATTACHED_PROPERTIES)
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
#include <QtCore/qglobal.h>
|
||||||
|
#include <QtCore>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <android/bitmap.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#include "filehandler.h"
|
||||||
|
|
||||||
|
#include <QJniObject>
|
||||||
|
#include <QJniEnvironment>
|
||||||
|
|
||||||
|
namespace Jni
|
||||||
|
{
|
||||||
|
|
||||||
|
using Object = QJniObject;
|
||||||
|
}
|
||||||
|
static const char qtAndroidWebViewControllerClass[] = "org/amnezia/vpn/WebViewController";
|
||||||
|
|
||||||
|
class AndroidWebViewPrivate;
|
||||||
|
|
||||||
|
typedef QMap<quintptr, AndroidWebViewPrivate *> WebViews;
|
||||||
|
Q_GLOBAL_STATIC(WebViews, g_webViews)
|
||||||
|
Q_GLOBAL_STATIC(QMutex, g_webMutex)
|
||||||
|
|
||||||
|
class AndroidWebViewPrivate : public AmneziaWebViewPrivate
|
||||||
|
{
|
||||||
|
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit AndroidWebViewPrivate(AmneziaWebView* q);
|
||||||
|
virtual ~AndroidWebViewPrivate();
|
||||||
|
|
||||||
|
static AndroidWebViewPrivate *get(AmneziaWebView *q)
|
||||||
|
{
|
||||||
|
return static_cast<AndroidWebViewPrivate*>(AmneziaWebViewPrivate::get(q));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setWindowParent(QWindow *parent);
|
||||||
|
virtual void setBackgroundColor(const QColor backgroundColor);
|
||||||
|
virtual void show();
|
||||||
|
virtual void hide();
|
||||||
|
virtual void setGeometry(const QRect &);
|
||||||
|
virtual QString innerHTML() const;
|
||||||
|
virtual void load(const QUrl& url);
|
||||||
|
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl());
|
||||||
|
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl);
|
||||||
|
virtual void evaluateJavaScript(const QString& scriptSource);
|
||||||
|
virtual bool isLoading() const;
|
||||||
|
virtual bool canGoBack() const;
|
||||||
|
virtual bool canGoForward() const;
|
||||||
|
virtual void back();
|
||||||
|
virtual void forward();
|
||||||
|
virtual void reload();
|
||||||
|
virtual void stop();
|
||||||
|
virtual void setUrl(const QUrl &url);
|
||||||
|
virtual QIcon icon() const;
|
||||||
|
virtual void setScale(qreal scale);
|
||||||
|
virtual qreal scale() const;
|
||||||
|
virtual QSize contentsSize() const;
|
||||||
|
virtual void setDefaultFontSize(int size);
|
||||||
|
virtual void setStandardFontFamily(const QString &family);
|
||||||
|
virtual void setTextZoom(int percent);
|
||||||
|
private:
|
||||||
|
|
||||||
|
quintptr viewId;
|
||||||
|
Jni::Object m_viewController;
|
||||||
|
};
|
||||||
|
|
||||||
|
AndroidWebViewPrivate::AndroidWebViewPrivate(AmneziaWebView* q): AmneziaWebViewPrivate(q),
|
||||||
|
viewId(reinterpret_cast<quintptr>(this))
|
||||||
|
{
|
||||||
|
m_viewController = Jni::Object(qtAndroidWebViewControllerClass,
|
||||||
|
"(Landroid/app/Activity;J)V",
|
||||||
|
QNativeInterface::QAndroidApplication::context().object(),
|
||||||
|
viewId);
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
g_webViews->insert(viewId, this);
|
||||||
|
setBackgroundColor(backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidWebViewPrivate::~AndroidWebViewPrivate()
|
||||||
|
{
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
m_viewController.callMethod<void>("release", "()V");
|
||||||
|
g_webViews->take(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate *AmneziaWebViewPrivate::create(AmneziaWebView *q)
|
||||||
|
{
|
||||||
|
return new AndroidWebViewPrivate(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setWindowParent(QWindow *parent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setBackgroundColor(const QColor backgroundColor)
|
||||||
|
{
|
||||||
|
m_viewController.callMethod<void>("setBackgroundColor", "(I)V",
|
||||||
|
jint(backgroundColor.rgb()));
|
||||||
|
emit backgroundColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AndroidWebViewPrivate::isLoading() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deprecated
|
||||||
|
void AndroidWebViewPrivate::setScale(qreal scale)
|
||||||
|
{
|
||||||
|
Q_UNUSED(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deprecated
|
||||||
|
qreal AndroidWebViewPrivate::scale() const
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon AndroidWebViewPrivate::icon() const
|
||||||
|
{
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize AndroidWebViewPrivate::contentsSize() const
|
||||||
|
{
|
||||||
|
return QSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setGeometry(const QRect &geometry)
|
||||||
|
{
|
||||||
|
if (this->geometry != geometry) {
|
||||||
|
this->geometry = geometry;
|
||||||
|
|
||||||
|
m_viewController.callMethod<void>("setGeometry", "(IIII)V",
|
||||||
|
jint(geometry.x()), jint(geometry.y()),
|
||||||
|
jint(geometry.width()), jint(geometry.height()) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setTextZoom(int percent)
|
||||||
|
{
|
||||||
|
m_viewController.callMethod<void>("setTextZoom", "(I)V", jint(percent));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::hide()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (visible) {
|
||||||
|
m_viewController.callMethod<void>("hide", "()V");
|
||||||
|
visible = false;
|
||||||
|
q->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::show()
|
||||||
|
{
|
||||||
|
if (!visible) {
|
||||||
|
m_viewController.callMethod<void>("show", "()V");
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
Q_UNUSED(data);
|
||||||
|
Q_UNUSED(mimeType);
|
||||||
|
Q_UNUSED(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setUrl(const QUrl &url)
|
||||||
|
{
|
||||||
|
AmneziaWebViewPrivate::setUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::load(const QUrl &url)
|
||||||
|
{
|
||||||
|
// Make WebView visible before loading
|
||||||
|
if (!visible) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
Jni::Object jurl = Jni::Object::fromString(url.isValid() ? url.toString() : QString("about:blank"));
|
||||||
|
m_viewController.callMethod<void>("loadUrl", "(Ljava/lang/String;)V", jurl.object<jstring>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setHtml(const QString &html, const QUrl &baseUrl)
|
||||||
|
{
|
||||||
|
if (html.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Jni::Object url = Jni::Object::fromString(baseUrl.isValid() ? baseUrl.toString() : QString("about:blank"));
|
||||||
|
Jni::Object data = Jni::Object::fromString(html);
|
||||||
|
Jni::Object mime = Jni::Object::fromString(QString("text/html"));
|
||||||
|
Jni::Object encoding = Jni::Object::fromString(QString("utf-8"));
|
||||||
|
|
||||||
|
m_viewController.callMethod<void>("loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||||
|
url.object<jstring>(), data.object<jstring>(), mime.object<jstring>(), encoding.object<jstring>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::evaluateJavaScript(const QString &scriptSource)
|
||||||
|
{
|
||||||
|
Jni::Object script = Jni::Object::fromString(scriptSource);
|
||||||
|
m_viewController.callMethod<void>("evaluateJavaScript", "(Ljava/lang/String;)V", script.object<jstring>());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidWebViewPrivate::canGoBack() const
|
||||||
|
{
|
||||||
|
jboolean can = m_viewController.callMethod<jboolean>("canGoBack", "()Z");
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidWebViewPrivate::canGoForward() const
|
||||||
|
{
|
||||||
|
jboolean can = m_viewController.callMethod<jboolean>("canGoForward", "()Z");
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::back()
|
||||||
|
{
|
||||||
|
m_viewController.callMethod<void>("goBack", "()V");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::forward()
|
||||||
|
{
|
||||||
|
m_viewController.callMethod<void>("goForward", "()V");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::reload()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::stop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AndroidWebViewPrivate::innerHTML() const
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setDefaultFontSize(int size)
|
||||||
|
{
|
||||||
|
m_viewController.callMethod<void>("setDefaultFontSize", "(I)V", jint(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebViewPrivate::setStandardFontFamily(const QString &family)
|
||||||
|
{
|
||||||
|
Jni::Object fontFamily = Jni::Object::fromString(family);
|
||||||
|
m_viewController.callMethod<void>("setStandardFontFamily", "(Ljava/lang/String;)V", fontFamily.object<jstring>());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
JNIEXPORT void Java_org_amnezia_vpn_WebViewController_pageStarted(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||||
|
{
|
||||||
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(obj);
|
||||||
|
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||||
|
if (view) {
|
||||||
|
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||||
|
const QUrl url = QUrl(QString(urlChars));
|
||||||
|
QMetaObject::invokeMethod(view, "onPageStarted", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void Java_org_amnezia_vpn_WebViewController_pageFinished(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||||
|
{
|
||||||
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(obj);
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||||
|
if (view) {
|
||||||
|
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||||
|
const QUrl url = QUrl(QString(urlChars));
|
||||||
|
QMetaObject::invokeMethod(view, "onPageFinished", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void Java_org_amnezia_vpn_WebViewController_urlChanged(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||||
|
{
|
||||||
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(obj);
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||||
|
if (view) {
|
||||||
|
|
||||||
|
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||||
|
const QUrl url = QUrl(QString(urlChars));
|
||||||
|
QMetaObject::invokeMethod(view, "onUrlChanged", Qt::QueuedConnection, Q_ARG( const QUrl, url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jbyteArray Java_org_amnezia_vpn_WebViewController_dataForUrl(JNIEnv *env, jobject obj, jlong viewId, jstring url, jobject mimeType, jobject encoding)
|
||||||
|
{
|
||||||
|
Q_UNUSED(env)
|
||||||
|
Q_UNUSED(obj)
|
||||||
|
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||||
|
if (view) {
|
||||||
|
|
||||||
|
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||||
|
const QUrl url = QUrl(QString(urlChars));
|
||||||
|
|
||||||
|
QByteArray buffer = view->dataForUrl(url);
|
||||||
|
QString mime = view->mimeTypeForUrl(url);
|
||||||
|
QString enc("utf-8");
|
||||||
|
|
||||||
|
jstring jMimeType = env->NewStringUTF(mime.toUtf8().constData());
|
||||||
|
Jni::Object jMimeTypeObject(mimeType);
|
||||||
|
if (jMimeTypeObject.isValid()) {
|
||||||
|
jMimeTypeObject.callObjectMethod("insert", "(ILjava/lang/String;)Ljava/lang/StringBuilder;", 0, jMimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring jEncoding = env->NewStringUTF(enc.toUtf8().constData());
|
||||||
|
Jni::Object jEncodingObject(encoding);
|
||||||
|
if (jEncodingObject.isValid()) {
|
||||||
|
|
||||||
|
jEncodingObject.callObjectMethod("insert", "(ILjava/lang/String;)Ljava/lang/StringBuilder;", 0, jEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
env->DeleteLocalRef(jEncoding);
|
||||||
|
env->DeleteLocalRef(jMimeType);
|
||||||
|
|
||||||
|
jbyteArray data = env->NewByteArray(buffer.size());
|
||||||
|
env->SetByteArrayRegion(data, 0, buffer.size(), (const jbyte*) buffer.data());
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean Java_org_amnezia_vpn_WebViewController_canHandleUrl(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||||
|
|
||||||
|
{
|
||||||
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(obj);
|
||||||
|
QMutexLocker lock(g_webMutex());
|
||||||
|
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||||
|
if (view) {
|
||||||
|
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||||
|
const QUrl url = QUrl(QString(urlChars));
|
||||||
|
return view->canHandleUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jboolean(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
#include <QtCore>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
|
||||||
|
#include "amneziawebview_desktop_p.h"
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#include "filehandler.h"
|
||||||
|
|
||||||
|
typedef QMap<quintptr, AmneziaWebViewPrivate *> WebViews;
|
||||||
|
Q_GLOBAL_STATIC(WebViews, g_webViews)
|
||||||
|
|
||||||
|
|
||||||
|
QrcHandler::QrcHandler()
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
FileHandler::FileHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
JsHandler::JsHandler(AmneziaWebView *host): _host(host), scriptObjectsInjected(false)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsHandler::~JsHandler() {}
|
||||||
|
|
||||||
|
WebPage::WebPage(QObject *parent)
|
||||||
|
: QWebPage(parent)
|
||||||
|
{
|
||||||
|
connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||||
|
this, SLOT(handleUnsupportedContent(QNetworkReply*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebPage::javaScriptAlert(QWebFrame *frame, const QString& msg)
|
||||||
|
{
|
||||||
|
Q_UNUSED(frame)
|
||||||
|
Q_UNUSED(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPage::~WebPage()
|
||||||
|
{
|
||||||
|
disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type)
|
||||||
|
{
|
||||||
|
return QWebPage::acceptNavigationRequest(frame, request, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebPage::handleUnsupportedContent(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
QString errorString = reply->errorString();
|
||||||
|
|
||||||
|
if (m_loadingUrl != reply->url()) {
|
||||||
|
// sub resource of this page
|
||||||
|
qWarning() << "Resource" << reply->url().toEncoded() << "has unknown Content-Type, will be ignored.";
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError && !reply->header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||||
|
errorString = "Unknown Content-Type";
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(QLatin1String(":/notfound.html"));
|
||||||
|
bool isOpened = file.open(QIODevice::ReadOnly);
|
||||||
|
Q_ASSERT(isOpened);
|
||||||
|
Q_UNUSED(isOpened)
|
||||||
|
|
||||||
|
QString title = QCoreApplication::translate("webview", "Error loading page: %1").arg(reply->url().toString());
|
||||||
|
QString html = QString(QLatin1String(file.readAll()))
|
||||||
|
.arg(title)
|
||||||
|
.arg(errorString)
|
||||||
|
.arg(reply->url().toString());
|
||||||
|
|
||||||
|
QBuffer imageBuffer;
|
||||||
|
imageBuffer.open(QBuffer::ReadWrite);
|
||||||
|
QIcon icon = view()->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, view());
|
||||||
|
QPixmap pixmap = icon.pixmap(QSize(32,32));
|
||||||
|
if (pixmap.save(&imageBuffer, "PNG")) {
|
||||||
|
html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"),
|
||||||
|
QString(QLatin1String(imageBuffer.buffer().toBase64())));
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QWebFrame*> frames;
|
||||||
|
frames.append(mainFrame());
|
||||||
|
while (!frames.isEmpty()) {
|
||||||
|
QWebFrame *frame = frames.takeFirst();
|
||||||
|
if (frame->url() == reply->url()) {
|
||||||
|
frame->setHtml(html, reply->url());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QList<QWebFrame *> children = frame->childFrames();
|
||||||
|
foreach(QWebFrame *frame, children)
|
||||||
|
frames.append(frame);
|
||||||
|
}
|
||||||
|
if (m_loadingUrl == reply->url()) {
|
||||||
|
mainFrame()->setHtml(html, reply->url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopWebViewPrivate::DesktopWebViewPrivate(AmneziaWebView* q): AmneziaWebViewPrivate(q)
|
||||||
|
, viewId(reinterpret_cast<quintptr>(this))
|
||||||
|
, containerWindow(nullptr)
|
||||||
|
, window(nullptr)
|
||||||
|
{
|
||||||
|
container = new QWidget(0, Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Tool);
|
||||||
|
container->setAttribute(Qt::WA_NativeWindow, true);
|
||||||
|
container->setAttribute(Qt::WA_DontCreateNativeAncestors, true);
|
||||||
|
|
||||||
|
// Do not remove next line -> prevent some sort of spontaneous crashes
|
||||||
|
QWebSettings::setObjectCacheCapacities(0, 0, 0);
|
||||||
|
|
||||||
|
view = new QWebView(container);
|
||||||
|
WebPage *page = new WebPage(view);
|
||||||
|
page->setForwardUnsupportedContent(true);
|
||||||
|
page->setNetworkAccessManager(networkAccessManager());
|
||||||
|
page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
|
||||||
|
view->setPage(page);
|
||||||
|
|
||||||
|
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
container->setLayout(new QHBoxLayout(container));
|
||||||
|
container->layout()->setSpacing(0);
|
||||||
|
container->layout()->setMargin(0);
|
||||||
|
container->layout()->addWidget(view);
|
||||||
|
|
||||||
|
setBackgroundColor(backgroundColor);
|
||||||
|
g_webViews->insert(viewId, this);
|
||||||
|
|
||||||
|
connect(view, SIGNAL(loadFinished(bool)), this, SIGNAL(loadFinished(bool)));
|
||||||
|
|
||||||
|
connect(page, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinished(bool)), Qt::QueuedConnection);
|
||||||
|
connect(page, SIGNAL(loadStarted()), this, SLOT(onPageStarted()), Qt::QueuedConnection);
|
||||||
|
connect(view, SIGNAL(urlChanged(const QUrl &)), this, SLOT(onUrlChanged(const QUrl &)), Qt::QueuedConnection);
|
||||||
|
container->createWinId();
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopWebViewPrivate::~DesktopWebViewPrivate()
|
||||||
|
{
|
||||||
|
disconnect(this, SLOT(loadFinished(bool)));
|
||||||
|
disconnect(this, SLOT(applicationStateChanged(Qt::ApplicationState)));
|
||||||
|
disconnect(this, SLOT(onUrlChanged(const QUrl &)));
|
||||||
|
disconnect(this, SLOT(onPageStarted()));
|
||||||
|
disconnect(this, SLOT(onLoadFinished(bool)));
|
||||||
|
|
||||||
|
g_webViews->take(viewId);
|
||||||
|
view->stop();
|
||||||
|
view->setPage(nullptr);
|
||||||
|
|
||||||
|
container->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate *AmneziaWebViewPrivate::create(AmneziaWebView *q)
|
||||||
|
{
|
||||||
|
return new DesktopWebViewPrivate(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setWindowParent(QWindow *parent)
|
||||||
|
{
|
||||||
|
if (window) {
|
||||||
|
window->removeEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
|
||||||
|
containerWindow = qobject_cast<QWindow*>(container->windowHandle());
|
||||||
|
containerWindow->setTransientParent(parent);
|
||||||
|
parent->installEventFilter(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
window = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setBackgroundColor(const QColor backgroundColor)
|
||||||
|
{
|
||||||
|
this->backgroundColor = backgroundColor;
|
||||||
|
QPalette p = container->palette();
|
||||||
|
p.setColor(QPalette::Background, backgroundColor);
|
||||||
|
container->setPalette(p);
|
||||||
|
p = view->palette();
|
||||||
|
p.setColor(QPalette::Background, backgroundColor);
|
||||||
|
view->setPalette(p);
|
||||||
|
emit backgroundColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deprecated
|
||||||
|
void DesktopWebViewPrivate::setScale(qreal scale)
|
||||||
|
{
|
||||||
|
Q_UNUSED(scale)
|
||||||
|
//qreal s = view->geometry().width() / view->page()->preferredContentsSize().width();
|
||||||
|
//view->setZoomFactor(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deprecated
|
||||||
|
qreal DesktopWebViewPrivate::scale() const
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon DesktopWebViewPrivate::icon() const
|
||||||
|
{
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize DesktopWebViewPrivate::contentsSize() const
|
||||||
|
{
|
||||||
|
return QSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::takeSnapshot()
|
||||||
|
{
|
||||||
|
if (geometry.isEmpty() || !containerWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
container->updateGeometry();
|
||||||
|
QPixmap pixmap = container->grab();
|
||||||
|
snapshot = pixmap.toImage();
|
||||||
|
emit snapshotChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setGeometry(const QRect &geometry)
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
QQuickWindow *window = q->window();
|
||||||
|
if (!window) return;
|
||||||
|
QRect newGeometry = QRect(window->mapToGlobal(geometry.topLeft()), QSize(geometry.width(), geometry.height()));
|
||||||
|
if (newGeometry.isValid() && container->geometry() != newGeometry ) {
|
||||||
|
|
||||||
|
this->geometry = geometry;
|
||||||
|
container->setGeometry(newGeometry);
|
||||||
|
container->updateGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(obj)
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
|
||||||
|
switch (event->type()) {
|
||||||
|
case QEvent::Move: {
|
||||||
|
QMoveEvent *moveEvent = static_cast<QMoveEvent*>(event);
|
||||||
|
QPoint p = q->mapToScene(QPointF(moveEvent->pos())).toPoint();
|
||||||
|
container->move(p);
|
||||||
|
//container->updateGeometry();
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case QEvent::Resize: {
|
||||||
|
|
||||||
|
//QRect newGeometry = QRect(window->mapToGlobal(geometry.topLeft()), QSize(geometry.width(), geometry.height()));
|
||||||
|
//container->setGeometry(newGeometry);
|
||||||
|
//container->updateGeometry();
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case QEvent::WindowStateChange: {
|
||||||
|
|
||||||
|
Qt::WindowState state = window->windowState();
|
||||||
|
if ((state == Qt::WindowMaximized) || (state == Qt::WindowFullScreen) || (state == Qt::WindowActive)) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::hide()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (!q->window()) return;
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "requestSnapshot", Qt::QueuedConnection);
|
||||||
|
QMetaObject::invokeMethod(container, "hide", Qt::QueuedConnection);
|
||||||
|
visible = false;
|
||||||
|
|
||||||
|
//if (q->isVisible())
|
||||||
|
// q->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::show()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (!q->window()) return;
|
||||||
|
if (!visible) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(container, "show", Qt::QueuedConnection);
|
||||||
|
QMetaObject::invokeMethod(container, "update", Qt::QueuedConnection);
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((qApp->topLevelWindows().at(0) != containerWindow) || !containerWindow->isVisible()) {
|
||||||
|
|
||||||
|
containerWindow->raise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::load(const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
QUrl url = baseUrl;
|
||||||
|
if (!url.isValid()) {
|
||||||
|
url = QUrl(QLatin1String("about:blank"));
|
||||||
|
}
|
||||||
|
view->load(url);
|
||||||
|
history()->append(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
QUrl url = baseUrl;
|
||||||
|
if (!url.isValid()) {
|
||||||
|
url = QUrl(QLatin1String("about:blank"));
|
||||||
|
}
|
||||||
|
view->setContent(data, mimeType, url);
|
||||||
|
history()->append(url, data, mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setHtml(const QString& html, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
if(html.isNull()) return;
|
||||||
|
QUrl url = baseUrl;
|
||||||
|
if (!baseUrl.isValid())
|
||||||
|
url = QUrl(QLatin1String("about:blank"));
|
||||||
|
|
||||||
|
view->setHtml(html, url);
|
||||||
|
history()->append(url, html.toUtf8(), "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::evaluateJavaScript(const QString& scriptSource)
|
||||||
|
{
|
||||||
|
view->page()->mainFrame()->evaluateJavaScript(scriptSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::canGoBack() const
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebPage::Back);
|
||||||
|
bool can = (pageAction && pageAction->isEnabled());
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::canGoForward() const
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebPage::Forward);
|
||||||
|
bool can = (pageAction && pageAction->isEnabled());
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::back()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebPage::Back);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::forward()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebPage::Forward);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::reload()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebPage::Reload);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::stop()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebPage::Stop);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::isLoading() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DesktopWebViewPrivate::innerHTML() const
|
||||||
|
{
|
||||||
|
QVariant result = view->page()->mainFrame()->evaluateJavaScript("document.body.innerHTML");
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::onLoadFinished(bool success)
|
||||||
|
{
|
||||||
|
if (success) {
|
||||||
|
QMetaObject::invokeMethod(this, "onPageFinished", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QMetaObject::invokeMethod(this, "onPageError", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setDefaultFontSize(int size)
|
||||||
|
{
|
||||||
|
view->settings()->setFontSize(QWebSettings::DefaultFontSize, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setStandardFontFamily(const QString &family)
|
||||||
|
{
|
||||||
|
view->settings()->setFontFamily(QWebSettings::StandardFont, family);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setTextZoom(int percent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(percent)
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#ifndef AMNEZIAWEBVIEW_DESKTOP_P_H
|
||||||
|
#define AMNEZIAWEBVIEW_DESKTOP_P_H
|
||||||
|
|
||||||
|
// QtWebKit is deprecated and not available in Qt 6
|
||||||
|
// This file should only be used with Qt 5 when WebEngineWidgets is not available
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
#error "amneziawebview_desktop_p.h uses QtWebKit which is not available in Qt 6. Use amneziawebview_webengine_p.h instead."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QtWebKitWidgets/QWebView>
|
||||||
|
#include <QtWebKitWidgets/QWebPage>
|
||||||
|
#include <QtWebKitWidgets/QWebFrame>
|
||||||
|
#include <QtWebKit/QWebSettings>
|
||||||
|
|
||||||
|
#include "amneziawebview.h"
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
|
||||||
|
class DesktopWebViewPrivate;
|
||||||
|
|
||||||
|
class WebPage : public QWebPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void loadingUrl(const QUrl &url);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WebPage(QObject *parent = nullptr);
|
||||||
|
virtual ~WebPage();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type);
|
||||||
|
virtual void javaScriptAlert(QWebFrame *frame, const QString& msg);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleUnsupportedContent(QNetworkReply *reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class DesktopWebViewPrivate;
|
||||||
|
QUrl m_loadingUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DesktopWebViewPrivate : public AmneziaWebViewPrivate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit DesktopWebViewPrivate(AmneziaWebView* q);
|
||||||
|
virtual ~DesktopWebViewPrivate();
|
||||||
|
|
||||||
|
virtual void setWindowParent(QWindow *parent);
|
||||||
|
|
||||||
|
virtual void setBackgroundColor(const QColor backgroundColor);
|
||||||
|
virtual void show();
|
||||||
|
virtual void hide();
|
||||||
|
virtual void takeSnapshot();
|
||||||
|
virtual void setGeometry(const QRect &);
|
||||||
|
virtual QString innerHTML() const;
|
||||||
|
virtual void load(const QUrl& url);
|
||||||
|
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl());
|
||||||
|
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl);
|
||||||
|
virtual void evaluateJavaScript(const QString& scriptSource);
|
||||||
|
virtual bool isLoading() const;
|
||||||
|
virtual bool canGoBack() const;
|
||||||
|
virtual bool canGoForward() const;
|
||||||
|
virtual void back();
|
||||||
|
virtual void forward();
|
||||||
|
virtual void reload();
|
||||||
|
virtual void stop();
|
||||||
|
|
||||||
|
virtual QIcon icon() const;
|
||||||
|
virtual void setScale(qreal scale);
|
||||||
|
virtual qreal scale() const;
|
||||||
|
virtual QSize contentsSize() const;
|
||||||
|
virtual void setDefaultFontSize(int size);
|
||||||
|
virtual void setTextZoom(int percent) { Q_UNUSED(percent); }
|
||||||
|
virtual void setStandardFontFamily(const QString &family);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onLoadFinished(bool);
|
||||||
|
|
||||||
|
private:
|
||||||
|
quintptr viewId;
|
||||||
|
QWebView *view;
|
||||||
|
QWidget *container;
|
||||||
|
QWindow *containerWindow;
|
||||||
|
QWindow *window;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // AMNEZIAWEBVIEW_DESKTOP_P_H
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,423 @@
|
|||||||
|
#include "amneziawebview_p.h"
|
||||||
|
#include "amneziawebhistory.h"
|
||||||
|
#include "websettings.h"
|
||||||
|
|
||||||
|
NetworkAccessManager::NetworkAccessManager(QNetworkAccessManager *manager, QObject *parent)
|
||||||
|
: QNetworkAccessManager(parent)
|
||||||
|
{
|
||||||
|
this->manager = manager;
|
||||||
|
setCache(manager->cache());
|
||||||
|
setCookieJar(manager->cookieJar());
|
||||||
|
setProxy(manager->proxy());
|
||||||
|
setProxyFactory(manager->proxyFactory());
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkAccessManager::init()
|
||||||
|
{
|
||||||
|
connect(this, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this,
|
||||||
|
SLOT(handleSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *NetworkAccessManager::createRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, QIODevice *device)
|
||||||
|
{
|
||||||
|
AmneziaWebView *view = qobject_cast<AmneziaWebView *>(parent());
|
||||||
|
AmneziaWebViewPrivate *d = AmneziaWebViewPrivate::get(view);
|
||||||
|
|
||||||
|
if (!d || !d->canHandleUrl(request.url())) {
|
||||||
|
return QNetworkAccessManager::createRequest(operation, request, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation == GetOperation) {
|
||||||
|
return new DataReply(this, operation, request, view);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
|
||||||
|
return QNetworkAccessManager::createRequest(operation, request, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkAccessManager::handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors)
|
||||||
|
{
|
||||||
|
Q_UNUSED(errors)
|
||||||
|
reply->ignoreSslErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataReply::DataReply(QObject *parent, const QNetworkAccessManager::Operation operation, const QNetworkRequest &request, AmneziaWebView *view): QNetworkReply(parent)
|
||||||
|
{
|
||||||
|
setRequest(request);
|
||||||
|
setUrl(request.url());
|
||||||
|
setOperation(operation);
|
||||||
|
setFinished(true);
|
||||||
|
this->view = view;
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
//QUrl url = request.url();
|
||||||
|
//url.setHost(QString());
|
||||||
|
//if (url.path().isEmpty())
|
||||||
|
// url.setPath(QLatin1String("/"));
|
||||||
|
//setUrl(url);
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "setContent", Qt::QueuedConnection );
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataReply::setContent()
|
||||||
|
{
|
||||||
|
if (!view || view.isNull()) return;
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate *q = AmneziaWebViewPrivate::get(view);
|
||||||
|
content = q->dataForUrl(url());
|
||||||
|
QString mimeType = q->mimeTypeForUrl(url()).toLower();
|
||||||
|
//open(ReadOnly | Unbuffered);
|
||||||
|
QNetworkReply::open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
int size = content.size();
|
||||||
|
if (size <= 0 ) {
|
||||||
|
|
||||||
|
QString msg = QString("Error opening %1").arg(url().toString());
|
||||||
|
qCritical() << msg;
|
||||||
|
setError(QNetworkReply::ContentNotFoundError, msg);
|
||||||
|
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
||||||
|
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QString("%1; charset=%2").arg(mimeType, "utf-8")));
|
||||||
|
|
||||||
|
setHeader(QNetworkRequest::ContentLengthHeader, size);
|
||||||
|
QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
|
||||||
|
QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
|
||||||
|
Q_ARG(qint64, size), Q_ARG(qint64, size));
|
||||||
|
QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
|
||||||
|
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataReply::abort()
|
||||||
|
{
|
||||||
|
QNetworkReply::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataReply::close()
|
||||||
|
{
|
||||||
|
QNetworkReply::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 DataReply::size() const
|
||||||
|
{
|
||||||
|
return content.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataReply::isSequential() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 DataReply::bytesAvailable() const
|
||||||
|
{
|
||||||
|
return content.size() - offset + QIODevice::bytesAvailable();
|
||||||
|
}
|
||||||
|
qint64 DataReply::readData(char *data, qint64 maxSize)
|
||||||
|
{
|
||||||
|
if (offset < content.size()) {
|
||||||
|
qint64 number = qMin(maxSize, content.size() - offset);
|
||||||
|
memcpy(data, content.constData() + offset, number);
|
||||||
|
offset += number;
|
||||||
|
return number;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate::AmneziaWebViewPrivate(AmneziaWebView *q) : QObject(q)
|
||||||
|
, pending(PendingNone)
|
||||||
|
, status(AmneziaWebView::Null)
|
||||||
|
, preferredwidth(0)
|
||||||
|
, preferredheight(0)
|
||||||
|
, progress(1.0)
|
||||||
|
, newWindowComponent(nullptr)
|
||||||
|
, newWindowParent(nullptr)
|
||||||
|
, jsHandler(q)
|
||||||
|
, rendering(true)
|
||||||
|
, overlapped(false)
|
||||||
|
, backgroundColor(QColor::fromRgb(28, 29, 33))
|
||||||
|
, visible(false)
|
||||||
|
, networkManager(nullptr)
|
||||||
|
, q_ptr(q)
|
||||||
|
, m_history(new AmneziaWebHistory(q))
|
||||||
|
, m_settings(new AmneziaWebViewSettings(q))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::init()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
m_settings->apply();
|
||||||
|
|
||||||
|
actions.setMapping(new QAction(q), (int)AmneziaWebView::Back);
|
||||||
|
actions.setMapping(new QAction(q), (int)AmneziaWebView::Forward);
|
||||||
|
actions.setMapping(new QAction(q), (int)AmneziaWebView::Stop);
|
||||||
|
actions.setMapping(new QAction(q), (int)AmneziaWebView::Reload);
|
||||||
|
connect(action(AmneziaWebView::Back), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||||
|
connect(action(AmneziaWebView::Forward), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||||
|
connect(action(AmneziaWebView::Forward), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||||
|
connect(action(AmneziaWebView::Forward), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||||
|
connect(&actions, SIGNAL(mappedInt(int)), this, SLOT(onAction(int)));
|
||||||
|
connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(applicationStateChanged(Qt::ApplicationState)));
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate::~AmneziaWebViewPrivate()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
disconnect(this, SLOT(onAction(int)));
|
||||||
|
disconnect(&actions, SLOT(map()));
|
||||||
|
if (networkManager && networkManager->parent() == q)
|
||||||
|
delete networkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate *AmneziaWebViewPrivate::get(AmneziaWebView *q)
|
||||||
|
{
|
||||||
|
if (!q) { return nullptr; }
|
||||||
|
return q->d_func();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::applicationStateChanged(Qt::ApplicationState state)
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if ((state == Qt::ApplicationActive) && q->isVisible()) {
|
||||||
|
emit q->update();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::requestShow()
|
||||||
|
{
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::requestHide()
|
||||||
|
{
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::move(const QPoint &point)
|
||||||
|
{
|
||||||
|
QRect newGeomentry = geometry;
|
||||||
|
newGeomentry.moveTo(point);
|
||||||
|
setGeometry(newGeomentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::windowObjectsClear(QQmlListProperty<QObject>* prop)
|
||||||
|
{
|
||||||
|
static_cast<AmneziaWebViewPrivate*>(prop->data)->windowObjects.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject *AmneziaWebViewPrivate::windowObjectsAt(QQmlListProperty<QObject> *prop, qsizetype index)
|
||||||
|
{
|
||||||
|
return static_cast<AmneziaWebViewPrivate *>(prop->data)->windowObjects.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype AmneziaWebViewPrivate::windowObjectsCount(QQmlListProperty<QObject> *prop)
|
||||||
|
{
|
||||||
|
return static_cast<AmneziaWebViewPrivate *>(prop->data)->windowObjects.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::windowObjectsAppend(QQmlListProperty<QObject>* prop, QObject* o)
|
||||||
|
{
|
||||||
|
static_cast<AmneziaWebViewPrivate*>(prop->data)->windowObjects.append(o);
|
||||||
|
static_cast<AmneziaWebViewPrivate*>(prop->data)->updateWindowObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::setTitle(const QString &title)
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (this->title != title) {
|
||||||
|
this->title = title;
|
||||||
|
emit q->titleChanged(this->title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray AmneziaWebViewPrivate::dataForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
QByteArray data;
|
||||||
|
if (qrcHandler.canHandleUrl(url)) {
|
||||||
|
data = qrcHandler.dataForUrl(url);
|
||||||
|
}
|
||||||
|
else if (jsHandler.canHandleUrl(url)) {
|
||||||
|
|
||||||
|
data = jsHandler.dataForUrl(url);
|
||||||
|
}
|
||||||
|
else if (fileHandler.canHandleUrl(url)) {
|
||||||
|
|
||||||
|
data = fileHandler.dataForUrl(url);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AmneziaWebViewPrivate::mimeTypeForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
QString mimeType("application/octet-stream");
|
||||||
|
if (qrcHandler.canHandleUrl(url)) {
|
||||||
|
mimeType = qrcHandler.mimeTypeForUrl(url);
|
||||||
|
}
|
||||||
|
else if (jsHandler.canHandleUrl(url)) {
|
||||||
|
mimeType = jsHandler.mimeTypeForUrl(url);
|
||||||
|
}
|
||||||
|
else if (fileHandler.canHandleUrl(url)) {
|
||||||
|
mimeType = fileHandler.mimeTypeForUrl(url);
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::onPageStarted()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
emit q->loadStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::onPageFinished()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
jsHandler.updateWebView();
|
||||||
|
|
||||||
|
if (lastError.length() > 0) {
|
||||||
|
emit q->loadFinished(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit q->loadFinished(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::onPageError()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::onUrlChanged(const QUrl &url)
|
||||||
|
{
|
||||||
|
history()->append(url);
|
||||||
|
setUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::setUrl(const QUrl &url)
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
|
||||||
|
QString urlString = url.toString();
|
||||||
|
while ( urlString.endsWith('#')) urlString.chop(1);
|
||||||
|
QUrl newUrl(urlString);
|
||||||
|
|
||||||
|
if (newUrl == QUrl(QLatin1String("about:blank")) ) {
|
||||||
|
newUrl = QUrl("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->url != newUrl) {
|
||||||
|
|
||||||
|
this->url = newUrl;
|
||||||
|
emit q->urlChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::addToJavaScriptWindowObject(const QString& name, QObject* object)
|
||||||
|
{
|
||||||
|
jsHandler.addToJavaScriptWindowObject(name, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AmneziaWebViewPrivate::canHandleUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
bool can = (jsHandler.canHandleUrl(url) || qrcHandler.canHandleUrl(url) || fileHandler.canHandleUrl(url));
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AmneziaWebViewPrivate::toHtml() const
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::load(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& body)
|
||||||
|
{
|
||||||
|
Q_UNUSED(request)
|
||||||
|
Q_UNUSED(operation)
|
||||||
|
Q_UNUSED(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkAccessManager* AmneziaWebViewPrivate::networkAccessManager()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
|
||||||
|
if (!networkManager)
|
||||||
|
networkManager = new NetworkAccessManager(q);
|
||||||
|
return networkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::setNetworkAccessManager(QNetworkAccessManager* manager)
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (manager == networkManager)
|
||||||
|
return;
|
||||||
|
if (networkManager && networkManager->parent() == q)
|
||||||
|
delete networkManager;
|
||||||
|
|
||||||
|
NetworkAccessManager *newManager = qobject_cast<NetworkAccessManager *>(manager);
|
||||||
|
if (!newManager && manager) {
|
||||||
|
newManager = new NetworkAccessManager(manager, q);
|
||||||
|
}
|
||||||
|
networkManager = newManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *AmneziaWebViewPrivate::action(AmneziaWebView::WebAction action) const
|
||||||
|
{
|
||||||
|
QAction *ret = qobject_cast<QAction*>(actions.mapping((int)action));
|
||||||
|
if (ret) {
|
||||||
|
switch (action) {
|
||||||
|
case AmneziaWebView::Back: {
|
||||||
|
bool can = canGoBack() || history()->canGoBack();
|
||||||
|
ret->setEnabled(can);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AmneziaWebView::Forward: {
|
||||||
|
bool can = canGoForward() || history()->canGoForward();
|
||||||
|
ret->setEnabled(can);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaWebViewPrivate::onAction(int action)
|
||||||
|
{
|
||||||
|
switch (action) {
|
||||||
|
case AmneziaWebView::Back: {
|
||||||
|
if (canGoBack()) {
|
||||||
|
back();
|
||||||
|
}
|
||||||
|
//else {
|
||||||
|
// history()->back();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AmneziaWebView::Forward:
|
||||||
|
if (canGoForward()) forward();
|
||||||
|
break;
|
||||||
|
case AmneziaWebView::Stop:
|
||||||
|
stop();
|
||||||
|
break;
|
||||||
|
case AmneziaWebView::Reload:
|
||||||
|
reload();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebHistory* AmneziaWebViewPrivate::history() const
|
||||||
|
{
|
||||||
|
return m_history.data();
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
#ifndef WEBVIEW_P_H
|
||||||
|
#define WEBVIEW_P_H
|
||||||
|
|
||||||
|
#include "amneziawebview.h"
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#include "jshandler.h"
|
||||||
|
#include "filehandler.h"
|
||||||
|
|
||||||
|
#include "amneziawebhistory.h"
|
||||||
|
|
||||||
|
class WebSettings;
|
||||||
|
class QIcon;
|
||||||
|
class QSize;
|
||||||
|
|
||||||
|
|
||||||
|
class WebViewSettings;
|
||||||
|
class WebSettings;
|
||||||
|
|
||||||
|
class AmneziaWebViewPrivate : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AmneziaWebViewPrivate(AmneziaWebView *q);
|
||||||
|
virtual ~AmneziaWebViewPrivate();
|
||||||
|
static AmneziaWebViewPrivate *create(AmneziaWebView *q);
|
||||||
|
void init();
|
||||||
|
|
||||||
|
virtual void setWindowParent(QWindow *parent) = 0;
|
||||||
|
virtual void setBackgroundColor(const QColor backgroundColor) = 0;
|
||||||
|
|
||||||
|
virtual void setGeometry(const QRect &) = 0;
|
||||||
|
virtual QString innerHTML() const = 0;
|
||||||
|
virtual void load(const QUrl& url) = 0;
|
||||||
|
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl()) = 0;
|
||||||
|
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl) = 0;
|
||||||
|
virtual void evaluateJavaScript(const QString& scriptSource) = 0;
|
||||||
|
virtual bool isLoading() const = 0;
|
||||||
|
virtual bool canGoBack() const = 0;
|
||||||
|
virtual bool canGoForward() const = 0;
|
||||||
|
virtual void back() = 0;
|
||||||
|
virtual void forward() = 0;
|
||||||
|
virtual void reload() = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
virtual QIcon icon() const = 0;
|
||||||
|
virtual void setScale(qreal scale) = 0;
|
||||||
|
virtual qreal scale() const = 0;
|
||||||
|
virtual QSize contentsSize() const = 0;
|
||||||
|
virtual void show() = 0;
|
||||||
|
virtual void hide() = 0;
|
||||||
|
virtual void setDefaultFontSize(int size) = 0;
|
||||||
|
virtual void setStandardFontFamily(const QString &family) = 0;
|
||||||
|
virtual void setTextZoom(int percent) = 0;
|
||||||
|
|
||||||
|
virtual void setUrl(const QUrl &url);
|
||||||
|
|
||||||
|
static AmneziaWebViewPrivate *get(AmneziaWebView *q);
|
||||||
|
|
||||||
|
enum { PendingNone, PendingUrl, PendingHtml, PendingContent } pending;
|
||||||
|
|
||||||
|
QString toHtml() const;
|
||||||
|
AmneziaWebHistory* history() const;
|
||||||
|
|
||||||
|
QByteArray dataForUrl(const QUrl &url) const;
|
||||||
|
QString mimeTypeForUrl(const QUrl &url) const;
|
||||||
|
bool canHandleUrl(const QUrl &url) const;
|
||||||
|
|
||||||
|
static void windowObjectsClear(QQmlListProperty<QObject> *prop);
|
||||||
|
static QObject *windowObjectsAt(QQmlListProperty<QObject> *prop, qsizetype index);
|
||||||
|
static qsizetype windowObjectsCount(QQmlListProperty<QObject> *prop);
|
||||||
|
static void windowObjectsAppend(QQmlListProperty<QObject> *prop, QObject *o);
|
||||||
|
|
||||||
|
void updateWindowObjects();
|
||||||
|
QObjectList windowObjects;
|
||||||
|
|
||||||
|
QNetworkAccessManager* networkAccessManager();
|
||||||
|
void setNetworkAccessManager(QNetworkAccessManager* manager);
|
||||||
|
void load(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& body);
|
||||||
|
QAction *action(AmneziaWebView::WebAction) const;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void setTitle(const QString &title);
|
||||||
|
void move(const QPoint &);
|
||||||
|
void requestHide();
|
||||||
|
void requestShow();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void loadStarted();
|
||||||
|
void loadFinished(bool ok);
|
||||||
|
void loadProgress(int progress);
|
||||||
|
void titleChanged(const QString& title);
|
||||||
|
void urlChanged(const QUrl& url);
|
||||||
|
void backgroundColorChanged();
|
||||||
|
public:
|
||||||
|
|
||||||
|
QUrl url;
|
||||||
|
AmneziaWebView::Status status;
|
||||||
|
qreal preferredwidth, preferredheight;
|
||||||
|
qreal progress;
|
||||||
|
QString statusText;
|
||||||
|
QUrl pendingUrl;
|
||||||
|
QString pendingString;
|
||||||
|
QByteArray pendingData;
|
||||||
|
|
||||||
|
QQmlComponent* newWindowComponent;
|
||||||
|
QQuickItem* newWindowParent;
|
||||||
|
|
||||||
|
QrcHandler qrcHandler;
|
||||||
|
JsHandler jsHandler;
|
||||||
|
FileHandler fileHandler;
|
||||||
|
|
||||||
|
bool rendering;
|
||||||
|
bool overlapped;
|
||||||
|
|
||||||
|
QColor backgroundColor;
|
||||||
|
QUrl baseUrl;
|
||||||
|
QString title;
|
||||||
|
QRect geometry;
|
||||||
|
QString lastError;
|
||||||
|
mutable bool visible;
|
||||||
|
|
||||||
|
protected Q_SLOTS:
|
||||||
|
void applicationStateChanged(Qt::ApplicationState state);
|
||||||
|
void onPageStarted();
|
||||||
|
void onPageFinished();
|
||||||
|
void onPageError();
|
||||||
|
void onUrlChanged(const QUrl &url);
|
||||||
|
void onAction(int);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void addToJavaScriptWindowObject(const QString& name, QObject* object);
|
||||||
|
|
||||||
|
QMutex renderMutex;
|
||||||
|
QNetworkAccessManager *networkManager;
|
||||||
|
AmneziaWebView *q_ptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSignalMapper actions;
|
||||||
|
QScopedPointer<AmneziaWebHistory> m_history;
|
||||||
|
QScopedPointer<AmneziaWebViewSettings> m_settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
//!internal
|
||||||
|
class NetworkAccessManager : public QNetworkAccessManager
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit NetworkAccessManager(QNetworkAccessManager *manager, QObject *parent);
|
||||||
|
explicit NetworkAccessManager(QObject *parent): QNetworkAccessManager(parent) { init(); }
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData = nullptr);
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
QNetworkAccessManager *manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DataReply : public QNetworkReply
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DataReply(QObject *parent, const QNetworkAccessManager::Operation operation, const QNetworkRequest &request, AmneziaWebView *view = nullptr);
|
||||||
|
virtual ~DataReply() = default;
|
||||||
|
|
||||||
|
virtual void abort();
|
||||||
|
virtual void close();
|
||||||
|
virtual qint64 size() const;
|
||||||
|
|
||||||
|
virtual qint64 bytesAvailable() const;
|
||||||
|
virtual bool isSequential() const;
|
||||||
|
protected:
|
||||||
|
virtual qint64 readData(char *data, qint64 maxSize);
|
||||||
|
Q_INVOKABLE void setContent();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QByteArray content;
|
||||||
|
qint64 offset;
|
||||||
|
QPointer<AmneziaWebView> view;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,494 @@
|
|||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QWebEngineUrlScheme>
|
||||||
|
|
||||||
|
#include "amneziawebview_webengine_p.h"
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#include "filehandler.h"
|
||||||
|
#include "amneziawebhistory.h"
|
||||||
|
|
||||||
|
typedef QMap<quintptr, AmneziaWebViewPrivate *> WebViews;
|
||||||
|
Q_GLOBAL_STATIC_WITH_ARGS(WebViews, g_webViews, ())
|
||||||
|
|
||||||
|
QrcHandler::QrcHandler()
|
||||||
|
{}
|
||||||
|
|
||||||
|
FileHandler::FileHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
JsHandler::JsHandler(AmneziaWebView *host): _host(host), scriptObjectsInjected(false)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsHandler::~JsHandler() {}
|
||||||
|
|
||||||
|
WebPage::WebPage(QObject *parent)
|
||||||
|
: QWebEnginePage(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPage::WebPage(QWebEngineProfile *profile, QObject *parent)
|
||||||
|
: QWebEnginePage(profile, parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPage::~WebPage()
|
||||||
|
{
|
||||||
|
disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebPage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
|
||||||
|
{
|
||||||
|
Q_UNUSED(type);
|
||||||
|
// Always accept navigation requests to open links within WebView
|
||||||
|
// This prevents opening links in external browser
|
||||||
|
if (isMainFrame) {
|
||||||
|
m_loadingUrl = url;
|
||||||
|
emit loadingUrl(url);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWebEnginePage *WebPage::createWindow(QWebEnginePage::WebWindowType type)
|
||||||
|
{
|
||||||
|
Q_UNUSED(type);
|
||||||
|
// Return this page instead of creating a new window
|
||||||
|
// This prevents opening new browser windows/tabs
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebPage::handleUnsupportedContent(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
QString errorString = reply->errorString();
|
||||||
|
|
||||||
|
if (m_loadingUrl != reply->url()) {
|
||||||
|
// sub resource of this page
|
||||||
|
qWarning() << "Resource" << reply->url().toEncoded() << "has unknown Content-Type, will be ignored.";
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError && !reply->header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||||
|
errorString = "Unknown Content-Type";
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(QLatin1String(":/notfound.html"));
|
||||||
|
bool isOpened = file.open(QIODevice::ReadOnly);
|
||||||
|
Q_ASSERT(isOpened);
|
||||||
|
Q_UNUSED(isOpened)
|
||||||
|
|
||||||
|
QString title = QCoreApplication::translate("webview", "Error loading page: %1").arg(reply->url().toString());
|
||||||
|
QString html = QString(QLatin1String(file.readAll()))
|
||||||
|
.arg(title)
|
||||||
|
.arg(errorString)
|
||||||
|
.arg(reply->url().toString());
|
||||||
|
|
||||||
|
QBuffer imageBuffer;
|
||||||
|
imageBuffer.open(QBuffer::ReadWrite);
|
||||||
|
QIcon icon = qApp->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0);
|
||||||
|
QPixmap pixmap = icon.pixmap(QSize(32,32));
|
||||||
|
if (pixmap.save(&imageBuffer, "PNG")) {
|
||||||
|
html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"),
|
||||||
|
QString(QLatin1String(imageBuffer.buffer().toBase64())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_loadingUrl == reply->url()) {
|
||||||
|
setHtml(html, reply->url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString &LocalSchemeHandler::scheme()
|
||||||
|
{
|
||||||
|
static const QString localScheme("local");
|
||||||
|
return localScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QMimeDatabase &LocalSchemeHandler::mimeDatabase()
|
||||||
|
{
|
||||||
|
static const QMimeDatabase mimeDatabase;
|
||||||
|
return mimeDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalSchemeHandler::requestStarted(QWebEngineUrlRequestJob *job)
|
||||||
|
{
|
||||||
|
const QByteArray requestMethod = job->requestMethod();
|
||||||
|
const QUrl requestUrl = job->requestUrl();
|
||||||
|
QString requestScheme = requestUrl.scheme();
|
||||||
|
DesktopWebViewPrivate *d = qobject_cast<DesktopWebViewPrivate *>(parent());
|
||||||
|
|
||||||
|
if (!d) {
|
||||||
|
job->fail(QWebEngineUrlRequestJob::RequestFailed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestScheme != LocalSchemeHandler::scheme()) {
|
||||||
|
job->fail(QWebEngineUrlRequestJob::UrlInvalid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QBuffer *buffer = nullptr;
|
||||||
|
QMimeType mimeType = mimeDatabase().mimeTypeForFile(requestUrl.fileName(), QMimeDatabase::MatchExtension);
|
||||||
|
|
||||||
|
if (d->fileHandler.canHandleUrl(requestUrl)) {
|
||||||
|
const QByteArray content = d->fileHandler.dataForUrl(requestUrl);
|
||||||
|
if (!content.isNull()) {
|
||||||
|
buffer = new QBuffer();
|
||||||
|
buffer->setData(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!buffer) {
|
||||||
|
|
||||||
|
auto historyItems = d->history()->items();
|
||||||
|
const auto it = std::find_if(historyItems.begin(), historyItems.end(),
|
||||||
|
[requestUrl](const auto &item) {
|
||||||
|
bool urlsMatch = item.url().matches(requestUrl, QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||||
|
bool hasData = (item.data().length() > 0);
|
||||||
|
return urlsMatch && hasData; });
|
||||||
|
|
||||||
|
if (it != historyItems.end()) {
|
||||||
|
buffer = new QBuffer();
|
||||||
|
buffer->setData(it->data());
|
||||||
|
mimeType = mimeDatabase().mimeTypeForName(it->mimeType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
job->fail(QWebEngineUrlRequestJob::UrlNotFound);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(job, &QObject::destroyed, buffer, &QObject::deleteLater);
|
||||||
|
job->reply(mimeType.name().toLocal8Bit(), buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopWebViewPrivate::DesktopWebViewPrivate(AmneziaWebView* q): AmneziaWebViewPrivate(q)
|
||||||
|
, viewId(reinterpret_cast<quintptr>(this))
|
||||||
|
, containerWindow(0)
|
||||||
|
, window(0)
|
||||||
|
{
|
||||||
|
m_localHandler = new LocalSchemeHandler(this);
|
||||||
|
|
||||||
|
container = new QWidget(0, Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Tool);
|
||||||
|
container->setAttribute(Qt::WA_NativeWindow, true);
|
||||||
|
container->setAttribute(Qt::WA_DontCreateNativeAncestors, true);
|
||||||
|
|
||||||
|
QWebEngineUrlScheme localScheme(LocalSchemeHandler::scheme().toUtf8());
|
||||||
|
localScheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed |
|
||||||
|
QWebEngineUrlScheme::SecureScheme |
|
||||||
|
QWebEngineUrlScheme::ViewSourceAllowed |
|
||||||
|
QWebEngineUrlScheme::ContentSecurityPolicyIgnored |
|
||||||
|
QWebEngineUrlScheme::CorsEnabled |
|
||||||
|
QWebEngineUrlScheme::FetchApiAllowed);
|
||||||
|
QWebEngineUrlScheme::registerScheme(localScheme);
|
||||||
|
|
||||||
|
QWebEngineUrlScheme qrcScheme("qrc");
|
||||||
|
qrcScheme.setFlags(QWebEngineUrlScheme::LocalScheme |
|
||||||
|
QWebEngineUrlScheme::LocalAccessAllowed |
|
||||||
|
QWebEngineUrlScheme::SecureScheme |
|
||||||
|
QWebEngineUrlScheme::ContentSecurityPolicyIgnored |
|
||||||
|
QWebEngineUrlScheme::CorsEnabled |
|
||||||
|
QWebEngineUrlScheme::FetchApiAllowed);
|
||||||
|
QWebEngineUrlScheme::registerScheme(qrcScheme);
|
||||||
|
|
||||||
|
view = new QWebEngineView(container);
|
||||||
|
QWebEngineProfile *profile = new QWebEngineProfile(view);
|
||||||
|
profile->installUrlSchemeHandler(LocalSchemeHandler::scheme().toUtf8(), m_localHandler);
|
||||||
|
|
||||||
|
m_page = new WebPage(profile, profile);
|
||||||
|
m_page->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);
|
||||||
|
connect(m_page, &QWebEnginePage::fileSystemAccessRequested, this, [](QWebEngineFileSystemAccessRequest request) {
|
||||||
|
request.accept();
|
||||||
|
});
|
||||||
|
|
||||||
|
view->settings()->setUnknownUrlSchemePolicy(QWebEngineSettings::AllowAllUnknownUrlSchemes);
|
||||||
|
view->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
|
||||||
|
view->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
|
||||||
|
view->settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
|
||||||
|
view->settings()->setAttribute(QWebEngineSettings::AllowGeolocationOnInsecureOrigins, true);
|
||||||
|
|
||||||
|
view->setPage(m_page);
|
||||||
|
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
container->setLayout(new QHBoxLayout(container));
|
||||||
|
container->layout()->setSpacing(0);
|
||||||
|
container->layout()->setContentsMargins(0, 0, 0, 0);
|
||||||
|
container->layout()->addWidget(view);
|
||||||
|
|
||||||
|
setBackgroundColor(backgroundColor);
|
||||||
|
g_webViews->insert(viewId, this);
|
||||||
|
|
||||||
|
connect(view, SIGNAL(loadFinished(bool)), this, SIGNAL(loadFinished(bool)));
|
||||||
|
connect(m_page, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinished(bool)), Qt::QueuedConnection);
|
||||||
|
connect(m_page, SIGNAL(loadStarted()), this, SLOT(onPageStarted()), Qt::QueuedConnection);
|
||||||
|
connect(view, SIGNAL(urlChanged(const QUrl &)), this, SLOT(onUrlChanged(const QUrl &)), Qt::QueuedConnection);
|
||||||
|
container->createWinId();
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopWebViewPrivate::~DesktopWebViewPrivate()
|
||||||
|
{
|
||||||
|
g_webViews->take(viewId);
|
||||||
|
disconnect(this, SLOT(loadFinished(bool)));
|
||||||
|
disconnect(this, SLOT(applicationStateChanged(Qt::ApplicationState)));
|
||||||
|
disconnect(this, SLOT(onUrlChanged(const QUrl &)));
|
||||||
|
disconnect(this, SLOT(onPageStarted()));
|
||||||
|
disconnect(this, SLOT(onLoadFinished(bool)));
|
||||||
|
|
||||||
|
delete container;
|
||||||
|
}
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate *AmneziaWebViewPrivate::create(AmneziaWebView *q)
|
||||||
|
{
|
||||||
|
return new DesktopWebViewPrivate(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setWindowParent(QWindow *parent)
|
||||||
|
{
|
||||||
|
if (window) {
|
||||||
|
window->removeEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
|
||||||
|
containerWindow = qobject_cast<QWindow*>(container->windowHandle());
|
||||||
|
containerWindow->setTransientParent(parent);
|
||||||
|
parent->installEventFilter(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
window = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setBackgroundColor(const QColor backgroundColor)
|
||||||
|
{
|
||||||
|
this->backgroundColor = backgroundColor;
|
||||||
|
QPalette p = container->palette();
|
||||||
|
p.setColor(QPalette::Window, backgroundColor);
|
||||||
|
container->setPalette(p);
|
||||||
|
p = view->palette();
|
||||||
|
p.setColor(QPalette::Window, backgroundColor);
|
||||||
|
view->setPalette(p);
|
||||||
|
emit backgroundColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deprecated
|
||||||
|
void DesktopWebViewPrivate::setScale(qreal scale)
|
||||||
|
{
|
||||||
|
Q_UNUSED(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deprecated
|
||||||
|
qreal DesktopWebViewPrivate::scale() const
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon DesktopWebViewPrivate::icon() const
|
||||||
|
{
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize DesktopWebViewPrivate::contentsSize() const
|
||||||
|
{
|
||||||
|
return QSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setGeometry(const QRect &geometry)
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
QQuickWindow *window = q->window();
|
||||||
|
if (!window) return;
|
||||||
|
QRect newGeometry = QRect(window->mapToGlobal(geometry.topLeft()), QSize(geometry.width(), geometry.height()));
|
||||||
|
if (newGeometry.isValid() && container->geometry() != newGeometry ) {
|
||||||
|
|
||||||
|
this->geometry = geometry;
|
||||||
|
container->setGeometry(newGeometry);
|
||||||
|
container->updateGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(obj);
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
|
||||||
|
switch (event->type()) {
|
||||||
|
case QEvent::Move: {
|
||||||
|
QMoveEvent *moveEvent = static_cast<QMoveEvent*>(event);
|
||||||
|
QPoint p = q->mapToScene(QPointF(moveEvent->pos())).toPoint();
|
||||||
|
container->move(p);
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QEvent::Resize: {
|
||||||
|
if (visible) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QEvent::WindowStateChange: {
|
||||||
|
|
||||||
|
Qt::WindowState state = window->windowState();
|
||||||
|
if ((state == Qt::WindowMaximized) || (state == Qt::WindowFullScreen) || (state == Qt::WindowActive)) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::hide()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (!q->window()) return;
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
QMetaObject::invokeMethod(container, "hide", Qt::QueuedConnection);
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::show()
|
||||||
|
{
|
||||||
|
Q_Q(AmneziaWebView);
|
||||||
|
if (!q->window()) return;
|
||||||
|
if (!visible) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(container, "show", Qt::QueuedConnection);
|
||||||
|
QMetaObject::invokeMethod(container, "update", Qt::QueuedConnection);
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
containerWindow = qobject_cast<QWindow*>(container->windowHandle());
|
||||||
|
|
||||||
|
if ((containerWindow != nullptr) &&
|
||||||
|
((qApp->topLevelWindows().at(0) != containerWindow) || !containerWindow->isVisible())) {
|
||||||
|
|
||||||
|
containerWindow->raise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::load(const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
QUrl url = baseUrl;
|
||||||
|
if (!url.isValid()) {
|
||||||
|
url = QUrl(QLatin1String("about:blank"));
|
||||||
|
}
|
||||||
|
view->load(url);
|
||||||
|
history()->append(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
QUrl url = baseUrl;
|
||||||
|
if (!url.isValid()) {
|
||||||
|
url = QUrl(QLatin1String("about:blank"));
|
||||||
|
}
|
||||||
|
view->setContent(data, mimeType, url);
|
||||||
|
history()->append(url, data, mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setHtml(const QString& html, const QUrl& baseUrl)
|
||||||
|
{
|
||||||
|
if(html.isNull()) return;
|
||||||
|
QUrl url = baseUrl;
|
||||||
|
if (!baseUrl.isValid())
|
||||||
|
url = QUrl(QLatin1String("about:blank"));
|
||||||
|
|
||||||
|
view->setHtml(html, url);
|
||||||
|
history()->append(url, html.toUtf8(), "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::evaluateJavaScript(const QString& scriptSource)
|
||||||
|
{
|
||||||
|
view->page()->runJavaScript(scriptSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::canGoBack() const
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebEnginePage::Back);
|
||||||
|
bool can = (pageAction && pageAction->isEnabled());
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::canGoForward() const
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebEnginePage::Forward);
|
||||||
|
bool can = (pageAction && pageAction->isEnabled());
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::back()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebEnginePage::Back);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::forward()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebEnginePage::Forward);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::reload()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebEnginePage::Reload);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::stop()
|
||||||
|
{
|
||||||
|
QAction *pageAction = view->pageAction(QWebEnginePage::Stop);
|
||||||
|
if (pageAction)
|
||||||
|
emit pageAction->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DesktopWebViewPrivate::isLoading() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DesktopWebViewPrivate::innerHTML() const
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::onLoadFinished(bool success)
|
||||||
|
{
|
||||||
|
if (success) {
|
||||||
|
QMetaObject::invokeMethod(this, "onPageFinished", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QMetaObject::invokeMethod(this, "onPageError", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setDefaultFontSize(int size)
|
||||||
|
{
|
||||||
|
view->settings()->setFontSize(QWebEngineSettings::DefaultFontSize, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setStandardFontFamily(const QString &family)
|
||||||
|
{
|
||||||
|
view->settings()->setFontFamily(QWebEngineSettings::StandardFont, family);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopWebViewPrivate::setTextZoom(int percent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(percent);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#ifndef AMNEZIAWEBVIEW_WEBENGINE_P_H
|
||||||
|
#define AMNEZIAWEBVIEW_WEBENGINE_P_H
|
||||||
|
|
||||||
|
#include <QtWebEngineWidgets/QtWebEngineWidgets>
|
||||||
|
|
||||||
|
#include <QtWebEngineWidgets>
|
||||||
|
#include <QWebEngineView>
|
||||||
|
#include <QWebEnginePage>
|
||||||
|
#include <QWebEngineUrlSchemeHandler>
|
||||||
|
#include <QWebEngineUrlRequestJob>
|
||||||
|
|
||||||
|
#include "amneziawebview.h"
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
|
||||||
|
class DesktopWebViewPrivate;
|
||||||
|
class LocalSchemeHandler;
|
||||||
|
|
||||||
|
class WebPage : public QWebEnginePage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void loadingUrl(const QUrl &url);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WebPage(QObject *parent = 0);
|
||||||
|
explicit WebPage(QWebEngineProfile *profile, QObject *parent = 0);
|
||||||
|
virtual ~WebPage();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) override;
|
||||||
|
QWebEnginePage *createWindow(QWebEnginePage::WebWindowType type) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleUnsupportedContent(QNetworkReply *reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class DesktopWebViewPrivate;
|
||||||
|
QUrl m_loadingUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DesktopWebViewPrivate : public AmneziaWebViewPrivate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit DesktopWebViewPrivate(AmneziaWebView* q);
|
||||||
|
virtual ~DesktopWebViewPrivate();
|
||||||
|
|
||||||
|
virtual void setWindowParent(QWindow *parent);
|
||||||
|
|
||||||
|
virtual void setBackgroundColor(const QColor backgroundColor);
|
||||||
|
virtual void show();
|
||||||
|
virtual void hide();
|
||||||
|
virtual void setGeometry(const QRect &);
|
||||||
|
virtual QString innerHTML() const;
|
||||||
|
virtual void load(const QUrl& url);
|
||||||
|
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl());
|
||||||
|
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl);
|
||||||
|
virtual void evaluateJavaScript(const QString& scriptSource);
|
||||||
|
virtual bool isLoading() const;
|
||||||
|
virtual bool canGoBack() const;
|
||||||
|
virtual bool canGoForward() const;
|
||||||
|
virtual void back();
|
||||||
|
virtual void forward();
|
||||||
|
virtual void reload();
|
||||||
|
virtual void stop();
|
||||||
|
|
||||||
|
virtual QIcon icon() const;
|
||||||
|
virtual void setScale(qreal scale);
|
||||||
|
virtual qreal scale() const;
|
||||||
|
virtual QSize contentsSize() const;
|
||||||
|
virtual void setDefaultFontSize(int size);
|
||||||
|
virtual void setTextZoom(int percent);
|
||||||
|
virtual void setStandardFontFamily(const QString &family);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onLoadFinished(bool);
|
||||||
|
|
||||||
|
private:
|
||||||
|
quintptr viewId;
|
||||||
|
QWebEngineView *view;
|
||||||
|
QWidget *container;
|
||||||
|
QWindow *containerWindow;
|
||||||
|
QWindow *window;
|
||||||
|
WebPage *m_page;
|
||||||
|
LocalSchemeHandler *m_localHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalSchemeHandler : public QWebEngineUrlSchemeHandler
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
LocalSchemeHandler(QObject *parent) : QWebEngineUrlSchemeHandler(parent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QString &scheme();
|
||||||
|
static const QMimeDatabase &mimeDatabase();
|
||||||
|
void requestStarted(QWebEngineUrlRequestJob *job) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AMNEZIAWEBVIEW_WEBENGINE_P_H
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
|
||||||
|
#include "filehandler.h"
|
||||||
|
#include "mimecache.h"
|
||||||
|
|
||||||
|
QList<QString> FileHandler::schemes()
|
||||||
|
{
|
||||||
|
static QList<QString> list = QList<QString>() << "file" << "local";
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray FileHandler::dataForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
QUrl fileUrl = url;
|
||||||
|
if (fileUrl.scheme() != "file") {
|
||||||
|
fileUrl.setScheme("file");
|
||||||
|
}
|
||||||
|
QString requestUrl(fileUrl.toLocalFile());
|
||||||
|
QFile resource(requestUrl);
|
||||||
|
QByteArray buffer;
|
||||||
|
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||||
|
|
||||||
|
buffer = resource.readAll();
|
||||||
|
resource.close();
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileHandler::canHandleUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
if (schemes().contains(url.scheme().toLower()))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString FileHandler::mimeTypeForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
return mimeTypeForExtension(url.path().section('.', -1));
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef FILEHANDLER_H
|
||||||
|
#define FILEHANDLER_H
|
||||||
|
|
||||||
|
class QString;
|
||||||
|
class QByteArray;
|
||||||
|
class QUrl;
|
||||||
|
|
||||||
|
class FileHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FileHandler();
|
||||||
|
static QList<QString> schemes();
|
||||||
|
bool canHandleUrl(const QUrl &url) const;
|
||||||
|
QByteArray dataForUrl(const QUrl &url) const;
|
||||||
|
QString mimeTypeForUrl(const QUrl &url) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
|
||||||
|
#include "filehandler.h"
|
||||||
|
|
||||||
|
FileHandler::FileHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <MobileCoreServices/UTCoreTypes.h>
|
||||||
|
#import <MobileCoreServices/UTType.h>
|
||||||
|
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include "mimecache.h"
|
||||||
|
#include "filehandler.h"
|
||||||
|
#define kProtocolFileScheme @"file"
|
||||||
|
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
|
||||||
|
@interface FileProtocol : NSURLProtocol
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FileHandler::FileHandler()
|
||||||
|
{
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
static bool protocolRegistered = false;
|
||||||
|
if (!protocolRegistered) {
|
||||||
|
[NSURLProtocol registerClass:[FileProtocol class]];
|
||||||
|
protocolRegistered = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
|
||||||
|
@implementation FileProtocol
|
||||||
|
|
||||||
|
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
NSString *scheme = request.URL.scheme;
|
||||||
|
if ([kProtocolFileScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString*)headFromHtml:(NSString*)html
|
||||||
|
{
|
||||||
|
if (!html) return nil;
|
||||||
|
|
||||||
|
NSString *head = nil;
|
||||||
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=<head>)[\\w\\W.]*(?=</head>)" options:NSRegularExpressionCaseInsensitive error:nil];
|
||||||
|
NSTextCheckingResult *headResult = [regex firstMatchInString:html options:0 range:NSMakeRange(0, html.length)];
|
||||||
|
|
||||||
|
if (headResult && headResult.range.location != NSNotFound) {
|
||||||
|
head = [html substringWithRange:[headResult range]];
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)charsetFromHtml:(NSString *)html
|
||||||
|
{
|
||||||
|
if (!html) return nil;
|
||||||
|
|
||||||
|
NSString *charset = nil;
|
||||||
|
NSString *charsetPattern = @"((?<=charset=)\\s*[a-zA-Z0-9-]*)";
|
||||||
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:charsetPattern options: NSRegularExpressionCaseInsensitive error:nil];
|
||||||
|
NSTextCheckingResult *charsetResult = [regex firstMatchInString:html options:kNilOptions range:NSMakeRange(0, [html length])];
|
||||||
|
if (charsetResult && charsetResult.range.location != NSNotFound) {
|
||||||
|
charset = [[html substringWithRange:[charsetResult range]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
charset = [charset lowercaseString];
|
||||||
|
}
|
||||||
|
return charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startLoading
|
||||||
|
{
|
||||||
|
/* retrieve the current request. */
|
||||||
|
NSURLRequest *request = [self request];
|
||||||
|
NSString *url = [[request URL] path];
|
||||||
|
NSString *mimeType = mimeTypeForExtension(QString::fromNSString(url.pathExtension)).toNSString();
|
||||||
|
|
||||||
|
QString requestUrl(QString("%1").arg(QString::fromNSString(url)));
|
||||||
|
QFile resource(requestUrl);
|
||||||
|
|
||||||
|
NSData *pageData = nil;
|
||||||
|
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||||
|
|
||||||
|
QByteArray buffer = resource.readAll();
|
||||||
|
|
||||||
|
//pageData = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
pageData = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
resource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageData) {
|
||||||
|
|
||||||
|
NSString *encoding = @"utf-8";
|
||||||
|
if ([mimeType isEqualToString:@"text/html"]) {
|
||||||
|
|
||||||
|
NSString *content = [[NSString alloc] initWithBytesNoCopy: (char* )[pageData bytes] length:pageData.length encoding:NSISOLatin1StringEncoding freeWhenDone:NO];
|
||||||
|
|
||||||
|
NSString *charset = [FileProtocol charsetFromHtml:[FileProtocol headFromHtml:content]];
|
||||||
|
if (charset) {
|
||||||
|
encoding = charset;
|
||||||
|
}
|
||||||
|
[content autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLResponse *response =[[NSURLResponse alloc]initWithURL:self.request.URL
|
||||||
|
MIMEType:mimeType
|
||||||
|
expectedContentLength:[pageData length]
|
||||||
|
textEncodingName:encoding];
|
||||||
|
|
||||||
|
|
||||||
|
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||||
|
[[self client] URLProtocol:self didLoadData:pageData];
|
||||||
|
[[self client] URLProtocolDidFinishLoading:self];
|
||||||
|
[response autorelease];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stopLoading
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QVariant>
|
||||||
|
#include <QtCore/QMetaMethod>
|
||||||
|
#include <QtCore/QMetaObject>
|
||||||
|
#include <QtCore/QGenericArgument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
#include "mimecache.h"
|
||||||
|
#include "jshandler.h"
|
||||||
|
|
||||||
|
QString JsHandler::scheme() const
|
||||||
|
{
|
||||||
|
return QString("js");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JsHandler::host() const
|
||||||
|
{
|
||||||
|
qulonglong h = (qulonglong)(void*)_host;
|
||||||
|
return QString("js%1").arg(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JsHandler::scriptObjectsUrl() const
|
||||||
|
{
|
||||||
|
return QString("%1://%2/%3.js").arg(scheme()).arg(host()).arg(scriptObjectsId());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JsHandler::scriptObjectsId() const
|
||||||
|
{
|
||||||
|
return QString("__scriptObjects__");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JsHandler::scriptObjects() const
|
||||||
|
{
|
||||||
|
QString script;
|
||||||
|
QList<QString> scripts = scriptParts.values();
|
||||||
|
|
||||||
|
for ( int i = 0; i < scripts.count(); i++) {
|
||||||
|
script = QString("%1 %2").arg(script).arg(scripts.at(i)).trimmed();
|
||||||
|
}
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsHandler::updateWebView()
|
||||||
|
{
|
||||||
|
if (!scriptObjectsInjected) {
|
||||||
|
|
||||||
|
QString injector = QString(
|
||||||
|
" var script = document.getElementById(\"%1\"); "
|
||||||
|
" if(script === null) { "
|
||||||
|
" script = document.createElement('script'); "
|
||||||
|
" script.type = \"text/javascript\"; "
|
||||||
|
" script.id = \"%2\"; "
|
||||||
|
" document.getElementsByTagName('head')[0].appendChild(script); "
|
||||||
|
" } "
|
||||||
|
" script.text = \"%3\"; "
|
||||||
|
).arg(scriptObjectsId()).arg(scriptObjectsId()).arg(scriptObjects());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
QString injector = QString(
|
||||||
|
" var script = document.getElementById(\"%1\"); "
|
||||||
|
" if(script === null) { "
|
||||||
|
" script = document.createElement('script'); "
|
||||||
|
" script.type = \"text/javascript\"; "
|
||||||
|
" script.id = \"%2\"; "
|
||||||
|
" document.getElementsByTagName('head')[0].appendChild(script); "
|
||||||
|
" } "
|
||||||
|
" script.src = \"%3\"; "
|
||||||
|
).arg(scriptObjectsId()).arg(scriptObjectsId()).arg(scriptObjectsUrl());
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
//убрали, так как теперь есть рабочий вебинспектор
|
||||||
|
//_host->evaluateJavaScript(injector);
|
||||||
|
scriptObjectsInjected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsHandler::addToJavaScriptWindowObject(const QString& name, QObject* object)
|
||||||
|
{
|
||||||
|
windowObjects[name] = object;
|
||||||
|
QString script = windowScriptObject(name, object);
|
||||||
|
scriptParts[name] = script;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsHandler::canHandleUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
if (scheme() == url.scheme() && url.host() == host())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (this->host() == url.host() && (url.scheme() == QString("http")))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JsHandler::mimeTypeForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
if (scheme() == url.scheme() && url.host() == host())
|
||||||
|
return mimeTypeForUrl(url);
|
||||||
|
|
||||||
|
if (this->host() == url.host() && (url.scheme() == QString("http")))
|
||||||
|
return mimeTypeForExtension("json");
|
||||||
|
|
||||||
|
return QString("application/octet-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JsHandler::windowScriptObject(const QString& name, QObject* object) const
|
||||||
|
{
|
||||||
|
QString script = QString(" var %1 = {}; "
|
||||||
|
" %2.responseText = null; "
|
||||||
|
).arg(name).arg(name);
|
||||||
|
if (object) {
|
||||||
|
const QMetaObject *meta = object->metaObject();
|
||||||
|
const QString prefix = QString("%1.").arg(name);
|
||||||
|
|
||||||
|
int methodCount = meta->methodCount();
|
||||||
|
for (int i = 0; i < methodCount; i++) {
|
||||||
|
QMetaMethod metaMethod = meta->method(i);
|
||||||
|
|
||||||
|
if ((metaMethod.access() == QMetaMethod::Public) && ((metaMethod.methodType() == QMetaMethod::Slot) ||
|
||||||
|
(metaMethod.methodType() == QMetaMethod::Method)) ) {
|
||||||
|
const QString methodName = QString("%1%2").arg(prefix).arg(QString(metaMethod.name()));
|
||||||
|
|
||||||
|
//Parameters
|
||||||
|
QList<QByteArray> parametersNames = metaMethod.parameterNames();
|
||||||
|
QString methodParameters = QString("");
|
||||||
|
QString stringifyParameters = QString("");
|
||||||
|
|
||||||
|
for (int j = 0; j < parametersNames.count(); j++) {
|
||||||
|
methodParameters += QString(parametersNames.at(j));
|
||||||
|
|
||||||
|
stringifyParameters += QString("'%1=' + encodeURIComponent(%2)").arg(QString(parametersNames.at(j)))
|
||||||
|
.arg(QString(parametersNames.at(j)));
|
||||||
|
|
||||||
|
if(j < (parametersNames.count() -1)) {
|
||||||
|
methodParameters += QString(", ");
|
||||||
|
stringifyParameters += QString(" + '&' + ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stringifyParameters.length() > 0)
|
||||||
|
stringifyParameters = QString(" + '?'+ %1").arg(stringifyParameters);
|
||||||
|
|
||||||
|
const QString methodUrl = QString("http://%1/%2").arg(host()).arg(methodName);
|
||||||
|
//Body
|
||||||
|
const QString bodyTemplate = QString(""
|
||||||
|
" var obj = this; "
|
||||||
|
" if (obj.responseText !== null) { "
|
||||||
|
" var ret = eval( obj.responseText ); "
|
||||||
|
" obj.responseText = null; "
|
||||||
|
" return ret;"
|
||||||
|
" }; "
|
||||||
|
" var caller = arguments.callee.caller; "
|
||||||
|
" var callerArgs = caller.arguments; "
|
||||||
|
" var xhr = new XMLHttpRequest; "
|
||||||
|
" xhr.onload=function(){ "
|
||||||
|
" if (xhr.status == 200) { "
|
||||||
|
" obj.responseText = xhr.responseText; "
|
||||||
|
" if (obj.responseText == null) { "
|
||||||
|
" obj.responceText = ''; "
|
||||||
|
" } "
|
||||||
|
" caller(callerArgs); "
|
||||||
|
" }; "
|
||||||
|
" }; "
|
||||||
|
" xhr.open('GET', '%1'%2, true); "
|
||||||
|
//" xhr.setRequestHeader('Access-Control-Allow-Origin', '*'); "
|
||||||
|
//" xhr.setRequestHeader('Access-Control-Allow-Headers', 'Content-Type'); "
|
||||||
|
" xhr.send(null);"
|
||||||
|
).arg(methodUrl).arg(stringifyParameters);
|
||||||
|
|
||||||
|
|
||||||
|
QString methodBody = QString("%1").arg(bodyTemplate);
|
||||||
|
|
||||||
|
script = script + QString("%1=function(%2){ %3 }; " )
|
||||||
|
.arg(methodName)
|
||||||
|
.arg(methodParameters)
|
||||||
|
.arg(methodBody);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const QString createPopup = QString( ""
|
||||||
|
" window.createPopup=function(){ "
|
||||||
|
" var popup=document.createElement('iframe'), "
|
||||||
|
" isShown=false, popupClicked=false; "
|
||||||
|
" popup.src='about:blank'; "
|
||||||
|
" popup.style.position='absolute'; "
|
||||||
|
" popup.style.border='0px'; "
|
||||||
|
" popup.style.display='none'; "
|
||||||
|
" popup.addEventListener('load', function(e){ "
|
||||||
|
" popup.document=(popup.contentWindow || popup.contentDocument); "
|
||||||
|
" if(popup.document.document) popup.document=popup.document.document; "
|
||||||
|
" }); "
|
||||||
|
" document.body.appendChild(popup); "
|
||||||
|
" var hidepopup=function(event){ "
|
||||||
|
" if(isShown) "
|
||||||
|
" setTimeout(function(){ "
|
||||||
|
//" if(!popupClicked){ "
|
||||||
|
" popup.hide(); "
|
||||||
|
//" } "
|
||||||
|
" popupClicked=false; "
|
||||||
|
" }, 150); "
|
||||||
|
" }; "
|
||||||
|
" popup.show=function(x, y, w, h, pElement){ "
|
||||||
|
" if(typeof(x) !== 'undefined'){ "
|
||||||
|
" var elPos=[0, 0]; "
|
||||||
|
" if(pElement) elPos=findPos(pElement); "
|
||||||
|
" elPos[0]+=y, elPos[1]+=x; "
|
||||||
|
" if(isNaN(w)) w=popup.document.scrollWidth; "
|
||||||
|
" if(isNaN(h)) h=popup.document.scrollHeight; "
|
||||||
|
" if(elPos[0] + w > document.body.clientWidth) elPos[0]=document.body.clientWidth - w - 5; "
|
||||||
|
" if(elPos[1] + h > document.body.clientHeight) elPos[1]=document.body.clientHeight - h - 5; "
|
||||||
|
" popup.style.left=elPos[0] + 'px'; "
|
||||||
|
" popup.style.top=elPos[1] + 'px'; "
|
||||||
|
" popup.style.width=(w + 'px'); "
|
||||||
|
" popup.style.height=(h + 'px'); "
|
||||||
|
" } "
|
||||||
|
" popup.style.display='block'; "
|
||||||
|
" isShown=true; "
|
||||||
|
" }; "
|
||||||
|
" popup.hide=function(){ "
|
||||||
|
" isShown=false; "
|
||||||
|
" popup.style.display='none'; "
|
||||||
|
" }; "
|
||||||
|
" window.addEventListener('click', hidepopup, true); "
|
||||||
|
" window.addEventListener('blur', hidepopup, true); "
|
||||||
|
" return popup; "
|
||||||
|
" }; "
|
||||||
|
" function findPos(obj, foundScrollLeft, foundScrollTop) { "
|
||||||
|
" var curleft = 0; "
|
||||||
|
" var curtop = 0; "
|
||||||
|
" if(obj.offsetLeft) curleft += parseInt(obj.offsetLeft); "
|
||||||
|
" if(obj.offsetTop) curtop += parseInt(obj.offsetTop); "
|
||||||
|
" if(obj.scrollTop && obj.scrollTop > 0) { "
|
||||||
|
" curtop -= parseInt(obj.scrollTop); "
|
||||||
|
" foundScrollTop = true; "
|
||||||
|
" } "
|
||||||
|
" if(obj.scrollLeft && obj.scrollLeft > 0) { "
|
||||||
|
" curleft -= parseInt(obj.scrollLeft); "
|
||||||
|
" foundScrollLeft = true; "
|
||||||
|
" } "
|
||||||
|
" if(obj.offsetParent) { "
|
||||||
|
" var pos = findPos(obj.offsetParent, foundScrollLeft, foundScrollTop); "
|
||||||
|
" curleft += pos[0]; "
|
||||||
|
" curtop += pos[1]; "
|
||||||
|
" } else if(obj.ownerDocument) { "
|
||||||
|
" var thewindow = obj.ownerDocument.defaultView; "
|
||||||
|
" if(!thewindow && obj.ownerDocument.parentWindow) "
|
||||||
|
" thewindow = obj.ownerDocument.parentWindow; "
|
||||||
|
" if(thewindow) { "
|
||||||
|
" if (!foundScrollTop && thewindow.scrollY && thewindow.scrollY > 0) curtop -= parseInt(thewindow.scrollY); "
|
||||||
|
" if (!foundScrollLeft && thewindow.scrollX && thewindow.scrollX > 0) curleft -= parseInt(thewindow.scrollX); "
|
||||||
|
" if(thewindow.frameElement) { "
|
||||||
|
" var pos = findPos(thewindow.frameElement); "
|
||||||
|
" curleft += pos[0]; "
|
||||||
|
" curtop += pos[1]; "
|
||||||
|
" } "
|
||||||
|
" }"
|
||||||
|
" }"
|
||||||
|
" return [curleft,curtop]; "
|
||||||
|
" } " );
|
||||||
|
|
||||||
|
|
||||||
|
void JsHandler::init()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
QString consoleObject = createPopup + QString (
|
||||||
|
"function popup(msg){ "
|
||||||
|
" var p = window.createPopup(); "
|
||||||
|
" var pbody = p.document.body; "
|
||||||
|
" pbody.style.backgroundColor='lime'; "
|
||||||
|
" pbody.style.border='solid black 1px'; "
|
||||||
|
" pbody.innerHTML=msg; "
|
||||||
|
" p.show(NaN,NaN,NaN,NaN, document.body); }"
|
||||||
|
" window.console={ "
|
||||||
|
" log=function(msg){ popup(msg); }"
|
||||||
|
" warning=function(msg){ popup(msg); }"
|
||||||
|
" error=function(msg){ popup(msg); }"
|
||||||
|
" info=function(msg){ popup(msg); }"
|
||||||
|
" }");
|
||||||
|
*/
|
||||||
|
|
||||||
|
QString consoleObject = QString ( ""
|
||||||
|
" webviewPopup=function(msg){ "
|
||||||
|
" var p = window.createPopup(); "
|
||||||
|
" var pbody = p.document.body; "
|
||||||
|
" pbody.style.backgroundColor='white'; "
|
||||||
|
" pbody.style.border='solid black 1px'; "
|
||||||
|
" pbody.innerHTML=msg + ' ' + document.body.clientWidth; "
|
||||||
|
" p.show(0, 0, document.body.clientWidth, NaN, document.body); }; "
|
||||||
|
" window.console.log=function(msg){ webviewPopup(msg); };"
|
||||||
|
" window.console.warning=function(msg){ webviewPopup(msg); };"
|
||||||
|
" window.console.error=function(msg){ webviewPopup(msg); };"
|
||||||
|
" window.console.info=function(msg){ webviewPopup(msg); };"
|
||||||
|
) + createPopup;
|
||||||
|
/*
|
||||||
|
QString object = QString( " var script = document.getElementById(\"consoleScriptObject\"); "
|
||||||
|
" if(script === null) { "
|
||||||
|
" script = document.createElement('script'); "
|
||||||
|
" script.type = \"text/javascript\";"
|
||||||
|
" script.text = \"%1\";"
|
||||||
|
" script.id = \"consoleScriptObject\"; "
|
||||||
|
" document.getElementsByTagName('head')[0].appendChild(script); "
|
||||||
|
" } "
|
||||||
|
).arg(consoleObject);
|
||||||
|
*/
|
||||||
|
|
||||||
|
scriptParts["__console__"] = consoleObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray JsHandler::dataForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
QByteArray buffer;
|
||||||
|
QVariant ret = callMethodForUrl(url);
|
||||||
|
if (ret.isValid()) {
|
||||||
|
|
||||||
|
QJsonValue value = QJsonValue::fromVariant(ret);
|
||||||
|
QJsonArray jsonArray;
|
||||||
|
jsonArray.append(value);
|
||||||
|
QJsonDocument jsonDoc(jsonArray);
|
||||||
|
buffer = jsonDoc.toJson();
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Method call specified by url of type: http://scripthost/object.methodname?paramName1=paramValue1¶mName2=paramValue2&...
|
||||||
|
QVariant JsHandler::callMethodForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
qDebug() << url.toString();
|
||||||
|
qDebug() << "Path: " << url.path();
|
||||||
|
qDebug() << "Query: " << url.query();
|
||||||
|
|
||||||
|
QVariant ret;
|
||||||
|
QStringList objectPath = url.path().trimmed().remove('/').split(".");
|
||||||
|
QStringList query;
|
||||||
|
if (url.query().trimmed().length() > 0)
|
||||||
|
query = url.query().trimmed().split("&");
|
||||||
|
QList<QGenericArgument> arguments;
|
||||||
|
QList<QByteArray> parametersNames;
|
||||||
|
QList<QByteArray> parametersTypes;
|
||||||
|
QString methodName;
|
||||||
|
QString signature;
|
||||||
|
|
||||||
|
AmneziaWebViewPrivate *d = AmneziaWebViewPrivate::get(_host);
|
||||||
|
|
||||||
|
if (d && objectPath.count() == 2) {
|
||||||
|
qDebug() << "Called method: " << objectPath[0] << "."<< objectPath[1];
|
||||||
|
QObject *object = d->jsHandler.windowObjects.value(objectPath[0]);
|
||||||
|
if (object) {
|
||||||
|
int methodCount = object->metaObject()->methodCount();
|
||||||
|
int methodIndex = -1;
|
||||||
|
for(int i = 0; i < methodCount; i++) {
|
||||||
|
const QMetaMethod method = object->metaObject()->method(i);
|
||||||
|
parametersNames = method.parameterNames();
|
||||||
|
methodName = method.name();
|
||||||
|
if ((query.count() == parametersNames.count()) && (methodName == objectPath[1])) {
|
||||||
|
methodIndex = i;
|
||||||
|
parametersTypes = method.parameterTypes();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodIndex >= 0) {
|
||||||
|
const QMetaMethod method = object->metaObject()->method(methodIndex);
|
||||||
|
QVariantList params;
|
||||||
|
for (int i = 0; i < query.count(); i++) {
|
||||||
|
QStringList param = query[i].split('=');
|
||||||
|
|
||||||
|
params.append(QVariant(QString(param[1].toLocal8Bit())));
|
||||||
|
QGenericArgument arg(param[0].toLocal8Bit().constData(), ¶ms[i]);
|
||||||
|
arguments.append(arg);
|
||||||
|
}
|
||||||
|
switch (arguments.count()) {
|
||||||
|
case 1:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||||
|
arguments[3]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||||
|
arguments[3], arguments[4]);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||||
|
arguments[3], arguments[4], arguments[5]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||||
|
arguments[3], arguments[4], arguments[5], arguments[6]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||||
|
arguments[3], arguments[4], arguments[5], arguments[6], arguments[7]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef JSHANDLER_H
|
||||||
|
#define JSHANDLER_H
|
||||||
|
|
||||||
|
class QUrl;
|
||||||
|
class QVariant;
|
||||||
|
class AmneziaWebView;
|
||||||
|
|
||||||
|
class JsHandler
|
||||||
|
{
|
||||||
|
friend class AmneziaWebView;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit JsHandler(AmneziaWebView *host);
|
||||||
|
virtual ~JsHandler();
|
||||||
|
|
||||||
|
bool canHandleUrl(const QUrl &url) const;
|
||||||
|
QByteArray dataForUrl(const QUrl &url) const;
|
||||||
|
void addToJavaScriptWindowObject(const QString& name, QObject* object);
|
||||||
|
QString mimeTypeForUrl(const QUrl &url) const;
|
||||||
|
|
||||||
|
void updateWebView();
|
||||||
|
QString host() const;
|
||||||
|
QString scheme() const;
|
||||||
|
QString scriptObjectsUrl() const;
|
||||||
|
QString scriptObjectsId() const;
|
||||||
|
QString scriptObjects() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QVariant callMethodForUrl(const QUrl &url) const;
|
||||||
|
QString windowScriptObject(const QString& name, QObject* object) const;
|
||||||
|
|
||||||
|
void init();
|
||||||
|
AmneziaWebView *_host;
|
||||||
|
bool scriptObjectsInjected;
|
||||||
|
|
||||||
|
QHash<QString, QObject*> windowObjects;
|
||||||
|
QHash<QString, QString> scriptParts;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QVariant>
|
||||||
|
#include <QtCore/QMetaMethod>
|
||||||
|
#include <QtCore/QMetaObject>
|
||||||
|
#include <QtCore/QGenericArgument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
#include "jshandler.h"
|
||||||
|
#include "mimecache.h"
|
||||||
|
|
||||||
|
JsHandler::JsHandler(AmneziaWebView *host): _host(host)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsHandler::~JsHandler() {}
|
||||||
|
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <MobileCoreServices/UTCoreTypes.h>
|
||||||
|
#import <MobileCoreServices/UTType.h>
|
||||||
|
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QHash>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QRect>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QVariant>
|
||||||
|
#include <QtCore/QMetaMethod>
|
||||||
|
#include <QtCore/QMetaObject>
|
||||||
|
#include <QtCore/QGenericArgument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
#include "mimecache.h"
|
||||||
|
#import "jshandler.h"
|
||||||
|
|
||||||
|
typedef QHash<QString, AmneziaWebView*> JavaScriptHosts;
|
||||||
|
Q_GLOBAL_STATIC(JavaScriptHosts, hosts);
|
||||||
|
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
|
||||||
|
@interface JsProtocol : NSURLProtocol
|
||||||
|
+ (NSString*) requestVarsKey;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface NSURLRequest (JsProtocol)
|
||||||
|
- (NSDictionary *)requestVars;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface NSMutableURLRequest (JsProtocol)
|
||||||
|
- (void)setRequestVars:(NSDictionary *)vars;
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsHandler::JsHandler(AmneziaWebView *h): _host(h)
|
||||||
|
, scriptObjectsInjected(false)
|
||||||
|
{
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
static bool protocolRegistered = false;
|
||||||
|
if (!protocolRegistered) {
|
||||||
|
[NSURLProtocol registerClass:[JsProtocol class]];
|
||||||
|
protocolRegistered = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
QString key = host();
|
||||||
|
if (!hosts()->keys().contains(key)) {
|
||||||
|
hosts()->insert(key, h);
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsHandler::~JsHandler()
|
||||||
|
{
|
||||||
|
QString key = host();
|
||||||
|
if (hosts()->keys().contains(key)) {
|
||||||
|
hosts()->remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
|
||||||
|
@implementation NSURLRequest (JsProtocol)
|
||||||
|
|
||||||
|
- (NSDictionary *)requestVars {
|
||||||
|
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||||
|
return [NSURLProtocol propertyForKey:[JsProtocol requestVarsKey] inRequest:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation NSMutableURLRequest (JsProtocol)
|
||||||
|
|
||||||
|
- (void)setRequestVars:(NSDictionary *)requestVars {
|
||||||
|
|
||||||
|
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||||
|
|
||||||
|
NSDictionary *specialVarsCopy = [requestVars copy];
|
||||||
|
[NSURLProtocol setProperty:specialVarsCopy forKey:[JsProtocol requestVarsKey] inRequest:self];
|
||||||
|
[specialVarsCopy release];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation JsProtocol
|
||||||
|
|
||||||
|
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
//NSString *url = request.URL.absoluteString;
|
||||||
|
QString host = QString::fromNSString(request.URL.host).toLower();
|
||||||
|
if (hosts()->keys().contains(host))
|
||||||
|
return YES;
|
||||||
|
|
||||||
|
//NSLog(@"Requested Url: %@", url);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString*) requestVarsKey
|
||||||
|
{
|
||||||
|
return @"requestVars";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startLoading
|
||||||
|
{
|
||||||
|
QString host = QString::fromNSString(self.request.URL.host);
|
||||||
|
AmneziaWebViewPrivate *d = AmneziaWebViewPrivate::get(hosts()->value(host));
|
||||||
|
NSURLRequest *request = [self request];
|
||||||
|
|
||||||
|
if ([request.URL.absoluteString isEqualToString: d->jsHandler.scriptObjectsUrl().toNSString()]) {
|
||||||
|
|
||||||
|
//NSString *mimeType = mimeTypeForExtension(QString::fromNSString(request.URL.path.pathExtension)).toNSString();
|
||||||
|
|
||||||
|
NSString *mimeType = d->jsHandler.mimeTypeForUrl(QUrl::fromNSURL(request.URL)).toNSString();
|
||||||
|
QByteArray buffer = d->jsHandler.scriptObjects().toLocal8Bit();
|
||||||
|
|
||||||
|
//NSData *data = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
NSData *data = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
|
||||||
|
NSURLResponse *response =[[NSURLResponse alloc]initWithURL:self.request.URL
|
||||||
|
MIMEType:mimeType
|
||||||
|
expectedContentLength:[data length]
|
||||||
|
textEncodingName:@"utf-8"];
|
||||||
|
|
||||||
|
|
||||||
|
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||||
|
[[self client] URLProtocol:self didLoadData:data];
|
||||||
|
[[self client] URLProtocolDidFinishLoading:self];
|
||||||
|
[response autorelease];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( d && [request.URL.scheme isEqualToString:@"http"]
|
||||||
|
&& [request.URL.host isEqualToString:host.toNSString()]) {
|
||||||
|
|
||||||
|
const QByteArray buffer = d->dataForUrl(QUrl::fromNSURL(request.URL));
|
||||||
|
//NSData *data = data = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
NSData *data = data = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
|
||||||
|
//NSString *mimeType = mimeTypeForExtension(QString("json")).toNSString();
|
||||||
|
NSString *mimeType = d->jsHandler.mimeTypeForUrl(QUrl::fromNSURL(request.URL)).toNSString();
|
||||||
|
|
||||||
|
NSDictionary *headers = @{@"Access-Control-Allow-Origin" : @"*",
|
||||||
|
@"Access-Control-Allow-Headers" : @"Content-Type",
|
||||||
|
@"Cache-Control" : @"no-cache",
|
||||||
|
@"Content-Type" : [NSString stringWithFormat:@"%@; %@", mimeType, @"charset=UTF-8"] };
|
||||||
|
|
||||||
|
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:request.URL
|
||||||
|
statusCode:200
|
||||||
|
HTTPVersion:@"HTTP/1.1"
|
||||||
|
headerFields:headers];
|
||||||
|
|
||||||
|
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||||
|
[self.client URLProtocol:self didLoadData:data];
|
||||||
|
[self.client URLProtocolDidFinishLoading:self];
|
||||||
|
[response autorelease];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stopLoading
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
#include "mimecache.h"
|
||||||
|
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QMimeDatabase>
|
||||||
|
#include <QtCore/QMimeType>
|
||||||
|
#include <QtCore/QHash>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
|
||||||
|
typedef QHash<QString, QString> MimeTypes;
|
||||||
|
Q_GLOBAL_STATIC(MimeTypes, cache)
|
||||||
|
Q_GLOBAL_STATIC(QMimeDatabase, mimeDatabase)
|
||||||
|
|
||||||
|
|
||||||
|
void initCache()
|
||||||
|
{
|
||||||
|
if (cache()->isEmpty()) {
|
||||||
|
|
||||||
|
cache()->insert("323", "text/h323");
|
||||||
|
cache()->insert("*", "application/octet-stream");
|
||||||
|
cache()->insert("acx", "application/internet-property-stream");
|
||||||
|
cache()->insert("ai", "application/postscript");
|
||||||
|
cache()->insert("aif", "audio/x-aiff");
|
||||||
|
cache()->insert("aifc", "audio/x-aiff");
|
||||||
|
cache()->insert("aiff", "audio/x-aiff");
|
||||||
|
cache()->insert("asf", "video/x-ms-asf");
|
||||||
|
cache()->insert("asr", "video/x-ms-asf");
|
||||||
|
cache()->insert("asx", "video/x-ms-asf");
|
||||||
|
cache()->insert("au", "audio/basic");
|
||||||
|
cache()->insert("avi", "video/x-msvideo");
|
||||||
|
cache()->insert("axs", "application/olescript");
|
||||||
|
cache()->insert("bas", "text/plain");
|
||||||
|
cache()->insert("bcpio", "application/x-bcpio");
|
||||||
|
cache()->insert("bin", "application/octet-stream");
|
||||||
|
cache()->insert("bmp", "image/bmp");
|
||||||
|
cache()->insert("c", "text/plain");
|
||||||
|
cache()->insert("cat", "application/vnd.ms-pkiseccat");
|
||||||
|
cache()->insert("cdf", "application/x-cdf");
|
||||||
|
cache()->insert("cdf", "application/x-netcdf");
|
||||||
|
cache()->insert("cer", "application/x-x509-ca-cert""cer");
|
||||||
|
cache()->insert("class", "application/octet-stream");
|
||||||
|
cache()->insert("clp", "application/x-msclip");
|
||||||
|
cache()->insert("cmx", "image/x-cmx");
|
||||||
|
cache()->insert("cod", "image/cis-cod");
|
||||||
|
cache()->insert("cpio", "application/x-cpio");
|
||||||
|
cache()->insert("crd", "application/x-mscardfile");
|
||||||
|
cache()->insert("crl", "application/pkix-crl");
|
||||||
|
cache()->insert("crt", "application/x-x509-ca-cert");
|
||||||
|
cache()->insert("csh", "application/x-csh");
|
||||||
|
cache()->insert("css", "text/css");
|
||||||
|
cache()->insert("dcr", "application/x-director");
|
||||||
|
cache()->insert("der", "application/x-x509-ca-cert");
|
||||||
|
cache()->insert("dir", "application/x-director");
|
||||||
|
cache()->insert("dll", "application/x-msdownload");
|
||||||
|
cache()->insert("dms", "application/octet-stream");
|
||||||
|
cache()->insert("doc", "application/msword");
|
||||||
|
cache()->insert("dot", "application/msword");
|
||||||
|
cache()->insert("dvi", "application/x-dvi");
|
||||||
|
cache()->insert("dxr", "application/x-director");
|
||||||
|
cache()->insert("eot", "application/vnd.ms-fontobject");
|
||||||
|
cache()->insert("eps", "application/postscript");
|
||||||
|
cache()->insert("etx", "text/x-setext");
|
||||||
|
cache()->insert("evy", "application/envoy");
|
||||||
|
cache()->insert("exe", "application/octet-stream");
|
||||||
|
cache()->insert("fif", "application/fractals");
|
||||||
|
cache()->insert("flr", "x-world/x-vrml");
|
||||||
|
cache()->insert("gif", "image/gif");
|
||||||
|
cache()->insert("gtar", "application/x-gtar");
|
||||||
|
cache()->insert("gz", "application/x-gzip");
|
||||||
|
cache()->insert("h", "text/plain");
|
||||||
|
cache()->insert("hdf", "application/x-hdf");
|
||||||
|
cache()->insert("hlp", "application/winhlp");
|
||||||
|
cache()->insert("hqx", "application/mac-binhex40");
|
||||||
|
cache()->insert("hta", "application/hta");
|
||||||
|
cache()->insert("htc", "text/x-component");
|
||||||
|
cache()->insert("htm", "text/html");
|
||||||
|
cache()->insert("html", "text/html");
|
||||||
|
cache()->insert("htt", "text/webviewhtml");
|
||||||
|
cache()->insert("ico", "image/x-icon");
|
||||||
|
cache()->insert("ief", "image/ief");
|
||||||
|
cache()->insert("iii", "application/x-iphone");
|
||||||
|
cache()->insert("ins", "application/x-internet-signup");
|
||||||
|
cache()->insert("isp", "application/x-internet-signup");
|
||||||
|
cache()->insert("jfif", "image/pipeg");
|
||||||
|
cache()->insert("jpe", "image/jpeg");
|
||||||
|
cache()->insert("jpeg", "image/jpeg");
|
||||||
|
cache()->insert("jpg", "image/jpeg");
|
||||||
|
cache()->insert("js", "application/x-javascript");
|
||||||
|
cache()->insert("json", "application/json");
|
||||||
|
cache()->insert("latex", "application/x-latex");
|
||||||
|
cache()->insert("lha", "application/octet-stream");
|
||||||
|
cache()->insert("lsf", "video/x-la-asf");
|
||||||
|
cache()->insert("lsx", "video/x-la-asf");
|
||||||
|
cache()->insert("lzh", "application/octet-stream");
|
||||||
|
cache()->insert("m13", "application/x-msmediaview");
|
||||||
|
cache()->insert("m14", "application/x-msmediaview");
|
||||||
|
cache()->insert("m3u", "audio/x-mpegurl");
|
||||||
|
cache()->insert("m4v", "video/x-m4v");
|
||||||
|
cache()->insert("man", "application/x-troff-man");
|
||||||
|
cache()->insert("mdb", "application/x-msaccess");
|
||||||
|
cache()->insert("me", "application/x-troff-me");
|
||||||
|
cache()->insert("mht", "message/rfc822");
|
||||||
|
cache()->insert("mhtml", "message/rfc822");
|
||||||
|
cache()->insert("mid", "audio/mid");
|
||||||
|
cache()->insert("mny", "application/x-msmoney");
|
||||||
|
cache()->insert("mov", "video/quicktime");
|
||||||
|
cache()->insert("movie", "video/x-sgi-movie""movie");
|
||||||
|
cache()->insert("mp2", "video/mpeg");
|
||||||
|
cache()->insert("mp3", "audio/mpeg");
|
||||||
|
cache()->insert("mpa", "video/mpeg");
|
||||||
|
cache()->insert("mpe", "video/mpeg");
|
||||||
|
cache()->insert("mpeg", "video/mpeg");
|
||||||
|
cache()->insert("mpg", "video/mpeg");
|
||||||
|
cache()->insert("mpp", "application/vnd.ms-project");
|
||||||
|
cache()->insert("mpv2", "video/mpeg");
|
||||||
|
cache()->insert("ms", "application/x-troff-ms");
|
||||||
|
cache()->insert("msg", "application/vnd.ms-outlook");
|
||||||
|
cache()->insert("mvb", "application/x-msmediaview");
|
||||||
|
cache()->insert("nc", "application/x-netcdf");
|
||||||
|
cache()->insert("nws", "message/rfc822");
|
||||||
|
cache()->insert("oda", "application/oda");
|
||||||
|
cache()->insert("otf", "application/font-sfnt");
|
||||||
|
cache()->insert("p10", "application/pkcs10");
|
||||||
|
cache()->insert("p12", "application/x-pkcs12");
|
||||||
|
cache()->insert("p7b", "application/x-pkcs7-certificates");
|
||||||
|
cache()->insert("p7c", "application/x-pkcs7-mime");
|
||||||
|
cache()->insert("p7m", "application/x-pkcs7-mime");
|
||||||
|
cache()->insert("p7r", "application/x-pkcs7-certreqresp");
|
||||||
|
cache()->insert("p7s", "application/x-pkcs7-signature");
|
||||||
|
cache()->insert("pbm", "image/x-portable-bitmap");
|
||||||
|
cache()->insert("pdf", "application/pdf");
|
||||||
|
cache()->insert("pfx", "application/x-pkcs12");
|
||||||
|
cache()->insert("pgm", "image/x-portable-graymap");
|
||||||
|
cache()->insert("pko", "application/ynd.ms-pkipko");
|
||||||
|
cache()->insert("pma", "application/x-perfmon");
|
||||||
|
cache()->insert("pmc", "application/x-perfmon");
|
||||||
|
cache()->insert("pml", "application/x-perfmon");
|
||||||
|
cache()->insert("pmr", "application/x-perfmon");
|
||||||
|
cache()->insert("pmw", "application/x-perfmon");
|
||||||
|
cache()->insert("pnm", "image/x-portable-anymap");
|
||||||
|
cache()->insert("pot", "application/vnd.ms-powerpoint");
|
||||||
|
cache()->insert("ppm", "image/x-portable-pixmap");
|
||||||
|
cache()->insert("pps", "application/vnd.ms-powerpoint");
|
||||||
|
cache()->insert("ppt", "application/vnd.ms-powerpoint");
|
||||||
|
cache()->insert("prf", "application/pics-rules");
|
||||||
|
cache()->insert("ps", "application/postscript");
|
||||||
|
cache()->insert("pub", "application/x-mspublisher");
|
||||||
|
cache()->insert("qt", "video/quicktime");
|
||||||
|
cache()->insert("ra", "audio/x-pn-realaudio");
|
||||||
|
cache()->insert("ram", "audio/x-pn-realaudio");
|
||||||
|
cache()->insert("ras", "image/x-cmu-raster");
|
||||||
|
cache()->insert("rgb", "image/x-rgb");
|
||||||
|
cache()->insert("rmi", "audio/mid");
|
||||||
|
cache()->insert("roff", "application/x-troff");
|
||||||
|
cache()->insert("rtf", "application/rtf""rtf");
|
||||||
|
cache()->insert("rtx", "text/richtext""rtx");
|
||||||
|
cache()->insert("scd", "application/x-msschedule");
|
||||||
|
cache()->insert("sct", "text/scriptlet");
|
||||||
|
cache()->insert("setpay", "application/set-payment-initiation");
|
||||||
|
cache()->insert("setreg", "application/set-registration-initiation");
|
||||||
|
cache()->insert("sh", "application/x-sh");
|
||||||
|
cache()->insert("shar", "application/x-shar");
|
||||||
|
cache()->insert("sit", "application/x-stuffit");
|
||||||
|
cache()->insert("snd", "audio/basic");
|
||||||
|
cache()->insert("spc", "application/x-pkcs7-certificates");
|
||||||
|
cache()->insert("spl", "application/futuresplash");
|
||||||
|
cache()->insert("src", "application/x-wais-source");
|
||||||
|
cache()->insert("sst", "application/vnd.ms-pkicertstore");
|
||||||
|
cache()->insert("stl", "application/vnd.ms-pkistl");
|
||||||
|
cache()->insert("stm", "text/html");
|
||||||
|
cache()->insert("sv4cpio", "application/x-sv4cpio");
|
||||||
|
cache()->insert("sv4crc", "application/x-sv4crc");
|
||||||
|
cache()->insert("svg", "image/svg+xml");
|
||||||
|
cache()->insert("swf", "application/x-shockwave-flash");
|
||||||
|
cache()->insert("t", "application/x-troff");
|
||||||
|
cache()->insert("tar", "application/x-tar");
|
||||||
|
cache()->insert("tcl", "application/x-tcl");
|
||||||
|
cache()->insert("tex", "application/x-tex");
|
||||||
|
cache()->insert("texi", "application/x-texinfo");
|
||||||
|
cache()->insert("texinfo", "application/x-texinfo");
|
||||||
|
cache()->insert("tgz", "application/x-compressed");
|
||||||
|
cache()->insert("tif", "image/tiff");
|
||||||
|
cache()->insert("tiff", "image/tiff");
|
||||||
|
cache()->insert("tr", "application/x-troff");
|
||||||
|
cache()->insert("trm", "application/x-msterminal");
|
||||||
|
cache()->insert("tsv", "text/tab-separated-values");
|
||||||
|
cache()->insert("txt", "text/plain");
|
||||||
|
cache()->insert("ttf", "application/font-sfnt");
|
||||||
|
cache()->insert("uls", "text/iuls");
|
||||||
|
cache()->insert("ustar", "application/x-ustar");
|
||||||
|
cache()->insert("vcf", "text/x-vcard");
|
||||||
|
cache()->insert("vrml", "x-world/x-vrml");
|
||||||
|
cache()->insert("wav", "audio/x-wav");
|
||||||
|
cache()->insert("wcm", "application/vnd.ms-works");
|
||||||
|
cache()->insert("wdb", "application/vnd.ms-works");
|
||||||
|
cache()->insert("wks", "application/vnd.ms-works");
|
||||||
|
cache()->insert("wmf", "application/x-msmetafile");
|
||||||
|
cache()->insert("woff", "application/font-woff");
|
||||||
|
cache()->insert("wps", "application/vnd.ms-works");
|
||||||
|
cache()->insert("wri", "application/x-mswrite");
|
||||||
|
cache()->insert("wrl", "x-world/x-vrml");
|
||||||
|
cache()->insert("wrz", "x-world/x-vrml");
|
||||||
|
cache()->insert("xaf", "x-world/x-vrml");
|
||||||
|
cache()->insert("xbm", "image/x-xbitmap");
|
||||||
|
cache()->insert("xla", "application/vnd.ms-excel");
|
||||||
|
cache()->insert("xlc", "application/vnd.ms-excel");
|
||||||
|
cache()->insert("xlm", "application/vnd.ms-excel");
|
||||||
|
cache()->insert("xls", "application/vnd.ms-excel");
|
||||||
|
cache()->insert("xlt", "application/vnd.ms-excel");
|
||||||
|
cache()->insert("xlw", "application/vnd.ms-excel");
|
||||||
|
cache()->insert("xof", "x-world/x-vrml");
|
||||||
|
cache()->insert("xpm", "image/x-xpixmap");
|
||||||
|
cache()->insert("xwd", "image/x-xwindowdump");
|
||||||
|
cache()->insert("z", "application/x-compress");
|
||||||
|
cache()->insert("zip", "application/zip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString mimeTypeForExtension(const QString &extension)
|
||||||
|
{
|
||||||
|
initCache();
|
||||||
|
QString ext = extension;
|
||||||
|
|
||||||
|
const int lastDot = ext.lastIndexOf(QLatin1Char('.'));
|
||||||
|
if (lastDot != -1) {
|
||||||
|
const int extLength = ext.length() - lastDot - 1;
|
||||||
|
ext = ext.right(extLength).toLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString mimeType = cache()->value(ext);
|
||||||
|
|
||||||
|
if (mimeType.isNull()) {
|
||||||
|
mimeType = QString("application/octet-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString mimeTypeForUrl(const QUrl &url)
|
||||||
|
{
|
||||||
|
initCache();
|
||||||
|
QString path = url.path();
|
||||||
|
|
||||||
|
QString extension;
|
||||||
|
QString mimeType;
|
||||||
|
|
||||||
|
const int lastDot = path.lastIndexOf(QLatin1Char('.'));
|
||||||
|
if (lastDot != -1) {
|
||||||
|
const int extLength = path.length() - lastDot - 1;
|
||||||
|
extension = path.right(extLength).toLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extension.isNull()) {
|
||||||
|
mimeType = cache()->value(extension);
|
||||||
|
}
|
||||||
|
if (mimeType.isNull()) {
|
||||||
|
QMimeType mime = mimeDatabase()->mimeTypeForUrl(url);
|
||||||
|
if (mime.isValid()) {
|
||||||
|
mimeType = mime.name();
|
||||||
|
if(!extension.isNull()) {
|
||||||
|
cache()->insert(extension, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType.isNull()) {
|
||||||
|
mimeType = QString("application/octet-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << mimeType;
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef MIMECACHE_H
|
||||||
|
#define MIMECACHE_H
|
||||||
|
|
||||||
|
class QString;
|
||||||
|
class QUrl;
|
||||||
|
|
||||||
|
QString mimeTypeForExtension(const QString &extension);
|
||||||
|
QString mimeTypeForUrl(const QUrl &url);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||||
|
** packaging of this file. Please review the following information to
|
||||||
|
** ensure the GNU Lesser General Public License version 3 requirements
|
||||||
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 2.0 or (at your option) the GNU General
|
||||||
|
** Public license version 3 or any later version approved by the KDE Free
|
||||||
|
** Qt Foundation. The licenses are as published by the Free Software
|
||||||
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||||
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a precompiled header file for use in Xcode / Mac GCC /
|
||||||
|
* GCC >= 3.4 / VC to greatly speed the building of Qt. It may also be
|
||||||
|
* of use to people developing their own project, but it is probably
|
||||||
|
* better to define your own header. Use of this header is currently
|
||||||
|
* UNSUPPORTED.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if defined __cplusplus
|
||||||
|
// for rand_s, _CRT_RAND_S must be #defined before #including stdlib.h.
|
||||||
|
// put it at the beginning so some indirect inclusion doesn't break it
|
||||||
|
#ifndef _CRT_RAND_S
|
||||||
|
#define _CRT_RAND_S
|
||||||
|
#endif
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <qglobal.h>
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
# define _POSIX_
|
||||||
|
# include <limits.h>
|
||||||
|
# undef _POSIX_
|
||||||
|
#endif
|
||||||
|
#include <QtCore/QtCore>
|
||||||
|
#ifndef Q_OS_WIN
|
||||||
|
#include <QtQuick/QtQuick>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include "plugin.h"
|
||||||
|
|
||||||
|
#include "amneziawebview.h"
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
void WebViewPlugin::registerTypes(const char* uri)
|
||||||
|
{
|
||||||
|
#ifndef QT_NO_ACTION
|
||||||
|
qmlRegisterAnonymousType<QAction>(uri, 1);
|
||||||
|
#endif
|
||||||
|
qmlRegisterAnonymousType<AmneziaWebViewSettings>(uri, 1);
|
||||||
|
qmlRegisterType<AmneziaWebView>(uri, 1, 0, "AmneziaWebView");
|
||||||
|
qmlRegisterRevision<AmneziaWebView, 0>("AmneziaWebView", 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class WebViewPlugin : public QQmlExtensionPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface" FILE "webview.json")
|
||||||
|
Q_INTERFACES(QQmlExtensionInterface)
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerTypes(const char* uri) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
module AmneziaWebView
|
||||||
|
plugin webview
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#include "mimecache.h"
|
||||||
|
|
||||||
|
QString QrcHandler::scheme()
|
||||||
|
{
|
||||||
|
return QString("qrc");
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray QrcHandler::dataForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
QString requestUrl(QString(":/%1").arg(url.path()));
|
||||||
|
QFile resource(requestUrl);
|
||||||
|
QByteArray buffer;
|
||||||
|
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||||
|
|
||||||
|
buffer = resource.readAll();
|
||||||
|
resource.close();
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QrcHandler::canHandleUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
if (scheme() == url.scheme())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QrcHandler::mimeTypeForUrl(const QUrl &url) const
|
||||||
|
{
|
||||||
|
return mimeTypeForExtension(url.path().section('.', -1));
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef QRCHANDLER_H
|
||||||
|
#define QRCHANDLER_H
|
||||||
|
|
||||||
|
class QString;
|
||||||
|
class QByteArray;
|
||||||
|
class QUrl;
|
||||||
|
|
||||||
|
class QrcHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit QrcHandler();
|
||||||
|
static QString scheme();
|
||||||
|
bool canHandleUrl(const QUrl &url) const;
|
||||||
|
QByteArray dataForUrl(const QUrl &url) const;
|
||||||
|
QString mimeTypeForUrl(const QUrl &url) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#include "mimecache.h"
|
||||||
|
|
||||||
|
QrcHandler::QrcHandler()
|
||||||
|
{}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <MobileCoreServices/UTCoreTypes.h>
|
||||||
|
#import <MobileCoreServices/UTType.h>
|
||||||
|
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include "mimecache.h"
|
||||||
|
|
||||||
|
#include "qrchandler.h"
|
||||||
|
#define kProtocolQrcScheme @"qrc"
|
||||||
|
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
@interface QrcProtocol : NSURLProtocol
|
||||||
|
|
||||||
|
+ (NSString*)protocolScheme;
|
||||||
|
+ (NSString*)requestVarsKey;
|
||||||
|
+ (void)registerProtocol;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface NSURLRequest (QrcProtocol)
|
||||||
|
- (NSDictionary *)requestVars;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface NSMutableURLRequest (QrcProtocol)
|
||||||
|
- (void)setRequestVars:(NSDictionary *)vars;
|
||||||
|
@end
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QrcHandler::QrcHandler()
|
||||||
|
{
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
[QrcProtocol registerProtocol];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(ENABLE_WKWEBVIEW)
|
||||||
|
|
||||||
|
@implementation NSURLRequest (QrcProtocol)
|
||||||
|
|
||||||
|
- (NSDictionary *)requestVars {
|
||||||
|
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||||
|
return [NSURLProtocol propertyForKey:[QrcProtocol requestVarsKey] inRequest:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation NSMutableURLRequest (QrcProtocol)
|
||||||
|
|
||||||
|
- (void)setRequestVars:(NSDictionary *)requestVars {
|
||||||
|
|
||||||
|
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||||
|
|
||||||
|
NSDictionary *specialVarsCopy = [requestVars copy];
|
||||||
|
[NSURLProtocol setProperty:specialVarsCopy forKey:[QrcProtocol requestVarsKey] inRequest:self];
|
||||||
|
[specialVarsCopy release];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation QrcProtocol
|
||||||
|
|
||||||
|
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
NSString *scheme = request.URL.scheme;
|
||||||
|
if ([kProtocolQrcScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString*) protocolScheme
|
||||||
|
{
|
||||||
|
return kProtocolQrcScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString*) requestVarsKey {
|
||||||
|
return @"requestVars";
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)registerProtocol
|
||||||
|
{
|
||||||
|
static bool qrcProtocolRegistered = false;
|
||||||
|
if (!qrcProtocolRegistered) {
|
||||||
|
[NSURLProtocol registerClass:[QrcProtocol class]];
|
||||||
|
qrcProtocolRegistered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString*)headFromHtml:(NSString*)html
|
||||||
|
{
|
||||||
|
if (!html) return nil;
|
||||||
|
|
||||||
|
NSString *head = nil;
|
||||||
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=<head>)[\\w\\W.]*(?=</head>)" options:NSRegularExpressionCaseInsensitive error:nil];
|
||||||
|
NSTextCheckingResult *headResult = [regex firstMatchInString:html options:0 range:NSMakeRange(0, html.length)];
|
||||||
|
|
||||||
|
if (headResult && headResult.range.location != NSNotFound) {
|
||||||
|
head = [html substringWithRange:[headResult range]];
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)charsetFromHtml:(NSString *)html
|
||||||
|
{
|
||||||
|
if (!html) return nil;
|
||||||
|
|
||||||
|
NSString *charset = nil;
|
||||||
|
NSString *charsetPattern = @"((?<=charset=)\\s*[a-zA-Z0-9-]*)";
|
||||||
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:charsetPattern options: NSRegularExpressionCaseInsensitive error:nil];
|
||||||
|
NSTextCheckingResult *charsetResult = [regex firstMatchInString:html options:kNilOptions range:NSMakeRange(0, [html length])];
|
||||||
|
if (charsetResult && charsetResult.range.location != NSNotFound) {
|
||||||
|
charset = [[html substringWithRange:[charsetResult range]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
charset = [charset lowercaseString];
|
||||||
|
}
|
||||||
|
return charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)startLoading
|
||||||
|
{
|
||||||
|
/* retrieve the current request. */
|
||||||
|
NSURLRequest *request = [self request];
|
||||||
|
NSString *url = [[request URL] path];
|
||||||
|
//NSString *absoluteString = request.URL.absoluteString;
|
||||||
|
|
||||||
|
//NSString *mimeType = [self mimeTypeForExtension: [url pathExtension]];
|
||||||
|
NSString *mimeType = mimeTypeForExtension(QString::fromNSString(url.pathExtension)).toNSString();
|
||||||
|
|
||||||
|
/* extract our special variables from the request. */
|
||||||
|
//NSDictionary *requestVars = [request requestVars];
|
||||||
|
NSData *pageData = nil;
|
||||||
|
|
||||||
|
QString requestUrl(QString(":/%1").arg(QString::fromNSString(url)));
|
||||||
|
QFile resource(requestUrl);
|
||||||
|
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||||
|
|
||||||
|
QByteArray buffer = resource.readAll();
|
||||||
|
|
||||||
|
//pageData = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
pageData = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||||
|
|
||||||
|
resource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageData) {
|
||||||
|
|
||||||
|
NSString *encoding = @"utf-8";
|
||||||
|
if ([mimeType isEqualToString:@"text/html"]) {
|
||||||
|
|
||||||
|
NSString *content = [[NSString alloc] initWithBytesNoCopy: (char* )[pageData bytes] length:pageData.length encoding:NSISOLatin1StringEncoding freeWhenDone:NO];
|
||||||
|
|
||||||
|
NSString *charset = [QrcProtocol charsetFromHtml:[QrcProtocol headFromHtml:content]];
|
||||||
|
if (charset) {
|
||||||
|
encoding = charset;
|
||||||
|
}
|
||||||
|
[content autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLResponse *response = [[NSURLResponse alloc]initWithURL:self.request.URL
|
||||||
|
MIMEType:mimeType
|
||||||
|
expectedContentLength:[pageData length]
|
||||||
|
textEncodingName:encoding];
|
||||||
|
|
||||||
|
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||||
|
[[self client] URLProtocol:self didLoadData:pageData];
|
||||||
|
[[self client] URLProtocolDidFinishLoading:self];
|
||||||
|
[response autorelease];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)stopLoading
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QSharedData>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "websettings.h"
|
||||||
|
#include "amneziawebview_p.h"
|
||||||
|
|
||||||
|
AmneziaWebViewSettings::AmneziaWebViewSettings(AmneziaWebView *view): QObject(view), s(new WebSettings(view))
|
||||||
|
{}
|
||||||
|
|
||||||
|
Q_GLOBAL_STATIC(QList<WebSettings*>, allSettings)
|
||||||
|
|
||||||
|
void WebSettings::apply()
|
||||||
|
{
|
||||||
|
if (view) {
|
||||||
|
|
||||||
|
WebSettings* global = WebSettings::globalSettings();
|
||||||
|
|
||||||
|
|
||||||
|
QString family = fontFamilies.value(WebSettings::StandardFont,
|
||||||
|
global->fontFamilies.value(WebSettings::StandardFont));
|
||||||
|
view->setStandardFontFamily(family);
|
||||||
|
|
||||||
|
|
||||||
|
int size = fontSizes.value(WebSettings::DefaultFontSize,
|
||||||
|
global->fontSizes.value(WebSettings::DefaultFontSize));
|
||||||
|
view->setDefaultFontSize(size);
|
||||||
|
|
||||||
|
bool value = attributes.value(WebSettings::AutoLoadImages,
|
||||||
|
global->attributes.value(WebSettings::AutoLoadImages));
|
||||||
|
|
||||||
|
QString encoding = !defaultTextEncoding.isEmpty() ? defaultTextEncoding: global->defaultTextEncoding;
|
||||||
|
|
||||||
|
|
||||||
|
Q_UNUSED(value)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
QList<WebSettings*> settings = *::allSettings();
|
||||||
|
for (int i = 0; i < settings.count(); ++i)
|
||||||
|
settings[i]->apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettings* WebSettings::globalSettings()
|
||||||
|
{
|
||||||
|
static WebSettings *global = nullptr;
|
||||||
|
if (!global) {
|
||||||
|
global = new WebSettings;
|
||||||
|
}
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettings::WebSettings()
|
||||||
|
{
|
||||||
|
// Initialize our global defaults
|
||||||
|
fontSizes.insert(WebSettings::MinimumFontSize, 0);
|
||||||
|
fontSizes.insert(WebSettings::MinimumLogicalFontSize, 0);
|
||||||
|
fontSizes.insert(WebSettings::DefaultFontSize, 16);
|
||||||
|
fontSizes.insert(WebSettings::DefaultFixedFontSize, 13);
|
||||||
|
|
||||||
|
QFont defaultFont;
|
||||||
|
defaultFont.setStyleHint(QFont::Serif);
|
||||||
|
fontFamilies.insert(WebSettings::StandardFont, defaultFont.defaultFamily());
|
||||||
|
fontFamilies.insert(WebSettings::SerifFont, defaultFont.defaultFamily());
|
||||||
|
|
||||||
|
defaultFont.setStyleHint(QFont::Fantasy);
|
||||||
|
fontFamilies.insert(WebSettings::FantasyFont, defaultFont.defaultFamily());
|
||||||
|
|
||||||
|
defaultFont.setStyleHint(QFont::Cursive);
|
||||||
|
fontFamilies.insert(WebSettings::CursiveFont, defaultFont.defaultFamily());
|
||||||
|
|
||||||
|
defaultFont.setStyleHint(QFont::SansSerif);
|
||||||
|
fontFamilies.insert(WebSettings::SansSerifFont, defaultFont.defaultFamily());
|
||||||
|
|
||||||
|
defaultFont.setStyleHint(QFont::Monospace);
|
||||||
|
fontFamilies.insert(WebSettings::FixedFont, defaultFont.defaultFamily());
|
||||||
|
|
||||||
|
attributes.insert(WebSettings::AutoLoadImages, true);
|
||||||
|
attributes.insert(WebSettings::DnsPrefetchEnabled, false);
|
||||||
|
attributes.insert(WebSettings::JavascriptEnabled, true);
|
||||||
|
attributes.insert(WebSettings::SpatialNavigationEnabled, false);
|
||||||
|
attributes.insert(WebSettings::LinksIncludedInFocusChain, true);
|
||||||
|
attributes.insert(WebSettings::ZoomTextOnly, false);
|
||||||
|
attributes.insert(WebSettings::PrintElementBackgrounds, true);
|
||||||
|
attributes.insert(WebSettings::OfflineStorageDatabaseEnabled, false);
|
||||||
|
attributes.insert(WebSettings::OfflineWebApplicationCacheEnabled, false);
|
||||||
|
attributes.insert(WebSettings::LocalStorageEnabled, false);
|
||||||
|
attributes.insert(WebSettings::LocalContentCanAccessRemoteUrls, false);
|
||||||
|
attributes.insert(WebSettings::LocalContentCanAccessFileUrls, true);
|
||||||
|
attributes.insert(WebSettings::AcceleratedCompositingEnabled, true);
|
||||||
|
attributes.insert(WebSettings::WebGLEnabled, true);
|
||||||
|
attributes.insert(WebSettings::WebAudioEnabled, false);
|
||||||
|
attributes.insert(WebSettings::CSSRegionsEnabled, true);
|
||||||
|
attributes.insert(WebSettings::CSSGridLayoutEnabled, false);
|
||||||
|
attributes.insert(WebSettings::HyperlinkAuditingEnabled, false);
|
||||||
|
attributes.insert(WebSettings::TiledBackingStoreEnabled, false);
|
||||||
|
attributes.insert(WebSettings::FrameFlatteningEnabled, false);
|
||||||
|
attributes.insert(WebSettings::SiteSpecificQuirksEnabled, true);
|
||||||
|
attributes.insert(WebSettings::ScrollAnimatorEnabled, false);
|
||||||
|
attributes.insert(WebSettings::CaretBrowsingEnabled, false);
|
||||||
|
attributes.insert(WebSettings::NotificationsEnabled, true);
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN32) && defined(DEBUG)
|
||||||
|
attributes.insert(WebSettings::DeveloperExtrasEnabled,true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
*/
|
||||||
|
WebSettings::WebSettings(AmneziaWebView *v)
|
||||||
|
: view(v)
|
||||||
|
|
||||||
|
{
|
||||||
|
allSettings()->append(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettings::~WebSettings()
|
||||||
|
{
|
||||||
|
if (view)
|
||||||
|
allSettings()->removeAll(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSettings::setFontSize(FontSize type, int size)
|
||||||
|
{
|
||||||
|
fontSizes.insert(type, size);
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebSettings::fontSize(FontSize type) const
|
||||||
|
{
|
||||||
|
int defaultValue = 0;
|
||||||
|
if (view) {
|
||||||
|
WebSettings* global = WebSettings::globalSettings();
|
||||||
|
defaultValue = global->fontSizes.value(type);
|
||||||
|
}
|
||||||
|
return fontSizes.value(type, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSettings::resetFontSize(FontSize type)
|
||||||
|
{
|
||||||
|
if (view) {
|
||||||
|
fontSizes.remove(type);
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSettings::setFontFamily(FontFamily which, const QString& family)
|
||||||
|
{
|
||||||
|
fontFamilies.insert(which, family);
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebSettings::fontFamily(FontFamily which) const
|
||||||
|
{
|
||||||
|
QString defaultValue;
|
||||||
|
if (view) {
|
||||||
|
WebSettings* global = WebSettings::globalSettings();
|
||||||
|
defaultValue = global->fontFamilies.value(which);
|
||||||
|
}
|
||||||
|
return fontFamilies.value(which, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSettings::resetFontFamily(FontFamily which)
|
||||||
|
{
|
||||||
|
if (view) {
|
||||||
|
fontFamilies.remove(which);
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSettings::setAttribute(WebAttribute attr, bool on)
|
||||||
|
{
|
||||||
|
attributes.insert(attr, on);
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSettings::testAttribute(WebAttribute attr) const
|
||||||
|
{
|
||||||
|
bool defaultValue = false;
|
||||||
|
if (view) {
|
||||||
|
WebSettings* global = WebSettings::globalSettings();
|
||||||
|
defaultValue = global->attributes.value(attr);
|
||||||
|
}
|
||||||
|
return attributes.value(attr, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSettings::resetAttribute(WebAttribute attr)
|
||||||
|
{
|
||||||
|
if (view) {
|
||||||
|
attributes.remove(attr);
|
||||||
|
apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#ifndef WEBSETTINGS_H
|
||||||
|
#define WEBSETTINGS_H
|
||||||
|
|
||||||
|
#include <QtQml>
|
||||||
|
|
||||||
|
class AmneziaWebView;
|
||||||
|
class AmneziaWebViewPrivate;
|
||||||
|
class WebSettingsData;
|
||||||
|
|
||||||
|
class WebSettings
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum FontFamily {
|
||||||
|
StandardFont,
|
||||||
|
FixedFont,
|
||||||
|
SerifFont,
|
||||||
|
SansSerifFont,
|
||||||
|
CursiveFont,
|
||||||
|
FantasyFont
|
||||||
|
};
|
||||||
|
enum WebAttribute {
|
||||||
|
AutoLoadImages,
|
||||||
|
JavascriptEnabled,
|
||||||
|
JavaEnabled,
|
||||||
|
PluginsEnabled,
|
||||||
|
PrivateBrowsingEnabled,
|
||||||
|
JavascriptCanOpenWindows,
|
||||||
|
JavascriptCanAccessClipboard,
|
||||||
|
DeveloperExtrasEnabled,
|
||||||
|
LinksIncludedInFocusChain,
|
||||||
|
ZoomTextOnly,
|
||||||
|
PrintElementBackgrounds,
|
||||||
|
OfflineStorageDatabaseEnabled,
|
||||||
|
OfflineWebApplicationCacheEnabled,
|
||||||
|
LocalStorageEnabled,
|
||||||
|
LocalContentCanAccessRemoteUrls,
|
||||||
|
DnsPrefetchEnabled,
|
||||||
|
XSSAuditingEnabled,
|
||||||
|
AcceleratedCompositingEnabled,
|
||||||
|
SpatialNavigationEnabled,
|
||||||
|
LocalContentCanAccessFileUrls,
|
||||||
|
TiledBackingStoreEnabled,
|
||||||
|
FrameFlatteningEnabled,
|
||||||
|
SiteSpecificQuirksEnabled,
|
||||||
|
JavascriptCanCloseWindows,
|
||||||
|
WebGLEnabled,
|
||||||
|
CSSRegionsEnabled,
|
||||||
|
HyperlinkAuditingEnabled,
|
||||||
|
CSSGridLayoutEnabled,
|
||||||
|
ScrollAnimatorEnabled,
|
||||||
|
CaretBrowsingEnabled,
|
||||||
|
NotificationsEnabled,
|
||||||
|
WebAudioEnabled
|
||||||
|
};
|
||||||
|
enum WebGraphic {
|
||||||
|
MissingImageGraphic,
|
||||||
|
MissingPluginGraphic,
|
||||||
|
DefaultFrameIconGraphic,
|
||||||
|
TextAreaSizeGripCornerGraphic,
|
||||||
|
DeleteButtonGraphic,
|
||||||
|
InputSpeechButtonGraphic,
|
||||||
|
SearchCancelButtonGraphic,
|
||||||
|
SearchCancelButtonPressedGraphic
|
||||||
|
};
|
||||||
|
enum FontSize {
|
||||||
|
MinimumFontSize,
|
||||||
|
MinimumLogicalFontSize,
|
||||||
|
DefaultFontSize,
|
||||||
|
DefaultFixedFontSize
|
||||||
|
};
|
||||||
|
enum ThirdPartyCookiePolicy {
|
||||||
|
AlwaysAllowThirdPartyCookies,
|
||||||
|
AlwaysBlockThirdPartyCookies,
|
||||||
|
AllowThirdPartyWithExistingCookies
|
||||||
|
};
|
||||||
|
|
||||||
|
static WebSettings *globalSettings();
|
||||||
|
|
||||||
|
void setFontSize(FontSize type, int size);
|
||||||
|
int fontSize(FontSize type) const;
|
||||||
|
void resetFontSize(FontSize type);
|
||||||
|
|
||||||
|
|
||||||
|
void setFontFamily(FontFamily which, const QString &family);
|
||||||
|
QString fontFamily(FontFamily which) const;
|
||||||
|
void resetFontFamily(FontFamily which);
|
||||||
|
|
||||||
|
void setAttribute(WebAttribute attr, bool on);
|
||||||
|
bool testAttribute(WebAttribute attr) const;
|
||||||
|
void resetAttribute(WebAttribute attr);
|
||||||
|
|
||||||
|
void apply();
|
||||||
|
|
||||||
|
WebSettings();
|
||||||
|
explicit WebSettings(AmneziaWebView *v);
|
||||||
|
virtual ~WebSettings();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class WebSettingsData;
|
||||||
|
friend class AmneziaWebViewPrivate;
|
||||||
|
friend class WebViewPrivate;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(WebSettings)
|
||||||
|
|
||||||
|
QHash<int, QString> fontFamilies;
|
||||||
|
QHash<int, int> fontSizes;
|
||||||
|
QHash<int, bool> attributes;
|
||||||
|
QString defaultTextEncoding;
|
||||||
|
AmneziaWebView *view;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class AmneziaWebViewSettings : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(int defaultFontSize READ defaultFontSize WRITE setDefaultFontSize)
|
||||||
|
Q_PROPERTY(QString standardFontFamily READ standardFontFamily WRITE setStandardFontFamily)
|
||||||
|
Q_PROPERTY(bool developerExtrasEnabled READ developerExtrasEnabled WRITE setDeveloperExtrasEnabled)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AmneziaWebViewSettings(AmneziaWebView *parent);
|
||||||
|
|
||||||
|
int defaultFontSize() const { return s->fontSize(WebSettings::DefaultFontSize); }
|
||||||
|
void setDefaultFontSize(int size) { s->setFontSize(WebSettings::DefaultFontSize, size); }
|
||||||
|
|
||||||
|
QString standardFontFamily() const { return s->fontFamily(WebSettings::StandardFont); }
|
||||||
|
void setStandardFontFamily(const QString& f) { s->setFontFamily(WebSettings::StandardFont, f); }
|
||||||
|
|
||||||
|
bool developerExtrasEnabled() const { return s->testAttribute(WebSettings::DeveloperExtrasEnabled); }
|
||||||
|
void setDeveloperExtrasEnabled(bool on) { s->setAttribute(WebSettings::DeveloperExtrasEnabled, on); }
|
||||||
|
|
||||||
|
void apply() { s->apply(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<WebSettings> s;
|
||||||
|
};
|
||||||
|
|
||||||
|
QML_DECLARE_TYPE(AmneziaWebViewSettings)
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"Keys": [ "AmneziaWebView" ]
|
||||||
|
}
|
||||||
@@ -163,6 +163,7 @@
|
|||||||
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
|
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
|
||||||
<file>ui/qml/Controls2/PageType.qml</file>
|
<file>ui/qml/Controls2/PageType.qml</file>
|
||||||
<file>ui/qml/Controls2/PopupType.qml</file>
|
<file>ui/qml/Controls2/PopupType.qml</file>
|
||||||
|
<file>ui/qml/Controls2/PremiumBannerType.qml</file>
|
||||||
<file>ui/qml/Controls2/ProgressBarType.qml</file>
|
<file>ui/qml/Controls2/ProgressBarType.qml</file>
|
||||||
<file>ui/qml/Controls2/ScrollBarType.qml</file>
|
<file>ui/qml/Controls2/ScrollBarType.qml</file>
|
||||||
<file>ui/qml/Controls2/StackViewType.qml</file>
|
<file>ui/qml/Controls2/StackViewType.qml</file>
|
||||||
@@ -226,6 +227,7 @@
|
|||||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageSetupWizardPremiumWebView.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
|
||||||
@@ -253,6 +255,10 @@
|
|||||||
<file>ui/qml/Components/AwgTextField.qml</file>
|
<file>ui/qml/Components/AwgTextField.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
||||||
<file>ui/qml/Components/SmartScroll.qml</file>
|
<file>ui/qml/Components/SmartScroll.qml</file>
|
||||||
|
<file>ui/qml/Components/AmneziaWebViewPanel.qml</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="/AmneziaWebView">
|
||||||
|
<file>core/webview/qmldir</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ namespace PageLoader
|
|||||||
PageSetupWizardQrReader,
|
PageSetupWizardQrReader,
|
||||||
PageSetupWizardApiServicesList,
|
PageSetupWizardApiServicesList,
|
||||||
PageSetupWizardApiServiceInfo,
|
PageSetupWizardApiServiceInfo,
|
||||||
|
PageSetupWizardPremiumWebView,
|
||||||
|
|
||||||
PageProtocolOpenVpnSettings,
|
PageProtocolOpenVpnSettings,
|
||||||
PageProtocolShadowSocksSettings,
|
PageProtocolShadowSocksSettings,
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import QtQuick 2.2
|
||||||
|
import AmneziaWebView 1.0
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
|
||||||
|
id: panel
|
||||||
|
|
||||||
|
property alias title: webView.title
|
||||||
|
property alias icon: webView.icon
|
||||||
|
property alias progress: webView.progress
|
||||||
|
property alias html: webView.html
|
||||||
|
property alias url: webView.url
|
||||||
|
property alias back: webView.back
|
||||||
|
property alias stop: webView.stop
|
||||||
|
property alias reload: webView.reload
|
||||||
|
property alias forward: webView.forward
|
||||||
|
property alias pressGrabTime: webView.pressGrabTime
|
||||||
|
|
||||||
|
property string onFailedUrl: ""
|
||||||
|
property string onFailedHtml: "<h2>" + qsTr("Can`t load page") + "</h2>"
|
||||||
|
property int preferredWidth: panel.width
|
||||||
|
property int preferredHeight: panel.height
|
||||||
|
|
||||||
|
property var scriptResult: undefined
|
||||||
|
property var requestResult: undefined
|
||||||
|
|
||||||
|
property var call_provider: undefined
|
||||||
|
property var call_data: undefined
|
||||||
|
|
||||||
|
property string status: "unknown" // success, failed
|
||||||
|
|
||||||
|
signal urlCalled(variant url)
|
||||||
|
signal alertCalled(variant message)
|
||||||
|
signal scriptCalled(string funName, variant args)
|
||||||
|
signal reloadCalled()
|
||||||
|
signal eventSended(variant event)
|
||||||
|
signal requestSended(variant request)
|
||||||
|
signal loadFinished()
|
||||||
|
signal loadFailed()
|
||||||
|
|
||||||
|
color: AmneziaStyle.color.onyxBlack
|
||||||
|
|
||||||
|
function call(funName, args) {
|
||||||
|
var script
|
||||||
|
if (panel.call_provider) {
|
||||||
|
panel.call_data = args
|
||||||
|
script = "call(" + funName + ")"
|
||||||
|
} else {
|
||||||
|
script = funName + "(" + JSON.stringify(args) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
webView.evaluateJavaScript(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
function event(e) {
|
||||||
|
call("receiveEvent", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(r) {
|
||||||
|
var req = r
|
||||||
|
req.result = call("receiveRequest", req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
function newEvent(type) {
|
||||||
|
var e = {}
|
||||||
|
e.type = type
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
function newRequest(type) {
|
||||||
|
var r = {}
|
||||||
|
r.type = type
|
||||||
|
r.result = null
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
function toogleScale() {
|
||||||
|
webView.doToogleScale()
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomIn() {
|
||||||
|
webView.doZoomIn()
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomOut() {
|
||||||
|
webView.doZoomOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AmneziaWebView {
|
||||||
|
id: webView
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property bool loaded: false
|
||||||
|
focus: true
|
||||||
|
smooth: false
|
||||||
|
backgroundColor: AmneziaStyle.color.onyxBlack
|
||||||
|
|
||||||
|
property int panelPreferredWidth: panel.preferredWidth
|
||||||
|
onPanelPreferredWidthChanged: {
|
||||||
|
webView.preferredWidth = panel.preferredWidth;
|
||||||
|
// if (webView.loaded)
|
||||||
|
// doZoomOrScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredWidth: panel.preferredWidth //webView.flexible ? panel.preferredWidth : Math.max(webView.contentsSize.width, 1024)
|
||||||
|
preferredHeight: panel.preferredHeight
|
||||||
|
|
||||||
|
contentsScale: 1
|
||||||
|
// onDoubleClick: {
|
||||||
|
// if (webView.flexible)
|
||||||
|
// return
|
||||||
|
|
||||||
|
// async.call(doToogleScale)
|
||||||
|
// }
|
||||||
|
|
||||||
|
function doToogleScale() {
|
||||||
|
// webView.needScale = !webView.needScale
|
||||||
|
// if (webView.needScale) {
|
||||||
|
// doScale()
|
||||||
|
// } else {
|
||||||
|
// webView.contentsScale = webView.zoomvalue
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function doZoomOrScale() {
|
||||||
|
// if (webView.needScale)
|
||||||
|
// doScale()
|
||||||
|
// else
|
||||||
|
// webView.contentsScale = webView.zoomvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doZoomIn() {
|
||||||
|
// if (webView.zoomvalue > 0.3) {
|
||||||
|
// webView.zoomvalue = webView.contentsScale
|
||||||
|
// webView.zoomvalue -= 0.1
|
||||||
|
// webView.contentsScale = webView.zoomvalue
|
||||||
|
// webView.needScale = false
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function doZoomOut() {
|
||||||
|
// if (webView.zoomvalue < 2.5) {
|
||||||
|
// webView.zoomvalue = webView.contentsScale
|
||||||
|
// webView.zoomvalue += 0.1
|
||||||
|
// webView.contentsScale = webView.zoomvalue
|
||||||
|
// webView.needScale = false
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function doScale() {
|
||||||
|
// var zoom = flickableItem.width / webView.preferredWidth
|
||||||
|
// webView.contentsScale = zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
pressGrabTime: 400
|
||||||
|
settings.defaultFontSize: 14
|
||||||
|
settings.standardFontFamily: "Arial"
|
||||||
|
//settings.developerExtrasEnabled: isDebugEnabled
|
||||||
|
onAlert: panel.alertCalled(message)
|
||||||
|
onUrlChanged: {
|
||||||
|
//flickableItem.contentX = 0
|
||||||
|
//flickableItem.contentY = 0
|
||||||
|
panel.urlCalled(url)
|
||||||
|
}
|
||||||
|
onLoadStarted: {
|
||||||
|
webView.loaded = false
|
||||||
|
//webView.contentsScale = 1
|
||||||
|
}
|
||||||
|
onLoadFinished: {
|
||||||
|
panel.status = "success"
|
||||||
|
webView.loaded = true
|
||||||
|
panel.loadFinished()
|
||||||
|
//async.call(doZoomOrScale)
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
console.debug("qml: html load failed: " + html)
|
||||||
|
if(html == ""){
|
||||||
|
if (panel.onFailedUrl !== "")
|
||||||
|
panel.url = panel.onFailedUrl;
|
||||||
|
else if (panel.onFailedHtml !== "")
|
||||||
|
panel.html = panel.onFailedHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.status = "failed"
|
||||||
|
panel.loadFailed()
|
||||||
|
}
|
||||||
|
javaScriptWindowObjects: [
|
||||||
|
QtObject {
|
||||||
|
AmneziaWebView.windowObjectName: "script"
|
||||||
|
function call(functionName, args) {
|
||||||
|
panel.scriptCalled(functionName, args)
|
||||||
|
return scriptResult;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
AmneziaWebView.windowObjectName: "send"
|
||||||
|
function event(e) {
|
||||||
|
panel.eventSended(e)
|
||||||
|
}
|
||||||
|
function request(r) {
|
||||||
|
var req = r
|
||||||
|
panel.requestSended(req)
|
||||||
|
req.result = requestResult
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
QtObject {
|
||||||
|
AmneziaWebView.windowObjectName: "log"
|
||||||
|
|
||||||
|
function error(msg){
|
||||||
|
webView.evaluateJavaScript("console.error('" + msg + "')")
|
||||||
|
return api.log.error(msg)
|
||||||
|
}
|
||||||
|
function warn(msg){
|
||||||
|
webView.evaluateJavaScript("console.warn('" + msg + "')")
|
||||||
|
return api.log.warn(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
function info(msg){
|
||||||
|
webView.evaluateJavaScript("console.info('" + msg + "')")
|
||||||
|
return api.log.info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
function debug(msg){
|
||||||
|
webView.evaluateJavaScript("console.log('" + msg + "')")
|
||||||
|
return api.log.debug(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
function time(tag, msg){
|
||||||
|
webView.evaluateJavaScript("console.log('" + msg + "')")
|
||||||
|
return api.log.time(tag, msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
AmneziaWebView.windowObjectName: "webView"
|
||||||
|
|
||||||
|
function scrollUp() {
|
||||||
|
//flickableItem.contentY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
//panel.reloadCalled()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
AmneziaWebView.windowObjectName: "call_provider"
|
||||||
|
function data(fn) {
|
||||||
|
return panel.call_provider ? panel.call_provider.data(fn, panel.call_data) : panel.call_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string headerText: qsTr("Try Amnesia Premium")
|
||||||
|
property string bodyText: qsTr("High speed and 20 countries to connect to 7 days free.")
|
||||||
|
|
||||||
|
property string hoveredColor: AmneziaStyle.color.charcoalGray
|
||||||
|
property string defaultColor: AmneziaStyle.color.onyxBlack
|
||||||
|
property int paddingContent: 20
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
height: contentItemRoot.implicitHeight
|
||||||
|
width: contentItemRoot.implicitWidth
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
id: backgroundRect
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: 16
|
||||||
|
|
||||||
|
color: root.hovered ? hoveredColor : defaultColor
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
PropertyAnimation { duration: 200 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
id: contentItemRoot
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitHeight: content.implicitHeight + root.paddingContent * 2
|
||||||
|
implicitWidth: content.implicitWidth + root.paddingContent * 2
|
||||||
|
anchors.margins: root.paddingContent
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: content
|
||||||
|
anchors.fill: parent
|
||||||
|
columns: 2
|
||||||
|
columnSpacing: 5
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: 0 // Позволяет сжиматься
|
||||||
|
implicitHeight: textColumn.implicitHeight
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: textColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
// Заголовок с выделением "Premium"
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: qsTr("Try Amnezia ") + '<span style="color: #E6007A;">Premium</span>'
|
||||||
|
textFormat: Text.RichText
|
||||||
|
color: AmneziaStyle.color.paleGray
|
||||||
|
font.pixelSize: 20
|
||||||
|
font.weight: 700
|
||||||
|
font.family: "PT Root UI VF"
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Описание
|
||||||
|
Text {
|
||||||
|
text: root.bodyText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: AmneziaStyle.color.mutedGray
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.weight: 400
|
||||||
|
font.family: "PT Root UI VF"
|
||||||
|
lineHeight: 20
|
||||||
|
lineHeightMode: Text.FixedHeight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Стрелка справа
|
||||||
|
Item {
|
||||||
|
implicitWidth: 40
|
||||||
|
implicitHeight: 40
|
||||||
|
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||||
|
Layout.minimumWidth: 40
|
||||||
|
Layout.maximumWidth: 40
|
||||||
|
Layout.preferredWidth: 40
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: "qrc:/images/controls/chevron-right.svg"
|
||||||
|
sourceSize: Qt.size(24, 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
onEntered: {
|
||||||
|
backgroundRect.color = root.hoveredColor
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: {
|
||||||
|
backgroundRect.color = root.defaultColor
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
root.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -204,6 +204,84 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 22
|
Layout.topMargin: 22
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PremiumBannerType {
|
||||||
|
id: premiumBannerHome
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.bottomMargin: 16
|
||||||
|
|
||||||
|
property bool isAmneziaFree: {
|
||||||
|
if (ServersModel.getServersCount() > 0 && ServersModel.isDefaultServerFromApi) {
|
||||||
|
var apiConfig = ServersModel.getDefaultServerData("apiConfig")
|
||||||
|
if (apiConfig) {
|
||||||
|
// Получаем serviceType из apiConfig (ключ "service_type")
|
||||||
|
var serviceType = ""
|
||||||
|
if (apiConfig.service_type !== undefined) {
|
||||||
|
serviceType = apiConfig.service_type
|
||||||
|
} else if (apiConfig.serviceType !== undefined) {
|
||||||
|
serviceType = apiConfig.serviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceType === "amnezia-free") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Альтернативная проверка через имя сервера
|
||||||
|
var serverName = ServersModel.defaultServerName
|
||||||
|
if (serverName) {
|
||||||
|
var nameLower = serverName.toString().toLowerCase()
|
||||||
|
if (nameLower.indexOf("free") >= 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ServersModel
|
||||||
|
function onDefaultServerIndexChanged() {
|
||||||
|
// Пересчитываем isAmneziaFree при изменении сервера
|
||||||
|
var apiConfig = ServersModel.getDefaultServerData("apiConfig")
|
||||||
|
var newValue = false
|
||||||
|
if (ServersModel.getServersCount() > 0 && ServersModel.isDefaultServerFromApi && apiConfig) {
|
||||||
|
var serviceType = ""
|
||||||
|
if (apiConfig.service_type !== undefined) {
|
||||||
|
serviceType = apiConfig.service_type
|
||||||
|
} else if (apiConfig.serviceType !== undefined) {
|
||||||
|
serviceType = apiConfig.serviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceType === "amnezia-free") {
|
||||||
|
newValue = true
|
||||||
|
} else {
|
||||||
|
var serverName = ServersModel.defaultServerName
|
||||||
|
if (serverName) {
|
||||||
|
var nameLower = serverName.toString().toLowerCase()
|
||||||
|
if (nameLower.indexOf("free") >= 0) {
|
||||||
|
newValue = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
premiumBannerHome.isAmneziaFree = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: isAmneziaFree
|
||||||
|
enabled: visible
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
PageController.goToPage(PageEnum.PageSetupWizardPremiumWebView)
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import "../Config"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool isAmneziaFreeSelected: false
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|
||||||
@@ -31,8 +33,8 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ColumnLayout {
|
||||||
id: listView
|
id: mainLayout
|
||||||
|
|
||||||
anchors.top: backButton.bottom
|
anchors.top: backButton.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -40,23 +42,46 @@ PageType {
|
|||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.topMargin: 16
|
anchors.topMargin: 16
|
||||||
|
|
||||||
header: ColumnLayout {
|
|
||||||
width: listView.width
|
|
||||||
|
|
||||||
BaseHeaderType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.bottomMargin: 24
|
|
||||||
|
|
||||||
headerText: qsTr("VPN by Amnezia")
|
|
||||||
descriptionText: qsTr("Choose a VPN service that suits your needs.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
model: SortFilterProxyModel {
|
BaseHeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.bottomMargin: 24
|
||||||
|
|
||||||
|
headerText: qsTr("VPN by Amnezia")
|
||||||
|
descriptionText: qsTr("Choose a VPN service that suits your needs.")
|
||||||
|
}
|
||||||
|
|
||||||
|
PremiumBannerType {
|
||||||
|
id: premiumBanner
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 16
|
||||||
|
|
||||||
|
visible: root.isAmneziaFreeSelected
|
||||||
|
enabled: visible
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
PageController.goToPage(PageEnum.PageSetupWizardPremiumWebView)
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
ListViewType {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
model: SortFilterProxyModel {
|
||||||
id: proxyApiServicesModel
|
id: proxyApiServicesModel
|
||||||
|
|
||||||
sourceModel: ApiServicesModel
|
sourceModel: ApiServicesModel
|
||||||
@@ -88,8 +113,29 @@ PageType {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (isServiceAvailable) {
|
if (isServiceAvailable) {
|
||||||
ApiServicesModel.setServiceIndex(proxyApiServicesModel.mapToSource(index))
|
var sourceIndex = proxyApiServicesModel.mapToSource(index)
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
|
||||||
|
// Устанавливаем индекс ПЕРЕД проверкой типа
|
||||||
|
ApiServicesModel.setServiceIndex(sourceIndex)
|
||||||
|
|
||||||
|
// Проверяем тип через метод
|
||||||
|
var serviceType = ApiServicesModel.getSelectedServiceType()
|
||||||
|
|
||||||
|
// Также проверяем имя напрямую из делегата
|
||||||
|
var nameLower = name ? name.toString().toLowerCase() : ""
|
||||||
|
var nameHasFree = nameLower.indexOf("free") >= 0
|
||||||
|
|
||||||
|
// Комбинированная проверка
|
||||||
|
var isAmneziaFree = (serviceType === "amnezia-free") || nameHasFree
|
||||||
|
|
||||||
|
if (isAmneziaFree) {
|
||||||
|
// Показываем баннер
|
||||||
|
root.isAmneziaFreeSelected = true
|
||||||
|
} else {
|
||||||
|
// Скрываем баннер и переходим на страницу сервиса
|
||||||
|
root.isAmneziaFreeSelected = false
|
||||||
|
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,5 +143,6 @@ PageType {
|
|||||||
Keys.onReturnPressed: clicked()
|
Keys.onReturnPressed: clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import Style 1.0
|
||||||
|
import AmneziaWebView 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string premiumUrl: LanguageModel.getCurrentLanguageIndex() === 1 // 1 = Russian
|
||||||
|
? "https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/premium"
|
||||||
|
: "https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/premium"
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||||
|
|
||||||
|
z: 1000 // Ensure BackButton is above WebView
|
||||||
|
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
// Focus handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: webViewContainer
|
||||||
|
|
||||||
|
anchors.top: backButton.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.topMargin: 16
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
AmneziaWebView {
|
||||||
|
id: webView
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
preferredWidth: webViewContainer.width
|
||||||
|
preferredHeight: webViewContainer.height
|
||||||
|
|
||||||
|
url: root.premiumUrl
|
||||||
|
|
||||||
|
onLoadFailed: {
|
||||||
|
console.error("Failed to load Premium page:", root.premiumUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoadFinished: {
|
||||||
|
console.log("Premium page loaded successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user