Infinite nested browser: breaking the Matryoshka effect

Matrioshka effect with infinite nested windows

It has become increasingly common to expect desktop applications to be capable of integrating to the web to incorporate available external information.

There are several ways this could be achieved: by consuming web services to get the raw data; or, by simply including a browser component within the application, in the form of a panel. In this second case, we’ll be capable of navigating to sites displaying up-to-date information or external multimedia content, such as maps, videos, images, and sound —as an example, visit Fish Facts, a live demo that shows basic GUI virtualization capabilities as well as an integration with the web browser and external web resources.

Once we transform these applications with Thinfinity® VirtualUI™, and we invoke them from the web, an effect similar to the famous Russian Nested Dolls case occurs: we run from the browser an application that contains within itself another browser. In short, we surf to an application that internally re-surfs. This is not bad in itself, but we can take advantage of already being in the web to access the final resource directly from the main browser. By doing this we will avoid a roundabout that, among other things, would unnecessarily increase the consumption of resources.

Let’s take a look at the diagrams of the same application running on a desktop…
Matryoshka-image1

… and from the Web:

Matryoshka-image2
As we can see, when we run the application from the desktop we must necessarily navigate through an embedded Webbrowser component. The application is the one that connects to the desired web address, and this webpage is seen in an internal panel, within the application.

But once we load the application from a browser we would experience the aforementioned effect. As a result, the application browses on its own, and the visible result of that surfing is re-transferred to our browser.

If we transfer that navigation to the only browser we need to use, we will access the intended resource without delay or additional bandwidth, processing, or time consumption.

image00

The demo application

The application we use in the example (whose complete source can be found here) attempts to break this browser-within-the-browser effect, simply by using the internal browser only when the application is accessed from the desktop. If the application is being accessed from the web, instead of using that component, the app would load the desired page in an iframe created on the fly. To achieve this integration (which requires a little bit more interaction between the application and the browser) we will add a jsRO object, which will control the remote iframe from the application.

The first addition to our application will be the addition of VirtualUI and its initialization:

using System;
using System.Windows.Forms;
namespace matryoshka
{
   static class Program
   {
       /// <summary>
       /// The main entry point for the application.
       /// </summary>
       [STAThread]
       static void Main()
       {
           new Cybele.Thinfinity.VirtualUI().Start();
           Application.EnableVisualStyles();
           Application.SetCompatibleTextRenderingDefault(false);
           Application.Run(new Form1());
       }
   }
}

Let’s now see what we have to add to the main form code, which it only has a panel where to enter the browsing URL and another one where the WebBrowser component is located.
The following is the application code before adding the changes:

using System;
using System.Drawing;
using System.Windows.Forms;
namespace matryoshka
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            // Sets Google Maps URL to run in the embedded browser
            txtURL.Text = "https://maps.google.com";
            Navigate();
        }
        private void btnGo_Click(object sender, EventArgs e)
        {
            Navigate();
        }
        private void txtURL_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (Convert.ToInt32(e.KeyChar) == 13)
            {
                Navigate();
            }
        }
        private void Navigate()
        {
            if (!txtURL.Text.Equals(""))
            {
                if (!txtURL.Text.StartsWith("http"))
                {
                    txtURL.Text = "http://" + txtURL.Text;
                }
                webBrowser1.Navigate(new Uri(txtURL.Text));
            }
            else {
                webBrowser1.Navigate(new Uri("about:_blank"));
            }
        }
    }
}

As a first step, we will add to the class the objects in the Cybele.Thinfinity library:

using System;
using System.Drawing;
using System.Windows.Forms;
using Cybele.Thinfinity;
namespace matryoshka
{
    public partial class Form1 : Form
    {
        private VirtualUI vui = new VirtualUI();
        private IJSObject mRemoteLayout = null;
        public Form1()
        ...

This demo application loads a Google Map. To be run embedded in an iframe, Google Maps needs a different URL from the one used on a full page. We know that it will run embedded in the iframe (using VirtualUI) and directly when loaded into the internal browser.

public Form1()
{
    InitializeComponent();
    if (vui.Active)
    {
        // Code executed only when the application runs with Thinfinity VirtualUI
        // Sets Google Maps URL to run embedded in an iframe
        txtURL.Text = "https://www.google.com/maps/embed";
    }
    else
    {
        //* Code executed only when the application runs from desktop
        // Sets Google Maps URL to run in the embedded browser
        txtURL.Text = "https://maps.google.com";
    }
}

Additionally, we must also make some changes to Navigate( ):

private void Navigate()
{
    string sURL = "";
    if (txtURL.Text.Equals("")){
        sURL = "about:_blank";
    }
    else
    {
        sURL = txtURL.Text;
        if (!sURL.StartsWith("http"))
        {
            sURL = "http://" + sURL;
        }
    }
    if (vui.Active) {
        mRemoteLayout.Properties["url"].AsString = sURL;
    }
    else {
        webBrowser1.Navigate(new Uri(sURL));
    }
} 

The last (but not least important) addition is the one concerning the jsRO object management: the first method, for its instantiation and definition of their getters; the other one, for the later updating of the iframe’s bounds.

private void Form1_Shown(object sender, EventArgs e)
{
    // Creates jsRO object for iframe control
    mRemoteLayout = new JSObject("layout");
    mRemoteLayout.Properties.Add("windowId").AsString =
                String.Format("virtualui_canvas_{0}", Handle);
    mRemoteLayout.Properties.Add("url").AsString = txtURL.Text;
    mRemoteLayout.Properties.Add("bounds")
        .OnGet(new JSBinding(        // Adds a "getter" to bounds
            delegate(IJSObject AObj, IJSProperty AProp)
            {
                // Returns a JSON object
                Point p = pnlNav.PointToScreen(new Point(0, 0));
                p.Offset(-Left, -Top);
                AProp.AsJSON = "{" + String.Format("\"left\":{0}, \"top\":{1}, \"width\":{2}, \"height\":{3}",
                     p.X, p.Y, pnlNav.Width, pnlNav.Height) + "}";
            }));
    mRemoteLayout.ApplyModel();
}
private void pnlNav_Resize(object sender, EventArgs e)
{
    // Updates all mRemoteLayout's properties "getters"
    if (mRemoteLayout != null)
    {
        mRemoteLayout.ApplyChanges();
    }
}

We can now introduce the additional information that must be included, at the javascript level, to the HTML page.
What follows is the base page to which we will be adding the functionality:

We will now create three global variables for the VirtualUI and jsRO objects, and for the iframe created on the fly.

<script type="text/javascript">
var virtualUI = null;
var jsro = null;
var nav = null;
$(document).ready(function () {
    ...

We will add three functions intended to create the iframe and keep it updated:

...
$(document).ready(function () {
    var navigate = function(url) {
        nav.src = url;
    }
    var applyBounds = function(bounds) {
        nav.style.left = bounds.left + "px";
        nav.style.top = bounds.top + "px";
        nav.style.width = bounds.width + "px";
        nav.style.height = bounds.height + "px";
    }
    var createNav = function () {
        nav = document.createElement("iframe");
        nav.id = "nav";
        nav.style.position = "absolute";
        nav.style.display = "none";
        nav.style.zIndex = 2;
        nav.style.border = "none";
    }
    ...

We will now instantiate the VirtualUI object, configure its events, and then connect to the application:

var createNav = function () {
   ...
   ...
}
virtualUI = new Thinfinity.VirtualUI();
virtualUI.onError = function (errorMsg) {
    alert("Application load failed");
};
virtualUI.onLoading = function () {
    console.log((virtualUI.devMode) ? "Waiting for application..." : "Loading...");
};
virtualUI.onClose = function (url) {
    if ((typeof url != 'undefined') && (url != '') && (url != null)) {
        if ((virtualUI.devMode != true) || (window.top.opener)) {
           window.top.close();
        }
        window.top.location.href = url;
        return;
    }
    if (virtualUI.devMode) { location.reload(); }
    if ((virtualUI.devMode != true) || (window.opener)) { window.close(); }
    if ((window.top == window) && (document.referrer) && (location.href != document.referrer)) {
        location.href = document.referrer;
    }
    else {
        if (nav) { nav.style.display = "none"; }
        alert("Application closed");
    }
};
// -- Connect to server...
virtualUI.connect();

Finally, we will create the jsRO object instance. Please note that it’s in the creation of the layout jsRO model (defined in the application) where the iframe is injected into the application window. The other two events are used to keep both the URL and the inserted iframe bounds updated:

...
virtualUI.connect();
// Defining Javascript Remote Objects Elements
jsro = new Thinfinity.JsRO();
jsro.on('model:layout', 'created', function (obj) {
    layout = jsro.model.layout;
    if (nav == null) {
        createNav();
        document.getElementById(layout.windowId).appendChild(nav);
        nav.style.display = "inline-block";
    }
    applyBounds(layout.bounds);
});
jsro.on('model:layout.url', 'changed', function (obj) {
    navigate(obj.value);
});
jsro.on('model:layout.bounds', 'changed', function (obj) {
    applyBounds(layout.bounds);
});

With this procedure, we’ve been able to break the “Matryoshka Effect”. From now on, the embedded browser will be used from the desktop, while the external one will be used from the web —but always being controlled by the remote application.

Running the application from the browser

To see the end result, we must first add the application to the list of registered apps in the Thinfinity VirtualUI Server and configure its home page, making sure it points to the page modified by us in this tutorial.
Once this is done, we can access the application from the browser. If we are running the application from the Thinfinity VirtualUI Development Lab, we must change the virtual path’ address so as to match the one defined in the configuration —in the VirtualUI’s manager.

 

Thinfinity Solutions for remote desktop, screen sharng, digital workspace and application virtualization.

Thinfinity Solutions

As you already know, Thinfinity VirtualUI is a web-enabling SDK to run apps on a browser without rewriting the code.

Explore our other remoting and web-enabling solutions, enjoy our free trials, or request a custom demo HERE. No commitment!

We will be happy to assist you and show you our portfolio for remote desktop, screen sharing, digital workspace, and application virtualization.

Leave a comment

Privacy Preferences
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.