Chromeアドオン開発のチュートリアル: ページの背景色を変えるアドオン(Getting Started Tutorial)
※この記事は,GoogleのGetting Started Tutorialを日本語訳したものです。
Chromeアドオンは,複数のコンポーネントによって成り立っています。
コンポーネント同士は,互いに関連性があります(cohesive)。
コンポーネントの種類としては,
- その他のロジックを記述したファイル
などがあります。
アドオンのコンポーネントは,HTML,CSS,JavaScriptといったWeb開発の技術を用いて作成されます。
アドオンごとに機能が異なりますので,必要となってくるコンポーネントも変わってきます。
このチュートリアルでは,一つのアドオンを作ります。
developer.chrome.com のドメイン上にある任意のページ上で,ページの背景色を変更することが可能になるようなアドオンです。
これを作ろうとすると,多くのコアコンポーネントを扱うことになるので,それらの関係を理解するための入門用デモとしてうってつけです。
始めるにあたって,まずこのアドオンのファイルを格納するためのフォルダを新規作成してください。
なお完成版のアドオンはこちらからダウンロードできます。
マニフェストを作成する
アドオン開発は,まずはマニフェスト作りから始まるのです。
manifest.json というファイルを作成し,下記のコードを記述してください。
(あるいは,こちらからダウンロードすることもできます。)
{ "name": "Getting Started Example", "version": "1.0", "description": "Build an Extension!", "manifest_version": 2 }
この時点ですでに,マニフェストのファイルが存在するフォルダを,デベロッパーモードでChromeに読み込ませることができます。
- クロームで chrome://extensions を開き,アドオンの管理ページを開いてください。
- ブラウザのメニューから「その他のツール→拡張機能」で開くこともできます。
- 「デベロッパーモード」の隣にあるツマミをクリックして,デベロッパーモードを有効にします。
- 「パッケージ化されていない拡張機能を読み込む」ボタンをクリックし,先程のアドオン開発用に作ったフォルダを選択します。
読み込めましたね?このアドオンのインストールに成功しました。
マニフェストにアイコンが定義されていないので,このアドオンを表すために,デフォルトのツールバーアイコンが表示されます。
処理を追加する
アドオンのインストールはできましたが,これではまだ,何も処理がありません。
そこで,アドオンのフォルダの中に,バックグラウンド・スクリプトを追加しましょう。
background.js という名前のファイルを作成するか,ここからダウンロードしてください。
バックグラウンドスクリプトもそうですが,アドオンを構成する重要なコンポーネントはどれも皆,マニフェストの中に登録され宣言されていなければなりません。
マニフェストの中に,バックグラウンド・スクリプトを宣言しておくと,アドオンはどのファイルをスクリプトとして参照すればよいのかがわかりますし,そのファイルがどういう風に振る舞うべきかもわかります。
{ "name": "Getting Started Example", "version": "1.0", "description": "Build an Extension!", "background": { // この部分を追記 "scripts": ["background.js"], "persistent": false }, "manifest_version": 2 }
こうしておけば,アドオンはバックグラウンド・スクリプトの存在を識別できます。
(マニフェストを見てわかるように,そのスクリプトは非persistentであるような属性を持っています。)
そしてアドオンは,このスクリプトを読み込み,リッスンすべきイベントを判別します。
このアドオンは,インストールされた直後に,persistent な変数からの情報読み込みを必要とします。
そのために, background.js の中に,runtime.onInstalled というイベントをリッスンするコードを書きましょう。
onInstalled リスナの中で,アドオンはstorage API を経由して,値を保存します。
こうすることで,アドンを構成する他の様々なコンポーネントの中から,その保存された値にアクセスし,値を更新することもできるようになります。
// アドオンのインストール時のイベントをリッスンする chrome.runtime.onInstalled.addListener(function() { // ストレージAPIを経由して値を保存する chrome.storage.sync.set({color: '#3aa757'}, function() { // ログ表示 console.log("The color is green."); }); });
ここではストレージAPIを使っていますが,ほぼどんなAPIを使う場合にも必要になるのが,利用するAPIの "permissions" をマニフェストの中で宣言しておくことです。
マニフェストの中で宣言しておくことで,アドオンはそのAPIを利用可能になります。
{ "name": "Getting Started Example", "version": "1.0", "description": "Build an Extension!", "permissions": ["storage"], // ここを追記 "background": { "scripts": ["background.js"], "persistent": false }, "manifest_version": 2 }
アドオンの管理ページを再度開き,今作っているアドオンのところにあるリロードボタンを押してください。
「ビューを検証」という文言の隣に,「バックグラウンドページ」というリンクができているはずです。
「バックグラウンドページ」のリンクをクリックすると,この部分が有効化され,Chromeの開発者ツールが開きます。
そこでもう一度,Chromeブラウザ上でアドオン管理ページを開き,今作っているアドオンのところにあるリロードボタンを押してみてください。
Chrome開発者ツール上で,"The color is green." というコンソールログが出力されます。
ユーザ・インタフェースを導入する
拡張機能は様々な形態のUI(ユーザ・インタフェース)を持つことができます。
今回の拡張機能の場合は,ポップアップという形でのUIになります。
フォルダ上に popup.html という名前のファイルを作成するか,こちらからダウンロードしてください。
この拡張機能では,背景色を変更するためのボタンが必要です。
<!DOCTYPE html> <html> <head> <style> button { height: 30px; width: 30px; outline: none; } </style> </head> <body> <button id="changeColor"></button> </body> </html>
バックグラウンド・スクリプトと同じように,このファイルもマニフェストに登録する必要があります。
page_action という項目内に,このファイルがポップアップとして動作するように記載しましょう。
{ "name": "Getting Started Example", "version": "1.0", "description": "Build an Extension!", "permissions": ["storage"], "background": { "scripts": ["background.js"], "persistent": false }, "page_action": { // この部分 "default_popup": "popup.html", }, "manifest_version": 2 }
さらに page_action の default_icon という項目内に,ツールバー用のアイコンを指定することができます。
画像フォルダをこちらからダウンロードして,解凍し,拡張機能のフォルダ内に設置してください。
マニフェストを更新し,拡張機能から見てこれらの画像がどう使われるかを指定しましょう。
{ "name": "Getting Started Example", "version": "1.0", "description": "Build an Extension!", "permissions": ["storage"], "background": { "scripts": ["background.js"], "persistent": false }, "page_action": { "default_popup": "popup.html", "default_icon": { // この部分 "16": "images/get_started16.png", "32": "images/get_started32.png", "48": "images/get_started48.png", "128": "images/get_started128.png" } }, "manifest_version": 2 }
拡張機能はさらに,アドオンの管理ページ上などで画像を表示します。
パーミッションの警告時の表示や,favicon としての表示もあります。
これらの用途での画像表示は,マニフェスト内の icons という項目内に記載・指定されます。
{ "name": "Getting Started Example", "version": "1.0", "description": "Build an Extension!", "permissions": ["storage"], "background": { "scripts": ["background.js"], "persistent": false }, "page_action": { "default_popup": "popup.html", "default_icon": { "16": "images/get_started16.png", "32": "images/get_started32.png", "48": "images/get_started48.png", "128": "images/get_started128.png" } }, "icons": { // この部分 "16": "images/get_started16.png", "32": "images/get_started32.png", "48": "images/get_started48.png", "128": "images/get_started128.png" }, "manifest_version": 2 }
この段階で,いま作っている拡張機能をリロードすると,グレイスケールのアイコン付きで拡張機能が表示されます。
しかし,機能的にはまだ何も新しいものを追加されたことになっていません。
マニフェスト内で page_action が宣言されていますが,
いつどんなタイミングでユーザが popup.html を使用できるのかがまだ指定されていないので,
拡張機能がブラウザ上にポップアップを表示できないのです。
そこで,バックグラウンド・スクリプトの runtime.onInstalled のイベントリスナの中に,
declarativeContent API を使って
いつポップアップファイルを使用できるのか明示的に宣言しましょう。
chrome.runtime.onInstalled.addListener(function() { chrome.storage.sync.set({color: '#3aa757'}, function() { console.log('The color is green.'); }); // この部分 chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { chrome.declarativeContent.onPageChanged.addRules( [{ conditions: [ new chrome.declarativeContent.PageStateMatcher({ pageUrl: { hostEquals: 'developer.chrome.com' }, }) ], actions: [ new chrome.declarativeContent.ShowPageAction() ] }] ); }); });
拡張機能は, declarativeContent API を使用するために,マニフェスト中でパーミッション設定が必要です。
{ "name": "Getting Started Example", ... "permissions": ["declarativeContent", "storage"], // この部分 ... }
こうすることで,ここまでブラウザのツールバー上でグレースケールだったアイコンが,フルカラーで表示されるようになります。
URLに "developer.chrome.com" が含まれるようなページにユーザが遷移した時に,
このアイコンはフルカラーになり,
ユーザはそれをクリックしてpopup.htmlを呼び出すことができます。
ポップアップUI導入の最後のステップは,UI内のボタンに色設定の機能をもたせることです。
拡張機能のフォルダに popup.js というファイルを作成するか,こちらからダウンロードしてください。
その中には,下記のようなコードを記述します。
let changeColor = document.getElementById('changeColor'); chrome.storage.sync.get('color', function(data) { changeColor.style.backgroundColor = data.color; changeColor.setAttribute('value', data.color); });
このコードは,popup.htmlの中からボタン要素を抽出し,
ストレージの中から色の設定値を取り出しています。
そして色の設定値をボタンの背景色に適用しています。
このロジック popup.js を,popup.htmlの中にscriptタグとして含めましょう。
<!DOCTYPE html> <html> ... <body> <button id="changeColor"></button> <script src="popup.js"></script> </body> </html>
拡張機能をリロードすると,緑色のボタンが見えるはずです。
これは,ボタンに背景色が適用されているということです。
タブ内の背景色を操作するロジック
ここまでで,拡張機能から見てポップアップが利用可能になりました。
developer.chrome.com上でポップアップを表示し,色のついたボタンを表示する,という所までできました。
ここから先,ユーザとのインタラクションに応じて,あと少しロジックの追加が必要です。
popup.jsに下記のようなコードを追記しましょう。
let changeColor = document.getElementById('changeColor'); ... // ここから追記 changeColor.onclick = function(element) { let color = element.target.value; chrome.tabs.query( { active: true, currentWindow: true }, function(tabs) { chrome.tabs.executeScript( tabs[0].id, { code : 'document.body.style.backgroundColor = "' + color + '";' } ); } ); };
上記のコードは,ボタンのonclickイベントを追加しています。
そこでは,動的に組み上げたJSコードをタブ内に注入しています。これにより,ページの背景色がボタンの背景色と同じ色になります。
このように動的にJSコードを注入すれば,Webページ上に不必要なコードを追記する必要がなく,ユーザの開いたページ上で任意のJSコードを実行することができます。
上記のコードを実行するためには,マニフェストに activeTab のパーミッションを追記します。
これにより,拡張機能は tabs API にアクセスすることが可能になり,tabs.executeScript を実行できるようになります。
{ "name": "Getting Started Example", ... "permissions": ["activeTab", "declarativeContent", "storage"], ... }
これで,今回作ろうとしていた拡張機能の動作が実装できました。
拡張機能をリロードし,Webページをリロードしてから,ポップアップを開き,ボタンを押して緑色にしてみましょう。
もう少し変化を加えてみます。ユーザはきっと,背景色を緑色だけでなく,他の色にも変化させてみたいはずです。
ユーザが選択可能なオプションを増やしてみる
今のところ,この拡張機能は,背景色を緑色に変えることしかできません。
オプションページを追加することによって,ユーザが拡張機能の動作をもっと変えられるようにし,ブラウザーの動作をより一層カスタマイズできるようにしてみましょう。
フォルダ上に options.html というファイルを作り,下記のコードを記述してください。こちらからダウンロードもできます。
<!DOCTYPE html> <html> <head> <style> button { height: 30px; width: 30px; outline: none; margin: 10px; } </style> </head> <body> <div id="buttonDiv"> </div> <div> <p>背景色を選んでください。</p> </div> </body> <script src="options.js"></script> </html>
このオプションページをマニフェストに登録します。
{ "name": "Getting Started Example", ... // ここ "options_page": "options.html", ... "manifest_version": 2 }
拡張機能をリロードし,詳細をクリックします。
詳細ページを下にスクロールし,拡張機能のオプション(Extension options)を選んでオプションページを表示します。今は空白のページとして表示されるはずです。
最後のステップは,オプションページにロジックを追加することです。
options.jsというファイルを作り,下記のコードを記述してください。こちらからダウンロードもできます。
let page = document.getElementById('buttonDiv'); const kButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1']; function constructOptions(kButtonColors) { for (let item of kButtonColors) { let button = document.createElement('button'); button.style.backgroundColor = item; button.addEventListener('click', function() { chrome.storage.sync.set({color: item}, function() { console.log('color is ' + item); }) }); page.appendChild(button); } } constructOptions(kButtonColors);
4つの色の選択肢があって,オプションページ内にボタンが生成され,onclickイベントリスナも作られます。
ユーザがボタンをクリックすると,拡張機能のstorage内の値(グローバル値)を書き換えます。
この拡張機能の全ファイルは,色情報の値をグローバル値としてのstorageから取り出していますので,他に何も更新すべき値はありません。
補足
Googleのドキュメント翻訳に関するライセンス・ポリシー:
Site Policies | Google Developers
https://developers.google.com/terms/site-policies
- When you see a page with this notice you are free to use nearly everything on the page in your own creations. For example, you could quote the text in a book, cut-and-paste sections to your blog, record it as an audiobook for the visually impaired, or even translate it into Swahili. Really. That's what open content licenses are all about. We just ask that you give us attribution when you reuse our work. (スワヒリ語に翻訳しても良い)
- You may also find the following notice on the bottom of some pages: Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies.
Chrome拡張機能とは?(ブラウザのアドオン開発に関する日本語ドキュメント)
※本記事は,クロームのアドオン開発に関するGoogle公式ドキュメントWhat are extensions?の日本語訳です。
Chromeアドオン(拡張機能,エクステンション)とは何か
アドオンとは,ブラウザ上の動作(browsing experience)をカスタマイズするための小さなソフトウェアです。
アドオンを使うと,各ユーザはChromeの機能を「自分好みに改造」できます。
ブラウザの動作を,細かいニーズや設定に合わせて改変することができるのです。
アドオンは,HTMLやJavaScript,CSSなどのWeb系テクノロジーで作られています。
1個1個のChromeアドオンは,それぞれ1個の小さな,単一の目的(single purpose)を果たせるように作成されるべきです。
各アドオンが果たす役割は,理解しやすいように設計しましょう。
ひとつのアドオンの中に,複数の要素(components)や幅広い機能を含めることができますが,
その場合は,すべての機能がアドオンのもともとの開発目的を成し遂げるために役立つようにしてください。
アドオンのUIは,最小限かつ意味のあるものにしてください。
Gmailチェッカーアドオンのように,ブラウザのバー上にシンプルなアイコンを一つ表示するだけでも良いです。
また一方で,ブラウザで表示中のページをまるごと書き換える(override)ような,大がかりなUIのアドオンもあるでしょう。
アドオンのファイルは,ZIP圧縮されて 拡張子 .crx の単一パッケージになっています。
ユーザはその .crx ファイルを,ストア等からダウンロードおよびインストールします。
これはつまり,ChromeのアドオンはWebアプリとは異なるということです。
アドオンが動作するために必要なファイルはパッケージ化されており,外部のWebサイトを読み込んで表示することに依存した構成ではありません。
Chromeアドオンのデベロッパーは,Chromeデベロッパー・ダッシュボード上でアドオンをアップロードします。
そこでアップロードしたアドオンは,Chrome Webストア上で公開されます。
より詳細な情報は,ストアの開発者向けドキュメントをご参照ください。
Hello Extensions(アドオン開発・はじめの一歩)
ここでは Hello Extensions という手短なサンプルを紹介します。
アドオン開発を始めるための小さな一歩です。
まず新しいフォルダを作成し,そこにアドオン開発のために必要なファイルを格納することにしましょう。
必要なファイルは,サンプル置き場からダウンロードしてくることもできます。
新しいフォルダを作成したら,そのフォルダの中に manifest.json というファイルを作成し,下記のコードを記載してください。
{ "name": "Hello Extensions", "description" : "Base Level Extension", "version": "1.0", "manifest_version": 2 }
全てのアドオンに,このような「マニフェスト」が必要です。
(ほとんどのアドオンは,マニフェストを中心に作るわけではありませんが,
今回のサンプルでは,わかりやすくするためにマニフェストファイルを中心に進めます。)
この中のbrowser_actionフィールドに追記しましょう。
このアドオンのポップアップファイル,およびアイコンファイルのファイル名を定義することにします。
下記のように記載してください。
{ "name": "Hello Extensions", "description" : "Base Level Extension", "version": "1.0", "manifest_version": 2, "browser_action": { // ここから下を追記 "default_popup": "hello.html", "default_icon": "hello_extensions.png" } }
ここで指定した hello_extensions.png は,こちらからダウンロードできます。
hello.htmlは,下記のような内容で作成してください。
<html> <body> <h1>Hello Extensions</h1> </body> </html>
(ここまでで,manifest.json およびpng, htmlファイルの合計3ファイルが,フォルダ内に格納されていることになります。)
今作っているのは,「アドオンのアイコンがクリックされたときに,hello.htmlを表示する」という動作です。
次のステップとして, manifest.json の中にキーボードショートカットを記載しましょう。
キーボードショートカットは必須ではありませんが,あると便利です。
{ "name": "Hello Extensions", "description" : "Base Level Extension", "version": "1.0", "manifest_version": 2, "browser_action": { "default_popup": "hello.html", "default_icon": "hello_extensions.png" }, "commands": { // ここから下を追記 "_execute_browser_action": { "suggested_key": { "default": "Ctrl+Shift+F", "mac": "MacCtrl+Shift+F" }, "description": "Opens hello.html" } } }
最後のステップは,あなたのローカルマシン上で,Chromeにこのアドオンをインストールすることです。
Chromeで chrome://extensions に移動してください。(アドレスバーに打ち込めば移動できます)
画面右上の「デベロッパーモード」をONにしてください。
「パッケージ化されていない拡張機能を読み込む」をクリックし,あなたがさっき作った「Hello Extensions」のアドオンのフォルダを選んでください。
これで,アドオンのインストールの完了です。
動作を試すには,ブラウザ上で表示されている hello_world.png のアイコンをクリックするか,Ctrl+Shift+F を押してください。ポップアップによるアドオンの動作を試せます。
次のステップ
次のチュートリアル記事:
Chromeアドオンの開発チュートリアルとしては,下記の記事も実践しやすいのでご参照ください。
今から3分で,Chromeブラウザの独自アドオンを自作してみよう (jQueryを使ったクローム拡張機能を,開発および動作テストする入門) - 主に言語とシステム開発に関して
https://language-and-engineering.hatenablog.jp/entry/2015/10/22/%E4%BB%8A%E3%81%8B%E3%82%89%EF%BC%93%E5%88%86%E3%81%A7%EF%BC%8CChrome%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E7%8B%AC%E8%87%AA%E3%82%A2%E3%83%89%E3%82%AA%E3%83%B3%E3%82%92%E8%87%AA
補足
Googleのドキュメント翻訳に関するライセンス・ポリシー:
Site Policies | Google Developers
https://developers.google.com/terms/site-policies
- When you see a page with this notice you are free to use nearly everything on the page in your own creations. For example, you could quote the text in a book, cut-and-paste sections to your blog, record it as an audiobook for the visually impaired, or even translate it into Swahili. Really. That's what open content licenses are all about. We just ask that you give us attribution when you reuse our work. (スワヒリ語に翻訳しても良い)
- You may also find the following notice on the bottom of some pages: Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies.
Excelブック内の全シートを,1ファイルずつに分けて一括保存するバッチ (シート単位で自動ファイル分割し,1シートごとに一斉ファイル出力)
単一のExcelブック内に,大量のワークシートが存在するとする。
シートがあまりにも多すぎて,ブックのサイズが何メガにも膨れ上がる。
そして,「もはや1ファイルで管理しきれない。
シートごとに別ファイルに分けよう」となる。
そういう場合,各シートを
「自動的に個別のファイルに分けてくれる」ツールがあると便利だ。
100シートあれば,100個のファイルに分割してくれる・・・というわけ。
下記のバッチで,すぐに実行できる。
以下のコードをメモ帳にコピペし,「シートごとに分割.bat」で保存。
そして,シートごとに分割したいExcelファイルを
このバッチにドロップすればよい。
@if(0)==(0) ECHO OFF cscript.exe //nologo //E:JScript "%~f0" %* @pause GOTO :EOF @end /* ドロップしたExcelファイルの全シートを, 1シートずつ個別のファイルに保存し直すバッチ */ // 引数取得 if( WScript.Arguments.length == 0 ) { WScript.Echo("引数がありません。"); WScript.Quit(); } var filename = WScript.Arguments.Unnamed(0); var ws = WScript.CreateObject("WScript.Shell"); var cwd = ws.CurrentDirectory; //var filepath = cwd + "\\" + filename; var filepath = filename; // Excelブックを開く var excel = WScript.CreateObject("Excel.Application"); excel.Visible = true; excel.Workbooks.Open( filepath ); var book = excel.Workbooks( excel.Workbooks.Count ); // ワークブックを取得 var xlMaximized = -4137; excel.ActiveWindow.WindowState = xlMaximized; // 最大化 // 全シートをスキャンして,シート名を調査 var sheets = book.WorkSheets; var e = new Enumerator( sheets ); var arr_sheets = []; for( ; ! e.atEnd() ; e.moveNext() ) { // シートを取得 var sheet = e.item(); // 保持 arr_sheets.push( sheet ); } // 取得済みの全シートについて for( var i = 0; i < arr_sheets.length; i ++ ){ // 新規ワークブックを作成 excel.Workbooks.Add(); var new_book = excel.Workbooks( excel.Workbooks.Count ); // ワークブックを取得 // シートを先頭にコピー挿入 //arr_sheets[i].Copy( new_book.Worksheets( new_book.Worksheets.Count ) ); arr_sheets[i].Copy( new_book.Worksheets( 1 ) ); // 不要シートを消す(Sheet1~Sheet3) new_book.Worksheets( 4 ).Delete(); new_book.Worksheets( 3 ).Delete(); new_book.Worksheets( 2 ).Delete(); // 新規ファイル保存。ファイル名はシート名 var new_filepath = cwd + "\\" + arr_sheets[i].Name + ".xlsx"; WScript.Echo( "新規ブックのパス:" + new_filepath ); new_book.SaveAs( new_filepath ); // 閉じる new_book.Close(); }
このソースは,MS-DOS(コマンドプロンプト)バッチの中に,WSH/JScriptのコードを埋め込んで記述してある。
Excelファイルをドロップすると,バッチの存在するフォルダ内に,
各シートごとに分割されたエクセルが一斉に生成・保存される。
参考にしたページ:
JScript/WSH で,Excelファイルを読み書きしよう
http://language-and-engineering.hatenablog.jp/entry/20090717/p1
Excelブックの「シート目次」を,自動的に作成するバッチ (WSH/JScriptで,各シートへのリンク付きの目次を自動生成)
http://language-and-engineering.hatenablog.jp/entry/20110921/p1
BATとWSHのコードを1ファイルに混在させるためのshebang記法(複雑なバッチを1ファイルで実現)
http://computer-technology.hateblo.jp/entry/20131025/p1
関連する記事:
WSH/JScriptで,Excelファイルを読み書きする際のテンプレート (シート上の全行を読み書きするコードのひな型) - 主に言語とシステム開発に関して
http://language-and-engineering.hatenablog.jp/entry/20140214/p1
ドキュメント作成を楽にするための,Excel VBA 頻出8パターン - 主に言語とシステム開発に関して
http://language-and-engineering.hatenablog.jp/entry/20090401/p1
Excelシートを読み取って,INSERT文SQLを生成するバッチ (Kingsoft SpreadsheetをWSH/JScriptで自動操作) - 主に言語とシステム開発に関して
http://language-and-engineering.hatenablog.jp/entry/20121218/p1
Excel VBAのマクロを,複数のブックから利用する方法 (標準モジュールをブックの外部で管理して,共通ライブラリとして読み込み) - 主に言語とシステム開発に関して
http://language-and-engineering.hatenablog.jp/entry/20090731/p1