画布总内存使用量超出最大限制(Safari 12)
我们正在开发一个 可视化 Web 应用程序 ,它使用 d3-force 在画布上绘制网络。
但现在我们在 iOS 浏览器上遇到了一个问题,在与界面进行几次交互后,进程崩溃了。 据我回忆,这不是旧版本(iOS12 之前)的问题,但我没有任何未更新的设备来确认这一点。
我认为此代码总结了问题:
const { range } = require('d3-array')
// create a 1MB image
const createImage = () => {
const size = 512
const canvas = document.createElement('canvas')
canvas.height = size
canvas.width = size
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, size, size)
return canvas
}
const createImages = i => {
// create i * 1MB images
let ctxs = range(i).map(() => {
return createImage()
})
console.log(`done for ${ctxs.length} MB`)
ctxs = null
}
window.cis = createImages
然后在 iPad 和检查器中:
> cis(256)
[Log] done for 256 MB (main-a9168dc888c2e24bbaf3.bundle.js, line 11317)
< undefined
> cis(1)
[Warning] Total canvas memory use exceeds the maximum limit (256 MB). (main-a9168dc888c2e24bbaf3.bundle.js, line 11307)
< TypeError: null is not an object (evaluating 'ctx.strokeRect')
首先,我创建了 256 x 1MB 画布,一切顺利,但我又创建了一个,canvas.getContext 返回了一个空指针。 然后就无法创建另一个画布了。
该限制似乎与设备有关,因为在 iPad 上是 256MB,在 iPhone X 上是 288MB。
> cis(288)
[Log] done for 288 MB (main-a9168dc888c2e24bbaf3.bundle.js, line 11317)
< undefined
> cis(1)
[Warning] Total canvas memory use exceeds the maximum limit (288 MB). (main-a9168dc888c2e24bbaf3.bundle.js, line 11307)
< TypeError: null is not an object (evaluating 'ctx.strokeRect')
由于它是一个缓存,我应该能够删除一些元素,但我没有(因为将 ctxs 或 ctx 设置为 null 应该会触发 GC,但这并不能解决问题)。
我在这个问题上找到的唯一相关页面是 webkit 源代码页面: HTMLCanvasElement.cpp 。
我怀疑问题可能来自 webkit 本身,但在发布到 webkit 问题跟踪器之前,我想确定一下。
还有其他方法可以破坏画布上下文吗?
在此先感谢您的任何想法、指点……
更新
我发现这个 Webkit 问题(可能是)此错误的描述: https://bugs.webkit.org/show_bug.cgi?id=195325
为了添加一些信息,我尝试了其他浏览器。Safari 12 在 macOS 上也存在同样的问题,即使限制更高(计算机内存的 1/4,如 webkit 源中所述)。我也尝试使用最新的 webkit 版本 (236590),但没有更多运气。 但代码适用于 Firefox 62 和 Chrome 69。
我改进了测试代码,因此可以直接从调试器控制台执行。如果有人可以在较旧的 Safari(例如 11)上测试代码,那将会非常有帮助。
let counter = 0
// create a 1MB image
const createImage = () => {
const size = 512
const canvas = document.createElement('canvas')
canvas.height = size
canvas.width = size
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, size, size)
return canvas
}
const createImages = n => {
// create n * 1MB images
const ctxs = []
for( let i=0 ; i<n ; i++ ){
ctxs.push(createImage())
}
console.log(`done for ${ctxs.length} MB`)
}
const process = (frequency,size) => {
setInterval(()=>{
createImages(size)
counter+=size
console.log(`total ${counter}`)
},frequency)
}
process(2000,1000)
有人发布了一个答案,展示了一种解决方法。这个想法是在删除画布之前将高度和宽度设置为 0。这实际上不是一个合适的解决方案,但它可以在我的缓存系统中工作。
我添加了一个小示例,该示例会创建画布,直到引发异常,然后清空缓存并继续。
感谢发布此答案的匿名人士。
let counter = 0
// create a 1MB image
const createImage = () => {
const size = 512
const canvas = document.createElement('canvas')
canvas.height = size
canvas.width = size
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, size, size)
return canvas
}
const createImages = nbImage => {
// create i * 1MB images
const canvases = []
for (let i = 0; i < nbImage; i++) {
canvases.push(createImage())
}
console.log(`done for ${canvases.length} MB`)
return canvases
}
const deleteCanvases = canvases => {
canvases.forEach((canvas, i, a) => {
canvas.height = 0
canvas.width = 0
})
}
let canvases = []
const process = (frequency, size) => {
setInterval(() => {
try {
canvases.push(...createImages(size))
counter += size
console.log(`total ${counter}`)
}
catch (e) {
deleteCanvases(canvases)
canvases = []
}
}, frequency)
}
process(2000, 1000)
另一个数据点:我发现 Safari Web Inspector (12.1 - 14607.1.40.1.4) 会保留打开时创建的每个 Canvas 对象,即使它们本来会被垃圾回收。关闭 Web 检查器并重新打开它,大多数旧画布都会消失。
这并不能解决最初的问题 - 不运行 Web 检查器时超出画布内存,但如果不知道这个小细节,我就会浪费大量时间走上错误的道路,以为我没有释放任何临时画布。
我花了整个周末制作了一个简单的网页,可以快速显示问题。我已经向 Google 和 Apple 提交了错误报告。该页面会显示一张地图。您可以随意平移和缩放,而 Safari 检查器(在 iPad 上运行 Web,使用 MacBook Pro 查看画布)看不到画布。
然后您可以点击一个按钮并绘制一条折线。执行此操作时,您会看到 41 个画布。平移或缩放,您会看到更多。每个画布为 1MB,因此在您拥有 256 个孤立画布后,由于 iPad 上的画布内存已满,错误开始出现。
重新加载页面,点击一个按钮放置一个多边形,同样的事情发生了。
同样有趣的是,我添加了按钮来为白天和夜晚设置地图样式。当它只是一张地图时,您可以来回切换(或只有标记的地图,有一个按钮可以在地图上显示一些标记)。没有孤立的画布。但是画一条线,然后当您更改样式时,您会看到更多孤立的画布。
在活动监视器中查看 MacBook 上的 Safari,在绘制多边形后,当您平移和缩放地图时,尺寸会继续变化*
我希望 Apple 和 Google 能够解决这个问题,而不是声称这是其他公司的问题。这一切都随着 IOS12 运行多年来一直稳定的网页而改变,并且仍然可以在 IOS 9 和 10 iPad 上运行,我保留这些 iPad 进行测试以确保旧设备可以显示当前网页。希望这个测试/实验有所帮助。