Closed sbwalker closed 4 years ago
@chlupac helped me isolate the source of the issue today so that I could reliably reproduce the problem. Ultimately it had to do with the order in which the JavaScript Interop function was updating the attributes on link elements. The function is named includeLink() and its purpose is to add or update link elements in the page head ( located in Oqtane.Server\wwwroot\js\interop.js ).
As I described above, link elements cannot be removed and then re-added because it causes the browser to "flash". So links need to be updated. The problem was the Oqtane theme had this link:
<link id="app-resource01" rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css" integrity="sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM" crossorigin="anonymous" >
And when I navigated to the other page with the Blazor theme, the link needed to be update to:
<link id="app-resource01" rel="stylesheet" href="Themes/Blazor.Theme/Theme.css">
However the JavaScript was updating the href attribute first - and the moment that value was update the browser would check if it matched the integrity attribute ( which had not been changed yet ) and it would throw an error "None of the “sha384” hashes in the integrity attribute match the content of the subresource.".
The solution was to modify the includeLink() funtion to remove the integrity and crossorigin attributes before it set the href attribute.
I want to thank @chlupac for his help tracking this down. Now I can complete the implementation by extending it to work for modules and scripts as well.
This has now been extended to modules and scripts in #499. So it is now possible for modules and themes to express their resource dependencies ( ie. stylesheets and scripts ) and the framework will take care of adding them to the page as needed.
One of the items still remaining in Oqtane is resource management - and by this I mean the ability for themes and modules to register their stylesheets and scripts in the DOM. There are some JSInterop methods in Oqtane which allow you to do this already however the problem is that unless they are centrally managed by the framework, they can result in unexpected behavior.
To see this in action, on a clean Oqtane install assign the Oqtane theme to the Private page. Now click the navigation links to alternate between the Home and Private pages. You will notice that the pages will not be displayed properly after a few clicks - the stylesheets will be confused. This is because Oqtane is a SPA so there are no full page refreshes. So when a theme uses JSInterop to add a link to the page head, that link stays there forever and is never removed. The simple trick we used in the early stages of Oqtane was to have a single theme link in the head of the page with an ID of "theme" and then update the href value for that ID when the theme changes. This is obviously very limiting as themes may have more than one stylesheet they need to reference. And in fact ,the recent changes to the Oqtane theme resulted in more than 1 stylesheet - a remote reference to a Bootswatch stylesheet and a local reference to a CSS file. This results in the behavior I described above where the stylesheets are not managed properly resulting in display issues.
Taking a step back it should be possible for themes and modules to have as many stylesheet or script references as they need. Some modules or themes may have none, some may have 1, some may have many. So there needs to be centralized way for themes and modules to be able to "register" their resources with the framework.
I merged a solution this morning for this problem which allows a theme to declare its resource requirements programmatically similar to how other properties are expressed in the framework ( ie. Panes are expressed in code in theme components so the framework can retrieve the value and utilize it ).
The solution works. You can look in the ThemeBuilder component to see how it iterates through the collection of resources for the current page and adds the necessary references using JSInterop. It has to do this in a specific way because if the old references are first removed and then the new ones are added, it causes the browser to flash in a very disruptive manner because there is a split second where there are no stylesheets assigned. So it needs to update the existing references, add new ones, and remove any remaining references which are no longer needed. This is all working.
However the problem is that the browser is not behaving consistently. Sometimes it is ignoring when stylesheet references which have been updated. I could really use your help to try and figure out why the browser is acting so inconsistently. The easiest way to test is to modify the Resources registration in the Default.razor components associated to the Blazor and Oqtane themes. Then step through the code in ThemeBuilder.razor and use your browser dev tools to see what is happening to the link elements in the head of the DOM.
To complete the solution, modules also need to be able to express their references through code ( ie, for stylesheet or script references ). Then the framework can take care of the registration of all page resources ( ie. themes and modules ) in a central manner. But before extending this approach to modules, we need to solve the current technical issue.