Atributos do DOM agora na cadeia do protótipo

A equipe do Chrome anunciou recentemente que estamos movendo as propriedades do DOM para a cadeia de protótipos. Essa mudança, implementada no Chrome 43 (Beta desde meados de abril de 2015), alinha o Chrome à especificação do IDL da Web e a outras implementações de navegadores, como o IE e o Firefox. Edição: esclarecimento   Os navegadores mais antigos baseados no WebKit não são compatíveis com a especificação, mas o Safari é.

O novo comportamento é positivo de várias maneiras. Ele:

  • Melhora a compatibilidade na Web (o IE e o Firefox já fazem isso) com a conformidade com a especificação.
  • Permite criar getters/setters de forma consistente e eficiente em todos os objetos DOM.
  • Aumenta a hackabilidade da programação DOM. Por exemplo, ele permite implementar polyfills que permitem emular de maneira eficiente a funcionalidade ausente em alguns navegadores e bibliotecas JavaScript que substituem os comportamentos padrão dos atributos DOM.

Por exemplo, uma especificação hipotética do W3C inclui algumas novas funcionalidades chamadas isSuperContentEditable, e o navegador Chrome não as implementa, mas é possível "polyfill" ou emular o recurso com uma biblioteca. Como desenvolvedor da biblioteca, você naturalmente vai querer usar o prototype da seguinte maneira para criar um polyfill eficiente:

Object.defineProperty(HTMLDivElement.prototype, "isSuperContentEditable", {
    get: function() { return true; },
    set: function() { /* some logic to set it up */ },
});

Antes dessa mudança, para manter a consistência com outras propriedades do DOM no Chrome, você teria que criar a nova propriedade em cada instância, o que seria muito ineficiente para cada HTMLDivElement na página.

Essas mudanças são importantes para a consistência, o desempenho e a padronização da plataforma da Web, mas podem causar alguns problemas para os desenvolvedores. Se você estava usando esse comportamento devido à compatibilidade legada entre o Chrome e o WebKit, recomendamos que verifique seu site e confira o resumo das mudanças abaixo.

Resumo das mudanças

O uso de hasOwnProperty em uma instância de objeto DOM agora vai retornar false

Às vezes, os desenvolvedores usam hasOwnProperty para verificar a presença de uma propriedade em um objeto. Isso não vai mais funcionar de acordo com a especificação porque os atributos DOM agora fazem parte da cadeia de protótipos, e hasOwnProperty só inspeciona os objetos atuais para saber se ele está definido.

Antes do Chrome 42, o seguinte retornaria true.

> div = document.createElement("div");
> div.hasOwnProperty("isContentEditable");

true

No Chrome 43 e versões mais recentes, ele vai retornar false.

> div = document.createElement("div");
> div.hasOwnProperty("isContentEditable");

false

Isso significa que, se você quiser verificar se isContentEditable está disponível no elemento, será necessário verificar o protótipo no objeto HTMLElement. Por exemplo, HTMLDivElement herda de HTMLElement, que define a propriedade isContentEditable.

> HTMLElement.prototype.hasOwnProperty("isContentEditable");

true

Não é necessário usar hasOwnProperty. Recomendamos o uso do operando in muito mais simples, porque ele verifica a propriedade em toda a cadeia de protótipos.

if("isContentEditable" in div) {
    // We have support!!
}

O Object.getOwnPropertyDescriptor na instância de objeto DOM não vai mais retornar um descritor de propriedade para atributos.

Se o site precisar acessar o descritor de propriedade de um atributo em um objeto DOM, será necessário seguir a cadeia de protótipos.

Se você quisesse receber a descrição da propriedade no Chrome 42 e versões anteriores, faria o seguinte:

> Object.getOwnPropertyDescriptor(div, "isContentEditable");

Object {value: "", writable: true, enumerable: true, configurable: true}

O Chrome 43 e versões mais recentes vão retornar undefined nesse cenário.

> Object.getOwnPropertyDescriptor(div, "isContentEditable");

undefined

O que significa que, para acessar o descritor de propriedade da propriedade isContentEditable, você precisa seguir a cadeia de protótipos da seguinte maneira:

> Object.getOwnPropertyDescriptor(HTMLElement.prototype, "isContentEditable");

Object {get: function, set: function, enumerable: false, configurable: false}

O JSON.stringify não vai mais serializar atributos DOM

JSON.stringify não serializa as propriedades do DOM que estão no protótipo. Por exemplo, isso pode afetar seu site se você estiver tentando serializar um objeto, como a PushSubscription das notificações push.

No Chrome 42 e versões anteriores, o seguinte teria funcionado:

> JSON.stringify(subscription);

{
    "endpoint": "https://something",
    "subscriptionId": "SomeID"
}

O Chrome 43 e versões mais recentes não serializam as propriedades definidas no protótipo, e um objeto vazio é retornado.

> JSON.stringify(subscription);

{}

Você vai precisar fornecer seu próprio método de serialização. Por exemplo, você pode fazer o seguinte:

function stringifyDOMObject(object)
{
    function deepCopy(src) {
        if (typeof src != "object")
            return src;
        var dst = Array.isArray(src) ? [] : {};
        for (var property in src) {
            dst[property] = deepCopy(src[property]);
        }
        return dst;
    }
    return JSON.stringify(deepCopy(object));
}
var s = stringifyDOMObject(domObject);

Escrever em propriedades somente leitura no modo restrito vai gerar um erro

A gravação em propriedades somente leitura deve gerar uma exceção quando você estiver usando o modo restrito. Por exemplo:

function foo() {
    "use strict";
    var d = document.createElement("div");
    console.log(d.isContentEditable);
    d.isContentEditable = 1;
    console.log(d.isContentEditable);
}

No Chrome 42 e versões anteriores, a função continuaria sendo executada, embora isContentEditable não tivesse sido alterada.

// Chrome 42 and earlier behavior
> foo();

false // isContentEditable
false // isContentEditable (after writing to read-only property)

Agora, no Chrome 43 e versões mais recentes, uma exceção será gerada.

// Chrome 43 and onwards behavior
> foo();

false
Uncaught TypeError: Cannot set property isContentEditable of #<HTMLElement> which has only a getter

Tenho um problema. O que devo fazer?

Siga as orientações ou deixe um comentário abaixo para conversarmos.

Identifiquei um problema em um site. O que devo fazer?

Ótima pergunta. A maioria dos problemas com sites é causada pelo fato de um site ter escolhido fazer a detecção de presença de atributos com o método getOwnProperty. Isso geralmente acontece quando o proprietário do site só direcionou os navegadores WebKit mais antigos. Há algumas coisas que um desenvolvedor pode fazer:

  • Informar um problema sobre o site afetado no nosso rastreador de problemas (do Chrome)
  • Registre um problema no radar do WebKit e consulte https://bugs.webkit.org/show_bug.cgi?id=49739

Tenho interesse em acompanhar essa mudança