4
3

More than 3 years have passed since last update.

Tablacus Explorer Blink版のスクリプトの構造

Last updated at Posted at 2020-12-13

WebView2を使ったTablacus Explorer Blink版では2つのJavaScriptエンジンで動作しています。

  1. UI用JavaScript (Blink/V8)
  2. 同期用JScript (Edge legacy/Chakra)

1.のBlinkのJavaScriptには外部にリアルタイムに戻り値を返せないという問題があるので2.のEdge用のChakraエンジンを併用しています。

スクリプトファイルの割り振り

  • scriptフォルダに入っているファイルでは 「consts.js」「common.js」はUI用JavaScript、同期用JScriptの両方で使用します。 「ui.js」「index.js」「options.js」はUI用JavaScriptのみで使用します。 「sync.js」「sync1.js」「syncb.js」は同期用JScriptのみで使用します。 「background.js」はマルチプロセスのスクリプト実行時に同期用JScriptで使用します。 「threads.js」はマルチスレッドのスクリプト実行時に同期用JScriptで使用します。 「update.js」はTablacus Explorer更新時にWSHで使用します。

※ sync*.js にありUI用JavaScript側にない関数はコピーされます。

  • 個々のアドオンでは 「script.js」「options.js」はUI用JavaScriptで使用しています。 「sync.js」はUI用JavaScriptで同期用JScriptで使用しています。

2つのJavaScriptエンジンの使い方

UI用JavaScript (Blink/V8)

タイプがJavaScriptの場合に実行されます。

Tablacus Explorerのオブジェクトのプロパティやメソッド、同期用JScriptの関数の戻り値などは非同期のPromiseオブジェクトで返されます。
基本的にawaitで受け取ってください。また、関数内でawaitを使う場合はasync function () {'や'async () => {みたいに非同期関数にしてください。

const FV = await GetFolderView();
wsh.Popup(await FV.FolderItem.Path);

GetFolderViewは同期用JScriptで現在開いているフォルダビューを取得します。
下のFV.FolderItem.Pathの様にオブジェクトのプロパティのプロパティを受け取る場合やオブジェクトのプロパティのメソッドの戻り値を取得する場合は前に一つawaitがあれば良いようです。

wsh.Popup(await (await FV.FolderItem).Path);

みたいに書くこともできます。


同期用JScriptのwindowオブジェクトは変数の$に入っています。
UI用JavaScriptを読み込む場合

importScript("options.js");

同期用JScriptを読み込む場合

$.importScript("sync.js");

メニューなどが開いている場合はUI用JavaScriptは停止しているようです。メニュー中のスクリプトは同期用JScriptを使用してください。

同期用JScript (Edge legacy/Chakra)

タイプがJScriptの場合に実行されます。awaitなどが不要なので標準のTrident版と互換性が高いです。
同期用JScriptからUI用JavaScriptは直接呼び出すとなぜかエラーが出るのでInvokeUI関数かapi.Invoke(を使います。

InvokeUI("Addons.FilterBar.Exec", [Ctrl, pt]);
api.Invoke(UI.Addons.FolderListMenu, [oMenu, items, pt]);

引数は2番目の配列として渡します。
UI用JavaScriptからの戻り値は必ずundefinedになります。値を返したい場合はコールバック関数を使います。

同期用JScriptからHTMLの部品にはアクセスできません。エラーが出ます。

const el = document.getElementById("○○");

上記の様にHTMLを操作する場合はUI用JavaScriptを使用します。

setTimeout関数は使えますが、UI用JavaScript用の物を利用している関係で戻り値のタイマーIDが取得できません。
タイマーIDを使うものはUI用JavaScriptを使用してください。

2つのJavaScriptエンジンで共有のオブジェクト

「g_」「Common」「Sync」「UI」「te」などの共有オブジェクトがあります。
g_ は基本的な設定など
Common はアドオン共有のオブジェクトで Common.AddressBar はアドレスバーで共有しています。
Sync は同期用JScriptの関数などを入れ、UI用JavaScriptから呼び出したりするのに使用しています。
UI は主に本体で2つのJavaScriptの連携に使用しています。(InvokeUIなど)
te は Tablacus Explorerのオブジェクトです。te.Dataに設定があります。

オブジェクト{}、配列[]の受け渡しについて

2つのJavaScriptエンジンでオブジェクト{}、配列[] を受け渡すと相性が悪くエラーが出やすいです。
以下のTablacus Explorerのオブジェクト、配列を使用すると問題なく動作します。
{}の代わりに api.CreateObject("Object")
[]の代わりに api.CreateObject("Array")
もしくはJSONなど文字列に変換して受け渡しを行ってください。

どうしてもUI用JavaScriptのオブジェクト{}、配列[]を同期用JScriptで読み込みたい場合は

api.ObjGetI(Object, "プロパティ名");

を使うとうまくいくかもしれません。書き込みはできません。

逆に同期用JScriptのオブジェクト{}、配列[]は一応読み込みできますが、未定義のプロパティを読み込むとエラーが出ます。
安全に読み書きを行う場合は

await api.ObjGetI(Object, "プロパティ名");
api.ObjPutI(Object, "プロパティ名", 新しい値);

を使用してください。

高速化テクニック

awaitが続くとどうしても遅くなるようです。

以下の固定値はui_にも置いています。

ui_ 意味
ui_.TEPath await api.GetModuleFileName(null) Tablacus Explorerのパス
ui_.Installed await te.Data.Installed Tablacus Explorerのフォルダ;
ui_.hwnd await te.hwnd Tablacus Explorerのウインドウハンドル
ui_.DoubleClickTime await sha.GetSystemInformation("DoubleClickTime") ダブルクリックの時間;
ui_.bit await api.sizeof("HANDLE") * 8 ビット数(32/64)

配列はネイティブの配列に変換できます。

配列はawait api.CreateObject("SafeArray", 変数名);でUI用JavaScriptのネイティブの配列に変換できます。

let ar = await api.CreateObject("Array");
await ar.push("abc", "def");
if (window.chrome) {
    ar = await api.CreateObject("SafeArray", ar);
}
wsh.Popup(ar.join("\n"));

Promise.all で複数の非同期オブジェクトをまとめることもできます。

const FV = await GetFolderView();
const FolderItem = await FolderItem;
wsh.Popup([await FolderItem.Name, await FolderItem.Path].join("\n"));

同期

const FV = await GetFolderView();
const FolderItem = await FolderItem;
wsh.Popup(await Promise.all([FolderItem.Name, FolderItem.Path]).join("\n"));

非同期

const FV = await GetFolderView();
const FolderItem = await FolderItem;
Promise.all([FolderItem.Name, FolderItem.Path]).then(function (r) {
    wsh.Popup(r.join("\n"));
});

非同期関数を使用しても並列動作になります。

その他注意点

UI用JavaScriptでは外部オブジェクトのlengthは取得できません。代わりにawait GetLength(使用するか配列などの場合は上記のネイティブ配列化を使います。

const nLength = await GetLength(ar);

受け渡しの文字列にNULL文字\0を含むとそこで切られます。

UI用JavaScriptから同期用JScriptにnullを渡すとundefinedになります。
逆に同期用JScriptからUI用JavaScriptにundefinedを渡すとnullになります。

以上の結果からundefinednullを厳密に判別するのはお勧めしません。

if (o == null) {// null もしくは undefied の場合 true
}

== null!= nullを使用しています。

HTMLのエレメントは外部に持ち出すと使えなくなります。

const el = document.getElementById("○○");
const o = await api.CreateObject("Object");
o.el = el;

o.el は別物になっています。HTMLのエレメントは必ず、UI用JavaScriptの変数に入れてください。

UI用JavaScriptのwindowオブジェクトは外部に持ち出せません

const o = await api.CreateObject("Object");
o.window = window;

上記コードはエラーが出ます。JSON化できないオブジェクトは持ち出し不可のようです。

HTML5 ドラッグ&ドロップは通常画面ではネイティブドラッグ&ドロップと共存できなかったので、ondragoverイベントとondropイベントは動作しません。ネイティブドラッグ&ドロップの方のイベントを使ってください。オプション画面の方はネイティブドラッグ&ドロップを使用していないので使えます。

※ネイティブドラッグ&ドロップのイベントはリアルタイムで戻り値を返さなければならない関係で、同期JScriptの方を使う事になるのですが、同期JScriptの方からはHTMLの部品にアクセスできないので、事前のDragEnterイベント辺りで項目の場所(RECT)を保存するなど工夫が必要で面倒だったりします。

標準版で使えるclipboardData.setData(が使えないのでテキストの場合は代替でapi.SetClipboardData(を使います。
Tablacus Explorer 20.12.13でWebView2版でもclipboardData.setData(が使えるようになりました。

clipboardData.setData("text", "ABC");
api.SetClipboardData("ABC");

おまけ:標準のTrident版

標準のTrident版Tablacus Explorerの場合は一つのJavaScriptエンジンで動作し、$ = window; になっています。
また、スクリプトの読み込み時に自動的にasync/awaitなどは削除していますので、Blink版と同じスクリプトで動作しています。
Promise.allも代替処理を入れて同じコードで動作させています。

以上、試行錯誤したものです。もっと良い方法があれば教えてください。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3