总画布内存使用超出最大限制(Safari 12)

Ogi*_*tre 23 javascript safari webkit mobile-safari html5-canvas

我们正在开发一个可视化Web应用程序,它使用d3-force在画布上绘制网络.由于每个节点都包含大量信息,并且由于CPU占用大量资源来重绘每个帧的所有内容,因此我们实现了一种缓存,其中每个节点都在其画布上绘制(未链接到DOM),每个缩放级别一次.然后在节点的位置在主画布(链接到DOM)上绘制那些画布.

我们对速度增益感到满意,即使结果现在是内存密集型(特别是在hidpi显示器(视网膜)上,像素密度可以是2或3).

但现在我们在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
Run Code Online (Sandbox Code Playgroud)

然后在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')
Run Code Online (Sandbox Code Playgroud)

存在,我创建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')
Run Code Online (Sandbox Code Playgroud)

因为它是一个缓存我应该能够删除一些元素,但我不是(因为将ctxs或ctx设置为null应该触发GC,但它不能解决问题).

我在这个问题上找到的唯一相关页面是webkit源代码页:HTMLCanvasElement.cpp.

我怀疑问题可能来自webkit本身,但我想在发布到webkit问题跟踪器之前确定.

有没有其他方法来破坏画布上下文?

提前感谢任何想法,指针,......

EDITS:

为了添加一些信息,我尝试了其他浏览器.Safari 12在macOS上也存在同样的问题,即使限制较高(计算机内存的1/4,如webkit源中所述).我也试过最新的webkit build(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)
Run Code Online (Sandbox Code Playgroud)

eep*_*ete 6

我花了一个周末制作了一个简单的网页,可以快速显示问题。我已经向Google和Apple提交了错误报告。该页面将显示一个地图。您可以平移和缩放所有所需的内容,而Safari检查器(在iPad上运行网络,使用MacBook Pro查看画布)看不到画布。

然后,您可以点击一个按钮并绘制一条折线。当您这样做时,您会看到41幅画布。平移或缩放,您将看到更多。每个画布为1MB,因此在您拥有256个孤立的画布之后,由于iPad上的画布内存已满,错误开始出现。

重新加载页面,点击一个按钮以放置一个多边形,然后发生同样的事情。

同样有趣的是,我添加了按钮来为白天和黑夜设置地图样式。您可以仅在地图上来回移动(或仅带有标记的地图,有一个按钮可以在地图上显示一些标记)。没有孤立的画布。但是画一条线,然后在更改样式时,会看到更多的孤立画布。

在Active Monitor中的MacBook上查看Safari时,绘制多边形后平移和缩放地图时,尺寸保持不变

我希望苹果和谷歌能解决这个问题,而不是声称这是另一家公司的问题。IOS12运行的网页已经稳定多年,并且仍然可以在IOS 9和10 iPad上运行,因此,所有这些都发生了变化,我一直进行测试以确保较旧的设备可以显示当前网页。希望这项测试/实验对您有所帮助。

  • 新的IOS 12.1不能_not_修复Safari中的此错误。 (3认同)
  • Google正确地认为这是Apple的问题-在IOS12更新的时间范围内,其JavaScript库没有更改。现在由苹果决定。我给他们发送了sys诊断转储,并等待他们确认问题。错误报告顶部的数字是45077639 (2认同)
  • 自12.3.1起不固定 (2认同)

Sha*_*vis 6

另一个数据点:我发现 Safari Web Inspector (12.1 - 14607.1.40.1.4) 保留在打开时创建的每个 Canvas 对象,即使它们会被垃圾收集。关闭网络检查器并重新打开它,大多数旧画布都会消失。

这并没有解决最初的问题——在不运行 web-inspector 时超出画布内存,但在不知道这个小花絮的情况下,我浪费了大量时间走错路,以为我没有释放任何临时画布.


Ogi*_*tre 5

有人发布了答案,显示了解决方法。想法是在删除画布之前将高度和宽度设置为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)
Run Code Online (Sandbox Code Playgroud)

  • 我在网络环境中使用Google Maps。Google Maps在画布上绘制地图。一切都很顺利,直到升级到IOS12。出现“ Total Canvas内存使用...”错误和“ ... null不是读取a.scale的对象”错误,并且在几次平移或缩放后一切都中断了。我的代码没有任何变化。一切仍可在运行IOS 9和10的旧设备上运行。IOS上的Apple Safari Land有所更改。装有Mojave(19.14)的MacBook Pro没问题。这不是一个答案,但是希望可以通过查看来自不同用例的类似问题来解决您的问题。 (2认同)

小智 5

WebKit的最近更改可能会导致这些问题 https://github.com/WebKit/webkit/commit/5d5b478917c685e50d1032ccf761ca53fc8f1b74#diff-b411cd4839e4bbc17b00570536abfa8f

  • 请为您的答案以及该提交所带来的更改添加一些解释,而不仅仅是链接。 (3认同)