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

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 () => {`みたいに非同期関数にしてください。

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

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

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

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

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

```js
importScript("options.js");
```
同期用JScriptを読み込む場合

```js
$.importScript("sync.js");
```

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


## 同期用JScript (Edge legacy/Chakra)

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

```js
InvokeUI("Addons.FilterBar.Exec", [Ctrl, pt]);
```
```js
api.Invoke(UI.Addons.FolderListMenu, [oMenu, items, pt]);
```
引数は2番目の配列として渡します。
UI用JavaScriptからの戻り値は必ず`undefined`になります。値を返したい場合はコールバック関数を使います。

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

```js
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で読み込みたい場合は

```js
api.ObjGetI(Object, "プロパティ名");
```
を使うとうまくいくかもしれません。書き込みはできません。

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

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

```js
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のネイティブの配列に変換できます。

```js
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 で複数の非同期オブジェクトをまとめることもできます。

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

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

```js
const FV = await GetFolderView();
const FolderItem = await FolderItem;
Promise.all([FolderItem.Name, FolderItem.Path]).then(function (r) {
    wsh.Popup(r.join("\n"));
});
```
https://gist.github.com/tablacus/ada487fe34a170b0dcdfe1fd21579ed8

# 非同期関数を使用しても並列動作になります。
https://gist.github.com/tablacus/9befe6a8e4088b1a304cc5ed341c5cf0

# その他注意点

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

```js
const nLength = await GetLength(ar);
```
受け渡しの文字列にNULL文字`\0`を含むとそこで切られます。

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

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

```js
if (o == null) {// null もしくは undefied の場合 true
}
```
`== null`や`!= null`を使用しています。

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

```js
const el = document.getElementById("○○");
const o = await api.CreateObject("Object");
o.el = el;
```
`o.el` は別物になっています。HTMLのエレメントは必ず、UI用JavaScriptの変数に入れてください。

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

```js
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(`が使えるようになりました。**

```js
clipboardData.setData("text", "ABC");
```

```js
api.SetClipboardData("ABC");
```

# おまけ：標準のTrident版

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


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