4

I'm writing a Safari Extension that adds some functionality to a web page that I don't own. I would like for my injected JavaScript page to be able to fire some JavaScript functions the host page uses, but it doesn't work. On the Console I get the message ReferenceError: Can't find variable: function_name.

My script is specified to be an End Script, so the whole page should be loaded. The function is also called from an onclick() handler on the page, like so:

onclick="function_name($(this).up());"

I can get a reference to that page element, but when I call element.onclick() I get another error: TypeError: 'undefined' is not a function (evaluating '$(this).up()').

Oddly, when I call the JavaScript function from an AppleScript (do JavaScript "function_name()" in page) it works fine. How can I trigger these functions?

Dov
  • 15,530
  • 13
  • 76
  • 177

2 Answers2

2

It's not working because the extension's injected script is sandboxed; it can't see the page's global objects except for the DOM (and vice versa). A way around this security limitation is to have the injected script create a <script> element with the statements you want and insert it into the document. For example:

var myScriptElement = document.createElement('script');
myScriptElement.innerHTML = 'alert("Page is using jQuery " + $.fn.jquery)';
document.querySelector('head').appendChild(myScriptElement);

However, the inserted script will not have access to the injected script's objects either. So, for example, if you try to access the extension's safari object from the inserted script, you will get a reference error.

chulster
  • 2,809
  • 15
  • 14
  • Do I have any way of communicating with the inserted script? That's useful information and explains what's happening, but I'd really like some way around it. – Dov Jan 19 '12 at 19:05
  • You could use the DOM for a sort of communication between the injected script (the extension) and the inserted script. If you wanted to pass a value from the inserted script to the injected script, or vice versa, the first script could set an attribute on an element in the document using element.setAttribute(), and at some later time the second script could read it using element.getAttribute(). For example, you could use: document.documentElement.setAttribute('my_attr', '999') and then to retrieve the value: document.documentElement.getAttribute('my_attr') – chulster Feb 08 '12 at 04:55
  • But it would have to poll at some interval? – Dov Feb 08 '12 at 18:02
  • Depending on your goals, that would be one way. There are probably others. – chulster Feb 12 '12 at 05:20
2

I can extend answer from canisbos. You can communicate with the inserted script with the PostMessage function.

injected script:

//insert script to page
var myScriptElement = document.createElement('script'); 
myScriptElement.innerHTML =
  'window.addEventListener("message", function(e) {' +
  '  if (e.data.msg == "do") {' +
  '    foo(e.data.info);' +
  '    postMessage({msg: "done", info: "answer"}, "*");' +
  '  };' +
  '}, false);'
document.querySelector('head').appendChild(myScriptElement);

//add answers listener
window.addEventListener('message', function(e) {
  if (e.data.msg == 'done') {
    console.log(e.data.info);
  };
}, false);

//add the testing function on the body click
document.addEventListener('click', function (e) {
  //call inserted script
  postMessage({msg: 'do', info: 'from inject'}, '*');
}, false);

Test html page:

<html>
<script>
  function foo(text) {
    console.log(text);
  };
</script>
<body>
  <button id='button' onclick='foo("from page")'>test</button>
</body>
</html>
anfilat
  • 706
  • 4
  • 4