Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

WebGL with AngularJS bug in firefox

Discussion in 'WebGL' started by abryden, May 15, 2015.

  1. abryden

    abryden

    Joined:
    Sep 29, 2014
    Posts:
    24
    edit: AWESOME. 5.1 WORKS IN FIREFOX! If you are reading this thread and having 5.1 trouble check out the thread for the pain I had to go through to work around a weird bug with upgrading a project across some of the 5.0x patch releases and 5.2 http://forum.unity3d.com/threads/failed-building-webgl-player-ongoing.326241/

    Still a very very useful thread for all of Dustin Horne's useful info on using webgl unity3d with angular.

    I'm trying to use the WebGL player embedded in an AngularJS view. This works decently in Chrome (haven't tried edge or Safari yet) but crashes hard in Firefox. The error is as follows:

    "Invoking error handler due to
    HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy"

    Has anyone found a work around for this or can anyone from the WebGL team chime in?

    Thanks!
     
    Last edited: Jun 10, 2015
  2. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    What version of Angular? Are you using UI-Router or just stock angular? How is the View being injected? i.e. is it being added with an ng-include? Do you also have jQuery on the site? And finally, the error in the console should also point to a place in the code that's causing it... could you tell us what that is?
     
  3. abryden

    abryden

    Joined:
    Sep 29, 2014
    Posts:
    24
    Hey,

    I really like JSON.NET for Unity by the way!

    Just stock angular.

    The view is being injected directly from the routes. I don't believe we have jquery on the site. Here is our bower.json

    {
    "name": "angular-test",
    "version": "0.0.0",
    "dependencies": {
    "angular": "1.3.0",
    "json3": "~3.3.1",
    "es5-shim": "~3.1.0",
    "bootstrap": "~3.2.0",
    "angular-resource": "1.3.0",
    "angular-cookies": "1.3.0",
    "angular-sanitize": "1.3.0",
    "angular-animate": "1.3.0",
    "angular-touch": "1.3.0",
    "angular-route": "1.3.0",
    "firebase": "2.1.x",
    "angularfire": "0.9.1"
    },
    "devDependencies": {
    "angular-mocks": "1.3.0",
    "angular-scenario": "1.3.0",
    "mockfirebase": "0.8.x"
    },
    "appPath": "app"
    }


    "exception thrown: HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy,_JS_Cursor_SetCursorString@
    _JS_Cursor_SetShow@
    isg@
    jsg@
    Urd@
    Rrd@
    q4b@
    Yud@
    Kqd@
    msg@
    callMain@
    doRun@
    run/<@
    "
    I've been trying different build options to try and get something more enlightening but so far no luck.

    Thanks!
     
  4. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Thanks :). I'm not familiar with bower. I know what it is, I just don't use it, bit I don't see anything off kilter there. What appears to be happening is that some invalid html is being generated. It's trying to insert an element where it shouldn't. This error has occurred I Firefox before and it was normally something silly like someone trying to insert a div into the title element. In this case it's angular doing something funky.

    One thing you might try instead of just putting the webgl in the view is to write a simple angular directive that outputs the webgl element. That way the view will be compiled by angular and maybe output at a later stage. You can put like an ng-if="ready" and in you controller inject a $timeout ser if and do:

    $timeout(function(){ $scope.ready = true;}));

    I used the $scope syntax because its the most common but the premise would be the same for the "controller as" syntax which is what I use due to better support for minification.
     
  5. Lasto

    Lasto

    Joined:
    Oct 2, 2014
    Posts:
    6
    I get the same error when I try to ng-include WebGL build into my site. I thought I have something wrong in my code, so I tried to add angular to index.html in build (no routes, no includes, no views) but error appeared as well. I really need to use angular with webgl
     
  6. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    The only thing I can think right now that might be causing the problem is when the javascript and html are being loaded... I wonder if the Unity javascript for the WebGL game are looking for elements that you don't have available yet. When you're using ng-include you're delaying the load until after the page has finished processing because the page has to finish and the angular app has to be bootstrapped first.
     
  7. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Actually, I had forgotten I was going to look at this so I'm going to do it now. I'm also going to try to write a directive that will help output the unity3d game. something like:

    Code (csharp):
    1.  
    2. <unity-webgl webgl-gamepath="/path/to/game">
    3. </unity-webgl>
    4.  
    Or

    Code (csharp):
    1.  
    2. <div unity-webgl webgl-gamepath="/path/to/game">
    3. </div>
    4.  
    Would I be leaving anyone out by using element and attribute binding? I may just use straight attribute.
     
  8. Lasto

    Lasto

    Joined:
    Oct 2, 2014
    Posts:
    6
    It worked fine back when I was adding webGL to page via <object> tags, but I wasnt able to use SenMessage method to comunicate with the game from my web script that is why I tried ng-include and also ajax load + innerHtml but that wasnt the way to go with. With your directive, will I be able to sendMessages to game ?
     
  9. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I dunno, we'll just have to see. :) First I have to get a WebGL build to actually run in IE which I always struggle with. I'm getting the stupid "run() was called but dependencies remain" error. Basic scene, no assets, just a camera and a cube. Grr. It runs fine in chrome. Once I've got the directive working I'll test sendMessage.
     
  10. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ugh... Unity sometimes writes some yucky javascript. I'm working on interpreting what I need to put into my directive and what I need to sideload. Lots of stuff in here polluting the global scope. Variables are redefined... for instance, the generated index.html file has:
    Code (csharp):
    1.  
    2. var Module = {
    3. ...
    4. }
    5.  
    and then the flileloader.js file is included which starts with:
    Code (csharp):
    1.  
    2. var Module;
    3. if(typeof Module ==='undefined') Module = eval('(function() { try { return Module || {} } catch(e) { return {} } })()');
    4.  
    fileloader.js also has some app name values hard coded which can't be set externally.
     
  11. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok, I got it working with a directive. It works fine in Chrome and works fine in IE11. But it fails with the node hierarchy in Firefox so this looks to me like a Firefox bug. The directive approach is really messy at the moment as it doesn't dynamically set anything, it's just a template that loads the necessary scripts (minus a few paths I didn't update). I'm thinking if I have some time this coming weekend I may completely dissect the javascript being generated by Unity to see if I can come up with something cleaner.

    wgldirective.PNG
     
  12. Lasto

    Lasto

    Joined:
    Oct 2, 2014
    Posts:
    6
    Awesome, SendMessage works ? It seems to me that node hierarchy error is caused by inserting HTML code with tags that dont belong there like <link> <head> <body> etc. I tried to leave them out, put all the JS lines to controller and dynamically create <scritpt> and load fileloader.js, UnityProgress.js and UnityConfig.js to page when needed (I intend to run WebGL in $modal window). After theese changes the node error was fixed, but then I had problems with link to files (error was in main unity file, it is minimized and has about 40MB so I can barely open it, and even if I open it it freezes my editor). Shall we report that error to mozila ? Would you care to share that directive with others :) ?
     
  13. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    So I made some changes... I put all of my scripts in a /webgl/ folder and I modified the javascript to point to that folder. The directive is actually really dumb and uses a templateurl to point to a template. I didn't do anything dynamically. So, the directive needs seriously cleaned up... but I'm thinking about completely rewriting fileloader.js, UnityConfig.js and the included script on the page so I can clean them up and make them friendly for dynamic injection.

    I haven't had a chance to test SendMessage yet as I don't actually have Unity here at work (day job, ugh) but I will be trying it hopefully tonight. Here are the dummy test files that I created (MVC5 Visual Studio Solution)... it's just hastily thrown together and messy and like I said, the javascript needs completely rewritten but this gives a quick and easy test that can be easily rewritten (even as a plain HTML site). If you're not familiar with MVC, the template is in /views/shared/_layout.cshtml and look in /App_Start/BundleConfig.cs to see where the javascript bundles are defined. Also here's the link to download the complete solution (too big to attach to the post @ 40MB):
    http://1drv.ms/1AYcLB8

    /app/app.js
    Code (csharp):
    1.  
    2. 'use strict';
    3. var __dependencies =[];
    4. angular.module('GLTest', __dependencies);
    5.  
    /app/views/homeView.html
    Code (csharp):
    1.  
    2. <div>
    3.   <h1>Hello</h1>
    4.     <div ng-include="'/app/views/webGLView.html'">
    5.   </div>
    6. </div>
    7.  
    /app/views/webGLView.html
    Code (csharp):
    1.  
    2. <div>
    3.     <div unity-webgl></div>
    4. </div>
    5.  
    /app/directives/unityWebgl.js (directive)
    Code (csharp):
    1.  
    2. 'use strict';
    3. angular.module('GLTest')
    4. .directive('unityWebgl',[function(){
    5. return{
    6. templateUrl:'/app/directives/unityWebglTemplate.html',
    7. link:function(scope, element, attr){
    8. },
    9. restrict:'EA'
    10. };
    11. }]);
    12.  
    /app/directives/unityWebglTemplate.html
    Code (csharp):
    1.  
    2. <linkrel="stylesheet"href="/webgl/TemplateData/style.css">
    3. <scriptsrc="/webgl/TemplateData/UnityProgress.js"></script>
    4. <divclass="template-wrap clear">
    5. <canvasclass="emscripten"id="canvas"oncontextmenu="event.preventDefault()"height="600px"width="600px"></canvas>
    6. <divclass="logo"></div>
    7. <divclass="fullscreen"><imgsrc="TemplateData/fullscreen.png"width="38"height="38"alt="Fullscreen"title="Fullscreen"onclick="SetFullscreen(1);"/></div>
    8. <divclass="title">New Unity Project 1</div>
    9. </div>
    10. <scripttype='text/javascript'>
    11. // connect to canvas
    12. var Module ={
    13. filePackagePrefixURL:"/webgl/Release/",
    14. memoryInitializerPrefixURL:"/webgl/Release/",
    15. preRun:[],
    16. postRun:[],
    17. print:(function(){
    18. returnfunction(text){
    19. console.log (text);
    20. };
    21. })(),
    22. printErr:function(text){
    23. console.error (text);
    24. },
    25. canvas: document.getElementById('canvas'),
    26. progress:null,
    27. setStatus:function(text){
    28. if(this.progress ==null)
    29. {
    30. if(typeof UnityProgress !='function')
    31. return;
    32. this.progress =new UnityProgress (canvas);
    33. }
    34. if(!Module.setStatus.last) Module.setStatus.last ={ time: Date.now(), text:''};
    35. if(text === Module.setStatus.text)return;
    36. this.progress.SetMessage (text);
    37. var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
    38. if(m)
    39. this.progress.SetProgress (parseInt(m[2])/parseInt(m[4]));
    40. if(text ==="")
    41. this.progress.Clear()
    42. },
    43. totalDependencies:0,
    44. monitorRunDependencies:function(left){
    45. this.totalDependencies = Math.max(this.totalDependencies, left);
    46. Module.setStatus(left ?'Preparing... ('+(this.totalDependencies-left)+'/'+this.totalDependencies +')':'All downloads complete.');
    47. }
    48. };
    49. Module.setStatus('Downloading (0.0/1)');
    50. </script>
    51. <scriptsrc="/webgl/Release/UnityConfig.js"></script>
    52. <scriptsrc="/webgl/Release/fileloader.js"></script>
    53. <script>
    54. if(!(!Math.fround)){
    55. var script = document.createElement('script');
    56. script.src ="/webgl/Release/WGLAngularTestBuild.js";
    57. document.body.appendChild(script);
    58. }else{
    59. var codeXHR =new XMLHttpRequest();
    60. codeXHR.open('GET','/webgl/Release/WGLAngularTestBuild.js',true);
    61. codeXHR.onload =function(){
    62. var code = codeXHR.responseText;
    63. if(!Math.fround){
    64. try{
    65. console.log('optimizing out Math.fround calls');
    66. var m =/var ([^=]+)=global\.Math\.fround;/.exec(code);
    67. var minified = m[1];
    68. if(!minified)throw'fail';
    69. var startAsm = code.indexOf('// EMSCRIPTEN_START_FUNCS');
    70. var endAsm = code.indexOf('// EMSCRIPTEN_END_FUNCS');
    71. var asm = code.substring(startAsm, endAsm);
    72. do{
    73. var moar =false;// we need to re-do, as x(x( will not be fixed
    74. asm = asm.replace(new RegExp('[^a-zA-Z0-9\\$\\_]'+ minified +'\\(','g'),function(s){ moar =true;return s[0]+'('});
    75. }while(moar);
    76. code = code.substring(0, startAsm)+ asm + code.substring(endAsm);
    77. code = code.replace("'use asm'","'almost asm'");
    78. }catch(e){ console.log('failed to optimize out Math.fround calls '+ e)}
    79. }
    80. var blob =new Blob([code],{ type:'text/javascript'});
    81. codeXHR =null;
    82. var src = URL.createObjectURL(blob);
    83. var script = document.createElement('script');
    84. script.src = URL.createObjectURL(blob);
    85. script.onload =function(){
    86. URL.revokeObjectURL(script.src);
    87. };
    88. document.body.appendChild(script);
    89. };
    90. codeXHR.send(null);
    91. }
    92. </script>
    93.  
     
  14. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ugh.. just download the project... for some reason pasting code into these forums somehow squishes out the spaces between html element names and attributes.
     
  15. Lasto

    Lasto

    Joined:
    Oct 2, 2014
    Posts:
    6
    That may be but the file is broken as well, wont open. I wanted to try SendMessage, I can ass soo as you upload working file.
     
    Last edited: Jun 9, 2015
  16. abryden

    abryden

    Joined:
    Sep 29, 2014
    Posts:
    24
    Wow! Thanks for looking into this more. I was kind of waiting to see if 5.1 magically fixed the Firefox angular issue and then after that my plan was to develop a minimal demonstration of the Firefox bug.

    @Lasto The directive method works well. and you can use SendMessage - to get back out to angular I use javascript functions that are outside of the controllers that call into the correct controller - maybe @Dustin Horne will have a better solution.

    Here is our injector:

    Code (JavaScript):
    1. 'use strict';
    2.  
    3. /**
    4. * @ngdoc directive
    5. * @name angularTestApp.directive:unity3dInjector
    6. * @description
    7. * # unity3dInjector
    8. */
    9. angular.module('angularTestApp')
    10.   .directive('unity3dInjector', function () {
    11.     return {
    12.         restrict: 'E',
    13.       templateUrl: 'views/unityview.html'
    14.     };
    15.   });
    Here is our template:

    In our post build process we merge the unity3d output with the compiled for distribution angular project by copying the folders built by unity into the root directory of the dist folder (we use Grunt with yeoman scaffold) and then copying the unity3d template into views/unityview.html (the one in the angular project just says - "looks like unity didn't build properly - go fix it"

    Code (Boo):
    1.  <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" height="%UNITY_HEIGHT%px" width="%UNITY_WIDTH%px"></canvas>
    2.     %UNITY_WEBGL_LOADER_GLUE%
     
  17. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Hmm... the file link is working fine for me. I can download and extract the zip.

    Yes. :) I wouldn't communicate directly into your controllers but rather use an Angular service. I have one that I can repurpose. It's a messagehub service that allows you to create messages and subscribers. You basically do as follows:

    Code (csharp):
    1.  
    2. messageService.Subscribe("messageKey", $scope, callbackFunction);
    3.  
    The reason it requires you to send it scope is that it maintains a list of subscribers and it wires up the $scope.$on("destroy") so that the message hub automatically removes subscribers when they get destroyed. Then you can do the following:

    Code (csharp):
    1.  
    2. messageService.Send("messageKey", value);
    3.  
    the message hub then checks for that message queue. If there are no subscribers it just does nothing and drops the message. If there are subscribers, it invokes each subscribers callback function and passes into it the value that was sent as the second parameter. There is also an optional Boolean third parameter that tells whether to pass the value by reference or not. By default this is false and it makes a deep copy of the object instead so that receivers can handle the data but not make changes to the original reference. You can supply true as the third parameter and change this behavior.

    So what you'd really want to do is have a script on your page something like this (assuming you're using jQuery):

    Code (csharp):
    1.  
    2.  
    3. function receiveMessage(messageData) {
    4.     window.MessageBus.Send('receiverKey', messageData);
    5. }
    6.  
    7. (function($, window, undefined) {
    8.    window.MessageBus = {
    9.         Send: function() {}
    10.     }
    11. })(jQuery, window);
    12.  
    That sets up a dummy object on window so it won't fail if the service fails. Now in the Angular code, when the service is created you'd create your return object with your service methods as normal, but assign it to the MessageBus. So for instance, I created a variable called "svc" and apply all my functions to it. My MessageHub actually looks something like this and I've added the line to associated it with your Window object:

    Code (csharp):
    1.  
    2. var svc = {
    3.     Subscribe: function() {
    4.         //logic here
    5.     },
    6.     Unsubscribe: function() {
    7.        //logic here
    8.     },
    9.     Send: function() {
    10.       //logic here
    11.     },
    12.     RemoveKey: function() {
    13.       //logic here
    14.     }
    15. };
    16.  
    17. //Tie the message bus to the service
    18. window.MessageBus.sendMessage = svc.sendMessage;
    19.  
    20. //return the service
    21. return svc;
    22.  
    Now in your controllers you would only need to inject the messageHubService and then call:

    Code (csharp):
    1.  
    2. messageHubService.subscribe('receiverKey', function(data){
    3.      alert(data);
    4. });
    5.  
    Since services are singletons you don't care that they are referenced via the window, and now you could selectively receive messages in different controllers. Then you could have multiple global methods and each one could call the service with a different key. For instance:

    Code (csharp):
    1.  
    2. function saySomething(data) {
    3.    window.MessageBus.Send('saySomething', data);
    4. }
    5.  
    6. function doSomething(data) {
    7.    window.MessageBus.Send('doSometing', data);
    8. }
    9.  
    And what we actually use this for is that we have pages that are highly componentized so we may have 10 controllers on a single page... we can send a message to the service and have multiple controllers handle that message through subscriptions.

    When I get the service cleaned up I'll post it.
     
    Last edited: Jun 9, 2015
  18. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok, the messageHubService script is attached to this post. There are no jQuery dependencies here and I changed the module name to angularTestApp so you can just drop it into your existing test app. Here's an example of how to test and use it in a controller:

    Code (csharp):
    1.  
    2. angular.module('angularTestApp').controller('testController',['$scope', 'messageHubService', '$window',function($scope, messageHub, $window){
    3.  
    4.     messageHub.Subscribe('showMessage', $scope, function(message) {
    5.             $window.alert(message);
    6.     });
    7. }]);
    8.  
    You're passing in the scope so it will handle automatic unsubscription when your controller gets destroyed. Now I also went ahead and created a global unity_messageHub variable and assigned it to window.MessageHub. And inside the service I did:

    Code (csharp):
    1.  
    2. unity_messageHub.Send = svc.Send;
    3.  
    It's all wired up, so if you include this service in your javascript then all you have to do is create your methods like this:

    Code (csharp):
    1.  
    2. function showMessage(message) {
    3.     window.MessageHub.Send('showMessage', message);
    4. }
    5.  
    That will automatically pass the message through to the messageHubService as long as it has been injected into a controller, and your controller can react to it. If you have any questions with using the messageHub let me know, and feel free to use it in any of your angular projects. We're using it heavily in production right now. :)
     

    Attached Files:

  19. abryden

    abryden

    Joined:
    Sep 29, 2014
    Posts:
    24
    Wow nice! That's definitely cleaner than what we are doing now! If I make an opensource angular+Unity3d minimal project to push harder on the Firefox bug can I use it there?

    Have you done anything with keeping the Unity View always loaded but hidden and then change its size or unhide it based on the route? The loading time is our last (other than the Firefox bug which were are hoping is fixed in 5.1 or soonish) issue with using Unity3D webGL in production. Loading it as soon as you load the single page app and just showing it/sending different content to it as needed is our current strategy to deal with loading time. Does anyone have any others?
     
  20. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Sure, use it as you see fit :). As for hiding, that type of thing would probably best be done with something like UI-Router that uses state based routing so you could just selectively hide base on the route or some $stateParams.
     
  21. abryden

    abryden

    Joined:
    Sep 29, 2014
    Posts:
    24
    Lasto and Dustin-Horne like this.
  22. farhankhwaja

    farhankhwaja

    Joined:
    Feb 28, 2017
    Posts:
    4
    Anyone here, has a demo app which works with AngularJs and Unity WebGL built.

    Because I have been trying to do it. I tried with the directive approach and I keep getting

    Uncaught Exception: unexpected token <.

    I did a built with and without compression but no success.

    Please if anyone can help me