I'm learning how to create Chrome extensions. I just started developing one to catch YouTube events. I want to use it with YouTube flash player (later I will try to make it compatible with 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!");
The problem is that the console gives me the "Started!", but there is no "State Changed!" when I play/pause YouTube videos.
When this code is put in the console, it worked. What am I doing wrong?
Best Answer
Underlying cause:
Content scripts are executed in an "isolated world" environment.
Solution::
To access functions/variables of the page context ("main world") you have to inject the code into the page itself using DOM. Same thing if you want to expose your functions/variables to the page context (in your case it's the
state()
method).Note in case communication with the page script is needed:
Use DOM
CustomEvent
handler. Examples: one, two, and three.Note in case
chrome
API is needed in the page script:Since
chrome.*
APIs can't be used in the page script, you have to use them in the content script and send the results to the page script via DOM messaging (see the note above).Safety warning:
A page may redefine or augment/hook a built-in prototype so your exposed code may fail if the page did it in an incompatible fashion. If you want to make sure your exposed code runs in a safe environment then you should either a) declare your content script with "run_at": "document_start" and use Methods 2-3 not 1, or b) extract the original native built-ins via an empty iframe, example. Note that with
document_start
you may need to useDOMContentLoaded
event inside the exposed code to wait for DOM.Table of contents
Method 1: Inject another file
The only ManifestV3-compatible method at the moment. Particularly good when you have lots of code. Put the code in a file within your extension, say
script.js
. Then load it in your content script like this:The js file must be exposed in
web_accessible_resources
:manifest.json example for ManifestV2
manifest.json example for ManifestV3
If not, the following error will appear in the console:
Method 2: Inject embedded code
This method is useful when you want to quickly run a small piece of code. (See also: How to disable facebook hotkeys with Chrome extension?).
Note: template literals are only supported in Chrome 41 and above. If you want the extension to work in Chrome 40-, use:
Method 2b: Using a function
For a big chunk of code, quoting the string is not feasible. Instead of using an array, a function can be used, and stringified:
This method works, because the
+
operator on strings and a function converts all objects to a string. If you intend on using the code more than once, it's wise to create a function to avoid code repetition. An implementation might look like:Note: Since the function is serialized, the original scope, and all bound properties are lost!
Method 3: Using an inline event
Sometimes, you want to run some code immediately, e.g. to run some code before the
<head>
element is created. This can be done by inserting a<script>
tag withtextContent
(see method 2/2b).An alternative, but not recommended is to use inline events. It is not recommended because if the page defines a Content Security policy that forbids inline scripts, then inline event listeners are blocked. Inline scripts injected by the extension, on the other hand, still run. If you still want to use inline events, this is how:
Note: This method assumes that there are no other global event listeners that handle the
reset
event. If there is, you can also pick one of the other global events. Just open the JavaScript console (F12), typedocument.documentElement.on
, and pick on of the available events.Dynamic values in the injected code
Occasionally, you need to pass an arbitrary variable to the injected function. For example:
To inject this code, you need to pass the variables as arguments to the anonymous function. Be sure to implement it correctly! The following will not work:
The solution is to use
JSON.stringify
before passing the argument. Example:If you have many variables, it's worthwhile to use
JSON.stringify
once, to improve readability, as follows: