웹엔진 위젯 단순 브라우저 예제

Qt WebEngine 위젯을 기반으로 하는 간단한 브라우저입니다.

Simple Browser는 Qt WebEngine C++ classes 를 사용하여 다음 요소가 포함된 작은 웹 브라우저 애플리케이션을 개발하는 방법을 보여줍니다:

  • 저장된 페이지를 열고 창과 탭을 관리하기 위한 메뉴 모음.
  • URL을 입력하고 웹 페이지 검색 기록에서 앞뒤로 이동하기 위한 탐색 모음.
  • 탭 안에 웹 콘텐츠를 표시하는 다중 탭 영역.
  • 마우스를 가져간 링크를 표시하는 상태 표시줄.
  • 간단한 다운로드 관리자.

웹 콘텐츠를 새 탭 또는 별도의 창에서 열 수 있습니다. 웹 페이지에 액세스할 때 HTTP 및 프록시 인증을 사용할 수 있습니다.

예제 실행하기

에서 예제를 실행하려면 Qt Creator에서 Welcome 모드를 열고 Examples 에서 예제를 선택합니다. 자세한 내용은 예제 빌드 및 실행하기를 참조하세요.

클래스 계층 구조

구현할 주요 클래스의 다이어그램을 스케치하는 것부터 시작합니다:

  • Browser 는 애플리케이션 창을 관리하는 클래스입니다.
  • BrowserWindow 는 메뉴, 탐색 모음, TabWidget, 상태 표시줄을 보여주는 QMainWindow 입니다.
  • TabWidgetQTabWidget 이며 하나 또는 여러 개의 브라우저 탭을 포함합니다.
  • WebViewQWebEngineView 이며 WebPage 에 대한 보기를 제공하고 TabWidget 에 탭으로 추가됩니다.
  • WebPage 는 웹사이트 콘텐츠를 나타내는 QWebEnginePage 입니다.

또한 몇 가지 보조 클래스를 구현할 것입니다:

  • WebPopupWindow 는 팝업 창을 표시하는 QWidget 입니다.
  • DownloadManagerWidget 는 다운로드 목록을 구현하는 QWidget 입니다.

브라우저 메인 창 만들기

이 예제는 Browser 객체가 소유하는 여러 개의 메인 창을 지원합니다. 이 클래스는 DownloadManagerWidget 도 소유하고 있으며 북마크 및 히스토리 관리자와 같은 추가 기능에 사용될 수 있습니다.

main.cpp 에서 첫 번째 BrowserWindow 인스턴스를 생성하고 Browser 객체에 추가합니다. 명령줄에 인수가 전달되지 않으면 Qt 홈페이지가 열립니다:

int main(int argc, char **argv)
{
    QCoreApplication::setOrganizationName("QtExamples");

    QApplication app(argc, argv);
    app.setWindowIcon(QIcon(u":AppLogoColor.png"_s));
    QLoggingCategory::setFilterRules(u"qt.webenginecontext.debug=true"_s);

    QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);
    QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true);
    QWebEngineProfile::defaultProfile()->settings()->setAttribute(
            QWebEngineSettings::ScreenCaptureEnabled, true);

    QUrl url = commandLineUrlArgument();

    Browser browser;
    BrowserWindow *window = browser.createHiddenWindow();
    window->tabWidget()->setUrl(url);
    window->show();
    return app.exec();
}

창을 OpenGL 렌더링으로 전환할 때 깜박임을 억제하기 위해 첫 번째 브라우저 탭이 추가된 후 show를 호출합니다.

탭 생성하기

BrowserWindow 생성자는 필요한 모든 사용자 인터페이스 관련 객체를 초기화합니다. BrowserWindowcentralWidget 에는 TabWidget 인스턴스가 포함되어 있습니다. TabWidget 에는 하나 또는 여러 개의 WebView 인스턴스가 탭으로 포함되어 있으며, 현재 선택된 탭에 신호와 슬롯을 위임합니다:

class TabWidget : public QTabWidget
{
    ...
signals:
    // current tab/page signals
    void linkHovered(const QString &link);
    void loadProgress(int progress);
    void titleChanged(const QString &title);
    void urlChanged(const QUrl &url);
    void favIconChanged(const QIcon &icon);
    void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled);
    void devToolsRequested(QWebEnginePage *source);
    void findTextFinished(const QWebEngineFindTextResult &result);

public slots:
    // current tab/page slots
    void setUrl(const QUrl &url);
    void triggerWebPageAction(QWebEnginePage::WebAction action);
    ...
};

각 탭에는 WebView 의 인스턴스가 포함되어 있습니다:

WebView *TabWidget::createTab()
{
    WebView *webView = createBackgroundTab();
    setCurrentWidget(webView);
    return webView;
}

WebView *TabWidget::createBackgroundTab()
{
    WebView *webView = new WebView;
    WebPage *webPage = new WebPage(m_profile, webView);
    webView->setPage(webPage);
    setupView(webView);
    int index = addTab(webView, tr("(Untitled)"));
    setTabIcon(index, webView->favIcon());
    // Workaround for QTBUG-61770
    webView->resize(currentWidget()->size());
    webView->show();
    return webView;
}

TabWidget::setupView() 에서는 TabWidget 이 항상 현재 선택된 WebView 의 신호를 전달하도록 합니다:

void TabWidget::setupView(WebView *webView)
{
    QWebEnginePage *webPage = webView->page();

    connect(webView, &QWebEngineView::titleChanged, [this, webView](const QString &title) {
        int index = indexOf(webView);
        if (index != -1) {
            setTabText(index, title);
            setTabToolTip(index, title);
        }
        if (currentIndex() == index)
            emit titleChanged(title);
    });
    connect(webView, &QWebEngineView::urlChanged, [this, webView](const QUrl &url) {
        int index = indexOf(webView);
        if (index != -1)
            tabBar()->setTabData(index, url);
        if (currentIndex() == index)
            emit urlChanged(url);
    });
    connect(webView, &QWebEngineView::loadProgress, [this, webView](int progress) {
        if (currentIndex() == indexOf(webView))
            emit loadProgress(progress);
    });
    connect(webPage, &QWebEnginePage::linkHovered, [this, webView](const QString &url) {
        if (currentIndex() == indexOf(webView))
            emit linkHovered(url);
    });
    connect(webView, &WebView::favIconChanged, [this, webView](const QIcon &icon) {
        int index = indexOf(webView);
        if (index != -1)
            setTabIcon(index, icon);
        if (currentIndex() == index)
            emit favIconChanged(icon);
    });
    connect(webView, &WebView::webActionEnabledChanged, [this, webView](QWebEnginePage::WebAction action, bool enabled) {
        if (currentIndex() ==  indexOf(webView))
            emit webActionEnabledChanged(action,enabled);
    });
    connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() {
        int index = indexOf(webView);
        if (webView->page()->inspectedPage())
            window()->close();
        else if (index >= 0)
            closeTab(index);
    });
    connect(webView, &WebView::devToolsRequested, this, &TabWidget::devToolsRequested);
    connect(webPage, &QWebEnginePage::findTextFinished, [this, webView](const QWebEngineFindTextResult &result) {
        if (currentIndex() == indexOf(webView))
            emit findTextFinished(result);
    });
}

탭 닫기

사용자가 탭을 닫으면 먼저 해당 WebView 에서 RequestClose 웹 액션을 트리거합니다:

    connect(tabBar, &QTabBar::tabCloseRequested, [this](int index) {
        if (WebView *view = webView(index))
            view->page()->triggerAction(QWebEnginePage::WebAction::RequestClose);
    });

이렇게 하면 모든 JavaScript beforeunload 이벤트 리스너가 실행되어 사용자에게 페이지를 닫을 것인지 확인하는 대화 상자를 표시할 수 있습니다. 이 경우 사용자는 닫기 요청을 거부하고 탭을 열어 둘 수 있으며, 그렇지 않으면 windowCloseRequested 신호가 전송되고 탭이 닫힙니다:

    connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() {
        int index = indexOf(webView);
        if (webView->page()->inspectedPage())
            window()->close();
        else if (index >= 0)
            closeTab(index);
    });

WebView 기능 구현하기

WebViewQWebEngineView 에서 파생되어 다음과 같은 기능을 지원합니다:

  • renderProcess 죽는 경우 오류 메시지 표시
  • createWindow 요청 처리
  • 컨텍스트 메뉴에 사용자 정의 메뉴 항목 추가하기

먼저 필요한 메서드와 시그널을 사용하여 WebView 페이지를 만듭니다:

class WebView : public QWebEngineView
{
    Q_OBJECT

public:
    explicit WebView(QWidget *parent = nullptr);
    ...
protected:
    void contextMenuEvent(QContextMenuEvent *event) override;
    QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;

signals:
    void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled);
    ...
};
오류 메시지 표시

렌더링 프로세스가 종료되면 오류 코드와 함께 QMessageBox 를 표시한 다음 페이지를 다시 로드합니다:

WebView::WebView(QWidget *parent)
    : QWebEngineView(parent)
{
    ...
    connect(this, &QWebEngineView::renderProcessTerminated,
            [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) {
        QString status;
        switch (termStatus) {
        case QWebEnginePage::NormalTerminationStatus:
            status = tr("Render process normal exit");
            break;
        case QWebEnginePage::AbnormalTerminationStatus:
            status = tr("Render process abnormal exit");
            break;
        case QWebEnginePage::CrashedTerminationStatus:
            status = tr("Render process crashed");
            break;
        case QWebEnginePage::KilledTerminationStatus:
            status = tr("Render process killed");
            break;
        }
        QMessageBox::StandardButton btn = QMessageBox::question(window(), status,
                                                   tr("Render process exited with code: %1\n"
                                                      "Do you want to reload the page ?").arg(statusCode));
        if (btn == QMessageBox::Yes)
            QTimer::singleShot(0, this, &WebView::reload);
    });
}
웹창 관리

예를 들어 JavaScript 프로그램에서 새 창이나 대화 상자에서 문서를 열도록 요청하는 경우 로드된 페이지에 QWebEnginePage::WebWindowType 유형의 창이 생성될 수 있습니다. 이는 QWebView::createWindow() 을 재정의하여 처리합니다:

QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type)
{
    BrowserWindow *mainWindow = qobject_cast<BrowserWindow*>(window());
    if (!mainWindow)
        return nullptr;

    switch (type) {
    case QWebEnginePage::WebBrowserTab: {
        return mainWindow->tabWidget()->createTab();
    }

QWebEnginePage::WebDialog 의 경우 사용자 정의 WebPopupWindow 클래스의 인스턴스를 생성합니다:

class WebPopupWindow : public QWidget
{
    Q_OBJECT

public:
    explicit WebPopupWindow(QWebEngineProfile *profile);
    WebView *view() const;

private slots:
    void handleGeometryChangeRequested(const QRect &newGeometry);

private:
    QLineEdit *m_urlLineEdit;
    QAction *m_favAction;
    WebView *m_view;
};
컨텍스트 메뉴 항목 추가하기

컨텍스트 메뉴에 메뉴 항목을 추가하여 사용자가 마우스 오른쪽 버튼을 클릭하여 새 창에서 인스펙터를 열 수 있도록 합니다. QWebEngineView::contextMenuEvent 을 재정의하고 QWebEnginePage::createStandardContextMenu를 사용하여 기본 QWebEnginePage::WebAction 액션 목록이 포함된 기본 QMenu 을 만듭니다.

QWebEnginePage::InspectElement 액션의 기본 이름은 Inspect 입니다. 명확성을 위해 인스펙터가 아직 없는 경우 Open Inspector In New Window 로, 이미 생성된 경우 Inspect Element 로 이름을 바꿉니다.

또한 메뉴에 QWebEnginePage::ViewSource 액션이 있는지 확인합니다. 그렇지 않은 경우 구분 기호도 추가해야 하기 때문입니다.

void WebView::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu *menu = createStandardContextMenu();
    const QList<QAction *> actions = menu->actions();
    auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement));
    if (inspectElement == actions.cend()) {
        auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource));
        if (viewSource == actions.cend())
            menu->addSeparator();

        QAction *action = menu->addAction("Open inspector in new window");
        connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); });
    } else {
        (*inspectElement)->setText(tr("Inspect element"));
    }

    // add conext menu for image policy
    QMenu *editImageAnimation = new QMenu(tr("Image animation policy"));

    m_imageAnimationGroup = new QActionGroup(editImageAnimation);
    m_imageAnimationGroup->setExclusive(true);

    QAction *disableImageAnimation =
            editImageAnimation->addAction(tr("Disable all image animation"));
    disableImageAnimation->setCheckable(true);
    m_imageAnimationGroup->addAction(disableImageAnimation);
    connect(disableImageAnimation, &QAction::triggered, [this]() {
        handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Disallow);
    });
    QAction *allowImageAnimationOnce =
            editImageAnimation->addAction(tr("Allow animated images, but only once"));
    allowImageAnimationOnce->setCheckable(true);
    m_imageAnimationGroup->addAction(allowImageAnimationOnce);
    connect(allowImageAnimationOnce, &QAction::triggered,
            [this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::AnimateOnce); });
    QAction *allowImageAnimation = editImageAnimation->addAction(tr("Allow all animated images"));
    allowImageAnimation->setCheckable(true);
    m_imageAnimationGroup->addAction(allowImageAnimation);
    connect(allowImageAnimation, &QAction::triggered, [this]() {
        handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Allow);
    });

    switch (page()->settings()->imageAnimationPolicy()) {
    case QWebEngineSettings::ImageAnimationPolicy::Allow:
        allowImageAnimation->setChecked(true);
        break;
    case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
        allowImageAnimationOnce->setChecked(true);
        break;
    case QWebEngineSettings::ImageAnimationPolicy::Disallow:
        disableImageAnimation->setChecked(true);
        break;
    default:
        allowImageAnimation->setChecked(true);
        break;
    }

    menu->addMenu(editImageAnimation);
    menu->popup(event->globalPos());
}

웹페이지 및 웹뷰 기능 구현하기

QWebEnginePage 의 서브클래스로 WebPage, WebViewQWebEngineView 의 서브클래스로 구현하여 웹 페이지에 액세스할 때 HTTP, 프록시 인증을 활성화하고 SSL 인증서 오류를 무시할 수 있도록 합니다:

class WebPage : public QWebEnginePage
{
    Q_OBJECT

public:
    explicit WebPage(QWebEngineProfile *profile, QObject *parent = nullptr);

signals:
    void createCertificateErrorDialog(QWebEngineCertificateError error);

private slots:
    void handleCertificateError(QWebEngineCertificateError error);
    void handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection);
    void handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request);
};

class WebView : public QWebEngineView
{
    Q_OBJECT

public:
    explicit WebView(QWidget *parent = nullptr);
    ~WebView();
    void setPage(WebPage *page);

    int loadProgress() const;
    bool isWebActionEnabled(QWebEnginePage::WebAction webAction) const;
    QIcon favIcon() const;

protected:
    void contextMenuEvent(QContextMenuEvent *event) override;
    QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;

signals:
    void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled);
    void favIconChanged(const QIcon &icon);
    void devToolsRequested(QWebEnginePage *source);
private slots:
    void handleCertificateError(QWebEngineCertificateError error);
    void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth);
    void handlePermissionRequested(QWebEnginePermission permission);
    void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth,
                                           const QString &proxyHost);
    void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request);
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
    void handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request);
    void handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request);
#endif
    void handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy);

private:
    void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction);
    void onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state);

private:
    int m_loadProgress = 100;
    WebAuthDialog *m_authDialog = nullptr;
    QActionGroup *m_imageAnimationGroup = nullptr;
};

위의 모든 경우에 사용자에게 적절한 대화 상자를 표시합니다. 인증의 경우 QAuthenticator 객체에 올바른 자격 증명 값을 설정해야 합니다:

void WebView::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
{
    QDialog dialog(window());
    dialog.setModal(true);
    dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);

    Ui::PasswordDialog passwordDialog;
    passwordDialog.setupUi(&dialog);

    passwordDialog.m_iconLabel->setText(QString());
    QIcon icon(window()->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, window()));
    passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));

    QString introMessage(tr("Enter username and password for \"%1\" at %2")
                                 .arg(auth->realm(),
                                      requestUrl.toString().toHtmlEscaped()));
    passwordDialog.m_infoLabel->setText(introMessage);
    passwordDialog.m_infoLabel->setWordWrap(true);

    if (dialog.exec() == QDialog::Accepted) {
        auth->setUser(passwordDialog.m_userNameLineEdit->text());
        auth->setPassword(passwordDialog.m_passwordLineEdit->text());
    } else {
        // Set authenticator null if dialog is cancelled
        *auth = QAuthenticator();
    }
}

handleProxyAuthenticationRequired 시그널 핸들러는 HTTP 프록시 인증에 대해 동일한 단계를 구현합니다.

SSL 오류의 경우, 메인 프레임에서 발생한 오류인지 페이지 내부의 리소스에서 발생한 오류인지 확인합니다. 리소스 오류는 사용자가 결정을 내릴 수 있는 충분한 컨텍스트가 없기 때문에 자동으로 인증서 거부를 트리거합니다. 다른 모든 경우에는 사용자가 인증서를 허용하거나 거부할 수 있는 대화 상자를 트리거합니다.

void WebPage::handleCertificateError(QWebEngineCertificateError error)
{
    // Automatically block certificate errors from page resources without prompting the user.
    // This mirrors the behavior found in other major browsers.
    if (!error.isMainFrame()) {
        error.rejectCertificate();
        return;
    }

    error.defer();
    QTimer::singleShot(0, this,
                       [this, error]() mutable { emit createCertificateErrorDialog(error); });
}

웹 페이지 열기

이 섹션에서는 새 페이지를 여는 워크플로에 대해 설명합니다. 사용자가 탐색 모음에 URL을 입력하고 Enter 을 누르면 QLineEdit::returnPressed 신호가 방출되고 새 URL이 TabWidget::setUrl 으로 전달됩니다:

BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools)
{
    ...
        connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() {
            m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text()));
        });
    ...
}

호출은 현재 선택된 탭으로 전달됩니다:

void TabWidget::setUrl(const QUrl &url)
{
    if (WebView *view = currentWebView()) {
        view->setUrl(url);
        view->setFocus();
    }
}

WebViewsetUrl() 방식은 url 을 연결된 WebPage 으로 전달하여 백그라운드에서 페이지 콘텐츠의 다운로드를 시작합니다.

비공개 브라우징 구현하기

비공개 브라우징, 시크릿 모드 또는 오프 더 레코드 모드는 쿠키, HTTP 캐시 또는 검색 기록과 같은 일반적으로 영구적인 데이터가 디스크에 흔적을 남기지 않고 메모리에만 보관되는 많은 브라우저의 기능입니다. 이 예에서는 창 수준에서 일반 또는 비공개 모드의 탭을 모두 하나의 창에 배치하여 비공개 브라우징을 구현하겠습니다. 또는 탭 수준에서 비공개 브라우징을 구현하여 한 창의 일부 탭은 일반 모드로, 다른 탭은 비공개 모드로 설정할 수도 있습니다.

비공개 브라우징을 구현하는 방법은 Qt WebEngine 을 사용하면 아주 쉽습니다. QWebEngineProfile 을 새로 만들어 기본 프로필 대신 QWebEnginePage 에서 사용하기만 하면 됩니다. 이 예제에서 이 새 프로필은 Browser 객체가 소유합니다:

class Browser
{
public:
    ...
    BrowserWindow *createHiddenWindow(bool offTheRecord = false);
    BrowserWindow *createWindow(bool offTheRecord = false);
private:
    ...
    QScopedPointer<QWebEngineProfile> m_profile;
};

비공개 브라우징을 위한 필수 프로필은 첫 번째 창과 함께 생성됩니다. QWebEngineProfile 의 기본 생성자는 이미 오프 더 레코드 모드로 설정되어 있습니다.

BrowserWindow *Browser::createHiddenWindow(bool offTheRecord)
{
    if (!offTheRecord && !m_profile) {
        const QString name = u"simplebrowser."_s + QLatin1StringView(qWebEngineChromiumVersion());
        m_profile.reset(new QWebEngineProfile(name));
    ...

남은 작업은 적절한 프로필을 적절한 QWebEnginePage 객체에 전달하기만 하면 됩니다. Browser 객체는 각각의 새로운 BrowserWindow 객체에 글로벌 기본 프로필( QWebEngineProfile::defaultProfile 참조) 또는 공유 오프더레코드 프로필 인스턴스 중 하나를 전달합니다:

    ...
        QObject::connect(m_profile.get(), &QWebEngineProfile::downloadRequested,
                         &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested);
    }
    auto profile = !offTheRecord ? m_profile.get() : QWebEngineProfile::defaultProfile();
    auto mainWindow = new BrowserWindow(this, profile, false);
    return mainWindow;
}

그러면 BrowserWindowTabWidget 객체는 창에 포함된 모든 QWebEnginePage 객체가 이 프로필을 사용하도록 합니다.

다운로드 관리

다운로드는 QWebEngineProfile 에 연결됩니다. 웹 페이지에서 다운로드가 트리거될 때마다 QWebEngineProfile::downloadRequested 신호가 QWebEngineDownloadRequest 로 전송되며, 이 예에서는 DownloadManagerWidget::downloadRequested 으로 전달됩니다:

Browser::Browser()
{
    // Quit application if the download manager window is the only remaining window
    m_downloadManagerWidget.setAttribute(Qt::WA_QuitOnClose, false);

    QObject::connect(
        QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested,
        &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested);
}

이 메서드는 사용자에게 파일 이름을 입력하라는 메시지를 표시하고(미리 입력된 제안과 함께) 다운로드를 시작합니다(사용자가 Save As 대화 상자를 취소하지 않는 한):

void DownloadManagerWidget::downloadRequested(QWebEngineDownloadRequest *download)
{
    Q_ASSERT(download && download->state() == QWebEngineDownloadRequest::DownloadRequested);

    QString path = QFileDialog::getSaveFileName(this, tr("Save as"), QDir(download->downloadDirectory()).filePath(download->downloadFileName()));
    if (path.isEmpty())
        return;

    download->setDownloadDirectory(QFileInfo(path).path());
    download->setDownloadFileName(QFileInfo(path).fileName());
    download->accept();
    add(new DownloadWidget(download));

    show();
}

QWebEngineDownloadRequest 객체는 다운로드 진행 상황을 잠재적 관찰자에게 알리기 위해 receivedBytesChanged 신호를 주기적으로 전송하고 다운로드가 완료되거나 오류가 발생하면 stateChanged 신호를 전송합니다. 이러한 신호를 처리하는 방법에 대한 예는 downloadmanagerwidget.cpp 을 참조하세요.

WebAuth/FIDO UX 요청 관리하기

WebAuth UX 요청은 QWebEnginePage 와 연결됩니다. 인증자에 사용자 상호 작용이 필요할 때마다 QWebEnginePage 에서 UX 요청이 트리거되고 QWebEnginePage::webAuthUxRequested 신호가 QWebEngineWebAuthUxRequest 로 전송되며, 이 예에서는 WebView::handleAuthenticatorRequired 으로 전달됩니다:

    connect(page, &QWebEnginePage::webAuthUxRequested, this, &WebView::handleWebAuthUxRequested);

이 메서드는 WebAuth UX 대화 상자를 생성하고 UX 요청 흐름을 시작합니다.

void WebView::handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
{
    if (m_authDialog)
        delete m_authDialog;

    m_authDialog = new WebAuthDialog(request, window());
    m_authDialog->setModal(false);
    m_authDialog->setWindowFlags(m_authDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);

    connect(request, &QWebEngineWebAuthUxRequest::stateChanged, this, &WebView::onStateChanged);
    m_authDialog->show();
}

QWebEngineWebAuthUxRequest 객체는 stateChanged 신호를 주기적으로 전송하여 잠재적 관찰자에게 현재 WebAuth UX 상태를 알립니다. 관찰자는 그에 따라 WebAuth 대화 상자를 업데이트합니다. 이러한 신호를 처리하는 방법에 대한 예는 webview.cppwebauthdialog.cpp 을 참조하세요.

macOS용 서명 요구 사항

macOS에서 Simple Browser를 실행할 때 웹 사이트에서 위치, 카메라 및 마이크에 액세스할 수 있도록 허용하려면 애플리케이션에 서명해야 합니다. 이 작업은 빌드 시 자동으로 수행되지만 빌드 환경에 대해 유효한 서명 ID를 설정해야 합니다.

파일 및 어트리뷰션

이 예제에서는 탱고 아이콘 라이브러리의 아이콘을 사용합니다:

예제 프로젝트 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.