그리스몽키에 빠져보기

2005-05-09

이 책은 http://diveintogreasemonkey.org/에 살고 있다. 다른 곳에서 이 책을 읽고 있다면, 최신 버전인지 확인해 보자.

이책은 샘플 코드 그리고 부록 비디오와 함께 무료 소프트웨어이다. 자유 소프트웨어 재단이 공표한 GNU 일반 공개 라이센스나 버전 2 또는 (선택에 따라) 그 이후의 버전 라이센스 조항 아래에서 재배포해도 좋다. 이 책은 샘플코드와 부록 비디오와 함께 도움이 되기를 바라는 마음으로 배포하지만 보증을 해주지는 않는다; 묵시적으로라도 특정한 목적에 맞다거나 상업적으로 이용가능하다고 보증하지 않는다. 더 자세한 사항은 GNU 일반 공개 라이센스를 참조하라.

목차

제 1 장 시작하기

1.1. 그리스몽키란 무엇인가?

그리스몽키(Greasemonkey)는 파이어폭스(Firefox) 확장으로서 스크립트를 작성해서 방문한 웹 페이지를 바꿀수 있다. 웹 사이트를 더 읽기 편하게 또는 더 유용하게 만들 수 있다. 화면가공 버그를 고칠 수 있어서 사이트 주인에게 고쳐달라고 조르지 않아도 된다. 페이지를 바꾸어서 웹 페이지를 읽어 주거나 점자로 바꾸어주는 장애보조 테크놀로지와 더 잘 작동시킬 수 있다. 다른 사이트들로부터 자동으로 데이터를 열람해서 두 사이트를 서로 더 긴밀하게 연결시킬 수 있다.

그리스몽키 그 자체는 이런 일들을 전혀 하지 않는다. 사실 설치하고 나면, 전혀 변화를 느끼지 못한다... “사용자 스크립트”를 설치할 때 까지는 말이다. 사용자 스크립트는 그저 자바스크립트 코드 쪼가리일 뿐이다. 그리스몽키에게 언제 어디에서 실행해야 하는지 알려주는 약간의 정보가 더 있을 뿐이다. 사용자 스크립트는 특정한 페이지, 특정한 사이트, 또는 사이트 크룹을 목표로 하여 자바스크립트로 할 수 있는 무엇이든 할 수 있다. 사실, 그 이상을 할 수도 있다. 그리스몽키는 사용자 스크립트에서만 사용가능한 특별한 기능을 제공하기 때문이다.

그리스몽키 스크립트 저장소에는 사람들이 개인적인 고충을 해결하기 위하여 작성한 수 백가지 사용자 스크립트가 있다. 독자적인 스크립트를 만들었다면, 그 코드를 저장소에 추가하셔도 좋다. 다른 사람들에게 쓸모가 있을 거라고 생각한다면 말이다. 또는 그냥 보관하셔도 좋다. 자신만의 브라우징 경험을 만들었다는 자신감이 넘쳐날 것이다.

그리스몽키 메일링 리스트도 있는데, 여기에서 질문하고, 사용자 스크립트를 선언하며, 그리고 새로운 특징들에 대한 아이디어를 토론할 수 있다. 그리스몽키 개발자들은 그 메일링 리스트에 자주 들린다; 어쩌면 그들이 여러분의 질문에 대답해 줄지도 모르겠다!

왜 이책인가?

그리스몽키에 빠지기(Dive Into Greasemonkey)는 그리스몽키 메일링 리스트에서의 토론에 힘입어 성장하였고, 사용자 스크립트를 만들어 본 경험으로부터 성장하였다. 메일링 리스트에 참여한지 일주일이 지나자, 나는 새내기들이 던지는 질문에 이미 대답이 있는 것을 보았다. 겨우 몇가지 사용자 스크립트를 확보했을 뿐인데도, 벌써 공통적인 패턴이 있는 것이 보였고, 특정한 문제들을 해결하는 재사용 가능한 코드 쪼가리들이 나오고 또 나오는 것을 보았다. 가장 유용한 패턴들을 문서화하기 시작했고, 나만의 코딩 결정을 설명하고, 그 과정에서 가능하면 많이 배웠다.

이 책은 그리스몽키 개발자들의 도움이 없었더라면 오늘날 같은 성과의 절반에도 이르지 못했을 것이다. 아론 부드맨(Aaron Boodman)과 제레미 던크(Jeremy Dunck), 그리고 기타 여러분들이 초안에 귀중한 되먹임을 주셨다. 모두에게 감사드리는 바이다.

1.2. 그리스 몽키 설치하기

사용자 스크립트를 작성하려면 먼저, 그리스몽키 브라우저 확장을 설치할 필요가 있다(0.3 이하는 보안 문제로 금지함).

절차: 그리스몽키 확장을 설치하기

  1. 그리스몽키 홈 페이지를 방문한다.

  2. 제목이 “Install Greasemonkey”인 링크를 클릭한다.

  3. 파이어폭스가 사이트에서 소프트웨어를 설치하는 것을 막았다고 (창의 상단에) 알려줄 것이다. Edit options...을 클릭하여 “Allowed Sites” 대화상자를 가져온다. 다음 Allow를 클릭해 그리스몽키 사이트를 소프트웨어 설치를 허용한 사이트 목록에 추가한다. OK를 클릭해 “Allowed Sites” 대화상자를 닫는다.

  4. 다시 한번, “Install Greasemonkey”이라는 제목의 링크를 클릭한다.

  5. 이제 설치 대화상자가 나타나 진짜로 설치하기 원하는지 확인한다. 몇 초를 기다리면 설치 버튼이 활성화되고, 그러면 Install now를 클릭한다.

    [screenshot of Software Installation dialog]
  6. 브라우저를 다시 기동한다.

브라우저가 다시 기동되면, Tools 메뉴을 선택하자. 세가지 메뉴 항목이 새로 보일 것이다: Install User Script..., Manage User Scripts..., 그리고 User Script Commands가 그것이다. 오직 Manage User Scripts...만이 활성화될 것이다. 그러나 그래도 좋다. 다른 것들은 오직 특별한 상황에서만 활성화된다.

기본으로, 그리스몽키를 설치하더라도 (앞의 세 가지 메뉴 항목 말고는) 브라우저에 어떤 기능이 덧붙는 것은 아니다. 하는 일은 오직 여러분이“사용자 스크립트”라는 추가 항목들을 설치하도록 해주는 것이다. 사용자 스크립트는 특정한 웹 페이지들을 맞춤재단해 준다.

1.3. 사용자 스크립트 설치하기

그리스몽키 “사용자 스크립트”는 하나 이상의 웹 페이지들을 맞춤재단해 주는 자바스크립트로 작성된 한 개짜리 파일이다.

[Tip]

많은 사용자 스크립트는 그리스몽키 스크립트 저장소에서 얻을 수 있다. 그렇지만 꼭 거기에서 가져올 필요는 없다. 아무 곳에서나 사용자 스크립트를 가져와도 된다 (또는 사용자가 설치할 수 있다). 웹 서버조차 필요없다; 사용자 스크립트를 지역 파일에서 설치해도 된다.

[Note]

사용자 스크립트의 파일이름은 반드시 .user.js로 끝나야 한다.

내가 작성한 첫 사용자 스크립트는 이름이 “Butler”였다. 버틀러는 구글 검색 결과에 기능을 추가한다.

절차: 버틀러 사용자 스크립트 설치하기

  1. Butler 홈페이지를 방문해서 버틀러가 제공하는 기능들의 간략한 설명을 보자. (모든 사용자 스크립트가 홈페이지를 가진 것은 아니다; 그리스몽키가 신경쓰는 것은 오직 스크립트일 뿐이다.)

  2. 제목이 “Download version...”인 링크를 클릭한다 (이 글을 쓰는 시점에서 버전은 0.3임), 그러면 브라우저에 몇 페이지에 걸쳐서 스크립트가 보일 것이다.

  3. Tools 메뉴에서, Install User Script... 항목이 이제 활성화되어 있을 것이다. 그것을 선택하자.

  4. 제목이 “Install User Script”인 대화상자가 튀어 나와서, 설치하고자 하는 스크립트의 이름과 간략한 설명 그리고 포함된 페이지와 배제된 페이지 목록이 표시된다. 이 모든 정보는 스크립트 자체에서 가져온다; 그것을 정의하는 법은 메타데이터로 사용자 스크립트 기술하기 섹션에서 배울 것이다.

  5. OK를 클릭해서 사용자 스크립트를 설치한다.

모든 것이 잘 되었다면, 그리스몽키는 “Success! Refresh page to see changes.”라는 주의 창을 화면에 띄워줄 것이다.

이제, Google에서 무언가를 찾아보자. 검색 결과 페이지 윗쪽에 “Try your search on: Yahoo, Ask Jeeves, AlltheWeb, ...”이라는 줄이 있다. 또 상단에 “Enhanced by Butler”라는 배너도 있다. 이 모든 것들은 버틀러 사용자 스크립트가 덧붙인 것이다.

더 읽어야 할 것

1.4. 사용자 스크립트 관리하기

원하는 만큼 그리스몽키 스크립트를 설치할 수 있다. 그리스몽키는 그래픽 환경구성 대화상자로 사용자 스크립트를 관리한다: 잠시 무력화 시키거나, 환경구성을 바구거나, 또는 완전히 설치를 제거한다.

절차: 버틀러를 잠시 무력화시키기

  1. 메뉴에서, ToolsManage User Scripts...를 선택한다. 그리스몽키가 “Manage User Scripts”라는 제목의 대화상자를 띄워줄 것이다.

  2. 그 대화상자 왼쪽 구역에 설치된 모든 사용자 스크립트의 목록이 있다. (처음부터 이 글을 따라왔다면, 여기에는 그저 스크립트가 하나일 것이다: Butler 스크립트 말이다.)

  3. 아직 선택되지 않았다면 그 목록에서 버틀러를 선택하고 Enabled 체크박스를 선택한다. 왼쪽 구역의 “Butler”의 색깔이 미묘하게 흑색에서 회색으로 변화할 것이다. (이것은 선택되어 있어도 보기가 어렵다. 그러나 수십가지 스크립트가 설치되어 있다면 더 유용하다.)

  4. OK를 클릭해서 “Manage User Scripts” 대화상자를 빠져나오자.

이제 버틀러는 설치되어 있으나, 비활성 상태이다. 구글에서 무언가를 검색해보면 이것을 확인해 볼 수 있다. 이제 더 이상 위 쪽에 “Enhanced by Butler”가 보이지 않을 것이다. 버틀러를 다시-활성화시키려면 위의 절차를 반복하고 “Manage User Scripts” 대화상자에서 Enabled 체크박스를 다시 선택하면 된다.

[Note]

사용자 스크립트를 “임시로” 무력화시키는 법을 언급하고 있지만, 명시적으로 재-활성화할 때까지 그대로 불능으로 있을 것이다. 정말로 임시적인 것은 쉽게 재활성화 할 수 있다는 것이다. 원래 스크립트를 나의 웹 사이트에서 찾아 다시 설치할 필요가 없다.

Manage User Scripts” 대화상자에서 스크립트의 설치를 완전히 제거할 수도 있다.

절차: 버틀러 설치제거하기

  1. 메뉴에서, ToolsManage User Scripts...를 선택한다. 그리스몽키가 “Manage User Scripts” 대화상자를 띄워줄 것이다.

  2. 왼쪽 구역에서, 버틀러를 선택해 Uninstall을 클릭한다. 다시 확인하지 않는다; 사용자 스크립트는 즉시 설치가 제거된다.

  3. 제 3 단계... 는 없다! (제프 골드블룸(Jeff Goldblum)에게 사과의 말씀을...)

그러나 기다리자. 남은 것이 있다! 이전에 설치한 사용자 스크립트의 환경구성을 바꿀수도 있다. 처음 버틀러를 설치할 때 보았던 그 대화상자를 기억하시는지? 포함하고 배제할 사이트들이 담긴 두 목록이 있던 대화상자 말이다. 자, 언제든지 “Manage User Scripts” 대화상자에 있는 그 매개변수들을 직접 바꿀 수 있다.

예를 들어, 버틀러는 마음에 들지만 구글의 제품 비교 사이트인 Froogle에는 전혀 사용하지 않는다고 해보자. 사용자 스크립트의 환경구성을 바꾸어서 그 사이트를 제외할 수 있지만, 다른 구글 사이트에는 작동하도록 그대로 두자.

절차: 버틀러를 재구성하여 Froogle 그대로 두기

  1. 메뉴에서, ToolsManage User Scripts...를 선태하자. 그리스몽키가 “Manage User Scripts” 대화상자를 띄워줄 것이다.

  2. 왼쪽 구역에서, “Butler”를 선택하자. 오른쪽 구역에, 리스트 두개가 나타날 것이다, 하나는 포함된 페이지들이고 (“http://*.google.*/*”) 또하나는 제외된 페이지들이다 (blank).

  3. Excluded pages” 목록 다음에, Add...를 클릭하자.

  4. 그리스몽키가 “Add Page”라는 제목의 두 번째 대화상자를 띄워주고 새로운 URL의 입력을 기다릴 것이다. http://froogle.google.com/*를 입력하고 OK를 클릭한다.

  5. Manage User Scripts” 대화상자로 되돌아가면, 제외된 페이지 목록에 이제 새로운 URL 와일드카드, http://froogle.google.com/*가 포함되어 있을텐데, 이것은 버틀러가 froogle.google.com 사이트의 페이지에서는 실행되지 않을 것이라는 뜻이다. 별표는 간단한 와일드카드로 기여하고, 그것을 URL의 어느 곳에나 사용해도 좋다: 도메인 이름, 경로, 또는 URL 체계 (http://) 안에서 사용해도 된다.

  6. OK를 클릭해 “Manage User Scripts” 대화상자를 빠져나온다. 그리고 Froogle에서 제품을 검색해 버틀러가 더 이상 실행되지 않는지 확인하자. 보통의 웹 검색 결과, 이미지 검색 결과, 그리고 기타 구글 사이트에는 여전히 실행될 것이다.

제 2 장 처음 만들어 보는 사용자 스크립트

2.1. Hello World

그리스몽키라는 경이로운 세계로의 긴 여정이 한 단계 진전했다. 기술 매뉴얼을 보면 흔히 보는 그런 단계이다: 컴퓨터에게 “Hello world”를 인쇄하라고 시키는 것이 그것이다.

예제: helloworld.user.js

// Hello World! example user script
// version 0.1 BETA!
// 2005-04-22
// Copyright (c) 2005, Mark Pilgrim
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Hello World", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          Hello World
// @namespace     http://diveintogreasemonkey.org/download/
// @description   example script to alert "Hello world!" on every page
// @include       *
// @exclude       http://diveintogreasemonkey.org/*
// @exclude       http://www.diveintogreasemonkey.org/*
// ==/UserScript==

alert('Hello world!');

보시다시피, Hello World 사용자 스크립트에서 대부분의 줄들은 주석이다. 이런 주석중에, 설치하는 법에 관한 설명같은 것은 특별한 의미가 없다; 그냥 초보 최종 사용자들을 위하여 두었을 뿐이다. 그렇지만, 주석에는 특별한 의미를 가지는 부분이 있다. 다음 섹션에서 보여주겠다.

사용자 스크립트가 작동하는 것을 보려면, 보통의 방법대로 설치하고, diveintogreasemonkey.org 도메인 말고 다른 사이트를 방문하면 된다 (예를 들어, Google). 페이지는 정상처럼 보이겠지만, “Hello world!”라는 경고창이 뜬다.

내려받기

2.2. 메타데이터로 사용자 스크립트 기술하기

사용자 스크립트마다 메타데이터 섹션이 있다. 여기에서 그리스몽키에게 어디로부터 왔는지, 그리고 언제 실행할지, 스크립트에 관한 정보를 알려준다.

예제: Hello World metadata

// ==UserScript==
// @name          Hello World
// @namespace     http://diveintogreasemonkey.org/download/
// @description   example script to alert "Hello world!" on every page
// @include       *
// @exclude       http://diveintogreasemonkey.org/*
// @exclude       http://www.diveintogreasemonkey.org/*
// ==/UserScript==

여기에는 여섯가지 메타데이터가, 그리스몽키-전용의 주석에 둘러싸여 있다. 순서대로 살펴보자. 먼저 포장자부터 시작한다.

// ==UserScript==
//
// ==/UserScript==

이 주석은 중요하다. 그리고 반드시 일치해야 한다. 그리스몽키는 이 주석을 사용하여 사용자 스크립트 메타데이터의 시작과 끝을 알린다. 이 섹션은 스크립트의 어느 곳에나 정의할 수 있지만, 보통 윗 쪽에 정의된다.

그리스몽키 메타데이터 섹션 안에서, 첫 항목은 이름이다.

// @name          Hello World

이것은 사용자 스크립트의 이름이다. 스크립트를 맨 처음 설치할 때, 설치 대화상자에 나온다. 그리고 나중에 “Manage User Scripts” 대화상자에 나온다. 짧게 요약되어야 한다.

@name은 선택적이다. 있으면, 오직 한 번만 나타날 수 있다. 없으면 기본값은 .user.js 확장자를 뺀 사용자 스크립트의 이름이다.

다음으로 이름공간이 온다.

// @namespace     http://diveintogreasemonkey.org/download/

이것은 URL로서, 그리스몽키가 이것을 이용하여 이름은 같지만 저자는 다른 사용자 스크립트를 분별한다. 도메인 이름이 있다면, 그걸 (또는 서브리렉토리를) 이름공간으로 사용해도 좋다. 그렇지 않으면 tag: URI를 사용해도 된다.

@namespace은 선택적이다. 있으면, 오직 한 번만 나타나야 한다. 없으면, 기본값은 사용자 스크립트를 내려받은 도메인이다.

[Tip]

사용자 스크립트의 메타데이터는 순서없이 지정해도 된다. 나는 @name, @namespace, @description, @include, 그리고 마지막으로 @exclude의 순서를 좋아하지만, 이 순서에 특별한 의미는 없다.

다음으로 설명이 온다.

// @description   example script to alert "Hello world!" on every page

이것은 사용자 스크립트가 무엇을 하는지 인간이-읽을 수 있는 형태로 기술된다. 처음 스크립트를 설치할 대 설치 대화상자에 나타나고, 나중에 “Manage User Scripts” 대화상자에 나타난다. 두 문장 이상 넘으면 안된다.

@description은 선택적이다. 있다면, 한 번만 나타나야 한다. 없다면, 기본값은 빈 문자열이다.

[Important]

@description 섹션을 빼먹지 말자. 사용자 스크립트가 오직 자신만을 위한 것이라고 할지라도, 결국 수십가지가 넘어갈 것이고, 설명이 없다면 그 모두를 “Manage User Scripts” 대화상자에서 관리하기가 더 어려워질 것이다.

다음 세 줄은 가장 중요한 항목들이다 (그리스몽키의 관점에서): @include@exclude URL들이 그것이다.

// @include       *
// @exclude       http://diveintogreasemonkey.org/*
// @exclude       http://www.diveintogreasemonkey.org/*

이 줄들은 어느 사이트에서 사용자 스크립트기를 원하는 지 그리스몽키에게 알려준다. 도메인 이름이나 경로의 일부분에 간단한 와일드카드로 * 문자와 함께, URL을 지정하자. 이 경우에는 그리스몽키에게 Hello World 스크립트를 http://diveintogreasemonkey.org/ 그리고 http://www.diveintogreasemonkey.org/를 제외한 모든 사이트에서 실행하라고 지시한다. 배제가 포함보다 우선권이 있다. 그래서 http://diveintogreasemonkey.org/download/* (모든 사이트)에 부합하더라도, http://diveintogreasemonkey.org/*에도 일치하기 때문에 제외될 것이다.

@include@exclude는 선택적이다. 얼마든지 필요한 만큼 URL을 지정할 수 있지만, 한 줄에 하나씩만 지정해야 한다. 아무것도 지정하지 않으면, 그리스몽키는 (마치 @include *로 지정한 것처럼) 사용자스크립트를 모든 사이트에서 실행한다.

[Note]

@include@exclude 메타데이터에 정확을 기할 필요가 있다. 그리스몽키는 최종 사용자가 동등하다고 착각할 URL에 관하여 어떤 가정도 하지 않는다. 한 사이트가 http://example.com/http://www.example.com/에 모두 반응한다면, 두 변종을 모두 선언할 필요가 있다.

더 읽어야 할 것

2.3. 사용자 스크립트 코딩하기

우리의 첫 사용자 스크립트는 실행되면 그냥 화면에 “Hello orld!” 경고를 표시한다.

예제: Display the “Hello world!” alert

alert('Hello world!');

이 코드가 너무 뻔해 보이고 정확하게 원하는 바를 실행하고 있지만, 그리스몽키는 배경뒤에서 수 많은 일을 하고 있다. 배경뒤에서 사용자 스크립트가 원래 페이지에 정의된 다른 스크립트와 상호작용이 잘못되지는 않는지 확인한다. 구체적으로 말하면, 사용자 스크립트를 익명 함수 포장자 안에 자동으로 포장한다. 보통은 이를 무시해도 되지만, 결국 기어올라 엉덩이를 물 것이다. 그래서 지금 잘 배워 두는 것이 좋다.

여러분을 물어 뜯는 한가지 가장 흔한 방식은 사용자 스크립트 안에 정의한 변수와 함수를 다른 스크립트에서 사용할 수 없다는 것이다. 실제로, 사용자 스크립트가 실행을 마치고 나면 사용이 불가능하다. 이것은 곧 자신만의 함수들을 나중에 호출할 수 있다고 생각한다면 문제에 봉착한다는 뜻이다. window.setTimeout 함수를 사용하거나, 또는 문자열-기반의 onclick 속성을 링크에 설정하고 자바스크립트가 함수 이름들을 나중에 평가할 것이라고 예상함으로써 말이다.

예를 들어, 이 사용자 스크립트는 helloworld라는 함수를 하나 정의하고, 다음 1 초 후에 호출하려고 타이머를 설정하려고 시도한다.

예제: 함수 호출을 지연시키는 나쁜 방법

function helloworld() {
    alert('Hello world!');
}

window.setTimeout("helloworld()", 60);

이것은 작동하지 않는다; 경고 창이 표시되지 않을 것이다. 자바스크립트 콘솔을 열어보면, 예외가 보일 것이다: Error: helloworld is not defined. 이것은 시간이 초과될 때까지 그리고 helloworld()에 대한 호출이 평가될 때까지, helloworld 함수가 존재하지 않기 때문이다.

사용자 스크립트의 변수나 함수를 나중에 참조할 필요가 있다면, 명시적으로 window 객체의 특성으로 정의해 둘 필요가 있다. 이 객체는 언제나 사용가능하니까 말이다.

예제: 함수 호출을 지연시키는 좀 나은 방법

window.helloworld = function() {
    alert('Hello world!');
}

window.setTimeout("helloworld()", 60);

이것은 예상대로 작동한다: 페이지 적재후 1초가 지나면, 경고 팝업이 자랑스럽게 나타난다“Hello world!

그렇지만, window에 특성을 설정하는 것도 이상적인 것은 아니다; 지역 변수로 충분한데도 불구하고 마치 전역 변수를 사용하는 것과 비슷하다. (실제로, 정확하게 그와 같다. window는 전역적이고 페이지의 모든 스크립트에서 볼 수 있기 때문이다.) 좀 실용적으로 말하면, 결과적으로 페이지에 정의된 또는 심지어 다른 사용자 스크립트에 정의된 스크립트를 방해할 수도 있다.

가장 좋은 해결책은 익명 함수를 직접 정의해서 그것을 첫 인자로 window.setTimeout에 넘기는 것이다.

예제: 함수 호출을 지연시키는 가장 좋은 방법

window.setTimeout(function() { alert('Hello world!') }, 60);

여기에서는 이름없는 함수(“익명 함수”)를 만들고, 다음 곧바로 그 함수를 window.setTimeout에 건네고 있다. 이렇게 하면 앞 예제와 같은 일을 달성한다. 그러나 아무 흔적을 남기지 않는다. 즉 다른 스크립트에서 탐지가 불가능하다.

사용자 스크립트를 작성할 때 나는 익명 함수를 주기적으로 사용한다는 사실을 깨닫았다. 익명함수는 “one-off” 함수를 작성해서 그 함수들을 window.setTimeout, document.addEventListener 같은 것에 건네거나, 또는 clicksubmit 같은 이벤트 처리자들에 할당하는데 꼭 맞다.

2.4. 사용자 스크립트의 편집

스크립트 저자들에게, “Manage User Scripts” 대화상자에는 특히 유용한 특징이 구비되어 있다: Edit 버튼이 그것인데 설치된 스크립트를 “산채로” 편집할 수 있다.

절차: Hello World 소스 코드를 편집하고 그 결과를 보자

  1. 메뉴에서, ToolsManage User Scripts...를 선택한다. 그리스몽키가 “Manage User Scripts” 대화상자를 띄워줄 것이다.

  2. 왼-쪽 구역에서, Hello World를 선택하고 Edit을 클릭한다. 이렇게 하면 텍스트 편집기에 설치된 Hello World 버전이 열린다. (그렇지 않다면, .js 파일이 텍스트 편집기에 연결되어 있는지 확인하자.)

  3. alert 서술문을 대신에 “Live editing!”이라고 화면에 표시하도록 바꾸자.

  4. 편집기에 수정된 내용을 저장하자. 다음 다시 브라우저로 돌아와 페이지 갱신으로 테스트해 보자. 수정한 결과를 즉시 보실 수 있을 것이다; 사용자 스크립트를 재설치 하거나 “refresh”할 필요가 전혀 없다. “산채로” 편집하기만 하면 된다.

[Tip]

Edit을 “Manage User Scripts” 대화상자에서 클릭할 때, 파이어폭스 프로파일 디렉토리 안에 깊숙히 묻힌 사용자 스크립트의 사본을 “산채로” 편집하고 있는 것이다. 나는 이런 버릇이 들었다. 일단 “산채로” 편집 세션을 마치면, 마지막으로 한 번 더 텍스트 편집기로 돌아가 FileSave as...를 선택해서, 그 사용자 스크립트를 또다른 디렉토리에 저장한다. 꼭 그래야 하는 것은 아니지만 (그리스몽키는 프로파일 디렉토리에 있는 사본에만 주의를 기울인다), 나는 나머지 작업을 하는 동안 스크립트의 “마스터 사본”을 또다른 디렉토리에 보관하는 것을 선호한다.

제 3 장 사용자 스크립트 디버깅하기

3.1. 자바스크립트 콘솔로 충돌 추적하기

사용자 스크립트가 실행되는 것 같지 않으면, 제일 먼저 점검할 것은 자바스크립트 콘솔이다. 이 콘솔에 사용자 스크립트를 포함하여 모든 스크립트-관련 에러가 나열된다.

절차: 자바스크립트 콘솔 열기

  1. 파이어폭스 메뉴에서, ToolsJavascript Console을 선택한다.

  2. 콘솔에 파이어폭스를 연 이후로 방문한 모든 페이지에 관련된 에러들이 모두 나열된다. 상당히 많을 수 있다. (주기적으로 충돌하는 스크립트를 가진 사양-높은 사이트가 얼마나 많은지를 알면 놀랄 것이다.) Clear를 클릭하여 리스트를 지우고 나서 디버그를 시작하자.

이제 작업할 페이지를 갱신하여 아무것도 하지 않는 것 같은 사용자 스크립트를 테스트하자. 정말로 충돌하고 있으면, 예외가 자바스크립트 콘솔에 나타난다.

[Note]

사용자 스크립트가 충돌하면, 자바스크립트 콘솔은 예외와 줄 번호를 화면에 표시한다. 그리스몽키가 사용자 스크립트를 페이지에 삽입하는 방식 때문에, 줄 번호는 실제로 도움이 되지 않아서 무시하는 것이 좋다. 예외가 일어나는 곳은 사용자 스크립트 안의 줄 번호 위치가 아니다.

3.2. GM_log로 로깅하기

그리스몽키는 상황기록 기능을 제공한다. GM_log가 그것인데, 메시지를 자바스크립트 콘솔에 쓸 수 있다. 그런 메시지들은 배출되기 전에 걸러야 하지만, 디버깅에는 아주 도움이 된다. 게다가, 그 콘솔에 로그 메시지가 쌓이는 것을 감시하는 것이 훨씬 더 만족스럽다. 경고를 코드 곳곳에서 뿌려대고 OK를 누르고 또 누르는 것보다 말이다.

GM_log는 기록될 문자열로 인자 하나를 취한다. 자바스크립트 콘솔에 기록한 후에, 사용자 스크립트는 계속해서 정상적으로 실행된다.

예제: Write to JavaScript Console and continue (gmlog.user.js)

if (/^http:\/\/diveintogreasemonkey\.org\//.test(window.location.href)) {
    GM_log('running on Dive Into Greasemonkey site w/o www prefix');
} else {
    GM_log('running elsewhere');
}
GM_log('this line is always printed');

이 사용자 스크립트를 설치하고 http://diveintogreasemonkey.org/를 열어보면, 다음 두 줄이 자바스크립트 콘솔에 나타난다:

Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
running on Dive Into Greasemonkey site w/o www prefix
Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
this line is always printed

보시다시피, 그리스몽키에는 사용자 스크립트의 메타데이터 섹션에서 취한 이름공간과 스크립트 이름, 그리고 GM_log에 인자로 건네어진 메시지가 포함된다.

http://diveintogreasemonkey.org/ 말고 다른 곳을 방문하면, 다음 두 줄이 자바스크립트 콘솔에 나타난다:

Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
running elsewhere
Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
this line is always printed

기록 메시지의 길이 제한을 알아내려고 했으나 실패했다. 길이는 255 문자 이상이다. 게다가, 자바스크립트 콘솔에서 줄들은 적절하게 접혀지기 때문에, 언제든지 화면을 내려서 나머지 로그 메시지를 볼 수 있다. 로깅에 미쳐보자!

[Tip]

자바스크립트 콘솔에서, 어떤 줄에서든 (맥 사용자는 control-click) 오른쪽-클릭을 한다음 Copy를 선택하면 그 줄을 클립보드로 복사할 수 있다.

다음도 참조

3.3. DOM 검열자로 요소 들여자보기

DOM 검열자는 어떤 페이지든 해석된 문서 객체 모델(DOM)을 탐험할 수 있도록 해준다. HTML 요소, 속성, 그리고 텍스트 노드에 관한 세부정보를 얻을 수 있다. 각 페이지의 스타일시트로부터 모든 CSS 규칙들을 볼 수 있다. 한 객체가 가진 스크립트 가능 특성들을 모두 탐험할 수 있다. 아주 강력하다.

DOM 검열자는 파이어폭스 설치 프로그램에 포함되지만, 플랫폼에 따라 기본으로 설치되어 있지 않을 수도 있다. Tools 메뉴에 DOM Inspector가 보이지 않는다면, 파이어폭스를 재설치해 DOM 검열자를 얻을 필요가 있다. 이렇게 해도 기존의 책갈피, 선호사항, 확장, 또는 사용자 스크립트에 영향을 미치지 않는다.

절차:  DOM 검열자 설치하기

  1. 파이어폭스 설치 프로그램을 실행한다.

  2. 라이센스 동의 조항을 받아들인 후에, Custom 설치를 선택한다.

  3. 목적지 디렉토리를 선택하고 나면, 설치 마법사가 설치하고 싶은 추가 구성요소들을 고르도록 요구한다. Developer Tools를 선택하자.

  4. 설치가 완료되면, 파이어폭스를 실행한다. ToolsDOM Inspector라는 새로운 매뉴 항목이 있을 것이다.

이 도구가 정말 어느 정도 파워가 있는지 느껴 보기 위해, Dive Into Greasemonkey 홈 페이지의 DOM을 둘러보자.

절차:  Dive Into Greasemonkey 홈페이지를 들여다보고 편집하기

  1. http://diveintogreasemonkey.org/를 방문한다.

  2. 메뉴에서, ToolsDOM Inspector를 선택해 DOM 검열자를 연다.

  3. DOM 검열자 창에서, 왼쪽 구역에 DOM 노드 리스트가 보일 것이다. 그렇지 않다면, 좌상 모퉁이에 드롭다운 메뉴를 열어서 DOM Nodes를 선택하자.

  4. DOM 노드 트리는 언제나 문서 요소부터 시작한다. #document라는 라벨이 붙어 있다. 이것을 확대하여 HTML 요소를 드러내자.

  5. HTML 요소를 확대하여 3 노드를 드러내자: HEAD, #text, 그리고 BODY가 나타난다. BODYdiveintogreasemonkey-orgid가 있음을 주목하자. 이것이 보이지 않으면 컬럼 너비를 조정하자.

  6. BODY를 확대하여 5 노드를 드러내자: #text, DIV id="intro", #text, DIV id="main", 그리고 #text가 나타난다.

  7. DIV id="intro"를 확대하여 2 노드를 드러내자: #text 그리고 DIV class="sectionInner"이 나타난다.

  8. DIV class="sectionInner"를 확대하여 2 노드를 드러내자: #textDIV class="sectionInner2"가 나타난다.

  9. DIV class="sectionInner2"를 확대하여 5 노드를 드러내자: #text, DIV class="s", #text, DIV class="s", 그리고 #text가 나타난다.

  10. 처음의 DIV class="s"를 확대하여 5 노드를 드러내자: #text, H1, #text, P, 그리고 #text가 나타난다.

  11. H1 노드를 선택하자. 원래 페이지에서 (DOM 검열자 뒤), H1 요소는 빨간색 둘레를 깜박이고 있을 것이다. 오른쪽 구역에는 그 노드 이름 (“H1”)과 이름공간 URI (비어있다. HTML은 이름공간이 없기 때문이다 -- 이것은 application/xhtml+xml로 서비스 되거나 다른 어떤 이름공간에 XML을 표시하는 페이지에만 사용된다) 그리고 노드 유형 (1은 요소이다)과 노드 값이 보일 것이다 (비어있다. 헤더는 값이 없기 때문이다 -- 헤더 안의 텍스트는 자신만의 노드를 가진다).

  12. 오른쪽 구역 상단에서 드롭다운 메뉴를 보면 선택사항이 많을 것이다: DOM Node, Box Model, XBL Bindings, CSS Style Rules, Computed Style, 그리고 Javascript Object가 있다. 이것들은 현재 선택된 노드에 대하여 다양한 정보를 제공한다. 어떤 것들은 편집이 가능하고, 수정하면 즉시 원래 페이지에 반영된다. Javascript Object를 클릭하면 선택된 H1 요소의 스크립트가 가능한 특성들과 메쏘드들을 모두 볼 수 있다.

  13. CSS Style Rules를 선택하면, 오른쪽 구역이 두 개의 구역으로 갈라진다. 위쪽 구역에서는 요소에 영향을 미치는 (브라우저 자체에 내장된 기본 규칙들을 포함하여) 모든 규칙을 담은 리스트가 보이고, 아래쪽 구역에서는 그런 규칙들에 의하여 정의된 개별 특성들을 보여준다.

  14. 우상 구역에서 두 번째 규칙을 클릭하자. 이는 http://diveintogreasemonkey.org/css/dig.css의 스타일시트에 정의된 스타일 규칙이다.

  15. 우하 구역에서 font-variant 특성을 더블-클릭한다. 그리고 normal의 새로운 값을 입력하자. (DOM 검열자 뒤의) 원래 페이지에서, “Dive Into Greasemonkey” 로고 텍스트가 소문자에서 보통의 대문자와 소문자로 즉시 변해야 한다.

  16. 우-하 구역에서 아무데서나 오른쪽-클릭하고 (맥 사용자는 control-click) New Property를 선택하자. “New Style Rule”이라는 타이틀의 대화상자가 튀어 나올 것이다. background-color의 특성 이름을 입력하자. 그리고 OK를 클릭해서, 특성 값을 red로 입력하고 OK를 크릭해서 새로운 특성을 적용시킨다. 새로운 특성과 값이 기존의 특성들과 함께 우-하 구역에 나타날 것이다. 그리고 원래 페이지의 로고 텍스트는 즉시 빨간색 배경으로 바뀌어야 한다.

가지마다 클릭을 하면서 DOM 노드 트리를 돌아다니는 것은 별로 좋은 생각이 아니다, 검열 요소(Inspect Element) 확장을 적극 추천한다. 이 확장으로 DOM 검열자의 특정한 요소에 직접 파고들 수 있다.

[Warning]

먼저 DOM 검열자를 설치하고 나서 Inspect Element 확장을 설치해야 한다. 그렇지 않으면, 파이어폭스는 시작시에 충돌을 일으킨다. 이미 이렇게 되버린 상황이라면, 명령어줄 창을 열어서, 파이어폭스를 설치한 디렉토리로 찾아가, firefox -safe-mode라고 타자한다. 그러면 파이어폭스는 확장을 설치하지 않고 시작된다. 다음 ToolsExtensions을 선택한다음 Inspect Element를 설치제거한다.

절차:  Inspect Element로 직접적으로 한 요소 검열하기

  1. Inspect Element 내려받기 페이지를 방문해 Install Now를 클릭한다.

  2. 파이어폭스를 재시작한다.

  3. http://diveintogreasemonkey.org/를 재방문한다.

  4. 로고 텍스트 Dive Into Greasemonkey에 오른쪽을 클릭한다(맥사용자들은 control-click).

  5. 정황메뉴에서, Inspect element를 선택한다. DOM 검열자가 이미 선택된 H1 요소와 함께 열릴 것이다. 그러면 즉시 그의 특성을 검열 그리고/또는 편집할 수 있다.

[Warning]

DOM 검열자는 브라우즈 하는 동안 “따라다니지 않는다” . DOM 검열자를 열어 놓고 원래 창의 다른 곳을 돌아다니면, DOM 검열자는 아주 혼란스러워 한다. 가장 좋은 방법은 가고 싶은 곳을 정해서 거기로 간 다음, 검열하고 싶은 것을 검열하고, 다음 DOM 검열자를 닫은 다음에 다른 일을 하는 것이다.

더 읽어야 할 것

3.4. 자바스크립트 쉘로 표현식 평가하기

자바스크립트 쉘은 현재 페이지의 문맥에서 자바스크립트 표현식을 평가할 수 있는 책갈피이다.

절차: 자바스크립트 쉘 설치하기

  1. 제시(Jesse)의 웹 개발 책갈피들에 방문하자.

  2. Shell 책갈피를 링크 툴바에 끌어 놓는다.

  3. (예를 들어, Dive Into Greasemonkey 홈페이지 같은) 페이지를 방문해서, 링크 툴바에서 Shell 책갈피를 클릭한다. 자바스크립트 쉘 창이 배경에서 열릴 것이다.

자바스크립트 쉘은 DOM 검열자와 똑 같은 파워를 제공하지만, 자유로운-형태의 환경에서 제공한다. 그것을 DOM을 위한 명령어 줄이라고 생각하자. 시작해 보자.

예제: 자바스크립트 쉘 소개

document.title
Dive Into Greasemonkey
document.title = 'Hello World'
Hello World
var paragraphs = document.getElementsByTagName('p')
paragraphs
[object HTMLCollection]
paragraphs.length
5
paragraphs[0]
[object HTMLParagraphElement]
paragraphs[0].innerHTML
Teaching an old web new tricks
paragraphs[0].innerHTML = 'Live editing, baby!'
Live editing, baby!

수정한 것이 ENTER를 때리자 마자 원래 페이지에 반영된다.

자바스크립트 쉘에 대해서 한 가지 더 언급하고 싶은 것은 props 함수에 관해서이다.

예제: 한 요소의 특성 얻기

var link = document.getElementsByTagName('a')[0]
props(link)
Methods of prototype: blur, focus
Fields of prototype: id, title, lang, dir, className, accessKey,
charset, coords, href, hreflang, name, rel, rev, shape, tabIndex,
target, type, protocol, host, hostname, pathname, search, port,
hash, text, offsetTop, offsetLeft, offsetWidth, offsetHeight,
offsetParent, innerHTML, scrollTop, scrollLeft, scrollHeight,
scrollWidth, clientHeight, clientWidth, style
Methods of prototype of prototype of prototype: insertBefore,
replaceChild, removeChild, appendChild, hasChildNodes, cloneNode,
normalize, isSupported, hasAttributes, getAttribute, setAttribute,
removeAttribute, getAttributeNode, setAttributeNode,
removeAttributeNode, getElementsByTagName, getAttributeNS,
setAttributeNS, removeAttributeNS, getAttributeNodeNS,
setAttributeNodeNS, getElementsByTagNameNS, hasAttribute,
hasAttributeNS, addEventListener, removeEventListener, dispatchEvent,
compareDocumentPosition, isSameNode, lookupPrefix, isDefaultNamespace,
lookupNamespaceURI, isEqualNode, getFeature, setUserData, getUserData
Fields of prototype of prototype of prototype: tagName, nodeName,
nodeValue, nodeType, parentNode, childNodes, firstChild, lastChild,
previousSibling, nextSibling, attributes, ownerDocument, namespaceURI,
prefix, localName, ELEMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE,
CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE, ENTITY_NODE,
PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, DOCUMENT_NODE,
DOCUMENT_TYPE_NODE, DOCUMENT_FRAGMENT_NODE, NOTATION_NODE,
baseURI, textContent, DOCUMENT_POSITION_DISCONNECTED,
DOCUMENT_POSITION_PRECEDING, DOCUMENT_POSITION_FOLLOWING,
DOCUMENT_POSITION_CONTAINS, DOCUMENT_POSITION_CONTAINED_BY,
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
Methods of prototype of prototype of prototype of prototype of prototype: toString

편하게 표현해서, “우와!” 이게 다 무엇인가? <a> 요소의 모든 특성과 메쏘드를 담은 리스트로서, 자바스크립트로 얻을 수 있다. DOM 객체 계층도에서의 수준별로 그룹지어져 있다. (blurfocus 메쏘드 그리고 hrefhreflang 특성들 같이) 링크 요소에 종속적인 메쏘드들이 제일 먼저 나열되고, (insertBefore 메쏘드 같은) 모든 유형의 노드들이 공유하는 메쏘드와 특성들이 그 다음을 따른다, 등등.

이것도 역시 DOM 검열자로 얻을 수 있는 정보와 똑 같지만... 타자와 실험은 더 필요하고 가리키기와 클릭은 덜 해도 된다.

[Warning]

DOM 검열자처럼, 자바스크립트 쉘은 여러분이 브라우즈 할 때 “따라다니지 않는다”. 자바스크립트 쉘을 열고 원래 창의 다른 곳을 항해하면, 자바스크립트 쉘은 아주 혼란스러워 한다. 가고 싶은 곳에 가서, 자바스크립트 쉘을 열고, 마음에 둔 일을 한 다음, 자바스크립트 쉘을 닫고 나서 다른 일을 하는 것이 최선이다.

3.5. 기타 디버깅 도구들

다음은 내가 유용하다고 판단한 기타 디버깅 도구 목록이다. 그러나 시간이 없어서 완전히 다루지는 않겠다.

더 읽어야 할 것

  • Web Developer 확장에는 페이지를 분해하는 함수들이 엄청나게 많다.
  • Aardvark는 상호대화적으로 태그 이름과 id 그리고 class 속성들을 화면에 표시한다.
  • Venkman Javascript 디버거는 완전한 실행-시간 자바스크립트 디버거이다.
  • Web Development Bookmarklets에는 수 많은 유용한 함수들이 담겨 있어서 툴바에 끌어다 놓을 수 있다.
  • JSUnit은 자바스크립트용 유닛 테스트 작업틀이다.
  • js-lint는 자바스크립트 코드에서 일반 에러들을 점검한다.

제 4 장 일반 패턴

4.1. 한 도메인과 그의 모든 하부도메인에서 사용자 스크립트 실행하기

많은 사이트들은 주소 앞에 www.를 붙이든 안 붙이든 똑같이 잘 작동한다. 그런 사이트를 위해서 사용자 스크립트를 작성하고 싶다면, 두 버전의 주소 모두를 잡도록 확인할 필요가 있다.

예제: 한 도메인과 그의 모든 하부도메인에 일치하는 데이터데이터 태그

// ==UserScript==
// @include http://example.com/*
// @include http://*.example.com/*
// ==/UserScript==

실제 예제들

4.2. 그리스몽키 함수가 사용가능한지 테스트하기

그리스몽키 새 버전은 새로운 함수들을 사용자 스크립트에 노출시켜 준다. 사용자 스크립트를 배포할 계획이라면, 반드시 사용할 그리스몽키 함수가 실제로 존재하는지 확인해야 한다.

예제:  GM_xmlhttpRequest 함수가 사용이 불가능하면 사용자에게 경고하기

if (!GM_xmlhttpRequest) {
    alert('Please upgrade to the latest version of Greasemonkey.');
    return;
}
// more code here that uses GM_xmlhttpRequest

4.3. 페이지에 HTML 요소가 포함되어 있는지 테스트하기

getElementsByTagName 함수를 사용하면 HTML 요소가 페이지에 존재하는지 테스트할 수 있다.

예제: 페이지에 <textarea>

가 들어 있는지 확인하기
var textareas = document.getElementsByTagName('textarea');
if (textareas.length) {
    // there is at least one textarea on this page
} else {
    // there are no textareas on this page
}

실제 예제들

4.4. HTML 요소마다 무엇인가 처리하기

페이지의 HTML 요소마다 무언가를 처리해야 할 경우가 많다. 파이어폭스는 getElementsByTagName('*')을 지원하는데, 이것이 돌려주는 집단에 회돌이하면 된다.

예제: 요소마다회돌이하기

var allElements, thisElement;
allElements = document.getElementsByTagName('*');
for (var i = 0; i < allElements.length; i++) {
    thisElement = allElements[i];
    // do something with thisElement
}
[Note]

요소마다 꼭 처리해야 할 필요가 있는 경우에만 이렇게 해야 한다. 어떤 특정 요소들을 처리하고 싶은지 미리 안다면, XPath 질의를 이용해서 원하는 요소들을 정확하게 얻는 것이 더 빠를 것이다. 더 자세한 정보는 어떤 속성을 가진 요소마다 무엇인가를 처리하기를 참조하자.

실제 예제들

4.5. 특정한 HTML 요소의 실체마다 무엇인가를 처리하기

페이지에서 특정한 HTML 요소의 실체마다 무엇인가를 해야할 경우가 있다. 예를 들어, <textarea>마다 폰트를 바꿀 수 있다. 가장 쉬운 방법은 getElementsByTagName('tagname')를 호출하는 것인데, 이 함수가 돌려주는 집단에 회돌이 하면 된다.

예제: Find all the textareas on a page

var allTextareas, thisTextarea;
allTextareas = document.getElementsByTagName('textarea');
for (var i = 0; i < allTextareas.length; i++) {
    thisTextarea = allTextareas[i];
    // do something with thisTextarea
}
[Note]

페이지의 링크마다 무엇인가를 하기 위해 이 패턴을 사용하면 안된다. <a> 요소는 페이지-안 링크로도 사용될 수 있기 때문이다. 페이지에서 모든 링크들을 찾는 법에 관해서는 어떤 속성을 가진 요소마다 무엇인가 처리하기를 참조하자.

실제 예제들

4.6. 어떤 속성을 가진 요소마다 무엇인가 처리하기

그리스몽키 무기고에서 가장 강력한 도구는 evaluate 메쏘드이다. 이 메쏘드는 XPath라고 부르는 질의 언어를 사용하여 페이지에서 요소, 속성, 그리고 텍스트를 찾는다.

예를 들어, 한 페이지에서 모든 링크들을 찾고 싶다고 해보자. 아마도 document.getElementsByTagName('a')의 사용을 고려하실 것 같다. 그러나 그러면 href 속성이 있는지 요소마다 점검할 필요가 있을 것이다. <a> 요소는 이름붙은 앵커에도 사용될 수 있기 때문이다.

대신에, 파이어폭스에 내장된 XPath 지원을 활용하여 href 속성이 있는 모든 <a> 요소들을 찾자.

예제: 한 페이지에서 모든 링크들을 찾기

var allLinks, thisLink;
allLinks = document.evaluate(
    '//a[@href]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
for (var i = 0; i < allLinks.snapshotLength; i++) {
    thisLink = allLinks.snapshotItem(i);
    // do something with thisLink
}

여기에서 document.evaluate 메쏘드가 핵심이다. 이 메쏘드는 XPath 질의를 문자열로 취하고, 다음 기타 다른 매개변수들을 취한다. 잠시 후에 설명하겠다. 이 XPath 질의는 href 속성을 가진 모든 <a> 요소들을 찾아서, 무작위 순서로 돌려준다. (다시 말해, 제일 처음 획득한 링크 요소가 그 페이지에서 제일 첫 링크 요소라는 보장이 없다.) 다음 allLinks.snapshotItem(i) 메쏘드로 발견된 각 요소에 접근한다.

XPath 표현식은 경이로운 일들을 할 수 있다. 다음은 title 속성이 있는 요소들을 모두 찾는 표현식이다.

예제: title 속성을 가진 모든 요소들을 찾기

var allElements, thisElement;
allElements = document.evaluate(
    '//*[@title]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
for (var i = 0; i < allElements.snapshotLength; i++) {
    thisElement = allElements.snapshotItem(i);
    switch (thisElement.nodeName.toUpperCase()) {
        case 'A':
            // this is a link, do something
            break;
        case 'IMG':
            // this is an image, do something else
            break;
        default:
            // do something with other kinds of HTML elements
    }
}
[Tip]

(thisElement와 같이) 한 요소에 대하여 참조점을 확보하면, thisElement.nodeName를 사용하여 그의 HTML 태그 이름을 결정할 수 있다. 그 페이지가 text/html로 서비스되고 있으면, 태그 이름은 원래 페이지에 어떻게 지정되어 있든 언제나 대문자로 반환된다. 그렇지만, 그 페이지가 application/xhtml+xml로 서비스되면, 태그 이름은 언제나 소문자이다. 그래서 나는 언제나 thisElement.nodeName.toUpperCase()을 사용하고 그에 관하여 신경쓰지 않는다.

다음은 XPath 질의로서 특정한 class를 가진 <div>를 하나하나 돌려준다.

예제:  sponsoredlinkclass로 모든 <div> 찾기

var allDivs, thisDiv;
allDivs = document.evaluate(
    "//div[@class='sponsoredlink']",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
for (var i = 0; i < allDivs.snapshotLength; i++) {
    thisDiv = allDivs.snapshotItem(i);
    // do something with thisDiv
}

XPath 질의 문자열 둘레에 겹 따옴표를 사용한 것에 주목하자. 그래야 그 안에 홑 따옴표를 사용할 수 있다.

document.evaluate 메쏘드에는 변종이 수 없이 많다. 두 번째 매개변수(앞 두 예제에서 document)는 어떤 요소도 될 수 있고, XPath 질의는 그 요소의 자손 노드들만을 돌려줄 것이다. 그래서 (예를 들어, document.getElementById 또는 document.getElementsByTagName 배열의 멤버로부터) 이미 한 요소에 대한 참조점을 가지고 있다면, 질의를 제한해 오직 그 요소의 자손들만을 검색할 수 있다.

세 번째 매개변수는 이름공간 해결자 함수에 대한 참조점이다. 이함수가 유일하게 쓸모있는 경우는 사용자 스크립트를 작성해서 application/xhtml+xml 미디어 유형으로 서비스되는 페이지에 작동시키고 싶을 때이다. 그게 뭔지 모르겟다면, 신경쓰지 말자; 그런 페이지는 별로 많지 않아서 아마도 만날 기회가 거의 없을 것이다. 정말 더 알고 싶다면 Mozilla XPath 문서에 그 사용법이 잘 설명되어 있으니 참고하자.

네번째 매개변수는 결과를 어떻게 돌려받고 싶은지 지정한다. 앞의 두 예제 모두 XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE를 사용하는데, 요소들을 무작위 순서로 돌려준다. 보통 99%는 이렇게 사용하지만, 어떤 이유로 페이지에서 나타나는 순서와 정확하게 똑 같은 순서로 돌려받고 싶다면, 대신에 XPathResult.ORDERED_NODE_SNAPSHOT_TYPE를 사용하면 된다. Mozilla XPath 문서에 다른 변형들의 예가 있다.

다섯번째 매개변수는 두 XPath 질의 결과를 병합하는데 사용된다. 앞서 document.evaluate를 호출한 결과에서, 두 질의의 결과를 조합하여 돌려줄 것이다. 앞의 두 예제 모두 null을 사용하는데, 첫 매개변수에 정의된 XPath 질의에만 관심이 있다는 뜻이다.

이해하셨는지? XPath는 쉽게 쓸 수도 있고 어렵게 쓸 수도 있다. XPath 구문에 관하여 더 자세하게 배우려면 이 탁월한 XPath 자습서를 읽어 보시기를 적극 권장한다. document.evaluate에 대한 다른 매개변수들에 대해서는 여기에서 보여준 경우를 제외하고는 거의 사용하지 않는다. 사실, 함수를 하나 정의해 그것들을 포장해 넣어도 된다(encapsulate).

예제: xpath 함수

function xpath(query) {
    return document.evaluate(query, document, null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}

이제 간단하게 xpath('//a[@href]')를 호출해서 그 페이지의 모든 링크를 얻을 수 있다. 또는 xpath('//*[@title]')를 호출하면 title 속성을 가진 모든 요소들을 얻을 수 있다. 결과의 각 항목에 접근하려면 여전히 snapshotItem 메쏘드를 사용할 필요가 있다; 결과는 일반적인 자바스크립트 배열이 아니다.

4.7. 한 요소의 앞에 내용 삽입하기

(어떻게해서든) 한 요소를 발견했다면, 그 앞에 추가로 내용을 삽입하고 싶을 수 있다. insertBefore 메쏘드로 이렇게 할 수 있다.

예제: 메인 내용 앞에 <hr> 삽입하기

ID가 "main"인 요소가 있다고 가정한다.

var main, newElement;
main = document.getElementById('main');
if (main) {
    newElement = document.createElement('hr');
    main.parentNode.insertBefore(newElement, main);
}

실제 예제들

4.8. 한 요소의 뒤에 내용 삽입하기

한 요소를 찾았다면, 추가로 그 뒤에 내용을 삽입하고 싶을 수 있다. nextSibling 특성과 함께 insertBefore 함수를 사용하면 역시 이렇게 할 수 있다.

예제: 항해 바 뒤에 <hr> 삽입하기

ID가 "navbar"인 요소가 있다고 가정한다.

var navbar, newElement;
navbar = document.getElementById('navbar');
if (navbar) {
    newElement = document.createElement('hr');
    navbar.parentNode.insertBefore(newElement, navbar.nextSibling);
}
[Tip]

someExistingElement가 그의 부모의 마지막 자손이라고 할지라도 (즉, 다음 형제가 없다면) 새로운 내용을 someExistingElement.nextSibling 앞에 삽입할 수 있다. 이 경우, someExistingElement.nextSiblingnull을 돌려주고, insertBefore 함수는 그냥 그 새로운 내용을 다른 모든 형제 뒤에 추가할 것이다. (이해가 안가더라도, 걱정할 것 없다. 요점은 이 예제가 언제나 작동한다는 것이다. 그럴 것 같아 보이지 않지만 말이다.)

실제 예제들

4.9. 요소 제거하기

그리스몽키를 사용하여 페이지의 전 조각을 순식간에 제거할 수 있다. removeChild 함수를 사용하면 된다.

예제: 광고 사이드바 제거하기

ID가 "ads"인 요소가 있다고 가정한다.

var adSidebar = document.getElementById('ads');
if (adSidebar) {
    adSidebar.parentNode.removeChild(adSidebar);
}
[Note]

removeChild로 한 요소를 제거하면 그 안에 든 내용도 모두 제거된다. 예를 들어, <table> 요소를 제거하면, 테이블 안에 든 모든 내용도 제거된다 (<td> 요소들).

[Tip]

오직 광고만 제거하고 싶을 뿐이라면, AdBlock최신 여과 목록을 설치하는 편이 직접 사용자 스크립트를 작성하는 것보다 아마도 더 쉬울 것이다.

실제 예제들

4.10. 한 요소를 새로운 내용으로 교체하기

한 요소를 발견하면, 그것을 완전히 새로운 내용으로 바꿀 수 있다. replaceChild 함수를 사용하면 된다.

예제: 이미지를 그의 alt 텍스트로 바꾸기

ID가 "annoyingsmily"인 한 요소가 있다고 가정한다.

var theImage, altText;
theImage = document.getElementById('annoyingsmily');
if (theImage) {
    altText = document.createTextNode(theImage.alt);
    theImage.parentNode.replaceChild(altText, theImage);
}
[Note]

한 요소를 커다란 HTML 조각으로 바꾸고 싶다면, 그 HTML 조각을 문자열로 변환한 다음 그 요소의 innerHTML 특성에 설정하자.

실제 예제들

4.11. 복잡한 HTML을 신속하게 삽입하기

innerHTML 특성으로 HTML 조각을 문자열로 구성한 다음 것을 페이지에 직접 삽입한다. 각 HTML 요소에 대하여 따로 DOM 객체들을 구성할 필요가 없으며 하나씩 그 속성들을 설정할 필요가 없다.

예제: Insert a banner at the top of the page

var logo = document.createElement("div");
logo.innerHTML = '<div style="margin: 0 auto 0 auto; ' +
    'border-bottom: 1px solid #000000; margin-bottom: 5px; ' +
    'font-size: small; background-color: #000000; ' +
    'color: #ffffff;"><p style="margin: 2px 0 1px 0;"> ' +
    'YOUR TEXT HERE ' +
    '</p></div>';
document.body.insertBefore(logo, document.body.firstChild);

여기에서 핵심은 두 번째 줄이다. 거기에서 logo.innerHTML이 문자열로 설정된다. 파이어폭스는 그 문자열을 HTML로 해석하고 필요한 객체들을 모두 만들어낸다. 처음 적재할 때 전체 페이지에 대하여 하는 일과 똑 같이 말이다. 그러면 나의 새로운 logo를 (<div> 안에 또다른 <div>가 있고 그 안에 또 <p>가 담김) 그 페이지 안 어느 곳에나 삽입할 수 있다. — 페이지 꼭대기, 페이지 아래, 또는 내가 선택한 요소의 이나 어디든지 말이다. 다른 말로 해서, 페이지의 아무곳에나 말이다.

실제 예제들

4.12. 중앙 서버를 거치지 않고 이미지 추가하기

파이어폭스는 data: URL들을 지원하는데, 이것은 따로 호출해서 서버로부터 그 데이터를 열람하는 대신에 데이터 쪼가리들을 URL에 내장시키는 방법이다. 아마도 data: URL들을 본 적이 없을 것이다. 왜냐하면 인터넷 익스플로러가 지원하지 않아서 아무도 사용하지 않기 때문이다. 그러나 사용자 스크립트에서는 편리할 수 있다.

예제: 페이지 상단에서 그래픽 로고 추가하기

var logo = document.createElement('img');
logo.src = 'data:image/gif;base64,R0lGODlhDQAOAJEAANno6wBmZgAAAAAAACH5BAAAAAAA'+
    'LAAAAAANAA4AQAIjjI8Iyw3GhACSQecutsFV3nzgNi7SVEbo06lZa66LRib2UQAAOw%3D%3D';
document.body.insertBefore(logo, document.body.firstChild);

이 예제에서, <img> 요소의 srcdata: URL로서 안에 인코드된 형태로 전체 이미지 데이터가 담긴다. 그 새 요소가 페이지에 삽입되면, 다른 이미지와 똑 같이 화면에 나타나겠지만, 그 이미지가 중앙 서버에 저장되기를 요구하지 않는다. 본질적으로, 이미지를 사용자 스크립트 안에 내장할 수 있으며 그 이미지들을 나머지 코드와 똑 같이 같은 파일에 넣어 배포할 수 있다.

[Tip]

data: URI kitchen을 사용하여 자신만의 data: URL을 구성하자.

실제 예제들

다음도 참조

4.13. CSS 스타일 추가하기

종종 나만의 CSS 스타일을 적용할 필요가 있었다. 스타일을 추가해서 기존의 스타일을 덮어쓰거나, 사용자 스크립트에 추가한 새 요소에 알맞게 스타일을 추가할 수 있다.

예제: 문단 텍스트를 더 크게 만들기

function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

addGlobalStyle('p { font-size: large ! important; }');

이 함수는 인자를 하나 취한다. 인자는 페이지에 추가하고자 하는 스타일 규칙을 담고 있는 문자열이다. 말 그대로 스타일 규칙이 포함된 그 페이지의 <head> 섹션에 <style> 요소를 삽입한다. 파이어폭스는 자동으로 수정사항들을 받아, 스타일 규칙을 해석해서 페이지에 적용한다. 단 한번의 함수 호출에 얼마든지 따로 스타일 규칙을 포함시켜도 좋다; 모두 하나로 모아 문자열을 만들어서 한 번에 건네면 된다.

[Tip]

addGlobalStyle 함수를 사용해서 페이지에 삽입한 요소들을 스타일 짓고 또는 원래 페이지의 일부인 요소들을 스타일 지을 수 있다. 그렇지만, 기존의 요소들을 스타일 짓고 있다면, 정의한 각 규칙에 ! important 키워드를 사용해서 여러분의 스타일이 확실하게 원래 페이지에서 정의한 규칙들을 덮어써야 한다.

4.14. 한 요소의 스타일 얻기

모든 CSS 규칙이 적용된 후에, 특정한 요소의 실제 스타일을 얻으면 유용한 경우가 있다. 순진하게 한 요소의 style 특성에서 얻을 것이라고 생각한다면, 실수하게 될 것이다. 그렇게 하면 그 요소의 style 속성의 내용만 반환될 뿐이다. (외부 스타일시트에 정의된 스타일을 포함하여) 최종 스타일을 얻으려면, getComputedStyle 함수를 사용할 필요가 있다.

이를 보여주기 위하여, 간단한 테스트 페이지를 만들어보자. 거기에는 모든 <p> 요소들에 대하여 스타일이 정의되지만, <p> 요소중 하나 만은 그 스타일을 자신의 style 속성으로 오버라이드한다.

<html>
<head>
<title>Style test page</title>
<style type="text/css">
p { background-color: white; color: red; }
</style>
</head>
<body>
<p id="p1">This line is red.</p>
<p id="p2" style="color: blue">This line is blue.</p>
</body>
</html>

예제:  한 요소의 style 속성이 정의한 스타일 얻기

var p1elem, p2elem;
p1elem = document.getElementById('p1');
p2elem = document.getElementById('p2');
alert(p1elem.style.color); // will display an empty string
alert(p2elem.style.color); // will display "blue"

정말 도움이 되지 않았다. 개별 스타일을 얻기 위해서 element.style을 사용해서는 안된다. (개별 스타일을 설정하는데는 좋다; 요소의 스타일 설정하기 참조.)

그래서 대신에 무엇을 사용해야 하는가? getComputedStyle()가 그 해답이다.

예제:  한 요소의 실체 스타일 얻기

var p1elem, p2elem, p1style, p2style;
p1elem = document.getElementById('p1');
p2elem = document.getElementById('p2');
p1style = getComputedStyle(p1elem, '');
p2style = getComputedStyle(p2elem, '');
alert(p1style.color); // will display "rgb(255, 0, 0)"
alert(p2style.color); // will display "rgb(0, 0, 255)"

실제 예제들

4.15. 한 요소의 스타일 설정하기

몇가지 스타일을 단 하나의 요소에 꼭 설정할 필요가 있다면, “수작업으로” 다양한 하부특성들을 그 요소의 style 특성에 설정하면 된다.

예제: 한 요소에 스타일 설정하기

여기에는 ID가 "logo"인 요소가 있다고 전제된다.

var logo = document.getElementById('logo');
logo.style.marginTop = '2em';
logo.style.backgroundColor = 'white';
logo.style.color = 'red';
[Warning]

스타일의 특성 이름들이 언제나 뻔한 것은 아니다. 일반적으로, 같은 패턴을 따른다. 즉 margin-topsomeElement.style.marginTop이 된다. 그러나 예외도 있다: float 특성은 someElement.style.cssFloat로 설정되는데, “float”가 자바스크립트에서 예약어이기 때문이다.

더 읽어야 할 것

4.16. 가공이 된 후에 페이지 후-처리하기

파이어폭스가 가공된 페이지를 내부적으로 저장하는 방식 때문에, 그 페이지가 적재를 마친 후에 그 페이지의 DOM에 많은 변화를 주어야 할 것이다. addEventListener 함수를 사용하면 함수의 처리를 미룰 수 있다.

예제: 맞춤 소로 전체 페이지 교체하기

var newBody = 
'<html>' +
'<head>' +
'<title>My New Page</title>' +
'</head>' +
'<body>' +
'<p>This page is a complete replacement of the original.</p>' +
'</body>' +
'</html>';
window.addEventListener(
    'load', 
    function() { document.body.innerHTML = newBody; },
    true);

이 코드를 자세히 살펴보면, 왜 잘 작동하는지 당연히 의아하리라 생각한다. 익명 함수를 선언하여 두 번째 인자로 window.addEventListener에 건네고 있는데, 바깥 함수에 정의된 newBody 변수에 접근한다. 이것이 이른바 “울타리(closure)”라고 하며, 자바스크립트에서 완전히 적법하다. 일반적으로, “바깥쪽” 함수 안에 정의된 “안쪽” 함수는 바깥 함수의 지역 변수 모두에 접근이 가능하다 -- 바깥 함수가 실행이 완료된 후에도 말이다. 이것은 아주 강력한 특징으로서 실행시간에 구성된 데이터 구조가 담긴 기타 함수들과 이벤트 처리자를 만드는 것이 가능해진다.

[Tip]

document.body.innerHTML을 빼서 교체해도 그 페이지 안의 어떤 것에도 영향을 미치지 않는다. 페이지 제목, CSS 스타일 그리고 스크립트를 포함하여 원래 페이지의 <head> 안에 정의된 것은 모두 여전히 영향을 받을 것이다. 이것들을 따로 수정하거나 제거할 수 있다.

실제 예제들

4.17. 대소문자-구별 속성 값들에 일치하기

HTML에서 많은 속성값들은 대소문자를 구별한다. 많은 것들은 이끄는 공백과 이끌리는 공백이 포함될 수도 있다. 모든 변형들을 발견하고 싶다면, XPath 질의를 약간 손 볼 필요가 있다.

예제: 메쏘드가 "POST" 또는 "post"인 폼 모두 찾기

var postforms = document.evaluate(
    "//form[translate(@method, 'POST ', 'post')='post']",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);

이 XPath 질의는 POST 메쏘드로 제출하는 폼을 찾는다. 먼저, method 속성을 소문자로 바꿀 필요가 있다. translate 함수로 대문자들을 소문자로 변환하면 된다. (XPath 2.0에는 lowercase 함수가 있지만, 사용하는데 성공해 보지 못했다.) 다음, 이끄는 그리고 이끌리는 공백들을 걷어낼 필요가 있다. 이것을 translate 함수의 호출에 통합할 수 있다. 첫 인자에 여분의 공백을 삽입하면 된다. 두 번째 인자에 상응하는 문자가 없으므로, 모든 공백들은 제거된다. 마지막으로, 그 결과 속성 값을 'post'와 비교하면 된다.

실제 예제들

4.18. 현재 도메인 이름 얻기

여러 도메인에 (또는 모든 도메인에) 운용되는 사용자 스크립트는 현재 페이지의 도메인을 탐지할 필요가 자주 있다. window.location.href를 사용하면 현재 페이지의 완전한 URL을 얻을 수 있다. 또는 window.location.host를 이용하면 도메인 이름만 얻을 수 있다.

예제: 현재 도메인 이름 얻기

var href = window.location.host;

실제 예제들

4.19. 링크 재작성하기

한 페이지에서 링크를 하나 발견했다면, href 속성을 설정하여 그 링크를 재작성할 수 있다.

예제:  링크의 끝에질의 매개변수 덧붙이기

id가 "article"인 링크가 있다고 전제한다.

var a = document.getElementById('article');
if (a.href.match(/\?/i)) {
    // link already contains other parameters, so append "&amp;printer=1"
    a.href += '&amp;printer=1';
} else {
    // link does not contain any parameters, so append "?printer=1"
    a.href += '?printer=1';
}

실제 예제들

4.20. 페이지 방향전환하기

그리스몽키를 사용하면 자동으로 특정 페이지를 방향전환할 수 있다. window.location.href 특성을 설정하면 된다.

예제: 그의 안전한 상대방으로 사이트 방향전환하기

window.location.href = window.location.href.replace(/^http:/, 'https:');

실제 예제들

4.21. 사용자 클릭 가로채기

링크 재작성은 쉽지만, 한 발자국 더 나아가 페이지의 어느 곳이든 모든 클릭을 나포할 수 있다. addEventListener 함수를 사용하면 된다. (이 함수는 링크에 대한 클릭도 가로챈다.) 가로챈 다음 무엇을 할지 생각할 수 있다: 클릭이 그냥 클릭된 요소로“흘러가게” 허용할지, 아니면 무엇인가 완전히 다른 일을 할지 결정할 수 있다.

예제: 사용자가 페이지에서 클릭할 때 무언가를 하기

document.addEventListener('click', function(event) {
    // event.target is the element that was clicked

    // do whatever you want here

    // if you want to prevent the default click action
    // (such as following a link), use these two commands:
    event.stopPropagation();
    event.preventDefault();
}, true);

익명 함수document.addEventListener 함수에 인자로 건넨 것에 주목하자.

4.22. 내장 자바스립트 덮어쓰기

prototype 특성을 사용하여 고유의 객체 메쏘드를 덮어쓸 수 있다.

예제: 폼이 제출되기 전에 무엇인가를 하기

function newsubmit(event) {
    var target = event ? event.target : this;

    // do anything you like here
    alert('Submitting form to ' + target.action);

    // call real submit function
    this._submit();
}

// capture the onsubmit event on all forms
window.addEventListener('submit', newsubmit, true);

// If a script calls someForm.submit(), the onsubmit event does not fire,
// so we need to redefine the submit method of the HTMLFormElement class.
HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = newsubmit;

여기에서 두 가지 일이 진행된다. 첫째 submit 이벤트를 가로챌 이벤트 청취자를 추가한다. submit 이벤트는 사용자가 폼에 제출 버튼을 클릭할 때 일어난다. 그렇지만, 또다른 스크립트가 수동으로 한 폼의 submit() 메쏘드를 호출한다면, 그 submit 이벤트는 촉발되지 않는다. 그래서, 두 번째로 HTMLFormElement 클래스의 submit 메쏘드를 덮어쓴다.

그러나 잠깐, 더 있다. 이벤트 청취자와 메쏘드 덮어쓰기는 똑 같은 함수, newsubmit를 가리킨다. newsubmitsubmit 이벤트를 통하여 호출되면, event 인자가 그 이벤트에 관한 정보가 담긴 이벤트 객체로 채워질 것이다. (예를 들어, event.target는 제출된 폼이 될 것이다). 그렇지만, 한 스크립트가 수동으로 submit 메쏘드를 호출하면, event 인자는 생략될 것이다. 그렇지만, 전역 변수 this가 그 폼을 가리킬 것이다. 그러므로, 여기 newsubmit 함수에서 event가 널(null)인지 테스트한다. 만약 그렇다면, 대신에 this에서 목표 폼을 가져오자.

[Tip]

submit 이벤트는 사용자가 보통의 방식대로 폼을 제출할 때 촉발된다. 다시 말해, 폼의 Submit 버튼을 클릭하거나 폼 안에서 ENTER를 눌러서 말이다. 그렇지만, submit 이벤트는 폼이 aForm.submit()를 호출하여 스크립트를 통하여 제출되면 촉발되지 않는다. 그러므로 폼 제출을 나포하려면 두 가지 일을 할 필요가 있다: 이벤트 처리자를 추가하여 submit 이벤트를 잡고, 그리고 HTMLFormElement 클래스의 프로토타입을 수정해 submit() 메쏘드를 여러분의 맞춤 함수로 방향전환시킨다.

4.23.  XML 해석하기

파이어폭스는 자동으로 현재 페이지를 DOM 안으로 해석해 넣는다. 그러나 수작업으로 XML 문자열로부터 DOM을 만들어도 된다. 아니면 직접 구성한 문자열 또는 원격 위치에서 열람한 XML로부터 만들어도 좋다.

예제:  임의의 문자열을 XML로 해석하기

var xmlString = '<passwd>' + 
'  <user id="101">' +
'    <login>mark</login>' + 
'    <group id="100"/>' +
'    <displayname>Mark Pilgrim</displayname>' + 
'    <homedir>/home/mark/</homedir>' +
'    <shell>/bin/bash</shell>' +
'  </user>' +
'</passwd>'
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xmlString, "application/xml");

여기에서 핵심은 DOMParser 객체이다. 이 객체에는 parseFromString 메쏘드가 있다. (다른 메쏘드도 있지만, 여기 우리한테는 쓸모가 없다.) parseFromString 메쏘드는 인자를 두개 취한다: 하나는 해석될 XML 문자열이고 다른 하나는 소 유형이다. 두 인자 모두 필수이다.

[Note]

DOMParserparseFromString 메쏘드는 소 유형을 그의 두 번째 매개변수로 취한다. 즉, application/xml, application/xhtml+xml, 그리고 text/xml를 취한다. 좀 우습지만, 언제나 application/xml을 사용해야 한다.

이 패턴은 GM_xmlhttpRequest 함수와 결합하여 원격 소스로부터 XML을 해석할 때 특히 강력하다.

예제:  원격 소스로부터 XML 해석하기

GM_xmlhttpRequest({
    method: 'GET',
    url: 'http://greaseblog.blogspot.com/atom.xml',
    headers: {
        'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey/0.3',
        'Accept': 'application/atom+xml,application/xml,text/xml',
    },
    onload: function(responseDetails) {
        var parser = new DOMParser();
        var dom = parser.parseFromString(responseDetails.responseText,
            "application/xml");
        var entries = dom.getElementsByTagName('entry');
        var title;
        for (var i = 0; i < entries.length; i++) {
            title = entries[i].getElementsByTagName('title')[0].textContent;
            alert(title);
        }
    }
});

이 코드는 http://greaseblog.blogspot.com/atom.xml으로부터 Atom 감을 적재하여, 그것을 DOM으로 해석하고, 그 DOM에 질의하여 엔트리 목록을 얻을 것이다. 각 엔트리마다, 그 DOM을 다시 질의하여 엔트리 제목을 얻고, 그것을 대화상자에 표시한다.

제 5 장 사례 연구

5.1. 사례 연구: GMail 보안

사이트가 보안 접속을 사용하도록 강제하기

GMail Secure는 GMail이 보안 접속을 사용하도록 강제한다. http://gmail.google.com/https://gmail.google.com/로 방향전환해서 말이다.

구글은 웹 메일 서비스로 보안 접속(https:// 주소) 또는 평문 접속을 통하여 (http:// 주소) GMail을 제공한다. 나는 언제나 (인터넷 카페 같은) 공중 네트워크에서 이메일을 점검할 때 보안 접속을 사용하는 것을 기억할 수 있다. 그러나 내 컴퓨터가 나 대신 수고해서 기억해 주면 좋지 않을까? 나는 비보안 접속으로 GMail을 사용하려고 하는 것을 탐지해서 보안 사이트로 방향전환 시켜주는 사용자 스크립트를 작성했다.

예제: GMail을 동등한 https:// 주소로 방향전환하기

// ==UserScript==
// @name          GMailSecure
// @namespace     http://diveintogreasemonkey.org/download/
// @description   force GMail to use secure connection
// @include       http://gmail.google.com/*
// ==/UserScript==

window.location.href = window.location.href.replace(/^http:/, 'https:');

이 사용자 스크립트는 아주 단순하다. 일의 “대부분”은 @include 줄에서 완수된다:

// @include       http://gmail.google.com/*

이 사용자 스크립트는 @include가 일치될 때만 실행된다. 그래서 그 스크립트가 실행중이라면, 내가 잘못된 곳에 있다는 것을 알 수 있다(비 보안 접속 위에서 GMail을 사용하고 있다는 것). 그렇다면, 나의 목표를 달성하는 것은 오직 한 줄의 코드이다. 즉 현재 페이지를 똑같은 URL로 방향전환 시키는 것이다. 단, 앞에 http:// 대신에 https://가 붙는다.

window.location.href = window.location.href.replace(/^http:/, 'https:');

다음도 참조

5.2. 사례 연구: Bloglines 자동적재

페이지 행동 자동화하기

Bloglines는 웹-기반에서 연합출판 감들을 수집한다. 인터페이스가 2-구역이다; 왼쪽 구역에는 구독현황이 나오고, 오른쪽 구역은 그 내용이 나타난다. 아주 멋진 인터페이스이다; 한가지 마음에 안드는 것은 내가 언제나 똑 같은 방식으로 사용한다는 것이다: Bloglines를 방문할 때마다, 나는 이전에 보지 못했던 것들을 모두 보고 싶다. 다시 말해, 읽지 못한 모든 항목들을 보고 싶다.

Bloglines에서, 읽지 않은 항목들을 모두 보여주려면 한 번 클릭이면 된다. 왼쪽 구역에서 구독현황의 루트 수준을 클릭하면 읽지 않은 항목들을 오른쪽 구역에 보여준다. 그러나 언제나 이렇게 하고 싶으므로, 사용자 스크립트를 만들어서 한 번 클릭으로 자동화 시켰다.

예제:  자동으로 Bloglines에서 읽지 못한 항목들을 표시하도록 만들기

// ==UserScript==
// @name          Bloglines Autoloader
// @namespace     http://diveintogreasemonkey.org/download/
// @description   Auto-display all new items in Bloglines
// @include       http://bloglines.com/myblogs*
// @include       http://www.bloglines.com/myblogs*
// ==/UserScript==

if (doLoadAll) {
    doLoadAll();
}

이 사용자 스크립트는 아주 단순하다. Bloglines에는 doLoadAll()라는 함수가 정의되어 있는데, 이것이 바로 내가 수작업으로 구독 리스트의 최상위 수준을 클릭할 때 실행되는 것이다. 이 함수를 호출하면 읽지 않은 모든 항목을 보여준다.

그렇지만, Bloglines는 프레임을 사용하므로, 사용자 스크립트는 각 프레임에서 실행될 것이다 (스크립트는 모두 내가 @include에 정의한 패턴에 일치하기 때문이다). 그래서 먼저 이 프레임에 doLoadAll() 함수가 존재하는지 점검할 필요가 있다:

if (doLoadAll) {

그 함수가 존재한다고 간주하고, 그냥 호출했다. 사용자 스크립트가 그 페이지와 같은 정황에서 실행되므로, 원래 페이지에서 정의한 스크립트라면 어떤 것이든 호출할 수 있다.

    doLoadAll();
    }

5.3. 사례 연구: 읽을 수 없는거야 ?

페이지 스타일 덮어쓰기

Ain't It Cool News는 오락 뉴스 전문 사이트이다. 이 사이트는 훌륭하다; 적극 추천한다. 불행하게도, 사이트의 모습은 참기 힘들다. 머릿기사는 너무 크고, 너무 굵다. 그리고 커서를 그 위로 옮기면 색깔이 변한다. 그래서 나는 마음에 안 드는 그 스타일을 바꿀 스크립트를 작성했다.

예제: aintitreadable.user.js

// ==UserScript==
// @name          Ain't It Readable
// @namespace     http://diveintogreasemonkey.org/download/
// @description   change style on aint-it-cool-news.com
// @include       http://aint-it-cool-news.com/*
// @include       http://*.aint-it-cool-news.com/*
// ==/UserScript==

function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

addGlobalStyle(
'h1, h2, h3, h4 {' +
'  font-size: 12px ! important;' +
'  line-height: 14px ! important;' +
'  font-weight: normal ! important;' +
'}' +
'h1:hover, h2:hover, h3:hover, h4:hover {' +
'  background-color: inherit ! important;' +
'  color: inherit ! important;' +
'}');

이 사용자 스크립트는 단순하다. 먼저 임의의 CSS 스타일을 페이지에 추가하는 함수를 정의한다. 이 함수에 관한 더 자세한 정보는 CSS 스타일 추가하기를 참고하자.

function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

다음 추가하고 싶은 CSS 스타일을 가지고 그냥 그 함수를 호출한다: 머릿기사를 더 작게 만들고, 굵기를 줄이고, 그리고 커서를 움직일 때 색깔이 변화하지 못하도록 만든다. 이 경우, 페이지에는 각각에 대해서 규칙이 정의되어 있고, 그래서 ! important 키워드를 사용해서 확실하게 나의 스타일이 적용되도록 만들었다. 그 페이지의 원래 규칙 대신에 말이다.

그 함수는 오직 인자를 추가할 모든 스타일이 담긴 문자열 하나만 취한다는데 주목하자. 그 문자열을 여기에서는 읽기 쉽게 포맷했지만, 여전히 그냥 하나의 문자열이다.

addGlobalStyle(
'h1, h2, h3, h4 {' +
'  font-size: 12px ! important;' +
'  line-height: 14px ! important;' +
'  font-weight: normal ! important;' +
'}' +
'h1:hover, h2:hover, h3:hover, h4:hover {' +
'  background-color: inherit ! important;' +
'  color: inherit ! important;' +
'}');

내려받기

다음도 참조

5.4. 사례 연구: Offsite Blank

오프사이트 링크가 새 창에서 열리도록 만들기

Offsite Blank는 누군가 그리스몽키 저장소에 요청을 게시한 후로 내가 작성한 사용자 스크립트이다. 개인적으로 나는 링크를 현재 창의 새로운 탭에 열기를 좋아한다. 그러나 다른 사람들은 각 사이트에 대하여 다른 창에 열기를 선호한다. Offsite Blank로 자동으로 이렇게 할 수 있다. 오프사이트 링크를 새 창에 열리도록 만들면 된다.

예제: offsiteblank.user.js

// ==UserScript==
// @name          Offsite Blank
// @namespace     http://diveintogreasemonkey.org/download/
// @description   force offsite links to open in a new window
// @include       http://*
// @include       https://*
// ==/UserScript==

var a, thisdomain, links;
thisdomain = window.location.host;
links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
    a = links[i];
    if (a.host && a.host != thisdomain) {
        a.target = "_blank";
    }
}

먼저, 이 사용자 스크립트는 모든 웹 페이지에서 실행되어야 한다고 선언한다 (그러나, FileOpen 메뉴에서 열리는, 지역 머신에 저장된 HTML 문서들은 아니다.).

// @include       http://*
// @include       https://*

코드는 네 부분으로 나뉜다:

  1. 현재 페이지의 도메인을 얻는다.
  2. 그 페이지의 링크가 모두 담긴 리스트를 얻는다.
  3. 각 링크의 도메인을 페이지의 도메인과 비교한다.
  4. 도메인이 일치하지 않으면, 그 링크의 목표가 새 창에서 열리도록 설정한다.

현재 페이지의 도메인은 쉽게 얻을 수 있다.. 더 자세한 정보는 현재 도메인 이름 얻기를 참고하자.

thisdomain = window.location.host;

그 페이지의 모든 링크를 담은 목록을 얻는 것도 역시 쉽다. 그렇지만 나는 나의 충고를 무시하고 XPath 질의를 하는 대신에 그냥 document.getElementsByTagName('a')를 사용했음을 지적해 둔다. 뭐 어느모로 가나 서울만 가면 되는거 아닌가(To-may-to, to-mah-to...)

links = document.getElementsByTagName('a');

다음으로 모든 링크를 회돌이 하면서 (모든 <a> 요소들, 혹시 링크일 가능성이 있는 요소들) 그 링크의 도메인이 현재 페이지의 도메인과 일치하는지 점검한다. 어떤 링크들은 비-HTTP URL들을 가리킬 수 있으므로 (예를 들어, 지역 파일을 가리키거나, 또는 FTP 서버를 가리킬 수도 있다), a.host가 존재하는지 점검하고, 다음 그것이 현재 도메인과 같은지 점검한다.

for (var i = 0; i < links.length; i++) {
    a = links[i];
    if (a.host && a.host != thisdomain) {
        ...
    }

도메인을 가졌으나 현재 도메인과 같지 않은 링크를 발견한다면, 그냥 그의 target 속성에 "_blank"를 설정해서 그 링크가 새 창에서 열리도록 만들었다.

        a.target = "_blank";

내려받기

5.5. 사례 연구: Dumb Quotes

바보(dumb) 따옴표를 지능형(smart) 따옴표로 변환하기

DumbQuotes는 많은 웹로거들이 공유한 좌절로부터 탄생하였다: 대부분의 출판 소프트웨어는 자동으로 단순 ASCII 따옴표를 지능적으로 “짝 따옴표(smart quotes)”로 변환한다. 그러나, 같은 소프트웨어가 한 저자가 그것들을 복사해 한 글에 붙일 때는 “짝 따옴표” 처리를 할 줄 모른다. 흔한 예는 한 블로거가 또다른 사이트로부터 한 문장을 인용하고 싶어할 때이다. 웹 브라우저에서 몇 문장을 선택해서, 그것을 웹 폼에 붙여 넣어 자신의 사이트에 게시한다. 그러나 붙여 넣은 문장들은 결국 원래 사이트와 전혀 같아 보이지 않는데 그 이유는 출판 소프트웨어가 문자 인코딩을 제대로 추적하지 못하기 때문이다.

세상의 출판 시스템을 모두 고칠 수는 없겠지만, 이 문제는 직접 고칠 수 있다. 사용자 스크립트를 작성해서 자동으로 짝 따옴표와 문제 있는 높은-비트 문자들을 7-비트 아스키 문자로 변환했다.

예제: dumbquotes.user.js

// ==UserScript==
// @name          DumbQuotes
// @namespace     http://diveintogreasemonkey.org/download/
// @description   straighten curly quotes and apostrophes, simplify fancy dashes, etc.
// @include       *
// ==/UserScript==

var replacements, regex, key, textnodes, node, s;

replacements = {
    "\xa0": " ",
    "\xa9": "(c)",
    "\xae": "(r)",
    "\xb7": "*",
    "\u2018": "'",
    "\u2019": "'",
    "\u201c": '"',
    "\u201d": '"',
    "\u2026": "...",
    "\u2002": " ",
    "\u2003": " ",
    "\u2009": " ",
    "\u2013": "-",
    "\u2014": "--",
    "\u2122": "(tm)"};
regex = {};
for (key in replacements) {
    regex[key] = new RegExp(key, 'g');
}

textnodes = document.evaluate(
    "//text()",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
for (var i = 0; i < textnodes.snapshotLength; i++) {
    node = textnodes.snapshotItem(i);
    s = node.data;
    for (key in replacements) {
        s = s.replace(regex[key], replacements[key]);
    }
    node.data = s;
}

코드는 네 부분으로 나뉜다:

  1. 문자열 교체 목록을 정의한다. 특정한 8-비트 문자들을 동등한 7-비트 문자에 짝짓는다.
  2. 현재 페이지의 모든 텍스트 노드를 얻는다.
  3. 텍스트 노드를 담은 리스트를 회돌이한다.
  4. 각 텍스트에서, 노드는 8-비트 문자에 대하여 그의 7-비트 동등물로 교체한다.

첫 단계는 실제로는 두 단계이다. 자바스크립트의 문자열 교체는 정규 표현식에 기반한다. 그래서 8-비트 문자들을 동등한 7-비트 문자로 바꾸려면, 실제로 정규 표현식 객체 집합을 만들 필요가 있다.

replacements = {
    "\xa0": " ",
    "\xa9": "(c)",
    "\xae": "(r)",
    "\xb7": "*",
    "\u2018": "'",
    "\u2019": "'",
    "\u201c": '"',
    "\u201d": '"',
    "\u2026": "...",
    "\u2002": " ",
    "\u2003": " ",
    "\u2009": " ",
    "\u2013": "-",
    "\u2014": "--",
    "\u2122": "(tm)"};
regex = {};
for (key in replacements) {
    regex[key] = new RegExp(key, 'g');
}

활괄호 구문을 사용하여 즉시 연관 배열을 만들었다. 이것은 따로따로 각 키-값 쌍을 할당하는 것과 같다 (그러나 타자수는 절감된다):

replacements["\xa0"] = " ";
replacements["\xa9"] = "(c)";
replacements["\xae"] = "(r)";
// and so forth

각각의 8-비트 문자들은 피신 구문인 "\xa0" 또는 "\u2018"을 사용하여 16진 값으로 표현된다. 문자열에 대한 문자 연관 배열이 있으므로, 그 배열을 회돌이하면서 정규 표현식을 담은 리스트를 만든다. 각 정규 표현식 객체는 전역적으로 8-비트 문자 하나를 검색할 것이다. (두 번째 인자에서 'g'는 전역적으로 검색하라는 뜻이다; 그렇지 않으면 각 정규 표현식은 오직 특정한 8-bit 문자의 첫 출현만을 찾아서 바꿀 것이고, 그래서 나머지를 잃어 버릴 수도 있는 것이다.)

다음 단계는 현재 문서의 모든 텍스트 노드를 담은 리스트를 얻는 것이다. 이렇게 말하고 싶은 유혹이 들 것이다. “이봐요, document.body.innerHTML을 사용하고 전체 페이지를 문자열 하나로 얻어서, 거기에서 검색해서 바꾸면 될 것 같은데

var tmp = document.body.innerHTML;
// do a bunch of search/replace on tmp
document.body.innerHTML = tmp;

그러나 이것은 나쁜 습관이다. 왜냐하면 innerHTML 특성은 그 페이지의 전체 소스를 돌려줄 것이다: 즉 모든 조판, 모든 스크립트, 모든 속성들, 모조리 말이다. 이 경우 (HTML 태그에는 8-비트 문자가 없어서) 아마도 문제를 일으키지는 않겠지만, 다른 경우라면 큰 재앙이 될 수 있으며 디버그하기가 아주 어려울 수 있다. 정확하게 무엇을 찾아서 바꾸려고 하는지 스스로에게 물어볼 필요가 있다. 그 대답이 “미가공된 페이지 소스”라면 어서 innerHTML을 사용하자. 그렇지만, 이 경우, 그 대답은 “페이지 위의 모든 텍스트”이므로 올바른 해결책은 XPath 질의를 사용해 모든 텍스트 노드를 얻는 것이다.

textnodes = document.evaluate(
    "//text()",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);

이 것은 XPath 함수인 text()로 작동하는데, 이 함수에는 어떤 텍스트 노드도 부합한다. 아마도 요소 노드들을 질의하는 것에 더 친숙할 것이다: 모든 <a> 요소들을 담은 리스트, 또는 alt 속성이 있는 모든 <img> 요소들. 그러나 DOM에도 노드들이 있다. 요소 안에 실제 텍스트 내용에 대한 노드들이 담기는 경우도 있다 (다른 종류의 노드도 있다. 주석이라든가 처리 지시어 등). 당장 관심을 가진 곳은 바로 그 텍스트 노드이다.

3 단계는 모든 텍스트 노드를 회돌이하는 것이다. 이것은 //a[@href]와 같이 XPath 질의가 돌려준 모든 요소들을 회돌이하는 것과 정확하게 똑 같다; 유일한 차이점은 회돌이에서 각 항목이 요소 노드가 아니라 텍스트 노드라는 점이다.

for (var i = 0; i < textnodes.snapshotLength; i++) {
    node = textnodes.snapshotItem(i);
    s = node.data;
    // do replacements
    node.data = s;

node는 회돌이에서 현재 텍스트 노드이다. s는 문자열로서 그 node의 실제 텍스트이다. s를 사용하여 교체해서 그리고 그 결과를 다시 원래 노드에 복사할 생각이다.

그래서 이제 노드 하나짜리 텍스트를 확보하였고. 실제로 교체를 할 필요가 있다. 정규 표현식의 리스트를 미리 만들어 두었으므로, 이것은 상대적으로 수월하다.

    for (key in replacements) {
        s = s.replace(regex[key], replacements[key]);
    }

내려받기

5.6. 사례 연구: Frownies

그래픽 스마일리를 텍스트로 변환하기

Frownies는 한 농담에 대한 응답으로 시작되었다. 누군가 그리스몽키 메일링 리스트에서 :-) 같은 ASCII “smilies”를 상응하는 그래픽 동등물로 변환하는 사용자 스크립트를 개발했다고 선언하였다. 다른 누군가 그 반대로 하는데는 얼마나 걸릴까 의문으로 그에 응답했다: 그래픽 스마일리를 다시 텍스트로 바꾸려면 얼마나 걸릴까.

이 레코드에 대하여, 대략 20분이 걸렸다. 대부분의 시간은 그래픽 스마일리를 자동-생산한 출판 소프트에어를 재검색해서, 변종들을 담은 종합적인 리스트를 컴파일하는데 소비되었다.

이 스크립트는 그래픽 스마일리를 생성하는 대부분의 출판 소프트웨어가 그에 상응하는 텍스트 동등물을 <img> 요소의 alt 속성에 둔다는데 의존한다. 그래서 실제로 이 스크립트가 하는 일은 이미지를 그의 ALT 텍스트로 교체하는 것이다. ALT 텍스트가 리스트에 미리-정의된 상수에 일치하면 말이다.

예제: frownies.user.js

// ==UserScript==
// @name          Frownies
// @namespace     http://diveintogreasemonkey.org/download/
// @description   convert graphical smilies to their text equivalents
// @include       *
// ==/UserScript==

var smilies, images, img, replacement;
smilies = [":)", ":-)" ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
    ":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
    "X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
    ":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
    "I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
    ":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
    "8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
    "/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
    "#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
    "[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
    "B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
    "#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
    ":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
    ":-J", "(*)", ":GRIN:", ":-)", ":SMILE:", ":SAD:", ":EEK:",
    ":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
    ":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
    ":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
    ":MRGREEN:"];
images = document.evaluate(
    '//img[@alt]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
for (var i = 0; i < images.snapshotLength; i++) {
    img = images.snapshotItem(i);
    alt = img.alt.toUpperCase();
    for (var j in smilies) {
        if (alt == smilies[j]) {
            replacement = document.createTextNode(alt);
            img.parentNode.replaceChild(replacement, img);
        }
    }
}

코드는 네 단계로 나뉜다:

  1. 스마일리 리스트를 (텍스트 문자열로) 정의한다.
  2. 페이지에서 alt 속성이 담긴 이미지들을 모두 찾는다.
  3. 각 이미지에 대하여, ALT 텍스트가 ASCII 스마일리 리스트중의 하나와 일치하는지 점검한다.
  4. 일치하면, <img> 요소를 오직 ASCII 스마일리만 담긴 텍스트 노드로 바꾼다.

첫 단계는 단순하게 자바스크립트의 [ ] 구문을 사용하여 리스트를 정의한다.

smilies = [":)", ":-)" ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
    ":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
    "X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
    ":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
    "I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
    ":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
    "8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
    "/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
    "#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
    "[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
    "B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
    "#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
    ":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
    ":-J", "(*)", ":GRIN:", ":-)", ":SMILE:", ":SAD:", ":EEK:",
    ":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
    ":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
    ":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
    ":MRGREEN:"];

다음으로 XPath 질의를 사용하여, 페이지를 검색해 alt 속성을 가진 <img> 요소들을 모두 찾는다. XPath 질의에 관한 더 자세한 정보는 어떤 속성을 가진 요소마다 무엇인가 처리하기를 참고하자.

images = document.evaluate(
    '//img[@alt]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);

3 단계는 이 모든 <img> 요소들을 회돌이하며, alt 속성이 정의된 스마일리와 일치하는지 점검한다. 어떤 스마일리들은 문자들이 담겨 있으므로, toUpperCase() 메쏘드를 사용하여 그 alt 속성을 대문자로 변환한 다음 비교했다.

for (var i = 0; i < images.snapshotLength; i++) {
    img = images.snapshotItem(i);
    alt = img.alt.toUpperCase())
    for (var j in smilies) {
        if (alt == smilies[j]) {
            // ...
        }
    }
}

마지막으로, 그 스마일리의 텍스트로 새로운 텍스트 노드를 만들고, 기존의 <img> 요소를 교체한다. 더 자세한 것은 새로운 내용으로 요소 교체하기를 참고하자.

            replacement = document.createTextNode(alt);
            img.parentNode.replaceChild(replacement, img);

내려받기

5.7. 사례 연구: Zoom Textarea

버튼을 zoom textareas에 추가하기

Zoom Textarea는 웹 폼을 고쳐서 <textarea> 요소마다 툴바를 덧붙인다 (여러 줄의 텍스트를 입력하는데 사용됨). 이 툴바로 나머지 페이지의 스타일에 바꾸지 않고, <textarea>의 텍스트 크기를 늘이고 줄일 수 있다. 버튼은 완전히 키보드에서-접근가능하다; 마우스로 클릭하는 대신에 버튼으로 가서 ENTER를 누를 수 있다. (접근성이 중요하기 때문에 그리고 생각보다 더 어려웠기 때문에, 이것을 앞에서 이미 언급한 바 있다.)

예제: zoomtextarea.user.js

// ==UserScript==
// @name          Zoom Textarea
// @namespace     http://diveintogreasemonkey.org/download/
// @description   add controls to zoom textareas
// @include       *
// ==/UserScript==

var textareas, textarea;

textareas = document.getElementsByTagName('textarea');
if (!textareas.length) { return; }

function textarea_zoom_in(event) {
    var link, textarea, s;
    link = event.currentTarget;
    textarea = link._target;
    s = getComputedStyle(textarea, "");
    textarea.style.width = (parseFloat(s.width) * 1.5) + "px";
    textarea.style.height = (parseFloat(s.height) * 1.5) + "px";
    textarea.style.fontSize = (parseFloat(s.fontSize) + 7.0) + 'px';
    event.preventDefault();
}

function textarea_zoom_out(event) {
    var link, textarea, s;
    link = event.currentTarget;
    textarea = link._target;
    s = getComputedStyle(textarea, "");
    textarea.style.width = (parseFloat(s.width) * 2.0 / 3.0) + "px";
    textarea.style.height = (parseFloat(s.height) * 2.0 / 3.0) + "px";
    textarea.style.fontSize = (parseFloat(s.fontSize) - 7.0) + "px";
    event.preventDefault();
}

function createButton(target, func, title, width, height, src) {
    var img, button;
    img = document.createElement('img');
    img.width = width;
    img.height = height;
    img.style.borderTop = img.style.borderLeft = "1px solid #ccc";
    img.style.borderRight = img.style.borderBottom = "1px solid #888";
    img.style.marginRight = "2px";
    img.src = src;
    button = document.createElement('a');
    button._target = target;
    button.title = title;
    button.href = '#';
    button.onclick = func;
    button.appendChild(img);
    return button;
}

for (var i = 0; i < textareas.length; i++) {
    textarea = textareas[i];
    textarea.parentNode.insertBefore(
        createButton(
            textarea,
            textarea_zoom_in,
            'Increase textarea size',
            20,
            20,
            'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1tva3uTj52NjY2JiY7KxtPf3%2BLOys6WkpmJiYvDw8fX19vb'+
'296Wlpre3uEZFR%2B%2Fv8aqpq9va3a6tr6Kho%2Bjo6bKytZqZml5eYMLBxNra21JSU3'+
'Jxc3RzdXl4emJhZOvq7KamppGQkr29vba2uGBgYdLR1dLS0lBPUVRTVYB%2Fgvj4%2BYK'+
'Bg6SjptrZ3cPDxb69wG1tbsXFxsrJy29vccDAwfT09VJRU6uqrFlZW6moqo2Mj4yLjLKy'+
's%2Fj4%2BK%2Busu7t783Nz3l4e19fX7u6vaalqNPS1MjHylZVV318ftfW2UhHSG9uccv'+
'KzfHw8qqqrNPS1eXk5tvb3K%2BvsHNydeLi40pKS2JhY2hnalpZWlVVVtDQ0URDRJmZm5'+
'mYm11dXp2cnm9vcFxcXaOjo0pJSsC%2FwuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'+
'H5BAAAAAAALAAAAAAUABQAAAeagGaCg4SFhoeIiYqKTSQUFwgwi4JlB0pOCkEiRQKKRxM'+
'gKwMGDFEqBYpPRj4GAwwLCkQsijwQBAQJCUNSW1mKSUALNiVVJzIvSIo7GRUaGzUOPTpC'+
'igUeMyNTIWMHGC2KAl5hCBENYDlcWC7gOB1LDzRdWlZMAZOEJl83VPb3ggAfUnDo5w%2F'+
'AFRQxJPj7J4aMhYWCoPyASFFRIAA7'),
        textarea);
    textarea.parentNode.insertBefore(
        createButton(
            textarea,
            textarea_zoom_out,
            'Decrease textarea size',
            20,
            20,
            'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1uTj59va3vDw8bKxtGJiYrOys6Wkpvj4%2BPb29%2FX19mJiY'+
'%2Ff3%2BKqqrLe3uLKytURDRFpZWqmoqllZW9va3aOjo6Kho4KBg729vWJhZK%2BuskZF'+
'R4B%2FgsLBxHNydY2Mj%2Ff396amptLS0l9fX9fW2dDQ0W1tbpmZm8DAwfT09fHw8n18f'+
'uLi49LR1V5eYOjo6VBPUa6tr769wEhHSNra20pJStPS1KuqrNPS1ZmYm%2B7t77Kys8rJ'+
'y%2Fj4%2BaSjpm9uca%2BvsMjHyqalqHRzdVJRU8PDxVRTVcvKzc3Nz0pKS9rZ3evq7MC'+
'%2FwsXFxp2cnnl4e1VVVu%2Fv8ba2uM7Oz29vcbu6vZqZmnJxc9vb3PHx8uXk5mhnamJh'+
'Y1xcXZGQklZVV29vcHl4eoyLjKqpq6Wlpl1dXuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAACH5BAAAAAAALAAAAAAUABQAAAeZgGaCg4SFhoeIiYqKR1IWVgcyi4JMBiQqA0heQgG'+
'KQTFLPQgMCVocBIoNNqMgCQoDVReKYlELCwUFI1glEYorOgopWSwiTUVfih8dLzRTKA47'+
'Ek%2BKBGE8GEAhFQYuPooBOWAHY2ROExBbSt83QzMbVCdQST8Ck4QtZUQe9faCABlGrvD'+
'rB4ALDBMU%2BvnrUuOBQkE4NDycqCgQADs%3D'),
        textarea);
    textarea.parentNode.insertBefore(
        document.createElement('br'),
        textarea);
}

코드가 복잡해 보이고, 사실 복잡하지만, 다른 이유가 있다. 복잡해 보이는 이유는 재잘대는 듯한 거대한 여러줄 문자열 때문이다. 이것들은 data: URI들이다. 보이기는 겁나지만 만들기는 쉽다. 진짜 복잡한 것은 다른 곳에 있다.

먼저, 페이지에서 모든 <textarea> 요소를 담은 리스트를 얻는다. 이 패턴에 관한 더 자세한 정보는 특정한 HTML 요소의 실체마다 무엇인가를 하기를 참조하자. 하나도 없다면, 계속할 이유가 없다. 그래서 리턴(return) 한다.

textareas = document.getElementsByTagName('textarea');
if (!textareas.length) { return; }

이제 좀 뛰어 놀아보자. 우리의 “툴바”는 시각적으로 한 행의 버튼들이지만, 각 버튼은 실제로는 그냥 무엇인가 누를 수 있는 이미지에 불과하다. 안에는 자바스크립트 함수중 하나가 실행되는 링크가 쌓여있는 이미지 말이다.

버튼을 여러개 만들 생각이기 때문에 (이 스크립트는 두 개뿐이지만, 쉽게 더 기능을 확장할 수 있다), 함수를 만들어서 모든 버튼-제조 로직을 집어 넣었다.

function createButton(target, func, title, width, height, src) {

createButton 버튼은 인자를 6개 취한다:

target
이 버튼이 통제할 <textarea> 요소 객체이다
func
사용자가 마우스로 버튼을 클릭할 때 또는 키보드로 활성화 시킬 때 호출될 자바스크립트 함수 객체이다.
title
사용자가 커서를 버튼 위로 이동 시킬 때 보여줄 툴팁 텍스트 문자열이다.
width
버튼 너비를 나타내는 정수이다. 이것은 src 인자에 주어지는 그래픽의 너비가 되어야 한다.
height
버튼 높이를 나타내는 정수이다. 이것은 src 인자에 주어지는 그래픽의 높이가 되어야 한다.
src
버튼 그래픽의 URL, 경로, 또는 data: URI를 나타내는 문자열이다.

버튼을 만드는 일은 두 단계로 나뉜다: <img> 요소를 만들고, 다음 그 둘레에 <a> 요소를 만든다.

document.createElement을 호출하여 스타일 속성르 포함한, 속성 몇가지를 설정하여, <img> 요소를 만든다. 이 패턴에 관한 더 자세한 정보는한 요소의 스타일 설정하기 를 참고하자.

    img = document.createElement('img');
    img._target = target;
    img.width = width;
    img.height = height;
    img.style.borderTop = img.style.borderLeft = "1px solid #ccc";
    img.style.borderRight = img.style.borderBottom = "1px solid #888";
    img.style.marginRight = "2px";
    img.src = src;

멋지다고 생각하기 때문에, 빨리 지적하고 싶은 한 가지는 똑 같은 값을 두 속성에 한 번에 할당할 수 있다는 것이다. 다음 구문을 사용하면 된다:

    img.style.borderTop = img.style.borderLeft = "1px solid #ccc";

좋다, 계속 가자. 버튼 만들기의 두 번째 단계는 링크를 만들고 ( <a> 요소) 그 안에 <img> 요소를 배치하는 것이다.

    button = document.createElement('a');
    button._target = target;
    button.title = title;
    button.href = '#';
    button.onclick = func;
    button.appendChild(img);

여기에서 두 가지를 지적하고 싶다. 첫째, 링크에 가짜 href 속성을 할당할 필요가 있다. 그렇지 않으면, 파이어폭스가 그것을 이름있는 앵커로 취급해서 탭 인덱스에 추가하지 않을 것이다 (즉, 거기에 탭을 붙일 수 없어서, 키보드로 접근이 불가능하다). 둘째, _target 속성을 설정해서 목표 <textarea>에 대한 참조점을 저장한다. 이것은 자바스크립트에서 완전히 적법하다; 객체에 새로운 속성을 만들 수 있다. 그냥 값을 할당하면 된다. 나중에 onclick 이벤트 처리자에서, 그 맞춤 _target 속성에 접근할 것이다.

이제 onclick 처리자에게로 돌아가보자. 각 처리자는 함수로서 event라는 한 개의 인자를 취한다.

function textarea_zoom_in(event)

event 객체에는 여러 특성이 있다. 그 중에서 당장 유일하게 관심이 있는 특성은: currentTarget이다.

    link = event.currentTarget;

Event 객체에 관한 문서를 읽어보면, 그냥 단순하게 target이라고 부르는 특성을 포함하여 목표에-관련된 특성들이 여럿 있는 것을 보게 될 것이다. 클릭된 링크에 대한 참조점을 얻기 위하여 event.target을 사용하고 싶은 유혹이 있을 것이다. 그러나 (내 생각으로는) 행동이 제멋대로이다. 사용자가 탭으로 그 버튼으로 와서 ENTER 키를 누르면, event.target이 그 링크이다. 그러나 사용자가 마우스로 버튼을 클릭하면, event.target는 그 링크 안의 이미지이다! 이에 대해서는 그럴만한 이유가 있다고 확신하지만, 그것은 DOM 이벤트 모델에 대한 나의 이해도를 넘어서는 것이다. 어떤 경우든, event.currentTarget는 링크를 돌려준다. 그래서 그것을 사용하면 된다.

다음으로 실제로 확대하고자 하는 <textarea>에 대한 참조점을 열람한다. 버튼을 만들 때 설정했던 맞춤 _target 특성을 통하여서 말이다.

    textarea = link._target;

이제 진짜 재미가 시작된다. (이미 재미있을 거라 생각하고 계시겠지만 말이다!) <textarea>의 현재 크기와 폰트 크기를 얻을 필요가 있다. 그래야 더 키울 수 있으니까 말이다. 단순하게 textarea.style(textarea.style.width, textarea.style.height, 그리고 textarea.style.fontSize)로부터 적당한 속성들을 열람하는 것은 작동하지 않을 것이다. 왜냐하면, 그런 것들이 설정되는 것은 페이지가 실제로 <textarea> 그 자체의 style 속성에 정의할 때이기 때문이다. 그것은 내가 원한 것이 아니다; 나는 실체 현재 스타일을 원한다. 그 때문에 getComputedStyle이 필요하다. 이 함수에 관한 더 자세한 정보는 한 요소의 스타일 얻기를 참고하자.

    s = getComputedStyle(textarea, "");
    textarea.style.width = (parseFloat(s.width) * 1.5) + "px";
    textarea.style.height = (parseFloat(s.height) * 1.5) + "px";
    textarea.style.fontSize = (parseFloat(s.fontSize) + 7.0) + 'px';

마지막으로, 버튼 링크에 추가한 가짜 href 값을 기억하시는지? 확실하게 키보드에서 접근이 가능하도록 하기 위해서 말이다. 이제, 그것은 쓸데 없게 되었다. 왜냐하면 파이어폭스가 onclick 처리자의 실행을 마치면, 그 링크를 따라 가려고 하기 때문이다. 비-존재 앵커를 가리키기 때문에, 파이어폭스는 그 버튼이 어디에 있든 페이지의 상단으로 점프한다. 이것은 짜증나는 일이다. 그것을 멈추려면 event.preventDefault()를 호출한 후에 onclick 처리자를 끝마쳐야 할 필요가 있다.

    event.preventDefault();

스크립트의 나머지는 보는 그대로다. 모든 <textarea> 요소들을 회돌이하면서, 각각에 대하여 확대 버튼을 만들어 (각각은 자신만의 onclick 처리자와 버튼 그래픽을 구비한다), 그 확대 버튼을 <textarea> 앞에 삽입한다. 각 이미지에 대하여, data: URI를 사용하여 인라인 그래픽을 만든다. 그래서 사용자는 버튼 그래픽을 얻기 위하여 중앙 서버를 거칠 필요가 없다. 이 패턴에 관한 더 자세한 정보는 한 요소의 앞에 내용 삽입하기 그리고 중앙 서버를 거치지 않고 이미지 추가하기를 참고하자.

for (var i = 0; i < textareas.length; i++) {
    textarea = textareas[i];
    textarea.parentNode.insertBefore(
        createButton(
            textarea,
            textarea_zoom_in,
            'Increase textarea size',
            20,
            20,
            'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1tva3uTj52NjY2JiY7KxtPf3%2BLOys6WkpmJiYvDw8fX19vb'+
'296Wlpre3uEZFR%2B%2Fv8aqpq9va3a6tr6Kho%2Bjo6bKytZqZml5eYMLBxNra21JSU3'+
'Jxc3RzdXl4emJhZOvq7KamppGQkr29vba2uGBgYdLR1dLS0lBPUVRTVYB%2Fgvj4%2BYK'+
'Bg6SjptrZ3cPDxb69wG1tbsXFxsrJy29vccDAwfT09VJRU6uqrFlZW6moqo2Mj4yLjLKy'+
's%2Fj4%2BK%2Busu7t783Nz3l4e19fX7u6vaalqNPS1MjHylZVV318ftfW2UhHSG9uccv'+
'KzfHw8qqqrNPS1eXk5tvb3K%2BvsHNydeLi40pKS2JhY2hnalpZWlVVVtDQ0URDRJmZm5'+
'mYm11dXp2cnm9vcFxcXaOjo0pJSsC%2FwuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'+
'H5BAAAAAAALAAAAAAUABQAAAeagGaCg4SFhoeIiYqKTSQUFwgwi4JlB0pOCkEiRQKKRxM'+
'gKwMGDFEqBYpPRj4GAwwLCkQsijwQBAQJCUNSW1mKSUALNiVVJzIvSIo7GRUaGzUOPTpC'+
'igUeMyNTIWMHGC2KAl5hCBENYDlcWC7gOB1LDzRdWlZMAZOEJl83VPb3ggAfUnDo5w%2F'+
'AFRQxJPj7J4aMhYWCoPyASFFRIAA7'),
        textarea);
    textarea.parentNode.insertBefore(
        createButton(
            textarea,
            textarea_zoom_out,
            'Decrease textarea size',
            20,
            20,
            'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1uTj59va3vDw8bKxtGJiYrOys6Wkpvj4%2BPb29%2FX19mJiY'+
'%2Ff3%2BKqqrLe3uLKytURDRFpZWqmoqllZW9va3aOjo6Kho4KBg729vWJhZK%2BuskZF'+
'R4B%2FgsLBxHNydY2Mj%2Ff396amptLS0l9fX9fW2dDQ0W1tbpmZm8DAwfT09fHw8n18f'+
'uLi49LR1V5eYOjo6VBPUa6tr769wEhHSNra20pJStPS1KuqrNPS1ZmYm%2B7t77Kys8rJ'+
'y%2Fj4%2BaSjpm9uca%2BvsMjHyqalqHRzdVJRU8PDxVRTVcvKzc3Nz0pKS9rZ3evq7MC'+
'%2FwsXFxp2cnnl4e1VVVu%2Fv8ba2uM7Oz29vcbu6vZqZmnJxc9vb3PHx8uXk5mhnamJh'+
'Y1xcXZGQklZVV29vcHl4eoyLjKqpq6Wlpl1dXuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAACH5BAAAAAAALAAAAAAUABQAAAeZgGaCg4SFhoeIiYqKR1IWVgcyi4JMBiQqA0heQgG'+
'KQTFLPQgMCVocBIoNNqMgCQoDVReKYlELCwUFI1glEYorOgopWSwiTUVfih8dLzRTKA47'+
'Ek%2BKBGE8GEAhFQYuPooBOWAHY2ROExBbSt83QzMbVCdQST8Ck4QtZUQe9faCABlGrvD'+
'rB4ALDBMU%2BvnrUuOBQkE4NDycqCgQADs%3D'),
        textarea);
    textarea.parentNode.insertBefore(
        document.createElement('br'),
        textarea);
}

내려받기

더 읽어야 할 것

5.8. 사례 연구: Access Bar

고정 상태 바에 접근키 표시하기

Access Bar는 웹 페이지에 정의된 접근 키들을 표시해 준다. 접근키(Accesskeys)는 접근성 특징으로서 페이지 저자가 정의한 키보드 단축키로 특정한 링크나 폼 필드로 점프할 수 있다. (접근키에 관하여 더 배워보자.)

파이어폭스는 접근키를 지원하지만, 어느 키가 페이지에 정의되어 있는지는 보여주지 않는다. 그래서, Access Bar가 탄생하였다.

예제: accessbar.user.js

// ==UserScript==
// @name          Access Bar
// @namespace     http://diveintogreasemonkey.org/download/
// @description   show accesskeys defined on page
// @include       *
// ==/UserScript==

function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

var akeys, descriptions, a, desc, label, div;
akeys = document.evaluate(
    "//*[@accesskey]",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
if (!akeys.snapshotLength) { return; }
descriptions = new Array();
desc = '';
for (var i = 0; i < akeys.snapshotLength; i++) {
    a = akeys.snapshotItem(i);
    desctext = '';
    if (a.nodeName == 'INPUT') {
        label = document.evaluate("//label[@for='" + a.name + "']",
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null).singleNodeValue;
        if (label) {
            desctext = label.title;
            if (!desctext) { desctext = label.textContent; }
        }
    }
    if (!desctext) { desctext = a.textContent; }
    if (!desctext) { desctext = a.title; }
    if (!desctext) { desctext = a.name; }
    if (!desctext) { desctext = a.id; }
    if (!desctext) { desctext = a.href; }
    if (!desctext) { desctext = a.value; }
    desc = '<strong>[' +
        a.getAttribute('accesskey').toUpperCase() + ']</strong> ';
    if (a.href) {
        desc += '<a href="' + a.href + '">' + desctext + '</a>';
    } else {
        desc += desctext;
    }
    descriptions.push(desc);
}
descriptions.sort();
div = document.createElement('div');
div.id = 'accessbar-div-0';
desc = '<div><ul><li class="first">' + descriptions[0] + '</li>';
for (var i = 1; i < descriptions.length; i++) {
    desc = desc + '<li>' + descriptions[i] + '</li>';
}
desc = desc + '</ul></div>';
div.innerHTML = desc;
document.body.style.paddingBottom = "4em";
window.addEventListener(
    "load",
    function() {
        document.body.appendChild(div);
    },
    true);
addGlobalStyle(
'#accessbar-div-0 {'+
'  position: fixed;' +
'  left: 0;' +
'  right: 0;' +
'  bottom: 0;' +
'  top: auto;' +
'  border-top: 1px solid silver;' +
'  background: black;' +
'  color: white;' +
'  margin: 1em 0 0 0;' +
'  padding: 5px 0 0.4em 0;' +
'  width: 100%;' +
'  font-family: Verdana, sans-serif;' +
'  font-size: small;' +
'  line-height: 160%;' +
'}' +
'#accessbar-div-0 a,' +
'#accessbar-div-0 li,' +
'#accessbar-div-0 span,' +
'#accessbar-div-0 strong {' +
'  background-color: transparent;' +
'  color: white;' +
'}' +
'#accessbar-div-0 div {' +
'  margin: 0 1em 0 1em;' +
'}' +
'#accessbar-div-0 div ul {' +
'  margin-left: 0;' +
'  margin-bottom: 5px;' +
'  padding-left: 0;' +
'  display: inline;' +
'}' +
'#accessbar-div-0 div ul li {' +
'  margin-left: 0;' +
'  padding: 3px 15px;' +
'  border-left: 1px solid silver;' +
'  list-style: none;' +
'  display: inline;' +
'}' +
'#accessbar-div-0 div ul li.first {' +
'  border-left: none;' +
'  padding-left: 0;' +
'}');

코드는 여섯 단계로 나뉜다:

  1. 도움자 함수 addGlobalStyle을 정의한다.
  2. accesskey 속성을 가진 페이지 요소들을 모두 찾는다
  3. 그 요소들을 회돌이하면서 각 요소를 기술할 가장 적절한 텍스트를 결정한다.
  4. accesskey-가능 요소들에 대한 링크들을 담은 정렬 리스트를 구성한다.
  5. 표준 ulli 요소를 사용하여, 정렬된 리스트를 페이지에 추가한다.
  6. 뷰포트 아래에 고정된 가짜-상태-바처럼 보이도록 리스트를 스타일한다.

무엇보다, 나의 CSS 스타일을 삽입하기 위해서 도움자 함수 addGlobalStyle이 필요하다 (6 단계에서 사용됨). 이 패턴에 관한 더 자세한 정보는 CSS 스타일 추가하기를 참고하자.

function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

2 단계는 페이지에서 accesskey 속성을 가진 요소들을 모두 담은 리스트를 얻는다. 이것은 파이어폭스 XPath 지원으로 쉽게 처리가능하다. XPath 질의가 반환 값을 돌려주지 않으면, 그냥 무시한다는 것을 주목하자. 화면에 표시할 것이 없기 때문이다. 이 패턴에 관한 더 자세한 정보는 어떤 속성을 가진 요소마다 무엇인가 처리하기를 참고하자.

var akeys, descriptions, a, i, desc, label, div;
akeys = document.evaluate(
    "//*[@accesskey]",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
if (!akeys.snapshotLength) { return; }

3 단계는 각 accesskey-가능 요소들에 대하여 적당한 설명이 담긴 리스트를 구성하는데, 스크립트에서 가장 복잡한 부분이다. 문제는 accesskey 속성이 서로다른 여러개의 HTML 요소처럼 보인다는 것이다.

한 폼에서 input 요소는 accesskey를 정의할 수 있다. input 요소들은 라벨과 연관될 수도 안 될 수도 있는데 label에는 input 필드에 대한 관련 텍스트 라벨이 담겨 있다. 그렇다면, label에는 input 필드에 관하여 보다 자세한 설명을 주는 title 속성이 있을 수 있거나 label 속성에 그냥 텍스트만 담겨 있을 수도 있다. 또는 input 요소가 전혀 라벨과 연관되지 않을 수도 있다. 어느 경우이든 input 요소의 value 속성이 최선의 방법이다.

한편, label 그 자체는 accesskey를 정의할 수 있다. 그 라벨이 기술하는 input 요소 대신에 말이다. 다시, label 요소의 title 속성의 설명을 찾아 보겠지만, title 속성이 존재하지 않으면 라벨 텍스트에 의지할 것이다.

링크는 accesskey 속성도 정의할 수 있다. 그렇다면, 그 링크 텍스트가 확실한 선택이다. 그러나 링크에 텍스트가 없다면 (예를 들어, 오직 이미지만 있을 뿐이라면), 그 링크의 title 속성이 다음으로 찾아 볼 곳이다. 링크에 텍스트도 없고 title도 없다면, 그 링크의 name 속성에 의존한다. 그 마저도 실패하면, 그 링크의 id 속성에 의존한다.

휴리스틱(추측알고리즘)이 멋지지 않은가? 다음이 그 완전한 알고리즘이다. 기억하자, akeysXPathResult 객체이고, 그래서 각 결과를 얻으려면 akeys.snapshotItem(i)를 호출할 필요가 있다.

descriptions = new Array();
desc = '';
for (var i = 0; i < akeys.snapshotLength; i++) {
    a = akeys.snapshotItem(i);
    desctext = '';
    if (a.nodeName == 'INPUT') {
        label = document.evaluate("//label[@for='" + a.name + "']",
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null).singleNodeValue;
        if (label) {
            desctext = label.title;
            if (!desctext) { desctext = label.textContent; }
        }
    }
    if (!desctext) { desctext = a.textContent; }
    if (!desctext) { desctext = a.title; }
    if (!desctext) { desctext = a.name; }
    if (!desctext) { desctext = a.id; }
    if (!desctext) { desctext = a.href; }
    if (!desctext) { desctext = a.value; }
    desc = '<strong>[' +
        a.getAttribute('accesskey').toUpperCase() + ']</strong> ';
    if (a.href) {
        desc += '<a href="' + a.href + '">' + desctext + '</a>';
    } else {
        desc += desctext;
    }
    descriptions.push(desc);
}

4 단계는 간단하다. 자바스크립트 배열은 sort 메쏘드가 있어서 배열을 제자리에서 정렬할 수 있다.

descriptions.sort();

5 단계는 HTML을 만들어서 accesskey-가능한 요소들의 리스트를 가공처리한다. <div> 포장자를 만들어서, 접근키 리스트에 대하여 문자열로 HTML을 구성하여, 포장자 <div>innerHTML 특성에 설정하고, 마지막으로 그것을 페이지의 끝에 추가하였다. 사용자 스크립트가 실행되면, 한 페이지가 적재될 때까지 그 페이지에 대한 복잡한 변화들이 지연될 것이다. 그래서 포장자 <div>를 페이지에 추가하기 위하여 window.addEventListener를 사용하여 onload 이벤트를 추가한다.

innerHTML에 관한 더 자세한 정보는 신속하게 복잡한 HTML 삽입하기를 참고하고, window.addEventListener에 관한 더 자세한 정보는 가공처리한 후에 페이지 후-처리하기를 참고하자.

div = document.createElement('div');
div.id = 'accessbar-div-0';
desc = '<div><ul><li class="first">' + descriptions[0] + '</li>';
for (var i = 1; i < descriptions.length; i++) {
    desc = desc + '<li>' + descriptions[i] + '</li>';
}
desc = desc + '</ul></div>';
div.innerHTML = desc;
document.body.style.paddingBottom = "4em";
window.addEventListener(
    "load",
    function() {
        document.body.appendChild(div);
    },
    true);

마지막으로, 6 단계에서 나만의 CSS 선언 세트를 페이지에 추가하여 끼워 넣을 HTML이 예쁘게 보이도록 만든다. (특히, 페이지 하단을 따라서 고정된 검정색 막대처럼 보일 것이다. 페이지를 스크롤하더라도 그 자리에서 움직이지 않는다. 이것은 파이어폭스가 position:fixed 표시 유형을 지원하기 때문에 가능하다.) 더 자세한 정보는 CSS 스타일 추가하기를 참고하자.

addGlobalStyle(
'#accessbar-div-0 {'+
'  position: fixed;' +
'  left: 0;' +
'  right: 0;' +
'  bottom: 0;' +
'  top: auto;' +
'  border-top: 1px solid silver;' +
'  background: black;' +
'  color: white;' +
'  margin: 1em 0 0 0;' +
'  padding: 5px 0 0.4em 0;' +
'  width: 100%;' +
'  font-family: Verdana, sans-serif;' +
'  font-size: small;' +
'  line-height: 160%;' +
'}' +
'#accessbar-div-0 a,' +
'#accessbar-div-0 li,' +
'#accessbar-div-0 span,' +
'#accessbar-div-0 strong {' +
'  background-color: transparent;' +
'  color: white;' +
'}' +
'#accessbar-div-0 div {' +
'  margin: 0 1em 0 1em;' +
'}' +
'#accessbar-div-0 div ul {' +
'  margin-left: 0;' +
'  margin-bottom: 5px;' +
'  padding-left: 0;' +
'  display: inline;' +
'}' +
'#accessbar-div-0 div ul li {' +
'  margin-left: 0;' +
'  padding: 3px 15px;' +
'  border-left: 1px solid silver;' +
'  list-style: none;' +
'  display: inline;' +
'}' +
'#accessbar-div-0 div ul li.first {' +
'  border-left: none;' +
'  padding-left: 0;' +
'}');

내려받기

제 6 장 고급 주제들

6.1. 영속 데이터 저장하기와 열람하기

그리스 몽키에 정의된 두 함수, GM_setValueGM_getValue로 사용자 스크립트는 “사적인” 데이터를 열람하고 저장할 수 있다. (다른 스크립트는 접근할 수 없으며, 물론다른 사용자 스크립트도 물론 접근이 불가능하다.) 이 함수들을 사용하여 스크립트-전용의 환경구성을 설정할 수 있어서, 페이지 사이에 영속하는 캐쉬를 유지관리하거나, 영구적인 활동 기록을 남길 수 있다.

[Note]

GM_setValue로 저장되고 GM_getValue 열람되는 데이터는 브라우저의 쿠키와 비슷하다. 그러나 중요한 차이점들이 있다. 둘 모두 지역 머신에 저장되지만, 쿠키는 도메인-종속적이고 원래의 도메인으로부터만 접근이 가능한 반면, 그리스몽키의 환경구성 값들은 스크립트-종속적이고 그것들을 만든 사용자 스크립트만 접근이 가능하다 (현재 사용자 스크립트가 실행중인 URL에 관련이 없다). 그리고 쿠키와 다르게, 사용자 스크립트 데이터는 원격 서버로 절대 전송되지 않는다.

GM_setValue는 스크립트-종속적 환경구성 값을 저장하고, GM_getValue는 그것을 열람한다.

function GM_setValue(key, value);

function GM_getValue(key, defaultValue);

key 인자는 고정된 형태가 없는 문자열이다. value는 문자열, 불리언, 또는 정수일 수 있다. GM_getValuedefaultValue인자는 선택적이다; 있다면, 요청된 key가 존재하지 않을 때 이것이 반환된다. defaultValue가 주어지지 않고 요청된 key 키가 존재하지 않으면, GM_getValueundefined를 돌려준다.

이 함수들은 그리스몽키 0.3에서 도입되었다. 반드시 존재하는지 테스트해 보고 없다면 우아하게 디그레이드하자.

더 읽어야 할 것

다음도 참조

6.2. 메뉴바에 항목 추가하기

그리스몽키에 정의된 함수인 GM_registerMenuCommand를 사용하면 사용자 스크립트는 항목을 파이어폭스 메뉴에 추가할 수 있다. 등록된 메뉴 항목들은 스크립트가 활성상태일 때 User Script Commands 하부메뉴에 아타난다.

function GM_registerMenuCommand(menuText, callbackFunction);

menuText는 추가할 메뉴 항목의 텍스트를 나타내는 문자열이다. callbackFunction는 함수 객체이다. 메뉴 항목이 선택되면, callbackFunction 함수가 호출된다.

이 함수는 그리스몽키 0.2.6에서 도입되었다. 반드시 이 함수가 존재하는지 테스트해서 존재하지 않는다면 우아하게 다운그레이드하자.

더 읽어야 할 것

  • POST Interceptor는 스크립트의 활성화 상태를 토글하는 메뉴 항목들을 추가한다.

다음도 참조

6.3. 다른 사이트로부터 데이터 통합하기

그리스몽키에 정의된 GM_xmlhttpRequest 함수로 사용자 스크립트는 다른 URL로부터 데이터를 가져올(GET)수 있거나 다른 URL로 데이터를 부칠(POST)수 있다.

더 자세한 정보와 예제는 GM_xmlhttpRequest를 참조하자.

이 함수는 그리스 몽키 0.2.6에서 도입되었다. 반드시 이 함수가 존재하는지 테스트해서 존재하지 않는다면 우아하게 다운그레이드하자.

더 읽어야 할 것

6.4.  확장으로 사용자 스크립트 컴파일하기

사용자 스크립트가 그리스몽키의 골격구조로부터 “성장했는가” 그리스몽키의 골격구조라? 사용자 스크립트가 특권있는 자바스크립트 함수이나 지역 파일에 접근할 필요가 있는가? 또는 완전히-자란 브라우저 확장에서만 사용가능한 기타 파이어폭스 특징에 접근할 필요가 있는가? 단 몇번의 클릭이면 사용자 스크립트를 다-자란 XPI로 바꿀 수 있다. 아드리언 홀로버티(Adrian Holovaty)의 놀라운 그리스몽키 컴파일러 덕분에 말이다!

절차: Butler를 브라우저 확장으로 컴파일하기

  1. Butler 사용자 스크립트 소스 코드를 방문한다. 메뉴에서, EditSelect All을 선택하여 (Ctrl-A) 소스 코드를 클립보드로 복사한다.

  2. 그리스몽키 컴파일러를 방문하자.

  3. Javascript” 필드로 간다. 메뉴에서, EditPaste (Ctrl-V)를 선택하여 Butler 소스 코드를 붙인다.

  4. Creator” 필드에, Mark Pilgrim을 입력한다.

  5. Version” 필드에, Butler 사용자 스크립트의 현재 버전을 입력한다 (이 글을 쓰는 시점에서 0.3이지만 정확한 버전은 사용자 스크립트의 메타데이터 섹션을 참고하자).

  6. 새 창을 열거나 GUID 생성기를 방문하여 무작위 GUID를 생성한다. 그 GUID를 활괄호를 포함하여 클립보드에 복사한다.

  7. 다시 “그리스몽키 컴파일러” 페이지로 돌아간다. “GUID” 필드에, GUID 생성기로부터 얻는 그 GUID를 붙이자.

  8. Homepage” 필드에, http://diveintomark.org/projects/butler/를 입력한다.

  9. Create the Firefox extension을 클릭한다. 파이어폭스가 “Opening butler.xpi”라는 제목의 대화상자를 띄워 줄 것이다. Save to disk를 선택해 디렉토리를 지정한다.

짜잔! 이제 브라우저 확장 버전의 Butler가 탄생하였다. Butler 확장을 설치하기 전에 먼저, Butler 사용자 스크립트를 무력화시키고 Google에서 아무거나 검색해 보고 진짜 무력화되었는지 확인해야 한다. 다음 Butler 확장을 설치하자. FileOpen...을 선택하고 다음 butler.xpi 파일을 선택하면 된다. 그리스몽키 컴파일로 만든 그 파일 말이다. 설치를 마치려면 꼭 브라우저를 재시작해야 한다.

.xpi 파일은 실제로는 그저 특정한 디렉토리 구조를 가진 ZIP 압축파일이다. 아무 ZIP 프로그램이나 사용하면 (윈도우즈에서 7-zip이라든가, Mac OS X에서 Stuffit Expander 같은 프로그램들 ) 그 압축파일을 풀어서 브라우저 확장을 구성하고 있는 파일들을 살펴볼 수 있다.

butler.xpi
|
+-- install.rdf
|
+-- chrome/
    |
    +-- butler/
        |
        +-- content/
            |
            +-- browser.xul
            |
            +-- contents.rdf
            |
            +-- javascript.js

네 개의 파일이 관련된다. 두개의 RDF 파일의 거의 대부분 파이어폭스 보일러플레이트이다. 나머지 두 개에 실체 코드가 담겨 있다.

install.rdf
확장에 관한 메타데이터, 여기에는 이름과 버전, 설명과 호환되는 파이어폭스 버전이 들어간다.
browser.xul
현재 URL을 스크립트의 @include@exclude 매개변수에 비교하여 점검하고, 그 스크립트를 삽입해 실행하는 기동코드.
contents.rdf
메타데이더, 파이어폭스 보일러플레이트.
javascript.js
사용자 스크립트의 소스 코드 원본.

기본적으로, 그리스몽키 컴파일러는 정말로 뼈대만 앙상한 그리스몽키를 만든다. 사용자 인터페이스도 없고 자동으로 단일 사용자 스크립트를 적절한 URL들에 적재한다. 그러나 이제 모든 자원 파일이 있고, 원하는 무엇이든 할 수 있다: 맞춤 대화상자를 만들고, 선호사항 구역을 수정하고, User Script Commands 메뉴 밖에 메뉴 항목들을 등록하고...등등 신나게 말이다!

더 읽어야 할 것

그리스몽키 API 참조서

이름

GM_log — 자바스크립트 콘솔에 보내는 로그 메시지

개요

function GM_log(message);

설명

GM_log는 자바스크립트 콘솔에 출력된다. 보통 디버깅 목적으로 사용된다.

이력

GM_log는 그리스몽키 0.3에 도입되었다.

다음도 참조


이름

GM_getValue —는 스크립트-종속적 환경구성 값을 얻는다

개요

returntype GM_getValue(key, defaultValue);

설명

GM_getValue는 스크립트-종속적 환경구성 값을 열람한다. 반환 값은 문자열, 불리언, 또는 정수일 수 있다. key 인자는 정형화된 포맷이 없는 문자열이다. defaultValue 인자는 선택적이다; 있다면, 요청된 key가 존재하지 않을 때 이것이 반환된다. defaultValue가 주어지지 않고 요청된 key마저 존재하지 않으면, GM_getValueundefined를 돌려준다.

그리스몽키 환경구성 값들은 브라우저 쿠키와 비슷하지만, 중요한 차이점들이 있다. 둘 모두 지역 머신에 저장되지만, 쿠키는 도메인-종속적이어서 오직 원래의 도메인으로부터만 접근이 가능한 반면, 그리스몽키 환경구성 값들은 스크립트-종속적이어서 그 값들을 만든 사용자 스크립트만 접근이 가능하다 (사용자 스크립트가 어디에서 실행중인지는 상관없다). 그리고 쿠키와 다르게, 환경구성 값들은 원격 서버로 전송되지 않는다.

[Tip]

저장된 환경구성 값들을 모두 보려면 about:config를 방문해서 greasemonkey.scriptvals를 여과해 보면 된다.

이력

GM_getValue는 그리스몽키 0.3에서 도입되었다.

다음도 참조


이름

GM_setValue — 스크립트-종속적 환경구성 값을 설정한다

개요

function GM_setValue(key, value);

설명

GM_setValue는 스크립트-종속적 환경구성 값을 저장한다. key 인자는 자유 형태의 문자열이다. value 인자는 문자열, 불리언 또는 정수일 수 있다. 두 인자 모두 필수이다.

그리스몽키 환경구성 값들은 브라우저 쿠기와 비슷하지만, 중요한 차이점들이 있다. 둘 모두 지역 머신에 저장되지만, 쿠키는 도메인-종속적이고 오직 원래 도메인으로부터만 접근이 가능한 반면, 그리스몽키 환경구성 값들은 스크리븥-종속적이며 오직 그것을 만든 사용자 스크립트만 접근할 수 있다 (사용자 스크립트가 현재 실행중인 주소에 상관이 없다). 쿠키와는 다르게, 환경구성 값들은 원격 서버로 전송되지 않는다.

이력

GM_getValue는 그리스몽키 0.3에서 도입되었다.

다음도 참조


이름

GM_registerMenuCommand — 메뉴 항목을 User Script Commands 서브메뉴에 추가한다

개요

function GM_registerMenuCommand(   menuText,
    callbackFunction);

설명

GM_registerMenuCommand는 메뉴 항목을 ToolsUser Script Commands 메뉴에 추가한다. menuText 인자는 문자열로서, 추가할 메뉴 항목의 텍스트이다. callbackFunction 인자는 함수 객체이다. 메뉴 항목이 선택되면, callbackFunction 함수가 호출된다.

function callbackFunction(e);

e 매개변수는 메뉴 선택 이벤트이다. 이것이 어디에 쓸모가 있는지는 나도 모르겠다.

버그

메뉴 가속 키를 설정할 방법이 없다. GM_registerMenuCommand('Some &menu text', myFunction)를 호출하면 타이틀이 “Some &menu text”인 메뉴가 생성될 것이다. (Bug 10090)

이력

GM_registerMenuCommand는 그리스몽키 0.2.6에서 도입되었다.


이름

GM_xmlhttpRequest — 임의의 HTTP 요청을 만든다

개요

GM_xmlhttpRequest(details);

설명

GM_xmlhttpRequest은 임의의 HTTP 요청을 만든다. details 인자는 7개의 필드까지 담을 수 있는 객체이다.

method
문자열, 이 요청에 사용할 HTTP 메쏘드. 필수. 일반적으로 GET이지만, POSTPUT 그리고 DELETE를 포함하여 HTTP 동사이면 무엇이든 된다.
url
문자열, 이 요청에 사용할 URL. 필수.
headers
이 요청에 포함시킬 HTTP 헤더의 연관 배열. 선택적으로, 기본값은 빈 배열이다. 예제:
headers: {'User-Agent': 'Mozilla/4.0 (compatible) Greasemonkey',
          'Accept': 'application/atom+xml,application/xml,text/xml'}
data
문자열, HTTP 요청의 몸체. 선택적으로, 기본값은 빈 문자열이다. 폼을 포스트하는 흉내를 내고 있다면 (method == 'POST'), 반드시 'application/x-www-form-urlencoded'라는 Content-typeheaders 필드에 포함시켜야 하며, URL-인코드된 폼 데이터를 data 필드에 포함시켜야 한다.
onload
함수 객체, 요청이 성공적으로 완료되었을 때 호출될 역호출 함수.
onerror
함수 객체, 요청을 처리하는 중에 에러가 일어나면 호출될 역호출 함수.
onreadystatechange
함수 객체, 요청이 처리중일 때 반복적으로 호출될 역호출 함수.

onload callback

onload에 대한 역호출함수는 responseDetails라는 인자르 하나 취한다.

function onloadCallback(responseDetails);

responseDetails는 필드가 다섯개인 객체이다.

status
정수이다. 응답의 HTTP 상태 코드이다. 200은 요청이 정상적으로 완료되었다는 뜻이다.
statusText
문자열이다. HTTP 상태 텍스트로서 서버에-종속적이다.
responseHeaders
문자열이다. 응답안에 포함된 HTTP 헤더이다.
responseText
문자열이다. 응답의 몸체이다.
readyState
사용되지 않는다

onerror callback

onerror에 대한 역호출 함수는 responseDetails라는 인자 하나를 취한다.

function onerrorCallback(responseDetails);

responseDetails는 필드가 다섯개인 객체이다.

status
정수이다. 응답의 HTTP 에러 코드이다. 404는 페이지가 없음을 뜻한다.
statusText
문자열이다. HTTP 상태 텍스트로서 서버에-종속적이다.
responseHeaders
문자열이다. 응답에 포함된 HTTP 헤더이다.
responseText
문자열이다. 응답의 몸체이다. HTTP 에러 페이지의 몸체는 서버에-종속적이다.
readyState
사용되지 않는다

onreadystatechange callback

onreadystatechange에 대한 역호출함수는 요청이 진행되는 동안 반복적으로 호출된다. responseDetails라는 매개변수를 하나 취한다.

function onreadystatechangeCallback(   responseDetails);

responseDetails는 필드가 다섯인 객체이다. responseDetails.readyState는 현재 요청이 어느 단계에 있는지 나타낸다.

status
정수이다. 응답의 HTTP 상태 코드이다. 이 상태 코드는 responseDetails.readyState < 4이면 0이다.
statusText
문자열이다. HTTP 상태 텍스트이다. 이 텍스트는 responseDetails.readyState < 4이면 빈 문자열이다.
responseHeaders
문자열이다. 응답에 포함된 HTTP 헤더부이다. 이 헤더부는 responseDetails.readyState < 4이면 빈 문자열이다.
responseText
문자열이다. 응답의 몸체이다. 이것은 responseDetails.readyState < 4이면 빈 문자열이다.
readyState
정수이다. HTTP 요청의 단계이다.
1
적재중이다. 요청이 준비되었다.
2
적재되었다. 요청이 서버에 전송될 준비가 되었지만, 아직 아무것도 보내지 않았다.
3
상호대화상태이다. 요청이 전송되었고 클라이언트가 서버가 데이터 전송을 마칠 때까지 기다리고 있는 중이다.
4
완료되었다. 요청이 완료되었고 모든 응답 데이터가 다른 필드에서 사용이 가능하다.

예제

다음 코드는 아톰 감을 http://greaseblog.blogspot.com/으로부터 가져와 그 결과와 함께 경고를 화면에 표시한다.

GM_xmlhttpRequest({
    method: 'GET',
    url: 'http://greaseblog.blogspot.com/atom.xml',
    headers: {
        'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
        'Accept': 'application/atom+xml,application/xml,text/xml',
    },
    onload: function(responseDetails) {
        alert('Request for Atom feed returned ' + responseDetails.status +
              ' ' + responseDetails.statusText + '\n\n' +
              'Feed data:\n' + responseDetails.responseText);
    }
});

버그

The onreadystatechange 역호출은 readyState < 4와 잘 작동하지 않는다.

주의

XMLHttpRequest 객체와 다르게, GM_xmlhttpRequest는 현재 도메인에 제한되지 않는다; 어떤 URL로부터도 데이터를 얻거나(GET) 부칠수 있다(POST).

더 읽어야 할 것” 링크 목록

[Tip]

이 책에서 “더 읽어야 할 것” 링크 목록을 볼 때마다, “더 읽어야 할 것” 제목을 클릭하면, 더 읽어야 할 것 링크가 모두 담긴 목록으로 점프할 수 있다.

  1. 그리스몽키 스크립트 저장소에 수 백가지 그리스몽키 스크립트가 있다. 1.3. 사용자 스크립트 설치하기
  2. tag: URIs 2.2. 메타데이터로 사용자 스크립트 기술하기
  3. 자바스크립트의 익명 함수 2.3. 사용자 스크립트 코딩하기
  4. 자바스크립트의 블록 영역 그리고 관련 줄 토론 2.3. 사용자 스크립트 코딩하기
  5. DOM 검열자 소개 3.3. DOM 검열자로 요소 검열하기
  6. 요소 검열 확장 3.3.  DOM 검열자로 요소 검열하기
  7. 검열자 위젯 확장, 요소 검열 확장에 대한 확장으로서 컨텍스트 메뉴 항목 대신에 툴바 버튼을 추가한다. 3.3. DOM 검열자로 요소 검열하기
  8. 웹 개발자 확장에는 페이지를 분해하는데 사용되는 함수들이 많이 들어있다. 3.5. 기타 디버깅 도구들
  9. 아드바크(Aardvark)는 상호대화적으로 태그 이름, id 그리고 class 속성을 화면에 표시한다. 3.5. 기타 디버깅 도구들
  10. 벤크맨 자바스크립트 디버거는 완전한 실행-시간 자바스크립트 디버거이다. 3.5. 기타 디버깅 도구들
  11. Web Development Bookmarklets에는 툴바로 끌어올 만한 유용한 함수들이 수 없이 담겨 있다. 3.5. 기타 디버깅 도구들
  12. JSUnit은 자바스크립트용 유닛 테스트 작업틀이다. 3.5. 기타 디버깅 도구들
  13. js-lint는 자바스크립트 코드에서 일반적인 에러들을 점검한다.3.5. 기타 디버깅 도구들
  14. Mozilla XPath 문서 4.6. 어떤 속성을 가진 요소마다 무엇인가 처리하기
  15. 예제로 본 XPath 자습서 4.6. 어떤 속성을 가진 요소마다 무엇인가 처리하기
  16. XPathResult 참조서 4.6. 어떤 속성을 가진 요소마다 무엇인가 처리하기
  17. CSS properties 4.15. 요소의 스타일 설정하기
  18. 자바스크립트 이벤트 호환 테이블 4.22. 내장 자바스크립트 메쏘드 오버라이드하기
  19. 모질라에서의 자바스크립트-DOM 프로토타입 4.22. 내장 자바스크립트 메쏘드 오버라이드하기
  20. 이벤트 객체 상수 보여주기 4.22. 내장 자바스크립트 메쏘드 오버라이드하기
  21. Event 문서 5.7. 사례 연구: Zoom Textarea
  22. MyPIPsTag는 처음 실행하면 사용자이름을 요구한다. 6.1. 영속 데이터를 저장하고 열람하기
  23. POST Interceptor는 (GM_registerMenuCommand로) 메뉴 항목을 추가하여 스크립트의 활성상태를 토글한다. 6.1. 영속 데이터를 저장하고 열람하기
  24. MSDN 언어 여과기는 그의 환경구성 옵션을 페이지에 삽입한다. 6.1. 영속 데이터 저장하기와 열람하기
  25. POST 인터셉터는 메뉴 항목을 추가하여 스크립트의 활성 여부를 토글한다. 6.2. 메뉴바에 항목 추가하기
  26. LibraryLookupAmazon.com을 지역 라이브러리에 통합한다. 6.3. 다른 사이트로부터 데이터 통합하기
  27. Annotate GoogleGoogledel.icio.us를 통합한다. 6.3. 다른 사이트로부터 데이터 통합하기
  28. Bloglines TweaksBloglines의 글 요약에 Expand 버튼을 추가하여 글 전체를 열람하고 화면에 표시한다. 6.3. 다른 사이트로부터 데이터 통합하기
  29. Flick Batch EnhancerGM_xmlhttpRequest를 사용하여 Flickr에 특징을 추가한다. Flickr 독자적인 REST API를 사용한다. 6.3. 다른 사이트로부터 데이터 통합하기
  30. Hide Google Redirects구글 개인 검색 이력서를 재프로그램하여 보통의 <a href="..."> 링크를 사용하도록 하지만, 적절한 추적 URLGM_xmlhttpRequest를 호출하여 여전히 클릭을 추적한다. 6.3. 다른 사이트로부터 데이터 통합하기
  31. 개발자의 확장은 파이어폭스 확장의 디버깅과 테스트에 귀중하다. 6.4. 확장으로 사용자 스크립트 컴파일하기
  32. 모질라의 XMLHttpRequest 지원 GM_xmlhttpRequest
  33. 인터넷 익스플로러의 XMLHttpRequest 지원 GM_xmlhttpRequest
  34. 사파리의 XMLHttpRequest 지원 GM_xmlhttpRequest
  35. HTTP 상태 코드 GM_xmlhttpRequest
  36. RFC 2616 GM_xmlhttpRequest

팁 목록

[Tip]

이 책에서 팁이나 경고를 볼 때마다, 팁 아이콘을 클릭하면 모든 팁 목록으로 점프할 수 있다.

  1. 많은 사용자 스크립트는 그리스몽키 스크립트 저장고소에서 얻을 수 있다. 그렇지만 여러분의 스크립트가 반드시 거기에 있어야 하는 것은 아니다. 어떤 곳으로부터도 스크립트를 가져와도 좋다 (사용자가 설치해도 좋다). 웹 서버조차 필요하지 않다; 사용자 스크립트를 지역 파일로부터 설치해도 된다. 1.3. 사용자 스크립트 설치하기

  2. 사용자 스크립트 메타데이터의 각 항목들을 순서에 관계없이 지정해도 된다. 나는 @name, @namespace, @description, @include, 그리고 마지막으로 @exclude의 순서를 좋아하지만, 이 순서에 특별한 뜻은 없다. 2.2. 메타데이터로 사용자 스크립트 기술하기

  3. Manage User Scripts” 대화상자에서 Edit을 클릭하면, 파이어폭스 프로파일 디렉토리 깊숙히 묻힌 사용자 스크립트의 사본을 “산채로” 편집할 수 있다. 나는 이런 습관이 들었다. “산채로” 편집 세션을 마치면, 마지막으로 한 번 더 텍스트 편집기로 되돌아가 FileSave as...를 선택하여, 사용자 스크립트를 또다른 디렉토리에 저장한다. (그리스몽키는 프로파일 디렉토리에 있는 사본에만 관심을 가지므로) 반드시 필수는 아니지만, 일이 끝난 후에 나의 스크립트의 “마스터 사본을” 또다른 디렉토리에 보관하기를 선호한다.2.4. 사용자 스크립트 편집하기

  4. 자바스크립트 콘솔에서, 아무 줄에서나 오른쪽-클릭을 (맥 사용자는 콘트롤-클릭) 하여 Copy를 선택해 사용자 스크립트를 클립보드에 복사할 수 있다. 3.2. GM_log로 기록하기

  5. (thisElement와 같이) 한 요소에 대한 참조점이 있으면, thisElement.nodeName를 사용하여 그의 HTML 태그 이름을 결정할 수 있다. 만약 페지이가 text/html로 서비스되고 있으면, 그 태그 이름은 언제나 대문자로 반환된다. 원래 페이지에 어떻게 지정되어 있는가에 상관이 없다. 그렇지만, 그 페이지가 application/xhtml+xml로 서비스되고 있으면, 그 태그 이름은 언제나 소문자이다. 언제나 나는 thisElement.nodeName.toUpperCase()의 형태로 사용하고 그에 관해서는 신경쓰지 않는다. 4.6. 어떤 속성을 가진 요소마다 무엇인가 처리하기

  6. 새로운 내용을 someExistingElement.nextSibling 앞에 입력하는 일은 someExistingElement가 그의 부모 노드의 마지막 자손이라고 할지라도 (즉, 다음 형제노드가 없더라도) 작동할 것이다. 이 경우, someExistingElement.nextSiblingnull을 돌려주고, insertBefore 함수는 그냥 그 새로운 내용을 다른 모든 형제 노드들 뒤에 추가할 것이다. (이것이 이해가 가지 않더라도, 걱정하지 말자. 요점은 이 예제가 그럴 것 같이 보이지 않는 상황에서도 언제나 작동을 할 것이라는 것이다.) 4.8. 요소 뒤에 내용 추가하기

  7. 하고 싶은 일이 오직 광고를 제거하는 것이라면, 아마도 AdBlock을 설치하고 최신 여과 리스트를 반입하는 것이 독자적으로 사용자 스크립트를 만드는 것보다 훨씬 더 쉬울 것이다. 4.9. 요소 제거하기

  8. data: URI kitchen을 사용하여 자신만의 data: URL을 만들자. 4.12. 중앙 서버를 거치지 않고 이미지 추가하기

  9. addGlobalStyle 함수를 사용하면 페이지에 삽입한 요소들에, 또는 원래 페이지의 일부에 속한 요소들에 스타일을 입힐 수 있다. 그렇지만, 기존의 요소를 스타일링하려면, 각 규칙에 대하여 ! important 키워드를 사용하여 여러분이 정의한 스타일이 원래 페이지에 정의된 규칙들을 확실하게 오버라이드 하도록 하자. 4.13. CSS 스타일 추가하기

  10. document.body.innerHTML을 뽑아서 교체하더라도 그 페이지에 관한 어떤 것에도 영향을 미치지 않는다. 페이지 제목과 CSS 스타일 그리고 스크립트를 포함하여 원래 페이지의 <head>에 정의된 어떤 것도 여전히 영향을 미칠 것이다. 이것들을 따로따로 수정하거나 제거할 수 있다. 4.16. 가공후에 페이지 후-처리하기

  11. 사용자가 평소대로 폼을 제출하면 submit 이벤트가 촉발된다. 즉, 그 폼의 Submit 버튼을 클릭하거나 폼 안에서 ENTER를 때리면 말이다. 그렇지만, 그 폼이 aForm.submit() 스크립트 호출을 통하여 제출되면 submit 이벤트는 촉발되지 않는다. 그러므로 폼 제출을 잡으려면 두 가지 일을 할 필요가 있다: 이벤트 청취자를 추가하여 submit 이벤트를 나포하고, 그리고 HTMLFormElement 클래스의 프로토타입을 수정하여 그 submit() 메쏘드를 여러분의 맞춤 함수로 방향전환한다. 4.22. 내장 자바스크립트 메쏘드 오버라이드하기

  12. 저장된 환경구성값을 모두 보려면 about:config에 방문해서 greasemonkey.scriptvals를 여과해 보면 된다. GM_getValue

  13. 이 책에서 “더 읽어야 할 것”링크 목록을 볼 때마다, “더 읽어야 할 것” 헤더를 클릭하면 더 읽어야 할 것 링크를 모두 담은 목록으로 점프할 수 있다. 더 읽어야 할 것”링크 목록

  14. 이 책에서 팁이나 경고를 볼 때마다, 팁 아이콘을 클릭하면 모든 팁 목록으로 점프할 수 있다. 팁 목록

  15. 이 책에서 비디오 링크를 볼 때마다, 아이콘에 클릭하면 모든 비디오 목록으로 점프할 수 있다. 절차 목록

예제 목록

  1. 예제: helloworld.user.js
  2. 예제: Hello World 메타데이타
  3. 예제: “Hello world!” 경고 보여주기
  4. 예제: 함수 호출을 지연시키는 나쁜 방법
  5. 예제: 함수 호출을 지연시키는 좋은 방법
  6. 예제: 함수 호출을 지연시키는 최고의 방법
  7. 예제: 자바스크립트 콘솔에 쓰고나서 계속진행하기 (gmlog.user.js)
  8. 예제: 자바스크립트 쉘 소개
  9. 예제: 한 요소의 특성 얻기
  10. 예제: 한 도메인과 모든 하부도메인에 부합하는 메타데이터 태그
  11. 예제: GM_xmlhttpRequest 함수를 사용할 수 없으면 사용자에게 경고하기
  12. 예제: 페이지에 <textarea>가 포함되어 있는지 점검하기
  13. 예제: 모든 요소를 회돌이하기
  14. 예제: 한 페이지에서 모든 textareas를 찾기
  15. 예제: 한 페이지에서 모든 링크를 찾기
  16. 예제:  title 속성을 가진 모든 요소를 찾기
  17. 예제:  sponsoredlink
  18. 클래스를 가진 모든 <div>를 찾기
  19. 예제: xpath 함수
  20. 예제: 메인 내용 앞에 <hr> 삽입하기
  21. 예제: 내비게이션 바 뒤에 <hr> 삽입하기
  22. 예제: 광고 사이드바 제거하기
  23. 예제: 그의 alt 텍스트로 이미지 교체하기
  24. 예제: 페이지의 상단에 배너 삽입하기
  25. 예제: 페이지 상단에 그래픽 로고 추가하기
  26. 예제: 문단 텍스트 확대하기
  27. 예제: 한 요소의 style 속성이 정의한 스타일 얻기
  28. 예제: 한 요소의 실제 스타일 얻기
  29. 예제: 한 요소에 스타일 설정하기
  30. 예제: 맞춤 소로 전체 페이지 교체하기
  31. 예제: 메쏘드가 "POST" 또는 "post"인 모든 폼을 찾기
  32. 예제: 현재 도메인 얻기
  33. 예제: 링크의 끝에 질의 매개변수를 추가하기
  34. 예제: 한 사이트를 그의 안전한 짝에게 방향전환하기
  35. 예제: 사용자가 페이지 위에 클릭할 때 무엇인가 하기
  36. 예제: 폼이 제출되기 전에 무엇인가 하기
  37. 예제:  XML로된 임의의 문자열을 해석하기
  38. 예제: 원격 소스에서 가져 온 XML 해석하기
  39. 예제: GMail을 동등한 https:// 주소로 방향전환하기
  40. 예제: 읽지 않는 모든 항목들을 Bloglines가 자동으로 표시하도록 만들기
  41. 예제: aintitreadable.user.js
  42. 예제: offsiteblank.user.js
  43. 예제: dumbquotes.user.js
  44. 예제: frownies.user.js
  45. 예제: zoomtextarea.user.js
  46. 예제: accessbar.user.js

절차 목록

[Tip]

이 책에서 비디오 링크를 볼 때마다, 아이콘에 클릭하면 모든 비디오 목록으로 점프할 수 있다.

  1. 절차: 그리스몽키 확장 설치하기
  2. 절차: Butler 사용자 스크립트 설치하기
  3. 절차: 잠시 Butler 무력화하기
  4. 절차: Butler 설치제거
  5. 절차: Froogle은 그대로 두는 Butler
  6. 절차: Hello World 소스 코드 편집해서 그 결과 보기
  7. 절차: 자바스크립트 콘솔 열기
  8. 절차: DOM 검열자 설치하기
  9. 절차: Dive Into Greasemonkey 홈페이지 검열과 편집
  10. 절차: Inspect Element로 요소 직접 검열하기
  11. 절차: 자바스크립트 쉘 설치하기
  12. 절차: 브라우저 확장으로 Butler 컴파일하기

Revision history

Revision History
Revision 2005-05-09 2005-05-09
Revision 2005-05-06 2005-05-06
Revision 2005-05-05 2005-05-05
Revision 2005-05-04 2005-05-04
Revision 2005-05-03 2005-05-03
  • Compressed videos further.
  • Added videos to downloadable HTML distribution.
Revision 2005-05-02 2005-05-02
Revision 2005-05-01 2005-05-01
  • Added Greasemonkey API Reference.
  • Incorporated copious feedback from Simon Willison. Thanks, Simon.
  • Incorporated copious feedback from Jeremy Dunck. Thanks, Jeremy.
Revision 2005-04-30 2005-04-30
Revision 2005-04-25 2005-04-25
  • Initial publication

이 책에 관하여

이 책은 Emacs에서 DocBook XML로 작성되었다. SAXON으로 HTML로 변환하였다 (노먼 월쉬(Norman Walsh)의 XSL 스타일시트를 맞춤 재단한 버전으로 말이다). HTMLDoc으로 PDF로 변환하였다. w3m으로 평범한 텍스트로 변환하였다. Plucker Distiller로 Palm OS™ 데이터베이스로 변환하여서 이동형 장치에서도 읽을 수 있게 하였다. Ant 스크립트로 전 과정을 자동화하였다.

CamStudio 그리고 TMPGEnc와 함께 부록 비디오를 만들었다.

딘 에드워즈(Dean Edwards)가 js-highlight를 작성하여서, 자바스크립트 예제에 자동 구문 강조를 제공하였다.

기술적 저작을 위해 DocBook을 배우는데 관심이 있다면, XML 소스를 내려받자. 여기에는 DocBook 버전과 더불어 다양한 버전의 책을 만드는데 사용한 모든 XSL 스타일시트와 구축 스크립트들이 들어 있다. 또 교과서, DocBook: The Definitive Guide도 일독하여야 한다. DocBook 메일링 리스트에 등록하셔도 좋다.

GNU 일반 공중 사용 허가서

[ 영어 | 한국어 ]


This is an unofficial translation of the GNU General Public License into Korean. It was not published by the Free Software Foundation, and does not legally state the distribution terms for software that uses the GNU GPL -- only the original English text of the GNU GPL does that. However, I hope that this translation will help Korean speakers understand the GNU GPL better.

이 문서는 자유 소프트웨어 재단(Free Software Foundation)의 GNU General Public License를 한국어로 번역한 것입니다. 이 문서는 GNU General Public License가 내포하고 있는 호혜적인 자유와 공유의 정신을 보다 많은 사람들에게 알리기 위한 희망에서 작성되었지만, 자유 소프트웨어 재단의 공식 문서로 취급될 수는 없습니다. 이는 원래의 문서가 의도하고 있는 내용이 왜곡되지 않고 법률적으로 유효하기 위해서 선행되어야 할 양국의 현행 법률과 언어의 적합성 여부에 대한 전문가들의 검토 작업에 많은 비용이 필요하기 때문입니다. 또한 공식 번역문으로 인정된 문서라 하더라도 다른 언어로의 번역에 따른 위험 부담은 여전히 남아 있게 됩니다. 따라서 자유 소프트웨어 재단은 오역이나 해석상의 난점으로 인해서 발생될 지도 모를 혼란과 분쟁의 가능성을 미연에 방지하고, 문서가 담고 있는 내용과 취지를 보다 많은 사람들에게 알리려는 상반된 목적을, 한국어 번역문을 공식적으로 승인하지 않는 방법으로 양립시키고 있습니다.

자유 소프트웨어 재단은 어떠한 언어에 대한 번역문도 공식적으로 인정하지 않고 있으며, 그러한 계획 또한 갖고 있지 않습니다. 자유 소프트웨어 재단은 GNU General Public License를 실무에 적용할 경우, 오직 영문판에 의해서만 그 법률적 효력이 올바르게 발생될 수 있음을 권고하고 있습니다. 이 번역문은 법률적 검토와 문서간의 동일성 여부에 대한 검증을 거치지 않은 것이며, 이로 인해서 야기될 수 있을 지도 모를 법률적인 문제에 대해서 어떠한 형태의 보증도 제공하지 않습니다. GNU General Public License를 상업적인 목적으로 사용하려고 할 경우에는 변호사나 변리사에게 직접 자문을 구하기 바랍니다. 그러나 대부분의 일반 사용자들에게는 이 번역문이 전달하려고 하는 내용과 취지를 이해하는 것만으로도 충분할 것입니다.

GPL에 대한 실제 사례들을 모은 참고할 만한 자료의 하나로 GPL 해설이 있습니다.

한국어 번역: 1998년 6월 18일 송창훈 <chsong@gnu.org>


목 차


GNU 일반 공중 사용 허가서

2판, 1991년 6월

Copyright (C) 1989, 1991 Free Software Foundation, Inc. 
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

누구든지 본 사용 허가서를 있는 그대로 복제하고 배포할 수
있습니다. 그러나 본문에 대한 수정은 허용되지 않습니다. 


전 문

소프트웨어에 적용되는 대부분의 사용 허가서(license)들은 소프트웨어에 대한 수정과 공유의 자유를 제한하려는 것을 그 목적으로 합니다. 그러나 GNU 일반 공중 사용 허가서(이하, ``GPL''이라고 칭합니다.)는 자유 소프트웨어에 대한 수정과 공유의 자유를 모든 사용자들에게 보장하기 위해서 성립된 것입니다. 자유 소프트웨어 재단이 제공하는 대부분의 소프트웨어들은 GPL에 의해서 관리되고 있으며, 몇몇 소프트웨어에는 별도의 사용 허가서인 GNU 라이브러리 일반 공중 사용 허가서(GNU Library General Public License)를 대신 적용하기도 합니다. 자유 소프트웨어란, 이를 사용하려고 하는 모든 사람에 대해서 동일한 자유와 권리가 함께 양도되는 소프트웨어를 말하며 프로그램 저작자의 의지에 따라 어떠한 종류의 프로그램에도 GPL을 적용할 수 있습니다. 따라서 여러분이 만든 프로그램에도 GPL을 적용할 수 있습니다.

자유 소프트웨어를 언급할 때 사용되는 ``자유''라는 단어는 무료(無料)를 의미하는 금전적인 측면의 자유가 아니라 구속되지 않는다는 관점에서의 자유를 의미하며, GPL은 자유 소프트웨어를 이용한 복제와 개작, 배포와 수익 사업 등의 가능한 모든 형태의 자유를 실질적으로 보장하고 있습니다. 여기에는 원시 코드(source code)의 전부 또는 일부를 원용해서 개선된 프로그램을 만들거나 새로운 프로그램을 창작할 수 있는 자유가 포함되며, 자신에게 양도된 이러한 자유와 권리를 보다 명확하게 인식할 수 있도록 하기 위한 규정도 포함되어 있습니다.

GPL은 GPL 안에 소프트웨어를 양도받을 사용자의 권리를 제한하는 조항과 단서를 별항으로 추가시키지 못하게 함으로써 사용자들의 자유와 권리를 실제적으로 보장하고 있습니다. 자유 소프트웨어의 개작과 배포에 관계하고 있는 사람들은 이러한 무조건적인 권리 양도 규정을 준수해야만 합니다.

예를 들어 GPL 프로그램을 배포할 경우에는 프로그램의 유료 판매나 무료 배포에 관계없이 자신이 해당 프로그램에 대해서 가질 수 있었던 모든 권리를, 프로그램을 받게될 사람에게 그대로 양도해 주어야 합니다. 이 경우, 프로그램의 원시 코드를 함께 제공하거나 원시 코드를 구할 수 있는 방법을 확실히 알려주어야 하고 이러한 모든 사항들을 사용자들이 분명히 알 수 있도록 명시해야 합니다.

자유 소프트웨어 재단은 다음과 같은 두 가지 단계를 통해서 사용자들을 권리를 보호합니다. (1) 소프트웨어에 저작권을 설정합니다. (2) 저작권의 양도에 관한 실정법에 의해서 유효한 법률적 효력을 갖는 GPL을 통해 소프트웨어를 복제하거나 개작 및 배포할 수 있는 권리를 사용자들에게 부여합니다.

자유 소프트웨어를 사용하는 사람들은 반복적인 재배포 과정을 통해 소프트웨어 자체에 수정과 변형이 일어날 수도 있으며, 이는 최초의 저작자가 만든 소프트웨어가 갖고 있는 문제가 아닐 수 있다는 개연성을 인식하고 있어야 합니다. 우리는 개작과 재배포 과정에서 다른 사람에 의해 발생된 문제로 인해 프로그램 원저작자들의 신망이 훼손되는 것을 원하지 않습니다. GPL에 자유 소프트웨어에 대한 어떠한 형태의 보증도 규정하지 않는 이유는 이러한 점들이 고려되었기 때문이며, 이는 프로그램 원저작자와 자유 소프트웨어 재단의 자유로운 활동을 보장하는 현실적인 수단이기도 합니다.

특허 제도는 자유 소프트웨어의 발전을 위협하는 요소일 수밖에 없습니다. 자유 프로그램을 재배포하는 사람들이 개별적으로 특허를 취득하게 되면, 결과적으로 그 프로그램이 독점 소프트웨어가 될 가능성이 있습니다. 자유 소프트웨어 재단은 이러한 문제에 대처하기 위해서 어떠한 특허에 대해서도 그 사용 권리를 모든 사람들(이하, ``공중(公衆)''이라고 칭합니다.)에게 자유롭게 허용하는 경우에 한해서만 자유 소프트웨어와 함께 사용할 수 있다는 것을 명확히 밝히고 있습니다.

복제(copying)와 개작(modification) 및 배포(distribution)에 관련된 구체적인 조건과 규정은 다음과 같습니다.

복제와 개작 및 배포에 관한 조건과 규정

제 0 조. 본 허가서는 GNU 일반 공중 사용 허가서의 규정에 따라 배포될 수 있다는 사항이 저작권자에 의해서 명시된 모든 컴퓨터 프로그램 저작물에 대해서 동일하게 적용됩니다. 컴퓨터 프로그램 저작물(이하, ``프로그램''이라고 칭합니다.)이란 특정한 결과를 얻기 위해서 컴퓨터 등의 정보 처리 능력을 가진 장치(이하, ``컴퓨터''라고 칭합니다.) 내에서 직접 또는 간접으로 사용되는 일련의 지시 및 명령으로 표현된 창작물을 의미하고, ``2차적 프로그램''이란 전술한 프로그램 자신 또는 저작권법의 규정에 따라 프로그램의 전부 또는 상당 부분을 원용하거나 다른 언어로의 번역을 포함할 수 있는 개작 과정을 통해서 창작된 새로운 프로그램과 이와 관련된 저작물을 의미합니다. (이후로 다른 언어로의 번역은 별다른 제한없이 개작의 범위에 포함되는 것으로 간주합니다.) ``피양도자''란 GPL의 규정에 따라 프로그램을 양도받은 사람을 의미하고, ``원(原)프로그램''이란 프로그램을 개작하거나 2차적 프로그램을 만들기 위해서 사용된 최초의 프로그램을 의미합니다.

본 허가서는 프로그램에 대한 복제와 개작 그리고 배포 행위에 대해서만 적용됩니다. 따라서 프로그램을 실행시키는 행위에 대한 제한은 없습니다. 프로그램의 결과물(output)에는, 그것이 프로그램을 실행시켜서 생성된 것인지 아닌지의 여부에 상관없이 결과물의 내용이 원프로그램으로부터 파생된 2차적 프로그램을 구성했을 때에 한해서 본 허가서의 규정들이 적용됩니다. 2차적 프로그램의 구성 여부는 2차적 프로그램 안에서의 원프로그램의 역할을 토대로 판단합니다.

제 1 조. 적절한 저작권 표시와 프로그램에 대한 보증이 제공되지 않는다는 사실을 각각의 복제물에 명시하는 한, 피양도자는 프로그램의 원시 코드를 자신이 양도받은 상태 그대로 어떠한 매체를 통해서도 복제하고 배포할 수 있습니다. 복제와 배포가 이루어 질 때는 본 허가서와 프로그램에 대한 보증이 제공되지 않는다는 사실에 대해서 언급되었던 모든 내용을 그대로 유지시켜야 하며, 영문판 GPL을 함께 제공해야 합니다.

배포자는 복제물을 물리적으로 인도하는데 소요된 비용을 청구할 수 있으며, 선택 사항으로 독자적인 유료 보증을 설정할 수 있습니다.

제 2 조. 피양도자는 자신이 양도받은 프로그램의 전부나 일부를 개작할 수 있으며, 이를 통해서 2차적 프로그램을 창작할 수 있습니다. 개작된 프로그램이나 창작된 2차적 프로그램은 다음의 사항들을 모두 만족시키는 조건에 한해서, 제1조의 규정에 따라 또다시 복제되고 배포될 수 있습니다.

제 1 항. 파일을 개작할 때는 파일을 개작한 사실과 그 날짜를 파일 안에 명시해야 합니다.

제 2 항. 배포하거나 공표하려는 저작물의 전부 또는 일부가 양도받은 프로그램으로부터 파생된 것이라면, 저작물 전체에 대한 사용 권리를 본 허가서의 규정에 따라 공중에게 무상으로 허용해야 합니다.

제 3 항. 개작된 프로그램의 일반적인 실행 형태가 대화형 구조로 명령어를 읽어 들이는 방식을 취하고 있을 경우에는, 적절한 저작권 표시와 프로그램에 대한 보증이 제공되지 않는다는 사실, (별도의 보증을 설정한 경우라면 해당 내용) 그리고 양도받은 프로그램을 본 규정에 따라 재배포할 수 있다는 사실과 GPL 사본을 참고할 수 있는 방법이 함께 포함된 문구가 프로그램이 대화형 구조로 평이하게 실행된 직후에 화면 또는 지면으로 출력되도록 작성되어야 합니다. (예외 규정: 양도받은 프로그램이 대화형 구조를 갖추고 있다 하더라도 통상적인 실행 환경에서 전술한 사항들이 출력되지 않는 형태였을 경우에는 이를 개작한 프로그램 또한 관련 사항들을 출력시키지 않아도 무방합니다.)

위의 조항들은 개작된 프로그램 전체에 적용됩니다. 만약, 개작된 프로그램에 포함된 특정 부분이 원프로그램으로부터 파생된 것이 아닌 별도의 독립 저작물로 인정될 만한 상당한 이유가 있을 경우에는 해당 저작물의 개별적인 배포에는 본 허가서의 규정들이 적용되지 않습니다. 그러나 이러한 저작물이 2차적 프로그램의 일부로서 함께 배포된다면 개별적인 저작권과 배포 기준에 상관없이 저작물 모두에 본 허가서가 적용되어야 하며, 전체 저작물에 대한 사용 권리는 공중에게 무상으로 양도됩니다.

이러한 규정은 개별적인 저작물에 대한 저작자의 권리를 침해하거나 인정하지 않으려는 것이 아니라, 원프로그램으로부터 파생된 2차적 프로그램이나 수집 저작물의 배포를 일관적으로 규제할 수 있는 권리를 행사하기 위한 것입니다.

원프로그램이나 원프로그램으로부터 파생된 2차적 프로그램을 이들로부터 파생되지 않은 다른 저작물과 함께 단순히 저장하거나 배포할 목적으로 동일한 매체에 모아 놓은 집합물의 경우에는, 원프로그램으로부터 파생되지 않은 다른 저작물에는 본 허가서의 규정들이 적용되지 않습니다.

제 3 조. 피양도자는 다음 중 하나의 항목을 만족시키는 조건에 한해서 제1조와 제2조의 규정에 따라 프로그램(또는 제2조에서 언급된 2차적 프로그램)을 목적 코드(object code)나 실행물(executable form)의 형태로 복제하고 배포할 수 있습니다.

제 1 항. 목적 코드나 실행물에 상응하는 컴퓨터가 인식할 수 있는 완전한 원시 코드를 함께 제공해야 합니다. 원시 코드는 제1조와 제2조의 규정에 따라 배포될 수 있어야 하며, 소프트웨어의 교환을 위해서 일반적으로 사용되는 매체를 통해 제공되어야 합니다.

제 2 항. 배포에 필요한 최소한의 비용만을 받고 목적 코드나 실행물에 상응하는 완전한 원시 코드를 배포하겠다는, 최소한 3년간 유효한 약정서를 함께 제공해야 합니다. 이 약정서는 약정서를 갖고 있는 어떠한 사람에 대해서도 유효해야 합니다. 원시 코드는 컴퓨터가 인식할 수 있는 형태여야 하고 제1조와 제2조의 규정에 따라 배포될 수 있어야 하며, 소프트웨어의 교환을 위해서 일반적으로 사용되는 매체를 통해 제공되어야 합니다.

제 3 항. 목적 코드나 실행물에 상응하는 원시 코드를 배포하겠다는 약정에 대해서 자신이 양도받은 정보를 함께 제공해야 합니다. (제3항은 위의 제2항에 따라 원시 코드를 배포하겠다는 약정을 프로그램의 목적 코드나 실행물과 함께 제공 받았고, 동시에 비상업적인 배포를 하고자 할 경우에 한해서만 허용됩니다.)

저작물에 대한 원시 코드란 해당 저작물을 개작하기에 적절한 형식을 의미합니다. 실행물에 대한 완전한 원시 코드란 실행물에 포함된 모든 모듈들의 원시 코드와 이와 관련된 인터페이스 정의 파일 모두, 그리고 실행물의 컴파일과 설치를 제어하는데 사용된 스크립트 전부를 의미합니다. 그러나 특별한 예외의 하나로서, 실행물이 실행될 운영체제의 주요 부분(컴파일러나 커널 등)과 함께 (원시 코드나 바이너리의 형태로) 일반적으로 배포되는 구성 요소들은 이러한 구성 요소 자체가 실행물에 수반되지 않는 한 원시 코드의 배포 대상에서 제외되어도 무방합니다.

목적 코드나 실행물을 지정한 장소로부터 복제해 갈 수 있게 하는 방식으로 배포할 경우, 동일한 장소로부터 원시 코드를 복제할 수 있는 동등한 접근 방법을 제공한다면 이는 원시 코드를 목적 코드와 함께 복제되도록 설정하지 않았다고 하더라도 원시 코드를 배포하는 것으로 간주됩니다.

제 4 조. 본 허가서에 의해 명시적으로 이루어 지지 않는 한 프로그램에 대한 복제와 개작 및 하위 허가권 설정과 배포가 성립될 수 없습니다. 이와 관련된 어떠한 행위도 무효이며 본 허가서가 보장한 권리는 자동으로 소멸됩니다. 그러나 본 허가서의 규정에 따라 프로그램의 복제물이나 권리를 양도받았던 제3자는 본 허가서의 규정들을 준수하는 한, 배포자의 권리 소멸에 관계없이 사용상의 권리를 계속해서 유지할 수 있습니다.

제 5 조. 본 허가서는 서명이나 날인이 수반되는 형식을 갖고 있지 않기 때문에 피양도자가 본 허가서의 내용을 반드시 받아들여야 할 필요는 없습니다. 그러나 프로그램이나 프로그램에 기반한 2차적 프로그램에 대한 개작 및 배포를 허용하는 것은 본 허가서에 의해서만 가능합니다. 만약 본 허가서에 동의하지 않을 경우에는 이러한 행위들이 법률적으로 금지됩니다. 따라서 프로그램(또는 프로그램에 기반한 2차적 프로그램)을 개작하거나 배포하는 행위는 이에 따른 본 허가서의 내용에 동의한다는 것을 의미하며, 복제와 개작 및 배포에 관한 본 허가서의 조건과 규정들을 모두 받아들이겠다는 의미로 간주됩니다.

제 6 조. 피양도자에 의해서 프로그램(또는 프로그램에 기반한 2차적 프로그램)이 반복적으로 재배포될 경우, 각 단계에서의 피양도자는 본 허가서의 규정에 따른 프로그램의 복제와 개작 및 배포에 대한 권리를 최초의 양도자로부터 양도받은 것으로 자동적으로 간주됩니다. 프로그램(또는 프로그램에 기반한 2차적 프로그램)을 배포할 때는 피양도자의 권리의 행사를 제한할 수 있는 어떠한 사항도 추가할 수 없습니다. 그러나 피양도자에게, 재배포가 일어날 시점에서의 제3의 피양도자에게 본 허가서를 준수하도록 강제할 책임은 부과되지 않습니다.

제 7 조. 법원의 판결이나 특허권 침해에 대한 주장 또는 특허 문제에 국한되지 않은 그밖의 이유들로 인해서 본 허가서의 규정에 배치되는 사항이 발생한다 하더라도 그러한 사항이 선행하거나 본 허가서의 조건과 규정들이 면제되는 것은 아닙니다. 따라서 법원의 명령이나 합의 등에 의해서 본 허가서에 위배되는 사항들이 발생한 상황이라도 양측 모두를 만족시킬 수 없다면 프로그램은 배포될 수 없습니다. 예를 들면, 특정한 특허 관련 허가가 프로그램의 복제물을 직접 또는 간접적인 방법으로 양도받은 임의의 제3자에게 해당 프로그램을 무상으로 재배포할 수 있게 허용하지 않는다면, 그러한 허가와 본 사용 허가를 동시에 만족시키면서 프로그램을 배포할 수 있는 방법은 없습니다.

본 조항은 특정한 상황에서 본 조항의 일부가 유효하지 않거나 적용될 수 없을 경우에도 본 조항의 나머지 부분들을 적용하기 위한 의도로 만들어 졌습니다. 따라서 그 이외의 상황에서는 본 조항을 전체적으로 적용하면 됩니다.

본 조항의 목적은 특허나 저작권 침해 등의 행위를 조장하거나 해당 권리를 인정하지 않으려는 것이 아니라, GPL을 통해서 구현되어 있는 자유 소프트웨어의 배포 체계를 통합적으로 보호하기 위한 것입니다. 많은 사람들이 배포 체계에 대한 신뢰있는 지원을 계속해 줌으로써 소프트웨어의 다양한 분야에 많은 공헌을 해 주었습니다. 소프트웨어를 어떠한 배포 체계로 배포할 것인가를 결정하는 것은 전적으로 저작자와 기증자들의 의지에 달려있는 것이지, 일반 사용자들이 강요할 수 있는 문제는 아닙니다.

본 조항은 본 허가서의 다른 조항들에서 무엇이 중요하게 고려되어야 하는 지를 명확하게 설명하기 위한 목적으로 만들어진 것입니다.

제 8 조. 특허나 저작권이 설정된 인터페이스로 인해서 특정 국가에서 프로그램의 배포와 사용이 함께 또는 개별적으로 제한되어 있는 경우, 본 사용 허가서를 프로그램에 적용한 최초의 저작권자는 문제가 발생하지 않는 국가에 한해서 프로그램을 배포한다는 배포상의 지역적 제한 조건을 명시적으로 설정할 수 있으며, 이러한 사항은 본 허가서의 일부로 간주됩니다.

제 9 조. 자유 소프트웨어 재단은 때때로 본 사용 허가서의 개정판이나 신판을 공표할 수 있습니다. 새롭게 공표될 판은 당면한 문제나 현안을 처리하기 위해서 세부적인 내용에 차이가 발생할 수 있지만, 그 근본 정신에는 변함이 없을 것입니다.

각각의 판들은 판번호를 사용해서 구별됩니다. 특정한 판번호와 그 이후 판을 따른다는 사항이 명시된 프로그램에는 해당 판이나 그 이후에 발행된 어떠한 판을 선택해서 적용해도 무방하고, 판번호를 명시하고 있지 않은 경우에는 자유 소프트웨어 재단이 공표한 어떠한 판번호의 판을 적용해도 무방합니다.

제 10 조. 프로그램의 일부를 본 허가서와 배포 기준이 다른 자유 프로그램과 함께 결합하고자 할 경우에는 해당 프로그램의 저작자로부터 서면 승인을 받아야 합니다. 자유 소프트웨어 재단이 저작권을 갖고 있는 소프트웨어의 경우에는 자유 소프트웨어 재단의 승인을 얻어야 합니다. 우리는 이러한 요청을 수락하기 위해서 때때로 예외 기준을 만들기도 합니다. 자유 소프트웨어 재단은 일반적으로 자유 소프트웨어의 2차적 저작물들을 모두 자유로운 상태로 유지시키려는 목적과 소프트웨어의 공유와 재활용을 증진시키려는 두가지 목적을 기준으로 승인 여부를 결정할 것입니다.

보증의 결여 (제11조, 제12조)

제 11 조. 본 허가서를 따르는 프로그램은 무상으로 양도되기 때문에 관련 법률이 허용하는 한도 내에서 어떠한 형태의 보증도 제공되지 않습니다. 프로그램의 저작권자와 배포자가 공동 또는 개별적으로 별도의 보증을 서면으로 제공할 때를 제외하면, 특정한 목적에 대한 프로그램의 적합성이나 상업성 여부에 대한 보증을 포함한 어떠한 형태의 보증도 명시적이나 묵시적으로 설정되지 않은 ``있는 그대로의'' 상태로 이 프로그램을 배포합니다. 프로그램과 프로그램의 실행에 따라 발생할 수 있는 모든 위험은 피양도자에게 인수되며 이에 따른 보수 및 복구를 위한 제반 경비 또한 피양도자가 모두 부담해야 합니다.

제 12 조. 저작권자나 배포자가 프로그램의 손상 가능성을 사전에 알고 있었다 하더라도 발생된 손실이 관련 법규에 의해 보호되고 있거나 이에 대한 별도의 서면 보증이 설정된 경우가 아니라면, 저작권자나 프로그램을 원래의 상태 또는 개작한 상태로 제공한 배포자는 프로그램의 사용이나 비작동으로 인해 발생된 손실이나 프로그램 자체의 손실에 대해 책임지지 않습니다. 이러한 면책 조건은 사용자나 제3자가 프로그램을 조작함으로써 발생된 손실이나 다른 소프트웨어와 프로그램을 함께 동작시키는 것으로 인해서 발생된 데이터의 상실 및 부정확한 산출 결과에만 국한되는 것이 아닙니다. 발생된 손실의 일반성이나 특수성 뿐 아니라 원인의 우발성 및 필연성도 전혀 고려되지 않습니다.

복제와 개작 및 배포에 관한 조건과 규정의 끝.

새로운 프로그램에 GPL을 적용하는 방법

새로운 프로그램을 개발하고 그 프로그램이 많은 사람들에게 최대한 유용하게 사용되기를 원한다면, 본 허가서의 규정에 따라 누구나 자유롭게 개작하고 재배포할 수 있는 자유 소프트웨어로 만드는 것이 최선의 방법입니다.

프로그램을 자유 소프트웨어로 만들기 위해서는 다음과 같은 사항을 프로그램에 추가하면 됩니다. 프로그램에 대한 보증이 제공되지 않는다는 사실을 가장 효과적으로 전달할 수 있는 방법은 원시 코드 파일의 시작 부분에 이러한 사항을 추가하는 것입니다. 각각의 파일에는 최소한 저작권을 명시한 행과 본 사용 허가서의 전체 내용을 참고할 수 있는 위치 정보를 명시해야 합니다.



프로그램의 이름과 용도를 한 줄 정도로 설명합니다.
Copyright (C) 20yy년 <프로그램 저작자의 이름>

이 프로그램은 자유 소프트웨어입니다. 소프트웨어의 피양도자는 자유 소프트웨어 재단이 공표한 GNU 일반 공중 사용 허가서 2판 또는 그 이후 판을 임의로 선택해서, 그 규정에 따라 프로그램을 개작하거나 재배포할 수 있습니다.

이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 대해서는 GNU 일반 공중 사용 허가서를 참고하시기 바랍니다.

GNU 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 만약, 이 문서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. (자유 소프트웨어 재단: Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA)


또한, 사용자들이 프로그램을 배포한 사람에게 전자 메일과 서면으로 연락할 수 있는 정보를 추가해야 합니다.

프로그램이 명령어 입력 방식에 의한 대화형 구조를 택하고 있다면, 프로그램이 대화형 방식으로 실행되었을 때 다음과 같은 주의 사항이 출력되어야 합니다.



Gnomovision version 69, Copyright (C) 20yy년 <프로그램 저작자의 이름>

Gnomovision 프로그램에는 제품에 대한 어떠한 형태의 보증도 제공되지 않습니다. 보다 자세한 사항은 `show w' 명령어를 실행해서 참고할 수 있습니다. 이 프로그램은 자유 소프트웨어입니다. 이 프로그램은 배포 규정을 만족시키는 조건하에서 자유롭게 재배포될 수 있습니다. 배포에 대한 규정들은 `show c' 명령어를 통해서 참고할 수 있습니다.


`show w'와 `show c'는 GPL의 해당 부분을 출력하기 위한 가상의 명령어입니다. 따라서 `show w'나 `show c'가 아닌 다른 형태를 사용해도 무방하며, 마우스 클릭이나 메뉴 방식과 같은 프로그램에 적합한 다른 형식을 사용해도 괜찮습니다.

만약, 프로그램 저작자가 학교나 기업과 같은 단체나 기관에 고용되어 있다면 프로그램의 자유로운 배포를 위해서 고용주나 해당 기관장으로부터 프로그램에 대한 저작권 포기 각서를 받아야 합니다. 예를 들면 다음과 같은 형식이 될 수 있다. (아래의 문구를 실제로 사용할 경우에는 예로 사용된 이름들을 실제 이름으로 대체하면 됩니다.)



본사는 제임스 해커가 만든 (컴파일러에서 패스를 생성하는) `Gnomovision' 프로그램에 관련된 모든 저작권을 포기합니다.

1989년 4월 1일
Yoyodye, Inc., 부사장: Ty Coon
서명: Ty Coon의 서명


GNU 일반 공중 사용 허가서는 자유 소프트웨어를 독점 소프트웨어와 함께 결합시키는 것을 허용하지 않습니다. 만약, 작성된 프로그램이 서브루틴 라이브러리일 경우에는 독점 소프트웨어가 해당 라이브러리를 링크할 수 있도록 허용하는 것이 보다 효과적으로 활용될 수 있는 방법이라고 생각할 수도 있을 것입니다. 이러한 경우에는 본 허가서 대신 GNU 라이브러리 일반 공중 사용 허가서(GNU Library General Public License)를 사용함으로써 소기의 목적을 충족시킬 수 있습니다.