【令和最新版】右クリック禁止解除スクリプト(ブックマークレット)

avatar icon
千本槍みなも@ナタクラゲ
目次

世間では、右クリック禁止を解除するブックマークレットが出回っている。以下のようなものだ。

javascript
javascript: ! function() {
    var a = document,
        b = ["userSelect", "khtmlUserSelect", "MozUserSelect", "MsUserSelect", "WebkitUserSelect"];
    f = function() {
        return true;
    };
    Array.prototype.forEach.call(a.all, function(a) {
        a.style &&
        b.forEach(function(b) {
            a.style[b] = "auto"
        }), a.onmousedown = a.onmousemove = a.onmouseup = a.onselectstart = ""
    }), a.onmousedown = a.onmousemove = a.onmouseup = a.onselectstart = a.oncontextmenu = a.body.oncontextmenu = f;
    obs = document.all;
    for (i = 0; i < obs.length; i++) {
        obs[i].oncontextmenu = 'return true;';
        obs[i].onselectstart = 'return true;';
    }
}();

ソースは以下。

右クリック禁止を無効に – ブログ運営のためのブログカスタマイズ

右クリック禁止を無効に – ブログ運営のためのブログカスタマイズ

・知ってそうで知らないブックマークレットの仕組みと使い方(と便利な11点まとめ) | ライフハッカー[日本版]ブックマークのURLのところにjavascriptを書いておくと、そのブックマークを呼び出すとjavascriptが実行されるとい

右クリック禁止を無効に – ブログ運営のためのブログカスタマイズblog.z0i.net

このサイトで参考とされているのはこれ(リンク切れ)

Page not found

Page not foundkanonji.info

とこれ。

右クリックと文字列選択を禁止する方法とそれを無効にするブックマークレット | You Look Too Cool

右クリックと文字列選択を禁止する方法とそれを無効にするブックマークレット | You Look Too Cool

ウェブページの文字列をコピーされないようにする方法として、右クリックを禁止する方法と文字列の選択を禁止する方法が考えられます。

右クリックと文字列選択を禁止する方法とそれを無効にするブックマークレット | You Look Too Coolstabucky.com

動作原理

このコードは、主要な右クリック禁止方法であるCSSによる指定JavaScriptによるコードの書き換えの2つに対応している。

CSSによる指定

CSSで要素にuser-select-moz-user-selectなどのセレクタを指定すると、その要素の右クリックを禁止できる。bはそれを列挙する関数である。

そこで上記コードの以下の部分では、Array.prototype.forEach.calla.all(documentのすべての子要素)に対して、すべてのbの全セレクタをの値をautoに設定している。

javascript
Array.prototype.forEach.call(a.all, function(a) {
    a.style &&
    b.forEach(function(b) {
        a.style[b] = "auto"
    }), ...

JavaScriptによるコードの書き換え

また、JavaScriptでは、要素でマウスを押下するとelement.onmousedownが、マウスを動かすとelement.onmousemoveが、というようにイベントハンドラの関数が呼び出される。そして、この関数がfalseを返すと、そこでイベントが停止するようになっている。だからブラウザの規定の動作が呼ばれず、選択ができなくなるというわけだ。

javascript
Array.prototype.forEach.call(a.all, function(a) {
    ...,
    a.onmousedown = a.onmousemove = a.onmouseup = a.onselectstart = ""
}), ...

この部分では、まず各要素のonmousedownなどのイベントハンドラに空文字を代入し、何もしないようにしている。

また、この処理

javascript
a.onmousedown = a.onmousemove = a.onmouseup = a.onselectstart = a.oncontextmenu = a.body.oncontextmenu = f;

では、最初に定義したa(つまりdocumentであり、forEach内のaとは異なる)の各イベントハンドラを、最初に定義したtrueを返すだけの関数であるfに置き換えている。

最後に

javascript
obs = document.all;
for (i = 0; i < obs.length; i++) {
    obs[i].oncontextmenu = 'return true;';
    obs[i].onselectstart = 'return true;';
}

ここで全要素のイベントハンドラoncontextmenuonselectstartに文字列としての"return true"を代入して終わり。

問題点

しかしこのコード、よくよく考えてみると不可解な点、改善できる点が多い。10年くらい前に書かれたコードっぽいので仕方ないのだが、複数の解除スクリプトを適当に並べただけ感もあり、あまり綺麗とはいえない。以下に列挙してみる。

varを使用している

constを使いましょう。

document.allを使用している

VSCodeに貼り付けて気づいたが、document.allというプロパティは非推奨で、調べてみるとIE時代の亡霊らしい。IEはもうこの世にいないので、もっと新しい方法を使う。ここではdocument.querySelectorAll("*")に置き換える。ついでにforEachのやり方も少し変わる。

イベントハンドラに文字列を代入している

イベントハンドラに文字列を代入するのはeval的で、セキュリティリスクがあるとされている。今回は任意のユーザ入力を扱うようなものではないが、なるべくならやめておきたい。

その他

  • obsvarletconstもない。
  • obsをなぜかa.allではなくdocument.allと書き直している
  • ループ内でonstartselectを空文字にしたのに、最後に改めて'return true;'を代入し直している
  • 区切りがコンマになっている部分がある。ブックマークレットならではの事情でもあるのかと思ったが、セミコロンの部分もある以上あまり関係ないと思われる

そもそも令和になっても右クリック禁止に効果があると思っているのが一番の不具合であるという点は置いておいて。

改善後のコード

リファクタリングした結果。

javascript
javascript:!function() {
    const d = document;
    const s = [
        "userSelect",
        "khtmlUserSelect",
        "MozUserSelect",
        "MsUserSelect",
        "WebkitUserSelect"
    ];
    const f = () => true;
    d.querySelectorAll('*').forEach(e => {
        e.style && s.forEach(b => {e.style[b] = "auto"});
        e.onmousedown = e.onmousemove = e.onmouseup = e.onselectstart = e.oncontextmenu = f;
    });
    d.onmousedown = d.onmousemove = d.onmouseup = d.onselectstart = d.oncontextmenu = d.body.oncontextmenu = f;
}();

テストはあまりしていないので、性能が維持されているかどうかはわからない。少なくとも進化はしてないと思う。

もっとうまくできる?

今は全要素のイベントハンドラに() => trueを割り当てているが、これには問題があるかもしれない。アプリケーション固有のイベントハンドラを全て無視するということでもあるからだ。

代わりに、現在の動作をラップして実行し、返り値はtrueであるような関数を作れば、元の動作を保ったまま右クリックを解放できるのではないか?

つまりこういうことだ。

javascript
javascript:!function() {
    const d = document;
    const s = [
        "userSelect",
        "khtmlUserSelect",
        "MozUserSelect",
        "MsUserSelect",
        "WebkitUserSelect"
    ];
    const f = (g) => {
        if (typeof g !== 'function') {
            return () => true;
        }
        return function(...args) {
            g.apply(this, args);
            return true;
        };
    };
    d.querySelectorAll('*').forEach(e => {
        e.style && s.forEach(b => {e.style[b] = "auto"});
        e.onmousedown = f(e.onmousedown);
        e.onmousemove = f(e.onmousemove);
        e.onmouseup = f(e.onmouseup);
        e.onselectstart = f(e.onselectstart);
        e.oncontextmenu = f(e.oncontextmenu);
    });
    d.onmousedown = f(d.onmousedown);
    d.onmousemove = f(d.onmousemove);
    d.onmouseup = f(d.onmouseup);
    d.onselectstart = f(d.onselectstart);
    d.oncontextmenu = f(d.oncontextmenu);
    d.body.oncontextmenu = f(d.body.oncontextmenu);
}();

このコードは

javascript
const f = (g) => {
    if (typeof g !== 'function') {
		    return () => true;
    }
    return function(...args) {
        g.apply(this, args);
        return true;
    };
};

この部分がキモで、この関数fは、関数(かもしれない変数)gを受け取り、gを実行しつつもtrueを返す新たな関数を作成する。もしgが関数でなければ、trueを返すだけの関数を作る。

元の動作が失われると問題が出るようなサイトを見たことがないため、果たしてこのコードがうまく動くのかは分かりません。本記事のコードに関してはCC0で自己責任で利用してください。"AS IS"ってやつですね。