Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/Framework/Framework/ResourceManagement/LinkResourceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public abstract class LinkResourceBase : ResourceBase, ILinkResource
public bool VerifyResourceIntegrity { get; set; } = true;
public string? IntegrityHash { get; set; }

public ResourceFetchPriority FetchPriority { get; set; } = ResourceFetchPriority.Auto;

public LinkResourceBase(ResourceRenderPosition renderPosition, string mimeType, IResourceLocation location) : base(renderPosition)
{
this.Location = location;
Expand Down Expand Up @@ -101,7 +103,7 @@ protected string GetLoadingScript(string javascriptCondition, string script) =>
script.text = originalScript.text;
script.id = originalScript.id;
document.head.appendChild(script);
}}";
}}".Replace("\r\n", "\n");

protected string RenderLinkToString(IResourceLocation location, IDotvvmRequestContext context, string resourceName)
{
Expand Down Expand Up @@ -146,6 +148,13 @@ protected void AddSrcAndIntegrity(IHtmlWriter writer, IDotvvmRequestContext cont
AddIntegrityAttribute(writer, context, url);
}
}

protected void AddFetchPriority(IHtmlWriter writer, IResourceLocation location, ResourceFetchPriority fetchPriority)
{
// add fetchpriority only to the primary location, not to fallbacks
if (location == (object)Location && fetchPriority != ResourceFetchPriority.Auto)
writer.AddAttribute("fetchpriority", fetchPriority == ResourceFetchPriority.High ? "high" : "low");
}
}

public class ResourceLocationFallback
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace DotVVM.Framework.ResourceManagement
{
public enum ResourceFetchPriority
{
Auto,
High,
Low
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ public static StylesheetResource RegisterStylesheet(
string name,
IResourceLocation location,
string[]? dependencies = null,
string? integrityHash = null)
string? integrityHash = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto)
{
var r = new StylesheetResource(location) {
Dependencies = dependencies ?? Array.Empty<string>(),
IntegrityHash = integrityHash
IntegrityHash = integrityHash,
FetchPriority = fetchPriority
};
repo.Register(name, r);
return r;
Expand All @@ -28,19 +30,21 @@ public static StylesheetResource RegisterStylesheetFile(
this DotvvmResourceRepository repo,
string name,
string filePath,
string[]? dependencies = null) =>
repo.RegisterStylesheet(name, new FileResourceLocation(filePath), dependencies);
string[]? dependencies = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto) =>
repo.RegisterStylesheet(name, new FileResourceLocation(filePath), dependencies, null, fetchPriority);

/// <summary> Registers a <see cref="StylesheetResource" /> with the specified URL.
/// If the URL is local, consider using the <see cref="RegisterStylesheetFile(DotvvmResourceRepository, string, string, string[])" /> method. </summary>
/// If the URL is local, consider using the <see cref="RegisterStylesheetFile(DotvvmResourceRepository, string, string, string[], ResourceFetchPriority)" /> method. </summary>
/// <param name="integrityHash"> is a hash of the served file, it's highly recommended to set it when the resource is from a 3rd party domain. See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity for more information. </param>
public static StylesheetResource RegisterStylesheetUrl(
this DotvvmResourceRepository repo,
string name,
string url,
string? integrityHash,
string[]? dependencies = null) =>
repo.RegisterStylesheet(name, new UrlResourceLocation(url), dependencies, integrityHash);
string[]? dependencies = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto) =>
repo.RegisterStylesheet(name, new UrlResourceLocation(url), dependencies, integrityHash, fetchPriority);

/// <summary> Registers a <see cref="ScriptResource" /> (a Javascript resource) with the specified name, location and dependencies.
/// <paramref name="integrityHash"/> does not have to be used when location is local. </summary>
Expand All @@ -51,13 +55,15 @@ public static LinkResourceBase RegisterScript(
bool defer = true,
bool module = false,
string[]? dependencies = null,
string? integrityHash = null)
string? integrityHash = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto)
{
if (!defer && module)
throw new ArgumentException("<script type='module'> always deferred, please do not specify defer: false", nameof(defer));
LinkResourceBase r = module ? new ScriptModuleResource(location) : new ScriptResource(location, defer);
r.Dependencies = dependencies ?? Array.Empty<string>();
r.IntegrityHash = integrityHash;
r.FetchPriority = fetchPriority;
repo.Register(name, r);
return r;
}
Expand All @@ -71,8 +77,9 @@ public static LinkResourceBase RegisterScriptFile(
string filePath,
bool defer = true,
bool module = false,
string[]? dependencies = null) =>
repo.RegisterScript(name, new FileResourceLocation(filePath), defer, module, dependencies);
string[]? dependencies = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto) =>
repo.RegisterScript(name, new FileResourceLocation(filePath), defer, module, dependencies, null, fetchPriority);

/// <summary> Registers a <see cref="ScriptModuleResource" /> from the specified file.
/// The file can be anywhere in the filesystem, it does not have to be in the wwwroot folder.
Expand All @@ -81,8 +88,9 @@ public static LinkResourceBase RegisterScriptModuleFile(
this DotvvmResourceRepository repo,
string name,
string filePath,
string[]? dependencies = null) =>
repo.RegisterScript(name, new FileResourceLocation(filePath), defer: true, module: true, dependencies);
string[]? dependencies = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto) =>
repo.RegisterScript(name, new FileResourceLocation(filePath), defer: true, module: true, dependencies, fetchPriority: fetchPriority);

/// <summary> Registers a <see cref="ScriptModuleResource" /> from the specified file.
/// The file can be anywhere in the filesystem, it does not have to be in the wwwroot folder.
Expand All @@ -97,7 +105,7 @@ public static LinkResourceBase RegisterScriptModuleFile(
repo.RegisterScript(name, new FileResourceLocation(filePath), defer: true, module: true, dependencies);

/// <summary> Registers a <see cref="ScriptResource" /> with the specified URL.
/// If the URL is local, consider using the <see cref="RegisterScriptFile(DotvvmResourceRepository, string, string, bool, bool, string[])" /> method. </summary>
/// If the URL is local, consider using the <see cref="RegisterScriptFile(DotvvmResourceRepository, string, string, bool, bool, string[], ResourceFetchPriority)" /> method. </summary>
/// <param name="integrityHash"> is a hash of the served file, it's highly recommended to set it when the resource is from a 3rd party domain. See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity for more information. </param>
public static LinkResourceBase RegisterScriptUrl(
this DotvvmResourceRepository repo,
Expand All @@ -106,19 +114,21 @@ public static LinkResourceBase RegisterScriptUrl(
string? integrityHash,
bool defer = true,
bool module = false,
string[]? dependencies = null) =>
repo.RegisterScript(name, new UrlResourceLocation(url), defer, module, dependencies, integrityHash);
string[]? dependencies = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto) =>
repo.RegisterScript(name, new UrlResourceLocation(url), defer, module, dependencies, integrityHash, fetchPriority);

/// <summary> Registers a <see cref="ScriptModuleResource" /> with the specified URL.
/// If the URL is local, consider using the <see cref="RegisterScriptFile(DotvvmResourceRepository, string, string, bool, bool, string[])" /> method. </summary>
/// If the URL is local, consider using the <see cref="RegisterScriptFile(DotvvmResourceRepository, string, string, bool, bool, string[], ResourceFetchPriority)" /> method. </summary>
/// <param name="integrityHash"> is a hash of the served file, it's highly recommended to set it when the resource is from a 3rd party domain. See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity for more information. </param>
public static LinkResourceBase RegisterScriptModuleUrl(
this DotvvmResourceRepository repo,
string name,
string url,
string? integrityHash,
bool defer = true,
string[]? dependencies = null) =>
repo.RegisterScript(name, new UrlResourceLocation(url), defer, module: true, dependencies, integrityHash);
string[]? dependencies = null,
ResourceFetchPriority fetchPriority = ResourceFetchPriority.Auto) =>
repo.RegisterScript(name, new UrlResourceLocation(url), defer, module: true, dependencies, integrityHash, fetchPriority);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public ScriptModuleResource(IResourceLocation location, bool defer)
public override void RenderLink(IResourceLocation location, IHtmlWriter writer, IDotvvmRequestContext context, string resourceName)
{
AddSrcAndIntegrity(writer, context, location.GetUrl(context, resourceName), "src");
AddFetchPriority(writer, location, FetchPriority);
writer.AddAttribute("type", "module");
writer.RenderBeginTag("script");
writer.RenderEndTag();
Expand Down
2 changes: 2 additions & 0 deletions src/Framework/Framework/ResourceManagement/ScriptResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public override void RenderLink(IResourceLocation location, IHtmlWriter writer,
private void RenderLink(IResourceLocation location, IHtmlWriter writer, IDotvvmRequestContext context, string resourceName, bool defer)
{
AddSrcAndIntegrity(writer, context, location.GetUrl(context, resourceName), "src");
AddFetchPriority(writer, location, FetchPriority);

if (MimeType != "text/javascript") // this is the default, no need to write it
writer.AddAttribute("type", MimeType);
if (defer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public StylesheetResource()
public override void RenderLink(IResourceLocation location, IHtmlWriter writer, IDotvvmRequestContext context, string resourceName)
{
AddSrcAndIntegrity(writer, context, location.GetUrl(context, resourceName), "href");
AddFetchPriority(writer, location, FetchPriority);
writer.AddAttribute("rel", "stylesheet");
writer.AddAttribute("type", MimeType);
writer.RenderSelfClosingTag("link");
Expand Down
62 changes: 62 additions & 0 deletions src/Tests/Runtime/ResourceRenderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.IO;
using CheckTestOutput;
using DotVVM.Framework.Controls;
using DotVVM.Framework.ResourceManagement;
using DotVVM.Framework.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DotVVM.Framework.Tests.Runtime
{
[TestClass]
public class ResourceRenderTests
{
private readonly OutputChecker check = new OutputChecker("testoutputs");

private static string Render(LinkResourceBase resource, string resourceName = "r")
{
var context = DotvvmTestHelper.CreateContext();

using var text = new StringWriter();
var html = new HtmlWriter(text, context);
resource.Render(html, context, resourceName);
return text.ToString();
}

[TestMethod]
public void Script_WithFallback_OnlyPrimaryHasFetchPriority()
{
var res = new ScriptResource(new UrlResourceLocation("http://primary.example.com/app.js")) {
FetchPriority = ResourceFetchPriority.High,
LocationFallback = new ResourceLocationFallback(
javascriptCondition: "true",
new UrlResourceLocation("http://fallback.example.com/app.js")
)
};

var output = Render(res);
check.CheckString(output, fileExtension: "html");
}

[TestMethod]
public void ScriptModule_FetchPriority_High()
{
var res = new ScriptModuleResource(new UrlResourceLocation("http://example.com/module.js")) {
FetchPriority = ResourceFetchPriority.Low
};

var output = Render(res);
check.CheckString(output, fileExtension: "html");
}

[TestMethod]
public void Stylesheet_FetchPriority_High()
{
var res = new StylesheetResource(new UrlResourceLocation("http://example.com/style.css")) {
FetchPriority = ResourceFetchPriority.High
};

var output = Render(res);
check.CheckString(output, fileExtension: "html");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.FileResourceLocation, DotVVM.Framework",
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"RenderPosition": "Anywhere"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"Dependencies": [
"knockout"
],
Expand All @@ -58,6 +59,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"Dependencies": [
"knockout"
],
Expand All @@ -75,6 +77,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"Dependencies": [
"dotvvm"
],
Expand All @@ -90,6 +93,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"RenderPosition": "Anywhere"
},
"knockout": {
Expand All @@ -102,6 +106,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"RenderPosition": "Anywhere"
}
},
Expand All @@ -115,6 +120,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/css",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"RenderPosition": "Head"
},
"dotvvm.internal-css": {
Expand All @@ -126,6 +132,7 @@
"LocationType": "DotVVM.Framework.ResourceManagement.EmbeddedResourceLocation, DotVVM.Framework",
"MimeType": "text/css",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"RenderPosition": "Head"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"IntegrityHash": "hash, maybe",
"FetchPriority": "Auto",
"RenderPosition": "Head"
},
"r2": {
Expand All @@ -63,6 +64,7 @@
},
"MimeType": "text/javascript",
"VerifyResourceIntegrity": true,
"FetchPriority": "Auto",
"Dependencies": [
"r1"
],
Expand All @@ -79,6 +81,7 @@
"MimeType": "text/css",
"VerifyResourceIntegrity": true,
"IntegrityHash": "hash, maybe",
"FetchPriority": "Auto",
"RenderPosition": "Head"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src=http://example.com/module.js fetchpriority=low type=module></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src=http://primary.example.com/app.js fetchpriority=high defer></script><script defer src="data:text/javascript;base64,aWYgKCEodHJ1ZSkpIHsKICAgIHZhciB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7CiAgICB3cmFwcGVyLmlubmVySFRNTCA9ICJcdTAwM0NzY3JpcHQgc3JjPWh0dHA6Ly9mYWxsYmFjay5leGFtcGxlLmNvbS9hcHAuanNcdTAwM0VcdTAwM0Mvc2NyaXB0XHUwMDNFIjsKICAgIHZhciBvcmlnaW5hbFNjcmlwdCA9IHdyYXBwZXIuY2hpbGRyZW5bMF07CiAgICB2YXIgc2NyaXB0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7CiAgICBzY3JpcHQuc3JjID0gb3JpZ2luYWxTY3JpcHQuc3JjOwogICAgc2NyaXB0LnR5cGUgPSBvcmlnaW5hbFNjcmlwdC50eXBlOwogICAgc2NyaXB0LnRleHQgPSBvcmlnaW5hbFNjcmlwdC50ZXh0OwogICAgc2NyaXB0LmlkID0gb3JpZ2luYWxTY3JpcHQuaWQ7CiAgICBkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHNjcmlwdCk7Cn0="></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<link href=http://example.com/style.css fetchpriority=high rel=stylesheet type=text/css />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src=http://example.com/module.js fetchpriority=low type=module></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<link href=http://example.com/style.css fetchpriority=high rel=stylesheet type=text/css />