1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/shadereditor/test/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,308 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 +"use strict"; 1.7 + 1.8 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; 1.9 + 1.10 +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); 1.11 + 1.12 +let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); 1.13 +// To enable logging for try runs, just set the pref to true. 1.14 +Services.prefs.setBoolPref("devtools.debugger.log", false); 1.15 + 1.16 +let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); 1.17 +let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.18 +let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); 1.19 +let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); 1.20 +let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); 1.21 +let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {}); 1.22 + 1.23 +let { WebGLFront } = devtools.require("devtools/server/actors/webgl"); 1.24 +let TiltGL = devtools.require("devtools/tilt/tilt-gl"); 1.25 +let TargetFactory = devtools.TargetFactory; 1.26 +let Toolbox = devtools.Toolbox; 1.27 + 1.28 +const EXAMPLE_URL = "http://example.com/browser/browser/devtools/shadereditor/test/"; 1.29 +const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html"; 1.30 +const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html"; 1.31 +const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html"; 1.32 +const OVERLAPPING_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_overlapping-geometry.html"; 1.33 +const BLENDED_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_blended-geometry.html"; 1.34 + 1.35 +// All tests are asynchronous. 1.36 +waitForExplicitFinish(); 1.37 + 1.38 +let gToolEnabled = Services.prefs.getBoolPref("devtools.shadereditor.enabled"); 1.39 + 1.40 +registerCleanupFunction(() => { 1.41 + info("finish() was called, cleaning up..."); 1.42 + Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); 1.43 + Services.prefs.setBoolPref("devtools.shadereditor.enabled", gToolEnabled); 1.44 + 1.45 + // These tests use a lot of memory due to GL contexts, so force a GC to help 1.46 + // fragmentation. 1.47 + info("Forcing GC after shadereditor test."); 1.48 + Cu.forceGC(); 1.49 +}); 1.50 + 1.51 +function addTab(aUrl, aWindow) { 1.52 + info("Adding tab: " + aUrl); 1.53 + 1.54 + let deferred = promise.defer(); 1.55 + let targetWindow = aWindow || window; 1.56 + let targetBrowser = targetWindow.gBrowser; 1.57 + 1.58 + targetWindow.focus(); 1.59 + let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); 1.60 + let linkedBrowser = tab.linkedBrowser; 1.61 + 1.62 + linkedBrowser.addEventListener("load", function onLoad() { 1.63 + linkedBrowser.removeEventListener("load", onLoad, true); 1.64 + info("Tab added and finished loading: " + aUrl); 1.65 + deferred.resolve(tab); 1.66 + }, true); 1.67 + 1.68 + return deferred.promise; 1.69 +} 1.70 + 1.71 +function removeTab(aTab, aWindow) { 1.72 + info("Removing tab."); 1.73 + 1.74 + let deferred = promise.defer(); 1.75 + let targetWindow = aWindow || window; 1.76 + let targetBrowser = targetWindow.gBrowser; 1.77 + let tabContainer = targetBrowser.tabContainer; 1.78 + 1.79 + tabContainer.addEventListener("TabClose", function onClose(aEvent) { 1.80 + tabContainer.removeEventListener("TabClose", onClose, false); 1.81 + info("Tab removed and finished closing."); 1.82 + deferred.resolve(); 1.83 + }, false); 1.84 + 1.85 + targetBrowser.removeTab(aTab); 1.86 + return deferred.promise; 1.87 +} 1.88 + 1.89 +function handleError(aError) { 1.90 + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); 1.91 + finish(); 1.92 +} 1.93 + 1.94 +function ifWebGLSupported() { 1.95 + ok(false, "You need to define a 'ifWebGLSupported' function."); 1.96 + finish(); 1.97 +} 1.98 + 1.99 +function ifWebGLUnsupported() { 1.100 + todo(false, "Skipping test because WebGL isn't supported."); 1.101 + finish(); 1.102 +} 1.103 + 1.104 +function test() { 1.105 + let generator = isWebGLSupported() ? ifWebGLSupported : ifWebGLUnsupported; 1.106 + Task.spawn(generator).then(null, handleError); 1.107 +} 1.108 + 1.109 +function createCanvas() { 1.110 + return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 1.111 +} 1.112 + 1.113 +function isWebGLSupported() { 1.114 + let supported = 1.115 + !TiltGL.isWebGLForceEnabled() && 1.116 + TiltGL.isWebGLSupported() && 1.117 + TiltGL.create3DContext(createCanvas()); 1.118 + 1.119 + info("Apparently, WebGL is" + (supported ? "" : " not") + " supported."); 1.120 + return supported; 1.121 +} 1.122 + 1.123 +function once(aTarget, aEventName, aUseCapture = false) { 1.124 + info("Waiting for event: '" + aEventName + "' on " + aTarget + "."); 1.125 + 1.126 + let deferred = promise.defer(); 1.127 + 1.128 + for (let [add, remove] of [ 1.129 + ["on", "off"], // Use event emitter before DOM events for consistency 1.130 + ["addEventListener", "removeEventListener"], 1.131 + ["addListener", "removeListener"] 1.132 + ]) { 1.133 + if ((add in aTarget) && (remove in aTarget)) { 1.134 + aTarget[add](aEventName, function onEvent(...aArgs) { 1.135 + aTarget[remove](aEventName, onEvent, aUseCapture); 1.136 + deferred.resolve(...aArgs); 1.137 + }, aUseCapture); 1.138 + break; 1.139 + } 1.140 + } 1.141 + 1.142 + return deferred.promise; 1.143 +} 1.144 + 1.145 +// Hack around `once`, as that only resolves to a single (first) argument 1.146 +// and discards the rest. `onceSpread` is similar, except resolves to an 1.147 +// array of all of the arguments in the handler. These should be consolidated 1.148 +// into the same function, but many tests will need to be changed. 1.149 +function onceSpread(aTarget, aEvent) { 1.150 + let deferred = promise.defer(); 1.151 + aTarget.once(aEvent, (...args) => deferred.resolve(args)); 1.152 + return deferred.promise; 1.153 +} 1.154 + 1.155 +function observe(aNotificationName, aOwnsWeak = false) { 1.156 + info("Waiting for observer notification: '" + aNotificationName + "."); 1.157 + 1.158 + let deferred = promise.defer(); 1.159 + 1.160 + Services.obs.addObserver(function onNotification(...aArgs) { 1.161 + Services.obs.removeObserver(onNotification, aNotificationName); 1.162 + deferred.resolve.apply(deferred, aArgs); 1.163 + }, aNotificationName, aOwnsWeak); 1.164 + 1.165 + return deferred.promise; 1.166 +} 1.167 + 1.168 +function waitForFrame(aDebuggee) { 1.169 + let deferred = promise.defer(); 1.170 + aDebuggee.requestAnimationFrame(deferred.resolve); 1.171 + return deferred.promise; 1.172 +} 1.173 + 1.174 +function isApprox(aFirst, aSecond, aMargin = 1) { 1.175 + return Math.abs(aFirst - aSecond) <= aMargin; 1.176 +} 1.177 + 1.178 +function isApproxColor(aFirst, aSecond, aMargin) { 1.179 + return isApprox(aFirst.r, aSecond.r, aMargin) && 1.180 + isApprox(aFirst.g, aSecond.g, aMargin) && 1.181 + isApprox(aFirst.b, aSecond.b, aMargin) && 1.182 + isApprox(aFirst.a, aSecond.a, aMargin); 1.183 +} 1.184 + 1.185 +function getPixels(aDebuggee, aSelector = "canvas") { 1.186 + let canvas = aDebuggee.document.querySelector(aSelector); 1.187 + let gl = canvas.getContext("webgl"); 1.188 + 1.189 + let { width, height } = canvas; 1.190 + let buffer = new aDebuggee.Uint8Array(width * height * 4); 1.191 + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer); 1.192 + 1.193 + info("Retrieved pixels: " + width + "x" + height); 1.194 + return [buffer, width, height]; 1.195 +} 1.196 + 1.197 +function getPixel(aDebuggee, aPosition, aSelector = "canvas") { 1.198 + let canvas = aDebuggee.document.querySelector(aSelector); 1.199 + let gl = canvas.getContext("webgl"); 1.200 + 1.201 + let { width, height } = canvas; 1.202 + let { x, y } = aPosition; 1.203 + let buffer = new aDebuggee.Uint8Array(4); 1.204 + gl.readPixels(x, height - y - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buffer); 1.205 + 1.206 + let pixel = { r: buffer[0], g: buffer[1], b: buffer[2], a: buffer[3] }; 1.207 + 1.208 + info("Retrieved pixel: " + pixel.toSource() + " at " + aPosition.toSource()); 1.209 + return pixel; 1.210 +} 1.211 + 1.212 +function ensurePixelIs(aDebuggee, aPosition, aColor, aWaitFlag = false, aSelector = "canvas") { 1.213 + let pixel = getPixel(aDebuggee, aPosition, aSelector); 1.214 + if (isApproxColor(pixel, aColor)) { 1.215 + ok(true, "Expected pixel is shown at: " + aPosition.toSource()); 1.216 + return promise.resolve(null); 1.217 + } 1.218 + if (aWaitFlag) { 1.219 + return Task.spawn(function() { 1.220 + yield waitForFrame(aDebuggee); 1.221 + yield ensurePixelIs(aDebuggee, aPosition, aColor, aWaitFlag, aSelector); 1.222 + }); 1.223 + } 1.224 + ok(false, "Expected pixel was not already shown at: " + aPosition.toSource()); 1.225 + return promise.reject(null); 1.226 +} 1.227 + 1.228 +function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") { 1.229 + executeSoon(() => content.history[aDirection]()); 1.230 + return once(aTarget, aWaitForTargetEvent); 1.231 +} 1.232 + 1.233 +function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") { 1.234 + executeSoon(() => aTarget.activeTab.navigateTo(aUrl)); 1.235 + return once(aTarget, aWaitForTargetEvent); 1.236 +} 1.237 + 1.238 +function reload(aTarget, aWaitForTargetEvent = "navigate") { 1.239 + executeSoon(() => aTarget.activeTab.reload()); 1.240 + return once(aTarget, aWaitForTargetEvent); 1.241 +} 1.242 + 1.243 +function initBackend(aUrl) { 1.244 + info("Initializing a shader editor front."); 1.245 + 1.246 + if (!DebuggerServer.initialized) { 1.247 + DebuggerServer.init(() => true); 1.248 + DebuggerServer.addBrowserActors(); 1.249 + } 1.250 + 1.251 + return Task.spawn(function*() { 1.252 + let tab = yield addTab(aUrl); 1.253 + let target = TargetFactory.forTab(tab); 1.254 + let debuggee = target.window.wrappedJSObject; 1.255 + 1.256 + yield target.makeRemote(); 1.257 + 1.258 + let front = new WebGLFront(target.client, target.form); 1.259 + return [target, debuggee, front]; 1.260 + }); 1.261 +} 1.262 + 1.263 +function initShaderEditor(aUrl) { 1.264 + info("Initializing a shader editor pane."); 1.265 + 1.266 + return Task.spawn(function*() { 1.267 + let tab = yield addTab(aUrl); 1.268 + let target = TargetFactory.forTab(tab); 1.269 + let debuggee = target.window.wrappedJSObject; 1.270 + 1.271 + yield target.makeRemote(); 1.272 + 1.273 + Services.prefs.setBoolPref("devtools.shadereditor.enabled", true); 1.274 + let toolbox = yield gDevTools.showToolbox(target, "shadereditor"); 1.275 + let panel = toolbox.getCurrentPanel(); 1.276 + return [target, debuggee, panel]; 1.277 + }); 1.278 +} 1.279 + 1.280 +function teardown(aPanel) { 1.281 + info("Destroying the specified shader editor."); 1.282 + 1.283 + return promise.all([ 1.284 + once(aPanel, "destroyed"), 1.285 + removeTab(aPanel.target.tab) 1.286 + ]); 1.287 +} 1.288 + 1.289 +// Due to `program-linked` events firing synchronously, we cannot 1.290 +// just yield/chain them together, as then we miss all actors after the 1.291 +// first event since they're fired consecutively. This allows us to capture 1.292 +// all actors and returns an array containing them. 1.293 +// 1.294 +// Takes a `front` object that is an event emitter, the number of 1.295 +// programs that should be listened to and waited on, and an optional 1.296 +// `onAdd` function that calls with the entire actors array on program link 1.297 +function getPrograms(front, count, onAdd) { 1.298 + let actors = []; 1.299 + let deferred = promise.defer(); 1.300 + front.on("program-linked", function onLink (actor) { 1.301 + if (actors.length !== count) { 1.302 + actors.push(actor); 1.303 + if (typeof onAdd === 'function') onAdd(actors) 1.304 + } 1.305 + if (actors.length === count) { 1.306 + front.off("program-linked", onLink); 1.307 + deferred.resolve(actors); 1.308 + } 1.309 + }); 1.310 + return deferred.promise; 1.311 +}