// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'dart:sky' as sky; import 'dart:typed_data'; const kMaxIterations = 100; int peakCount = 1000; // this is the number that must be reached for us to start reporting the peak number of nodes in the tree each frame void report(String s) { // uncomment this if you want to see what the mutations are // print("$s\n"); } sky.LayoutRoot layoutRoot = new sky.LayoutRoot(); sky.Document document = new sky.Document(); sky.Element root; math.Random random = new math.Random(); bool pickThis(double odds) { return random.nextDouble() < odds; } String generateCharacter(int min, int max) { String result = new String.fromCharCode(random.nextInt(max)+min); report("generated character: '$result'"); return result; } String colorToCSSString(sky.Color color) { return 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})'; } void mutate(sky.Canvas canvas) { // mutate the DOM randomly int iterationsLeft = kMaxIterations; sky.Node node = root; sky.Node other = null; while (node != null && iterationsLeft > 0) { iterationsLeft -= 1; if (node is sky.Element && pickThis(0.4)) { node = (node as sky.Element).firstChild; } else if (node.nextSibling != null && pickThis(0.5)) { node = node.nextSibling; } else if (other == null && node != root && pickThis(0.1)) { other = node; node = root; } else if (node != root && other != null && pickThis(0.1)) { report("insertBefore()"); try { node.insertBefore([other]); } catch (_) { } break; } else if (node != root && pickThis(0.001)) { report("remove()"); node.remove(); } else if (node is sky.Element) { if (pickThis(0.1)) { report("appending a new text node (ASCII)"); node.appendChild(document.createText(generateCharacter(0x20, 0x7F))); break; } else if (pickThis(0.05)) { report("appending a new text node (Latin1)"); node.appendChild(document.createText(generateCharacter(0x20, 0xFF))); break; } else if (pickThis(0.025)) { report("appending a new text node (BMP)"); node.appendChild(document.createText(generateCharacter(0x20, 0xFFFF))); break; } else if (pickThis(0.0125)) { report("appending a new text node (Unicode)"); node.appendChild(document.createText(generateCharacter(0x20, 0x10FFFF))); break; } else if (pickThis(0.1)) { report("appending a new Element"); node.appendChild(document.createElement('t')); break; } else if (pickThis(0.1)) { report("styling: color"); node.style['color'] = colorToCSSString(new sky.Color(random.nextInt(0xFFFFFFFF) | 0xC0808080)); break; } else if (pickThis(0.1)) { report("styling: font-size"); node.style['font-size'] = "${random.nextDouble() * 48.0}px"; break; } else if (pickThis(0.2)) { report("styling: font-weight"); node.style['font-weight'] = "${random.nextInt(8)+1}00"; break; } else if (pickThis(0.1)) { report("styling: line-height, dynamic"); node.style['line-height'] = "${random.nextDouble()*1.5}"; break; } else if (pickThis(0.001)) { report("styling: line-height, fixed"); node.style['line-height'] = "${random.nextDouble()*1.5}px"; break; } else if (pickThis(0.025)) { report("styling: font-style italic"); node.style['font-style'] = "italic"; break; } else if (pickThis(0.05)) { report("styling: font-style normal"); node.style['font-style'] = "normal"; break; } else if (pickThis(0.1)) { report("styling: text-decoration"); node.style['text-decoration-line'] = "none"; break; } else if (pickThis(0.01)) { report("styling: text-decoration"); node.style['text-decoration-line'] = "underline"; break; } else if (pickThis(0.01)) { report("styling: text-decoration"); node.style['text-decoration-line'] = "overline"; break; } else if (pickThis(0.01)) { report("styling: text-decoration"); node.style['text-decoration-line'] = "underline overline"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-style: inherit"); node.style['text-decoration-style'] = "inherit"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-style: solid"); node.style['text-decoration-style'] = "solid"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-style: double"); node.style['text-decoration-style'] = "double"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-style: dotted"); node.style['text-decoration-style'] = "dotted"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-style: dashed"); node.style['text-decoration-style'] = "dashed"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-style: wavy"); node.style['text-decoration-style'] = "wavy"; break; } else if (pickThis(0.1)) { report("styling: text-decoration-color"); node.style['text-decoration-color'] = colorToCSSString(new sky.Color(random.nextInt(0xFFFFFFFF))); break; } } else { assert(node is sky.Text); final sky.Text text = node; if (pickThis(0.1)) { report("appending a new text node (ASCII)"); text.appendData(generateCharacter(0x20, 0x7F)); break; } else if (pickThis(0.05)) { report("appending a new text node (Latin1)"); text.appendData(generateCharacter(0x20, 0xFF)); break; } else if (pickThis(0.025)) { report("appending a new text node (BMP)"); text.appendData(generateCharacter(0x20, 0xFFFF)); break; } else if (pickThis(0.0125)) { report("appending a new text node (Unicode)"); text.appendData(generateCharacter(0x20, 0x10FFFF)); break; } else if (text.length > 1 && pickThis(0.1)) { report("deleting character from Text node"); text.deleteData(random.nextInt(text.length), 1); break; } } } report("counting..."); node = root; int count = 1; while (node != null) { if (node is sky.Element && node.firstChild != null) { node = (node as sky.Element).firstChild; count += 1; } else { while (node != null && node.nextSibling == null) node = node.parentNode; if (node != null && node.nextSibling != null) { node = node.nextSibling; count += 1; } } } report("node count: $count\r"); if (count > peakCount) { peakCount = count; print("peak node count so far: $count\r"); } // draw the result report("recording..."); layoutRoot.maxWidth = sky.view.width; layoutRoot.layout(); layoutRoot.paint(canvas); report("painting..."); } sky.Picture paint(sky.Rect paintBounds) { sky.PictureRecorder recorder = new sky.PictureRecorder(); sky.Canvas canvas = new sky.Canvas(recorder, paintBounds); mutate(canvas); return recorder.endRecording(); } sky.Scene composite(sky.Picture picture, sky.Rect paintBounds) { final double devicePixelRatio = sky.view.devicePixelRatio; sky.Rect sceneBounds = new sky.Rect.fromLTWH(0.0, 0.0, sky.view.width * devicePixelRatio, sky.view.height * devicePixelRatio); Float32List deviceTransform = new Float32List(16) ..[0] = devicePixelRatio ..[5] = devicePixelRatio ..[10] = 1.0 ..[15] = 1.0; sky.SceneBuilder sceneBuilder = new sky.SceneBuilder(sceneBounds) ..pushTransform(deviceTransform) ..addPicture(sky.Offset.zero, picture, paintBounds) ..pop(); return sceneBuilder.build(); } void beginFrame(double timeStamp) { sky.Rect paintBounds = new sky.Rect.fromLTWH(0.0, 0.0, sky.view.width, sky.view.height); sky.Picture picture = paint(paintBounds); sky.Scene scene = composite(picture, paintBounds); sky.view.scene = scene; sky.view.scheduleFrame(); } void main() { root = document.createElement('p'); root.style['display'] = 'paragraph'; root.style['color'] = '#FFFFFF'; layoutRoot.rootElement = root; sky.view.setFrameCallback(beginFrame); sky.view.scheduleFrame(); }