風柳メモ

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

セルの右横にユーザーフォームを出したかっただけなのに……

軽い気持ちというかほとんどネタでVBAのユーザーフォームを用いたなんちゃってモダンなDatePickerを作ってみたのですが、その際に「セルの右横にユーザーフォームを表示する」という一見簡単そうなことが一筋縄ではいきませんでした。

試行錯誤の結果、ひとまずは実用にできそうな手法がわかったので、備忘として記事にしておきます。

セルの右横にユーザーフォームを表示したかっただけなのに……




せっかちな方へ

「能書きはいいから具体例を出せ」という向きは、

[Excel][VBA] ユーザーフォームをアクティブセルの右横に表示する実装例 · GitHub

こちらに実装例(マクロ有効ワークシート(*.xlsm))とソースコードを置いてあるので、ご自分で読み解いてください。

ちなみにこれを利用して作ったDatePicker(カレンダー)はこちら。

[Excel][VBA] Excel用DatePicker · GitHub

前提知識

  • エクセルVBAでは、オブジェクト(Window、Range、UserForm等)の位置や大きさは、基本的に「ポイント」という単位で管理されている(Top/Left/Width/Height等のプロパティの数値は全てポイント単位)
  • 位置を表す座標系には、大きく分けて、画面*1の左上を原点とする「画面座標系」(WindowオブジェクトやUserFormオブジェクト等はこちらで考える)と、ワークシートの左上*2を原点とする「ドキュメント座標系」(Rangeオブジェクト等はこちらで考える)とが存在する*3
  • 「画面座標系」と「ドキュメント座標系」とでは、同じ1ポイントであっても、画面上における大きさは異なる場合がある(例えばウィンドウの拡大率などに左右される)
  • ポイントとピクセル(ドット・画素)との比率も、環境によって異なる場合がある(画面解像度や拡大率に左右される)*4

アクティブセルの右側にユーザーフォームを表示したいときの座標計算方法の具体例

ユーザーフォームをアクティブセルの右側に表示する場合の座標の求め方

アクティブウィンドウ上にあるアクティブなセルのすぐ右側にユーザーフォームを表示する際の、ユーザーフォームに指定する座標(Top/Leftプロパティ)の値(ポイント)の具体的な求め方を記します。

なお、ここでは例として、

画面 横3840ピクセル✕縦2160ピクセル(4K)・拡大率150%
アクティブウィンドウ Top:84.0・Left:221.5・Width:1379.0・Height:822.0・拡大率200%
ウィンドウ枠の固定 有り(B3の位置)
アクティブセル アドレス:BR118・Top:2106.0・Left:3726.0・Width:54.0・Height:18.0

のようになっているものとします。

アクティブセルの位置を画面座標系に変換

アクティブセルのプロパティ(Top/Left)は、ドキュメント座標系上のポイント単位の数値です。
ひとまずこれを、画面座標系へと変換します。

これには、ペイン(Pane)オブジェクトのPointsToScreenPixelsY()PointsToScreenPixelsX()メソッドを使用します。

このとき注意するのが、アクティブセルが属しているPaneオブジェクトを指定する必要があることです。
ウィンドウ枠の固定をしている場合、ActiveWindow.Panes.Countは最大4まで存在するため、アクティブセルがPanes(1)~Panes(4)のいずれの下にあるかを調べる必要があります。
もっとも、アクティブセルが前提なので、この場合は、PaneオブジェクトとしてActiveWindow.ActivePaneオブジェクトを指定するのが簡単でしょう。

これで、Top: 2106.0ポイント→1003ピクセル、Left: 3726.0ポイント→1399ピクセルのように、アクティブセルの左上の、画面座標上の位置をピクセル単位で求めることができます。

画面座標系上の位置をピクセル単位からポイント単位に変換

求まった値はピクセル単位ですが、ユーザーフォームのプロパティ(Top/Left)に設定しようと思うと、ポイント単位への変換が必要になります。

画面座標系におけるピクセル→ポイントの変換方法として、自分は、ActiveWindowのHeight・Widthプロパティ(ポイント単位)と、WinAPIであるGetWindowRect()により得られるActiveWindowの座標(Top/Bottom/Left/Right・ピクセル単位)から求まる高さ・幅との比率を求めて使う、という方法を思いつきました。
もっと簡便な方法があればご教示願います。

これにより、上記の例では1ポイント=2.0ピクセルと出ましたので、Top:1003ピクセル→501.5ポイント、Left:1399ピクセル→699.5ポイントのように、アクティブセルの左上の、画面座標上の位置をポイント単位に変換できました。

セル幅を画面座標上のポイント単位に変換

セルの「右」に表示するので、横方向の座標にはセル幅(Widthプロパティ)分を加える必要があります。

Widthプロパティはポイント単位なので、これを足せばいいのか……と思いきや、ドキュメント座標上のポイントと画面座標上のポイントでは実サイズが異なってきます。
具体的には、ウィンドウの拡大率に左右されます。
アクティブウィンドウの拡大率は、ActiveWindow.Zoom÷100で求まり、上記の例(200%)だと2.0となります。

よって、ドキュメント座標上のWidth:54.0ポイントは、画面座標上では108.0ポイントとなります。

ユーザーフォームのプロパティを設定

以上により、ユーザーフォームの設定すべき画面座標上の位置は計算上(ポイント値で)Top:501.5、Left:699.5+108.0=807.5となります。
あとは、ユーザーフォームのStartUpPositionプロパティを0(Manual)にして、TopとLeftプロパティに値を設定すればいいわけですが……さらにいくつかハマりどころがあるのが困ったところですね。

ハマりどころ

第3の座標系が存在!?(ユーザーフォームの位置が想定よりも右下にずれていく問題)

環境によっては(画面解像度や拡大率、マルチモニタ等が影響?)上記のようにして計算した値をユーザーフォームのプロパティにセットしても、想定位置からずれてしまう場合があります。

計算値と実際の表示位置がずれる

しかも、画面の右下に行くに連れてズレも広がっていくようです。

この現象に最初に遭遇したときにいろいろと試してみたところ、上記の計算値に一定の係数(試したケースでは14/15=0.93…)を掛けたものをTop/Leftプロパティに設定すると、想定位置に表示されることがわかりました。
いわば、画面座標系と原点は共通ですが、目盛(1ポイント当たりの大きさ)の大きい座標系(ユーザーフォーム座標系?)が存在するようなイメージです。

この係数は環境により異なってくるため、定数にすることはできません。
そこで、

  1. GetWindowRect()でユーザーフォームのピクセル単位での座標を取得し、これから高さと幅を求める(実測値)
  2. ユーザーフォームのHeight/Widthプロパティに、ActiveWindowから求めたピクセル/ポイント比をかけて、ピクセル単位の高さと幅を求める(想定値)

のように二種の方法で高さと幅を求め、想定値と実測値の比を割り出し、これを係数として使用することで、対応することにしました。

とりあえず、この補正を行うことで、状況は改善されたようです。

計算値と実際の表示位置のズレを補正
画面の右の方にいくと、突然ユーザーフォームが左側にずれるようになる

環境によっては(おそらくマルチモニタ環境でかつ特定の解像度や拡大率の場合に)、画面の左側だと問題なく表示されているのに、あるところから突然ユーザーフォームが想定位置よりも左に大きくずれ始める、という現象が発生することがあります。

画面の右の方でいきなりずれだす

調べてみると、Pane.PointsToScreenPixelsX()が返す値が、ある列より右側では明らかにおかしくなっていました

PoinstsToScreenPixelsXテスト(4K・150%)

しばらく原因がわからず途方にくれていたのですが、その後、たまたま気がついたエクセルの設定を変更することで改善されることがわかりました。

ファイル>オプション>設定>全般>ユーザー インターフェイスのオプション>複数ディスプレイを使用する場合
で、

◉ 表示を優先した最適化 (アプリケーションの再起動が必要)

にしていると発生することがあるようで、試しに

◉ 互換性に対応した最適化

に変更(これもエクセルの再起動は必要)すると、

全般>複数ディスプレイを使用する場合は「互換性に対応した最適化」を選択

正常動作するようになったようです。

PoinstsToScreenPixelsXテスト(4K・150%)・「互換性に対応した最適化」設定後
その他注意点・制限事項など

処理を簡便化するモジュール

上記の処理の一部を簡便化するためのプロシージャ群を標準モジュールとしてまとめてあります

ConvertToScreenPosition()
Type ScreenPosition
    x As Double
    y As Double
End Type

Function ConvertToScreenPosition(TargetTop As Double, TargetLeft As Double, Optional TargetWindow As Window) As ScreenPosition

ドキュメント座標系の座標(TargetTop/TargetLeft・ポイント)を、画面座標上の座標(ピクセル単位)に変換して返します(変換不可な場合にはx=0・y=0が返ります)。
TargetWindowで対象となるWindowオブジェクトを指定可能です(省略時はActiveWindowになります)。
例えば対象となるセルのTop/Leftを指定するだけでよく、Panesのどれに属しているかは意識しなくても済むようにひと工夫してあります。

GetDisplayDotsPerPoint()
Type DotsPerPoint
    x As Double
    y As Double
End Type

Function GetDisplayDotsPerPoint(Optional TargetWindow As Window) As DotsPerPoint

画面座標系上の1ポイントあたりのドット(ピクセル)数を返します。
TargetWindowで対象となるWindowオブジェクトを指定可能です(省略時はActiveWindowになります)。

SetUserFormPosition()
Type CoordinateFactor
    x As Double
    y As Double
End Type

Function SetUserFormPosition(TargetForm, Top As Double, Left As Double, Optional Calibration As Boolean = True) As CoordinateFactor

TargetFormで指定したユーザーフォームを、画面座標系上の指定位置(Top/Left・ポイント)に移動します(このプロシージャでは表示は行わないことに注意してください。別途、TargetForm.Showプロシージャで表示する必要があります)。
このとき、位置ずれ補正(画面の右下に行くほどにずれる現象に対する補正)も自動で行います(Calibration:=Falseで補正を無効化することもできます)。
戻り値として、適用した補正係数を返します(Calibration:=False時はx=1・y=1が返ります)。

具体的な使用方法は、実装例を参照してください

ひとりごと

ユーザーフォームってあまり使った経験がないのですけれど、まさかセルの右横にユーザーフォームを表示するだけでこんなに苦労することになろうとは思いもよりませんでした……。
よりよい方法があれば、ご教示願います。


*1:マルチモニタ環境における「画面」は、すべての有効なモニタ画面を包括するような仮想的な矩形となると思われる

*2:セルの設定で非表示になっているものは含まないため、必ずしもA1ではないことに注意

*3:座標系の名前は便宜上のもので、自分のソースコード内では「ディスプレイ座標系」「ワークシート座標系」などと書いてある場合もある(統一が取れていなくてすみません……)

*4:ネットではDPI(Dots Per Inch)を96として決め打ちしている実装をよくみかけるが、あまり望ましくないと考えられる(ポイント(DTPポイント)の方は1インチあたり72ポイントがもともとの定義。なお、これもPoints Per Inchを略してPPIと書いてしまうと、Pixels Per Inchと混同してしまうため(こちらがより一般的なPPI)混乱の元なので避けたほうがよさそう)

私のSurface Book 2が不安定なのはどう考えてもグラフィック・ドライバーが悪い!?

Surface Book 2をメインPCとして愛用しているのですが作業中に突然ディスプレイがブラックアウトして、ひどいときにはそのまま復帰せずに手の施しようがなくなる……という現象に悩まされてきました(なんどかブルースクリーンにもなった覚えがあります)。

その際に調べたところでは、「デスクトップ ウィンドウ マネージャー(dwm.exe)」がメモリを数GB~数十GB程も占有している、という状況でした(通常は数十MB程度のはず)。

どうやらこれは、Surface Book 2(自分のは15")のIntel(R) UHD Graphics 620用のドライバーが原因だったようです

「それなら、Intel® Graphics – Windows* DCH Driversを更新すればよいんでしょ?」となるわけですが……(少なくとも自分のところでは)一筋縄ではいかなかったので、試行錯誤の内容を備忘録として記しておきます。




せっかちな人向けの手順

デスクトップ ウィンドウ マネージャー(dwm.exe)のメモリリーク対策として、Surface Book 2のIntel® Graphics – Windows* DCH Driversを更新する手順を示す。
自分のところは「Surface Book 2 15"(8th Gen Intel® Core™ i7-8650U クアッドコア プロセッサ, 4.2GHz Max Turbo)」なので、ドライバー名等は自身の環境にあわせて適宜読み替えのこと

  1. デバイスマネージャーを起動して、
    ディスプレイ アダプター>Intel(R) UHD Graphics 620のプロパティ>ドライバー
    を確認。
    これのバージョンが「30.0.101.1191」以降(例:「30.0.101.1339」)に既になっている場合、対策済みのはず
  2. 上記よりも前のバージョンになっている場合(自分のところでは「27.20.100.8682」「26.20.100.8141」が存在)、同じくデバイスマネージャーから、
    ディスプレイアダプター>Intel(R) UHD Graphics 620
    の「デバイスのアンインストール」(「☑ このデバイスのドライバーソフトェアを削除します」にチェックの上で)を実施(必要に応じて再起動)し、
    ディスプレイアダプター>Intel(R) UHD Graphics 620
    が存在しなくなるまで繰り返す
    自分のところだとディスプレイアダプター下には「NVDIC GeForce GTX 1060」のみが残った
  3. MicrosoftのSurface 用のドライバーとファームウェアをダウンロードするページより、Surface のドライバーとファームウェアの手動更新から「Surface デバイス モデルを選択する」で自分のモデルにあったリンクに遷移した上で(Surface Book 2の場合はこちら)最新のドライバーとファームウェアをダウンロードの上、実行してインストールを実施
    Surface Book 2の場合、ダウンロードされたのは「SurfaceBook2_Win10_19041_22.080.2839.0.msi」だった(2022/10/11現在)
  4. 再起動後、Intel(R) UHD Graphics 620のドライバーのバージョンが「30.0.101.1339」(もしくは「30.0.101.1191」以降)になっていることを確認

不具合(dwm.exeのメモリリーク)の原因

www.intel.co.jp
に書かれている通り、デスクトップ ウィンドウ マネージャー(dwm.exe)がいつの間にか理不尽なほどにメモリを占有してしまう不具合は、Intel製ドライバーに起因するメモリリークが原因だった模様。
対象となるドライバー(Intel® Graphics – Windows* DCH Drivers)は、

  • 「27.20.100.8587」以降で不具合発生
  • 「30.0.101.1191」以降であれば対策済み

とのこと。

試行錯誤

とくになにもせずとも通常のWindows Updateのみでいつのまにか「30.0.101.1339」に更新された……というケースもあるようなので(Surfaceかどうかは不明)、あくまで参考まで……。
自分の場合、過去にも試行錯誤していたため、その際に余分なことをしてしまった可能性も大いにある

ドライバーのバージョンを確認してみる

自分の環境で確認してみると、「Intel(R) UHD Graphics 620」のドライバーは

  • 日付:2020/09/06
  • バージョン: 27.20.100.8682

となっており、見事に不具合発生するバージョンであった。
これは……なんとかしてバージョンアップをしなければ……!
なお、試しにデバイスマネージャーの「ドライバーの更新(P)」を行ってみたところ……バージョン「26.20.100.8141」(日付: 2020/04/11)になってしまった……待って……なんで下がるの……(その後、「ドライバーを元に戻す(R)」でとりあえず「27.20.100.8682」に戻しておいた)

Intel公式ページよりダウンロードしたドライバーはインストール不可

最初は単純に「Intel公式ページから最新のをダウンロードしてインストールすればいいんでしょ?」と思ったが、実際にやってみると「お使いのシステムには製造元の仕様にロックされたドライバーがあります」なる無慈悲なメッセージが表示されてしまい、インストールできない。
なお、そのあたりを配慮して更新してくれそうな「Automatic Driver and Software Updates」なるものもあるが、自分の環境だとインストールはできたものの、その後のダウンロードができない(インジケータが20数パーセントくらいまで進むと0になるを繰り返す→そのうちダウンロードがキャンセルされる)現象が発生し、うまくいかなかった

ドライバー/ファームウェアの最新版を入れてみたが……

Surface Book 2 更新履歴を見てみると、「2022 年 6 月の更新プログラム(6 月 17 日リリース)」のところに、

Windows Update の名前 デバイス マネージャー
Surface - システム – 6.134.139.0 Surface Integration Service Device – システム デバイス
Intel Corporation - ディスプレイ – 30.0.101.1339 Intel(R) UHD Graphics 620 (15") – ディスプレイ アダプター
Intel Corporation - ディスプレイ – 30.0.101.1339 Intel(R) HD Graphics 620 (13") – ディスプレイ アダプター
Surface Book 2 更新履歴 - Microsoft サポート

という記述が……! これこそ自分が求めていたものではあるまいかっ!

喜び勇んでダウンロードセンターから最新版(SurfaceBook2_Win10_19041_22.080.2839.0.msi(Date Published:8/26/2022))をダウンロードし、インストール&再起動したところ……ドライバーのバージョンは「27.20.100.8682」のままだった……いや、なんでよ……?

ドライバーはアンインストールした上でのクリーンインストールが必要だった……!

なんどか入れ直したり再起動したりを繰り返すも、状況は変わらず。
さすがに諦めかけた頃に、もう一度このページを見直してみたところ、解決方法のところにしっかりと

dwm.exe の漏洩の修正プログラムを含むドライバー 30.0.101.1191 をインストールする前に、 ドライバーのクリーン・インストール を実行する必要があります。

dwm.exe (デスクトップ PC ウィンドウマネージャー) は、27.20.100.8587...

と書かれていることに気づく。

ダメ元だと、

  1. 念のため、インストールしてあったSurface Book 2 のドライバー/ファームウェア(Surface Book 2 Update 22.080.2839.0 (64 bit))をアンインストール
  2. デバイスマネージャーから、
    ディスプレイアダプター>Intel(R) UHD Graphics 620
    の「デバイスのアンインストール」を、
    ☑ このデバイスのドライバーソフトェアを削除します
    にチェックした状態で実施(必要に応じて再起動)
    ディスプレイアダプター>Intel(R) UHD Graphics 620の項目がなくなる(自分の場合、「NVDIC GeForce GTX 1060」のみが残る)まで繰り返し(「27.20.100.8682」「26.20.100.8141」の2つの削除が必要だった)
  3. Surface Book 2のドライバー/ファームウェア(SurfaceBook2_Win10_19041_22.080.2839.0.msi)を再インストール

とやってみると……なんとっ!

Intel(R) UHD Graphics 620のドライバーのバージョンを確認

見事に、バージョン: 30.0.101.1339(日付: 2022/01/22)のドライバーへと更新されていた……!

Windows Helloが使えなくなっちゃった!

無事更新されたと喜んだものの、なぜかWindows Helloによる顔認証が効かなくなってしまった……。
ログイン画面で「カメラをオンにできませんでした。PINを使ってサインインしてください。」とか言われる

自分の場合には幸いにして、
設定>アカウント>サインインオプション>Windows Hello 顔認証
より[認識精度を高める]を再度実施することによって、使えるようになった模様。
うまくいかない場合、こちらの記事などを参照のこと



とりあえず、この状態で様子見中。

この記事を書くきっかけ

2022/10/11に発生し、Windowsの動作が重くなったことで気がついたdwm.exeのメモリリークはこんな感じ。

デスクトップウィンドウマネージャーのメモリリーク

2GB弱なので、まだまだ序の口という印象だが……。
過去にもいろいろと調べてみたものの抜本的な対策が見つからずにあきらめていたのだが、なんとか対応する方法はないものか……と再度調べてみると、Surface Book 2 更新履歴に対策されたらしき履歴があったので、試してみた次第。

過去のツイート等

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)の項目に書かれている

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

参考