コフス技術ブログ

Facebook Pixel Codeの<noscript>タグの位置

この記事はにメンテナンスが行われています。

ある時、公式が案内する手順<head>内にFacebook Pixel Codeが導入された状態のHTMLをjsdomにてパースしserialize()を行っていると、シリアライズ前後でHTML構造が変わってしまう現象に出会いました。

再現できるコードが以下です。HTMLは支給されたものでした。

index.js
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const html = `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<!-- Facebook Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{your-pixel-id-goes-here}');
fbq('track', 'PageView');
</script>
<noscript>
<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id={your-pixel-id-goes-here}&ev=PageView&noscript=1"/>
</noscript>
<!-- End Facebook Pixel Code -->
</head>
<body></body>
</html>`
const dom = new JSDOM(html);
const serializeDom = dom.serialize();
console.log(serializeDom);

これを実行すると以下が得られます。

Terminal window
$ node index.js
<!DOCTYPE html><html lang="ja"><head>
<meta charset="utf-8">
<!-- Facebook Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{your-pixel-id-goes-here}');
fbq('track', 'PageView');
</script>
<noscript>
</noscript></head><body><img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id={your-pixel-id-goes-here}&amp;ev=PageView&amp;noscript=1">
<!-- End Facebook Pixel Code -->
</body></html>

<noscript>タグ内の<img>タグが<body>直下へ移動してしまい、入出力でDOM構造が変わっている事が確認できます。なぜでしょうか?

原因

<head>内に以下の記述がありますが、この記述に問題があります。

<noscript>
<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id={your-pixel-id-goes-here}&ev=PageView&noscript=1"/>
</noscript>

上記記述では<noscript>は子要素として<img>タグを持っています。
ですがHTML5の仕様では<head>を親に持つ<noscript>が持てる子要素は<link>,<style>,<meta>の3種類のタグみで、<img>タグは許可されていません。

スクリプトの実行が無効かつ<head>要素の子孫である場合: 任意の順序で、0個以上の<link>要素、0個以上の<style>要素、0個以上の<meta>要素。
スクリプトの実行が無効かつ<head>要素の子孫ではない場合: 任意の透過的コンテンツ、ただし<noscript>要素を入れ子にしてはならない。
上記以外の場合: フローコンテンツ、記述コンテンツ。

引用:<noscript>- MDN

つまり上記記述はそもそもHTML5の仕様として間違っていたのです。W3Cのバリデーションにも当然通りません。

仕様上間違っているHTMLをjsdomserialize()を通すことで、HTML5の仕様として正しい状態に正された形で出力された結果が事の流れでした。

正しくはこう

上記記述を正すには、<noscript></noscript>をそのまま<body>直下に移動してしまうだけです。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<!-- Facebook Pixel Code -->
<script>
!function (f, b, e, v, n, t, s) {
if (f.fbq) return; n = f.fbq = function () {
n.callMethod ?
n.callMethod.apply(n, arguments) : n.queue.push(arguments)
};
if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0';
n.queue = []; t = b.createElement(e); t.async = !0;
t.src = v; s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s)
}(window, document, 'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '{your-pixel-id-goes-here}');
fbq('track', 'PageView');
</script>
<noscript>
<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id={your-pixel-id-goes-here}&ev=PageView&noscript=1" />
</noscript>
<!-- End Facebook Pixel Code -->
</head>
<body>
<noscript>
<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id={your-pixel-id-goes-here}&ev=PageView&noscript=1" />
</noscript>
</body>
</html>

問題

原因はHTML5の仕様外という初歩的で単純なものでしたが、問題はFacebook Pixel Codeの導入手順として公式がHTML5仕様外の内容で案内をしている点です。

公式が案内する手順では以下の内容があり、「head終了タグのすぐ上に」とあるのでやはり<head>内への記述を推奨しているように思えます。(2021/09/28時点)

  1. イベントマネージャを開きます。
  2. 設定するピクセルを選択します。
  3. [ピクセルの設定を続行]をクリックします。
  4. [Facebookピクセル]を選択して[リンクする]をクリックします。
  5. [手動でコードをインストール]を選択します。
  6. ピクセルベースコードをコピーします。
  7. ウェブサイトのヘッダーを探すか、CMSまたはウェブプラットフォームのヘッダーテンプレートを探します。
  8. ヘッダーセクションの末尾にあるhead終了タグのすぐ上に、ベースコードを貼り付けます。
    引用:https://www.facebook.com/business/help/952192354843755?id=1205376682832142

導入作業者全員がHTMLの知識があるわけではないでしょうから、上記案内は正すべき内容と言えるでしょう。

ちなみにこの問題は2017年頃にはコミュニティー内で報告があったようですので、事実であればいまだ正されない事にも問題があるように感じます。

実は、この問題について、Facebook上のプライベートグループである「Facebook Developer Community」にて2017年1月に報告を行っています。その時の返信をまとめると「Webページに書いてある手順は推奨であって必須ではない、最終的にどこに設置するかは開発者が決めれば良い」というものでした。正常に動作しない手順を推奨している、というよく分からない回答であり、それから3年以上経過した今でも同じ状況になっている、ということで今回記事にしました。

引用:https://sem-technology.info/blog/posts/00126/