Return-Path: X-Original-To: apmail-tapestry-dev-archive@www.apache.org Delivered-To: apmail-tapestry-dev-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 5D922110A8 for ; Thu, 7 Aug 2014 18:55:45 +0000 (UTC) Received: (qmail 57532 invoked by uid 500); 7 Aug 2014 18:55:45 -0000 Delivered-To: apmail-tapestry-dev-archive@tapestry.apache.org Received: (qmail 57479 invoked by uid 500); 7 Aug 2014 18:55:45 -0000 Mailing-List: contact commits-help@tapestry.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@tapestry.apache.org Delivered-To: mailing list commits@tapestry.apache.org Received: (qmail 57455 invoked by uid 99); 7 Aug 2014 18:55:44 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 07 Aug 2014 18:55:44 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id A894E94A537; Thu, 7 Aug 2014 18:55:44 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: hlship@apache.org To: commits@tapestry.apache.org Message-Id: <2fcaf4d19c934f789fcdbaf6a4850677@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: git commit: TAP5-2371: Prevent interaction with page until fully loaded Date: Thu, 7 Aug 2014 18:55:44 +0000 (UTC) Repository: tapestry-5 Updated Branches: refs/heads/master 50ebb518e -> 0119f9a89 TAP5-2371: Prevent interaction with page until fully loaded Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/0119f9a8 Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/0119f9a8 Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/0119f9a8 Branch: refs/heads/master Commit: 0119f9a890c8becb80fa1d5f36c1ab1f4ec8660c Parents: 50ebb51 Author: Howard M. Lewis Ship Authored: Thu Aug 7 11:55:59 2014 -0700 Committer: Howard M. Lewis Ship Committed: Thu Aug 7 11:55:59 2014 -0700 ---------------------------------------------------------------------- .../META-INF/modules/t5/core/pageinit.coffee | 5 ++- .../org/apache/tapestry5/SymbolConstants.java | 15 +++++-- .../internal/services/DocumentLinkerImpl.java | 39 +++++++++++------- .../tapestry5/modules/TapestryModule.java | 7 +++- .../assets/tapestry5/pageloader-mask.gif | Bin 0 -> 13270 bytes .../META-INF/assets/tapestry5/tapestry.css | 41 +++++++++++++++++++ .../services/DocumentLinkerImplTest.groovy | 32 +++++++-------- 7 files changed, 103 insertions(+), 36 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee index 1161335..a500ef7 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee @@ -1,5 +1,3 @@ -# Copyright 2012, 2013 The Apache Software Foundation -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -129,6 +127,9 @@ define ["underscore", "./console", "./dom", "./events"], dom.body.attr "data-page-initialized", "true" + for mask in dom.body.find ".pageloading-mask" + mask.remove() + exports = _.extend loadLibrariesAndInitialize, # Passed a list of initializers, executes each initializer in order. Due to asynchronous loading # of modules, the exact order in which initializer functions are invoked is not predictable. http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java index bef3df4..0257c1e 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java @@ -510,13 +510,13 @@ public class SymbolConstants * @since 5.4 */ public static final String OMIT_EXPIRATION_CACHE_CONTROL_HEADER = "tapestry.omit-expiration-cache-control-header"; - + /** * Defines whether HTML5 features should be used. Value used in the default implementation of - * {@link Html5Support#isHtml5SupportEnabled()}. Default value: false. + * {@link Html5Support#isHtml5SupportEnabled()}. Default value: false. * - * @since 5.4 * @see Html5Support#isHtml5SupportEnabled() + * @since 5.4 */ public static final String ENABLE_HTML5_SUPPORT = "tapestry.enable-html5-support"; @@ -527,4 +527,13 @@ public class SymbolConstants * @since 5.4 */ public static final String RESTRICTIVE_ENVIRONMENT = "tapestry.restrictive-environment"; + + /** + * If true, then when a page includes any JavaScript, a {@code script} block is added to insert + * a pageloader mask into the page; the pageloader mask ensure that the user can't interact with the page + * until after the page is fully initialized. + * + * @since 5.4 + */ + public static final String ENABLE_PAGELOADING_MASK = "tapestry.enable-pageloading-mask"; } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java index 67d4dba..a6f5198 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java @@ -1,5 +1,3 @@ -// Copyright 2007-2013 The Apache Software Foundation -// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -23,28 +21,27 @@ import org.apache.tapestry5.services.javascript.ModuleConfigurationCallback; import org.apache.tapestry5.services.javascript.ModuleManager; import org.apache.tapestry5.services.javascript.StylesheetLink; -import java.util.Collections; import java.util.List; import java.util.Set; public class DocumentLinkerImpl implements DocumentLinker { - + private final static Set HTML_MIME_TYPES = CollectionFactory.newSet("text/html", "application/xml+xhtml"); - + private final List coreLibraryURLs = CollectionFactory.newList(); private final List libraryURLs = CollectionFactory.newList(); private final ModuleInitsManager initsManager = new ModuleInitsManager(); - + private final List moduleConfigurationCallbacks = CollectionFactory.newList(); private final List includedStylesheets = CollectionFactory.newList(); private final ModuleManager moduleManager; - private final boolean omitGeneratorMetaTag; + private final boolean omitGeneratorMetaTag, enablePageloadingMask; private final String tapestryBanner; @@ -56,13 +53,14 @@ public class DocumentLinkerImpl implements DocumentLinker * used to identify the root folder for dynamically loaded modules * @param omitGeneratorMetaTag * via symbol configuration + * @param enablePageloadingMask * @param tapestryVersion - * version of Tapestry framework (for meta tag) */ - public DocumentLinkerImpl(ModuleManager moduleManager, boolean omitGeneratorMetaTag, String tapestryVersion) + public DocumentLinkerImpl(ModuleManager moduleManager, boolean omitGeneratorMetaTag, boolean enablePageloadingMask, String tapestryVersion) { this.moduleManager = moduleManager; this.omitGeneratorMetaTag = omitGeneratorMetaTag; + this.enablePageloadingMask = enablePageloadingMask; tapestryBanner = String.format("Apache Tapestry Framework (version %s)", tapestryVersion); } @@ -115,11 +113,12 @@ public class DocumentLinkerImpl implements DocumentLinker { return; } - + // TAP5-2200: Generating XML from pages and templates is not possible anymore // only add JavaScript and CSS if we're actually generating final String mimeType = document.getMimeType(); - if (mimeType != null && !HTML_MIME_TYPES.contains(mimeType)) { + if (mimeType != null && !HTML_MIME_TYPES.contains(mimeType)) + { return; } @@ -182,7 +181,7 @@ public class DocumentLinkerImpl implements DocumentLinker // TAPESTRY-2364 - addScriptsToEndOfBody(body); + addContentToBody(body); } /** @@ -219,8 +218,20 @@ public class DocumentLinkerImpl implements DocumentLinker * @param body * element to add the dynamic scripting to */ - protected void addScriptsToEndOfBody(Element body) + protected void addContentToBody(Element body) { + if (enablePageloadingMask) + { + // This adds a mask element to the page, based on the Bootstrap modal dialog backdrop. The mark + // is present immediately, but fades in visually after a short delay, and is removed + // after page initialization is complete. For a client that doesn't have JavaScript enabled, + // this will do nothing (though I suspect the page will not behave to expectations!). + Element script = body.element("script", "type", "text/javascript"); + script.raw("document.write(\"
\");"); + + script.moveToTop(body); + } + moduleManager.writeConfiguration(body, moduleConfigurationCallbacks); // Write the core libraries, which includes RequireJS: @@ -294,5 +305,5 @@ public class DocumentLinkerImpl implements DocumentLinker assert callback != null; moduleConfigurationCallbacks.add(callback); } - + } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java index a90003f..195b617 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java @@ -1732,13 +1732,16 @@ public final class TapestryModule @Symbol(SymbolConstants.INCLUDE_CORE_STACK) final boolean includeCoreStack, + @Symbol(SymbolConstants.ENABLE_PAGELOADING_MASK) + final boolean enablePageloadingMask, + final ValidationDecoratorFactory validationDecoratorFactory) { MarkupRendererFilter documentLinker = new MarkupRendererFilter() { public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) { - DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, omitGeneratorMeta, tapestryVersion); + DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, omitGeneratorMeta, enablePageloadingMask, tapestryVersion); environment.push(DocumentLinker.class, linker); @@ -2137,6 +2140,8 @@ public final class TapestryModule configuration.add(SymbolConstants.ENABLE_HTML5_SUPPORT, false); configuration.add(SymbolConstants.RESTRICTIVE_ENVIRONMENT, false); + + configuration.add(SymbolConstants.ENABLE_PAGELOADING_MASK, true); } /** http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif new file mode 100644 index 0000000..861468d Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif differ http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css index c7d47eb..b1d7025 100644 --- a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css +++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css @@ -31,3 +31,44 @@ div.datefield-popup.well { text-align: right; } +@-webkit-keyframes pageloading-mask-fade-in { + from { + opacity: 0; + } + to { + opacity: .50 + } +} + +@-moz-keyframes pageloading-mask-fade-in { + from { + opacity: 0; + } + to { + opacity: .50 + } +} + +/** Added via JavaScript when a page is initially loaded, then removed after all page initializations occur. */ +.pageloading-mask { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; + -webkit-animation-name: pageloading-mask-fade-in; + -webkit-animation-duration: 250ms; + -webkit-animation-delay: 250ms; + -webkit-animation-fill-mode: forwards; + -moz-animation-name: pageloading-mask-fade-in; + -moz-animation-duration: 250ms; + -moz-animation-delay: 250ms; + -moz-animation-fill-mode: forwards; +} + +.pageloading-mask div { + height: 100%; + background: url(pageloader-mask.gif) no-repeat center center; +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy index 4535403..b1a335f 100644 --- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy +++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy @@ -32,7 +32,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("not-html").text("not an HTML document") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") // Only checked if there's something to link. @@ -55,7 +55,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("not-html").text("not an HTML document") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") // Only checked if there's something to link. @@ -76,7 +76,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { void missing_root_element_is_a_noop() { Document document = new Document() - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") linker.addLibrary("foo.js") linker.addScript(InitializationPriority.NORMAL, "doSomething();") @@ -94,7 +94,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { def manager = mockModuleManager(["core.js", "foo.js", "bar/baz.js"], [new JSONArray("t5/core/pageinit:evalJavaScript", "pageINIT();")]) - DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3") replay() @@ -122,7 +122,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("html").element("body").element("p").text("Ready to be marked with generator meta.") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, false, "1.2.3") linker.updateDocument(document) @@ -141,7 +141,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("no_html").text("Generator meta only added if root is html tag.") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, false, "1.2.3") linker.updateDocument(document) @@ -158,7 +158,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("html").element("body").element("p").text("Ready to be updated with styles.") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") linker.addStylesheetLink(new StylesheetLink("foo.css")) linker.addStylesheetLink(new StylesheetLink("bar/baz.css", new StylesheetOptions("print"))) @@ -178,7 +178,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("html").element("head").comment(" existing head ").container.element("body").text( "body content") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") linker.addStylesheetLink(new StylesheetLink("foo.css")) @@ -198,7 +198,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { def manager = mockModuleManager([], [new JSONArray("t5/core/pageinit:evalJavaScript", "doSomething();")]) - DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, true, "1.2.3") replay() @@ -207,7 +207,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { linker.updateDocument(document) check document, ''' -

Ready to be updated with scripts.

+

Ready to be updated with scripts.

''' verify() @@ -224,7 +224,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { def manager = mockModuleManager(["foo.js"], []) - DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3") replay() @@ -251,7 +251,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { def manager = mockModuleManager([], [new JSONArray("['immediate/module:myfunc', {'fred':'barney'}]")]) - DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3") replay() @@ -273,7 +273,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("html") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") linker.addStylesheetLink(new StylesheetLink("everybody.css")) linker.addStylesheetLink(new StylesheetLink("just_ie.css", new StylesheetOptions().withCondition("IE"))) @@ -295,7 +295,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { document.newRootElement("html") - DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3") linker.addStylesheetLink(new StylesheetLink("whatever.css")) linker.addStylesheetLink(new StylesheetLink("insertion-point.css", new StylesheetOptions().asAjaxInsertionPoint())) @@ -319,7 +319,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { new JSONArray("my/other/module:normal", 111, 222), new JSONArray("my/other/module:late", 333, 444)]) - DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3") replay() @@ -347,7 +347,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase { def manager = mockModuleManager([], ["my/module", new JSONArray("my/other/module:normal", 111, 222)]) - DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3") + DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3") replay()