2026/07/29

Chromeから任意のテキストエディタでHTMLソースを開く

長年できないと思っていたこれ。AIを頼ればそろそろできるんじゃないかと思ってやってみたらできたので共有してみる。

コンパイルも何もせず簡単に作ってある、ローカル端末に元ファイルを管理しているだけの完全静的サイトのメンテでは、これをやれると各段にラクなため、今までは渋々既定のブラウザをFirefoxにし、about:configをいじって、表示中のページのソースをCtrl+U一発でいつも使っているエディタ経由で開けるようにしていた。
だが、Firefoxもモダンブラウザといえど、Chromium系とは見た目や挙動の微妙な差が発生することがある。いい加減何とかしようと思い立ってこのほど成功したわけだけど、(手順通りにやれば難しいことはないが)Pythonを利用し、レジストリもいじるので、その時点で無いわという人はここまで。お帰りの際はせっかくなのでDigHelperで気になる音源探しでもお楽しみください。
ちなみにMacには対応していないものの、まるで違うやり方ということではなく、少し変えればいけるらしい。ファイルの中身一式とセットアップ手順をAIに突っ込めば何とかしてくれるでしょう。

ファイル構成と中身

要領としては、「Chrome拡張を自作して、Pythonで橋渡しする」という形になる。
設置先は任意だがひとまず C:\tools\html_opener として進める(変更する場合はファイル内の該当箇所も要変更)。
ファイル構成は以下のとおり。

C:\tools\html_opener
├─ com.my_company.html_opener.json
├─ host.py
├─ run.bat
└─ extension
├─ manifest.json
├─ background.js
└─ icon.png

ファイル一式のダウンロードはここから(ZIP形式)要変更箇所が3つあるので注意(詳細は後述)。同梱のREADME.txtは以下の説明と同じ。
こんな零細個人サイトで配られているZIPファイルなど怖くて開けない、という人のためにソースも公開しておく。各ファイル名をクリックすると展開するので、テキストエディタにコピペして、上記どおりのディレクトリにUTF-8で保存。要変更箇所は色つきで示した。

{
  "name": "com.my_company.html_opener",
  "description": "Open HTML Source in Editor",
  "path": "C:\\tools\\html_opener\\run.bat",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://<YOUR_EXTENSION_ID>/"
  ]
}

import sys
import json
import struct
import subprocess
import tempfile
import os
from urllib.parse import urlparse, unquote

sys.stdin = open(sys.stdin.fileno(), 'rb', buffering=0)
sys.stdout = open(sys.stdout.fileno(), 'wb', buffering=0)

def read_message():
    raw_length = sys.stdin.read(4)
    if not raw_length or len(raw_length) < 4:
        return None
    message_length = struct.unpack('@I', raw_length)[0]
    message = sys.stdin.read(message_length).decode('utf-8')
    return json.loads(message)

def send_message(response_data):
    encoded = json.dumps(response_data).encode('utf-8')
    sys.stdout.write(struct.pack('@I', len(encoded)))
    sys.stdout.write(encoded)
    sys.stdout.flush()

def main():
    data = read_message()
    if not data:
        return

    target_path = None

    if 'filePath' in data:
        target_path = data['filePath']
    elif 'html' in data:
        temp_dir = tempfile.gettempdir()
        
        file_name = "index.html"
        if 'url' in data and data['url']:
            parsed_url = urlparse(data['url'])
            path_str = unquote(parsed_url.path).strip('/')
            
            if path_str:
                if path_str.endswith('/'):
                    path_str += "index.html"
                elif not os.path.splitext(path_str)[1]:
                    path_str += "/index.html"
                
                file_name = path_str.replace('/', '__')

        target_path = os.path.join(temp_dir, file_name)
        
        with open(target_path, "w", encoding="utf-8") as f:
            f.write(data['html'])

    if target_path:
        editor_path = r"<EDITOR_EXECUTABLE_PATH>"
        
        if os.path.exists(editor_path):
            subprocess.Popen([editor_path, target_path], cwd=os.path.dirname(editor_path))
            send_message({"status": "success"})
        else:
            send_message({"status": "error", "message": "editor not found"})

if __name__ == "__main__":
    main()

@echo off
"<PYTHON_EXECUTABLE_PATH>" "C:\tools\html_opener\host.py"

chrome.action.onClicked.addListener(async (tab) => {
  if (tab.url && (tab.url.startsWith("file:///") || tab.url.startsWith("file://"))) {
    try {
      const urlObj = new URL(tab.url);
      let localPath = "";

      if (urlObj.host) {
        localPath = `\\\\${urlObj.host}${decodeURIComponent(urlObj.pathname)}`;
      } else {
        localPath = decodeURIComponent(urlObj.pathname.replace(/^\//, ""));
      }

      localPath = localPath.replace(/\//g, "\\");

      chrome.runtime.sendNativeMessage(
        "com.my_company.html_opener",
        { filePath: localPath },
        () => {
          if (chrome.runtime.lastError) {}
        }
      );
    } catch (e) {}
  } else {
    const [{ result: htmlSource }] = await chrome.scripting.executeScript({
      target: { tabId: tab.id },
      func: () => document.documentElement.outerHTML
    });

    chrome.runtime.sendNativeMessage(
      "com.my_company.html_opener",
      { html: htmlSource, url: tab.url },
      () => {
        if (chrome.runtime.lastError) {}
      }
    );
  }
});

{
  "manifest_version": 3,
  "name": "Open Source in Editor",
  "version": "1.0",
  "permissions": [
    "activeTab",
    "scripting",
    "nativeMessaging"
  ],
  "action": {
    "default_title": "Open Source in Editor",
    "default_icon": "icon.png"
  },
  "icons": {
    "128": "icon.png"
  },
  "background": {
    "service_worker": "background.js"
  }
}

右クリック保存用。なんでもいいけどこんな感じで。Chrome拡張の仕様上、SVGは不可らしい。

セットアップ手順

1. Chrome拡張機能の読み込みとID設定

  1. Chromeで chrome://extensions/ を開く
  2. 右上の「デベロッパーモード」を有効にする
  3. 「パッケージ化されていない拡張機能を読み込む」をクリックし、C:\tools\html_opener\extension を選択する
  4. 発行された「ID」文字列をコピーする(32文字のランダムなアルファベット)
  5. com.my_company.html_opener.json を開き、<YOUR_EXTENSION_ID> の部分をコピーしたIDに置き換えて保存する

2. パスの設定

Pythonの準備・フルパス確認

コマンドプロンプトで以下を実行する

py -0p
パスが表示された場合:

表示された python.exe のフルパスをコピーし、run.bat 内の <PYTHON_EXECUTABLE_PATH> に設定する

「コマンドが見つからない」または何も表示されない場合(未インストール):
  1. 公式サイトからPythonをダウンロードしてインストールする
    ※インストーラー最初の画面で「Add python.exe to PATH」にチェックを入れる
    ※Microsoft Store経由のインストールは動作不良の原因になるため避けること
  2. インストール完了後、再度先述の py -0p を実行し、表示されたフルパスを run.bat の該当箇所に設定する
テキストエディタのフルパス設定

利用するテキストエディタ(VS Code、サクラエディタ、秀丸エディタ等)の実行ファイルパス(例: C:\Program Files...\editor.exe)を確認し、host.py 内の <EDITOR_EXECUTABLE_PATH> に設定する

3. Windowsレジストリへの登録

コマンドプロンプトで以下を実行する:

reg add "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.my_company.html_opener" /ve /t REG_SZ /d "C:\tools\html_opener\com.my_company.html_opener.json" /f

若干面倒だがこれにて完了。あとは拡張機能設定の画面で「更新」をクリックして読み込み直すかChromeを完全に立ち上げ直し、「Open Source in Editor」をピン留めして、Chromeで任意のローカルHTMLファイルを開き、追加された拡張機能アイコンをクリックすると、紐づけたテキストエディタでポコッとソースが開いてくれるはず。ウェブ上のページに対して使うのは想定用途外だが、一応「ディレクトリ名__ファイル名.html」として開くようになっている。
以上、入り用の向きはお役立てをば。