From 642082e9fba662bd908a278cd103ccc9efb50bbd Mon Sep 17 00:00:00 2001
From: grossmj <grossmj@gns3.net>
Date: Tue, 31 Jan 2023 18:56:07 +0800
Subject: [PATCH] Use a hidden iframe to open console on Firefox

(cherry picked from commit 83d72787f41d83cb712c1f43e1b5fec6b4527d56)
---
 ...console-device-action-browser.component.ts | 64 +++++++++++++++----
 1 file changed, 50 insertions(+), 14 deletions(-)

diff --git a/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts b/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts
index b2db1704..b5ae982a 100644
--- a/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts
+++ b/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts
@@ -1,4 +1,5 @@
 import { Component, Input } from '@angular/core';
+import { DeviceDetectorService } from 'ngx-device-detector';
 import { Node } from '../../../../../cartography/models/node';
 import{ Controller } from '../../../../../models/controller';
 import { NodeService } from '../../../../../services/node.service';
@@ -12,7 +13,7 @@ export class ConsoleDeviceActionBrowserComponent {
   @Input() controller:Controller ;
   @Input() node: Node;
 
-  constructor(private toasterService: ToasterService, private nodeService: NodeService) {}
+  constructor(private toasterService: ToasterService, private nodeService: NodeService, private deviceService: DeviceDetectorService) {}
 
   openConsole() {
     this.nodeService.getNode(this.controller, this.node).subscribe((node: Node) => {
@@ -21,6 +22,31 @@ export class ConsoleDeviceActionBrowserComponent {
     });
   }
 
+  createHiddenIframe(target: Element, uri: string) {
+    const iframe = document.createElement("iframe");
+    iframe.src = uri;
+    iframe.id = "hiddenIframe";
+    iframe.style.display = "none";
+    target.appendChild(iframe);
+    return iframe;
+  }
+
+  openUriUsingFirefox(uri: string) {
+      var iframe = (document.querySelector("#hiddenIframe") as HTMLIFrameElement);
+
+      if (!iframe) {
+          iframe = this.createHiddenIframe(document.body, "about:blank");
+      }
+
+      try {
+          iframe.contentWindow.location.href = uri;
+      } catch (e) {
+          if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL") {
+              this.toasterService.error('Protocol handler does not exist');
+          }
+      }
+  }
+
   startConsole() {
     if (this.node.status !== 'started') {
       this.toasterService.error('This node must be started before a console can be opened');
@@ -33,20 +59,30 @@ export class ConsoleDeviceActionBrowserComponent {
         this.node.console_host = this.controller.host;
       }
 
-      if (
-        this.node.console_type === 'telnet' ||
-        this.node.console_type === 'vnc' ||
-        this.node.console_type === 'spice'
-      ) {
-        try {
-          location.assign(
-            `gns3+${this.node.console_type}://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
-          );
-        } catch (e) {
-          this.toasterService.error(e);
+      const device = this.deviceService.getDeviceInfo();
+
+      try {
+        var uri;
+        if (this.node.console_type === 'telnet') {
+          uri = `gns3+telnet://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`;
+        } else if (this.node.console_type === 'vnc') {
+          uri = `gns3+vnc://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`;
+        } else if (this.node.console_type.startsWith('spice')) {
+          uri = `gns3+spice://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
+        } else {
+          this.toasterService.error('Supported console types are: telnet, vnc, spice and spice+agent.');
         }
-      } else {
-        this.toasterService.error('Supported console types: telnet, vnc, spice.');
+
+        if (device.browser === "Firefox") {
+            // Use a hidden iframe otherwise Firefox will disconnect
+            // from the GNS3 controller websocket if we use location.assign()
+            this.openUriUsingFirefox(uri);
+        } else {
+            location.assign(uri);
+        }
+
+      } catch (e) {
+          this.toasterService.error(e);
       }
     }
   }