[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pearsonca/rstudio/master/src/cpp/desktop/DesktopBrowserWindow.cpp [Back]  [Original]

/*
 * DesktopBrowserWindow.cpp
 *
 * Copyright (C) 2009-19 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "DesktopBrowserWindow.hpp"

#include 
#include 
#include 
#include 
#include 
#include 

#include "DesktopInfo.hpp"
#include "DesktopOptions.hpp"

namespace rstudio {
namespace desktop {

namespace {

void initializeWebchannel(BrowserWindow* pWindow)
{
   auto* page = pWindow->webView()->webPage();
   
   // create web channel
   auto* channel = new QWebChannel(pWindow);
   channel->registerObject(QStringLiteral("desktopInfo"), &desktopInfo());
   page->setWebChannel(channel);
   
   // load qwebchannel.js
   QFile webChannelJsFile(QStringLiteral(":/qtwebchannel/qwebchannel.js"));
   if (!webChannelJsFile.open(QFile::ReadOnly))
      qWarning() scripts().insert(script);
}

// Partial fix for VoiceOver / Screen Reader support on macOS, based on the work described
// in link below. This is the subset that is in application code, more complete fix also requires
// changes to QtWebEngine itself, tentatively expected in Qt 5.14.
//
// This partial fix somewhat improves behavior of VoiceOver on desktop RStudio in that more
// keyboard focus changes are noticed and announced than without the change. Still
// it is far from usable and at this stage is mostly for fact-finding experimentation.
//
// https://codereview.qt-project.org/c/qt/qtwebengine/+/281210
//
class BrowserWindowAccessibility : public QAccessibleWidget
{
   static QAccessibleInterface *findFocusChild(QAccessibleInterface *iface)
   {
      if (!iface)
         return nullptr;

      if (iface->state().focused)
         return iface;

      for (int i = 0; i < iface->childCount(); ++i)
      {
         if (QAccessibleInterface *focusChild = findFocusChild(iface->child(i)))
            return focusChild;
      }

      return nullptr;
   }

public:
   BrowserWindowAccessibility(BrowserWindow *bw) : QAccessibleWidget(bw, QAccessible::Window)
   {
   }

   QAccessibleInterface *focusChild() const override
   {
      QAccessibleInterface *iface = QAccessibleWidget::focusChild();

      BrowserWindow *bw = qobject_cast(widget());
      if (iface == QAccessible::queryAccessibleInterface(bw->webView()->focusWidget()))
      {
         QAccessibleInterface *viewInterface = QAccessible::queryAccessibleInterface(bw->webView());
         return findFocusChild(viewInterface->child(0));
      }

      return iface;
   }
};

QAccessibleInterface *accessibleFactory(const QString &key, QObject *object)
{
   Q_UNUSED(key)

   if (BrowserWindow *bw = qobject_cast(object))
      return new BrowserWindowAccessibility(bw);

   return nullptr;
}

} // end anonymous namespace

BrowserWindow::BrowserWindow(bool showToolbar,
                             bool adjustTitle,
                             QString name,
                             QUrl baseUrl,
                             QWidget* pParent,
                             WebPage* pOpener,
                             bool allowExternalNavigate) :
   QMainWindow(pParent),
   name_(name),
   pOpener_(pOpener)
{
#ifdef Q_OS_MAC
   // NSAccessibility queries the window for the focused accessibility
   // element. QAccessibleWidget::focusChild() returns the accessibility interface
   // of the RenderWidgetHostViewQtDelegateWidget because it has the active
   // focus instead of the QWebEngineView, so install accessibility factory to
   // compensate for this macOS-specific behavior.
   if (desktop::options().enableAccessibility())
      QAccessible::installFactory(&accessibleFactory);
#endif

   adjustTitle_ = adjustTitle;
   progress_ = 0;

   pView_ = new WebView(baseUrl, this, allowExternalNavigate);
   connect(pView_, SIGNAL(titleChanged(QString)), SLOT(adjustTitle()));
   connect(pView_, SIGNAL(loadProgress(int)), SLOT(setProgress(int)));
   connect(pView_, SIGNAL(loadFinished(bool)), SLOT(finishLoading(bool)));
   
   initializeWebchannel(this);
   
   // set zoom factor
   double zoomLevel = options().zoomLevel();
   pView_->setZoomFactor(zoomLevel);

   // Kind of a hack to new up a toolbar and not attach it to anything.
   // Once it is clear what secondary browser windows look like we can
   // decide whether to keep this.
   pToolbar_ = showToolbar ? addToolBar(tr("Navigation")) : new QToolBar();
   pToolbar_->setMovable(false);

   setCentralWidget(pView_);

   desktop::enableFullscreenMode(this, false);
}

void BrowserWindow::closeEvent(QCloseEvent *event)
{
   if (pOpener_ == nullptr)
   {
      // if we don't know where we were opened from, check window.opener
      // (note that this could also be empty)
      QString cmd = QString::fromUtf8("if (window.opener && "
         "window.opener.unregisterDesktopChildWindow)"
         "   window.opener.unregisterDesktopChildWindow('");
      cmd.append(name_);
      cmd.append(QString::fromUtf8("');"));
      webPage()->runJavaScript(cmd);
   }
   else if (pOpener_)
   {
      // if we do know where we were opened from and it has the appropriate
      // handlers, let it know we're closing
      QString cmd = QString::fromUtf8(
                  "if (window.unregisterDesktopChildWindow) "
                  "   window.unregisterDesktopChildWindow('");
      cmd.append(name_);
      cmd.append(QString::fromUtf8("');"));
      webPage()->runJavaScript(cmd);
   }

   // forward the close event to the page
   webPage()->event(event);
}

void BrowserWindow::adjustTitle()
{
   if (adjustTitle_)
      setWindowTitle(pView_->title());
}

void BrowserWindow::setProgress(int p)
{
   progress_ = p;
   adjustTitle();
}

void BrowserWindow::finishLoading(bool succeeded)
{
   progress_ = 100;
   adjustTitle();

   if (succeeded)
   {
      // If the screen that the Window is being shown on has changed,
      // then force a quick re-size of the window so that the contents
      // can be re-rendered. This helps fix issues where the window is
      // moved between displays with different DPIs, where the text
      // can occasionally be briefly 'fuzzy'.
      QObject::connect(
               this->window()->windowHandle(),
               &QWindow::screenChanged,
               [&]()
      {
         // We use a timer here as it's been observed messing with the
         // window size while the screenChanged signal is being handled
         // can cause the screenChanged signal to be repeatedly fired
         // during the window move.
         QTimer::singleShot(0, [&]() {
            QSize originalSize = size();
            resize(originalSize.width(), originalSize.height() + 1);
            resize(originalSize.width(), originalSize.height() + 0);
         });

      });

      QString cmd = QString::fromUtf8("if (window.opener && "
         "window.opener.registerDesktopChildWindow)"
         "   window.opener.registerDesktopChildWindow('");
      cmd.append(name_);
      cmd.append(QString::fromUtf8("', window);"));
      webPage()->runJavaScript(cmd);
   }
}

WebView* BrowserWindow::webView()
{
   return pView_;
}

void BrowserWindow::avoidMoveCursorIfNecessary()
{
#ifdef Q_OS_MAC
   webView()->page()->runJavaScript(
         QString::fromUtf8("document.body.className = document.body.className + ' avoid-move-cursor'"));
#endif
}

QWidget* BrowserWindow::asWidget()
{
   return this;
}

WebPage* BrowserWindow::webPage()
{
   return webView()->webPage();
}

void BrowserWindow::postWebViewEvent(QEvent *keyEvent)
{
   for (auto* child : webView()->children())
      QApplication::sendEvent(child, keyEvent);
}

void BrowserWindow::triggerPageAction(QWebEnginePage::WebAction action)
{
   webView()->triggerPageAction(action);
}

QString BrowserWindow::getName()
{
   return name_;
}

WebPage* BrowserWindow::opener()
{
   return pOpener_;
}

} // namespace desktop
} // namespace rstudio

Web Proxy Viewer  |  New URL  |  Original Page