/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WI.Recording = class Recording { constructor(version, type, initialState, frames, data) { this._version = version; this._type = type; this._initialState = initialState; this._frames = frames; this._data = data; this._displayName = WI.UIString("Recording"); this._swizzle = []; this._source = null; let actions = [new WI.RecordingInitialStateAction].concat(...this._frames.map((frame) => frame.actions)); this._actions = Promise.all(actions.map((action) => action.swizzle(this))).then(() => { for (let action of actions) { if (!action.valid) continue; let prototype = null; if (this._type === WI.Recording.Type.Canvas2D) prototype = CanvasRenderingContext2D.prototype; else if (this._type === WI.Recording.Type.CanvasWebGL) prototype = WebGLRenderingContext.prototype; if (prototype) { let validName = action.name in prototype; let validFunction = !action.isFunction || typeof prototype[action.name] === "function"; if (!validName || !validFunction) { action.markInvalid(); WI.Recording.synthesizeError(WI.UIString("ā€œ%sā€ is invalid.").format(this._name)); } } } return actions; }); } static fromPayload(payload) { if (typeof payload !== "object" || payload === null) return null; if (isNaN(payload.version) || payload.version <= 0) return null; let type = null; switch (payload.type) { case RecordingAgent.Type.Canvas2D: type = WI.Recording.Type.Canvas2D; break; case RecordingAgent.Type.CanvasWebGL: type = WI.Recording.Type.CanvasWebGL; break; default: type = String(payload.type); break; } if (typeof payload.initialState !== "object" || payload.initialState === null) payload.initialState = {}; if (typeof payload.initialState.attributes !== "object" || payload.initialState.attributes === null) payload.initialState.attributes = {}; if (!Array.isArray(payload.initialState.parameters)) payload.initialState.parameters = []; if (typeof payload.initialState.content !== "string") payload.initialState.content = ""; if (!Array.isArray(payload.frames)) payload.frames = []; if (!Array.isArray(payload.data)) payload.data = []; let frames = payload.frames.map(WI.RecordingFrame.fromPayload); return new WI.Recording(payload.version, type, payload.initialState, frames, payload.data); } static displayNameForSwizzleType(swizzleType) { switch (swizzleType) { case WI.Recording.Swizzle.None: return WI.unlocalizedString("None"); case WI.Recording.Swizzle.Number: return WI.unlocalizedString("Number"); case WI.Recording.Swizzle.Boolean: return WI.unlocalizedString("Boolean"); case WI.Recording.Swizzle.String: return WI.unlocalizedString("String"); case WI.Recording.Swizzle.Array: return WI.unlocalizedString("Array"); case WI.Recording.Swizzle.TypedArray: return WI.unlocalizedString("TypedArray"); case WI.Recording.Swizzle.Image: return WI.unlocalizedString("Image"); case WI.Recording.Swizzle.ImageData: return WI.unlocalizedString("ImageData"); case WI.Recording.Swizzle.DOMMatrix: return WI.unlocalizedString("DOMMatrix"); case WI.Recording.Swizzle.Path2D: return WI.unlocalizedString("Path2D"); case WI.Recording.Swizzle.CanvasGradient: return WI.unlocalizedString("CanvasGradient"); case WI.Recording.Swizzle.CanvasPattern: return WI.unlocalizedString("CanvasPattern"); case WI.Recording.Swizzle.WebGLBuffer: return WI.unlocalizedString("WebGLBuffer"); case WI.Recording.Swizzle.WebGLFramebuffer: return WI.unlocalizedString("WebGLFramebuffer"); case WI.Recording.Swizzle.WebGLRenderbuffer: return WI.unlocalizedString("WebGLRenderbuffer"); case WI.Recording.Swizzle.WebGLTexture: return WI.unlocalizedString("WebGLTexture"); case WI.Recording.Swizzle.WebGLShader: return WI.unlocalizedString("WebGLShader"); case WI.Recording.Swizzle.WebGLProgram: return WI.unlocalizedString("WebGLProgram"); case WI.Recording.Swizzle.WebGLUniformLocation: return WI.unlocalizedString("WebGLUniformLocation"); default: console.error("Unknown swizzle type", swizzleType); return null; } } static synthesizeError(message) { const target = WI.mainTarget; const source = WI.ConsoleMessage.MessageSource.Other; const level = WI.ConsoleMessage.MessageLevel.Error; let consoleMessage = new WI.ConsoleMessage(target, source, level, WI.UIString("Recording error: %s").format(message)); consoleMessage.shouldRevealConsole = true; WI.consoleLogViewController.appendConsoleMessage(consoleMessage); } // Public get displayName() { return this._displayName; } get type() { return this._type; } get initialState() { return this._initialState; } get frames() { return this._frames; } get data() { return this._data; } get actions() { return this._actions; } get source() { return this._source; } set source(source) { this._source = source; } createDisplayName(suggestedName) { let recordingNameSet; if (this._source) { recordingNameSet = this._source[WI.Recording.CanvasRecordingNamesSymbol]; if (!recordingNameSet) this._source[WI.Recording.CanvasRecordingNamesSymbol] = recordingNameSet = new Set; } else recordingNameSet = WI.Recording._importedRecordingNameSet; let name; if (suggestedName) { name = suggestedName; let duplicateNumber = 2; while (recordingNameSet.has(name)) name = `${suggestedName} (${duplicateNumber++})`; } else { let recordingNumber = 1; do { name = WI.UIString("Recording %d").format(recordingNumber++); } while (recordingNameSet.has(name)); } recordingNameSet.add(name); this._displayName = name; } async swizzle(index, type) { if (typeof this._swizzle[index] !== "object") this._swizzle[index] = {}; if (type === WI.Recording.Swizzle.Number) return parseFloat(index); if (type === WI.Recording.Swizzle.Boolean) return !!index; if (type === WI.Recording.Swizzle.Array) return Array.isArray(index) ? index : []; if (type === WI.Recording.Swizzle.DOMMatrix) return new DOMMatrix(index); // FIXME: Web Inspector: send data for WebGL objects during a recording instead of a placeholder string if (type === WI.Recording.Swizzle.TypedArray || type === WI.Recording.Swizzle.WebGLBuffer || type === WI.Recording.Swizzle.WebGLFramebuffer || type === WI.Recording.Swizzle.WebGLRenderbuffer || type === WI.Recording.Swizzle.WebGLTexture || type === WI.Recording.Swizzle.WebGLShader || type === WI.Recording.Swizzle.WebGLProgram || type === WI.Recording.Swizzle.WebGLUniformLocation) { return index; } if (!(type in this._swizzle[index])) { try { let data = this._data[index]; switch (type) { case WI.Recording.Swizzle.None: this._swizzle[index][type] = data; break; case WI.Recording.Swizzle.String: this._swizzle[index][type] = String(data); break; case WI.Recording.Swizzle.Image: this._swizzle[index][type] = await new Promise((resolve, reject) => { let image = new Image; let resolveWithImage = () => { resolve(image); }; image.addEventListener("load", resolveWithImage); image.addEventListener("error", resolveWithImage); image.src = data; }); break; case WI.Recording.Swizzle.ImageData: this._swizzle[index][type] = new ImageData(new Uint8ClampedArray(data[0]), parseInt(data[1]), parseInt(data[2])); break; case WI.Recording.Swizzle.Path2D: this._swizzle[index][type] = new Path2D(data); break; case WI.Recording.Swizzle.CanvasGradient: var gradientType = await this.swizzle(data[0], WI.Recording.Swizzle.String); var context = document.createElement("canvas").getContext("2d"); this._swizzle[index][type] = gradientType === "radial-gradient" ? context.createRadialGradient(...data[1]) : context.createLinearGradient(...data[1]); for (let stop of data[2]) { let color = await this.swizzle(stop[1], WI.Recording.Swizzle.String); this._swizzle[index][type].addColorStop(stop[0], color); } break; case WI.Recording.Swizzle.CanvasPattern: var [image, repeat] = await Promise.all([ this.swizzle(data[0], WI.Recording.Swizzle.Image), this.swizzle(data[1], WI.Recording.Swizzle.String), ]); var context = document.createElement("canvas").getContext("2d"); this._swizzle[index][type] = context.createPattern(image, repeat); break; } } catch { } } return this._swizzle[index][type]; } toJSON() { let initialState = {}; if (!isEmptyObject(this._initialState.attributes)) initialState.attributes = this._initialState.attributes; if (this._initialState.parameters.length) initialState.parameters = this._initialState.parameters; if (this._initialState.content && this._initialState.content.length) initialState.content = this._initialState.content; return { version: this._version, type: this._type, initialState, frames: this._frames.map((frame) => frame.toJSON()), data: this._data, }; } }; WI.Recording._importedRecordingNameSet = new Set; WI.Recording.CanvasRecordingNamesSymbol = Symbol("canvas-recording-names"); WI.Recording.Type = { Canvas2D: "canvas-2d", CanvasWebGL: "canvas-webgl", }; // Keep this in sync with WebCore::RecordingSwizzleTypes. WI.Recording.Swizzle = { None: 0, Number: 1, Boolean: 2, String: 3, Array: 4, TypedArray: 5, Image: 6, ImageData: 7, DOMMatrix: 8, Path2D: 9, CanvasGradient: 10, CanvasPattern: 11, WebGLBuffer: 12, WebGLFramebuffer: 13, WebGLRenderbuffer: 14, WebGLTexture: 15, WebGLShader: 16, WebGLProgram: 17, WebGLUniformLocation: 18, };