From c2e06ac6ba6b930e61257049ba7cbb36b537d4d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=A0t=C4=9Bp=C3=A1nek?= Date: Tue, 20 Aug 2019 13:02:47 +0200 Subject: [PATCH 1/2] Fix LocationFallback overwriting the entire page on postback --- .../ResourceManagement/LinkResourceBase.cs | 14 +++++- .../Resources/Scripts/DotVVM.js | 46 ++++++++++--------- .../Resources/Scripts/DotVVM.ts | 46 ++++++++++--------- .../CommonConfiguration.cs | 5 ++ .../Controls/ResourceRequiringButton.cs | 32 +++++++++++++ .../DotVVM.Samples.Common.csproj | 1 + .../Resources/RequiredOnPostback.dothtml | 15 ++++++ .../Feature/ResourcesTests.cs | 20 +++++++- .../SamplesRouteUrls.designer.cs | 3 +- 9 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 src/DotVVM.Samples.Common/Controls/ResourceRequiringButton.cs create mode 100644 src/DotVVM.Samples.Common/Views/FeatureSamples/Resources/RequiredOnPostback.dothtml diff --git a/src/DotVVM.Framework/ResourceManagement/LinkResourceBase.cs b/src/DotVVM.Framework/ResourceManagement/LinkResourceBase.cs index 4367707bde..a1f4a3d9a9 100644 --- a/src/DotVVM.Framework/ResourceManagement/LinkResourceBase.cs +++ b/src/DotVVM.Framework/ResourceManagement/LinkResourceBase.cs @@ -62,7 +62,19 @@ public override void Render(IHtmlWriter writer, IDotvvmRequestContext context, s { writer.AddAttribute("type", "text/javascript"); writer.RenderBeginTag("script"); - writer.WriteUnencodedText($"{LocationFallback.JavascriptCondition} || document.write({JsonConvert.ToString(link, '\'').Replace("<", "\\u003c")})"); + var script = JsonConvert.ToString(link, '\'').Replace("<", "\\u003c"); + writer.WriteUnencodedText( +$@"if (!({LocationFallback.JavascriptCondition})) {{ + var wrapper = document.createElement('div'); + wrapper.innerHTML = {script}; + var originalScript = wrapper.children[0]; + var script = document.createElement('script'); + script.src = originalScript.src; + script.type = originalScript.type; + script.text = originalScript.text; + script.id = originalScript.id; + document.head.appendChild(script); +}}"); writer.RenderEndTag(); } } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js index 14db0720eb..8321207f84 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js @@ -1416,45 +1416,47 @@ var DotVVM = /** @class */ (function () { callback(); return; } - var el = elements[offset]; + var element = elements[offset]; var waitForScriptLoaded = false; - if (el.tagName.toLowerCase() == "script") { - // create the script element + if (element.tagName.toLowerCase() == "script") { + var originalScript = element; var script = document.createElement("script"); - if (el.src) { - script.src = el.src; + if (originalScript.src) { + script.src = originalScript.src; waitForScriptLoaded = true; } - if (el.type) { - script.type = el.type; + if (originalScript.type) { + script.type = originalScript.type; } - if (el.text) { - script.text = el.text; + if (originalScript.text) { + script.text = originalScript.text; } - if (el.id) { - script.id = el.id; + if (element.id) { + script.id = element.id; } - el = script; + element = script; } - else if (el.tagName.toLowerCase() == "link") { + else if (element.tagName.toLowerCase() == "link") { // create link + var originalLink = element; var link = document.createElement("link"); - if (el.href) { - link.href = el.href; + if (originalLink.href) { + link.href = originalLink.href; } - if (el.rel) { - link.rel = el.rel; + if (originalLink.rel) { + link.rel = originalLink.rel; } - if (el.type) { - link.type = el.type; + if (originalLink.type) { + link.type = originalLink.type; } - el = link; + element = link; } // load next script when this is finished if (waitForScriptLoaded) { - el.onload = function () { return _this.loadResourceElements(elements, offset + 1, callback); }; + element.addEventListener("load", function () { return _this.loadResourceElements(elements, offset + 1, callback); }); + element.addEventListener("error", function () { return _this.loadResourceElements(elements, offset + 1, callback); }); } - document.head.appendChild(el); + document.head.appendChild(element); if (!waitForScriptLoaded) { this.loadResourceElements(elements, offset + 1, callback); } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts b/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts index e12c3d249f..b1cde48907 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts @@ -673,46 +673,48 @@ class DotVVM { callback(); return; } - var el = elements[offset]; + var element = elements[offset]; var waitForScriptLoaded = false; - if (el.tagName.toLowerCase() == "script") { - // create the script element + if (element.tagName.toLowerCase() == "script") { + var originalScript = element; var script = document.createElement("script"); - if (el.src) { - script.src = el.src; + if (originalScript.src) { + script.src = originalScript.src; waitForScriptLoaded = true; } - if (el.type) { - script.type = el.type; + if (originalScript.type) { + script.type = originalScript.type; } - if (el.text) { - script.text = el.text; + if (originalScript.text) { + script.text = originalScript.text; } - if (el.id) { - script.id = el.id; + if (element.id) { + script.id = element.id; } - el = script; + element = script; } - else if (el.tagName.toLowerCase() == "link") { + else if (element.tagName.toLowerCase() == "link") { // create link + var originalLink = element; var link = document.createElement("link"); - if (el.href) { - link.href = el.href; + if (originalLink.href) { + link.href = originalLink.href; } - if (el.rel) { - link.rel = el.rel; + if (originalLink.rel) { + link.rel = originalLink.rel; } - if (el.type) { - link.type = el.type; + if (originalLink.type) { + link.type = originalLink.type; } - el = link; + element = link; } // load next script when this is finished if (waitForScriptLoaded) { - el.onload = () => this.loadResourceElements(elements, offset + 1, callback); + element.addEventListener("load", () => this.loadResourceElements(elements, offset + 1, callback)); + element.addEventListener("error", () => this.loadResourceElements(elements, offset + 1, callback)); } - document.head.appendChild(el); + document.head.appendChild(element); if (!waitForScriptLoaded) { this.loadResourceElements(elements, offset + 1, callback); } diff --git a/src/DotVVM.Samples.Common/CommonConfiguration.cs b/src/DotVVM.Samples.Common/CommonConfiguration.cs index bc3dffcc00..df382a617e 100644 --- a/src/DotVVM.Samples.Common/CommonConfiguration.cs +++ b/src/DotVVM.Samples.Common/CommonConfiguration.cs @@ -52,6 +52,11 @@ private static void RegisterResources(DotvvmResourceRepository resources) LocationFallback = new ResourceLocationFallback("window.dotvvmTestResource", new FileResourceLocation("~/Scripts/testResource2.js")) }); + resources.Register("FeatureSamples_Resources_RequiredOnPostback", new ScriptResource() { + Location = new UrlResourceLocation("http://unavailable.local/testResource.js"), + LocationFallback = new ResourceLocationFallback("window.dotvvmTestResource", new FileResourceLocation("~/Scripts/testResource.js")) + }); + resources.Register("Errors_InvalidLocationFallback", new ScriptResource { Location = new FileResourceLocation("~/Scripts/testResource.js"), LocationFallback = new ResourceLocationFallback("window.dotvvmTestResource", new FileResourceLocation("~/Scripts/testResource2.js")) diff --git a/src/DotVVM.Samples.Common/Controls/ResourceRequiringButton.cs b/src/DotVVM.Samples.Common/Controls/ResourceRequiringButton.cs new file mode 100644 index 0000000000..97eff170b4 --- /dev/null +++ b/src/DotVVM.Samples.Common/Controls/ResourceRequiringButton.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Controls; +using DotVVM.Framework.Hosting; + +namespace DotVVM.Samples.BasicSamples.Controls +{ + public class ResourceRequiringButton : Button + { + public string ResourceName + { + get { return (string)GetValue(ResourceNameProperty); } + set { SetValue(ResourceNameProperty, value); } + } + public static readonly DotvvmProperty ResourceNameProperty + = DotvvmProperty.Register(c => c.ResourceName, null); + + + protected override void OnPreRender(IDotvvmRequestContext context) + { + if (context.IsPostBack) + { + context.ResourceManager.AddRequiredResource(ResourceName); + } + base.OnPreRender(context); + } + } +} diff --git a/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj b/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj index 50b7c4775b..e301abecaa 100644 --- a/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj +++ b/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj @@ -35,6 +35,7 @@ + diff --git a/src/DotVVM.Samples.Common/Views/FeatureSamples/Resources/RequiredOnPostback.dothtml b/src/DotVVM.Samples.Common/Views/FeatureSamples/Resources/RequiredOnPostback.dothtml new file mode 100644 index 0000000000..0c70f10ac8 --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/FeatureSamples/Resources/RequiredOnPostback.dothtml @@ -0,0 +1,15 @@ +@viewModel object + + + + + Resource required on postback + + +

Welcome

+ + + diff --git a/src/DotVVM.Samples.Tests/Feature/ResourcesTests.cs b/src/DotVVM.Samples.Tests/Feature/ResourcesTests.cs index 94bb100762..7b0a0393ef 100644 --- a/src/DotVVM.Samples.Tests/Feature/ResourcesTests.cs +++ b/src/DotVVM.Samples.Tests/Feature/ResourcesTests.cs @@ -41,7 +41,7 @@ public void Feature_Resources_OnlineNonameResourceLoad() RunInAllBrowsers(browser => { browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_Resources_OnlineNonameResourceLoad); - //click buton + //click button browser.First("input[type=button]").Click(); //check that alert showed @@ -51,6 +51,24 @@ public void Feature_Resources_OnlineNonameResourceLoad() }); } + [Fact] + public void Feature_Resource_RequiredOnPostback() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_Resources_RequiredOnPostback); + browser.WaitUntilDotvvmInited(); + + var welcome = browser.Single("welcome", SelectByDataUi); + AssertUI.TextEquals(welcome, "Welcome"); + + browser.Single("button", SelectByDataUi).Click(); + browser.WaitFor(() => AssertUI.AlertTextEquals(browser, "javascript resource loaded!"), 5000); + + browser.ConfirmAlert(); + browser.WaitFor(() => AssertUI.TextEquals(welcome, "Welcome"), 1000); + }); + } + public ResourcesTests(ITestOutputHelper output) : base(output) { } diff --git a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs index 7e2839e913..03e7e87a28 100644 --- a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs +++ b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs @@ -134,8 +134,8 @@ public partial class SamplesRouteUrls public const string Errors_EmptyBinding = "Errors/EmptyBinding"; public const string Errors_EncryptedPropertyInValueBinding = "Errors/EncryptedPropertyInValueBinding"; public const string Errors_FieldInValueBinding = "Errors/FieldInValueBinding"; - public const string Errors_InvalidServiceDirective = "Errors/InvalidServiceDirective"; public const string Errors_InvalidLocationFallback = "Errors/InvalidLocationFallback"; + public const string Errors_InvalidServiceDirective = "Errors/InvalidServiceDirective"; public const string Errors_InvalidViewModel = "Errors/InvalidViewModel"; public const string Errors_MalformedBinding = "Errors/MalformedBinding"; public const string Errors_MarkupControlInvalidViewModel = "Errors/MarkupControlInvalidViewModel"; @@ -215,6 +215,7 @@ public partial class SamplesRouteUrls public const string FeatureSamples_Resources_CdnScriptPriority = "FeatureSamples/Resources/CdnScriptPriority"; public const string FeatureSamples_Resources_CdnUnavailableResourceLoad = "FeatureSamples/Resources/CdnUnavailableResourceLoad"; public const string FeatureSamples_Resources_OnlineNonameResourceLoad = "FeatureSamples/Resources/OnlineNonameResourceLoad"; + public const string FeatureSamples_Resources_RequiredOnPostback = "FeatureSamples/Resources/RequiredOnPostback"; public const string FeatureSamples_ReturnedFile_ReturnedFileSample = "FeatureSamples/ReturnedFile/ReturnedFileSample"; public const string FeatureSamples_Serialization_EnumSerializationWithJsonConverter = "FeatureSamples/Serialization/EnumSerializationWithJsonConverter"; public const string FeatureSamples_Serialization_ObservableCollectionShouldContainObservables = "FeatureSamples/Serialization/ObservableCollectionShouldContainObservables"; From c9a689dfed09484f73f93c9979222d8a1f3c3c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=A0t=C4=9Bp=C3=A1nek?= Date: Fri, 6 Sep 2019 12:51:14 +0200 Subject: [PATCH 2/2] Use safer unavailable URLs --- src/DotVVM.Samples.Common/CommonConfiguration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotVVM.Samples.Common/CommonConfiguration.cs b/src/DotVVM.Samples.Common/CommonConfiguration.cs index df382a617e..4991425c34 100644 --- a/src/DotVVM.Samples.Common/CommonConfiguration.cs +++ b/src/DotVVM.Samples.Common/CommonConfiguration.cs @@ -43,7 +43,7 @@ private static void RegisterResources(DotvvmResourceRepository resources) resources.Register("ControlSamples_SpaContentPlaceHolder_MasterPageResource", new ScriptResource(new FileResourceLocation("Scripts/testResource2.js"))); resources.Register("FeatureSamples_Resources_CdnUnavailableResourceLoad", new ScriptResource() { - Location = new UrlResourceLocation("http://unavailable.local/testResource.js"), + Location = new UrlResourceLocation("~/nonexistentResource.js"), LocationFallback = new ResourceLocationFallback("window.dotvvmTestResource", new FileResourceLocation("~/Scripts/testResource.js")) }); @@ -53,7 +53,7 @@ private static void RegisterResources(DotvvmResourceRepository resources) }); resources.Register("FeatureSamples_Resources_RequiredOnPostback", new ScriptResource() { - Location = new UrlResourceLocation("http://unavailable.local/testResource.js"), + Location = new UrlResourceLocation("~/nonexistentResource.js"), LocationFallback = new ResourceLocationFallback("window.dotvvmTestResource", new FileResourceLocation("~/Scripts/testResource.js")) });