風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

Chrome拡張機能Manifest V3対応の勘どころ(?)

Chrome ウェブストアに登録している4つの拙作Chrome拡張機能Manifest V3への対応が先日ようやく完了しました

Manifest V3対応のために当方が実際に行った作業の概要やハマった点などを備忘として残しておきます。
詳細でなかったり整理できていない点に関してはご容赦ください



何はともあれ、公式ページを見ておくべし

developer.chrome.com
手探りでいろいろと検索して調べたりしましたが、結局は公式ページに書いてあることでした……というパターンも多々あったため、ひととおり目を通しておくことをおすすめします。
といいながら、英語が苦手な自分は見てない所も多いんですけどね……(汗)

Firefoxとコードを共通化したいんだけど?

現在(2022/10/1)のところ、Firefox(Web Extensions)ではManifest V3には(一般向けとしては)未対応です(2022年末に向けてサポート予定)。

でも製作者としては、manifest.jsonは切り替えるにしても、JavaScriptのコードはなるべく共通化したいという場合もあるかと思います。
その場合、

const MANIFEST_VERSION = chrome.runtime.getManifest().manifest_version;

のようにすれば、manifest.jsonで定義した"manifest_version"の値を取得できるので、コード内であらかじめManifestのバージョンを取得しておき、随時場合分けすることで対応することができるかと思います。

backgroundはService Workerに変更

Manifest V3では、backgroundページはService Workerに置き換わっています
V2ではmanifest.jsonに

    "background" : {
        "scripts" : [ "js/background.js" ],
        "persistent": true
    }

みたいに記述していたのが、V3では

    "background" : {
        "service_worker" : "js/background.js"
    }

のようになります。
注意点として、V2では配列で複数のスクリプトを指定できていたのが、V3では指定可能なスクリプトは1つだけです。
なのでV3で複数のスクリプトを指定したい場合には、メインとして指定したスクリプトからimportScripts()("background"で"type": "module"オプション(ES Module)を設定した場合にはimport)を使ってその他のスクリプト(ライブラリ)を読み込む必要があります。
"persistent"オプションは無くなったようです(常に"persistent": falseになるイメージか?)。
また、"type": "module"とすると、ES Moduleとして動作するらしいです(試していないです)。

Manifest V3では削除されてしまったAPIに注意

もともとV2時代から非推奨(deprecated)となっていたAPIのいくつかは、V3になると削除されて使えなくなっているので要注意です
古くからある拡張機能だと、chrome.extension.xx といったAPIが残っていたりすることもあると思われます。
自分の場合、chrome.extension.getURL()等が残っていてはまりました

manifest.json上"permissions"の指定方法変更(ホスト指定の分離)

Manifest V2では

"permissions": [
    "storage",
    "webRequest", 
    "webRequestBlocking", 
    "*://*.twitter.com/*", 
    "*://pbs.twimg.com/*",
    "*://video.twimg.com/*",
    "*://*.cdn.vine.co/*"
]

のように、機能の使用権限とリソース(URL)へのアクセス許可とが混在していましたが、V3では

"permissions" : [
        "storage",
        "declarativeNetRequest",
        "declarativeNetRequestFeedback"
],
"host_permissions" : [
    "*://*.twitter.com/*", 
    "*://pbs.twimg.com/*",
    "*://video.twimg.com/*",
    "*://*.cdn.vine.co/*"
]

のように明確に分離されています(permissionsはhost_permissionsに、optional_permissionsはoptional_host_permissionsに、それぞれ対応しているようです)。
なお、declarativeNetRequestのルールが適用されるホストについては、ほとんどの場合はhost_permissionsによる許可の方は不要ですが、一部必要となる場合もあります(リダイレクト時・ヘッダ変更時など)

Action APIについての変更(browser_actionとpage_actionの統合)

Manifest V2にあった"browser_action"と"page_action"はV3では"action"に統一されているようです
manifest.json上では単に"browser_action"/"page_action"→"action"に変更・統合すればよいのですが、JavaScriptのソースコード中でchrome.browserAction/chrome.pageActionとなっている箇所を探して書き換えないといけないので、

chrome.browserAction.setIcon( { path : icon_path } );

( chrome.action || chrome.browserAction ).setIcon( { path : icon_path } );

若干手間ですね。

content_scripts等から拡張機能内のファイルにアクセスしたい場合は?(Web-accessible resourcesの指定方法変更)

Web-accessible resourcesの扱いも(manifest.jsonでの定義方法)も、Manifest V2とV3では異なっています
例えばV2だと

"web_accessible_resources" : [
        "scripts/*.js"
]

のように単にアクセス許可を出したいリソースのパスだけを指定すればよかったものが、V3だと少なくとも

"web_accessible_resources" : [{
        "resources" : [ "scripts/*.js" ],
        "matches" : [ "*://*.twitter.com/*" ]
}]

のように、どのサイトからアクセスが可能かを明示する必要があります。
他にもリソースにアクセスできる拡張機能をIDで指定できるなどの設定が追加されています(詳細は公式ページを参照)。

background内でwindowにアクセスできなくなった!

Manifest V3だと、background(Service Worker)ではDOMやwindowオブジェクトは存在しません(undefined)。

自分の場合、V2ではwindowはグローバルなオブジェクトとしての利用で、オプション画面(options_ui)に提供したい関数を生やす(options_ui側でchrome.extension.getBackgroundPage()によりbackgroundのwindowを取得)という使い方でした。
V3ではそもそもこういう使い方はできないのですが、ソースコードを共通化するための便宜上、V3でのグローバルなオブジェクトであるselfを暫定的に割り当てています。

((window) => {
// V2/V3共通のbackgroundの処理
})(
    (typeof window !== 'undefined' ? window : self)
);

当然ながらこの方法だとたとえばwindowサイズを知りたい等の用途では使えませんので、必要に応じてその都度別のやり方を検討する必要があります。

background内でlocalStorageが使えなくなった!

Manifest V2では、background内でlocalStorageが使えたので、オプション設定の記憶用等に使っていることもあるかと思いますが、これはV3では使えなくなってしまいます。
代わりにchrome.storage APIが使えますが、

  • localStorageは同期的に使えましたが、chrome.storage.localは非同期です
    これが面倒でいつまでも置き換えなかったツケが今回回ってきました…
  • 当然localStorageとの互換性はないので、既存のバージョンで保持していたオプション設定などはリセットされてしまいます
    丁寧にやるならlocalStorage→chrome.storage.localにデータを移行する処理を盛り込めばいいのですが……正直面倒なので拙作では手を抜きました……あしからず

ちなみに以前からそもそもbackground等でlocalStorageの使用は推奨されておらず、chrome.storageを使うようにという注意書きがどこかにあったかと思います
なお、chrome.storage APIを使うためには、manifest.jsonの"permissions"に"storage"の追加が必要です。

ちなみに、拙作拡張機能は該当しませんでしたが、background(Service Worker)ではsetTimeout()やsetInterval()等も使えないことにご注意を……代わりにchrome.alarms APIを使います("permissions"に"alarms"の追加が必要です)。
なお、こちらはV2でも"persistent": falseなbackgroundだと使えなかった気がします

オプション画面(options_ui)でbackgroundの関数が呼び出せなくなった!

Manifest V2だと、chrome.extension.getBackgroundPage()によりbackgroundのページ(windowオブジェクト)を取得することができ、その下の関数を呼び出したりもできたのですが、V3でbackgroundがService Workerに変わったことによりこれができなくなりました(chrome.extension.getBackgroundPage()でundifinedが返される)。
options_uiとbackgroundとの間でやり取りを行いたい場合には、

  • 機能を呼び出したい場合、chrome.runtime.sendMessageを使う
  • データを共有したい場合、chrome.storage APIを使う

のように、content_scriptsの場合と同じような仕組みに書き換える必要があると思われます(他によい方法があれば教えてください)。

HTTPヘッダの書き換えなどはどうなるの?(webRequestBlockingからdeclarativeNetRequestへ)

Manifest V3では、"webRequestBlocking"(ブロッキング機能付きのWeb Request API)の機能が廃止され、"declarativeNetRequest"(宣言的なNet Request API)へと置き換えられています
なお、"webRequest"の方はV3でも存在しており、例えばネットワークトラフィックをモニタする等の用途でならば使用できます

ざっくりというと、今まではbackground(JavaScript)内でHTTP Request HeadersやResponse Headersを任意の条件で動的に書き換えたりできていたものが(chrome.webRequest.onBeforeSendHeaders.addListener/chrome.webRequest.onHeadersReceived.addListener)、予め宣言された定義に基づいての書き換えとなる、ということですかね。

定義方法には静的なものと動的なものがあります。
詳細は公式ページを参照してください

NetRequestの静的な定義方法

manifest.json内で、"permissions"に"declarativeNetRequest"を追加の上で、

"declarative_net_request": {
    "rule_resources": [
        {
            "id": "ruleset",
            "enabled": true,
            "path": "ruleset.json"
        }
    ]
}

のように、ルールを記述したJSON(例ではruleset.json)のパスを指定しておき、そのJSON内でルールを定義する方法です。
ルールの定義例はこちら

NetRequestの動的な定義方法

manifest.json内では、"permissions"に"declarativeNetRequest"を追加するだけにとどめ、background(Service Worker)のソースコード内にて、ルールを定義したオブジェクトをchrome.declarativeNetRequest.updateDynamicRules()にて追加/削除する方法です。
具体例はこちら
なお、動的に追加された既存のルール(拡張機能の更新前に登録されたものも含む)に同じidのものが存在したりすると登録に失敗するようなので、追加したいルールと同じidをremoveRuleIds で指定しておくのが無難なようです(よりよい方法があれば教えてください)

Service Workerのエラーが確認できない!?

Manifest V3にしたら(backgroundがService Worker)、拡張機能のページ(chrome://extensions)で[エラー]となっているのに、これをクリックしても正しく表示されないし、「Service Worker (無効)」を開こうとしても開けない(ついでにいえば単に「(無効)」と出ているだけなので、未使用時にサスペンドされているだけなのか、エラーにより起動されないのか区別がつかない)という現象がたまに発生します。

どうも、Service Workerの起動(初期化)時にエラーが発生した場合などでこうなってしまうようです。
もっとも、「Service worker registration failed. 」のようにきちんとエラーが表示されることもあるので、いまひとつ発生条件がよくわかりませんが……

これを回避してエラー内容を確認するためには、

といった対処方法があるようです。

自分はとりあえず後者でしのいでいます。
[manifest.json]

"background" : {
    "service_worker" : "background-wrapper.js"
}

[background-wrapper.js]

try {
    importScripts('scripts/background.js');
}
catch (error) {
    console.log(error);
}

「Wrapperはmanifest.jsonと同じ階層に置く必要がある」とのことですが、それ以外の階層においたときにどうなるのかは未検証です(例えば"scripts/background-wrapper.js"のように違う階層においても、importScripts()するときのパスにさえ気をつければ通常の動作では問題無さそうではあるんですが)

onRuleMatchedDebugはデベロッパーモードによる開発時にのみ有効(Chrome ウェブストアからインストールすると動かない件)

Twitter メディアダウンローダのManifest V3対応版Chrome ウェブストア等にアップロードしたところ、なぜかそこからインストール(更新)した拡張機能が動作しない、という罠にはまりました

原因は、デバッグ用に設定していたchrome.declarativeNetRequest.onRuleMatchedDebug.addListener()が、実はローカルで[パッケージ化されていない拡張機能を読み込む]により読み込んだ拡張機能でしか機能しない(Chrome ウェブストアからインストールした場合にはonRuleMatchedDebugはundefinedになってしまう)というものでした。
その時点でエラーになることでService Workerの実行が中断されて拡張機能が動作しない&wrapper設定前だったのでエラーの確認もできないという状態

これ、たしかに公式ページにも

Fired when a rule is matched with a request. Only available for unpacked extensions with the declarativeNetRequestFeedback permission as this is intended to be used for debugging purposes only.

https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#event-onRuleMatchedDebug

としっかり書かれているので、確認しなかった自分が悪いんですよ、ね……。

実運用としては、

のがよいかと思われます。

manifest.json上の"permissions"と、インストール時に確認される権限の関係について

Manifest V3に対応した拡張機能だと、更新時に無効化されて、再度有効化しようとすると追加で権限の確認をされることもあるかと思います。
今回自分が遭遇したパターンだと、

permissions(manifest.json) 権限(拡張機能) Permissions(Extension)
declarativeNetRequest 任意のページのコンテンツをブロック Block content on any page
declarativeNetRequestFeedback 閲覧履歴の読み取り Read your browsing history

が該当しました。
これ、V2のときにwebRequest/webRequestBlockingで実現していたのと同じようなことをやっているだけであっても、V2のときだと拡張機能のインストール時や更新時には権限として特になにも表示されなかったのに、declarativeNetRequest*にした途端、表示(権限の確認)がされるようになってしまいます。

まぁ、それだけセキュリティ的に厳しくなったということなんですが……正直、

  • 最初のインストール時→ほとんどの人が警告の中身を見ないでOKしてるんじゃないの?
  • 更新時→追加で権限を求める警告がでるので、そこで初めて気づくユーザーも多いと思うんだけど……これ、いたずらに不安を煽るだけになってない?
    更新したことで表示された=悪意がある機能が追加された、と思われて開発者に対する悪印象も植えつけられてしまう可能性

とも思えてしまい、どうにもモニョる……というのはここだけの話です……。
かといってより適切な注意喚起の仕方を思いつくわけでもないのが辛いところ……

その他(覚書)

拙作拡張機能では未対応もしくは対応する必要がなかったポイントについて覚書程度にメモしておきます。

Service Workerは「無効」時には破棄されてしまう

しばらく動いていないService Workerは「無効」化状態になります(破棄されます)。このとき、グローバル変数等も初期化されてしまうために、消えてほしくないデータに関してはchrome.storage APIを使うなりして保持しておく必要がある模様です。
拙作拡張機能は現時点(2022年10月初旬)で一部未対応(オプション変更時に把握可能なTwitterのページなどをリロードさせる処理など)

CSP(Content security policy)についても変更がある

CSP(Content security policy)のmanifest.jsonでの指定方法等も変更になっており、またsandbox用のオプションが追加されているようです
幸いにして(?)拙作拡張機能ではCSPを指定したものは無かったので、深く調べること無くスルーしてしまっています💦

リモートコードの制限

Manifest V2では可能であった、リモートでホストされている(拡張機能のパッケージに含まれていない)ソースコードを読み込んでの実行はできなくなっています
これはTampermonkey等のユーザースクリプトマネージャーにとっては致命的な制限となりえます。
どうやらGoogle側も対応予定ではあるようですが、2022年10月初旬時点ではどうなるのか不透明です。
単に自分が認識できていないだけですでに対応されているのかも知れませんが……

任意コード実行の制限

Manifest V2では可能であった、任意のコード文字列の埋め込み(実行)も、Manifest V3では制限が厳しくなっています
具体的にはmanifest.jsonの"permissions"に"scripting" 権限を追加した上で、chrome.scripting APIの仕様に従って、staticなファイルや任意のfunctionの挿入を行う必要があるようです。

「LOVE iLLUSiON #502 Ver.」歌詞 (「冴えない彼女の育てかた Fes. Fine ~glory moment~」特典CDより)

LOVE iLLUSiON #502 Ver.

  • 作詞: 稲葉エミ
  • 作曲: 奥井康介
  • 歌: 加藤恵(安野希世乃)/澤村・スペンサー・英梨々(大西沙織)/霞ヶ丘詩羽(茅野愛衣)/氷堂美智留(矢作紗友里)/波島出海(赤﨑千夏)
歌詞 英梨々 詩羽 美智留 出海
カシマシ 神絵師 自画自賛も才能 🌸
(Come on baby! "Illustrator") 🌸
度肝抜く 起承転結 本望だわ 🌸
(Gyu Gyu Bang! Gyu! "Novelist") 🌸
振り返ると なんだかなぁだよね 🌸
(Come on baby! "Main Heroin") 🌸
ともあれ それぞれ 笑えたら成功! 🌸 🌸 🌸 🌸 🌸
(Take a chance! 3, 2, 1, Let's go!) 🌸 🌸 🌸 🌸 🌸
凄腕の社長? 🌸
はたまた 命知らず? 🌸
いいえ ただのオタクだわ 🌸
うっかりここまできたね 🌸
人生ってわからないね 🌸
結局 もう とことんやるだけ 🌸 🌸 🌸 🌸 🌸
(Go Go Let's go! せーの!!) 🌸 🌸 🌸 🌸 🌸
妄想癖(クリエイティブ)にゴールはない 🌸 🌸 🌸 🌸 🌸
新たな勘違いの幕開け 🌸 🌸 🌸 🌸 🌸
おんなじとこクルクルリ 🌸 🌸 🌸 🌸 🌸
遠回りしても全員集合 🌸 🌸 🌸 🌸 🌸
なんだろこの感じ 🌸
愛かな? 🌸
ヘンな集まりかな? 🌸
夢のツヅキを見よう 🌸 🌸 🌸 🌸 🌸
みんなそう自称天才 🌸 🌸 🌸 🌸 🌸
あっぱれ☆LOVE iLLUSiON 🌸 🌸 🌸 🌸 🌸
画力と魅力を磨きますキュッキュッキュッ 🌸
(Oh my Darling! "Illustrator") 🌸
聴いてよ新曲 ギター背負(しょ)って暴走 🌸
(Ja Ja Jan! Ja Ja Ja! "Singer") 🌸
いつか拝みたい私たちルート 🌸 🌸
(Oh my Darling! Don't let me down!) 🌸 🌸
みんなで歌えばコワくないキャラソン 🌸 🌸 🌸 🌸 🌸
(Take a chance! 3, 2, 1, Let's go!) 🌸 🌸 🌸 🌸 🌸
好きをつらぬくと 🌸
決めたあの日の未来 🌸
なぜ? オタクの代弁者 🌸
忙殺の現場じゃ所詮 🌸
「かく」か「かかされる」か 🌸
やめて! 完徹のドーパミン 🌸 🌸
(Go Go Let's go! せーの!!) 🌸 🌸 🌸 🌸 🌸
二次元(ファンタズム)をこじらせて 🌸 🌸 🌸 🌸 🌸
ひょっとして型破りの距離感 🌸 🌸 🌸 🌸 🌸
煩悩だらけチラチラリ 🌸 🌸 🌸 🌸 🌸
呼んでもないのに全員集合 🌸 🌸 🌸 🌸 🌸
言っちゃおー アイシテルヨ 🌸
ギャグか バグか はったりかな? 🌸
一緒に悩んで進もう 🌸 🌸 🌸 🌸 🌸
毎日が最高傑作 🌸 🌸 🌸 🌸 🌸
どんだけ☆LOVE iLLUSiON 🌸 🌸 🌸 🌸 🌸
出会った日から 🌸 🌸
今日まで過ごした 🌸 🌸
言葉にできない 🌸 🌸
想い集めて 🌸 🌸
モノヅクリをしよう 🌸 🌸 🌸 🌸 🌸
安心してね ここにいる 🌸
想像を超えた絵を描きまくる 🌸
官能寄りの感動でfix 🌸
アイディア持ち寄り 🌸 🌸
全員集合 🌸 🌸 🌸 🌸 🌸
妄想癖(クリエイティブ)にゴールはない 🌸 🌸 🌸 🌸 🌸
新たな勘違いの幕開け 🌸 🌸 🌸 🌸 🌸
挑戦状をギラギラリ 🌸 🌸 🌸 🌸 🌸
握りしめたら全員集合 🌸 🌸 🌸 🌸 🌸
なんだろこの感じ 🌸
愛かな? 🌸
ヘンな集まりかな? 🌸
夢のツヅキを見よう 🌸 🌸 🌸 🌸 🌸
みんなそう自称天才 🌸 🌸 🌸 🌸 🌸
あっぱれ☆LOVE iLLUSiON 🌸 🌸 🌸 🌸 🌸
合格☆ 🌸
LOVE iLLUSiON 🌸 🌸 🌸 🌸 🌸

「冴えない彼女の育てかた Fes. Fine ~glory moment~」特典CD 収録

備考

歌詞を確認したくて「LOVE iLLUSiON #502 Ver.」で検索してみたら出てこなかったので、たまたま以前書き起こしていたものを貼り付けただけです。
なお、同じ特典CD内には「LOVE iLLUSION (Michiru & Izumi Ver)」も収録されているんですが、そちらは書き起こしていません……あしからず。

Power Queryのデータソースを間接的に(相対パスで)指定する場合のプライバシー保護に関するリスクについて

Power Queryにおいて、CSVファイル等のデータソースを間接指定する(相対パスを使う)ことはプライバシー保護上のリスク(情報漏えい)が発生しうるために一般には推奨されないという話題をみかけたのですが、では「どうして間接指定をすると(相対パスを使うと)リスクが発生するのか?」その理由について、簡単な具体例を交え備忘録として記事にしておきます。
また、Power Queryにおけるプライバシーレベルに関して、望ましいと思われる設定(ポリシー)についても自分が把握できた範囲で言及してあります。



概要

Power Queryを用いてCSVファイル等からデータを取得する場合には通常、データソース(対象ファイルの場所)を絶対パス("D:\Path\To\Data.csv"のような形)で直接指定する必要があります。

これだと、たとえばAさんが使っていたエクセルファイルをBさんのPCに持っていくようなケースでは、Bさんの環境にあわせてパスを書き換える必要があったりして不便なため、
データソースとなるファイルは、Power Queryを設定したワークブック(エクセルファイル)と同じ場所に置く
といったルールを定めた上で、
データソースをワークブックからの相対パスで間接的に指定
したくなります。
対象のファイル名("Data.csv"のようなもの)だけを決めておいて、ThisWorkbook.Path & "\Data.csv"のようにして組み立てたパスをPower Queryに渡してやるイメージです

実際、例えばこちらの記事に書かれているように、CSVファイル等のデータソースのパスをワークブックの名前定義を介してPower Queryに受け渡す手法等を用いて、間接的に指定することは可能です。

ワークブックと同じところに置かれたCSVファイル(例えば商品マスターと売上データ)のみをPower Queryで読み込んだり結合等の加工を行うといった用途なら、外部とのやり取りは発生しないため、このような手法(間接指定)をとっても特に問題は生じないと考えられます。
もしこのケースでもリスクが発生しうるということであれば、ご指摘願います

ところが、この手法を用いると、
データソースに対するプライバシーレベルを直接設定することができない
という制限が生じます。

この際、Power Query エディターの「データソース設定」を見ると

⚠ 手動で作成したクエリのため、一部のデータ ソースが一覧に表示されない可能性があります。

のように表示されますので、データソースが動的に変更されること自体は予め想定されており、その上で注意を促しているようですね

プライバシーレベルを直接設定できないデータソースが存在すると、例えばその中に含まれるデータを元にして異なる場所(信頼できる領域の外)にあるデータベース等の別のデータソースとの間でデータをマージ(結合)したい場合には

  1. プライバシーレベルのチェックを無視する設定にする
  2. 全てのプライバシーレベルを「パブリック」にする
  3. ワークブックおよびデータソースが存在するフォルダ/ドライブのプライバシーレベルを「組織」に設定する(後述)

のいずれかにする必要が出てきます。

このとき、安易に1.や2.の手段をとってしまうと、当該データソースの情報が外部に漏出してしまう可能性が出てきてしまうため、特に機密情報等が含まれていた場合には深刻なリスクとなってしまいます

不適切なプライベートレベルの設定に伴うリスク

フォルダ/ドライブに対するプライバシーレベル設定について(2022/01/19追記)

CSVファイル等のデータソースをワークブックと同じところに置いているという条件ならば、
ワークブックと、それが置かれているフォルダ/ドライブのプライバシーレベルを「組織」に設定する
ことで、プライバシーレベルが「パブリック」のデータベース等への情報送出は制限しつつ、データのマージが可能なようです(詳細は後述)
組織内で適切な運用ができるのであれば、間接(相対パス)指定しているデータを元にパブリックなデータソースとマージすることも選択肢として取りうるのかも知れません。
この場合に別のリスク等が発生しうるのかについてまでは検証できていませんが、少なくとも本来「プライベート」に設定すべきデータソースに関しては、間接(相対パス)指定は避けるべきではあるでしょう

プライバシー保護(情報漏えいの防止)の観点からの望ましい設定と運用(ポリシー)(2022/01/19追記)

調べた範囲で、 プライバシー保護(情報漏えいの防止)の観点から望ましいと思われるポリシーについて記載しておきます。

クエリのオプションのグローバルプライバシーレベルの設定

データ>データの取得>クエリオプション>グローバル>セキュリティにおいて、プライバシーレベル

グローバルのプライバシーレベル

の設定を

  1. 常に各ソースのプライバシー レベル設定に従ってデータを結合します
  2. 各ファイルのプライバシー レベル設定に従ってデータを結合します

のどちらかにしておきます(「常にプライバシー レベル設定を無視します」は選択しないようにします)。
なお、1.と2.の違いですが、1.を選択しておくと、現在のブック>プライバシーでは設定がグレーアウトされ(プライバシーレベルを無視する設定にできない)、個別のクエリについても「プライバシーレベルのチェックを無視…」にチェックを入れて保存したとしてもこれは無効化されるようです。

クエリのプライバシーレベルは適切な設定を!

クエリのマージを行う場合などには、必要に応じてプライバシーレベルの設定ダイアログが表示されます。

プライバシーレベルの設定ダイアログ

これは、Power Queryが
「データの一部(照合列として指定した列の情報など)を使って問い合わせればクエリの効率化はできるけれど、そうするとその情報を相手に渡さないといけないから、プライバシー上問題がある(データが漏洩する)可能性がありますよ、どうしますか? 」
と尋ねてきているイメージです。
逆に、上記のグローバルプライバシーレベルが適切に設定されていて、かつ、プライバシーレベルの設定ダイアログが出ないようなら、当該クエリはプライバシー保護の観点からは問題ないものと判断できると思われます

このときには、適切なプライバシーレベルを判断して、保存するようにします。

参考として、プライバシーレベルの組み合わせによる情報送信の有無は次のようになるようです
クエリのマージで結合の種類を「左外部 (最初の行すべて、および 2 番目の行のうち一致するもの)」にした場合
情報送信を「する」となっている組み合わせでは、1番目に指定しているデータソースの情報が送られて問題が無いものかどうか、よく注意する必要があります。

1番目(左(上)側) 2番目(右(下)側) 情報送信 クエリ最適化
プライベート プライベート しない しない
プライベート 組織 しない しない
プライベート パブリック しない しない
組織 プライベート しない しない
組織 組織 する する
組織 パブリック しない しない
パブリック プライベート する する
パブリック 組織 する する
パブリック パブリック する する

特に、

  1. 「このファイルのプライバシー レベルのチェックを無視します」にはチェックを入れない
    プライバシーレベルのチェックは無視しない
  2. 全てを「パブリック」にするといった安易な設定は行わない
    安易にすべてパブリックにしたりしない

ように留意しましょう。

データソースを間接指定することの是非

データソースを相対パスで間接指定すること自体が問題と言うよりも、Formula.Firewallの警告が出てしまうなどしてクエリが動作しないからといって、安易にプライバシーレベルのチェックそのものを無効化したり、すべてをパブリックに設定してしまうようなユーザーの行動(およびそのような設定をリスクの説明をすることなく推奨する記事等)の方に問題があると考えられます。

上述したポリシーを遵守した上で、

  • ワークブックと同じ場所に置いたCSVファイル等のデータソースのみを扱うケース
  • 「組織」(ワークブックと間接指定したデータソースを置くフォルダ)と「パブリック」(外部データソース)のふたつのプライバシーレベルのみで運用可能なケース(※留意点はこちら

であれば、とくに問題はないように思えます。
他にリスクなどが発生するケースがあればご指摘ください


ここからは具体例等です、詳細を知りたい方は御覧ください

リスクの生じうる具体例

前提

プライベートな(ローカルのストレージ上にあって、機密情報を含むような)CSVファイルと、パブリックな場所にあるデータベース上のデータを、Power Queryでマージしてテーブルデータを作成するような場合を想定しています。

シチュエーション

データソースとしては、CSVファイル(202201.csv)と

CSVファイルから読み込んだデータ
データベースのテーブル(t_holiday)
データベースのテーブルから読み込んだデータ

とがあり、これを日付を照合列としてマージし、
マージ設定
一つのテーブルにします。
マージされたデータ
単なる例ですので、データの内容に意味はありません、念のため。

Power Queryのクエリのオプション(データ>データの取得>クエリ オプション)として、プライバシーレベルは「● 各ファイルのプライバシー レベル設定に従ってデータを結合します」の状態になっているものとします。

グローバルなプライバシーレベルの設定

また、各データソースについては、初期のプライバシーレベルは「なし」になっているものとします。

CSVファイルのプライバシーレベル(初期状態)

データベース上のテーブルのプライバシーレベル(初期状態)

絶対パスで指定する場合(直接指定)

CSVファイル用のクエリ

CSVファイル用のクエリ(直接指定)

マージに伴うプライバシーレベルの設定

CSVファイルのクエリとデータベースのクエリとをマージしようとすると、次のようなプライバシーレベルに関するダイアログが表示されました。

プライバシーレベルのダイアログ

これを適切に設定し(CSVファイルについては「プライベート」、データベースについては「パブリック」)、

プライバシーレベルの設定

保存すると、無事目的のテーブルを得ることができました。

相対パスで指定する場合(間接指定)

CSVファイル用のクエリ

ワークシート上と名前定義で次のように間接的にCSVファイルの場所(DateListFilePath)を指定できるようにし、

シートと名前定義の状態

定義した名前をPower Queryのクエリ中で指定しています。
CSVファイル用のクエリ(間接指定)

マージに伴うプライバシーレベルの設定

CSVファイルのクエリとデータベースのクエリとをマージしようとすると、直接指定のときと同じようにプライバシーレベルに関するダイアログが表示されますが、内容が若干異なっていました。

プライバシーレベルのダイアログ
「現在のブック」というものが増えていました。
とりあえず、現在のブックとd:\については「プライベート」、データベースについては「パブリック」に設定して
プライバシーレベルの設定

保存してみたのですが、
Formula.Firewallの警告

Formula.Firewall: クエリ '202201' (ステップ '変更された型') が、同時に使用できないプライバシー レベルの複数のデータ ソースにアクセスしています。このデータの組み合わせを再構築してください。

のような警告が表示されてしまい、テーブルを取得することができません。

また、この状態でデータソース設定を開いてみると、

間接指定時のデータソース設定

手動で作成したクエリのため、一部のデータ ソースが一覧に表示されない可能性があります。

なる警告が表示されています。

やはり、データソースを間接設定すると、当該データソースに対してはプライバシーレベルの設定ができず、このままでは他のデータソースとのデータのマージ(組み合わせ)ができなくなってしまうようです。

間接指定の場合にはプライバシーレベルのチェックを無視する必要あり

先の警告が出たままだと何も出来ないので、とりあえず、一旦データソース設定をもとに戻してみました(すべてのデータソースについてプライバシーレベルを「なし」に変更)。すると、マージしたクエリの警告が

データのプライバシーに関する情報が必要

データのプライバシーに関する情報が必要です。 [続行]

のように変わりました。

[続行]を押すと、改めてプライバシーレベルの設定ダイアログが表示されました。

プライバシーレベルのダイアログ

今度は、

プライバシーレベルのチェックを無視

☑ このファイルのプライバシー レベルのチェックを無視します。プライバシー レベルを無視すると、未承認の人に機微または機密の情報が開示される可能性があります。

の設定にチェックを付けた状態で保存してみます。
すると、ようやく無事に(?)目的のテーブルを得ることができました。

ちなみに、上記のチェックをしなくても全てのプライバシーレベルをパブリックにすることでも

プライバシーレベルを全てパブリックに設定

目的を果たせはしますが、実質プライバシーレベルのチェックを無視するのと変わらないですよね。

直接(絶対パス)指定時と間接(相対パス)指定時のデータの流れの違いと間接指定時のリスクについて

プライバシーレベルのチェックを無視するようにすると、 別のデータソースのあるパブリックな場所にプライベートな(機密情報等を含んだ)データが送られてしまうリスクが有ります。
例えば「SELECT * FROM <パブリックなDB上のテーブル> WHERE secret = <プライベートなデータの照合列の値(機密情報)>」のようなSQLクエリをパブリックなデータベースに対して発行するイメージです

上記の具体例において、直接(絶対パス)指定時と間接(相対パス)指定時にはデータの流れの観点からは次のようになっていると推測されます。

直接指定時

直接指定時(プライバシーレベルを適切に設定した場合)

クエリのオプション>現在のブックのプライバシーレベル

● 各ソースについて、プライバシー レベル設定に合わせてデータを結合します

プライバシーレベルが適切に設定されていれば、機密情報等を含んだデータを不用意に漏洩する危険はなくなります。
ただし、データをマージするためにはいったん全てのデータを取得しておく必要があるため、パフォーマンス的には低下してしまう傾向にあると推測されます(適宜バッファリングはされるようですが)。

間接指定時

間接指定時(プライバシーレベルのチェックを無視しないと動作しないためこうならざるを得ない)(プライバシーレベルのチェックを無視するか全てパブリックにした場合)

クエリのオプション>現在のブックのプライバシーレベル

● プライバシー レベルを無視すると、パフォーマンスが向上する場合があります

データソースのプライバシーレベルを設定することができないため、別のデータソースとマージするためには、プライバシーレベルのチェックを無視する(もしくは全てのプライバシーレベルをパブリックにする)しか有りません→追記:別の回避策もあるようです(後述)
そうすると、必要に応じて別のデータソースにデータが送られることになるため、機密情報等を含んだデータを扱う場合にはリスクが存在することになります。
DBから必要なデータのみ(例ではCSV上に存在する日付だけ)を取得するクエリが発行されることでパフォーマンス的には向上する傾向はあると推測されますが、情報漏洩のリスクとトレードオフの関係にあることに注意が必要です

間接指定時にもプライバシーレベルをうまく設定すれば大丈夫かも?(2022/01/19追記)

はけた@excelspeedupさんよりご指摘があり、

試してみると確かに、間接(相対パス)指定時でも例えば

プライバシーレベルを適切に設定すれば間接(相対パス)指定でもうまくいく(ブックがあるフォルダを指定)

もしくは
プライバシーレベルを適切に設定すれば間接(相対パス)指定でもうまくいく(ドライブ全体を指定)

のように、

  • 対象のワークブックのプライバシーレベルを「組織」に
  • 対象のワークブックが含まれているフォルダ/ドライブ全体(同じフォルダ下にデータソースであるCSVファイルも含まれる前提)のプライバシーレベルを「組織」に
  • 外部のデータベースのプライバシーレベルを「パブリック」に

であれば、プライバシーレベルのチェックを無視せずとも正常にテーブルがマージされました。

なおこの場合(ブックとCSVファイルを)移動やコピーしたり、他の人のところにコピーしたりすると、再度プライバシーレベルの設定が必要になることがあります。

その場合でも、クエリの更新時に出る

プライバシーレベルの設定を促すダイアログ

のようなダイアログにおいて、移動先のフォルダ/ドライブのプライバシーレベルを「組織」に設定するだけですので、ルールをきちんと決めておけばコピー/移動する毎にデータソースを指定し直すよりも運用が楽かも知れません。

留意点

間接(相対パス)指定時にはそのデータソースのプライバシーレベルを「プライベート」にすることは実質的にできないため、少なくとも Microsoftサポートのプライバシー レベルの設定 (Power Query)の項目に書かれている

データソースのプライバシーレベルを設定する
「プライベート」に該当するようなデータソースについては、(それ以外のセキュリティレベルのデータソースとマージ等をする可能性がある場合には)間接指定はしないようにするべきでしょう。

参考

エクセルでピザカッター!(画像を等しい角度で分割)

PowerPointで、丸い画像を強引に等分する方法というツイートをみかけて、ふと「エクセルでもできるんだろうか?」と思い、試してみたくなったしだいです。
残念ながらPowerPointのように手作業でも簡単にできる方法は思いつかなかったためVBAの力を借りておりますが……
動作イメージはこんな感じです*1



手っ取り早く試してみたい方へ

マクロ有効ワークシートを共有してありますので、ダウンロードしてお試し下さい。
drive.google.com
ソースコードだけ見たい方は、こちらに晒してあります

きっともっと簡便な方法もある気がするので、アドバイス歓迎です!

備忘録

はっきり言って役に立つのか何なのかという一発ネタな感じですが、作成過程で何回もはまりいくつか知見を得られましたので、おおよそはまった気づいた順に書き留めておきます。

画像はなべて96dpi

いくつかの画像をエクセルに挿入して試していたところ、元の画像のサイズ(ピクセル)通りに表示されるものと(図の書式設定でサイズをリセットしても)異なった大きさで表示されるものとがありました。
エクセルに画像を挿入する場合は元のラスタ画像ファイル(JPEG・PNG等)は解像度を96dpiで保存しておくのが良いようです。
画像を挿入した後、図の書式設定でサイズをリセット(高さ/幅の倍率を100%にする)することで元の画像と同じ大きさにできるので、その後の調整も楽になります*2

f:id:furyu-tei:20210530114458p:plain
エクセルに挿入する画像は解像度によって見え方が異なる
画像を図形でトリミングする方法

エクセルに挿入した画像は、これを選択しつつ「図の形式(Alt+JP)→トリミング(V)→図形に合わせてトリミング(S)」から図形を選ぶことで、選んだ図形でトリミングすることができます。
f:id:furyu-tei:20210530125901p:plain
また、先に図形を挿入しておいてこれを選択しつつ「図形の書式(Alt+JD)→図形の塗りつぶし(FS)→図(P)」から画像ファイルを指定して塗りつぶすことにより、図形の形に合わせて画像をトリミングすることもできます。
f:id:furyu-tei:20210530125917p:plain

手動でやる分にはどちらでも構いません。
前者の方が直感的で、いちいち画像ファイルを選択する必要もなく(一度画像ファイルを挿入しておけば後はコピペで済む)デフォルトの塗りつぶしや枠線に煩わされることもないのでおすすめでしょうか。

今回はVBAでやるためにコピー&ペーストを極力使いたくなかったので(クリップボード周りの機能は結構な頻度で失敗するため処理が煩雑になりがち・後述)後者の方法を用いています。
このため、指定したパスに実際に画像ファイルが存在する必要があります
【追記】
最初にシート間で一回だけコピー&ペーストしておけば後はShape.Duplicateで複製できることに気づいたので、マクロ有効ワークシートの方でも前者の方法に変更してあります。

#Const DuplicatePictureToMakePiece = True ' True: 元の画像を複製 / False: 1ピース毎に画像読み込み

のTrueをFalseに変更すれば、後者の方法にも戻せます。

トリミングの位置調整はむずかしい

任意の矩形を取るラスタ画像(円形に見える画像も実際のデータは通常は矩形)に対応させたかったので、矩形の外接円(半径が\frac{\sqrt{短辺^2+長辺^2}}2)を考えて、これを等しい角度の部分円でトリミングしていくことで分割する方法を取ることにしました。

とりあえず部分円のサイズを計算して調整した上で画像を挿入すると、直後は下記のようになります。
f:id:furyu-tei:20210530131933p:plain
どうやらデフォルトは図形にフィットするように画像がサイズ調整されるようですので、図の書式設定→画像の位置で幅・高さを元の画像と同じにしてやります。
f:id:furyu-tei:20210530132345p:plain
うまく切り取られているように見えます。お、これなら簡単にいくんじゃない……? と思っていた頃が自分にもありました。

同じ操作を180°よりも小さい角度の部分円でやってやると……
f:id:furyu-tei:20210530135033p:plain
こうなります……おや、画像の表示位置が……?(わかりやすいように期待している位置を半透明で重ねています)
どうやらトリミングのときには、デフォルト(トリミングの画像位置の横方向/縦方向に移動の値が0cm)では画像の中心を図形の見えている部分(部分円だと扇型の部分)を囲む矩形の中心に合わせるようになっているようです。
つまり、図形の見えている部分(扇型の部分)の表示範囲を計算して、それに合わせて画像位置の横方向/縦方向に移動の値を調整してやる必要があったのです……め、面倒くさい……。
なんでや……図形の大きさ自体は変わってないんやし、図形の中心でええやないか……せめてそっちの設定を選ばせてくれ
f:id:furyu-tei:20210530135428p:plain

部分円を囲む矩形の求め方(三角関数なんて忘れていたかった)

部分円(扇型)を囲む矩形か……とりあえず扇形の外周上の座標X・Yそれぞれの最大値と最小値を求めればよいのだろうけれど、円弧の部分はどうすれば……? ある角度範囲をとるときのsinとcosの最大値・最小値を求める必要がありそう?🤔 そんなの、数学が苦手な自分にできるわけないだろっ!😫
最初は力技で(角度を少しずつ変化させていって最大値・最小値を取得)やっていましたが、いき.xls[互換モード]@aero_iki さん助けを求めていろいろとアドバイスをいただきつつ、

なんとか解決にいたりました。

いき.xls[互換モード]さん、ありがとうございました!

図形のままだとシートの端には寄せられない

ここまでで、なんとか画像を分割できるようになりました。
このままでもよかったのですが、図形のままだと
f:id:furyu-tei:20210530143234p:plain:w350
のように、見た目には余白があるにも関わらず実際の図形はより大きい領域を占めているため、シートの端の方に寄せられない状態になります(図形や画像の座標はシートの左上が原点となっており、負の値は指定できません)。
図形のままで任意の矩形にトリミングできればよいのですが、たぶんできません(もし方法があれば教えて下さい)。

図形を画像に変換するときの問題点

そこで、図形を画像に変換してから矩形でトリミングすることを考えたのですが。
図形を選択してコピー→図 (PNG) として貼り付けると……
f:id:furyu-tei:20210530144020p:plain
のように、見えている部分だけが残る形で画像化されてしまいます。
え、待って……これってまた面倒な座標計算をしないといけない予感?🤔

そろそろ疲れていたので💦「見えている部分だけが残るのなら……残したい部分を見えるようにすればいいじゃない!」という発想のもと、図形の正方形/長方形を基準となる外接円と同じ高さ&幅にして挿入、部分円に重ね合わせてグループ化したものをコピー→図(PNG)として貼り付ける手段を取りました。
f:id:furyu-tei:20210530145329p:plain
実際の処理では矩形は透明にしていますが、矩形の場合はその状態でコピペしても元と同じ大きさとなりました

あとは、元の画像の矩形とそれぞれの部分円が重なる領域が収まる矩形領域で(実際には先の部分円の円弧座標の最大・最小を求める処理と同じ処理で、元画像の矩形と円弧の重なり部分の座標の最大・最小もいっしょに計算しています)画像を切り取れば(ShapeのPictureFormatでCrop◯◯を設定)完成です。
f:id:furyu-tei:20210530152824p:plain
まぁ画像に変換してしまうと今度は透明部分もクリックできてしまうので、これはこれで操作しづらいのですが💦

追記:トリミングしても元画像のデータは残る

「完成です」と書いておいてあれですが💦ひとつ忘れていました。
画像をトリミング(ShapeのPictureFormatでCrop○○を設定)した場合ですが、この状態で画像を選択して「図の形式(Alt+JP)→トリミング(V)→トリミング(C)」してみるとわかるように、
f:id:furyu-tei:20210531212058p:plain
切り取られた部分(灰色になっている箇所)もデータとしては残ってしまっています。

手動でなら「図の形式(Alt+JP)→図の圧縮(M)」を選択することで
f:id:furyu-tei:20210531212304p:plain
のようなダイアログが表示され、「☑ 図のトリミング部分を削除する(D)」という便利な機能も使えるのですが、残念ながらVBAからはうまく使えません。
一応「Application.CommandBars.ExecuteMso "PicturesCompress"」を実行すればダイアログが呼び出せるので、SendKeysと組み合わせることで圧縮できなくもないのですが、オプションの選択がやりにくかったりときどき失敗する等、使い勝手がよくありません

どうしようかと思ったのですが、図形を画像に変換するときの問題点ではやっかいだった「コピー→図 (PNG) として貼り付け」をもう一回行えば、見えている部分だけが残る形で画像化されるんだからこれでいいじゃない! ということに気が付きました。
実際に試してみると……
f:id:furyu-tei:20210531213302p:plain
「コピー→図 (PNG) として貼り付け」した画像(右側)はトリミングで見ても余分な箇所は残っていません。
これで無事、みかけだけではなくてちゃんとトリミングされた画像にすることができました。

VBAでクリップボードが絡む処理は鬼門

図形を画像に変換する際には、Shapeの.Copy&Worksheetの.PasteSpecialを実行しているのですが、これをそのまま行うと高確率で.Copyもしくは.PasteSpecialのいずれかでエラーが発生してしまいます。
場合によってはエラーが発生していないのにも関わらず、画像が正常に貼付けされないケースもあります。

これはコピー→ペーストの過程で行われているクリップボードの操作で発生する問題のようです。

踊るエクセル@ExcelVBAerさんのアドバイス


に参考にクリップボード関係のWinAPIを使い、クリップボードのクリア→コピー→データがクリップボードに入ったことを確認→ペースト…という手順(エラーが発生した場合はリトライ)にしてみました。
踊るエクセルさん、ありがとうございました!
追記:記事作成後のアドバイスも追加しています

ただ、いかにも冗長な処理だし、これが適切な処理なのかも自信がないしで(クリップボード関係のWinAPIをよくわかっていない)もやもやします。よりよいやり方があれば教えて下さい。
標準のメソッドを使っているのにクリップボード関係の処理は高頻度に失敗するというのがそもそも納得いかないというのもありますが

Twitter で「work」ドメインの記事を引用するとツイートが表示されなくなってしまう件

このブログは「memo.furyutei.work」のように、gTLDとして「.work」を使用しているわけですが。
Twitter にて「.work」ドメインの記事(URL)を引用してしまうと、状況によってはそのツイートが表示されないといった不具合が発生してしまう、というお話です(2021/02/11現在)。




不具合の内容

元ツイートが「.work」ドメインの記事を含む場合

「.work」ドメインの記事を引用してツイートした場合、そのツイートにリプライが付くと、元ツイートを開いたらリプライツイートも表示されます。
ここまではいいのですが、実は、

  • リプライツイート(およびリプライツリーで繋がったツイート)を開くと、(自分以外のユーザーからは)元のツイート(リプライ先・「.work」ドメインの記事を引用したもの)は見えず、「このツイートは表示できません。This Tweet is unavailable. )」状態となってしまう

という不具合が発生します。
これ、自分で見る分には普通に表示されるため、誰かに指摘されたりしないと気が付かない罠

リプライツイートが「.work」ドメインの記事を含む場合

また、Twitter のツイートに対するリプライ中に「.work」ドメインの記事(URL)を引用してしまうと、

  • 元ツイートのユーザーには通知が行かない
  • (自分以外のユーザーが)元ツイートを表示しても、「.work」ドメインの記事を引用したリプライツイートは見えず、リプライ数としてもカウントされない
    この場合も、自分で見る分には普通に表示されているので気が付きにくい
  • 当該リプライツイートにさらにリプライが付いた場合、当該ツイートは「このツイートは表示できません。This Tweet is unavailable. )」状態となってしまう
    もちろん自分では普通に見える

という不具合が発生してしまいます。
なお、 (自分以外のユーザーであっても)当該リプライツイートを直接URLを指定して開けば見ることはできます。

自分のところだけではないらしい

最初、自分のところだけかと思いましたが、gTLD「.work」を使っているサイトを3か所ほど探して試してみたところ、いずれも同様の不具合が発生していたので、少なくとも「.work」に関しては普遍的に発生するのではないかと予想しています。
他の TLD で同様の不具合が発生するかどうかまでは調べられていません。
「.work」は新gTLDと呼ばれる比較的歴史の浅いものらしいですが、そういうのが関係していたりするのですかね?

回避方法

現状、「.work」ドメインの URL は引用しないようにするしか……。
このブログの場合、もともとはてなブログなので「furyu.hatenablog.com」の方の URL を使って引用する、という手はないではないです(面倒くさいけれど……)。

Twitter さんに不具合報告はしているのだけれど


2020/12/03・12/09、2021/02/09 と、これまで都合3回この不具合に関して報告しているのだけれど、なしのつぶて……(2021/02/11現在)。

追記


やっぱりTwitterさんにスパム判定されているってことですかねぇ。
安さで判断したのが悪いって? さすがにこういう「安かろう悪かろう」は納得いかないのですが……。