はじめに
この言葉を知ったのは、最近です。
いままで、chromeのプラグインを作成してきましたが、この言葉には直接、出会ったことは有りませんでした。すでにShadowRootという機能はあったようですが、必要に迫られたことはありませんでした。
それが、これも最近ですが、デバッガーを見ていたら、#shadowrootという文字が目に入りました。
その時は、何だろうとあまり気に留めていませんでした。#documentという文字はiframeでよく見ていました。
次項以下では、どの場面でShadowRootという文字に出会ったのかについて記述します。
出会った場面
今、作成中のプラグインで、アクティブタブにiframeを追加し、その時のsrc属性にbingのサイトを指定しています。そのbingのサーチページにあるいくつかのリンクのtarget属性を見ると_blankになっていますので、それを_selfに変更しています。そうすることで、同じページにリンク先のページを表示できます。
この方法で、リンクをクリックしても、新規にタブを開くことはなく、ワンストップで解決します。リンクをクリックするたびにタブが次々と開くことは避けられます。ところが、ある時、target="_blank"をtarget="_self"に変更できていないアンカー要素がありました。
何でだろうと、調べて行くうちに、#ShadowRootというエレメントに出会いました。このShadowRootというエレメントのコンセプトは親のルートから分離してカプセル化したものだとわかりました。
何でも、そのShadowRootのエレメントは外部から変更されたくない要素を見えなくする機能をもっているということです。読み込み専用ともでていました。
おそらく、これが原因で、リンクのtarget="_blank"を修正できなかったのかと合点しました。
何か変更する対策はないものだろうか
行いたいことは、ShadowRootであれ、なんであれ、すべてのリンクのtarget="_blank"は、"_self"に変更したいことです。
更に調べて行くうちに、エレメントをShadowRoot化するときに、mode="open"という指定ができる、APIがあるということで、そのリンクがあるiframeを見ると確かに、#ShadowRoot(open)と表示されています。このopen指定があれば、外部からも変更ができそうだということがありました。しかし、今のところ、試行錯誤してテストしていますが、まだ、成功には至っていません。
ネットでいろいろなサイトをデバッガーで見ていると、このShadowRootはどこのサイトでも使用しているのがわかります。
見せたくないエレメントは隠すようにしているようです。
また、外部から変更もできないようにカプセル化しているようです。
iframeに表示した外部ページにあるリンクをクリックしたときの動作
このリンク(アンカー要素)のtarget属性は"_blank"とか"_top"、"_parent"があります。target属性がないリンクもあります。ここで、リンクのtarget="_blank"を"_self"に変更してみます。
次にそのリンクをクリックすると、ブラウザーはsrc属性に指定されたurlへリクエストを出します。サーバーはそのリクエストのレスポンスをブラウザー側へ返します。ここで、ブラウザーはそのレスポンスデータをtargetをチェックして"_self"ならば、新規にタブを開かずに、もとのiframeページに表示します。
今回は、リンクをクリックするたびに、サーバーからのページデータは元のiframeページに表示するものです。行いたいことはこの動作です。
しかし、一部のページでは、target属性の変更ができません。
何かしらの原因で、変更が許可されていないケースがあります。
現在、この原因を調査中です。
target属性の変更ができないのは
コメントを戴いた方からのアドバイスもあり、targetのプロパティ記述子を調べています。もしかしたら、この時のwritableフラグがfalseになっているのかと思いました。ページの製作者側で意図的にそうしているのであれば、そのプロパティは外部から変更できない仕様なので、あきらめるしか方法はないかもしれません。
以下のコードは、iframeドキュメントで実行するexecuteScriptのスクリプトコードです。
/*◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
◆ここは、target_updateのexecuteScriptのコードです。
◆ここで、ドキュメントのすべてのtarget=_blankを_selfに変更
◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆*/
console.log("◆◆◆target_update このフレームIdは、 = "+frameId)
if(HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')===true){
alert("shadowRootがあります。");
}
let anchor = document.querySelectorAll("a");
if(anchor.length !== 0){
Array.from(anchor).forEach( a => a.target !== "_self" ? a.target = "_self" : null );
console.log("target_update すべてのaアンカーをtarget=_selfにセットしました"+frameId);
let aaa = document.querySelectorAll("a");
if(aaa.length!==0){
let cc = -0;
for(const sss of aaa){
cc++;
//console.log("target = "+cc+" "+sss.attributes.target.value);
descriptor = Object.getOwnPropertyDescriptor(sss.attributes,'target');
console.log(frameId+" "+cc+" target = "+sss.attributes.target.value+" descriptor value = "+descriptor.value.name+" writable = "+descriptor.writable+" configurable = "+descriptor.configurable);
sss.attributes.target = "_top";
console.log("target更新 = "+sss.attributes.target.value);
//テストのため、"_top"としています。
Object.defineProperty(sss.attributes,'target', {
value: "_top"
});
}
}
}
この時、Object.definePropertyで、以下のエラーがでました。
このページのリンクのtargetには、セツターが登録されていないようです。そのため、"_top"で登録できなかったようです。
VM141:30 Uncaught TypeError: Failed to set a named property 'target' on 'NamedNodeMap': Named property setter is not supported.
この部分で、targetを変更しています。
Array.from(anchor).forEach( a => a.target !== "_self" ? a.target = "_self" : null );
このコードは正常に実行して、"_blank"が"_self"に変更されています。しかし、不思議なのは、targetのwritableがfalseなのに、書き換えられています。
このように、まだ、腑に落ちない点がありますので、引き続き調査を続けます。
プロパティデスクリプタ結果
targetのPropertyDescriptorをデバッガーで見ると以下のようになっています。
value: targetの内容
writableフラグを見るとfalseになっています。この場合は、書き込み禁止のはづですが、実行すると、"_self"になっています。
あとがき
まだ、ShadowRootを学習中のため、今のプラグインは完成していません。このShadowRoot下のリンクのtargetをすべて"_self"化することができた暁にはプラグインが完成するときです。
その時がいつ来るのか、はっきり言ってわかりません。挫折するのか、克服するのかもう少し頑張ってみます。
補足
プラグインを作成していて、少し凝ったものを作ろうとすると、途端に、難しいWeb仕様にぶつかります。今回も、ShadowRootとか、PropertyDescriptorとかにぶつかりました。Web仕様は日々、変化していますので、都度、追いかけていると疲れますので、そのままにしています。しかし、何かの時に突然、不具合に出会い、そこで初めて気が付きます。新しい仕様の時は、以下のサイトを見ることにします。
開発者による
開発者向けリソース
内容が難しいですが、出来るだけ原文で見るようにしています。
日本語化すると大事なところで意味が不明になりますので、その時は原文を見ます。