You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2014/07/18 02:24:32 UTC

[5/5] git commit: TAP5-2361: Overriding the default Bootstrap files can make exception report or dashboard pages unreadable

TAP5-2361: Overriding the default Bootstrap files can make exception report or dashboard pages unreadable


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/6512f5f3
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/6512f5f3
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/6512f5f3

Branch: refs/heads/master
Commit: 6512f5f3a0d8a21a0992b10489760860304aec67
Parents: 5b46442
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Thu Jul 17 17:22:36 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Thu Jul 17 17:22:36 2014 -0700

----------------------------------------------------------------------
 .../java/org/apache/tapestry5/BooleanHook.java  | 24 ++++++
 .../corelib/base/AbstractInternalPage.java      | 43 +++++++++++
 .../corelib/pages/ExceptionReport.java          |  9 +--
 .../tapestry5/corelib/pages/T5Dashboard.java    |  7 +-
 .../tapestry5/internal/InternalConstants.java   | 10 ++-
 .../services/ajax/JavaScriptSupportImpl.java    | 31 ++++----
 .../internal/services/javascript/Internal.java  | 33 ++++++++
 .../tapestry5/modules/JavaScriptModule.java     | 80 +++++++++++++++-----
 .../integration/app5/services/AppModule.groovy  |  2 +
 .../ajax/JavaScriptSupportAutofocusTests.groovy |  2 +-
 .../ajax/JavaScriptSupportImplTest.groovy       | 80 +++++++++++++++-----
 .../integration/app5/components/Layout.tml      |  3 +-
 12 files changed, 256 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/BooleanHook.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/BooleanHook.java b/tapestry-core/src/main/java/org/apache/tapestry5/BooleanHook.java
new file mode 100644
index 0000000..d715630
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/BooleanHook.java
@@ -0,0 +1,24 @@
+// 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
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5;
+
+/**
+ * Encapsulates a bit of information that can be represented as a boolean.
+ *
+ * @since 5.4
+ */
+public interface BooleanHook
+{
+    /** Evaluates the hook and returns its value. */
+    boolean checkHook();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractInternalPage.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractInternalPage.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractInternalPage.java
new file mode 100644
index 0000000..e78a676
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractInternalPage.java
@@ -0,0 +1,43 @@
+// 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
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.corelib.base;
+
+import org.apache.tapestry5.annotations.Import;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.services.Request;
+
+/**
+ * Base page for Tapestry internal pages, that should suppress any application changes to the core stack's CSS.
+ * CSS from the core stack is suppressed, instead the internal stack (which exists for just this purpose)
+ * is imported.
+ *
+ * @since 5.4
+ */
+public abstract class AbstractInternalPage
+{
+    @Inject @Property(write = false)
+    protected Request request;
+
+    void setupRender()
+    {
+        request.setAttribute(InternalConstants.SUPPRESS_CORE_STYLESHEETS, true);
+    }
+
+    @Import(stack = "internal")
+    void beginRender()
+    {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index bdc853a..98d0b21 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -19,6 +19,7 @@ import org.apache.tapestry5.annotations.ContentType;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
+import org.apache.tapestry5.corelib.base.AbstractInternalPage;
 import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Mapper;
 import org.apache.tapestry5.internal.InternalConstants;
@@ -44,8 +45,8 @@ import java.util.regex.Pattern;
  */
 @UnknownActivationContextCheck(false)
 @ContentType("text/html")
-@Import(stack = "core", stylesheet = "ExceptionReport.css")
-public class ExceptionReport implements ExceptionReporter
+@Import(stylesheet = "ExceptionReport.css")
+public class ExceptionReport extends AbstractInternalPage implements ExceptionReporter
 {
     private static final String PATH_SEPARATOR_PROPERTY = "path.separator";
 
@@ -57,10 +58,6 @@ public class ExceptionReport implements ExceptionReporter
     private String attributeName;
 
     @Inject
-    @Property
-    private Request request;
-
-    @Inject
     @Symbol(SymbolConstants.PRODUCTION_MODE)
     @Property(write = false)
     private boolean productionMode;

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/T5Dashboard.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/T5Dashboard.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/T5Dashboard.java
index e8c5695..aa815f1 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/T5Dashboard.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/T5Dashboard.java
@@ -1,5 +1,3 @@
-// Copyright 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
@@ -17,6 +15,7 @@ package org.apache.tapestry5.corelib.pages;
 import org.apache.tapestry5.Block;
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.annotations.*;
+import org.apache.tapestry5.corelib.base.AbstractInternalPage;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.dashboard.DashboardManager;
@@ -28,8 +27,8 @@ import org.apache.tapestry5.services.dashboard.DashboardManager;
 @UnknownActivationContextCheck(false)
 @WhitelistAccessOnly
 @ContentType("text/html")
-@Import(stack = "core", stylesheet = "dashboard.css")
-public class T5Dashboard
+@Import(stylesheet = "dashboard.css")
+public class T5Dashboard extends AbstractInternalPage
 {
     @Inject
     @Symbol(SymbolConstants.TAPESTRY_VERSION)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
index 6d179fd..c069f11 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalConstants.java
@@ -1,5 +1,3 @@
-// Copyright 2006-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
@@ -180,4 +178,12 @@ public final class InternalConstants
      * @since 5.4
      */
     public static final String ACTIVE_PAGE_LOADED = "tapestry.active-page-loaded";
+
+    /**
+     * Used to suppress the stylesheets from the 'core' stack; this is used on certain pages
+     * that want to work around application-specific overrides to the core stack stylesheets.
+     *
+     * @since 5.4
+     */
+    public static final String SUPPRESS_CORE_STYLESHEETS = "tapestry.suppress-core-stylesheets";
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
index 08e1b88..cc3a155 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
@@ -1,5 +1,3 @@
-// Copyright 2010-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
@@ -15,6 +13,7 @@
 package org.apache.tapestry5.internal.services.ajax;
 
 import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.BooleanHook;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.FieldFocusPriority;
 import org.apache.tapestry5.func.F;
@@ -29,11 +28,7 @@ import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.javascript.*;
 
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 public class JavaScriptSupportImpl implements JavaScriptSupport
 {
@@ -59,6 +54,8 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
 
     private final boolean partialMode;
 
+    private final BooleanHook suppressCoreStylesheetsHook;
+
     private FieldFocusPriority focusPriority;
 
     private String focusFieldId;
@@ -107,9 +104,9 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     }
 
     public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource,
-                                 JavaScriptStackPathConstructor stackPathConstructor)
+                                 JavaScriptStackPathConstructor stackPathConstructor, BooleanHook suppressCoreStylesheetsHook)
     {
-        this(linker, javascriptStackSource, stackPathConstructor, new IdAllocator(), false);
+        this(linker, javascriptStackSource, stackPathConstructor, new IdAllocator(), false, suppressCoreStylesheetsHook);
     }
 
     /**
@@ -117,7 +114,7 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
      *         responsible for assembling all the information gathered by JavaScriptSupport and
      *         attaching it to the Document (for a full page render) or to the JSON response (in a partial render)
      * @param javascriptStackSource
-     *         source of information about {@link JavaScriptStack}s, used when handling the import
+     *         source of information about {@link org.apache.tapestry5.services.javascript.JavaScriptStack}s, used when handling the import
      *         of libraries and stacks (often, to handle transitive dependencies)
      * @param stackPathConstructor
      *         encapsulates the knowledge of how to represent a stack (which may be converted
@@ -128,16 +125,19 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
      * @param partialMode
      *         if true, then the JSS configures itself for a partial page render (part of an Ajax request)
      *         which automatically assumes the "core" library has been added (to the original page render)
-     *         and makes other minor changes to behavior.
+     * @param suppressCoreStylesheetsHook
+     *         a hook that enables ignoring CSS files on the core stack
      */
     public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource,
-                                 JavaScriptStackPathConstructor stackPathConstructor, IdAllocator idAllocator, boolean partialMode)
+                                 JavaScriptStackPathConstructor stackPathConstructor, IdAllocator idAllocator, boolean partialMode,
+                                 BooleanHook suppressCoreStylesheetsHook)
     {
         this.linker = linker;
         this.idAllocator = idAllocator;
         this.javascriptStackSource = javascriptStackSource;
         this.stackPathConstructor = stackPathConstructor;
         this.partialMode = partialMode;
+        this.suppressCoreStylesheetsHook = suppressCoreStylesheetsHook;
 
         // In partial mode, assume that the infrastructure stack is already present
         // (from the original page render).
@@ -189,7 +189,7 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     }
 
     public void addInitializerCall(InitializationPriority priority, String functionName,
-            JSONArray parameter)
+                                   JSONArray parameter)
     {
         // TAP5-2300: In 5.3, a JSONArray implied an array of method arguments, so unwrap and add
         // functionName to the arguments
@@ -379,7 +379,10 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
             }
         }
 
-        stylesheetLinks.addAll(stack.getStylesheets());
+        if (!(addAsCoreLibrary && suppressCoreStylesheetsHook.checkHook()))
+        {
+            stylesheetLinks.addAll(stack.getStylesheets());
+        }
 
         String initialization = stack.getInitialization();
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/Internal.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/Internal.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/Internal.java
new file mode 100644
index 0000000..9998a5a
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/Internal.java
@@ -0,0 +1,33 @@
+// 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
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.javascript;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Marker annotation for the internal {@link org.apache.tapestry5.services.javascript.JavaScriptStack}.
+ *
+ * @since 5.4
+ */
+@Target(
+        {ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
+@Retention(RUNTIME)
+@Documented
+public @interface Internal
+{
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
index 314c423..903fb1a 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
@@ -1,5 +1,3 @@
-// Copyright 2012-2014 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
@@ -14,6 +12,7 @@
 
 package org.apache.tapestry5.modules;
 
+import org.apache.tapestry5.BooleanHook;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.annotations.Path;
@@ -41,12 +40,14 @@ import org.apache.tapestry5.services.messages.ComponentMessagesSource;
 import java.util.Locale;
 
 /**
- * Defines the services related to JavaScript.
+ * Defines the services related to JavaScript and {@link org.apache.tapestry5.services.javascript.JavaScriptStack}s.
  *
  * @since 5.4
  */
 public class JavaScriptModule
 {
+    private final static String ROOT = "${tapestry.asset.root}";
+
     private final Environment environment;
 
     private final EnvironmentalShadowBuilder environmentalBuilder;
@@ -62,17 +63,21 @@ public class JavaScriptModule
         binder.bind(ModuleManager.class, ModuleManagerImpl.class);
         binder.bind(JavaScriptStackSource.class, JavaScriptStackSourceImpl.class);
         binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Core.class).withId("CoreJavaScriptStack");
+        binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Internal.class).withId("InternalJavaScriptStack");
     }
 
     /**
-     * Contributes the "core" {@link JavaScriptStack}s
+     * Contributes the "core" and "internal" {@link JavaScriptStack}s
      *
      * @since 5.2.0
      */
     @Contribute(JavaScriptStackSource.class)
-    public static void provideBuiltinJavaScriptStacks(MappedConfiguration<String, JavaScriptStack> configuration, @Core JavaScriptStack coreStack)
+    public static void provideBuiltinJavaScriptStacks(MappedConfiguration<String, JavaScriptStack> configuration,
+                                                      @Core JavaScriptStack coreStack,
+                                                      @Internal JavaScriptStack internalStack)
     {
         configuration.add(InternalConstants.CORE_STACK_NAME, coreStack);
+        configuration.add("internal", internalStack);
     }
 
     // These are automatically bundles with the core JavaScript stack; some applications may want to add a few
@@ -111,9 +116,6 @@ public class JavaScriptModule
                                                 @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER)
                                                 String provider)
     {
-
-        final String ROOT = "${tapestry.asset.root}";
-
         configuration.add("requirejs", StackExtension.library(ROOT + "/require.js"));
         configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.5.2.js"));
 
@@ -146,8 +148,33 @@ public class JavaScriptModule
 
         add(configuration, StackExtensionType.MODULE, "jquery");
 
+        addCoreStylesheets(configuration, "${" + SymbolConstants.BOOTSTRAP_ROOT + "}/css/bootstrap.css");
+
+        for (String name : bundledModules)
+        {
+            String full = "t5/core/" + name;
+            configuration.add(full, StackExtension.module(full));
+        }
+
+        configuration.add("underscore-module", StackExtension.module("underscore"));
+    }
+
+    @Contribute(JavaScriptStack.class)
+    @Internal
+    public static void setupInternalJavaScriptStack(OrderedConfiguration<StackExtension> configuration)
+    {
+
+        // For the internal stack, ignore the configuration and just use the Bootstrap CSS shipped with the
+        // framework. This is part of a hack to make internal pages (such as ExceptionReport and T5Dashboard)
+        // render correctly even when the Bootstrap CSS has been replaced by the application.
+
+        addCoreStylesheets(configuration, ROOT + "/bootstrap/css/bootstrap.css");
+    }
+
+    private static void addCoreStylesheets(OrderedConfiguration<StackExtension> configuration, String bootstrapPath)
+    {
         add(configuration, StackExtensionType.STYLESHEET,
-                "${" + SymbolConstants.BOOTSTRAP_ROOT + "}/css/bootstrap.css",
+                bootstrapPath,
 
                 ROOT + "/tapestry.css",
 
@@ -156,14 +183,6 @@ public class JavaScriptModule
                 ROOT + "/tapestry-console.css",
 
                 ROOT + "/tree.css");
-
-        for (String name : bundledModules)
-        {
-            String full = "t5/core/" + name;
-            configuration.add(full, StackExtension.module(full));
-        }
-
-        configuration.add("underscore-module", StackExtension.module("underscore"));
     }
 
     private static void add(OrderedConfiguration<StackExtension> configuration, StackExtensionType type, String... paths)
@@ -220,9 +239,12 @@ public class JavaScriptModule
     @Contribute(MarkupRenderer.class)
     public void exposeJavaScriptSupportForFullPageRenders(OrderedConfiguration<MarkupRendererFilter> configuration,
                                                           final JavaScriptStackSource javascriptStackSource,
-                                                          final JavaScriptStackPathConstructor javascriptStackPathConstructor)
+                                                          final JavaScriptStackPathConstructor javascriptStackPathConstructor,
+                                                          final Request request)
     {
 
+        final BooleanHook suppressCoreStylesheetsHook = createSuppressCoreStylesheetHook(request);
+
         MarkupRendererFilter javaScriptSupport = new MarkupRendererFilter()
         {
             public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
@@ -230,7 +252,7 @@ public class JavaScriptModule
                 DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
 
                 JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource,
-                        javascriptStackPathConstructor);
+                        javascriptStackPathConstructor, suppressCoreStylesheetsHook);
 
                 environment.push(JavaScriptSupport.class, support);
 
@@ -257,8 +279,12 @@ public class JavaScriptModule
     public void exposeJavaScriptSupportForPartialPageRender(OrderedConfiguration<PartialMarkupRendererFilter> configuration,
                                                             final JavaScriptStackSource javascriptStackSource,
 
-                                                            final JavaScriptStackPathConstructor javascriptStackPathConstructor)
+                                                            final JavaScriptStackPathConstructor javascriptStackPathConstructor,
+
+                                                            final Request request)
     {
+        final BooleanHook suppressCoreStylesheetsHook = createSuppressCoreStylesheetHook(request);
+
         PartialMarkupRendererFilter javascriptSupport = new PartialMarkupRendererFilter()
         {
             public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer)
@@ -272,7 +298,7 @@ public class JavaScriptModule
                 DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
 
                 JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource,
-                        javascriptStackPathConstructor, idAllocator, true);
+                        javascriptStackPathConstructor, idAllocator, true, suppressCoreStylesheetsHook);
 
                 environment.push(JavaScriptSupport.class, support);
 
@@ -287,6 +313,18 @@ public class JavaScriptModule
         configuration.add("JavaScriptSupport", javascriptSupport, "after:DocumentLinker");
     }
 
+    private BooleanHook createSuppressCoreStylesheetHook(final Request request)
+    {
+        return new BooleanHook()
+        {
+            @Override
+            public boolean checkHook()
+            {
+                return request.getAttribute(InternalConstants.SUPPRESS_CORE_STYLESHEETS) != null;
+            }
+        };
+    }
+
 
     @Contribute(ModuleManager.class)
     public static void setupBaseModules(MappedConfiguration<String, Object> configuration,

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy
index 33c0f5b..49f784c 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app5/services/AppModule.groovy
@@ -33,6 +33,8 @@ class AppModule {
     void contributeApplicationDefaults(MappedConfiguration conf) {
         conf.add(SymbolConstants.PRODUCTION_MODE, false)
         conf.add(SymbolConstants.SUPPORTED_LOCALES, "en,fr")
+        // Override to test TAP5-2361
+        conf.add(SymbolConstants.BOOTSTRAP_ROOT, "context:bootstrap")
     }
 
     def decorateComponentRequestSelectorAnalyzer(ComponentRequestSelectorAnalyzer delegate, ApplicationStateManager mgr) {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
index afd7279..c631d44 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
@@ -42,7 +42,7 @@ class JavaScriptSupportAutofocusTests extends InternalBaseTestCase {
         replay()
 
         // Test in partial mode, to bypass the logic about importing the "core' stack.
-        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor, null, true)
+        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor, null, true, null)
 
         cls jss
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
index 5e3a4f4..9694989 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
@@ -1,7 +1,7 @@
 package org.apache.tapestry5.internal.services.ajax
 
-import org.testng.annotations.Test;
 import org.apache.tapestry5.Asset
+import org.apache.tapestry5.BooleanHook
 import org.apache.tapestry5.ComponentResources
 import org.apache.tapestry5.internal.InternalConstants
 import org.apache.tapestry5.internal.services.DocumentLinker
@@ -14,6 +14,18 @@ import org.testng.annotations.Test
 
 class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
+    class StaticHook implements BooleanHook {
+
+        final boolean value
+
+        StaticHook(value) { this.value = value }
+
+        @Override
+        boolean checkHook() { value }
+    }
+
+    def falseHook = new StaticHook(false)
+
     @Test
     void allocate_id_from_resources() {
         ComponentResources resources = mockComponentResources()
@@ -22,7 +34,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupport jss = new JavaScriptSupportImpl(null, null, null)
+        JavaScriptSupport jss = new JavaScriptSupportImpl(null, null, null, null)
 
         assertEquals(jss.allocateClientId(resources), "tracy")
         assertEquals(jss.allocateClientId(resources), "tracy_0")
@@ -33,7 +45,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
     @Test
     void commit_with_no_javascript() {
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(null, null, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(null, null, null, null)
 
         jss.commit()
     }
@@ -46,11 +58,11 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
         train_for_just_core_stack stackSource
 
         linker.addInitialization(InitializationPriority.NORMAL, "t5/core/pageinit", "evalJavaScript",
-                new JSONArray().put("doSomething();"))
+            new JSONArray().put("doSomething();"))
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, null, new IdAllocator(), true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, null, new IdAllocator(), true, null)
 
         jss.addScript("doSomething();")
 
@@ -96,7 +108,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
 
         jss.addScript(InitializationPriority.IMMEDIATE, "doSomething();")
 
@@ -117,7 +129,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.importJavaScriptLibrary(library)
 
@@ -153,7 +165,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.importJavaScriptLibrary(library1)
 
@@ -163,6 +175,38 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
     }
 
     @Test
+    void core_stack_stylesheets_may_be_suppressed() {
+        DocumentLinker linker = mockDocumentLinker()
+        JavaScriptStackSource stackSource = mockJavaScriptStackSource()
+        JavaScriptStackPathConstructor pathConstructor = mockJavaScriptStackPathConstructor()
+
+        JavaScriptStack stack = mockJavaScriptStack()
+
+        def cssLink = new StylesheetLink("foo.css", null)
+
+        expect(stackSource.getStack(InternalConstants.CORE_STACK_NAME)).andReturn(stack)
+
+        expect(stack.stacks).andReturn([])
+
+        // NO class to getStylesheets, because its the core stack and the hook is true.
+
+        expect(pathConstructor.constructPathsForJavaScriptStack(InternalConstants.CORE_STACK_NAME)).andReturn([])
+
+        expect(stack.initialization).andReturn null
+
+        replay()
+
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, false, new StaticHook(true))
+
+        jss.importStack(InternalConstants.CORE_STACK_NAME)
+
+        jss.commit()
+
+        verify()
+
+    }
+
+    @Test
     void requireing_a_module_may_import_a_stack() {
         DocumentLinker linker = mockDocumentLinker()
         JavaScriptStackSource stackSource = mockJavaScriptStackSource()
@@ -189,7 +233,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.require("foo/bar")
 
@@ -231,7 +275,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
 
         jss.importStack("custom")
 
@@ -287,7 +331,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
 
         jss.importStack("child")
 
@@ -312,7 +356,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.importJavaScriptLibrary(library1)
         jss.importJavaScriptLibrary(library2)
@@ -336,7 +380,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", "chuck")
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", "charley")
@@ -362,7 +406,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", chuck)
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", buzz)
@@ -392,7 +436,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
 
         jss.addInitializerCall("setup", "chuck")
 
@@ -426,7 +470,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, falseHook)
 
         jss.addInitializerCall("setup", chuck)
 
@@ -450,7 +494,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
 
         jss.importStylesheet(stylesheet)
 
@@ -473,7 +517,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
 
         jss.importStylesheet(new StylesheetLink("style.css", options))
         jss.importStylesheet(new StylesheetLink("style.css", new StylesheetOptions("hologram")))

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/6512f5f3/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/components/Layout.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/components/Layout.tml b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/components/Layout.tml
index 59d5038..f2bc728 100644
--- a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/components/Layout.tml
+++ b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app5/components/Layout.tml
@@ -5,7 +5,7 @@
     <body>
         <div class="container">
 
-            <h1>Default Layout</h1>
+            <h1>Default Layout <t:devtool/></h1>
 
             <t:body/>
 
@@ -29,7 +29,6 @@
 
             <div class="row">
                 <t:actionlink t:id="reset" class="btn btn-large btn-warning">reset session</t:actionlink>
-
             </div>
 
         </div>