You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2014/08/07 20:55:44 UTC

git commit: TAP5-2371: Prevent interaction with page until fully loaded

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 <hl...@apache.org>
Authored: Thu Aug 7 11:55:59 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
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: <code>false</code>. 
+     * {@link Html5Support#isHtml5SupportEnabled()}. Default value: <code>false</code>.
      *
-     * @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<String> HTML_MIME_TYPES = CollectionFactory.newSet("text/html", "application/xml+xhtml");
-    
+
     private final List<String> coreLibraryURLs = CollectionFactory.newList();
 
     private final List<String> libraryURLs = CollectionFactory.newList();
 
     private final ModuleInitsManager initsManager = new ModuleInitsManager();
-    
+
     private final List<ModuleConfigurationCallback> moduleConfigurationCallbacks = CollectionFactory.newList();
 
     private final List<StylesheetLink> 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(\"<div class=\\\"pageloading-mask\\\"><div></div></div>\");");
+
+            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, '''
-<html><body data-page-initialized="false"><p>Ready to be updated with scripts.</p><!--MM-CONFIG--><!--MM-INIT--></body></html>
+<html><body data-page-initialized="false"><script type="text/javascript">document.write("<div class=\\"pageloading-mask\\"><div></div></div>");</script><p>Ready to be updated with scripts.</p><!--MM-CONFIG--><!--MM-INIT--></body></html>
 '''
 
         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()