銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

C++でJsonを扱うクラスライブラリ jsonexpr

8月から支援に入った画像処理(リアルタイム・オブジェクト・トラッキング)プロジェクトの検証システムで、C++の本体からJSONを吐く必要がありまして、急いでやっつけたのですが、「そういや昔、C++JSON扱うクラスライブラリ作ったね・・・」と思い出しました(遅い)。

ということで、古いGitHubから引っ張り出して確認すると、どうにか使えそうなので、この場を借りて宣伝します(笑)

f:id:takamints:20170924182608p:plain

実践OpenCV 3 for C++画像映像情報処理
永田 雅人 豊沢 聡
カットシステム
売り上げランキング: 263,159

元々 Node.jsで出力したJSONファイルをC++で読み込んで処理させる目的で作ったものです。 当時、ニューラルネットワークバックプロパゲーションを理解するための実験を何故かC++で書いていましたが、Node.jsで大量に生成した訓練用データを入力する必要があったのです。結局本体もJavaScriptに移植したので存在を忘れてた。

C++で学ぶディープラーニング
藤田 毅
マイナビ出版 (2017-06-26)
売り上げランキング: 192,886

機能

JavaScriptの変数と同じように扱うためのjson::varというクラスが、ほぼすべての機能を含んでいます。他のクラスはこのクラスの内部で使用するものです。

ビルドについて

ビルドはCMakeを使っています。手元の環境では、Visual Studio 2015のソリューションは生成できました。Mingw32のG++用スクリプトが入っていますがエラーになります(原因は追求していません)。Linuxでは試していませんが、またそのうち。

ライセンス

ライセンスを明記していなかったので急遽MITライセンスを謳っておきました。

リポジトリはコチラです

github.com

同カテゴリでは picojson など著名なライブラリがいくつかあるようですが、興味のある方は試してみて下さい。 今見ると少し手を入れたい所がありますが、取り急ぎ現状のままお送りします。

以下、READMEを貼っておきます。

jsonexpr

JSONC++で自然に扱うために作ったライブラリです。

json::var クラスのインスタンスが、JavaScript のデータ型(Number, boolean, string, Array, Object)に相当します。

データ型

  1. 数値 - double型の数値
  2. 文字列 - std::string型の文字列
  3. 配列 - json::var型の配列
  4. オブジェクト - 文字列からjson::var型へのディクショナリ

初期化

json::var のコンストラクタにJSON文字列を与えて初期化できます。 プリミティブ型ではあとで説明していますが、代入するほうが直感的かも知れませんね。

//数値の初期化(内部表現は全てdouble)
json::var real("-1.234e+5");  //浮動小数点
json::var dec("1234");        //10進整数
json::var hex("0x1234");      //16進整数
json::var oct("0644");        //8進整数

//文字列で初期化
json::var str("'string'");    //シングルクォート

//配列の初期化
json::var arr("[-1.234e+5,'string']");

//オブジェクトの初期化
json::var obj("{'key':'value', foo: 'bar', arr:[1,2,3,4]}");

配列操作

  1. length() - 要素数を得る
  2. push(値) - 要素の追加。値は数値、文字列、配列、オブジェクト
  3. remove(index) - 要素の削除。
json::var arr("[]");//空の配列
arr.push(1.0);      //数値を追加
arr.push("string"); //文字列を追加
for(int i = 0; i < arr.length(); i++) {
    cout << arr[i];
}

オブジェクト操作

  1. exists(key) - キーの有無を調べる
  2. keys() - キーの配列を返す。
  3. remove(key) - キーの削除。

新たなキーに値を関連付けるには、[]で直接代入します。 ※ 定数でないoperator[](const std::string&)では、参照するだけでキーが作成されることに注意。

json::var obj("{}");    //空のオブジェクト
obj["A"] = "B";         //文字列の値を追加
obj["R"] = arr;         //既にある配列のコピーを追加
json::var keys = obj.keys();
for(int i = 0; i < keys.length(); i++) {
    cout << "#" << keys[i] << " => " << obj[keys[i]] << endl;
}

値の参照

数値型は、doubleへのキャスト、文字列型は std::string&へのキャストで値を参照します。

配列要素とオブジェクトの値は[]を使用。配列要素は添え字が整数、オブジェクトは文字列型。 ネストしたオブジェクトは二次元配列のようにかぎかっこ([])をつないで参照します。

json::var real = -1.234e+5;
double r = (double)real;     // r == -1.234e+5;

json::var str = "ABC";
std::string s = (const std::string&)str;

json::var element = arr[3];             //配列要素の参照
json::var value = obj["arr"][0];      //オブジェクト内の配列要素の参照

内部表現と矛盾する参照を行うと、例外が投入されます。

値の代入

data = 1.234;           //数値を代入
data = "string";        //文字列を代入
arr[3] = 1.234;         //配列要素の書き換え
obj["key"] = 1.234;     //オブジェクト要素の書き換え
obj["new key"] = arr;   //オブジェクトへ新たな項目を追加

JSONデータのストリーム入出力

C++の標準ストリーム入出力機能を実装しています。

std::istream からのJSONの読み込み

json::var dataobj;
ifstream is("input.json");
is >> dataobj;

std::ostreamへのJSON出力

json::var dataobj;
ofstream is("output.json");
os << dataobj;

制限事項

javascriptの仕様に完全準拠しているわけではありません。

  • NaNを扱えません。
  • Functionを扱えません。
  • オブジェクトのキーは文字列だけです。
  • 数値は内部的にdoubleだけです。

長いサンプル

    // json 文字列からの構築
    json::var num("-1.2345e+3");
    json::var str("'this is string.'");
    json::var arr("[ \"key\", 'str', 'hex', 0xABCD, 0777 ]");
    json::var obj("{ foo : 'bar', 'boo':1.2345e-6, 'arr': [0,1,2,3]}");

    // 参照
    double numval = num;
    string strval = str;
    cout << numval << endl;
    cout << strval << endl;

    numval = arr[3];
    cout << numval << endl;

    strval = (string)obj["foo"];
    cout << strval << endl;

    strval = (string)obj["arr"][2];
    cout << strval << endl;

    // 配列の扱い
    cout << "arr.length() = " << arr.length() << endl;
    arr.push(9.876);
    arr.push("string element");
    arr.push(obj);
    arr.push(arr);
    cout << "arr.length() = " << arr.length() << endl;

    //
    // オブジェクトへ新たなキーを追加
    //
    cout << "obj.exists('new key') = " << (obj.exists("new key")?"true":"false") << endl;
    obj["new key"] = json::var("[0,1,2,3]");
    cout << "obj.exists('new key') = " << (obj.exists("new key")?"true":"false") << endl;

    //
    // std::ostreamへのJSONの書き出し
    //
    ostringstream os;
    os << obj;

    //
    // std::istreamからJSONを読み出し
    //
    istringstream is("{ foo : 'bar', 'boo':1.2345e-6, 'arr': [9,8,7,6] }");
    is >> obj;

    //
    // 別の値(別の型)に書き換え
    //
    num = 4.0;
    cout << num << endl;

    num = "overwrite string";
    cout << num << endl;

    num = obj;
    cout << num["boo"] << endl;

    num["boo"] = "change by reference";
    cout << num["boo"] << endl;

    num["arr"][1] = "change by reference";
    cout << num["arr"][1] << endl;

LICENSE

このソフトウェアは、MIT ライセンスにて、提供します。LICENSE を参照下さい。

This software is released under the MIT License, see LICENSE

Node.js+Sqlite(npm sqlite3)のeachメソッドは中断できない

f:id:takamints:20170829222928p:plain
photo credit: Skakerman 39/52 - Signs via photopin (license)

8月初旬に急遽「C++で画像処理(オブジェクトトラッキング)」ってなプロジェクト(しかもOpenCVを使わないという骨太方針w)のヘルプに駆り出されて、それまでやってた「Node.js+sqlite3」の悩みどころや困りどころを、今やスッカリ忘れそうになっているので、思い出しつつ書いておきます。

[改訂第4版]SQLポケットリファレンス
朝井 淳
技術評論社
売り上げランキング: 95,176

sqlite3のeachメソッドは中断できない

表題と同じですけど大切なことなので2回・・・

ええ、クエリ結果セットから一件ずつレコードを読むための Database#each または、 Statement#each メソッドなんですが、 どちらも公式ドキュメントに「今のところ中断できない」と書いてあって実質的に使えないです。

Database#each

Database#each(sql, [param, …], [callback], [complete])

Runs the SQL query with the specified parameters and calls the callback once for each result row. The function returns the Database object to allow for function chaining. The parameters are the same as the Database#run function, with the following differences:

The signature of the callback is function(err, row) {}. If the result set succeeds but is empty, the callback is never called. In all other cases, the callback is called once for every retrieved row. The order of calls correspond exactly to the order of rows in the result set.

After all row callbacks were called, the completion callback will be called if present. The first argument is an error object, and the second argument is the number of retrieved rows. If you specify only one function, it will be treated as row callback, if you specify two, the first (== second to last) function will be the row callback, the last function will be the completion callback.

If you know that a query only returns a very limited number of rows, it might be more convenient to use Database#all to retrieve all rows at once.

There is currently no way to abort execution.

これ見落としていて、えらい目に遭いました。まさかこんなしれっと書いてあるなんて。

Statement#each(↓)なんて、なぜだかビックリマーク付きですよ。

Statement#each

Statement#each([param, …], [callback], [complete])

Binds parameters, executes the statement and calls the callback for each result row. The function returns the Statement object to allow for function chaining. The parameters are the same as the Statement#run function, with the following differences:

The signature of the callback is function(err, row) {}. If the result set succeeds but is empty, the callback is never called. In all other cases, the callback is called once for every retrieved row. The order of calls correspond exactly to the order of rows in the result set.

After all row callbacks were called, the completion callback will be called if present. The first argument is an error object, and the second argument is the number of retrieved rows. If you specify only one function, it will be treated as row callback, if you specify two, the first (== second to last) function will be the row callback, the last function will be the completion callback.

Like with Statement#run, the statement will not be finalized after executing this function.

If you know that a query only returns a very limited number of rows, it might be more convenient to use Statement#all to retrieve all rows at once.

There is currently no way to abort execution!

結局 all と同じですからー

通常この手のメソッドは、SQLを発行してフェッチした行を順次1行ずつコールバックで通知してくれて、 「もう十分。これ以上必要ない」って時に中断したいわけですが、それが(今のところは)できないってこと。 つまり結局最後まで読みきらないといけないってことで、それなら Database#allStatement#allと同じですよね。

LIMIT使えばいいじゃない / allだけで行けるじゃない?

結局、同じようなことをするためには、SQLでLIMIT句を使って、allメソッドでページングしながら、複数行を繰り返し取ってくるしか手はありませんでした。

結果は大体同じですけど、SQLを複数回発行することになるので、パフォーマンスはちょっと低下すると思います。

LIMIT句をパラメタライズしてStatementを用意しておけば、あまり気にならないとは思いますが。

ま、巨大な結果セットを返さないようにSQLを変更できるなら、それを優先する必要がありますね。

ここぞとコントリビュートしなさいよ

オープンソースなんだから「使えねー」とか文句ばっか言ってないで、フォークして対応してプルリク投げればよいのですが、、、

コレぐらいバカでかいプロジェクトだとちょっと躊躇しますね(笑)

今のところ画像処理方面でアレなんで(言い訳)、またそのうち・・・。

github.com

意外に悩ましい整数部分の四捨五入

f:id:takamints:20170718211752p:plain
photo credit: danmachold calculator via photopin (license)

JavaScriptで整数部分を四捨五入する場合の注意点。桁落ちに気を付けましょうってお話です。

JavaScriptで、小数を整数に四捨五入するにはMath.roundを使いますね。

でもこれ、残念なことに桁数指定ができません。

なので例えば、実数nを小数点以下2桁に丸めたい場合はMath.round(n * 100) / 100 などとしますよね。

毎回掛けたり割ったりするのも邪魔くさいので関数化すればヨロシイですねと、 実数 n を小数点以下 m 桁に四捨五入する関数は、以下のようになりますよっと(Nodeのモジュールとして書いています)。

桁数指定で小数点以下を四捨五入する関数(モジュール)

"use strict";
var assert = require("assert");

/**
 * 小数部の桁数指定で四捨五入。
 *
 * @param {number} n 元の数値
 * @param {number} m 桁数指定。0以上の整数。
 * @returns {number} 結果を返す
 */
function round(n, m) {
    assert(m == Math.round(m));
    assert(m >= 0);
    var r = Math.pow(10, m);
    return Math.round(n * r) / r;
}
module.exports = round;

ここで、 m に負の値を与えれば、整数部分も丸められるように一見思えるのですが、 m の絶対値が大きくなると桁落ちが発生して正しい結果が得られません。いつ桁落ちするかわからないので結局安心して使えません。

桁数指定で整数部分も四捨五入できる関数

てなことで、整数部も正しく四捨五入するために以下のようにしてみました。

"use strict";
var assert = require("assert");

/**
 * 桁数指定で四捨五入。
 *
 * @param {number} n 元の数値
 * @param {number} m 桁数指定。負の値で整数部を四捨五入
 * @returns {number} 結果を返す
 */
function round(n, m) {
    assert(m == Math.round(m));
    if(m < 0) {
        var i = Math.floor(n);
        var R = Math.pow(10, -m);
        var sgn = Math.sign(n);
        var h = sgn * R / 2;
        var mod = sgn * Math.abs(i % R);
        var up = sgn * (sgn >= 0 ? (mod >= h ? R : 0) : (mod < h ? R : 0));
        return  i - mod + up;
    }
    var r = Math.pow(10, m);
    return Math.round(n * r) / r;
}
module.exports = round;

当初「簡単!簡単!」と軽い気持ちで書いたら、 n が負の時に結果がおかしくて、最終的に結構ややこしくなってしまいました。 m < 0 の時、とか、 n の符号による判定が離散的でスッキリしないですねえ。なんかいい方法無いものかな。

言い訳

実は、あまり多くのテストケースを通していないので、ちょっと不安が残っております。

あと、npmには負の桁数指定ができるroundやceil, floorを提供するモジュールはあるようなのですが、 桁落ちに対してどうなっているのか明記されたものが、よくわかりませんでした(英語力の問題かも)。

「プロミス地獄」に落ちないための基本事項

「コールバック地獄」からボクらを救ってくれた「Promise」ですが、ふと気が付けば、ちょっと種類の違う別の地獄に落ちてる場合がありますよと。

「なんだPromiseお前もかっ!」的な(笑)

f:id:takamints:20170618182837p:plain

「コールバック地獄」は見た目にネストが深くて「ダメだコリャ感」がわかりやすい。 一方言わば「Promise地獄」は、パッと見スッキリしてるんだけど、少し複雑になると、ホントに正しく動いているのかどうか判別しにくい。 というのも、Promise的に間違っていてもJavaScriptの構文的には正しいことが多々あって、どこでバグっているかがわかりにくい。 結果、変なハマり方をしてしまうんですね。

てことで、今まで自分でハマった「Promise地獄」を思い返して知見をまとめておこうと思います。

約束 (創元推理文庫)
約束 (創元推理文庫)
posted with amazlet at 17.06.18
東京創元社 (2017-05-11)
売り上げランキング: 8,345

Promiseのハンドラーでtry~catchする必要はありません

Promiseオブジェクト生成時のexecutor関数内からエラーが投入されると、そのPromiseはrejectされます。

つまり特別な理由がない限りtry~catchでエラーを捕まえてrejectする必要はありません。

throw-error-from-ctor.js

function someAsync() {
    return new Promise(function(resolve, reject) {
        throw new Error("to reject the promise");
    });
}

someAsync().then(function() {
    console.error("OK.");
}).catch(function(err) {
    console.error("Error:", err.message);
});

実行結果

$ node throw-error-from-ctor.js
Error: to reject the promise

$ 

つまり以下は冗長です。実行結果は上と同じ。

throw-error-redundant.js

function someAsync() {
    return new Promise(function(resolve, reject) {
        try {
            throw new Error("to reject the promise.");
        } catch(err) {
            reject(err);
        }
    });
}

そしてコレはthenでも同じ

上のことはthenのonfulfilledにも当てはまります。つまりthenの中でtry~catchする必要はありません。 以下のPromiseもrejectされます。

throw-error-from-then.js

function someAsync() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() { resolve(); }, 1000);
    });
}

someAsync().then(function() {
    throw new Error("to reject the promise");
}).catch(function(err) {
    console.error("Error:", err.message);
});

しかしネストした非同期処理はtry-catchで囲むべきです

以下のコードでも、Promiseは結果的にrejectされていますが、投げられたエラーがキャッチされておらず、スタックトレースがプリントされています。

function someAsync() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() { resolve(); }, 1000);
    });
}

someAsync().then(function() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            throw new Error("to reject the promise");
        }, 1000);
    });
}).then(function() {
    console.error("OK.");
}).catch(function(err) {
    console.error("Error:", err.message);
});

実行結果

$ node throw-error-from-then-nest.js
throw-error-from-then-nest.js:10
            throw new Error("to reject the promise");
            ^

Error: to reject the promise
    at Timeout._onTimeout (throw-error-from-then-nest.js:10:19)
    at ontimeout (timers.js:365:14)
    at tryOnTimeout (timers.js:237:5)
    at Timer.listOnTimeout (timers.js:207:5)

実は予想外の動きでした。rejectされないと思っていたのです。もしかして古いバージョンではrejectされないかと確認してみましたが、少なくともnode 4.8.3ではrejectされていました。 なんにせよ、上のようにネストした非同期処理は、以下のようにtry~catchでエラーをハンドリングして明示的にrejectしたほうが良さそうです。

function someAsync() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() { resolve(); }, 1000);
    });
}

someAsync().then(function() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            try {
                throw new Error("to reject the promise");
            } catch(err) {
                reject(err);
            }
        }, 1000);
    });
}).then(function() {
    console.error("OK.");
}).catch(function(err) {
    console.error("Error:", err.message);
});

そのnew Promise、ホントに必要?

Promiseチェーンが長くなってくると、その一部分をまとめてPromiseを返す関数として独立させたくなったりしますが、その新しい関数の中でPromiseオブジェクトを生成する必要は多分ありません。

以下のようにthenがフラットにたくさん続くと、それはそれでイラッときます。

someAsync().then(function () {
    return asyncA1();
}).then(function () {
    return asyncA2();
}).then(function () {
    return asyncA3();
}).then(function () {
    return asyncB1();
}).then(function () {
    return asyncB2();
}).then(function () {
    console.log("OK.");
}).catch(function(err) {
    console.error("Error:", err.message);
});

非同期処理をAとBでまとめるには、以下のように組み直せば良いのですが、、、

function asyncA() {
    return asyncA1().then(function() {
        return asyncA2();
    }).then(function () {
        return asyncA3();
    });
}

function asyncB() {
    return asyncB1().then(function() {
        return asyncB2();
    });
}

someAsync().then(function () {
    return asyncA();
}).then(function () {
    return asyncB();
}).then(function () {
    console.log("OK.");
}).catch(function(err) {
    console.error("Error:", err.message);
});

しかしついつい、以下のように新しいPromiseを作って返すコードを書いてしまうことがあるんです。 間違ってはいないけれど冗長ですし、これが積み重なると、まさに「プロミス地獄」が始まる気がする。

function asyncA() {
    return new Promise(function(resolve, reject) {
        asyncA1().then(function() {
            return asyncA2();
        }).then(function () {
            return asyncA3();
        }).then(function() {
            resolve();    
        }).catch(function(err) {
            reject(err);
        });
    });
}

function asyncB() {
    return new Promise(function(resolve, reject) {
        asyncB1().then(function() {
            return asyncB2();
        }).then(function() {
            resolve();    
        }).catch(function(err) {
            reject(err);
        });
    });
}

someAsync().then(function () {
    return asyncA();
}).then(function () {
    return asyncB();
}).catch(function(err) {
    console.error("Error:", err.message);
});

まとめ

アレっ?書き始める前はもっとあった気がするんですけどね。

思いついたら随時追記するつもりですー。

WHATWG Fullscreen API を仕様通りに使えるモジュール「fullscrn」

f:id:takamints:20170528140557p:plain

WEBページ内の特定HTML要素を画面全体に広げられるフルスクリーンAPIのラッパーモジュールをnpmで公開しました。

WHATWGが策定しているフルスクリーンAPIは、現状(2017年5月現在)、多くのブラウザで、プリフィックス付きの実装(mozとかwebkitというアレですね)となっています。

なので、いろんなブラウザで動作させるには結構邪魔くさいことをするわけですが、このモジュールを使えば標準仕様と同じように利用できます。

目次

  1. リリースファイル
  2. サンプル
  3. API
    1. プロパティ
    2. メソッド
    3. イベント
  4. リンク
  5. あとがき

↓npm fullscrnはコチラです。

www.npmjs.com

HTML5 & CSS3 デザインレシピ集
狩野 祐東
技術評論社
売り上げランキング: 3,429
アッと驚く為五郎
アッと驚く為五郎
posted with amazlet at 17.05.28
EMIミュージック・ジャパン (2015-09-23)
売り上げランキング: 247,047

1. リリースファイル

  • fullscrn.js - SCRIPTタグで読み込む用。Browserifyでまとめているからちょっと読みにくいかも。
  • fullscrn.min.js - ↑をミニファイしたもの(uglify使用)
  • index.js - ソースファイル。npmでBrowserifyを使用してrequireする場合はコチラが使用されると思います。

2. サンプル

あまり意味のないサンプルですけどすみません。動きはわかると思います。

sample/injected.html

<body onload="main();">
    <button type="button" onclick="request1();"
    >Full&gt;&gt;</button>
    <span id="panel">
        <button type="button" onclick="request2();"
        >Full&gt;&gt;</button>
        <button type="button" id="exitButton"
        onclick="exit();">&lt;&lt;Exit</button>
    </span>
    <script src="fullscrn.js"></script> 
    <script>
        var panel = document.getElementById("panel");
        var exitButton = document.getElementById("exitButton");
        Fullscreen.debugMode(true);// Enables debug log
        function main() {
            // Handle change event 
            document.addEventListener("fullscreenchange",
                function() {
                    var fse = document.fullscreenElement;
                    console.log("FULLSCREEN CHANGE: " +
                        ((fse == null)? "(null)": "#" + fse.id));
                });
 
            // Handle error event 
            document.addEventListener("fullscreenerror",
                function() { console.log("FULLSCREEN ERROR"); });
 
            request1(); // This should be error 
        }
        function request1() {
            panel.requestFullscreen().then(function(){
                console.log("request1 done.");
            }).catch(function(err) {
                console.error(err.message);
            });
        }
        function request2() {
            exitButton.requestFullscreen().then(function(){
                console.log("request2 done.");
            }).catch(function(err) {
                console.error(err.message);
            });
        }
        function exit() {
            document.exitFullscreen()
            .then(function(){
                console.log("exit done.");
            }).catch(function(err) {
                console.error(err.message);
            });
        }
    </script> 
</body>

3. API

以降の各インターフェースは、標準仕様の通りに使用できるように、DOMの DocumentクラスやElementクラスのprototypeに、インジェクトしています。

ただし、完全一致を狙っているわけではないので、完全に同じ挙動をすることを保証しません。各社ブラウザ間でも挙動が微妙に違っていたりしますが、そこには全く触れていません。

また、2017-05-16にWHATWGの仕様が改定されており、本モジュール作成にあたっては、それ以前の仕様を参考にしていたため、違うところがあるかもしれません(未確認)。

※ 別途、独立したAPIもエクスポートしていますが、使う必要が無いのでここでは説明しません。

1) プロパティ

Document.fullscreenEnabled

フルスクリーンAPIが使用できるかどうかを示すbool型のプロパティ。スクリプトの読み込み時に決定します。

Document.fullscreenElement

全画面モードになっている要素を保持します。全画面モードでないときはnullです。

Document.fullscreen

参照時点で全画面モードかどうかを示すbool型のプロパティ(Document.fullscreenElement != null と同等)。

2) メソッド

以下、どちらも、操作の完了時に解決するPromiseを返します。まあ殆どの場合無視して構いません。

Element.requestFullscreen()

HTML要素で全画面モードを開始する要求を行います。

Document.exitFullscreen()

全画面モードを終了します。

3) イベント

全画面の状態が変化した場合や、エラー発生時のイベントです。

どちらも、document.addEventListenerイベントハンドラを登録して利用してください。 (Document.onfullscreenchange / Documentonfullscreenerror には未対応です)

Document "fullscreenchange"

全画面モードの状態変化時に発生します。

Document "fullscreenerror"

全画面モードに関するエラーがあった時に発生します。

ていうか標準仕様と同じですから。

4. リンク

5. あとがき

標準仕様と実装の混乱ぶり

現状、フルスクリーンAPIはプリフィックス付きで実装されているのに、既にObsoleteな仕様があったりしてカオスです。

さらに「Fullscreenなの?FullScreenなの?どっち?」という若干低レベルな混乱ぶりも垣間見れ、非常に使いづらい状況でした。

各社勝手に実装して、後から仕様をまとめるからこうなるんだろうなあ。知らんけど。

どうでもいいこと

このモジュール、ほとんどフルスクリーンAPIのPolyfillだと言えると思うのですが、npmでは既にfullscreenというモジュールが公開されていたため、仕方なくfullscrnという名になってます。

アチラはAPIのインターフェースがWHATWGの仕様に合致していないっぽいので、色んな意味でちょっと残念。 (# ̄З ̄)

しかしダウンロード数が遥かに多くて(約20倍!)、本家といえばアチラなのかも?