This commit is contained in:
Wes Kocher 2014-07-22 18:05:18 -07:00
Родитель a96de11629 eb39fe3503
Коммит 7c2f1b4d73
18 изменённых файлов: 438 добавлений и 90 удалений

Просмотреть файл

@ -207,8 +207,8 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, {
let thumbnail = document.createElementNS(HTML_NS, "canvas");
thumbnail.className = "snapshot-item-thumbnail";
thumbnail.width = CanvasFront.THUMBNAIL_HEIGHT;
thumbnail.height = CanvasFront.THUMBNAIL_HEIGHT;
thumbnail.width = CanvasFront.THUMBNAIL_SIZE;
thumbnail.height = CanvasFront.THUMBNAIL_SIZE;
let title = document.createElement("label");
title.className = "plain snapshot-item-title";
@ -712,14 +712,16 @@ let CallsListView = Heritage.extend(WidgetMethods, {
* A single "snapshot-image" instance received from the backend.
*/
showScreenshot: function(screenshot) {
let { index, width, height, flipped, pixels } = screenshot;
let { index, width, height, scaling, flipped, pixels } = screenshot;
let screenshotNode = $("#screenshot-image");
screenshotNode.setAttribute("flipped", flipped);
drawBackground("screenshot-rendering", width, height, pixels);
let dimensionsNode = $("#screenshot-dimensions");
dimensionsNode.setAttribute("value", ~~width + " x " + ~~height);
let actualWidth = (width / scaling) | 0;
let actualHeight = (height / scaling) | 0;
dimensionsNode.setAttribute("value", actualWidth + " x " + actualHeight);
window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED);
},
@ -754,8 +756,8 @@ let CallsListView = Heritage.extend(WidgetMethods, {
let thumbnailNode = document.createElementNS(HTML_NS, "canvas");
thumbnailNode.setAttribute("flipped", flipped);
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_HEIGHT, width);
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_HEIGHT, height);
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width);
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height);
drawImage(thumbnailNode, width, height, pixels, { centered: true });
thumbnailNode.className = "filmstrip-thumbnail";

Просмотреть файл

@ -5,6 +5,7 @@ support-files =
doc_simple-canvas-bitmasks.html
doc_simple-canvas-deep-stack.html
doc_simple-canvas-transparent.html
doc_webgl-bindings.html
doc_webgl-enum.html
head.js
@ -17,6 +18,7 @@ support-files =
[browser_canvas-actor-test-07.js]
[browser_canvas-actor-test-08.js]
[browser_canvas-actor-test-09.js]
[browser_canvas-actor-test-10.js]
[browser_canvas-frontend-call-highlight.js]
[browser_canvas-frontend-call-list.js]
[browser_canvas-frontend-call-search.js]

Просмотреть файл

@ -18,9 +18,7 @@ function ifTestingSupported() {
ok(true, "Target automatically navigated when the front was set up.");
let snapshotActor = yield front.recordAnimationFrame();
let animationOverview = yield snapshotActor.getOverview();
let functionCalls = animationOverview.calls;
is(functionCalls[0].name, "clearRect",

Просмотреть файл

@ -26,6 +26,11 @@ function ifTestingSupported() {
is(functionCalls[0].argsPreview, "DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT",
"The bits passed into `gl.clear` have been cast to their enum values.");
is(functionCalls[1].name, "bindTexture",
"The function's name is correct.");
is(functionCalls[1].argsPreview, "TEXTURE_2D, null",
"The bits passed into `gl.bindTexture` have been cast to their enum values.");
yield removeTab(target.tab);
finish();
}

Просмотреть файл

@ -0,0 +1,94 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the correct framebuffer, renderbuffer and textures are re-bound
* after generating screenshots using the actor.
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_BINDINGS_URL);
let navigated = once(target, "navigate");
yield front.setup({ reload: true });
ok(true, "The front was setup up successfully.");
yield navigated;
ok(true, "Target automatically navigated when the front was set up.");
let snapshotActor = yield front.recordAnimationFrame();
let animationOverview = yield snapshotActor.getOverview();
let functionCalls = animationOverview.calls;
let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
is(firstScreenshot.index, -1,
"The first screenshot didn't encounter any draw call.");
is(firstScreenshot.scaling, 0.25,
"The first screenshot has the correct scaling.");
is(firstScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
"The first screenshot has the correct width.");
is(firstScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
"The first screenshot has the correct height.");
is(firstScreenshot.flipped, true,
"The first screenshot has the correct 'flipped' flag.");
is(firstScreenshot.pixels.length, 0,
"The first screenshot should be empty.");
let gl = debuggee.gl;
is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer,
"The debuggee's gl context framebuffer wasn't changed.");
is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer,
"The debuggee's gl context renderbuffer wasn't changed.");
is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture,
"The debuggee's gl context texture binding wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[0], 128,
"The debuggee's gl context viewport's left coord. wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[1], 256,
"The debuggee's gl context viewport's left coord. wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[2], 384,
"The debuggee's gl context viewport's left coord. wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[3], 512,
"The debuggee's gl context viewport's left coord. wasn't changed.");
let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
is(secondScreenshot.index, 1,
"The second screenshot has the correct index.");
is(secondScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
"The second screenshot has the correct width.");
is(secondScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
"The second screenshot has the correct height.");
is(secondScreenshot.scaling, 0.25,
"The second screenshot has the correct scaling.");
is(secondScreenshot.flipped, true,
"The second screenshot has the correct 'flipped' flag.");
is(secondScreenshot.pixels.length, Math.pow(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, 2),
"The second screenshot should not be empty.");
is(new Uint8Array(secondScreenshot.pixels.buffer)[0], 0,
"The second screenshot has the correct red component.");
is(new Uint8Array(secondScreenshot.pixels.buffer)[1], 0,
"The second screenshot has the correct green component.");
is(new Uint8Array(secondScreenshot.pixels.buffer)[2], 255,
"The second screenshot has the correct blue component.");
is(new Uint8Array(secondScreenshot.pixels.buffer)[3], 255,
"The second screenshot has the correct alpha component.");
let gl = debuggee.gl;
is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer,
"The debuggee's gl context framebuffer still wasn't changed.");
is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer,
"The debuggee's gl context renderbuffer still wasn't changed.");
is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture,
"The debuggee's gl context texture binding still wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[0], 128,
"The debuggee's gl context viewport's left coord. still wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[1], 256,
"The debuggee's gl context viewport's left coord. still wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[2], 384,
"The debuggee's gl context viewport's left coord. still wasn't changed.");
is(gl.getParameter(gl.VIEWPORT)[3], 512,
"The debuggee's gl context viewport's left coord. still wasn't changed.");
yield removeTab(target.tab);
finish();
}

Просмотреть файл

@ -0,0 +1,61 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>WebGL editor test page</title>
</head>
<body>
<canvas id="canvas" width="1024" height="1024"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
let canvas, gl;
let customFramebuffer;
let customRenderbuffer;
let customTexture;
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
gl.clearColor(1.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
customFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, customFramebuffer);
customRenderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, customRenderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1024, 1024);
customTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, customTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, customTexture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, customRenderbuffer);
gl.viewport(128, 256, 384, 512);
gl.clearColor(0.0, 1.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawScene();
}
function drawScene() {
gl.clearColor(0.0, 0.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
window.requestAnimationFrame(drawScene);
}
</script>
</body>
</html>

Просмотреть файл

@ -25,6 +25,7 @@
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, null);
window.requestAnimationFrame(drawScene);
}
</script>

Просмотреть файл

@ -30,6 +30,7 @@ const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html";
const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html";
const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html";
// All tests are asynchronous.
waitForExplicitFinish();

Просмотреть файл

@ -125,27 +125,29 @@ interface nsIPrincipal;
*
* Parent process Child processes
* ---------------- -----------------
* global PPMM
* global (GPPMM)
* |
* +<----> child PPMM
* +-->parent in-process PIPMM<-->child in-process CIPPMM
* |
* +-->parent PMM1<------------------>child process CMM1
* +-->parent (PPMM1)<------------------>child (CPMM1)
* |
* +-->parent PMM2<------------------>child process PMM2
* +-->parent (PPMM2)<------------------>child (CPMM2)
* ...
*
* For example: the parent-process PMM1 sends messages directly to
* only the child-process CMM1.
* Note, PIPMM and CIPPMM both run in the parent process.
*
* For example: CMM1 sends messages directly to PMM1. The global PPMM
* For example: the parent-process PPMM1 sends messages to the
* child-process CPMM1.
*
* For example: CPMM1 sends messages directly to PPMM1. The global GPPMM
* will also notify their message listeners when the message arrives.
*
* For example: messages sent through the global PPMM will be
* dispatched to the listeners of the same-process, "child PPMM".
* They will also be broadcast to PPM1, PPM2, etc.
* For example: messages sent through the global GPPMM will be
* dispatched to the listeners of the same-process, CIPPMM, CPMM1,
* CPMM2, etc.
*
* ***** PERFORMANCE AND SECURITY WARNING *****
* Messages broadcast through the global PPMM can result in messages
* Messages broadcast through the GPPMM can result in messages
* being dispatched across many OS processes, and to many processes
* with different permissions. Great care should be taken when
* broadcasting.

Просмотреть файл

@ -22,6 +22,7 @@
#include "mozilla/Telemetry.h"
#include "mozilla/unused.h"
#include "mozilla/VisualEventTracer.h"
#include "URIUtils.h"
#ifdef MOZ_LOGGING
// so we can get logging even in release builds (but only for some things)
@ -4559,16 +4560,24 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI *aURI,
// if this is a Strict-Transport-Security host and the cert
// is bad, don't allow overrides (STS Spec section 7.3).
nsCOMPtr<nsISiteSecurityService> sss =
do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t flags =
mInPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
uint32_t type = nsISiteSecurityService::HEADER_HSTS;
uint32_t flags = mInPrivateBrowsing
? nsISocketProvider::NO_PERMANENT_STORAGE
: 0;
bool isStsHost = false;
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
aURI, flags, &isStsHost);
NS_ENSURE_SUCCESS(rv, rv);
if (XRE_GetProcessType() == GeckoProcessType_Default) {
nsCOMPtr<nsISiteSecurityService> sss =
do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(type, aURI, flags, &isStsHost);
NS_ENSURE_SUCCESS(rv, rv);
} else {
mozilla::dom::ContentChild* cc =
mozilla::dom::ContentChild::GetSingleton();
mozilla::ipc::URIParams uri;
SerializeURI(aURI, uri);
cc->SendIsSecureURI(type, uri, flags, &isStsHost);
}
uint32_t bucketId;
if (isStsHost) {

Просмотреть файл

@ -98,6 +98,7 @@
#include "nsIPresShell.h"
#include "nsIRemoteBlob.h"
#include "nsIScriptError.h"
#include "nsISiteSecurityService.h"
#include "nsIStyleSheet.h"
#include "nsISupportsPrimitives.h"
#include "nsIURIFixup.h"
@ -3239,6 +3240,23 @@ ContentParent::RecvGetSystemMemory(const uint64_t& aGetterId)
return true;
}
bool
ContentParent::RecvIsSecureURI(const uint32_t& type,
const URIParams& uri,
const uint32_t& flags,
bool* isSecureURI)
{
nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID));
if (!sss) {
return false;
}
nsCOMPtr<nsIURI> ourURI = DeserializeURI(uri);
if (!ourURI) {
return false;
}
nsresult rv = sss->IsSecureURI(type, ourURI, flags, isSecureURI);
return NS_SUCCEEDED(rv);
}
bool
ContentParent::RecvLoadURIExternal(const URIParams& uri)

Просмотреть файл

@ -422,6 +422,9 @@ private:
virtual bool RecvGetRandomValues(const uint32_t& length,
InfallibleTArray<uint8_t>* randomValues) MOZ_OVERRIDE;
virtual bool RecvIsSecureURI(const uint32_t& type, const URIParams& uri,
const uint32_t& flags, bool* isSecureURI);
virtual bool DeallocPHalParent(PHalParent*) MOZ_OVERRIDE;
virtual bool DeallocPIndexedDBParent(PIndexedDBParent* aActor) MOZ_OVERRIDE;

Просмотреть файл

@ -478,6 +478,9 @@ parent:
async GetSystemMemory(uint64_t getterId);
sync IsSecureURI(uint32_t type, URIParams uri, uint32_t flags)
returns (bool isSecureURI);
PHal();
PIndexedDB();

Просмотреть файл

@ -4,6 +4,7 @@
package org.mozilla.gecko.db;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import android.content.ContentUris;
@ -110,10 +111,11 @@ public class SearchHistoryProvider extends SharedBrowserDatabaseProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String groupBy = null;
String having = null;
final String groupBy = null;
final String having = null;
final String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
final Cursor cursor = getReadableDatabase(uri).query(SearchHistory.TABLE_NAME, projection,
selection, selectionArgs, groupBy, having, sortOrder);
selection, selectionArgs, groupBy, having, sortOrder, limit);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}

Просмотреть файл

@ -13,11 +13,13 @@ import org.mozilla.gecko.db.SearchHistoryProvider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class testSearchHistoryProvider extends ContentProviderTest {
// Translations of "United Kingdom" in several different languages
private static final String[] testStrings = {"An Ríocht Aontaithe", // Irish
private static final String[] testStrings = {
"An Ríocht Aontaithe", // Irish
"Angli", // Albanian
"Britanniarum Regnum", // Latin
"Britio", // Esperanto
@ -95,6 +97,7 @@ public class testSearchHistoryProvider extends ContentProviderTest {
mTests.add(new TestInsert());
mTests.add(new TestUnicodeQuery());
mTests.add(new TestTimestamp());
mTests.add(new TestLimit());
mTests.add(new TestDelete());
mTests.add(new TestIncrement());
}
@ -111,6 +114,80 @@ public class testSearchHistoryProvider extends ContentProviderTest {
}
}
/**
* Verify that we can pass a LIMIT clause using a query parameter.
*/
private class TestLimit extends TestCase {
@Override
public void test() throws Exception {
ContentValues cv;
for (int i = 0; i < testStrings.length; i++) {
cv = new ContentValues();
cv.put(SearchHistory.QUERY, testStrings[i]);
mProvider.insert(SearchHistory.CONTENT_URI, cv);
}
final int limit = 5;
// Test 1: Handle proper input.
Uri uri = SearchHistory.CONTENT_URI
.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
.build();
Cursor c = mProvider.query(uri, null, null, null, null);
try {
mAsserter.is(c.getCount(), limit,
String.format("Should have %d results", limit));
} finally {
c.close();
}
// Test 2: Empty input yields all results.
uri = SearchHistory.CONTENT_URI
.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_LIMIT, "")
.build();
c = mProvider.query(uri, null, null, null, null);
try {
mAsserter.is(c.getCount(), testStrings.length, "Should have all results");
} finally {
c.close();
}
// Test 3: Illegal params.
String[] illegalParams = new String[] {"a", "-1"};
boolean success = true;
for (String param : illegalParams) {
success = true;
uri = SearchHistory.CONTENT_URI
.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_LIMIT, param)
.build();
try {
c = mProvider.query(uri, null, null, null, null);
success = false;
} catch(IllegalArgumentException e) {
// noop.
} finally {
if (c != null) {
c.close();
}
}
mAsserter.ok(success, "LIMIT", param + " should have been an invalid argument");
}
}
}
/**
* Verify that we can insert values into the DB, including unicode.
*/

Просмотреть файл

@ -20,6 +20,7 @@
#include "mozilla/Preferences.h"
#include "mozilla/LinkedList.h"
#include "nsSecurityHeaderParser.h"
#include "nsXULAppAPI.h"
// A note about the preload list:
// When a site specifically disables sts by sending a header with
@ -87,6 +88,11 @@ NS_IMPL_ISUPPORTS(nsSiteSecurityService,
nsresult
nsSiteSecurityService::Init()
{
// Child processes are not allowed direct access to this.
if (XRE_GetProcessType() != GeckoProcessType_Default) {
MOZ_CRASH("Child process: no direct access to nsSiteSecurityService");
}
nsresult rv;
mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);

Просмотреть файл

@ -210,9 +210,12 @@ let FunctionCallActor = protocol.ActorClass({
// XXX: All of this sucks. Make this smarter, so that the frontend
// can inspect each argument, be it object or primitive. Bug 978960.
let serializeArgs = () => args.map((arg, i) => {
if (typeof arg == "undefined") {
if (arg === undefined) {
return "undefined";
}
if (arg === null) {
return "null";
}
if (typeof arg == "function") {
return "Function";
}
@ -631,7 +634,7 @@ CallWatcherFront.ENUM_METHODS[CallWatcherFront.CANVAS_WEBGL_CONTEXT] = {
stencilOpSeparate: [0, 1, 2, 3],
texImage2D: (args) => args.length > 6 ? [0, 2, 6, 7] : [0, 2, 3, 4],
texParameterf: [0, 1],
texParameteri: [0, 1],
texParameteri: [0, 1, 2],
texSubImage2D: (args) => args.length === 9 ? [0, 6, 7] : [0, 4, 5],
vertexAttribPointer: [2]
};
@ -643,7 +646,7 @@ CallWatcherFront.ENUM_METHODS[CallWatcherFront.CANVAS_WEBGL_CONTEXT] = {
* For example, when gl.clear(gl.COLOR_BUFFER_BIT) is called, the actual passed
* argument's value is 16384, which we want identified as "COLOR_BUFFER_BIT".
*/
var gEnumRegex = /^[A-Z_]+$/;
var gEnumRegex = /^[A-Z][A-Z0-9_]+$/;
var gEnumsLookupTable = {};
// These values are returned from errors, or empty values,

Просмотреть файл

@ -80,6 +80,7 @@ protocol.types.addDictType("snapshot-image", {
index: "number",
width: "number",
height: "number",
scaling: "number",
flipped: "boolean",
pixels: "uint32-array"
});
@ -117,7 +118,7 @@ let FrameSnapshotActor = protocol.ActorClass({
protocol.Actor.prototype.initialize.call(this, conn);
this._contentCanvas = canvas;
this._functionCalls = calls;
this._lastDrawCallScreenshot = screenshot;
this._animationFrameEndScreenshot = screenshot;
},
/**
@ -127,7 +128,7 @@ let FrameSnapshotActor = protocol.ActorClass({
return {
calls: this._functionCalls,
thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e),
screenshot: this._lastDrawCallScreenshot
screenshot: this._animationFrameEndScreenshot
};
}, {
response: { overview: RetVal("snapshot-overview") }
@ -148,7 +149,7 @@ let FrameSnapshotActor = protocol.ActorClass({
// To get a screenshot, replay all the steps necessary to render the frame,
// by invoking the context calls up to and including the specified one.
// This will be done in a custom framebuffer in case of a WebGL context.
let { replayContext, lastDrawCallIndex } = ContextUtils.replayAnimationFrame({
let replayData = ContextUtils.replayAnimationFrame({
contextType: global,
canvas: canvas,
calls: calls,
@ -156,24 +157,25 @@ let FrameSnapshotActor = protocol.ActorClass({
last: index
});
// To keep things fast, generate an image that's relatively small.
let dimensions = Math.min(CanvasFront.SCREENSHOT_HEIGHT_MAX, canvas.height);
let { replayContext, replayContextScaling, lastDrawCallIndex, doCleanup } = replayData;
let [left, top, width, height] = replayData.replayViewport;
let screenshot;
// Depending on the canvas' context, generating a screenshot is done
// in different ways. In case of the WebGL context, we also need to reset
// the framebuffer binding to the default value.
// in different ways.
if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
screenshot = ContextUtils.getPixelsForWebGL(replayContext);
replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, null);
screenshot = ContextUtils.getPixelsForWebGL(replayContext, left, top, width, height);
screenshot.flipped = true;
}
// In case of 2D contexts, no additional special treatment is necessary.
else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
screenshot = ContextUtils.getPixelsFor2D(replayContext);
} else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
screenshot = ContextUtils.getPixelsFor2D(replayContext, left, top, width, height);
screenshot.flipped = false;
}
// In case of the WebGL context, we also need to reset the framebuffer
// binding to the original value, after generating the screenshot.
doCleanup();
screenshot.scaling = replayContextScaling;
screenshot.index = lastDrawCallIndex;
return screenshot;
}, {
@ -188,17 +190,17 @@ let FrameSnapshotActor = protocol.ActorClass({
let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
this._lastDrawCallScreenshot = null;
this._animationFrameEndScreenshot = null;
this._cachedScreenshots = new WeakMap();
},
/**
* This implementation caches the last draw call screenshot to optimize
* This implementation caches the animation frame end screenshot to optimize
* frontend requests to `generateScreenshotFor`.
*/
getOverview: custom(function() {
return this._getOverview().then(data => {
this._lastDrawCallScreenshot = data.screenshot;
this._animationFrameEndScreenshot = data.screenshot;
return data;
});
}, {
@ -211,7 +213,7 @@ let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, {
*/
generateScreenshotFor: custom(function(functionCall) {
if (CanvasFront.ANIMATION_GENERATORS.has(functionCall.name)) {
return promise.resolve(this._lastDrawCallScreenshot);
return promise.resolve(this._animationFrameEndScreenshot);
}
let cachedScreenshot = this._cachedScreenshots.get(functionCall);
if (cachedScreenshot) {
@ -370,12 +372,13 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
let index = this._lastDrawCallIndex;
let width = this._lastContentCanvasWidth;
let height = this._lastContentCanvasHeight;
let flipped = this._lastThumbnailFlipped;
let flipped = !!this._lastThumbnailFlipped; // undefined -> false
let pixels = ContextUtils.getPixelStorage()["32bit"];
let lastDrawCallScreenshot = {
let animationFrameEndScreenshot = {
index: index,
width: width,
height: height,
scaling: 1,
flipped: flipped,
pixels: pixels.subarray(0, width * height)
};
@ -385,7 +388,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
let frameSnapshot = new FrameSnapshotActor(this.conn, {
canvas: this._lastDrawCallCanvas,
calls: functionCalls,
screenshot: lastDrawCallScreenshot
screenshot: animationFrameEndScreenshot
});
this._currentAnimationFrameSnapshot.resolve(frameSnapshot);
@ -408,7 +411,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
let h = this._lastContentCanvasHeight = contentCanvas.height;
// To keep things fast, generate images of small and fixed dimensions.
let dimensions = CanvasFront.THUMBNAIL_HEIGHT;
let dimensions = CanvasFront.THUMBNAIL_SIZE;
let thumbnail;
// Create a thumbnail on every draw call on the canvas context, to augment
@ -529,7 +532,7 @@ let ContextUtils = {
*/
resizePixels: function(srcPixels, srcWidth, srcHeight, dstHeight) {
let screenshotRatio = dstHeight / srcHeight;
let dstWidth = Math.floor(srcWidth * screenshotRatio);
let dstWidth = (srcWidth * screenshotRatio) | 0;
// Use a plain array instead of a Uint32Array to make serializing faster.
let dstPixels = new Array(dstWidth * dstHeight);
@ -540,8 +543,8 @@ let ContextUtils = {
for (let dstX = 0; dstX < dstWidth; dstX++) {
for (let dstY = 0; dstY < dstHeight; dstY++) {
let srcX = Math.floor(dstX / screenshotRatio);
let srcY = Math.floor(dstY / screenshotRatio);
let srcX = (dstX / screenshotRatio) | 0;
let srcY = (dstY / screenshotRatio) | 0;
let cPos = srcX + srcWidth * srcY;
let dPos = dstX + dstWidth * dstY;
let color = dstPixels[dPos] = srcPixels[cPos];
@ -566,8 +569,11 @@ let ContextUtils = {
* the respective canvas, and the rendering will be performed into it.
* This is necessary because some state (like shaders, textures etc.) can't
* be shared between two different WebGL contexts.
* Hopefully, once SharedResources are a thing this won't be necessary:
* http://www.khronos.org/webgl/wiki/SharedResouces
* - Hopefully, once SharedResources are a thing this won't be necessary:
* http://www.khronos.org/webgl/wiki/SharedResouces
* - Alternatively, we could pursue the idea of using the same context
* for multiple canvases, instead of trying to share resources:
* https://www.khronos.org/webgl/public-mailing-list/archives/1210/msg00058.html
*
* In case of a 2D context, a new canvas is created, since there's no
* intrinsic state that can't be easily duplicated.
@ -583,33 +589,59 @@ let ContextUtils = {
* @param number last
* The last (inclusive) function call to end at.
* @return object
* The context on which the specified calls were invoked and the
* last registered draw call's index.
* The context on which the specified calls were invoked, the
* last registered draw call's index and a cleanup function, which
* needs to be called whenever any potential followup work is finished.
*/
replayAnimationFrame: function({ contextType, canvas, calls, first, last }) {
let w = canvas.width;
let h = canvas.height;
let replayCanvas;
let replayContext;
let replayContextScaling;
let customViewport;
let customFramebuffer;
let lastDrawCallIndex = -1;
let doCleanup = () => {};
// In case of WebGL contexts, rendering will be done offscreen, in a
// custom framebuffer, but on the provided canvas context.
// custom framebuffer, but using the same provided context. This is
// necessary because it's very memory-unfriendly to rebuild all the
// required GL state (like recompiling shaders, setting global flags, etc.)
// in an entirely new canvas. However, special care is needed to not
// permanently affect the existing GL state in the process.
if (contextType == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
replayCanvas = canvas;
replayContext = this.getWebGLContext(replayCanvas);
customFramebuffer = this.createBoundFramebuffer(replayContext, w, h);
// To keep things fast, replay the context calls on a framebuffer
// of smaller dimensions than the actual canvas (maximum 256x256 pixels).
let scaling = Math.min(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, h) / h;
replayContextScaling = scaling;
w = (w * scaling) | 0;
h = (h * scaling) | 0;
// Fetch the same WebGL context and bind a new framebuffer.
let gl = replayContext = this.getWebGLContext(canvas);
let { newFramebuffer, oldFramebuffer } = this.createBoundFramebuffer(gl, w, h);
customFramebuffer = newFramebuffer;
// Set the viewport to match the new framebuffer's dimensions.
let { newViewport, oldViewport } = this.setCustomViewport(gl, w, h);
customViewport = newViewport;
// Revert the framebuffer and viewport to the original values.
doCleanup = () => {
gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer);
gl.viewport.apply(gl, oldViewport);
};
}
// In case of 2D contexts, draw everything on a separate canvas context.
else if (contextType == CallWatcherFront.CANVAS_2D_CONTEXT) {
let contentDocument = canvas.ownerDocument;
replayCanvas = contentDocument.createElement("canvas");
let replayCanvas = contentDocument.createElement("canvas");
replayCanvas.width = w;
replayCanvas.height = h;
replayContext = replayCanvas.getContext("2d");
replayContext.clearRect(0, 0, w, h);
replayContextScaling = 1;
customViewport = [0, 0, w, h];
}
// Replay all the context calls up to and including the specified one.
@ -620,23 +652,33 @@ let ContextUtils = {
// to the default value, since we want to perform the rendering offscreen.
if (name == "bindFramebuffer" && args[1] == null) {
replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, customFramebuffer);
} else {
if (type == CallWatcherFront.METHOD_FUNCTION) {
replayContext[name].apply(replayContext, args);
} else if (type == CallWatcherFront.SETTER_FUNCTION) {
replayContext[name] = args;
} else {
// Ignore getter calls.
}
if (CanvasFront.DRAW_CALLS.has(name)) {
lastDrawCallIndex = i;
continue;
}
// Also prevent WebGL context calls that try to change the viewport
// while our custom framebuffer is bound.
if (name == "viewport") {
let framebufferBinding = replayContext.getParameter(replayContext.FRAMEBUFFER_BINDING);
if (framebufferBinding == customFramebuffer) {
replayContext.viewport.apply(replayContext, customViewport);
continue;
}
}
if (type == CallWatcherFront.METHOD_FUNCTION) {
replayContext[name].apply(replayContext, args);
} else if (type == CallWatcherFront.SETTER_FUNCTION) {
replayContext[name] = args;
}
if (CanvasFront.DRAW_CALLS.has(name)) {
lastDrawCallIndex = i;
}
}
return {
replayContext: replayContext,
lastDrawCallIndex: lastDrawCallIndex
replayContextScaling: replayContextScaling,
replayViewport: customViewport,
lastDrawCallIndex: lastDrawCallIndex,
doCleanup: doCleanup
};
},
@ -691,16 +733,21 @@ let ContextUtils = {
* The generated framebuffer object.
*/
createBoundFramebuffer: function(gl, width, height) {
let framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
let oldFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
let oldRenderbufferBinding = gl.getParameter(gl.RENDERBUFFER_BINDING);
let oldTextureBinding = gl.getParameter(gl.TEXTURE_BINDING_2D);
// Use a texture as the color rendebuffer attachment, since consumenrs of
let newFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, newFramebuffer);
// Use a texture as the color renderbuffer attachment, since consumers of
// this function will most likely want to read the rendered pixels back.
let colorBuffer = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorBuffer);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
let depthBuffer = gl.createRenderbuffer();
@ -710,10 +757,24 @@ let ContextUtils = {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBuffer, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, oldTextureBinding);
gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbufferBinding);
return framebuffer;
return { oldFramebuffer, newFramebuffer };
},
/**
* Sets the viewport of the drawing buffer for a WebGL context.
* @param WebGLRenderingContext gl
* @param number width
* @param number height
*/
setCustomViewport: function(gl, width, height) {
let oldViewport = XPCNativeWrapper.unwrap(gl.getParameter(gl.VIEWPORT));
let newViewport = [0, 0, width, height];
gl.viewport.apply(gl, newViewport);
return { oldViewport, newViewport };
}
};
@ -734,8 +795,8 @@ CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS);
CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS);
CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS);
CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS);
CanvasFront.THUMBNAIL_HEIGHT = 50; // px
CanvasFront.SCREENSHOT_HEIGHT_MAX = 256; // px
CanvasFront.THUMBNAIL_SIZE = 50; // px
CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT = 256; // px
CanvasFront.INVALID_SNAPSHOT_IMAGE = {
index: -1,
width: 0,