Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
136 views
in Technique[技术] by (71.8m points)

javascript - Executing code at page-level from Background.js and returning the value

I've got a web page with its own scripts and variables that I need to execute and retrieve return values from my extension's Background.js.

I understand (I think!) that in order to interact with the web page, it must be done via chrome.tabs.executeScript or a ContentScript, but because the code must execute in the context of the original page (in order to have scope to the scripts and variables), it needs to be injected into the page first.

Following this great post by Rob W, I'm able to invoke the page-level script/variables, but I'm struggling to understand how to return values in this way.

Here's what I've got so far...

Web page code (that I want to interact with):

<html>
<head>
<script>
    var favColor = "Blue";

    function getURL() {
      return window.location.href;
    }
</script>
</head>

<body>
    <p>Example web page with script content I want interact with...</p>
</body>
</html>

manifest.json:

{
  // Extension ID: behakphdmjpjhhbilolgcfgpnpcoamaa
  "name": "MyExtension",
  "version": "1.0",
  "manifest_version": 2,
  "description": "My Desc Here",
  "background": {
    "scripts": ["background.js"]
  },  
  "icons": {
    "128": "icon-128px.png"
  },
  "permissions": [
    "background",
    "tabs",
    "http://*/",
    "https://*/",
    "file://*/",           //### (DEBUG ONLY)
    "nativeMessaging"
  ]
}

background.js

codeToExec = ['var actualCode = "alert(favColor)";',
                  'var script = document.createElement("script");',
                  ' script.textContent = actualCode;',
                  '(document.head||document.documentElement).appendChild(script);',
                  'script.parentNode.removeChild(script);'].join('
');
chrome.tabs.executeScript( tab.id, {code:codeToExec}, function(result) {
   console.log('Result = ' + result);
} );

I realise the code is currently just "alerting" the favColor variable (this was just a test to make sure I could see it working). However, if I ever try returning that variable (either by leaving it as the last statement or by saying "return favColor"), the executeScript callback never has the value.

So, there appear to be (at least) three levels here:

  1. background.js
  2. content scripts
  3. actual web page (containing scripts/variables)

...and I would like to know what is the recommended way to talk from level 1 to level 3 (above) and return values?

Thanks in advance :o)

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You are quite right in understanding the 3-layer context separation.

  • A background page is a separate page and therefore doesn't share JS or DOM with visible pages.
  • Content scripts are isolated from the webpage's JS context, but share DOM.
  • You can inject code into the page's context using the shared DOM. It has access to the JS context, but not to Chrome APIs.

To communicate, those layers use different methods:

Background <-> Content talk through Chrome API.
The most primitive is the callback of executeScript, but it's impractical for anything but one-liners.
The common way is to use Messaging.
Uncommon, but it's possible to communicate using chrome.storage and its onChanged event.

Page <-> Extension cannot use the same techniques.
Since injected page-context scripts do not technically differ from page's own scripts, you're looking for methods for a webpage to talk to an extension. There are 2 methods available:

  1. While pages have very, very limited access to chrome.* APIs, they can nevertheless use Messaging to contact the extension. This is achieved through "externally_connectable" method.

    I have recently described it in detail this answer. In short, if your extension declared that a domain is allowed to communicate with it, and the domain knows the extension's ID, it can send an external message to the extension.

    The upside is directly talking to the extension, but the downside is the requirement to specifically whitelist domains you're using this from, and you need to keep track of your extension ID (but since you're injecting the code, you can supply the code with the ID). If you need to use it on any domain, this is unsuitable.

  2. Another solution is to use DOM Events. Since the DOM is shared between the content script and the page script, an event generated by one will be visible to another.

    The documentation demonstrates how to use window.postMessage for this effect; using Custom Events is conceptually more clear.

    Again, I answered about this before.

    The downside of this method is the requirement for a content script to act as a proxy. Something along these lines must be present in the content script:

    window.addEventListener("PassToBackground", function(evt) {
      chrome.runtime.sendMessage(evt.detail);
    }, false);
    

    while the background script processes this with a chrome.runtime.onMessage listener.

I encourage you to write a separate content script and invoke executeScript with a file attribute instead of code, and not rely on its callback. Messaging is cleaner and allows to return data to background script more than once.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...