Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't access variables defined in <script> tags on the actual page. #2700

Closed
obskyr opened this issue Nov 18, 2017 · 14 comments
Closed

Can't access variables defined in <script> tags on the actual page. #2700

obskyr opened this issue Nov 18, 2017 · 14 comments

Comments

@obskyr
Copy link

obskyr commented Nov 18, 2017

With the update to Greasemonkey 4, many userscripts using jQuery seem to have been broken. In the previous versions, the following code would work on a page using jQuery in a <script> tag:

// ==UserScript==
// @name        Variable access test
// @namespace   example.com
// ==/UserScript==

$.ready(document, function() {
    console.log("Accessed jQuery successfully.")
});

However, that doesn't work anymore - instead, you just get a classic $ is not defined. That tells me Greasemonkey might be running the userscript before the <script> tag gets loaded. That's odd, however, seeing as the script still runs when document.readyState is interactive!

To be sure, though, I tried accessing jQuery after a window.addEventListener('load', ...). Lo and behold, the following exhibits a different error:

// ==UserScript==
// @name        Variable access test
// @namespace   example.com
// ==/UserScript==

window.addEventListener('load', function() {
    console.log("Before accessing jQuery")
    $;
    console.log("Accessed jQuery successfully!")
});

It neither runs nor throws an error - instead, it freezes when trying to access $. The script simply runs up to that line and no further.


Try it on any page that uses jQuery and you should get the same result. Using @require is not a solution to this problem - for some applications, you need access to the same instance of jQuery as the page. This most likely affects scripts other than jQuery, too.

@Sxderp
Copy link
Contributor

Sxderp commented Nov 18, 2017

Not really a Greasemonkey issue, more of a Firefox content script issue.
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Accessing_page_script_objects_from_content_scripts

Either assign your own window.wrappedJSObject or use unsafeWindow in which Greasemonkey has already assigned the wrapped object.

@obskyr
Copy link
Author

obskyr commented Nov 18, 2017

@Sxderp I don't know about "not really a Greasemonkey issue" - both Tampermonkey and Violentmonkey handle this very case without problems. Not only that, but this is a breaking change that isn't mentioned in any of the blog posts.

In order to work as expected with existing userscripts and to be cross-compatible with other popular userscript managers, shouldn't this be fixed?

Also... does this really explain the issue? Why would the script freeze and not just have a failed lookup?

@Sxderp
Copy link
Contributor

Sxderp commented Nov 18, 2017

I'm not sure about the freezing. Copied your code and it doesn't freeze for me.

Edit: Sorry, misunderstood the 'freeze' comment. I thought you meant Greasemonkey doesn't work anymore. It 'freezes' (stops executing) at that line because Javascript hits a use before declaration error. Pretty sure that's considered 'fatal'. As in, doesn't continue executing code.


Taking a quick look at Violentmonkey code, it looks like they inject scripts by creating <script> elements. Rather than using the tabs.executeScripts() API method.

Using that has its own drawbacks too, of course.

@obskyr
Copy link
Author

obskyr commented Nov 18, 2017

So... when Firefox runs into that line, it just silently stops executing? Without any error message? What an incredibly strange thing to do.

In any case, is it not a goal to maintain compatibility with existing userscripts? This isn't a niche feature, after all - interfacing with the page's own JavaScript is a very common thing to do.

@arantius
Copy link
Collaborator

In any case, is it not a goal to maintain compatibility with existing userscripts?

Of course it is. We're not perfect, and this is a free open source project created by volunteers with only so much free time.

At this point, I've got a plan for how to improve this, but it's waiting on Mozilla to implement a feature that would make it possible to do securely. #2549

@obskyr
Copy link
Author

obskyr commented Nov 19, 2017

@arantius I think I might've come off a bit confrontatively... Sorry about that.

I'm guessing it's a thought-out decision to prioritize security over usability here. Until the situation gets resolved, what can we do as a workaround on the script author side of things? In my specific case, I tried both var $ = window.wrappedJSObject.$; and var $= unsafeWindow.$; at the top of a script, but that gave me another error upon trying to use $(document).ready(...): Error: Permission denied to access property Symbol.toStringTag.

@Sxderp
Copy link
Contributor

Sxderp commented Nov 19, 2017

Error: Permission denied to access property Symbol.toStringTag

I don't think that's a problem of the window.wrappedJSObject.$ but rather some other object that you created in the userscript, either as a object literal or with the new constructor, has not been properly imported into scope in which you're trying to access it (probably the page's window scope).

The link above provides details on cloneInto which is waht you'll need.

@obskyr
Copy link
Author

obskyr commented Nov 19, 2017

No, I'm fairly certain the problem is with window.wrappedJSObject.$. The following fails with that very same error:

// ==UserScript==
// @name        Variable access test
// @namespace   example.com
// ==/UserScript==

var $ = window.wrappedJSObject.$;

$(document).ready(function() {});

This, however, fails silently:

// ==UserScript==
// @name        Variable access test
// @namespace   example.com
// ==/UserScript==

var $ = window.wrappedJSObject.$;

$.ready(function() {});

I've tried using cloneInto, but it doesn't help. Do you think you could provide a short example userscript which uses the page's instance of $?

@Sxderp
Copy link
Contributor

Sxderp commented Nov 19, 2017

Ah, in that case you don't have permission to access the function. You then need to use exportFunction.

// ==UserScript==
// @name         ExampleJQuery
// @version      1
// @include      *
// @grant        none
// ==/UserScript==

var unsafeWindow = window.wrappedJSObject;
var $;

// For sanity just return if we don't have the object
if (typeof unsafeWindow.$ === 'undefined') {
  console.log('No jQuery object, returning');
  return;
} else {
  $ = unsafeWindow.$;
}

// Create the function we want to export
function onReady() {
  console.log("I'm ready!");
}
// Export it. Some details on this.
// Argument 1. The function to export
// Argument 2. The scope to export it to. In general this will be window,
//             or some object in the scope. While it is valid to use
//             "window", I prefer to use "unsafeWindow" if I'm exporting
//             into that scope. I find it less confusing.
// Return      This is a reference to exported function. In general you'll
//             assign this to some property of the scope you're exporting
//             into. However, it's not always neccessary. For example, if
//             you're going to use it as a callback (like for .ready())
//             then you don't need to assign it into the exported scope.
//             But if you want it globally (or scopally) accessable then
//             you need to assign it.
let exported_onReady = exportFunction(onReady, unsafeWindow);
// OR
// unsafeWindow.onReady = exportFunction(onReady, unsafeWindow);

$(document).ready(exported_onReady);
// OR
// $(document).ready(unsafeWindow.onReady);

@obskyr
Copy link
Author

obskyr commented Nov 21, 2017

I've tried that approach now (in different configurations), and I keep getting permission errors whatever I do. It really isn't worth the hassle at this point.

It sounds harsh, but I think I'll just have to recommend people use Tampermonkey until this is out of the way. Content scripts, in their current incarnation, really don't fit the userscript use case very well at all.

@SiZapPaaiGwat
Copy link

Agree with @obskyr, just recomment my people to use tampermonkey

@AlexP11223
Copy link

+1, moved to Tampermonkey (https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/), seems like all my scripts work there.

@Eselce
Copy link
Contributor

Eselce commented Jan 6, 2018

+1, moved to Tampermonkey (https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/), seems like all my scripts work there.

This may work in the short term, and it works fine to keep scripts running, but in the long run, it could be a false decision to rely on a complete substitution. The short term success may be short sighted... ;-)

I use TM as a substitute, but there are some issues with it, I can't deny. Additionally, there may be some security issues with FF Quantum, so it works, but one should not rely on a single solution only...

@AlexP11223
Copy link

Well, I need to support TM in my scripts anyway because not everyone uses Firefox. So before I was supporting GM and TM, now it's just TM. Didn't notice any issues so far.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants