Allow canvas to use display-list drawing for testing
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Jan 2016 01:53:18 +0000 (01:53 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Jan 2016 01:53:18 +0000 (01:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=153475

Reviewed by Dean Jackson.

Source/WebCore:

Optionally have 2D <canvas> use display-list drawing, which is only enabled
via Internals for now.

Support displayListAsText() and replayDisplayListAsText() on canvas, so we can
use it to test playback optimizations. [Note that displayListAsText() always
returns an empty string currently, because the display list is cleared when the
canvas is painted to the page.]

Display list rendering is implemented by giving CanvasRenderingContext2D an
optional DisplayListDrawingContext, which packages up a display list, recorder
and recording context. The existing paintRenderingResultsToCanvas() is overridden
to replay the recorded display list into the primary canvas context.

Tracked replay display lists are stored in a static map, keyed by the CanvasRenderingContext2D.

Test: displaylists/canvas-display-list.html

* html/HTMLCanvasElement.cpp:
(WebCore::HTMLCanvasElement::HTMLCanvasElement):
(WebCore::HTMLCanvasElement::getContext):
(WebCore::HTMLCanvasElement::paint):
(WebCore::HTMLCanvasElement::setUsesDisplayListDrawing):
(WebCore::HTMLCanvasElement::setTracksDisplayListReplay):
(WebCore::HTMLCanvasElement::displayListAsText):
(WebCore::HTMLCanvasElement::replayDisplayListAsText):
* html/HTMLCanvasElement.h:
* html/canvas/CanvasRenderingContext.h:
* html/canvas/CanvasRenderingContext2D.cpp:
(WebCore::DisplayListDrawingContext::DisplayListDrawingContext):
(WebCore::contextDisplayListMap):
(WebCore::CanvasRenderingContext2D::~CanvasRenderingContext2D):
(WebCore::CanvasRenderingContext2D::setTracksDisplayListReplay):
(WebCore::CanvasRenderingContext2D::displayListAsText):
(WebCore::CanvasRenderingContext2D::replayDisplayListAsText):
(WebCore::CanvasRenderingContext2D::paintRenderingResultsToCanvas):
(WebCore::CanvasRenderingContext2D::drawingContext):
(WebCore::CanvasRenderingContext2D::CanvasRenderingContext2D): Deleted.
* html/canvas/CanvasRenderingContext2D.h:
* testing/Internals.cpp:
(WebCore::Internals::setElementUsesDisplayListDrawing):
(WebCore::Internals::setElementTracksDisplayListReplay):
(WebCore::Internals::displayListForElement):
(WebCore::Internals::replayDisplayListForElement):

LayoutTests:

Simple canvas-based display list test.

* displaylists/canvas-display-list-expected.txt: Added.
* displaylists/canvas-display-list.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@195646 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/displaylists/canvas-display-list-expected.txt [new file with mode: 0644]
LayoutTests/displaylists/canvas-display-list.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLCanvasElement.cpp
Source/WebCore/html/HTMLCanvasElement.h
Source/WebCore/html/canvas/CanvasRenderingContext.h
Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
Source/WebCore/html/canvas/CanvasRenderingContext2D.h
Source/WebCore/testing/Internals.cpp

index bf72201..4798ce8 100644 (file)
@@ -1,3 +1,15 @@
+2016-01-26  Simon Fraser  <simon.fraser@apple.com>
+
+        Allow canvas to use display-list drawing for testing
+        https://bugs.webkit.org/show_bug.cgi?id=153475
+
+        Reviewed by Dean Jackson.
+
+        Simple canvas-based display list test.
+
+        * displaylists/canvas-display-list-expected.txt: Added.
+        * displaylists/canvas-display-list.html: Added.
+
 2016-01-26  Chris Dumez  <cdumez@apple.com>
 
         Setting HTMLInputElement.value to null to set its value to the empty string
diff --git a/LayoutTests/displaylists/canvas-display-list-expected.txt b/LayoutTests/displaylists/canvas-display-list-expected.txt
new file mode 100644 (file)
index 0000000..a883e68
--- /dev/null
@@ -0,0 +1,13 @@
+(set-state
+  (change-flags 256)
+  (fill-color #C80000))
+(fill-rect
+  (extent at (10,10) size 55x50)
+  (rect at (10,10) size 55x50))
+(set-state
+  (change-flags 256)
+  (fill-color #0000C87F))
+(fill-rect
+  (extent at (30,30) size 55x50)
+  (rect at (30,30) size 55x50))
diff --git a/LayoutTests/displaylists/canvas-display-list.html b/LayoutTests/displaylists/canvas-display-list.html
new file mode 100644 (file)
index 0000000..ba824f6
--- /dev/null
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <script>
+        function drawCanvas()
+        {
+            var ctx = document.getElementById('canvas').getContext('2d');
+            ctx.fillStyle = "rgb(200,0,0)";
+            ctx.fillRect (10, 10, 55, 50);
+
+            ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
+            ctx.fillRect (30, 30, 55, 50);
+        }
+
+        if (window.testRunner)
+            testRunner.dumpAsText();
+
+        var canvas;
+        function doTest()
+        {
+            document.body.offsetWidth;
+            canvas = document.getElementById('canvas');
+            if (window.internals) {
+                internals.setElementUsesDisplayListDrawing(canvas, true);
+                internals.setElementTracksDisplayListReplay(canvas, true);
+            }
+            
+            drawCanvas();
+
+            if (window.testRunner)
+                testRunner.display();
+
+            if (window.internals)
+                document.getElementById('output').textContent = internals.replayDisplayListForElement(canvas);
+        }
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+<canvas id="canvas" width="300" height="200"></canvas>
+<pre id="output"></pre>
+</body>
+</html>
index 9860bc9..489aae0 100644 (file)
@@ -1,3 +1,54 @@
+2016-01-26  Simon Fraser  <simon.fraser@apple.com>
+
+        Allow canvas to use display-list drawing for testing
+        https://bugs.webkit.org/show_bug.cgi?id=153475
+
+        Reviewed by Dean Jackson.
+
+        Optionally have 2D <canvas> use display-list drawing, which is only enabled
+        via Internals for now.
+
+        Support displayListAsText() and replayDisplayListAsText() on canvas, so we can
+        use it to test playback optimizations. [Note that displayListAsText() always
+        returns an empty string currently, because the display list is cleared when the
+        canvas is painted to the page.]
+
+        Display list rendering is implemented by giving CanvasRenderingContext2D an
+        optional DisplayListDrawingContext, which packages up a display list, recorder
+        and recording context. The existing paintRenderingResultsToCanvas() is overridden
+        to replay the recorded display list into the primary canvas context.
+
+        Tracked replay display lists are stored in a static map, keyed by the CanvasRenderingContext2D.
+
+        Test: displaylists/canvas-display-list.html
+
+        * html/HTMLCanvasElement.cpp:
+        (WebCore::HTMLCanvasElement::HTMLCanvasElement):
+        (WebCore::HTMLCanvasElement::getContext):
+        (WebCore::HTMLCanvasElement::paint):
+        (WebCore::HTMLCanvasElement::setUsesDisplayListDrawing):
+        (WebCore::HTMLCanvasElement::setTracksDisplayListReplay):
+        (WebCore::HTMLCanvasElement::displayListAsText):
+        (WebCore::HTMLCanvasElement::replayDisplayListAsText):
+        * html/HTMLCanvasElement.h:
+        * html/canvas/CanvasRenderingContext.h:
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::DisplayListDrawingContext::DisplayListDrawingContext):
+        (WebCore::contextDisplayListMap):
+        (WebCore::CanvasRenderingContext2D::~CanvasRenderingContext2D):
+        (WebCore::CanvasRenderingContext2D::setTracksDisplayListReplay):
+        (WebCore::CanvasRenderingContext2D::displayListAsText):
+        (WebCore::CanvasRenderingContext2D::replayDisplayListAsText):
+        (WebCore::CanvasRenderingContext2D::paintRenderingResultsToCanvas):
+        (WebCore::CanvasRenderingContext2D::drawingContext):
+        (WebCore::CanvasRenderingContext2D::CanvasRenderingContext2D): Deleted.
+        * html/canvas/CanvasRenderingContext2D.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::setElementUsesDisplayListDrawing):
+        (WebCore::Internals::setElementTracksDisplayListReplay):
+        (WebCore::Internals::displayListForElement):
+        (WebCore::Internals::replayDisplayListForElement):
+
 2016-01-26  Joseph Pecoraro  <pecoraro@apple.com>
 
         Generalize ResourceUsageData gathering to be used outside of ResourceUsageOverlay
index 934029c..f6f5165 100644 (file)
@@ -232,6 +232,10 @@ CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, Canvas
             }
 
             m_context = std::make_unique<CanvasRenderingContext2D>(this, document().inQuirksMode(), usesDashbardCompatibilityMode);
+
+            downcast<CanvasRenderingContext2D>(*m_context).setUsesDisplayListDrawing(m_usesDisplayListDrawing);
+            downcast<CanvasRenderingContext2D>(*m_context).setTracksDisplayListReplay(m_tracksDisplayListReplay);
+
 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
             // Need to make sure a RenderLayer and compositing layer get created for the Canvas
             setNeedsStyleRecalc(SyntheticStyleChange);
@@ -410,6 +414,7 @@ void HTMLCanvasElement::paint(GraphicsContext& context, const LayoutRect& r, boo
     if (m_context) {
         if (!paintsIntoCanvasBuffer() && !document().printing())
             return;
+
         m_context->paintRenderingResultsToCanvas();
     }
 
@@ -589,6 +594,44 @@ size_t HTMLCanvasElement::memoryCost() const
     return 4 * m_imageBuffer->internalSize().width() * m_imageBuffer->internalSize().height();
 }
 
+void HTMLCanvasElement::setUsesDisplayListDrawing(bool usesDisplayListDrawing)
+{
+    if (usesDisplayListDrawing == m_usesDisplayListDrawing)
+        return;
+    
+    m_usesDisplayListDrawing = usesDisplayListDrawing;
+
+    if (m_context && is<CanvasRenderingContext2D>(*m_context))
+        downcast<CanvasRenderingContext2D>(*m_context).setUsesDisplayListDrawing(m_usesDisplayListDrawing);
+}
+
+void HTMLCanvasElement::setTracksDisplayListReplay(bool tracksDisplayListReplay)
+{
+    if (tracksDisplayListReplay == m_tracksDisplayListReplay)
+        return;
+
+    m_tracksDisplayListReplay = tracksDisplayListReplay;
+
+    if (m_context && is<CanvasRenderingContext2D>(*m_context))
+        downcast<CanvasRenderingContext2D>(*m_context).setTracksDisplayListReplay(m_tracksDisplayListReplay);
+}
+
+String HTMLCanvasElement::displayListAsText(DisplayList::AsTextFlags flags) const
+{
+    if (m_context && is<CanvasRenderingContext2D>(*m_context))
+        return downcast<CanvasRenderingContext2D>(*m_context).displayListAsText(flags);
+
+    return String();
+}
+
+String HTMLCanvasElement::replayDisplayListAsText(DisplayList::AsTextFlags flags) const
+{
+    if (m_context && is<CanvasRenderingContext2D>(*m_context))
+        return downcast<CanvasRenderingContext2D>(*m_context).replayDisplayListAsText(flags);
+
+    return String();
+}
+
 void HTMLCanvasElement::createImageBuffer() const
 {
     ASSERT(!m_imageBuffer);
index c2566b8..02dbc57 100644 (file)
@@ -52,6 +52,10 @@ class ImageData;
 class ImageBuffer;
 class IntSize;
 
+namespace DisplayList {
+typedef unsigned AsTextFlags;
+}
+
 class CanvasObserver {
 public:
     virtual ~CanvasObserver() { }
@@ -135,6 +139,11 @@ public:
 
     bool shouldAccelerate(const IntSize&) const;
 
+    WEBCORE_EXPORT void setUsesDisplayListDrawing(bool);
+    WEBCORE_EXPORT void setTracksDisplayListReplay(bool);
+    WEBCORE_EXPORT String displayListAsText(DisplayList::AsTextFlags) const;
+    WEBCORE_EXPORT String replayDisplayListAsText(DisplayList::AsTextFlags) const;
+
     size_t memoryCost() const;
 
 private:
@@ -171,6 +180,9 @@ private:
     bool m_rendererIsCanvas { false };
     bool m_ignoreReset { false };
 
+    bool m_usesDisplayListDrawing { false };
+    bool m_tracksDisplayListReplay { false };
+
     // m_createdImageBuffer means we tried to malloc the buffer.  We didn't necessarily get it.
     mutable bool m_hasCreatedImageBuffer { false };
     mutable bool m_didClearImageBuffer { false };
index 51a21fe..362a892 100644 (file)
@@ -81,4 +81,9 @@ private:
 
 } // namespace WebCore
 
+#define SPECIALIZE_TYPE_TRAITS_CANVASRENDERINGCONTEXT(ToValueTypeName, predicate) \
+SPECIALIZE_TYPE_TRAITS_BEGIN(ToValueTypeName) \
+    static bool isType(const WebCore::CanvasRenderingContext& context) { return context.predicate; } \
+SPECIALIZE_TYPE_TRAITS_END()
+
 #endif
index 972119a..8b76ddf 100644 (file)
@@ -40,6 +40,8 @@
 #include "CanvasGradient.h"
 #include "CanvasPattern.h"
 #include "DOMPath.h"
+#include "DisplayListRecorder.h"
+#include "DisplayListReplayer.h"
 #include "ExceptionCodePlaceholder.h"
 #include "FloatQuad.h"
 #include "HTMLImageElement.h"
@@ -55,6 +57,7 @@
 #include "StyleResolver.h"
 #include "TextMetrics.h"
 #include "TextRun.h"
+#include "TextStream.h"
 
 #include <wtf/CheckedArithmetic.h>
 #include <wtf/MathExtras.h>
@@ -85,6 +88,25 @@ static const int defaultFontSize = 10;
 static const char* const defaultFontFamily = "sans-serif";
 static const char* const defaultFont = "10px sans-serif";
 
+struct DisplayListDrawingContext {
+    GraphicsContext context;
+    DisplayList::Recorder recorder;
+    DisplayList::DisplayList displayList;
+    
+    DisplayListDrawingContext(const FloatRect& clip)
+        : recorder(context, displayList, clip, AffineTransform())
+    {
+    }
+};
+
+typedef HashMap<const CanvasRenderingContext2D*, std::unique_ptr<DisplayList::DisplayList>> ContextDisplayListHashMap;
+
+static ContextDisplayListHashMap& contextDisplayListMap()
+{
+    static NeverDestroyed<ContextDisplayListHashMap> sharedHashMap;
+    return sharedHashMap;
+}
+
 class CanvasStrokeStyleApplier : public StrokeStyleApplier {
 public:
     CanvasStrokeStyleApplier(CanvasRenderingContext2D* canvasContext)
@@ -112,7 +134,6 @@ private:
 CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, bool usesCSSCompatibilityParseMode, bool usesDashboardCompatibilityMode)
     : CanvasRenderingContext(canvas)
     , m_stateStack(1)
-    , m_unrealizedSaveCount(0)
     , m_usesCSSCompatibilityParseMode(usesCSSCompatibilityParseMode)
 #if ENABLE(DASHBOARD_SUPPORT)
     , m_usesDashboardCompatibilityMode(usesDashboardCompatibilityMode)
@@ -141,6 +162,9 @@ CanvasRenderingContext2D::~CanvasRenderingContext2D()
 #if !ASSERT_DISABLED
     unwindStateStack();
 #endif
+
+    if (UNLIKELY(tracksDisplayListReplay()))
+        contextDisplayListMap().remove(this);
 }
 
 bool CanvasRenderingContext2D::isAccelerated() const
@@ -162,6 +186,8 @@ void CanvasRenderingContext2D::reset()
     m_stateStack.first() = State();
     m_path.clear();
     m_unrealizedSaveCount = 0;
+    
+    m_recordingContext = nullptr;
 }
 
 CanvasRenderingContext2D::State::State()
@@ -1907,8 +1933,64 @@ void CanvasRenderingContext2D::didDraw(const FloatRect& r, unsigned options)
     canvas()->didDraw(dirtyRect);
 }
 
+void CanvasRenderingContext2D::setTracksDisplayListReplay(bool tracksDisplayListReplay)
+{
+    if (tracksDisplayListReplay == m_tracksDisplayListReplay)
+        return;
+
+    m_tracksDisplayListReplay = tracksDisplayListReplay;
+    if (!m_tracksDisplayListReplay)
+        contextDisplayListMap().remove(this);
+}
+
+String CanvasRenderingContext2D::displayListAsText(DisplayList::AsTextFlags flags) const
+{
+    if (m_recordingContext)
+        return m_recordingContext->displayList.asText(flags);
+    
+    return String();
+}
+
+String CanvasRenderingContext2D::replayDisplayListAsText(DisplayList::AsTextFlags flags) const
+{
+    auto it = contextDisplayListMap().find(this);
+    if (it != contextDisplayListMap().end()) {
+        TextStream stream;
+        stream << it->value->asText(flags);
+        return stream.release();
+    }
+
+    return String();
+}
+
+void CanvasRenderingContext2D::paintRenderingResultsToCanvas()
+{
+    if (UNLIKELY(m_usesDisplayListDrawing)) {
+        if (!m_recordingContext)
+            return;
+        
+        FloatRect clip(FloatPoint::zero(), canvas()->size());
+        DisplayList::Replayer replayer(*canvas()->drawingContext(), m_recordingContext->displayList);
+
+        if (UNLIKELY(m_tracksDisplayListReplay)) {
+            auto replayList = replayer.replay(clip, m_tracksDisplayListReplay);
+            contextDisplayListMap().add(this, WTFMove(replayList));
+        } else
+            replayer.replay(clip);
+
+        m_recordingContext->displayList.clear();
+    }
+}
+
 GraphicsContext* CanvasRenderingContext2D::drawingContext() const
 {
+    if (UNLIKELY(m_usesDisplayListDrawing)) {
+        if (!m_recordingContext)
+            m_recordingContext = std::make_unique<DisplayListDrawingContext>(FloatRect(FloatPoint::zero(), canvas()->size()));
+
+        return &m_recordingContext->context;
+    }
+
     return canvas()->drawingContext();
 }
 
index d6999bf..a334873 100644 (file)
@@ -237,6 +237,15 @@ public:
         High
     };
 
+    bool usesDisplayListDrawing() const { return m_usesDisplayListDrawing; };
+    void setUsesDisplayListDrawing(bool flag) { m_usesDisplayListDrawing = flag; };
+
+    bool tracksDisplayListReplay() const { return m_tracksDisplayListReplay; }
+    void setTracksDisplayListReplay(bool);
+
+    String displayListAsText(DisplayList::AsTextFlags) const;
+    String replayDisplayListAsText(DisplayList::AsTextFlags) const;
+
 private:
     enum class Direction {
         Inherit,
@@ -320,6 +329,8 @@ private:
     void didDraw(const FloatRect&, unsigned options = CanvasDidDrawApplyAll);
     void didDrawEntireCanvas();
 
+    void paintRenderingResultsToCanvas() override;
+
     GraphicsContext* drawingContext() const;
 
     void unwindStateStack();
@@ -384,13 +395,19 @@ private:
 #endif
 
     Vector<State, 1> m_stateStack;
-    unsigned m_unrealizedSaveCount;
+    unsigned m_unrealizedSaveCount { 0 };
     bool m_usesCSSCompatibilityParseMode;
 #if ENABLE(DASHBOARD_SUPPORT)
     bool m_usesDashboardCompatibilityMode;
 #endif
+
+    bool m_usesDisplayListDrawing { false };
+    bool m_tracksDisplayListReplay { false };
+    mutable std::unique_ptr<struct DisplayListDrawingContext> m_recordingContext;
 };
 
 } // namespace WebCore
 
+SPECIALIZE_TYPE_TRAITS_CANVASRENDERINGCONTEXT(WebCore::CanvasRenderingContext2D, is2d())
+
 #endif
index a3ac67d..e3dff4f 100644 (file)
@@ -57,6 +57,7 @@
 #include "FormController.h"
 #include "FrameLoader.h"
 #include "FrameView.h"
+#include "HTMLCanvasElement.h"
 #include "HTMLIFrameElement.h"
 #include "HTMLImageElement.h"
 #include "HTMLInputElement.h"
@@ -1982,11 +1983,21 @@ void Internals::setElementUsesDisplayListDrawing(Element* element, bool usesDisp
         return;
     }
 
-    if (!element || !element->renderer() || !element->renderer()->hasLayer()) {
+    if (!element || !element->renderer()) {
         ec = INVALID_ACCESS_ERR;
         return;
     }
 
+    if (is<HTMLCanvasElement>(*element)) {
+        downcast<HTMLCanvasElement>(*element).setUsesDisplayListDrawing(usesDisplayListDrawing);
+        return;
+    }
+
+    if (!element->renderer()->hasLayer()) {
+        ec = INVALID_ACCESS_ERR;
+        return;
+    }
+    
     RenderLayer* layer = downcast<RenderLayerModelObject>(element->renderer())->layer();
     if (!layer->isComposited()) {
         ec = INVALID_ACCESS_ERR;
@@ -2004,7 +2015,17 @@ void Internals::setElementTracksDisplayListReplay(Element* element, bool isTrack
         return;
     }
 
-    if (!element || !element->renderer() || !element->renderer()->hasLayer()) {
+    if (!element || !element->renderer()) {
+        ec = INVALID_ACCESS_ERR;
+        return;
+    }
+
+    if (is<HTMLCanvasElement>(*element)) {
+        downcast<HTMLCanvasElement>(*element).setTracksDisplayListReplay(isTrackingReplay);
+        return;
+    }
+
+    if (!element->renderer()->hasLayer()) {
         ec = INVALID_ACCESS_ERR;
         return;
     }
@@ -2031,21 +2052,29 @@ String Internals::displayListForElement(Element* element, unsigned flags, Except
         return String();
     }
 
-    if (!element || !element->renderer() || !element->renderer()->hasLayer()) {
+    if (!element || !element->renderer()) {
         ec = INVALID_ACCESS_ERR;
         return String();
     }
-    
+
+    DisplayList::AsTextFlags displayListFlags = 0;
+    if (flags & DISPLAY_LIST_INCLUDES_PLATFORM_OPERATIONS)
+        displayListFlags |= DisplayList::AsTextFlag::IncludesPlatformOperations;
+
+    if (is<HTMLCanvasElement>(*element))
+        return downcast<HTMLCanvasElement>(*element).displayListAsText(displayListFlags);
+
+    if (!element->renderer()->hasLayer()) {
+        ec = INVALID_ACCESS_ERR;
+        return String();
+    }
+
     RenderLayer* layer = downcast<RenderLayerModelObject>(element->renderer())->layer();
     if (!layer->isComposited()) {
         ec = INVALID_ACCESS_ERR;
         return String();
     }
 
-    DisplayList::AsTextFlags displayListFlags = 0;
-    if (flags & DISPLAY_LIST_INCLUDES_PLATFORM_OPERATIONS)
-        displayListFlags |= DisplayList::AsTextFlag::IncludesPlatformOperations;
-    
     return layer->backing()->displayListAsText(displayListFlags);
 }
 
@@ -2062,21 +2091,29 @@ String Internals::replayDisplayListForElement(Element* element, unsigned flags,
         return String();
     }
 
-    if (!element || !element->renderer() || !element->renderer()->hasLayer()) {
+    if (!element || !element->renderer()) {
         ec = INVALID_ACCESS_ERR;
         return String();
     }
-    
+
+    DisplayList::AsTextFlags displayListFlags = 0;
+    if (flags & DISPLAY_LIST_INCLUDES_PLATFORM_OPERATIONS)
+        displayListFlags |= DisplayList::AsTextFlag::IncludesPlatformOperations;
+
+    if (is<HTMLCanvasElement>(*element))
+        return downcast<HTMLCanvasElement>(*element).replayDisplayListAsText(displayListFlags);
+
+    if (!element->renderer()->hasLayer()) {
+        ec = INVALID_ACCESS_ERR;
+        return String();
+    }
+
     RenderLayer* layer = downcast<RenderLayerModelObject>(element->renderer())->layer();
     if (!layer->isComposited()) {
         ec = INVALID_ACCESS_ERR;
         return String();
     }
 
-    DisplayList::AsTextFlags displayListFlags = 0;
-    if (flags & DISPLAY_LIST_INCLUDES_PLATFORM_OPERATIONS)
-        displayListFlags |= DisplayList::AsTextFlag::IncludesPlatformOperations;
-    
     return layer->backing()->replayDisplayListAsText(displayListFlags);
 }