Chrome 확장 프로그램을 만드는 방법을 배우고 있습니다. 방금 YouTube 이벤트를 잡기 위해 하나를 개발하기 시작했습니다. YouTube 플래시 플레이어와 함께 사용하고 싶습니다 (나중에 HTML5와 호환되도록 노력할 것입니다).
manifest.json :
{
"name": "MyExtension",
"version": "1.0",
"description": "Gotta catch Youtube events!",
"permissions": ["tabs", "http://*/*"],
"content_scripts" : [{
"matches" : [ "www.youtube.com/*"],
"js" : ["myScript.js"]
}]
}
myScript.js :
function state() { console.log("State Changed!"); }
var player = document.getElementById("movie_player");
player.addEventListener("onStateChange", "state");
console.log("Started!");
문제는 콘솔이 “시작되었습니다!”라는 메시지를 표시 한다는 것 입니다. 하지만 ‘상태가 변경되었습니다!’ 는 없습니다 . YouTube 동영상을 재생 / 일시 중지 할 때
이 코드를 콘솔에 넣으면 작동했습니다. 내가 무엇을 잘못하고 있지?
답변
컨텐츠 스크립트는 “격리 된 세계”환경 에서 실행됩니다 . state()
메소드를 페이지 자체 에 주입해야 합니다.
chrome.*
스크립트 에서 API 중 하나를 사용하려면 다음 답변에 설명 된대로 특수한 이벤트 핸들러를 구현해야합니다. Chrome 확장 프로그램-Gmail의 원래 메시지 검색 .
그렇지 않으면 chrome.*
API 를 사용할 필요가 없으면 <script>
태그 를 추가하여 페이지에 모든 JS 코드를 삽입하는 것이 좋습니다 .
목차
- 방법 1 : 다른 파일 삽입
- 방법 2 : 임베디드 코드 삽입
- 방법 2b : 함수 사용
- 방법 3 : 인라인 이벤트 사용
- 삽입 된 코드의 동적 값
방법 1 : 다른 파일 삽입
이것은 코드가 많을 때 가장 쉽고 가장 좋은 방법입니다. 확장명 내의 파일에 실제 JS 코드를 포함하십시오 (예 🙂 script.js
. 그런 다음 콘텐츠 스크립트를 다음과 같이하십시오 ( Google Chome“애플리케이션 바로 가기”사용자 정의 Javascript 여기에 설명 )
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
참고 :이 방법을 사용하는 경우 주입 된 script.js
파일을 "web_accessible_resources"
섹션에 추가해야합니다 ( 예 ). 그렇지 않으면 Chrome에서 스크립트로드 를 거부 하고 콘솔에 다음 오류가 표시됩니다.
chrome-extension : // [EXTENSIONID] /script.js의로드 거부 확장 프로그램 외부의 페이지에서로드하려면 리소스가 web_accessible_resources 매니페스트 키에 나열되어 있어야합니다.
방법 2 : 임베디드 코드 삽입
이 방법은 작은 코드를 빠르게 실행하려는 경우에 유용합니다. (또한 Chrome 확장 프로그램으로 페이스 북 단축키를 비활성화하는 방법은 무엇입니까? ).
var actualCode = `// Code here.
// If you want to use a variable, use $ and curly braces.
// For example, to use a fixed random number:
var someFixedRandomValue = ${ Math.random() };
// NOTE: Do not insert unsafe variables in this way, see below
// at "Dynamic values in the injected code"
`;
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
참고 : 템플릿 리터럴 은 Chrome 41 이상에서만 지원됩니다. 확장 프로그램이 Chrome 40에서 작동하게하려면 다음을 사용하십시오.
var actualCode = ['/* Code here. Example: */' + 'alert(0);',
'// Beware! This array have to be joined',
'// using a newline. Otherwise, missing semicolons',
'// or single-line comments (//) will mess up your',
'// code ----->'].join('\n');
방법 2b : 함수 사용
큰 코드 덩어리의 경우 문자열을 인용하는 것이 가능하지 않습니다. 배열을 사용하는 대신 함수를 사용하고 문자열화할 수 있습니다.
var actualCode = '(' + function() {
// All code is executed in a local scope.
// For example, the following does NOT overwrite the global `alert` method
var alert = null;
// To overwrite a global variable, prefix `window`:
window.alert = null;
} + ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
+
문자열과 함수 의 연산자는 모든 객체를 문자열로 변환 하기 때문에이 방법이 작동 합니다. 코드를 두 번 이상 사용하려는 경우 코드 반복을 피하는 함수를 작성하는 것이 좋습니다. 구현은 다음과 같습니다.
function injectScript(func) {
var actualCode = '(' + func + ')();'
...
}
injectScript(function() {
alert("Injected script");
});
참고 : 함수가 직렬화되므로 원래 범위와 모든 바인딩 된 속성이 손실됩니다!
var scriptToInject = function() {
console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output: "undefined"
방법 3 : 인라인 이벤트 사용
경우에 따라 <head>
요소를 만들기 전에 일부 코드를 실행하는 등 일부 코드를 즉시 실행하려는 경우가 있습니다 . <script>
태그를 삽입하여 수행 할 수 있습니다 textContent
(방법 2 / 2b 참조).
대안 이지만 권장되지 는 않지만 인라인 이벤트를 사용하는 것입니다. 페이지가 인라인 스크립트를 금지하는 컨텐츠 보안 정책을 정의하면 인라인 이벤트 리스너가 차단되므로 권장하지 않습니다. 반면, 확장에 의해 주입 된 인라인 스크립트는 여전히 실행됩니다. 인라인 이벤트를 계속 사용하려면 다음과 같이하십시오.
var actualCode = '// Some code example \n' +
'console.log(document.documentElement.outerHTML);';
document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');
참고 :이 메소드는 이벤트를 처리하는 다른 글로벌 이벤트 리스너가 없다고 가정합니다 reset
. 있는 경우 다른 글로벌 이벤트 중 하나를 선택할 수도 있습니다. JavaScript 콘솔 (F12)을 열고을 입력 document.documentElement.on
한 후 사용 가능한 이벤트를 선택하십시오.
삽입 된 코드의 동적 값
간혹 주입 된 함수에 임의의 변수를 전달해야합니다. 예를 들면 다음과 같습니다.
var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
alert(GREETING + NAME);
};
이 코드를 삽입하려면 변수를 인수로 익명 함수에 전달해야합니다. 올바르게 구현하십시오! 다음은 작동 하지 않습니다 .
var scriptToInject = function (GREETING, NAME) { ... };
var actualCode = '(' + scriptToInject + ')(' + GREETING + ',' + NAME + ')';
// The previous will work for numbers and booleans, but not strings.
// To see why, have a look at the resulting string:
var actualCode = "(function(GREETING, NAME) {...})(Hi, I'm ,Rob)";
// ^^^^^^^^ ^^^ No string literals!
해결책은 JSON.stringify
인수를 전달하기 전에 사용 하는 것입니다. 예:
var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
변수가 많은 경우 JSON.stringify
다음과 같이 가독성을 높이기 위해 한 번만 사용하는 것이 좋습니다 .
...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
답변
유일한 것 잃어버린 Rob W의 탁월한 답변에 숨겨진 것은 삽입 된 페이지 스크립트와 컨텐츠 스크립트 사이의 통신 방법입니다.
수신 측 (콘텐츠 스크립트 또는 삽입 된 페이지 스크립트)에서 이벤트 리스너를 추가하십시오.
document.addEventListener('yourCustomEvent', function (e) {
var data = e.detail;
console.log('received', data);
});
이니시에이터 측 (콘텐츠 스크립트 또는 삽입 된 페이지 스크립트)에서 이벤트를 보내십시오.
var data = {
allowedTypes: 'those supported by structured cloning, see the list below',
inShort: 'no DOM elements or classes/functions',
};
document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));
노트:
- DOM 메시징은 구조적 복제 알고리즘을 사용하는데,이 알고리즘 은 기본 값 외에 일부 데이터 유형 만 전송할 수 있습니다 . 클래스 인스턴스, 함수 또는 DOM 요소를 보낼 수 없습니다.
-
Firefox에서 컨텐츠 스크립트의 객체 (예 : 기본 값이 아님)를 페이지 컨텍스트로 보내려면
cloneInto
(내장 함수)를 사용하여 명시 적으로 대상에 오브젝트를 복제해야합니다 . 그렇지 않으면 보안 위반 오류로 실패합니다. .document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: cloneInto(data, document.defaultView), }));
답변
또한로드 된 스크립트의 순서 문제에 직면했습니다.로드 된 스크립트는 순차적으로로드 된 스크립트를 통해 해결되었습니다. 로딩은 Rob W의 답변을 기반으로 합니다.
function scriptFromFile(file) {
var script = document.createElement("script");
script.src = chrome.extension.getURL(file);
return script;
}
function scriptFromSource(source) {
var script = document.createElement("script");
script.textContent = source;
return script;
}
function inject(scripts) {
if (scripts.length === 0)
return;
var otherScripts = scripts.slice(1);
var script = scripts[0];
var onload = function() {
script.parentNode.removeChild(script);
inject(otherScripts);
};
if (script.src != "") {
script.onload = onload;
document.head.appendChild(script);
} else {
document.head.appendChild(script);
onload();
}
}
사용 예는 다음과 같습니다.
var formulaImageUrl = chrome.extension.getURL("formula.png");
var codeImageUrl = chrome.extension.getURL("code.png");
inject([
scriptFromSource("var formulaImageUrl = '" + formulaImageUrl + "';"),
scriptFromSource("var codeImageUrl = '" + codeImageUrl + "';"),
scriptFromFile("EqEditor/eq_editor-lite-17.js"),
scriptFromFile("EqEditor/eq_config.js"),
scriptFromFile("highlight/highlight.pack.js"),
scriptFromFile("injected.js")
]);
사실, 나는 JS에 익숙하지 않으므로 더 나은 방법으로 나를 핑 (ping) 할 수 있습니다.
답변
Content script에서는 ‘onmessage’핸들러를 바인딩하는 헤드에 스크립트 태그를 추가합니다. 부스 콘텐츠 스크립트에서 onmessage 핸들러도 사용하므로 양방향 통신이 가능합니다.
크롬 문서
//Content Script
var pmsgUrl = chrome.extension.getURL('pmListener.js');
$("head").first().append("<script src='"+pmsgUrl+"' type='text/javascript'></script>");
//Listening to messages from DOM
window.addEventListener("message", function(event) {
console.log('CS :: message in from DOM', event);
if(event.data.hasOwnProperty('cmdClient')) {
var obj = JSON.parse(event.data.cmdClient);
DoSomthingInContentScript(obj);
}
});
pmListener.js는 게시물 메시지 URL 리스너입니다.
//pmListener.js
//Listen to messages from Content Script and Execute Them
window.addEventListener("message", function (msg) {
console.log("im in REAL DOM");
if (msg.data.cmnd) {
eval(msg.data.cmnd);
}
});
console.log("injected To Real Dom");
이렇게하면 CS와 Real Dom간에 양방향 통신이 가능합니다. 예를 들어 webscoket 이벤트를 수신해야하거나 메모리 변수 또는 이벤트를 수신해야하는 경우 매우 유용합니다.
답변
페이지 컨텍스트에서 코드를 실행하고 반환 된 값을 가져 오기 위해 만든 유틸리티 함수를 사용할 수 있습니다.
이것은 함수를 문자열로 직렬화하고 웹 페이지에 주입하여 수행됩니다.
이 유틸리티는 GitHub에서 사용할 수 있습니다 .
사용 예-
// Some code that exists only in the page context -
window.someProperty = 'property';
function someFunction(name = 'test') {
return new Promise(res => setTimeout(()=>res('resolved ' + name), 1200));
}
/////////////////
// Content script examples -
await runInPageContext(() => someProperty); // returns 'property'
await runInPageContext(() => someFunction()); // returns 'resolved test'
await runInPageContext(async (name) => someFunction(name), 'with name' ); // 'resolved with name'
await runInPageContext(async (...args) => someFunction(...args), 'with spread operator and rest parameters' ); // returns 'resolved with spread operator and rest parameters'
await runInPageContext({
func: (name) => someFunction(name),
args: ['with params object'],
doc: document,
timeout: 10000
} ); // returns 'resolved with params object'
답변
텍스트 대신 순수한 기능을 주입하려면 다음 방법을 사용할 수 있습니다.
function inject(){
document.body.style.backgroundColor = 'blue';
}
// this includes the function as text and the barentheses make it run itself.
var actualCode = "("+inject+")()";
document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');
그리고 매개 변수를 전달할 수 있습니다 (불행히도 개체와 배열을 문자열로 지정할 수 없습니다). 다음과 같이 맨더에 추가하십시오.
function inject(color){
document.body.style.backgroundColor = color;
}
// this includes the function as text and the barentheses make it run itself.
var color = 'yellow';
var actualCode = "("+inject+")("+color+")";