GUI -- Strange Device View Table layout fixed in Safari.

(cherry-picks from master 55ee09b1fa7da1ad01e3aca51c1c7ca1af4dd69b)

Change-Id: Icfb3d80654a2ab009ca12b64f35a20ba98f37681
diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js
index 3e2fb29..6a72d7f 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -122,6 +122,28 @@
         return patt.test(ua);
     }
 
+    // Returns true if the current browser determined to be Chrome
+    function isChrome() {
+        var isChromium = $window.chrome,
+            vendorName = $window.navigator.vendor,
+            isOpera = $window.navigator.userAgent.indexOf("OPR") > -1;
+        return (isChromium !== null &&
+        isChromium !== undefined &&
+        vendorName === "Google Inc." &&
+        isOpera == false);
+    }
+
+    // Returns true if the current browser determined to be Safari
+    function isSafari() {
+        return ($window.navigator.userAgent.indexOf('Safari') !== -1 &&
+        $window.navigator.userAgent.indexOf('Chrome') === -1);
+    }
+
+    // Returns true if the current browser determined to be Firefox
+    function isFirefox() {
+        return typeof InstallTrigger !== 'undefined';
+    }
+
     // search through an array of objects, looking for the one with the
     // tagged property matching the given key. tag defaults to 'id'.
     // returns the index of the matching object, or -1 for no match.
@@ -222,6 +244,9 @@
                 areFunctionsNonStrict: areFunctionsNonStrict,
                 windowSize: windowSize,
                 isMobile: isMobile,
+                isChrome: isChrome,
+                isSafari: isSafari,
+                isFirefox: isFirefox,
                 debugOn: debugOn,
                 find: find,
                 inArray: inArray,
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 4178653..496df27 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -267,33 +267,46 @@
             $log.log('OvDeviceCtrl has been created');
         }])
 
-        .directive('deviceDetailsPanel', ['$rootScope', '$window',
-        function ($rootScope, $window) {
-            return function (scope) {
+    .directive('deviceDetailsPanel', ['$rootScope', '$window', '$timeout',
+    function ($rootScope, $window, $timeout) {
+        return function (scope) {
+            var unbindWatch;
 
-                function heightCalc() {
-                    pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
-                                            + mast.mastHeight() + topPdg;
-                    wSize = fs.windowSize(pStartY);
-                    pHeight = wSize.height;
-                }
+            function heightCalc() {
+                pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+                                        + mast.mastHeight() + topPdg;
+                wSize = fs.windowSize(pStartY);
+                pHeight = wSize.height;
+            }
+
+            function initPanel() {
                 heightCalc();
-
                 createDetailsPane();
+            }
 
-                scope.$watch('panelData', function () {
-                    if (!fs.isEmptyObject(scope.panelData)) {
-                        populateDetails(scope.panelData);
-                        detailsPanel.show();
-                    }
-                });
+            // Safari has a bug where it renders the fixed-layout table wrong
+            // if you ask for the window's size too early
+            if (scope.onos.browser === 'safari') {
+                $timeout(initPanel);
+            } else {
+                initPanel();
+            }
 
-                $rootScope.$watchCollection(
+            // if the panelData changes
+            scope.$watch('panelData', function () {
+                if (!fs.isEmptyObject(scope.panelData)) {
+                    populateDetails(scope.panelData);
+                    detailsPanel.show();
+                }
+            });
+
+            // if the window size changes
+            unbindWatch = $rootScope.$watchCollection(
                     function () {
                         return {
                             h: $window.innerHeight,
                             w: $window.innerWidth
-                        }
+                        };
                     }, function () {
                         if (!fs.isEmptyObject(scope.panelData)) {
                             heightCalc();
@@ -303,6 +316,7 @@
                 );
 
                 scope.$on('$destroy', function () {
+                    unbindWatch();
                     ps.destroyPanel(pName);
                 });
             };
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index d800d34..216a5ee 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -143,7 +143,7 @@
 
 </head>
 <body class="light" ng-app="onosApp">
-<div id="frame" ng-controller="OnosCtrl as onosCtrl">
+<div id="frame" ng-controller="OnosCtrl as onosCtrl" detect-browser>
     <div id="mast"
          ng-controller="MastCtrl as mastCtrl"
          ng-include="'app/fw/mast/mast.html'"></div>
diff --git a/web/gui/src/main/webapp/onos.js b/web/gui/src/main/webapp/onos.js
index 01e910d..7ca5455 100644
--- a/web/gui/src/main/webapp/onos.js
+++ b/web/gui/src/main/webapp/onos.js
@@ -69,12 +69,12 @@
     angular.module('onosApp', moduleDependencies)
 
         .controller('OnosCtrl', [
-            '$log', '$route', '$routeParams', '$location',
+            '$log', '$scope', '$route', '$routeParams', '$location',
             'KeyService', 'ThemeService', 'GlyphService', 'VeilService',
             'PanelService', 'FlashService', 'QuickHelpService',
             'WebSocketService',
 
-            function ($log, $route, $routeParams, $location,
+            function ($log, $scope, $route, $routeParams, $location,
                       ks, ts, gs, vs, ps, flash, qhs, wss) {
                 var self = this;
 
@@ -83,6 +83,9 @@
                 self.$location = $location;
                 self.version = '1.2.0';
 
+                // shared object inherited by all views:
+                $scope.onos = {};
+
                 // initialize services...
                 ts.init();
                 ks.installOn(d3.select('body'));
@@ -131,5 +134,29 @@
                     });
                 }
             });
+        }])
+
+        .directive('detectBrowser', ['$log', 'FnService',
+            function ($log, fs) {
+                return function (scope) {
+                    var body = d3.select('body'),
+                        browser = '';
+                    if (fs.isChrome()) {
+                        browser = 'chrome';
+                    } else if (fs.isSafari()) {
+                        browser = 'safari';
+                    } else if (fs.isFirefox()) {
+                        browser = 'firefox';
+                    }
+                    body.classed(browser, true);
+                    scope.onos.browser = browser;
+
+                    if (fs.isMobile()) {
+                        body.classed('mobile', true);
+                        scope.onos.mobile = true;
+                    }
+
+                    $log.debug('Detected browser is', fs.cap(browser));
+                };
         }]);
 }());